centrifuge / go-substrate-rpc-client

Substrate RPC client for go aka GSRPC
Apache License 2.0
204 stars 179 forks source link

How to use `GetDecodedFieldAsType`? #370

Closed khssnv closed 7 months ago

khssnv commented 11 months ago

Hello. Could you tell how to use GetDecodedFieldAsType properly? For instance, an attempt to decode Balances.Deposit.who field to types.AccountID returns the following error:

decoded field value processing error: decoded field value type mismatch: expected types.AccountID, got registry.DecodedFields

At the same time there is no error with decoding Balances.Deposit.amount field to types.U128.

Please find a code sample which generates the mentioned error below.

Thank you!

main.go ```go package main import ( "fmt" gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4" "github.com/centrifuge/go-substrate-rpc-client/v4/registry" "github.com/centrifuge/go-substrate-rpc-client/v4/registry/retriever" "github.com/centrifuge/go-substrate-rpc-client/v4/registry/state" "github.com/centrifuge/go-substrate-rpc-client/v4/types" ) // Event type definition in substrate: // https://github.com/paritytech/polkadot-sdk/blob/a250652/substrate/frame/balances/src/lib.rs#L360 type Deposit struct { Who types.AccountID Amount types.U128 } func main() { api, _ := gsrpc.NewSubstrateAPI("ws://localhost:9944") eventsRetriever, _ := retriever.NewDefaultEventRetriever( state.NewEventProvider(api.RPC.State), api.RPC.State, ) sub, _ := api.RPC.Chain.SubscribeNewHeads() defer sub.Unsubscribe() for header := range sub.Chan() { blockHash, _ := api.RPC.Chain.GetBlockHash(uint64(header.Number)) events, _ := eventsRetriever.GetEvents(blockHash) for _, event := range events { if event.Name == "Balances.Deposit" { who, err := registry.GetDecodedFieldAsType[types.AccountID]( event.Fields, func(fieldIndex int, field *registry.DecodedField) bool { return fieldIndex == 0 }, ) if err != nil { fmt.Printf("Balances.Deposit.Who: %s\n", err.Error()) } fmt.Printf("Who: %s \n", who) amount, err := registry.GetDecodedFieldAsType[types.U128]( event.Fields, func(fieldIndex int, field *registry.DecodedField) bool { return fieldIndex == 1 }, ) if err != nil { fmt.Printf("Amount parsing err: %s\n", err.Error()) } fmt.Printf("Amount: %s \n", amount) deposit := &Deposit{ Who: who, Amount: amount, } fmt.Printf("Deposit: %v \n", deposit) } } } } ``` ```console $ go run main.go Balances.Deposit.Who: decoded field value processing error: decoded field value type mismatch: expected types.AccountID, got registry.DecodedFields Who: Amount: 195024070 Deposit: &{[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 195024070} Balances.Deposit.Who: decoded field value processing error: decoded field value type mismatch: expected types.AccountID, got registry.DecodedFields Who: Amount: 195024070 Deposit: &{[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 195024070} ```
Dancia commented 8 months ago

+1 How did you solve this one?

cdamian commented 8 months ago

Hey there,

One option would be to use the following:

...
who, err := registry.ProcessDecodedFieldValue[*types.AccountID](
    event.Fields,
    func(fieldIndex int, field *registry.DecodedField) bool {
        return field.Name == "sp_core.crypto.AccountId32.who"
    },
    func(value any) (*types.AccountID, error) {
        fields, ok := value.(registry.DecodedFields)

        if !ok {
            return nil, fmt.Errorf("unexpected value: %v", value)
        }

        accByteSlice, err := registry.GetDecodedFieldAsSliceOfType[types.U8](fields, func(fieldIndex int, field *registry.DecodedField) bool {
            return fieldIndex == 0
        })

        if err != nil {
            return nil, err
        }

        var accBytes []byte

        for _, accByte := range accByteSlice {
            accBytes = append(accBytes, byte(accByte))
        }

        return types.NewAccountID(accBytes)
    },
)
...

Alternatively, you could use a field override for the slice of bytes that we manually convert above, as follows:

...
 // 1 is the lookup index for the `[u8; 32]` field on Centrifuge chain, on your chain it might differ, make sure to double-check this.

retriever, err := NewDefaultEventRetriever(state.NewEventProvider(api.RPC.State), api.RPC.State, registry.FieldOverride{
    FieldLookupIndex: 1,
    FieldDecoder:     &registry.ValueDecoder[types.AccountID]{},
})
...

who, err := registry.ProcessDecodedFieldValue[types.AccountID](
    event.Fields,
    func(fieldIndex int, field *registry.DecodedField) bool {
        return field.Name == "sp_core.crypto.AccountId32.who"
    },
    func(value any) (types.AccountID, error) {
        fields, ok := value.(registry.DecodedFields)

        if !ok {
            return types.AccountID{}, fmt.Errorf("unexpected value: %v", value)
        }

        return registry.GetDecodedFieldAsType[types.AccountID](fields, func(fieldIndex int, field *registry.DecodedField) bool {
            return fieldIndex == 0
        })
    },
)
cdamian commented 8 months ago

@khssnv @Dancia Can I go ahead and close this or do you still need support here?

khssnv commented 8 months ago

Thank you @cdamian, your solution works for me. Could you also tell is there a general rule to decide when to use ProcessDecodedFieldValue as you show for AccountID and when an easier GetDecodedFieldAsType should work like it works for the U128 from my initial message?

cdamian commented 7 months ago

@khssnv GetDecodedFieldAsType should be used as a helper func in ProcessDecodedFieldValue, we do something similar here.