Decoding ethereum transaction input function with Golang (go-ethereum in 2023) - go

I am trying to decode the input data for an ethereum transaction in Golang using the go-ethereum package. I have looked on here, ethereum stack exchange and the Go-ethereum documentation in order to try and help. I've even asked ChatGPT and it can't fix it either.
The error message I receive stems from the rlp package when calling the rlp.DecodeBytes function. The error message received is: rlp: expected input list for []main.SwapFunction.
Here is the transaction: https://etherscan.io/tx/0xd918b14504b82d4c5334a751e0439826e682a57c6813f16a707801f89f589b90
And here are my types in my code which correspond to the function being called in the transaction:
package main
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
)
type SwapDescription struct {
SrcToken common.Address
DstToken common.Address
SrcReceiver common.Address
DstReceiver common.Address
Amount *big.Int
MinReturnAmount *big.Int
GuaranteedAmount *big.Int
Flags *big.Int
Referrer common.Address
Permit []byte
}
type CallDescription struct {
TargetWithMandatory *big.Int
GasLimit *big.Int
Value *big.Int
Data []byte
}
type SwapFunction struct {
Caller common.Address
Desc SwapDescription
Calls []CallDescription
}
Here is my code to try and decode the input data into the SwapFunction struct in order to access the fields:
func main() {
// Connect to Ethereum network
client, err := ethclient.Dial("https://eth-mainnet.alchemyapi.io/jsonrpc/MY-API-KEY")
if err != nil {
fmt.Println("Error connecting to Ethereum network:", err)
return
}
// Define the transaction hash
txHash := "0xd918b14504b82d4c5334a751e0439826e682a57c6813f16a707801f89f589b90"
hash := common.HexToHash(txHash)
tx, isPending, err := client.TransactionByHash(context.Background(), hash)
if err != nil {
fmt.Println(err)
return
}
if isPending {
fmt.Println("Transaction is pending")
return
}
input := tx.Data()
var functionSignature string
if len(input) >= 4 {
functionSignature = string(input[:4])
}
fmt.Println("Function signature:", functionSignature)
var decodedData []SwapFunction
fmt.Println(decodedData)
err = rlp.DecodeBytes(input[4:], &decodedData)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Decoded data:", decodedData)
}
Please can someone show me how to fix the error "rlp: expected input list for []main.SwapFunction?
I have tried changing the decodedData variable to a single SwapFunction and then in the code above, to a slice of SwapFunction.
I am not sure why this error is happening and the documentation in rlp is relatviely vague around this area. I have read the following page: https://pkg.go.dev/github.com/ethereum/go-ethereum#v1.10.26/rlp.
Someone please help as I am at a roadblock. Thank you for anyone who helps and apologies if I have missed anything.

Related

Error while trying to fetch queryresult.KV object in JSON.Unmarshal

I am a little bit confused here and although I have searched a lot on this, something is clearly missing from my knowledge and I am asking your help.
I have created a Hyperledger Fabric Network and installed a chaincode in it. And I want to make a function that retrieves all the World State inputs about the Keys. I have done it already with the bytes.Buffer and it worked. But what I want to do is to do it with a struct.
So, I created the following struct that has only the key:
type WSKeys struct {
Key string `json: "key"`
Namespace string `json: "Namespace"`
}
And this is my code function:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArrayStr []WSKeys
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
var qry_key_json WSKeys
json.Unmarshal([]byte(queryResponse), &qry_key_json)
keyArray = append(keyArray, qry_key_json)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
When executing the above I get the following error:
cannot convert queryResponse (type *queryresult.KV) to type []byte
I can get the results correctly if I, for example do this:
func (s *SmartContract) getAllWsDataStruct(APIstub shim.ChaincodeStubInterface , args []string) sc.Response {
var keyArray []string
resultsIterator, err := APIstub.GetQueryResult("{\"selector\":{\"_id\":{\"$ne\": null }} }")
if err != nil {
return shim.Error("Error occured when trying to fetch data: "+err.Error())
}
for resultsIterator.HasNext() {
// Get the next record
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
fmt.Println(queryResponse)
keyArray = append(keyArray, queryResponse.Key)
}
defer resultsIterator.Close()
all_bytes, _ := json.Marshal(keyArray)
fmt.Println(keyArray)
return shim.Success(all_bytes)
}
But, why I get the above error when trying to add the queryResponse into a custom struct?
Do I need to add it to a struct that is only its type?
Please someone can explain what I am missing here?
The error statement is verbose enough to indicate, that your []byte conversion failed for the type queryResponse which, with a bit of lookup seems to be a struct type. In Go you cannot natively convert a struct instance to its constituent bytes without encoding using gob or other means.
Perhaps your intention was to use the Key record in the struct for un-marshalling
json.Unmarshal([]byte(queryResponse.Key), &qry_key_json)

