Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: Use EventRetriever when parsing events #1603

Merged
merged 8 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 137 additions & 58 deletions centchain/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (

gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4"
"github.com/centrifuge/go-substrate-rpc-client/v4/client"
"github.com/centrifuge/go-substrate-rpc-client/v4/registry"
"github.com/centrifuge/go-substrate-rpc-client/v4/registry/parser"
"github.com/centrifuge/go-substrate-rpc-client/v4/registry/retriever"
"github.com/centrifuge/go-substrate-rpc-client/v4/signature"
"github.com/centrifuge/go-substrate-rpc-client/v4/types"
"github.com/centrifuge/go-substrate-rpc-client/v4/types/codec"
Expand Down Expand Up @@ -50,15 +53,6 @@ type ExtrinsicInfo struct {
Hash types.Hash
BlockHash types.Hash
Index uint // index number of extrinsic in a block

// EventsRaw contains all the events in the given block
// if you want to filter events for an extrinsic, use the Index
EventsRaw types.EventRecordsRaw
}

// Events returns all the events occurred in a given block
func (e ExtrinsicInfo) Events(meta *types.Metadata) (events Events, err error) {
return events, e.EventsRaw.DecodeEventRecords(meta, &events)
}

//go:generate mockery --name API --structname APIMock --filename api_mock.go --inpackage
Expand Down Expand Up @@ -162,10 +156,11 @@ func (dsa *defaultSubstrateAPI) GetPendingExtrinsics() ([]types.Extrinsic, error
}

type api struct {
sapi substrateAPI
dispatcher jobs.Dispatcher
accounts map[string]uint32
accMu sync.Mutex // accMu to protect accounts
sapi substrateAPI
dispatcher jobs.Dispatcher
accounts map[string]uint32
accMu sync.Mutex
eventRetriever retriever.EventRetriever

centChainMaxRetries int
centChainRetryInterval time.Duration
Expand All @@ -177,6 +172,7 @@ func NewAPI(
dispatcher jobs.Dispatcher,
centChainMaxRetries int,
centChainRetryInterval time.Duration,
eventRetriever retriever.EventRetriever,
) API {
return &api{
sapi: sapi,
Expand All @@ -185,6 +181,7 @@ func NewAPI(
accMu: sync.Mutex{},
centChainMaxRetries: centChainMaxRetries,
centChainRetryInterval: centChainRetryInterval,
eventRetriever: eventRetriever,
}
}

Expand Down Expand Up @@ -384,8 +381,7 @@ func (a *api) getDispatcherRunnerFunc(

log.Debugf("Extrinsic %s found in block %d", txHash.Hex(), *blockNumber)

eventsRaw, err := a.checkExtrinsicEventSuccess(meta, bh, extIdx)
if err != nil {
if err := a.checkExtrinsicEventSuccess(meta, bh, extIdx); err != nil {
log.Errorf("Couldn't check extrinsic event success in block %d: %s", *blockNumber, err)

return nil, err
Expand All @@ -395,7 +391,6 @@ func (a *api) getDispatcherRunnerFunc(
Hash: txHash,
BlockHash: bh,
Index: uint(extIdx),
EventsRaw: eventsRaw,
}

return info, nil
Expand All @@ -404,73 +399,157 @@ func (a *api) getDispatcherRunnerFunc(
return fn
}

const (
ExtrinsicSuccessEventName = "System.ExtrinsicSuccess"
ExtrinsicFailedEventName = "System.ExtrinsicFailed"
DispatchErrorFieldName = "sp_runtime.DispatchError.dispatch_error"
)

func (a *api) checkExtrinsicEventSuccess(
meta *types.Metadata,
blockHash types.Hash,
extrinsicIdx int,
) (eventsRaw types.EventRecordsRaw, err error) {
key, err := types.CreateStorageKey(meta, "System", "Events")
) error {
events, err := a.eventRetriever.GetEvents(blockHash)

if err != nil {
return nil, err
return fmt.Errorf("event retrieval error: %w", err)
}

err = a.sapi.GetStorage(key, &eventsRaw, blockHash)
for _, event := range events {
switch {
case event.Name == ExtrinsicSuccessEventName &&
event.Phase.IsApplyExtrinsic &&
event.Phase.AsApplyExtrinsic == uint32(extrinsicIdx):
if err := checkSuccessfulProxyExecution(meta, events, extrinsicIdx); err != nil {
return fmt.Errorf("proxy call was not successful: %w", err)
}

return nil
case event.Name == ExtrinsicFailedEventName &&
event.Phase.IsApplyExtrinsic &&
event.Phase.AsApplyExtrinsic == uint32(extrinsicIdx):
errorID, err := registry.ProcessDecodedFieldValue[*registry.ErrorID](
event.Fields,
func(fieldIndex int, field *registry.DecodedField) bool {
return field.Name == DispatchErrorFieldName
},
getErrorIDFromDispatchError,
)

if err != nil {
return fmt.Errorf("extrinsic with index %d failed", extrinsicIdx)
}

return getMetaError(meta, errorID)
}
}

return errors.New("should not have reached this step: %v", events)
}

func getMetaError(meta *types.Metadata, errorID *registry.ErrorID) error {
metaErr, err := meta.FindError(errorID.ModuleIndex, errorID.ErrorIndex)

if err != nil {
return nil, err
return fmt.Errorf("extrinsic failed")
}

return errors.New(
"extrinsic failed with '%s - %s'",
metaErr.Name,
metaErr.Value,
)
}

func getErrorIDFromDispatchError(value any) (*registry.ErrorID, error) {
dispatchErrorFields, ok := value.(registry.DecodedFields)

if !ok {
return nil, fmt.Errorf("expected dispatch error field to be a slice of decoded fields")
}

events := Events{}
err = eventsRaw.DecodeEventRecords(meta, &events)
if len(dispatchErrorFields) != 1 {
return nil, fmt.Errorf("expected dispatch error to have one field")
}

moduleErrorFields, ok := dispatchErrorFields[0].Value.(registry.DecodedFields)

if !ok {
return nil, fmt.Errorf("expected module error fields to be a slice of decoded fields")
}

moduleIndex, err := registry.GetDecodedFieldAsType[types.U8](
moduleErrorFields,
func(fieldIndex int, field *registry.DecodedField) bool {
return field.Name == "index"
},
)

if err != nil {
return nil, err
return nil, fmt.Errorf("module index retrieval: %w", err)
}

// Check success events
for _, es := range events.System_ExtrinsicSuccess {
if es.Phase.IsApplyExtrinsic && es.Phase.AsApplyExtrinsic == uint32(extrinsicIdx) {
// Extra check for proxy calls.
if err := checkSuccessfulProxyExecution(events, meta, extrinsicIdx); err != nil {
return nil, errors.New("proxy call was not successful: %s", err)
}
errorIndex, err := registry.GetDecodedFieldAsSliceOfType[types.U8](
moduleErrorFields,
func(fieldIndex int, field *registry.DecodedField) bool {
return field.Name == "error"
},
)

return eventsRaw, nil
}
if err != nil {
return nil, fmt.Errorf("error index retrieval: %w", err)
}

// Otherwise, check failure events
for _, es := range events.System_ExtrinsicFailed {
if es.Phase.IsApplyExtrinsic && es.Phase.AsApplyExtrinsic == uint32(extrinsicIdx) {
return nil, handleDispatchError(meta, es.DispatchError, extrinsicIdx)
}
if len(errorIndex) != 4 {
return nil, fmt.Errorf("unexpected error index length")
}

return nil, errors.New("should not have reached this step: %v", events)
}
var errorIndexArray [4]types.U8

func handleDispatchError(meta *types.Metadata, dispatchError types.DispatchError, extrinsicIdx int) error {
if dispatchError.IsModule {
moduleErr := dispatchError.ModuleError
if metaErr, findErr := meta.FindError(moduleErr.Index, moduleErr.Error); findErr == nil {
return errors.New(
"extrinsic %d failed with '%s - %s'",
extrinsicIdx,
metaErr.Name,
metaErr.Value,
)
}
for i, item := range errorIndex {
errorIndexArray[i] = item
}

return errors.New("extrinsic %d failed: %v", extrinsicIdx, dispatchError)
return &registry.ErrorID{
ModuleIndex: moduleIndex,
ErrorIndex: errorIndexArray,
}, nil
}

func checkSuccessfulProxyExecution(events Events, meta *types.Metadata, extrinsicIdx int) error {
for _, event := range events.Proxy_ProxyExecuted {
if event.Phase.IsApplyExtrinsic && event.Phase.AsApplyExtrinsic == uint32(extrinsicIdx) {
if !event.Result.Ok {
return handleDispatchError(meta, event.Result.Error, extrinsicIdx)
const (
ProxyExecutedEventName = "Proxy.ProxyExecuted"
ResultFieldName = "Result.result"
ProxyExecutedExpectedLookupIndex = 40
)

func checkSuccessfulProxyExecution(meta *types.Metadata, events []*parser.Event, extrinsicIdx int) error {
for _, event := range events {
if event.Name == ProxyExecutedEventName && event.Phase.IsApplyExtrinsic && event.Phase.AsApplyExtrinsic == uint32(extrinsicIdx) {
res, err := registry.GetDecodedFieldAsType[registry.DecodedFields](event.Fields, func(fieldIndex int, field *registry.DecodedField) bool {
return field.Name == ResultFieldName
})

if err != nil {
return fmt.Errorf("result field retrieval: %w", err)
}

return nil
if len(res) != 1 {
return errors.New("result field has unexpected size")
}

if res[0].Value == nil && res[0].LookupIndex == ProxyExecutedExpectedLookupIndex {
// The DispatchResult is Ok(()).
return nil
}

errorID, err := getErrorIDFromDispatchError(res[0].Value)

if err != nil {
return errors.New("proxy execution was unsuccessful")
}

return getMetaError(meta, errorID)
}
}

Expand Down
8 changes: 2 additions & 6 deletions centchain/api_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestApi_GetBlockLatest(t *testing.T) {
assert.NotNil(t, block)
}

func TestApi_SubmitAndWatch(t *testing.T) {
func TestApi_SubmitAndWatch_ExtrinsicSuccess(t *testing.T) {
meta, err := testAPI.GetMetadataLatest()
assert.NoError(t, err)

Expand All @@ -129,12 +129,8 @@ func TestApi_SubmitAndWatch(t *testing.T) {
podOperator, err := cfgSrv.GetPodOperator()
assert.NoError(t, err)

info, err := testAPI.SubmitAndWatch(ctx, meta, call, podOperator.ToKeyringPair())
_, err = testAPI.SubmitAndWatch(ctx, meta, call, podOperator.ToKeyringPair())
assert.NoError(t, err)

events, err := info.Events(meta)
assert.NoError(t, err)
assert.True(t, len(events.System_ExtrinsicSuccess) > 1)
}

func TestApi_GetPendingExtrinsics(t *testing.T) {
Expand Down
Loading