Skip to content

Commit

Permalink
lib: Add extrinsic decoder, adjust tests, types, and rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
cdamian committed Sep 4, 2024
1 parent 9859058 commit 6672dc6
Show file tree
Hide file tree
Showing 50 changed files with 2,029 additions and 5,085 deletions.
8 changes: 1 addition & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,10 @@ test-dockerized: run-substrate-docker
run-substrate-docker: ## starts the Substrate Docker image
@docker-compose up -d substrate

generate-test-data: ## generate data for types decode test
@go generate -tags=types_test ./types/test/...

test-types-decode: ## run tests for types decode
@go test -tags=types_test ./types/test/...

generate-mocks: ## generate mocks
@docker run -v `pwd`:/app -w /app --entrypoint /bin/sh vektra/mockery:v2.13.0-beta.1 -c 'go generate ./...'

help: ## shows this help
@sed -ne '/@sed/!s/## //p' $(MAKEFILE_LIST)

.PHONY: install-deps lint lint-fix test test-cover test-dockerized run-substrate-docker clean generate-test-data
.PHONY: clean lint lint-fix test test-cover test-dockerized run-substrate-docker generate-mocks
4 changes: 2 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func Example_makeASimpleTransfer() {
}

// Create the extrinsic
ext := extrinsic.NewExtrinsic(&c)
ext := extrinsic.NewExtrinsic(c)