Extra data in buffer error when decoding with gob - golang

I'm decoding with gob several objects fetched from a key/value database called "bitcask".
When I try to decode all of them one by one I get the "extra data in buffer" error from the gob decoder but exclusively for the first element that was added in the database and only after I've already fetched them at least once.
What am I doing wrong?
// Just the nest of all the bugs.
type Nest struct {
buf bytes.Buffer
dec *gob.Decoder
enc *gob.Encoder
db *bitcask.Bitcask
sync.Mutex
}
// Saves the bug into the nest.
func (n *Nest) Put(id int64, b Bug) error {
n.Lock()
defer n.Unlock()
if err := n.enc.Encode(b); err != nil {
return err
}
defer n.buf.Reset()
return n.db.Put(itosl(id), n.buf.Bytes())
}
// Retrieves a bug from the nest.
func (n *Nest) Get(id int64) (Bug, error) {
var bg Bug
b, err := n.db.Get(itosl(id))
if err != nil {
return bg, err
}
n.Lock()
defer n.Unlock()
if _, err = n.buf.Write(b); err != nil {
return bg, err
}
defer n.buf.Reset()
return bg, n.dec.Decode(&bg) // error extra data in buffer
}
Note: the "Get" function get's called for every ID in the database everytime the API endpoint is called.
The structs I'm encoding/decoding are the following:
type Comment struct {
Date int64 `json:"date"`
Text string `json:"text"`
Author string `json:"author"`
}
type Bug struct {
Id int64 `json:"id"`
Body string `json:"body"`
Open bool `json:"open"`
Tags []string `json:"tags"`
Date int64 `json:"date"`
Comments []Comment `json:"comments"`
Author string `json:"author"`
}
Also when I try to use a new decoder and buffer each time the "Get" function gets called (as I've seen in an answer to a similar question) the decode operation results in the following error: gob: unknown type id or corrupted data.
Refer to this link for the complete source code:
https://github.com/NicoNex/ladybug

how to use protobuf.any in golang

I'm using grpc in my go project. Below is code:
example.proto:
syntax = "proto3";
message Example {
string message = 1;
google.protobuf.Any details = 2;
}
main.go
func logMessage (m string, d interface{}) {
message := & example.message{
message: m,
details: ??
}
log(&message)
}
But I'm not sure how to deal with the details(interface{}) field. I know I can use any type for interface, but not sure how to use it here. Anyone can help? Thanks
Since protobuf/ptypes is deprecated, it worth using anypb.UnmarshalTo
import (
"google.golang.org/protobuf/types/known/anypb"
"github.com/golang/protobuf/ptypes/any"
)
func Unmarshal(data *any.Any) (*YourMessage, err) {
var m YourMessage
err := anypb.UnmarshalTo(data, &m, proto.UnmarshalOptions{})
return &m,err
}
The protobuf/ptypes package has utilities to convert to/from arbitrary proto messages to any:
MarshalAny:
func MarshalAny(m proto.Message) (*anypb.Any, error)
MarshalAny marshals the given message m into an anypb.Any message.
UnmarshalAny:
func UnmarshalAny(any *anypb.Any, m proto.Message) error
UnmarshalAny unmarshals the encoded value contained in the anypb.Any message into the provided message m. It returns an error if the target message does not match the type in the Any message or if an unmarshal error occurs.
In your example, you would use something along the lines of:
func logMessage (m string, d proto.Message) {
details, err := ptypes.MarshalAny(d)
if err != nil {
panic(err)
}
message := & example.message{
message: m,
details: details
}
log(&message)
}
func pbany(v interface{}) (*anypb.Any, error) {
pv, ok := v.(proto.Message)
if !ok {
return &anypb.Any{}, fmt.Errorf("%v is not proto.Message", pv)
}
return anypb.New(pv)
use anypb.New api, In your code, pass d to pbany function
func interfaceToAny(v interface{}) (*anypb.Any, error) {
bytes, err := json.Marshal(v)
if err != nil {
println("error json.Marshal interfaceToAny")
return nil, err
}
m := api.Bytes{B: bytes}
return anypb.New(&m)
}
message Bytes {
bytes b = 1;
}

How to convert interface to custom type

I am trying to parse out a specific field from a json string, and currently I have the following code snippet.
package main
import (
"encoding/json"
"fmt"
)
type PlatformID string
type Map map[string]interface{}
func Str2Map(str string) (Map, error) {
var dictionary Map
bytes := []byte(str)
err := json.Unmarshal(bytes, &dictionary)
if err != nil {
fmt.Println(err)
}
return dictionary, err
}
func parsePlatformID(str string) (PlatformID, error) {
fmt.Println(str)
dict, err := Str2Map(str)
fmt.Println(dict)
return dict["platform-id"].(PlatformID), err
}
func main() {
PlatformDictStr := "{\"platform-id\":\"platform_BnjliXLEUV26\",\"platform-labels\":\"test\",\"OptimizeScheme\":\"None\"}"
fmt.Println(PlatformDictStr)
ID, _ := parsePlatformID(PlatformDictStr)
fmt.Println(ID)
}
When I try to run it, it gives me the following error
{"platform-id":"platform_BnjliXLEUV26","platform-labels":"test","OptimizeScheme":"None"}
{"platform-id":"platform_BnjliXLEUV26","platform-labels":"test","OptimizeScheme":"None"}
map[platform-id:platform_BnjliXLEUV26 platform-labels:test OptimizeScheme:None]
panic: interface conversion: interface is string, not main.PlatformID
goroutine 1 [running]:
panic(0x126aa0, 0x10532300)
/usr/local/go/src/runtime/panic.go:500 +0x720
main.parsePlatformID(0x13e6fe, 0x58, 0x0, 0x0, 0x0, 0x0)
/tmp/sandbox256874711/main.go:26 +0x220
main.main()
/tmp/sandbox256874711/main.go:34 +0x100
This question sort of answers why I got panic: interface conversion: interface is string
If I try to change the type assertion to string, the underlying type of PlatformID, it won't even compile tmp/sandbox325023244/main.go:26: cannot use dict["platform-id"].(string) (type string) as type PlatformID in return argument
So how should I modify the return line so that I can retrieve PlatformID?
After looking at PlatformDictStr, I think var dictionary map[string]string should do the job.
PlatformID(dict["platform-id"].(string)) can be avoided in that case. Working example here
https://play.golang.org/p/A5kiVm_XbP
After playing around with the syntax a bit more, I think I need to do both type conversion and type assertion.
So the following line solves the problem
return PlatformID(dict["platform-id"].(string)), err
In retrospect, I need to first assert the interface type to a base type string, and from there I can just do a type conversion to PlatformID
PS 1: The use case is that I got the raw string in the request body, REST layer will parse out certain fields in a dictionary, then forward the rest of unparsed string to API layer for further processing. The keys in dictionary vary depends on workload, so I can't really Unmarshal it to a well defined struct.
Is there any specific reason for which you have created types for string and map? If not, I think you are over-engineering a simple use-case. The following works fine:
package main
import (
"encoding/json"
"fmt"
)
func Str2Map(str string) (map[string]interface{}, error) {
var dictionary map[string]interface{}
bytes := []byte(str)
err := json.Unmarshal(bytes, &dictionary)
if err != nil {
fmt.Println(err)
}
return dictionary, err
}
func parsePlatformID(str string) (string, error) {
fmt.Println(str)
dict, err := Str2Map(str)
fmt.Println(dict)
return dict["platform-id"].(string), err
}
func main() {
PlatformDictStr := "{\"platform-id\":\"platform_BnjliXLEUV26\",\"platform-labels\":\"test\",\"OptimizeScheme\":\"None\"}"
fmt.Println(PlatformDictStr)
ID, _ := parsePlatformID(PlatformDictStr)
fmt.Println(ID)
}
Working Playground example: https://play.golang.org/p/mpPpDmiz7x

Gob Decoder Returning EOF Error

I am attempting to implement an interface based message queue where jobs are pushed as bytes to a redis queue. But I keep receiving an EOF error when attempting to decode the byte stream.
https://play.golang.org/p/l9TBvcn9qg
Could someone point me in the right direction?
Thank you!
In your Go Playground example, you're trying to encode an interface and interfaces don't have a concrete implementation. If you remove the interface from your A struct, that should work. Like the following:
package main
import "fmt"
import "encoding/gob"
import "bytes"
type testInterface interface{}
type A struct {
Name string
Interface *B // note this change here
}
type B struct {
Value string
}
func main() {
var err error
test := &A {
Name: "wut",
Interface: &B{Value: "BVALUE"},
}
buf := bytes.NewBuffer([]byte{})
enc := gob.NewEncoder(buf)
dec := gob.NewDecoder(buf)
// added error checking as per Mark's comment
err = enc.Encode(test)
if err != nil {
panic(err.Error())
}
result := &A{}
err := dec.Decode(result)
fmt.Printf("%+v\n", result)
fmt.Println("Error is:", err)
fmt.Println("Hello, playground")
}
Also, just as a side note you will see some sort of output like the following: &{Name:wut Interface:0x1040a5a0} because A is referencing a reference to a B struct. To clean that up further:
type A struct{
Name string
Interface B // no longer a pointer
}
func main() {
// ...
test := &A{Name: "wut", Interface: B{Value: "BVALUE"}}
// ...
}
Found the answer to the problem from Mark above. I have forgotten to do a gob.Register(B{})
https://play.golang.org/p/7rQDHvMhD7

Resources