diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 253787dd9a..b3f4b54f34 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -23,6 +23,7 @@ jobs: matrix: packages: [ + github.com/ChainSafe/gossamer/dot/core, github.com/ChainSafe/gossamer/dot/rpc/modules, github.com/ChainSafe/gossamer/lib/babe, ] diff --git a/dot/core/helpers_test.go b/dot/core/helpers_test.go index 9ef6e994ad..3a2fdcdcbf 100644 --- a/dot/core/helpers_test.go +++ b/dot/core/helpers_test.go @@ -7,10 +7,9 @@ import ( "path/filepath" "testing" + "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/network" - "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/dot/state" - "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" @@ -21,7 +20,6 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/utils" "github.com/golang/mock/gomock" - "github.com/libp2p/go-libp2p-core/peer" "github.com/stretchr/testify/require" ) @@ -35,9 +33,7 @@ func NewTestService(t *testing.T, cfg *Config) *Service { } if cfg.DigestHandler == nil { - digestHandler := NewMockDigestHandler(ctrl) - digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) - cfg.DigestHandler = digestHandler + cfg.DigestHandler = &digest.Handler{} // only for nil check in NewService } if cfg.Keystore == nil { @@ -126,14 +122,7 @@ func NewTestService(t *testing.T, cfg *Config) *Service { cfg.BlockState.StoreRuntime(cfg.BlockState.BestBlockHash(), cfg.Runtime) if cfg.Network == nil { - net := NewMockNetwork(ctrl) - net.EXPECT().GossipMessage(gomock.AssignableToTypeOf(new(network.TransactionMessage))) - net.EXPECT().IsSynced().Return(true) - net.EXPECT().ReportPeer( - gomock.AssignableToTypeOf(peerset.ReputationChange{}), - gomock.AssignableToTypeOf(peer.ID("")), - ) - cfg.Network = net + cfg.Network = new(network.Service) // only for nil check in NewService } if cfg.CodeSubstitutes == nil { diff --git a/dot/core/messages_integration_test.go b/dot/core/messages_integration_test.go index c721cf467b..d38ed2fe3f 100644 --- a/dot/core/messages_integration_test.go +++ b/dot/core/messages_integration_test.go @@ -11,10 +11,9 @@ import ( "github.com/centrifuge/go-substrate-rpc-client/v3/signature" ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/sync" "github.com/ChainSafe/gossamer/dot/types" @@ -23,6 +22,10 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/pkg/scale" + + "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" ) func createExtrinsic(t *testing.T, rt runtime.Instance, genHash common.Hash, nonce uint64) types.Extrinsic { @@ -75,6 +78,10 @@ func TestService_HandleBlockProduced(t *testing.T) { Keystore: keystore.NewGlobalKeystore(), } + digestHandler := NewMockDigestHandler(ctrl) + digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) + cfg.DigestHandler = digestHandler + s := NewTestService(t, cfg) err := s.Start() require.NoError(t, err) @@ -130,9 +137,22 @@ func TestService_HandleTransactionMessage(t *testing.T) { telemetryMock := NewMockClient(ctrl) telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() + digestHandler := NewMockDigestHandler(ctrl) + digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) + + net := NewMockNetwork(ctrl) + net.EXPECT().GossipMessage(gomock.AssignableToTypeOf(new(network.TransactionMessage))).AnyTimes() + net.EXPECT().IsSynced().Return(true).AnyTimes() + net.EXPECT().ReportPeer( + gomock.AssignableToTypeOf(peerset.ReputationChange{}), + gomock.AssignableToTypeOf(peer.ID("")), + ).AnyTimes() + cfg := &Config{ Keystore: ks, TransactionState: state.NewTransactionState(telemetryMock), + DigestHandler: digestHandler, + Network: net, } s := NewTestService(t, cfg) diff --git a/dot/core/service.go b/dot/core/service.go index a58ce53d64..e267b5a051 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -4,6 +4,7 @@ package core import ( + "bytes" "context" "errors" "sync" @@ -20,7 +21,8 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/services" "github.com/ChainSafe/gossamer/lib/transaction" - "github.com/ChainSafe/gossamer/pkg/scale" + cscale "github.com/centrifuge/go-substrate-rpc-client/v3/scale" + ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" ) var ( @@ -372,14 +374,9 @@ func (s *Service) handleChainReorg(prev, curr common.Hash) error { for _, ext := range *body { logger.Tracef("validating transaction on re-org chain for extrinsic %s", ext) - encExt, err := scale.Marshal(ext) - if err != nil { - return err - } - - // decode extrinsic and make sure it's not an inherent. - decExt := &types.ExtrinsicData{} - if err = decExt.DecodeVersion(encExt); err != nil { + decExt := &ctypes.Extrinsic{} + decoder := cscale.NewDecoder(bytes.NewReader(ext)) + if err = decoder.Decode(&decExt); err != nil { continue } @@ -388,14 +385,15 @@ func (s *Service) handleChainReorg(prev, curr common.Hash) error { continue } - externalExt := types.Extrinsic(append([]byte{byte(types.TxnExternal)}, encExt...)) + externalExt := make(types.Extrinsic, 0, 1+len(ext)) + externalExt = append(externalExt, byte(types.TxnExternal)) + externalExt = append(externalExt, ext...) txv, err := rt.ValidateTransaction(externalExt) if err != nil { logger.Debugf("failed to validate transaction for extrinsic %s: %s", ext, err) continue } - - vtx := transaction.NewValidTransaction(encExt, txv) + vtx := transaction.NewValidTransaction(ext, txv) s.transactionState.AddToPool(vtx) } } diff --git a/dot/core/service_integration_test.go b/dot/core/service_integration_test.go index 437d8570e5..d6f860f2a1 100644 --- a/dot/core/service_integration_test.go +++ b/dot/core/service_integration_test.go @@ -6,10 +6,10 @@ package core import ( - "errors" "fmt" + "math/big" "os" - "sort" + "path/filepath" "testing" "time" @@ -19,21 +19,121 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" - runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/lib/trie" + "github.com/ChainSafe/gossamer/lib/utils" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/golang/mock/gomock" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/telemetry Client +const testSlotNumber = 21 + +func balanceKey(t *testing.T, pub []byte) (bKey []byte) { + t.Helper() + + h0, err := common.Twox128Hash([]byte("System")) + require.NoError(t, err) + bKey = append(bKey, h0...) + h1, err := common.Twox128Hash([]byte("Account")) + require.NoError(t, err) + bKey = append(bKey, h1...) + h2, err := common.Blake2b128(pub) + require.NoError(t, err) + bKey = append(bKey, h2...) + bKey = append(bKey, pub...) + return +} + +func newTestDigest(t *testing.T, slotNumber uint64) scale.VaryingDataTypeSlice { + t.Helper() + testBabeDigest := types.NewBabeDigest() + err := testBabeDigest.Set(types.BabeSecondaryPlainPreDigest{ + AuthorityIndex: 17, + SlotNumber: slotNumber, + }) + require.NoError(t, err) + data, err := scale.Marshal(testBabeDigest) + require.NoError(t, err) + vdts := types.NewDigest() + err = vdts.Add( + types.PreRuntimeDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: data, + }, + types.ConsensusDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: data, + }, + types.SealDigest{ + ConsensusEngineID: types.BabeEngineID, + Data: data, + }, + ) + require.NoError(t, err) + return vdts +} + +func generateTestValidRemarkTxns(t *testing.T, pubKey []byte, accInfo types.AccountInfo) ([]byte, runtime.Instance) { + t.Helper() + projectRootPath := filepath.Join(utils.GetProjectRootPathTest(t), "chain/gssmr/genesis.json") + gen, err := genesis.NewGenesisFromJSONRaw(projectRootPath) + require.NoError(t, err) + + genTrie, err := genesis.NewTrieFromGenesis(gen) + require.NoError(t, err) + + genState, err := rtstorage.NewTrieState(genTrie) + require.NoError(t, err) + + nodeStorage := runtime.NodeStorage{ + BaseDB: runtime.NewInMemoryDB(t), + } + cfg := &wasmer.Config{ + InstanceConfig: runtime.InstanceConfig{ + Storage: genState, + LogLvl: log.Error, + NodeStorage: nodeStorage, + }, + Imports: nil, + } + + rt, err := wasmer.NewRuntimeFromGenesis(cfg) + require.NoError(t, err) + + aliceBalanceKey := balanceKey(t, pubKey) + encBal, err := scale.Marshal(accInfo) + require.NoError(t, err) + + rt.(*wasmer.Instance).GetContext().Storage.Set(aliceBalanceKey, encBal) + // this key is System.UpgradedToDualRefCount -> set to true since all accounts have been upgraded to v0.9 format + rt.(*wasmer.Instance).GetContext().Storage.Set(common.UpgradedToDualRefKey, []byte{1}) + + genesisHeader := &types.Header{ + Number: 0, + StateRoot: genTrie.MustHash(), + } + + // Hash of encrypted centrifuge extrinsic + testCallArguments := []byte{0xab, 0xcd} + extHex := runtime.NewTestExtrinsic(t, rt, genesisHeader.Hash(), genesisHeader.Hash(), + 0, "System.remark", testCallArguments) + + extBytes := common.MustHexToBytes(extHex) + const txnType = byte(types.TxnExternal) + extBytes = append([]byte{txnType}, extBytes...) + + runtime.InitializeRuntimeToTest(t, rt, genesisHeader.Hash()) + return extBytes, rt +} + func TestMain(m *testing.M) { wasmFilePaths, err := runtime.GenerateRuntimeWasmFile() if err != nil { @@ -62,10 +162,15 @@ func TestStartService(t *testing.T) { func TestAnnounceBlock(t *testing.T) { ctrl := gomock.NewController(t) net := NewMockNetwork(ctrl) + cfg := &Config{ Network: net, } + digestHandler := NewMockDigestHandler(ctrl) + digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) + cfg.DigestHandler = digestHandler + s := NewTestService(t, cfg) err := s.Start() require.NoError(t, err) @@ -195,8 +300,8 @@ func TestService_HasKey_UnknownType(t *testing.T) { cfg := &Config{ Keystore: ks, } - s := NewTestService(t, cfg) + s := NewTestService(t, cfg) res, err := s.HasKey(kr.Alice().Public().Hex(), "xxxx") require.EqualError(t, err, "invalid keystore name") require.False(t, res) @@ -358,97 +463,96 @@ func TestHandleChainReorg_WithReorg_Transactions(t *testing.T) { } func TestMaintainTransactionPool_EmptyBlock(t *testing.T) { - // TODO: update these to real extrinsics on update to v0.9 (#904) - txs := []*transaction.ValidTransaction{ - { - Extrinsic: []byte("a"), - Validity: &transaction.Validity{Priority: 1}, - }, - { - Extrinsic: []byte("b"), - Validity: &transaction.Validity{Priority: 4}, - }, - { - Extrinsic: []byte("c"), - Validity: &transaction.Validity{Priority: 2}, - }, - { - Extrinsic: []byte("d"), - Validity: &transaction.Validity{Priority: 17}, - }, - { - Extrinsic: []byte("e"), - Validity: &transaction.Validity{Priority: 2}, + accountInfo := types.AccountInfo{ + Nonce: 0, + Data: types.AccountData{ + Free: scale.MustNewUint128(big.NewInt(1152921504606846976)), + Reserved: scale.MustNewUint128(big.NewInt(0)), + MiscFrozen: scale.MustNewUint128(big.NewInt(0)), + FreeFrozen: scale.MustNewUint128(big.NewInt(0)), }, } + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + alicePub := common.MustHexToBytes(keyring.Alice().Public().Hex()) + encExt, runtimeInstance := generateTestValidRemarkTxns(t, alicePub, accountInfo) + cfg := &Config{ + Runtime: runtimeInstance, + } ctrl := gomock.NewController(t) telemetryMock := NewMockClient(ctrl) telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() - ts := state.NewTransactionState(telemetryMock) - hashes := make([]common.Hash, len(txs)) - - for i, tx := range txs { - h := ts.AddToPool(tx) - hashes[i] = h + transactionState := state.NewTransactionState(telemetryMock) + tx := &transaction.ValidTransaction{ + Extrinsic: types.Extrinsic(encExt), + Validity: &transaction.Validity{Priority: 1}, } - - s := &Service{ - transactionState: ts, + _ = transactionState.AddToPool(tx) + + service := NewTestService(t, cfg) + service.transactionState = transactionState + + // provides is a list of transaction hashes that depend on this tx, see: + // https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/sr-primitives/src/transaction_validity.rs#L195 + provides := common.MustHexToBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d00000000") + txnValidity := &transaction.Validity{ + Priority: 39325240425794630, + Provides: [][]byte{provides}, + Longevity: 18446744073709551614, + Propagate: true, } - s.maintainTransactionPool(&types.Block{ + expectedTx := transaction.NewValidTransaction(tx.Extrinsic, txnValidity) + + service.maintainTransactionPool(&types.Block{ Body: *types.NewBody([]types.Extrinsic{}), }) - res := make([]*transaction.ValidTransaction, len(txs)) - for i := range txs { - res[i] = ts.Pop() - } - - sort.Slice(res, func(i, j int) bool { - return res[i].Extrinsic[0] < res[j].Extrinsic[0] - }) - require.Equal(t, txs, res) + resultTx := transactionState.Pop() + require.Equal(t, expectedTx, resultTx) - for _, tx := range txs { - ts.RemoveExtrinsic(tx.Extrinsic) - } - head := ts.Pop() + transactionState.RemoveExtrinsic(tx.Extrinsic) + head := transactionState.Pop() require.Nil(t, head) } func TestMaintainTransactionPool_BlockWithExtrinsics(t *testing.T) { - txs := []*transaction.ValidTransaction{ - { - Extrinsic: []byte("a"), - Validity: &transaction.Validity{Priority: 1}, - }, - { - Extrinsic: []byte("b"), - Validity: &transaction.Validity{Priority: 4}, + accountInfo := types.AccountInfo{ + Nonce: 0, + Data: types.AccountData{ + Free: scale.MustNewUint128(big.NewInt(1152921504606846976)), + Reserved: scale.MustNewUint128(big.NewInt(0)), + MiscFrozen: scale.MustNewUint128(big.NewInt(0)), + FreeFrozen: scale.MustNewUint128(big.NewInt(0)), }, } + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + alicePub := common.MustHexToBytes(keyring.Alice().Public().Hex()) + extrinsicBytes, _ := generateTestValidRemarkTxns(t, alicePub, accountInfo) ctrl := gomock.NewController(t) telemetryMock := NewMockClient(ctrl) telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes() ts := state.NewTransactionState(telemetryMock) - hashes := make([]common.Hash, len(txs)) - for i, tx := range txs { - h := ts.AddToPool(tx) - hashes[i] = h + // Maybe replace validity + tx := &transaction.ValidTransaction{ + Extrinsic: types.Extrinsic(extrinsicBytes), + Validity: &transaction.Validity{Priority: 1}, } + ts.AddToPool(tx) + s := &Service{ transactionState: ts, } s.maintainTransactionPool(&types.Block{ - Body: types.Body([]types.Extrinsic{txs[0].Extrinsic}), + Body: types.Body([]types.Extrinsic{extrinsicBytes}), }) res := []*transaction.ValidTransaction{} @@ -459,8 +563,8 @@ func TestMaintainTransactionPool_BlockWithExtrinsics(t *testing.T) { } res = append(res, tx) } - require.Equal(t, 1, len(res)) - require.Equal(t, res[0], txs[1]) + // Extrinsic is removed. so empty res + require.Empty(t, res) } func TestService_GetRuntimeVersion(t *testing.T) { @@ -477,7 +581,16 @@ func TestService_GetRuntimeVersion(t *testing.T) { } func TestService_HandleSubmittedExtrinsic(t *testing.T) { - s := NewTestService(t, nil) + cfg := &Config{} + ctrl := gomock.NewController(t) + digestHandler := NewMockDigestHandler(ctrl) + digestHandler.EXPECT().HandleDigests(gomock.AssignableToTypeOf(new(types.Header))) + cfg.DigestHandler = digestHandler + + net := NewMockNetwork(ctrl) + net.EXPECT().GossipMessage(gomock.AssignableToTypeOf(new(network.TransactionMessage))) + cfg.Network = net + s := NewTestService(t, cfg) genHeader, err := s.blockState.BestBlockHeader() require.NoError(t, err) @@ -671,8 +784,9 @@ func TestTryQueryStore_WhenThereIsDataToRetrieve(t *testing.T) { storageStateTrie.Set(testKey, testValue) require.NoError(t, err) - header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(), - common.Hash{}, 1, types.NewDigest()) + digest := newTestDigest(t, testSlotNumber) + header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(), common.Hash{}, 1, digest) + require.NoError(t, err) err = s.storageState.StoreTrie(storageStateTrie, header) @@ -701,8 +815,8 @@ func TestTryQueryStore_WhenDoesNotHaveDataToRetrieve(t *testing.T) { storageStateTrie, err := rtstorage.NewTrieState(trie.NewTrie(nil)) require.NoError(t, err) - header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(), - common.Hash{}, 1, types.NewDigest()) + digest := newTestDigest(t, testSlotNumber) + header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(), common.Hash{}, 1, digest) require.NoError(t, err) err = s.storageState.StoreTrie(storageStateTrie, header) @@ -730,10 +844,10 @@ func TestTryQueryStore_WhenDoesNotHaveDataToRetrieve(t *testing.T) { func TestTryQueryState_WhenDoesNotHaveStateRoot(t *testing.T) { s := NewTestService(t, nil) + digest := newTestDigest(t, testSlotNumber) header, err := types.NewHeader( s.blockState.GenesisHash(), - common.Hash{}, common.Hash{}, - 1, types.NewDigest()) + common.Hash{}, common.Hash{}, 1, digest) require.NoError(t, err) testBlock := &types.Block{ @@ -817,8 +931,8 @@ func createNewBlockAndStoreDataAtBlock(t *testing.T, s *Service, storageStateTrie.Set(key, value) require.NoError(t, err) - header, err := types.NewHeader(parentHash, storageStateTrie.MustRoot(), - common.Hash{}, number, types.NewDigest()) + digest := newTestDigest(t, 421) + header, err := types.NewHeader(parentHash, storageStateTrie.MustRoot(), common.Hash{}, number, digest) require.NoError(t, err) err = s.storageState.StoreTrie(storageStateTrie, header) @@ -834,115 +948,3 @@ func createNewBlockAndStoreDataAtBlock(t *testing.T, s *Service, return testBlock } - -func TestDecodeSessionKeys(t *testing.T) { - ctrl := gomock.NewController(t) - - mockInstance := new(runtimemocks.Instance) - mockInstance.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")).Return([]byte{}, nil).Once() - - mockBlockState := NewMockBlockState(ctrl) - mockBlockState.EXPECT().GetRuntime(gomock.AssignableToTypeOf(new(common.Hash))). - Return(mockInstance, nil) - - coreservice := new(Service) - coreservice.blockState = mockBlockState - - b, err := coreservice.DecodeSessionKeys([]byte{}) - - mockInstance.AssertCalled(t, "DecodeSessionKeys", []uint8{}) - - require.NoError(t, err) - require.Equal(t, b, []byte{}) -} - -func TestDecodeSessionKeys_WhenGetRuntimeReturnError(t *testing.T) { - ctrl := gomock.NewController(t) - - mockBlockState := NewMockBlockState(ctrl) - mockBlockState.EXPECT().GetRuntime(gomock.AssignableToTypeOf(new(common.Hash))). - Return(nil, errors.New("problems")) - - coreservice := new(Service) - coreservice.blockState = mockBlockState - - b, err := coreservice.DecodeSessionKeys([]byte{}) - - require.Error(t, err, "problems") - require.Nil(t, b) -} - -func TestGetReadProofAt(t *testing.T) { - keysToProof := [][]byte{[]byte("first_key"), []byte("another_key")} - mockedProofs := [][]byte{[]byte("proof01"), []byte("proof02")} - - t.Run("When Has Block Is Empty", func(t *testing.T) { - ctrl := gomock.NewController(t) - - mockedStateRootHash := common.NewHash([]byte("state root hash")) - expectedBlockHash := common.NewHash([]byte("expected block hash")) - - mockBlockState := NewMockBlockState(ctrl) - mockBlockState.EXPECT().BestBlockHash().Return(expectedBlockHash) - mockBlockState.EXPECT().GetBlockStateRoot(expectedBlockHash). - Return(mockedStateRootHash, nil) - - mockStorageStage := NewMockStorageState(ctrl) - mockStorageStage.EXPECT().GenerateTrieProof(mockedStateRootHash, keysToProof). - Return(mockedProofs, nil) - - s := &Service{ - blockState: mockBlockState, - storageState: mockStorageStage, - } - - b, p, err := s.GetReadProofAt(common.Hash{}, keysToProof) - require.NoError(t, err) - require.Equal(t, p, mockedProofs) - require.Equal(t, expectedBlockHash, b) - }) - - t.Run("When GetStateRoot fails", func(t *testing.T) { - ctrl := gomock.NewController(t) - - mockedBlockHash := common.NewHash([]byte("fake block hash")) - - mockBlockState := NewMockBlockState(ctrl) - mockBlockState.EXPECT().GetBlockStateRoot(mockedBlockHash). - Return(common.Hash{}, errors.New("problems while getting state root")) - - s := &Service{ - blockState: mockBlockState, - } - - b, p, err := s.GetReadProofAt(mockedBlockHash, keysToProof) - require.True(t, b.IsEmpty()) - require.Nil(t, p) - require.Error(t, err) - }) - - t.Run("When GenerateTrieProof fails", func(t *testing.T) { - ctrl := gomock.NewController(t) - - mockedBlockHash := common.NewHash([]byte("fake block hash")) - mockedStateRootHash := common.NewHash([]byte("state root hash")) - - mockBlockState := NewMockBlockState(ctrl) - mockBlockState.EXPECT().GetBlockStateRoot(mockedBlockHash). - Return(common.Hash{}, errors.New("problems while getting state root")) - - mockStorageStage := NewMockStorageState(ctrl) - mockStorageStage.EXPECT().GenerateTrieProof(mockedStateRootHash, keysToProof). - Return(nil, errors.New("problems to generate trie proof")) - - s := &Service{ - blockState: mockBlockState, - storageState: mockStorageStage, - } - - b, p, err := s.GetReadProofAt(mockedBlockHash, keysToProof) - require.True(t, b.IsEmpty()) - require.Nil(t, p) - require.Error(t, err) - }) -} diff --git a/dot/core/service_test.go b/dot/core/service_test.go index 2db8217a47..47eeae82f1 100644 --- a/dot/core/service_test.go +++ b/dot/core/service_test.go @@ -4,14 +4,18 @@ package core import ( + "bytes" "context" "errors" "testing" "github.com/ChainSafe/gossamer/dot/network" + testdata "github.com/ChainSafe/gossamer/dot/rpc/modules/test_data" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" mocksruntime "github.com/ChainSafe/gossamer/lib/runtime/mocks" @@ -19,6 +23,10 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/lib/trie" + "github.com/ChainSafe/gossamer/pkg/scale" + cscale "github.com/centrifuge/go-substrate-rpc-client/v3/scale" + "github.com/centrifuge/go-substrate-rpc-client/v3/signature" + ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -27,6 +35,87 @@ import ( var errTestDummyError = errors.New("test dummy error") +const ( + authoringVersion = 0 + specVersion = 25 + implVersion = 0 + transactionVersion = 0 +) + +func generateTestCentrifugeMetadata(t *testing.T) *ctypes.Metadata { + t.Helper() + metadataHex := testdata.NewTestMetadata() + rawMeta, err := common.HexToBytes(metadataHex) + require.NoError(t, err) + var decoded []byte + err = scale.Unmarshal(rawMeta, &decoded) + require.NoError(t, err) + + meta := &ctypes.Metadata{} + err = ctypes.DecodeFromBytes(decoded, meta) + require.NoError(t, err) + return meta +} + +func generateExtrinsic(t *testing.T) (extrinsic, externalExtrinsic types.Extrinsic, body *types.Body) { + t.Helper() + meta := generateTestCentrifugeMetadata(t) + + testAPIItem := runtime.APIItem{ + Name: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + Ver: 99, + } + rv := runtime.NewVersionData( + []byte("polkadot"), + []byte("parity-polkadot"), + authoringVersion, + specVersion, + implVersion, + []runtime.APIItem{testAPIItem}, + transactionVersion, + ) + + keyring, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + bobPub := keyring.Bob().Public().Hex() + + bob, err := ctypes.NewMultiAddressFromHexAccountID(bobPub) + require.NoError(t, err) + + const balanceTransfer = "Balances.transfer" + call, err := ctypes.NewCall(meta, balanceTransfer, bob, ctypes.NewUCompactFromUInt(12345)) + require.NoError(t, err) + + // Create the extrinsic + centrifugeExtrinsic := ctypes.NewExtrinsic(call) + testGenHash := ctypes.NewHash(common.Hash{}.ToBytes()) + require.NoError(t, err) + o := ctypes.SignatureOptions{ + BlockHash: testGenHash, + Era: ctypes.ExtrinsicEra{IsImmortalEra: true}, + GenesisHash: testGenHash, + Nonce: ctypes.NewUCompactFromUInt(uint64(0)), + SpecVersion: ctypes.U32(rv.SpecVersion()), + Tip: ctypes.NewUCompactFromUInt(0), + TransactionVersion: ctypes.U32(rv.TransactionVersion()), + } + + // Sign the transaction using Alice's default account + err = centrifugeExtrinsic.Sign(signature.TestKeyringPairAlice, o) + require.NoError(t, err) + + // Encode the signed extrinsic + extEnc := bytes.Buffer{} + encoder := cscale.NewEncoder(&extEnc) + err = centrifugeExtrinsic.Encode(*encoder) + require.NoError(t, err) + + encExt := []types.Extrinsic{extEnc.Bytes()} + testExternalExt := types.Extrinsic(append([]byte{byte(types.TxnExternal)}, encExt[0]...)) + testUnencryptedBody := types.NewBody(encExt) + return encExt[0], testExternalExt, testUnencryptedBody +} + func Test_Service_StorageRoot(t *testing.T) { t.Parallel() emptyTrie := trie.NewEmptyTrie() @@ -68,15 +157,15 @@ func Test_Service_StorageRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - s := tt.service + service := tt.service if tt.trieStateCall { ctrl := gomock.NewController(t) mockStorageState := NewMockStorageState(ctrl) mockStorageState.EXPECT().TrieState(nil).Return(tt.retTrieState, tt.retErr) - s.storageState = mockStorageState + service.storageState = mockStorageState } - res, err := s.StorageRoot() + res, err := service.StorageRoot() assert.ErrorIs(t, err, tt.expErr) if tt.expErr != nil { assert.EqualError(t, err, tt.expErrMsg) @@ -102,8 +191,8 @@ func Test_Service_handleCodeSubstitution(t *testing.T) { testRuntime := []byte{21} t.Run("nil value", func(t *testing.T) { t.Parallel() - s := &Service{codeSubstitute: map[common.Hash]string{}} - err := s.handleCodeSubstitution(common.Hash{}, nil, newTestInstance) + service := &Service{codeSubstitute: map[common.Hash]string{}} + err := service.handleCodeSubstitution(common.Hash{}, nil, newTestInstance) assert.NoError(t, err) }) @@ -118,11 +207,11 @@ func Test_Service_handleCodeSubstitution(t *testing.T) { ctrl := gomock.NewController(t) mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().GetRuntime(&blockHash).Return(nil, errTestDummyError) - s := &Service{ + service := &Service{ codeSubstitute: testCodeSubstitute, blockState: mockBlockState, } - execTest(t, s, blockHash, errTestDummyError) + execTest(t, service, blockHash, errTestDummyError) }) t.Run("code substitute error", func(t *testing.T) { @@ -144,12 +233,12 @@ func Test_Service_handleCodeSubstitution(t *testing.T) { mockBlockState.EXPECT().GetRuntime(&blockHash).Return(runtimeMock, nil) mockCodeSubState := NewMockCodeSubstitutedState(ctrl) mockCodeSubState.EXPECT().StoreCodeSubstitutedBlockHash(blockHash).Return(errTestDummyError) - s := &Service{ + service := &Service{ codeSubstitute: testCodeSubstitute, blockState: mockBlockState, codeSubstitutedState: mockCodeSubState, } - execTest(t, s, blockHash, errTestDummyError) + execTest(t, service, blockHash, errTestDummyError) }) t.Run("happyPath", func(t *testing.T) { @@ -172,12 +261,12 @@ func Test_Service_handleCodeSubstitution(t *testing.T) { mockBlockState.EXPECT().StoreRuntime(blockHash, gomock.Any()) mockCodeSubState := NewMockCodeSubstitutedState(ctrl) mockCodeSubState.EXPECT().StoreCodeSubstitutedBlockHash(blockHash).Return(nil) - s := &Service{ + service := &Service{ codeSubstitute: testCodeSubstitute, blockState: mockBlockState, codeSubstitutedState: mockCodeSubState, } - err := s.handleCodeSubstitution(blockHash, nil, newTestInstance) + err := service.handleCodeSubstitution(blockHash, nil, newTestInstance) assert.NoError(t, err) }) } @@ -193,8 +282,8 @@ func Test_Service_handleBlock(t *testing.T) { } t.Run("nil input", func(t *testing.T) { t.Parallel() - s := &Service{} - execTest(t, s, nil, nil, ErrNilBlockHandlerParameter) + service := &Service{} + execTest(t, service, nil, nil, ErrNilBlockHandlerParameter) }) t.Run("storeTrie error", func(t *testing.T) { @@ -211,8 +300,8 @@ func Test_Service_handleBlock(t *testing.T) { mockStorageState := NewMockStorageState(ctrl) mockStorageState.EXPECT().StoreTrie(trieState, &block.Header).Return(errTestDummyError) - s := &Service{storageState: mockStorageState} - execTest(t, s, &block, trieState, errTestDummyError) + service := &Service{storageState: mockStorageState} + execTest(t, service, &block, trieState, errTestDummyError) }) t.Run("addBlock quit error", func(t *testing.T) { @@ -231,11 +320,11 @@ func Test_Service_handleBlock(t *testing.T) { mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().AddBlock(&block).Return(errTestDummyError) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, } - execTest(t, s, &block, trieState, errTestDummyError) + execTest(t, service, &block, trieState, errTestDummyError) }) t.Run("addBlock parent not found error", func(t *testing.T) { @@ -254,11 +343,11 @@ func Test_Service_handleBlock(t *testing.T) { mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().AddBlock(&block).Return(blocktree.ErrParentNotFound) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, } - execTest(t, s, &block, trieState, blocktree.ErrParentNotFound) + execTest(t, service, &block, trieState, blocktree.ErrParentNotFound) }) t.Run("addBlock error continue", func(t *testing.T) { @@ -280,12 +369,12 @@ func Test_Service_handleBlock(t *testing.T) { mockDigestHandler := NewMockDigestHandler(ctrl) mockDigestHandler.EXPECT().HandleDigests(&block.Header) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, digestHandler: mockDigestHandler, } - execTest(t, s, &block, trieState, errTestDummyError) + execTest(t, service, &block, trieState, errTestDummyError) }) t.Run("handle runtime changes error", func(t *testing.T) { @@ -310,12 +399,12 @@ func Test_Service_handleBlock(t *testing.T) { mockDigestHandler := NewMockDigestHandler(ctrl) mockDigestHandler.EXPECT().HandleDigests(&block.Header) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, digestHandler: mockDigestHandler, } - execTest(t, s, &block, trieState, errTestDummyError) + execTest(t, service, &block, trieState, errTestDummyError) }) t.Run("code substitution ok", func(t *testing.T) { @@ -339,13 +428,13 @@ func Test_Service_handleBlock(t *testing.T) { mockDigestHandler := NewMockDigestHandler(ctrl) mockDigestHandler.EXPECT().HandleDigests(&block.Header) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, digestHandler: mockDigestHandler, ctx: context.Background(), } - execTest(t, s, &block, trieState, nil) + execTest(t, service, &block, trieState, nil) }) } @@ -360,8 +449,8 @@ func Test_Service_HandleBlockProduced(t *testing.T) { } t.Run("nil input", func(t *testing.T) { t.Parallel() - s := &Service{} - execTest(t, s, nil, nil, ErrNilBlockHandlerParameter) + service := &Service{} + execTest(t, service, nil, nil, ErrNilBlockHandlerParameter) }) t.Run("happy path", func(t *testing.T) { @@ -404,14 +493,14 @@ func Test_Service_HandleBlockProduced(t *testing.T) { mockNetwork := NewMockNetwork(ctrl) mockNetwork.EXPECT().GossipMessage(msg) - s := &Service{ + service := &Service{ storageState: mockStorageState, blockState: mockBlockState, digestHandler: mockDigestHandler, net: mockNetwork, ctx: context.Background(), } - execTest(t, s, &block, trieState, nil) + execTest(t, service, &block, trieState, nil) }) } @@ -444,11 +533,11 @@ func Test_Service_maintainTransactionPool(t *testing.T) { mockTxnState.EXPECT().PendingInPool().Return([]*transaction.ValidTransaction{vt}) mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMock, nil) - s := &Service{ + service := &Service{ transactionState: mockTxnState, blockState: mockBlockState, } - s.maintainTransactionPool(&block) + service.maintainTransactionPool(&block) }) t.Run("Validate Transaction ok", func(t *testing.T) { @@ -482,11 +571,11 @@ func Test_Service_maintainTransactionPool(t *testing.T) { mockTxnState.EXPECT().RemoveExtrinsicFromPool(types.Extrinsic{21}) mockBlockStateOk := NewMockBlockState(ctrl) mockBlockStateOk.EXPECT().GetRuntime(nil).Return(runtimeMock, nil) - s := &Service{ + service := &Service{ transactionState: mockTxnState, blockState: mockBlockStateOk, } - s.maintainTransactionPool(&block) + service.maintainTransactionPool(&block) }) } @@ -500,12 +589,12 @@ func Test_Service_handleBlocksAsync(t *testing.T) { blockAddChan := make(chan *types.Block) ctx, cancel := context.WithCancel(context.Background()) cancel() - s := &Service{ + service := &Service{ blockState: mockBlockState, blockAddCh: blockAddChan, ctx: ctx, } - s.handleBlocksAsync() + service.handleBlocksAsync() }) t.Run("channel not ok", func(t *testing.T) { @@ -515,12 +604,12 @@ func Test_Service_handleBlocksAsync(t *testing.T) { mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{}) blockAddChan := make(chan *types.Block) close(blockAddChan) - s := &Service{ + service := &Service{ blockState: mockBlockState, blockAddCh: blockAddChan, ctx: context.Background(), } - s.handleBlocksAsync() + service.handleBlocksAsync() }) t.Run("nil block", func(t *testing.T) { @@ -533,12 +622,12 @@ func Test_Service_handleBlocksAsync(t *testing.T) { blockAddChan <- nil close(blockAddChan) }() - s := &Service{ + service := &Service{ blockState: mockBlockState, blockAddCh: blockAddChan, ctx: context.Background(), } - s.handleBlocksAsync() + service.handleBlocksAsync() }) t.Run("handleChainReorg error", func(t *testing.T) { @@ -576,12 +665,710 @@ func Test_Service_handleBlocksAsync(t *testing.T) { blockAddChan <- &block close(blockAddChan) }() - s := &Service{ + service := &Service{ blockState: mockBlockState, transactionState: mockTxnStateErr, blockAddCh: blockAddChan, ctx: context.Background(), } - s.handleBlocksAsync() + service.handleBlocksAsync() + }) +} + +func TestService_handleChainReorg(t *testing.T) { + t.Parallel() + execTest := func(t *testing.T, s *Service, prevHash common.Hash, currHash common.Hash, expErr error) { + err := s.handleChainReorg(prevHash, currHash) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + } + + testPrevHash := common.MustHexToHash("0x01") + testCurrentHash := common.MustHexToHash("0x02") + testAncestorHash := common.MustHexToHash("0x03") + testSubChain := []common.Hash{testPrevHash, testCurrentHash, testAncestorHash} + + // A valid extrinsic is needed since it will be validated in handleChainReorg + ext, externExt, body := generateExtrinsic(t) + testValidity := &transaction.Validity{Propagate: true} + vtx := transaction.NewValidTransaction(ext, testValidity) + + t.Run("highest common ancestor err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(common.Hash{}, errDummyErr) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, errDummyErr) + }) + + t.Run("highest common ancestor err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(common.Hash{}, errDummyErr) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, errDummyErr) + }) + + t.Run("ancestor eq priv", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testPrevHash, nil) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, nil) + }) + + t.Run("subchain err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testAncestorHash, nil) + mockBlockState.EXPECT().SubChain(testAncestorHash, testPrevHash).Return([]common.Hash{}, errDummyErr) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, errDummyErr) + }) + + t.Run("empty subchain", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testAncestorHash, nil) + mockBlockState.EXPECT().SubChain(testAncestorHash, testPrevHash).Return([]common.Hash{}, nil) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, nil) + }) + + t.Run("get runtime err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testAncestorHash, nil) + mockBlockState.EXPECT().SubChain(testAncestorHash, testPrevHash).Return(testSubChain, nil) + mockBlockState.EXPECT().GetRuntime(nil).Return(nil, errDummyErr) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, errDummyErr) + }) + + t.Run("invalid transaction", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + runtimeMockErr := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testAncestorHash, nil) + mockBlockState.EXPECT().SubChain(testAncestorHash, testPrevHash).Return(testSubChain, nil) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMockErr, nil) + mockBlockState.EXPECT().GetBlockBody(testCurrentHash).Return(nil, errDummyErr) + mockBlockState.EXPECT().GetBlockBody(testAncestorHash).Return(body, nil) + runtimeMockErr.On("ValidateTransaction", externExt).Return(nil, errTestDummyError) + + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testPrevHash, testCurrentHash, nil) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + runtimeMockOk := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().HighestCommonAncestor(testPrevHash, testCurrentHash). + Return(testAncestorHash, nil) + mockBlockState.EXPECT().SubChain(testAncestorHash, testPrevHash).Return(testSubChain, nil) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMockOk, nil) + mockBlockState.EXPECT().GetBlockBody(testCurrentHash).Return(nil, errDummyErr) + mockBlockState.EXPECT().GetBlockBody(testAncestorHash).Return(body, nil) + runtimeMockOk.On("ValidateTransaction", externExt). + Return(testValidity, nil) + mockTxnStateOk := NewMockTransactionState(ctrl) + mockTxnStateOk.EXPECT().AddToPool(vtx).Return(common.Hash{}) + + service := &Service{ + blockState: mockBlockState, + transactionState: mockTxnStateOk, + } + execTest(t, service, testPrevHash, testCurrentHash, nil) + }) +} + +func TestServiceInsertKey(t *testing.T) { + t.Parallel() + keyStore := keystore.GlobalKeystore{ + Babe: keystore.NewBasicKeystore(keystore.BabeName, crypto.Sr25519Type), + } + + keyring, _ := keystore.NewSr25519Keyring() + aliceKeypair := keyring.Alice().(*sr25519.Keypair) + type args struct { + kp crypto.Keypair + keystoreType string + } + tests := []struct { + name string + service *Service + args args + expErr error + expErrMsg string + }{ + { + name: "ok case", + service: &Service{ + keys: &keyStore, + }, + args: args{ + kp: aliceKeypair, + keystoreType: string(keystore.BabeName), + }, + }, + { + name: "err case", + service: &Service{ + keys: &keyStore, + }, + args: args{ + kp: aliceKeypair, + keystoreType: "test", + }, + expErr: keystore.ErrInvalidKeystoreName, + expErrMsg: keystore.ErrInvalidKeystoreName.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + service := tt.service + err := service.InsertKey(tt.args.kp, tt.args.keystoreType) + assert.ErrorIs(t, err, tt.expErr) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErrMsg) + } + }) + } +} + +func TestServiceHasKey(t *testing.T) { + t.Parallel() + keyStore := keystore.GlobalKeystore{ + Babe: keystore.NewBasicKeystore(keystore.BabeName, crypto.Sr25519Type), + } + + keyring, _ := keystore.NewSr25519Keyring() + aliceKeypair := keyring.Alice().(*sr25519.Keypair) + type args struct { + pubKeyStr string + keystoreType string + } + tests := []struct { + name string + service *Service + args args + exp bool + expErr error + expErrMsg string + }{ + { + name: "ok case", + service: &Service{ + keys: &keyStore, + }, + args: args{ + pubKeyStr: aliceKeypair.Public().Hex(), + keystoreType: string(keystore.BabeName), + }, + }, + { + name: "err case", + service: &Service{ + keys: &keyStore, + }, + args: args{ + pubKeyStr: aliceKeypair.Public().Hex(), + keystoreType: "test", + }, + expErr: keystore.ErrInvalidKeystoreName, + expErrMsg: keystore.ErrInvalidKeystoreName.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + service := tt.service + res, err := service.HasKey(tt.args.pubKeyStr, tt.args.keystoreType) + assert.ErrorIs(t, err, tt.expErr) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErrMsg) + } + assert.Equal(t, tt.exp, res) + }) + } +} + +func TestService_DecodeSessionKeys(t *testing.T) { + t.Parallel() + testEncKeys := []byte{1, 2, 3, 4} + execTest := func(t *testing.T, s *Service, enc []byte, exp []byte, expErr error) { + res, err := s.DecodeSessionKeys(enc) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, exp, res) + } + + t.Run("ok case", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + runtimeMock := new(mocksruntime.Instance) + runtimeMock.On("DecodeSessionKeys", testEncKeys).Return(testEncKeys, nil) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMock, nil) + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testEncKeys, testEncKeys, nil) + }) + + t.Run("err case", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(nil, errDummyErr) + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, testEncKeys, nil, errDummyErr) + }) +} + +func TestServiceGetRuntimeVersion(t *testing.T) { + t.Parallel() + testAPIItem := runtime.APIItem{ + Name: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + Ver: 99, + } + rv := runtime.NewVersionData( + []byte("polkadot"), + []byte("parity-polkadot"), + authoringVersion, + specVersion, + implVersion, + []runtime.APIItem{testAPIItem}, + transactionVersion, + ) + emptyTrie := trie.NewEmptyTrie() + ts, err := rtstorage.NewTrieState(emptyTrie) + require.NoError(t, err) + + execTest := func(t *testing.T, s *Service, bhash *common.Hash, exp runtime.Version, expErr error) { + res, err := s.GetRuntimeVersion(bhash) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, exp, res) + } + + t.Run("get state root err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, &common.Hash{}, nil, errDummyErr) + }) + + t.Run("trie state err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(&common.Hash{}, nil) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, &common.Hash{}, nil, errDummyErr) + }) + + t.Run("get runtime err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(&common.Hash{}, nil) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(ts, nil).MaxTimes(2) + + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(&common.Hash{}).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + blockState: mockBlockState, + } + execTest(t, service, &common.Hash{}, nil, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(&common.Hash{}, nil).MaxTimes(2) + mockStorageState.EXPECT().TrieState(&common.Hash{}).Return(ts, nil).MaxTimes(2) + + runtimeMock := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(&common.Hash{}).Return(runtimeMock, nil) + runtimeMock.On("SetContextStorage", ts) + runtimeMock.On("Version").Return(rv, nil) + service := &Service{ + storageState: mockStorageState, + blockState: mockBlockState, + } + execTest(t, service, &common.Hash{}, rv, nil) + }) +} + +func TestServiceHandleSubmittedExtrinsic(t *testing.T) { + t.Parallel() + ext := types.Extrinsic{} + externalExt := types.Extrinsic(append([]byte{byte(types.TxnExternal)}, ext...)) + execTest := func(t *testing.T, s *Service, ext types.Extrinsic, expErr error) { + err := s.HandleSubmittedExtrinsic(ext) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + } + + t.Run("nil network", func(t *testing.T) { + t.Parallel() + service := &Service{} + execTest(t, service, nil, nil) + }) + + t.Run("trie state err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(nil, errDummyErr) + mockTxnState := NewMockTransactionState(ctrl) + mockTxnState.EXPECT().Exists(nil) + service := &Service{ + storageState: mockStorageState, + transactionState: mockTxnState, + net: NewMockNetwork(ctrl), + } + execTest(t, service, nil, errDummyErr) + }) + + t.Run("get runtime err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(&rtstorage.TrieState{}, nil) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(nil, errDummyErr) + mockTxnState := NewMockTransactionState(ctrl) + mockTxnState.EXPECT().Exists(nil).MaxTimes(2) + service := &Service{ + storageState: mockStorageState, + transactionState: mockTxnState, + blockState: mockBlockState, + net: NewMockNetwork(ctrl), + } + execTest(t, service, nil, errDummyErr) + }) + + t.Run("validate txn err", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(&rtstorage.TrieState{}, nil) + mockTxnState := NewMockTransactionState(ctrl) + mockTxnState.EXPECT().Exists(types.Extrinsic{}) + runtimeMockErr := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMockErr, nil).MaxTimes(2) + runtimeMockErr.On("SetContextStorage", &rtstorage.TrieState{}) + runtimeMockErr.On("ValidateTransaction", externalExt).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + transactionState: mockTxnState, + blockState: mockBlockState, + net: NewMockNetwork(ctrl), + } + execTest(t, service, types.Extrinsic{}, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(&rtstorage.TrieState{}, nil) + runtimeMock := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMock, nil).MaxTimes(2) + runtimeMock.On("SetContextStorage", &rtstorage.TrieState{}) + runtimeMock.On("ValidateTransaction", externalExt). + Return(&transaction.Validity{Propagate: true}, nil) + mockTxnState := NewMockTransactionState(ctrl) + mockTxnState.EXPECT().Exists(types.Extrinsic{}).MaxTimes(2) + mockTxnState.EXPECT().AddToPool(transaction.NewValidTransaction(ext, &transaction.Validity{Propagate: true})) + mockNetState := NewMockNetwork(ctrl) + mockNetState.EXPECT().GossipMessage(&network.TransactionMessage{Extrinsics: []types.Extrinsic{ext}}) + service := &Service{ + storageState: mockStorageState, + transactionState: mockTxnState, + blockState: mockBlockState, + net: mockNetState, + } + execTest(t, service, types.Extrinsic{}, nil) + }) +} + +func TestServiceGetMetadata(t *testing.T) { + t.Parallel() + execTest := func(t *testing.T, s *Service, bhash *common.Hash, exp []byte, expErr error) { + res, err := s.GetMetadata(bhash) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, exp, res) + } + + t.Run("get state root error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, &common.Hash{}, nil, errDummyErr) + }) + + t.Run("trie state error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, nil, nil, errDummyErr) + }) + + t.Run("get runtime error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(&rtstorage.TrieState{}, nil) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + blockState: mockBlockState, + } + execTest(t, service, nil, nil, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().TrieState(nil).Return(&rtstorage.TrieState{}, nil) + runtimeMockOk := new(mocksruntime.Instance) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().GetRuntime(nil).Return(runtimeMockOk, nil) + runtimeMockOk.On("SetContextStorage", &rtstorage.TrieState{}) + runtimeMockOk.On("Metadata").Return([]byte{1, 2, 3}, nil) + service := &Service{ + storageState: mockStorageState, + blockState: mockBlockState, + } + execTest(t, service, nil, []byte{1, 2, 3}, nil) + }) +} + +func TestService_tryQueryStorage(t *testing.T) { + t.Parallel() + execTest := func(t *testing.T, s *Service, block common.Hash, keys []string, exp QueryKeyValueChanges, expErr error) { + res, err := s.tryQueryStorage(block, keys...) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, exp, res) + } + + t.Run("get state root error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, common.Hash{}, nil, nil, errDummyErr) + }) + + t.Run("get storage error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(&common.Hash{}, nil) + mockStorageState.EXPECT().GetStorage(&common.Hash{}, common.MustHexToBytes("0x01")).Return(nil, errDummyErr) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, common.Hash{}, []string{"0x01"}, nil, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{}).Return(&common.Hash{}, nil) + mockStorageState.EXPECT().GetStorage(&common.Hash{}, common.MustHexToBytes("0x01")). + Return([]byte{1, 2, 3}, nil) + expChanges := make(QueryKeyValueChanges) + expChanges["0x01"] = common.BytesToHex([]byte{1, 2, 3}) + service := &Service{ + storageState: mockStorageState, + } + execTest(t, service, common.Hash{}, []string{"0x01"}, expChanges, nil) + }) +} + +func TestService_QueryStorage(t *testing.T) { + t.Parallel() + execTest := func(t *testing.T, s *Service, from common.Hash, to common.Hash, + keys []string, exp map[common.Hash]QueryKeyValueChanges, expErr error) { + res, err := s.QueryStorage(from, to, keys...) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, exp, res) + } + + t.Run("subchain error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{2}) + mockBlockState.EXPECT().SubChain(common.Hash{1}, common.Hash{2}).Return(nil, errDummyErr) + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, common.Hash{1}, common.Hash{}, nil, nil, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{2}) + mockBlockState.EXPECT().SubChain(common.Hash{1}, common.Hash{2}).Return([]common.Hash{{0x01}}, nil) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GetStateRootFromBlock(&common.Hash{0x01}).Return(&common.Hash{}, nil) + mockStorageState.EXPECT().GetStorage(&common.Hash{}, common.MustHexToBytes("0x01")). + Return([]byte{1, 2, 3}, nil) + expChanges := make(QueryKeyValueChanges) + expChanges["0x01"] = common.BytesToHex([]byte{1, 2, 3}) + expQueries := make(map[common.Hash]QueryKeyValueChanges) + expQueries[common.Hash{0x01}] = expChanges + service := &Service{ + blockState: mockBlockState, + storageState: mockStorageState, + } + execTest(t, service, common.Hash{1}, common.Hash{}, []string{"0x01"}, expQueries, nil) + }) +} + +func TestService_GetReadProofAt(t *testing.T) { + t.Parallel() + execTest := func(t *testing.T, s *Service, block common.Hash, keys [][]byte, + expHash common.Hash, expProofForKeys [][]byte, expErr error) { + resHash, resProofForKeys, err := s.GetReadProofAt(block, keys) + assert.ErrorIs(t, err, expErr) + if expErr != nil { + assert.EqualError(t, err, expErr.Error()) + } + assert.Equal(t, expHash, resHash) + assert.Equal(t, expProofForKeys, resProofForKeys) + } + + t.Run("get block state root error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{2}) + mockBlockState.EXPECT().GetBlockStateRoot(common.Hash{2}).Return(common.Hash{}, errDummyErr) + service := &Service{ + blockState: mockBlockState, + } + execTest(t, service, common.Hash{}, nil, common.Hash{}, nil, errDummyErr) + }) + + t.Run("generate trie proof error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{2}) + mockBlockState.EXPECT().GetBlockStateRoot(common.Hash{2}).Return(common.Hash{3}, nil) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GenerateTrieProof(common.Hash{3}, [][]byte{{1}}). + Return([][]byte{}, errDummyErr) + service := &Service{ + blockState: mockBlockState, + storageState: mockStorageState, + } + execTest(t, service, common.Hash{}, [][]byte{{1}}, common.Hash{}, nil, errDummyErr) + }) + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + mockBlockState := NewMockBlockState(ctrl) + mockBlockState.EXPECT().BestBlockHash().Return(common.Hash{2}) + mockBlockState.EXPECT().GetBlockStateRoot(common.Hash{2}).Return(common.Hash{3}, nil) + mockStorageState := NewMockStorageState(ctrl) + mockStorageState.EXPECT().GenerateTrieProof(common.Hash{3}, [][]byte{{1}}). + Return([][]byte{{2}}, nil) + service := &Service{ + blockState: mockBlockState, + storageState: mockStorageState, + } + execTest(t, service, common.Hash{}, [][]byte{{1}}, common.Hash{2}, [][]byte{{2}}, nil) }) } diff --git a/dot/rpc/modules/system_integration_test.go b/dot/rpc/modules/system_integration_test.go index 3f8abe0dc3..695227a132 100644 --- a/dot/rpc/modules/system_integration_test.go +++ b/dot/rpc/modules/system_integration_test.go @@ -298,12 +298,7 @@ func setupSystemModule(t *testing.T) *SystemModule { aliceAcctInfo := types.AccountInfo{ Nonce: 3, //RefCount: 0, - Data: struct { - Free *scale.Uint128 - Reserved *scale.Uint128 - MiscFrozen *scale.Uint128 - FreeFrozen *scale.Uint128 - }{ + Data: types.AccountData{ Free: scale.MustNewUint128(big.NewInt(0)), Reserved: scale.MustNewUint128(big.NewInt(0)), MiscFrozen: scale.MustNewUint128(big.NewInt(0)), diff --git a/dot/types/account.go b/dot/types/account.go index 39f5a2794e..7612c5f246 100644 --- a/dot/types/account.go +++ b/dot/types/account.go @@ -14,10 +14,13 @@ type AccountInfo struct { Consumers uint32 Producers uint32 // The additional data that belongs to this account. Used to store the balance(s) in a lot of chains. - Data struct { - Free *scale.Uint128 - Reserved *scale.Uint128 - MiscFrozen *scale.Uint128 - FreeFrozen *scale.Uint128 - } + Data AccountData +} + +// AccountData represents the data of the AccountInfo +type AccountData struct { + Free *scale.Uint128 + Reserved *scale.Uint128 + MiscFrozen *scale.Uint128 + FreeFrozen *scale.Uint128 } diff --git a/dot/types/extrinsic.go b/dot/types/extrinsic.go index e4851fbbae..444369842c 100644 --- a/dot/types/extrinsic.go +++ b/dot/types/extrinsic.go @@ -4,11 +4,7 @@ package types import ( - "bytes" - "github.com/ChainSafe/gossamer/lib/common" - "github.com/centrifuge/go-substrate-rpc-client/v3/scale" - ctypes "github.com/centrifuge/go-substrate-rpc-client/v3/types" ) // Extrinsic is a generic transaction whose format is verified in the runtime @@ -50,19 +46,3 @@ func BytesArrayToExtrinsics(b [][]byte) []Extrinsic { } return exts } - -// ExtrinsicData is a transaction which embeds the `ctypes.Extrinsic` and has additional functionality. -type ExtrinsicData struct { - ctypes.Extrinsic -} - -// DecodeVersion decodes only the version field of the Extrinsic. -func (e *ExtrinsicData) DecodeVersion(encExt Extrinsic) error { - decoder := scale.NewDecoder(bytes.NewReader(encExt)) - _, err := decoder.DecodeUintCompact() - if err != nil { - return err - } - - return decoder.Decode(&e.Version) -} diff --git a/lib/genesis/helpers.go b/lib/genesis/helpers.go index 20bd099894..7ff492e84b 100644 --- a/lib/genesis/helpers.go +++ b/lib/genesis/helpers.go @@ -595,12 +595,7 @@ func buildBalances(kv *keyValue, res map[string]string) error { accInfo := types.AccountInfo{ Nonce: 0, //RefCount: 0, - Data: struct { - Free *scale.Uint128 - Reserved *scale.Uint128 - MiscFrozen *scale.Uint128 - FreeFrozen *scale.Uint128 - }{ + Data: types.AccountData{ Free: scale.MustNewUint128(kv.iVal[i+1].(*big.Int)), Reserved: scale.MustNewUint128(big.NewInt(0)), MiscFrozen: scale.MustNewUint128(big.NewInt(0)), diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index ccb83fa45e..7bd150e10a 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -272,12 +272,7 @@ func TestNodeRuntime_ValidateTransaction(t *testing.T) { accInfo := types.AccountInfo{ Nonce: 0, - Data: struct { - Free *scale.Uint128 - Reserved *scale.Uint128 - MiscFrozen *scale.Uint128 - FreeFrozen *scale.Uint128 - }{ + Data: types.AccountData{ Free: scale.MustNewUint128(big.NewInt(1152921504606846976)), Reserved: scale.MustNewUint128(big.NewInt(0)), MiscFrozen: scale.MustNewUint128(big.NewInt(0)), diff --git a/lib/runtime/wasmer/instance.go b/lib/runtime/wasmer/instance.go index da3b5441a9..8a2dd3ea12 100644 --- a/lib/runtime/wasmer/instance.go +++ b/lib/runtime/wasmer/instance.go @@ -187,6 +187,11 @@ func (in *Instance) GetCodeHash() common.Hash { return in.codeHash } +// GetContext returns the context of the instance +func (in *Instance) GetContext() *runtime.Context { + return in.ctx +} + // UpdateRuntimeCode updates the runtime instance to run the given code func (in *Instance) UpdateRuntimeCode(code []byte) error { in.Stop()