diff --git a/chains/substrate/client/client.go b/chains/substrate/client/client.go index 4bcdc260..4368563a 100644 --- a/chains/substrate/client/client.go +++ b/chains/substrate/client/client.go @@ -7,14 +7,16 @@ import ( "bytes" "context" "fmt" + "github.com/centrifuge/go-substrate-rpc-client/v4/registry/retriever" + "github.com/centrifuge/go-substrate-rpc-client/v4/scale" "github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic" "github.com/centrifuge/go-substrate-rpc-client/v4/types/extrinsic/extensions" "math/big" + "strings" "sync" "time" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/author" - "github.com/centrifuge/go-substrate-rpc-client/v4/scale" "github.com/centrifuge/go-substrate-rpc-client/v4/signature" "github.com/centrifuge/go-substrate-rpc-client/v4/types" "github.com/rs/zerolog/log" @@ -23,20 +25,22 @@ import ( ) type SubstrateClient struct { - key *signature.KeyringPair // Keyring used for signing - nonceLock sync.Mutex // Locks nonce for updates - nonce types.U32 // Latest account nonce - tip uint64 - Conn *connection.Connection - ChainID *big.Int + key *signature.KeyringPair // Keyring used for signing + nonceLock sync.Mutex // Locks nonce for updates + nonce types.U32 // Latest account nonce + tip uint64 + Conn *connection.Connection + eventRetriever retriever.EventRetriever + ChainID *big.Int } -func NewSubstrateClient(conn *connection.Connection, key *signature.KeyringPair, chainID *big.Int, tip uint64) *SubstrateClient { +func NewSubstrateClient(conn *connection.Connection, key *signature.KeyringPair, chainID *big.Int, tip uint64, eventRetriever retriever.EventRetriever) *SubstrateClient { return &SubstrateClient{ - key: key, - Conn: conn, - ChainID: chainID, - tip: tip, + key: key, + Conn: conn, + ChainID: chainID, + tip: tip, + eventRetriever: eventRetriever, } } @@ -56,7 +60,7 @@ func (c *SubstrateClient) Transact(method string, args ...interface{}) (types.Ha return types.Hash{}, nil, fmt.Errorf("failed to construct call: %w", err) } - ext := extrinsic.NewDynamicExtrinsic(&call) + ext := extrinsic.NewExtrinsic(call) // Get latest runtime version rv, err := c.Conn.RPC.State.GetRuntimeVersionLatest() @@ -87,8 +91,7 @@ func (c *SubstrateClient) Transact(method string, args ...interface{}) (types.Ha if err != nil { return types.Hash{}, nil, fmt.Errorf("submission of extrinsic failed: %w", err) } - - hash, err := DynamicExtrinsicHash(ext) + hash, err := ExtrinsicHash(ext) if err != nil { return types.Hash{}, nil, err } @@ -99,7 +102,7 @@ func (c *SubstrateClient) Transact(method string, args ...interface{}) (types.Ha return hash, sub, nil } -func (c *SubstrateClient) TrackExtrinsic(extHash types.Hash, sub *author.ExtrinsicStatusSubscription) error { +func (c *SubstrateClient) TrackExtrinsic(extHash types.Hash, sub *author.ExtrinsicStatusSubscription, extrinsicMethod string) error { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Minute*10)) defer sub.Unsubscribe() defer cancel() @@ -113,7 +116,7 @@ func (c *SubstrateClient) TrackExtrinsic(extHash types.Hash, sub *author.Extrins } if status.IsFinalized { log.Info().Str("extrinsic", extHash.Hex()).Msgf("Extrinsic is finalized in block with hash: %#x", status.AsFinalized) - return c.checkExtrinsicSuccess(extHash, status.AsFinalized) + return c.checkExtrinsicSuccess(extHash, status.AsFinalized, extrinsicMethod) } } case <-ctx.Done(): @@ -148,13 +151,13 @@ func (c *SubstrateClient) nextNonce(meta *types.Metadata) (types.U32, error) { return latestNonce, nil } -func (c *SubstrateClient) submitAndWatchExtrinsic(meta *types.Metadata, ext extrinsic.DynamicExtrinsic, opts ...extrinsic.SigningOption) (*author.ExtrinsicStatusSubscription, error) { +func (c *SubstrateClient) submitAndWatchExtrinsic(meta *types.Metadata, ext extrinsic.Extrinsic, opts ...extrinsic.SigningOption) (*author.ExtrinsicStatusSubscription, error) { err := ext.Sign(*c.key, meta, opts...) if err != nil { return nil, err } - sub, err := c.Conn.RPC.Author.SubmitAndWatchDynamicExtrinsic(ext) + sub, err := c.Conn.RPC.Author.SubmitAndWatchExtrinsic(ext) if err != nil { return nil, err } @@ -162,26 +165,17 @@ func (c *SubstrateClient) submitAndWatchExtrinsic(meta *types.Metadata, ext extr return sub, nil } -func (c *SubstrateClient) checkExtrinsicSuccess(extHash types.Hash, blockHash types.Hash) error { - block, err := c.Conn.Chain.GetBlock(blockHash) - if err != nil { - return err - } - - evts, err := c.Conn.GetBlockEvents(blockHash) +func (c *SubstrateClient) checkExtrinsicSuccess(extHash types.Hash, blockHash types.Hash, extrinsicMethod string) error { + blockEvents, err := c.eventRetriever.GetEvents(blockHash) if err != nil { return err } - for _, event := range evts { - index := event.Phase.AsApplyExtrinsic - hash, err := ExtrinsicHash(block.Block.Extrinsics[index]) - if err != nil { - return err - } - - if extHash != hash { - continue + extrinsicCallCounter := 0 + extrinsicSuccessCounter := 0 + for _, event := range blockEvents { + if strings.EqualFold(event.Name, extrinsicMethod) { + extrinsicCallCounter++ } if event.Name == events.ExtrinsicFailedEvent { @@ -191,10 +185,14 @@ func (c *SubstrateClient) checkExtrinsicSuccess(extHash types.Hash, blockHash ty return fmt.Errorf("extrinsic failed with failed handler execution") } if event.Name == events.ExtrinsicSuccessEvent { - return nil + extrinsicSuccessCounter++ } } + if extrinsicCallCounter == 1 && extrinsicSuccessCounter >= 1 { + return nil + } + return fmt.Errorf("no event found") } @@ -206,17 +204,7 @@ func (c *SubstrateClient) LatestBlock() (*big.Int, error) { return big.NewInt(int64(block.Block.Header.Number)), nil } -func ExtrinsicHash(ext types.Extrinsic) (types.Hash, error) { - extHash := bytes.NewBuffer([]byte{}) - encoder := scale.NewEncoder(extHash) - err := ext.Encode(*encoder) - if err != nil { - return types.Hash{}, err - } - return types.NewHash(extHash.Bytes()), nil -} - -func DynamicExtrinsicHash(ext extrinsic.DynamicExtrinsic) (types.Hash, error) { +func ExtrinsicHash(ext extrinsic.Extrinsic) (types.Hash, error) { extHash := bytes.NewBuffer([]byte{}) encoder := scale.NewEncoder(extHash) err := ext.Encode(*encoder) diff --git a/chains/substrate/listener/listener.go b/chains/substrate/listener/listener.go index 18d66ab9..9b9d104f 100644 --- a/chains/substrate/listener/listener.go +++ b/chains/substrate/listener/listener.go @@ -5,6 +5,7 @@ package listener import ( "context" + "github.com/centrifuge/go-substrate-rpc-client/v4/types/block" "math/big" "time" @@ -19,7 +20,7 @@ type EventHandler interface { type ChainConnection interface { GetFinalizedHead() (types.Hash, error) - GetBlock(blockHash types.Hash) (*types.SignedBlock, error) + GetBlock(blockHash types.Hash) (*block.SignedBlock, error) } type BlockStorer interface { diff --git a/chains/substrate/listener/listener_test.go b/chains/substrate/listener/listener_test.go index d9154ecd..9e01aab1 100644 --- a/chains/substrate/listener/listener_test.go +++ b/chains/substrate/listener/listener_test.go @@ -3,6 +3,7 @@ package listener_test import ( "context" "fmt" + "github.com/centrifuge/go-substrate-rpc-client/v4/types/block" "math/big" "testing" "time" @@ -69,8 +70,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_RetriesIfBlockUnavailable() { func (s *ListenerTestSuite) Test_ListenToEvents_SleepsIfBlockTooNew() { s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: 104, }, @@ -91,8 +92,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_RetriesInCaseOfHandlerFailure() // First pass s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(head.Int64()), }, @@ -102,8 +103,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_RetriesInCaseOfHandlerFailure() s.mockEventHandler.EXPECT().HandleEvents(startBlock, new(big.Int).Sub(endBlock, big.NewInt(1))).Return(fmt.Errorf("error")) // Second pass s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(head.Int64()), }, @@ -115,8 +116,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_RetriesInCaseOfHandlerFailure() s.mockBlockStorer.EXPECT().StoreBlock(endBlock, s.domainID).Return(nil) // third pass s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: 100, }, @@ -138,8 +139,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_IgnoresBlockStorerError() { // First pass s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(head.Int64()), }, @@ -151,8 +152,8 @@ func (s *ListenerTestSuite) Test_ListenToEvents_IgnoresBlockStorerError() { s.mockBlockStorer.EXPECT().StoreBlock(endBlock, s.domainID).Return(fmt.Errorf("error")) // second pass s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: 95, }, @@ -173,24 +174,24 @@ func (s *ListenerTestSuite) Test_ListenToEvents_UsesHeadAsStartBlockIfNilPassed( newHead := big.NewInt(120) s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(oldHead.Int64()), }, }, }, nil) s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(newHead.Int64()), }, }, }, nil) s.mockClient.EXPECT().GetFinalizedHead().Return(types.Hash{}, nil) - s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&types.SignedBlock{ - Block: types.Block{ + s.mockClient.EXPECT().GetBlock(gomock.Any()).Return(&block.SignedBlock{ + Block: block.Block{ Header: types.Header{ Number: types.BlockNumber(95), }, diff --git a/go.mod b/go.mod index ab36e116..48d7466f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 toolchain go1.22.2 require ( - github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240724202640-8bafff8c25ea + github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240917092335-6cd32ee625ca github.com/ethereum/go-ethereum v1.13.2 github.com/imdario/mergo v0.3.12 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 7008b650..a829f084 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlH github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240724202640-8bafff8c25ea h1:O2DsP9UvPHFXQVt5W/+i7H+KegncyVKEyO6tkZ9klUs= -github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240724202640-8bafff8c25ea/go.mod h1:k61SBXqYmnZO4frAJyH3iuqjolYrYsq79r8EstmklDY= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240917092335-6cd32ee625ca h1:fOZsrGNsIDZjHSAqUbtHG5T90HGZFe1TogncYKmr3+4= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.2-0.20240917092335-6cd32ee625ca/go.mod h1:k61SBXqYmnZO4frAJyH3iuqjolYrYsq79r8EstmklDY= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= diff --git a/mock/substrateListener.go b/mock/substrateListener.go index 676cd181..3f3395e9 100644 --- a/mock/substrateListener.go +++ b/mock/substrateListener.go @@ -12,6 +12,7 @@ import ( reflect "reflect" types "github.com/centrifuge/go-substrate-rpc-client/v4/types" + block "github.com/centrifuge/go-substrate-rpc-client/v4/types/block" gomock "go.uber.org/mock/gomock" ) @@ -39,10 +40,10 @@ func (m *MockChainConnection) EXPECT() *MockChainConnectionMockRecorder { } // GetBlock mocks base method. -func (m *MockChainConnection) GetBlock(arg0 types.Hash) (*types.SignedBlock, error) { +func (m *MockChainConnection) GetBlock(arg0 types.Hash) (*block.SignedBlock, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBlock", arg0) - ret0, _ := ret[0].(*types.SignedBlock) + ret0, _ := ret[0].(*block.SignedBlock) ret1, _ := ret[1].(error) return ret0, ret1 }