genesisHash, err := api.RPC.Chain.GetBlockHash(0)
if err != nil {
Expand Down Expand Up @@ -427,7 +427,7 @@ func Example_transactionWithEvents() {
}

// Create the extrinsic
ext := extrinsic.NewExtrinsic(&c)
ext := extrinsic.NewExtrinsic(c)

genesisHash, err := api.RPC.Chain.GetBlockHash(0)
if err != nil {
Expand Down
14 changes: 5 additions & 9 deletions registry/REGISTRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ By leveraging the on-chain metadata, GSRPC is more robust to changes on types, a

This registry can be used afterwards to decode data read from live chains (events & extrinsics).

## How to parse events and its types
## How to parse events and their fields
First we instantiate the API with the client node and open a connection:
```go
testURL := "wss://fullnode.parachain.centrifuge.io" // Your endpoint
Expand Down Expand Up @@ -68,16 +68,12 @@ for _, event := range events {

## Extended Usage
Since docs get outdated fairly quick, here are links to tests that will always be up-to-date.
### Populate Call, Error & Events Registries
[Browse me](registry_test.go)
### Populate Call, Error & Events Registries, Extrinsic Decoder
[Factory tests](factory_test.go)
[Decoder tests](decoder_test.go)

### Event retriever
[TestLive_EventRetriever_GetEvents](retriever/event_retriever_live_test.go)
### Extrinsic retriever
Since chain runtimes can be customized, modifying core types such as Accounts, Signature payloads or Payment payloads, the code supports a customizable way of passing those custom types to the extrinsic retriever.

On the other hand, since a great majority of chains do not need to change these types, the tool provides a default for the most common used ones.
#### Using Chain Defaults
[TestExtrinsicRetriever_NewDefault](retriever/extrinsic_retriever_test.go#L179)
#### Using Custom core types
### Extrinsic retriever
[TestLive_ExtrinsicRetriever_GetExtrinsics](retriever/extrinsic_retriever_live_test.go)
274 changes: 160 additions & 114 deletions registry/decoder.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package registry

import (
"bytes"
"fmt"
"github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic"
"github.com/ethereum/go-ethereum/common/hexutil"

"github.com/centrifuge/go-substrate-rpc-client/v4/scale"
"github.com/centrifuge/go-substrate-rpc-client/v4/types"
"github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic"
)

// FieldDecoder is the interface implemented by all the different types that are available.
Expand Down Expand Up @@ -198,120 +201,42 @@ func (t *TypeDecoder) Decode(decoder *scale.Decoder) (DecodedFields, error) {
return decodedFields, nil
}

// DecodedExtrinsic is the type returned when an extrinsic is decoded.
type DecodedExtrinsic struct {
Version byte
DecodedFields DecodedFields
}

// IsSigned returns true if the extrinsic is signed.
func (d DecodedExtrinsic) IsSigned() bool {
return d.Version&extrinsic.BitSigned == extrinsic.BitSigned
}

const (
ExtrinsicAddressFieldName = "Address"
ExtrinsicSignatureFieldName = "Signature"
ExtrinsicExtraFieldName = "Extra"
ExtrinsicCallFieldName = "Call"
)

// ExtrinsicDecoder holds all the decoders for all the fields of an extrinsic.
type ExtrinsicDecoder struct {
Fields []*Field
}

func (d *ExtrinsicDecoder) getFieldWithName(fieldName string) (*Field, error) {
for _, field := range d.Fields {
if field.Name == fieldName {
return field, nil
}
}

return nil, ErrExtrinsicFieldNotFound.WithMsg("expected field name '%s'", fieldName)
}

func (d *ExtrinsicDecoder) decodeField(fieldName string, decoder *scale.Decoder) (*DecodedField, error) {
addressField, err := d.getFieldWithName(fieldName)

if err != nil {
return nil, err
}

decodedField, err := addressField.Decode(decoder)

if err != nil {
return nil, err
}

return decodedField, nil
}

// Decode is used to decode the fields of an extrinsic in the following order:
//
// 1. Address
// 2. Signature
// 3. Extra
// 4. Call
//
// NOTE - the decoding order is different from the order of the Extrinsic parameters provided in the metadata.
func (d *ExtrinsicDecoder) Decode(decoder *scale.Decoder) (*DecodedExtrinsic, error) {
if d == nil {
return nil, ErrNilExtrinsicDecoder
}

decodedExtrinsic := &DecodedExtrinsic{}

// compact length encoding (1, 2, or 4 bytes) (may not be there for Extrinsics older than Jan 11 2019)
_, err := decoder.DecodeUintCompact()

if err != nil {
return nil, ErrExtrinsicCompactLengthDecoding.Wrap(err)
// getPrimitiveDecoder parses a primitive type definition and returns a ValueDecoder.
func getPrimitiveDecoder(primitiveTypeDef types.Si0TypeDefPrimitive) (FieldDecoder, error) {
switch primitiveTypeDef {
case types.IsBool:
return &ValueDecoder[bool]{}, nil
case types.IsChar:
return &ValueDecoder[byte]{}, nil
case types.IsStr:
return &ValueDecoder[string]{}, nil
case types.IsU8:
return &ValueDecoder[types.U8]{}, nil
case types.IsU16:
return &ValueDecoder[types.U16]{}, nil
case types.IsU32:
return &ValueDecoder[types.U32]{}, nil
case types.IsU64:
return &ValueDecoder[types.U64]{}, nil
case types.IsU128:
return &ValueDecoder[types.U128]{}, nil
case types.IsU256:
return &ValueDecoder[types.U256]{}, nil
case types.IsI8:
return &ValueDecoder[types.I8]{}, nil
case types.IsI16:
return &ValueDecoder[types.I16]{}, nil
case types.IsI32:
return &ValueDecoder[types.I32]{}, nil
case types.IsI64:
return &ValueDecoder[types.I64]{}, nil
case types.IsI128:
return &ValueDecoder[types.I128]{}, nil
case types.IsI256:
return &ValueDecoder[types.I256]{}, nil
default:
return nil, ErrPrimitiveTypeNotSupported.WithMsg("primitive type %v", primitiveTypeDef)
}

if err := decoder.Decode(&decodedExtrinsic.Version); err != nil {
return nil, ErrExtrinsicVersionDecoding.Wrap(err)
}

var decodedFields DecodedFields

decodedAddress, err := d.decodeField(ExtrinsicAddressFieldName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedAddress)

if decodedExtrinsic.IsSigned() {
decodedSignature, err := d.decodeField(ExtrinsicSignatureFieldName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedSignature)
}

decodedExtraField, err := d.decodeField(ExtrinsicExtraFieldName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedExtraField)

decodedCall, err := d.decodeField(ExtrinsicCallFieldName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedCall)

decodedExtrinsic.DecodedFields = decodedFields

return decodedExtrinsic, nil
}

// Field represents one field of a TypeDecoder.
Expand Down Expand Up @@ -455,3 +380,124 @@ func convertSliceToType[T any](slice []any) ([]T, error) {

return res, nil
}

// DecodedExtrinsic is the type returned when an extrinsic is decoded.
type DecodedExtrinsic struct {
Version byte
DecodedFields DecodedFields
}

// IsSigned returns true if the extrinsic is signed.
func (d DecodedExtrinsic) IsSigned() bool {
return d.Version&extrinsic.BitSigned == extrinsic.BitSigned
}

// ExtrinsicDecoder holds all the decoders for all the fields of an extrinsic.
type ExtrinsicDecoder struct {
Fields []*Field
}

func (d *ExtrinsicDecoder) getFieldWithName(fieldName string) (*Field, error) {
for _, field := range d.Fields {
if field.Name == fieldName {
return field, nil
}
}

return nil, ErrExtrinsicFieldNotFound.WithMsg("expected field name '%s'", fieldName)
}

func (d *ExtrinsicDecoder) decodeField(fieldName string, decoder *scale.Decoder) (*DecodedField, error) {
extrinsicField, err := d.getFieldWithName(fieldName)

if err != nil {
return nil, err
}

decodedField, err := extrinsicField.Decode(decoder)

if err != nil {
return nil, ErrExtrinsicFieldDecoding.Wrap(err).WithMsg("field name - '%s'", fieldName)
}

return decodedField, nil
}

func (d *ExtrinsicDecoder) DecodeHex(hexEncodedExtrinsic string) (*DecodedExtrinsic, error) {
extrinsicBytes, err := hexutil.Decode(hexEncodedExtrinsic)

if err != nil {
return nil, err
}

decoder := scale.NewDecoder(bytes.NewReader(extrinsicBytes))

return d.Decode(decoder)
}

// Decode is used to decode the fields of an extrinsic in the following order:
//
// 1. Address
// 2. Signature
// 3. Extra
// 4. Call
//
// NOTE - the decoding order is different from the order of the Extrinsic parameters provided in the metadata.
func (d *ExtrinsicDecoder) Decode(decoder *scale.Decoder) (*DecodedExtrinsic, error) {
if d == nil {
return nil, ErrNilExtrinsicDecoder
}

decodedExtrinsic := &DecodedExtrinsic{}

// compact length encoding (1, 2, or 4 bytes) (may not be there for Extrinsics older than Jan 11 2019)
_, err := decoder.DecodeUintCompact()

if err != nil {
return nil, ErrExtrinsicCompactLengthDecoding.Wrap(err)
}

if err := decoder.Decode(&decodedExtrinsic.Version); err != nil {
return nil, ErrExtrinsicVersionDecoding.Wrap(err)
}

var decodedFields DecodedFields

if decodedExtrinsic.IsSigned() {
decodedAddress, err := d.decodeField(ExtrinsicAddressName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedAddress)

decodedSignature, err := d.decodeField(ExtrinsicSignatureName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedSignature)

decodedExtraField, err := d.decodeField(ExtrinsicExtraName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedExtraField)
}

decodedCall, err := d.decodeField(ExtrinsicCallName, decoder)

if err != nil {
return nil, err
}

decodedFields = append(decodedFields, decodedCall)

decodedExtrinsic.DecodedFields = decodedFields

return decodedExtrinsic, nil
}
Loading

0 comments on commit 6672dc6

Please sign in to comment.