diff --git a/dot/rpc/modules/author.go b/dot/rpc/modules/author.go index 6f37c2c82b..577f3aadf8 100644 --- a/dot/rpc/modules/author.go +++ b/dot/rpc/modules/author.go @@ -97,7 +97,7 @@ func NewAuthorModule(logger log.Logger, coreAPI CoreAPI, runtimeAPI RuntimeAPI, } // InsertKey inserts a key into the keystore -func (cm *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error { +func (am *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error { keyReq := *req pkDec, err := common.HexToBytes(keyReq[1]) @@ -119,22 +119,22 @@ func (cm *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *K return fmt.Errorf("generated public key does not equal provide public key") } - cm.coreAPI.InsertKey(keyPair) - cm.logger.Info("inserted key into keystore", "key", keyPair.Public().Hex()) + am.coreAPI.InsertKey(keyPair) + am.logger.Info("inserted key into keystore", "key", keyPair.Public().Hex()) return nil } // HasKey Checks if the keystore has private keys for the given public key and key type. -func (cm *AuthorModule) HasKey(r *http.Request, req *[]string, res *bool) error { +func (am *AuthorModule) HasKey(r *http.Request, req *[]string, res *bool) error { reqKey := *req var err error - *res, err = cm.coreAPI.HasKey(reqKey[0], reqKey[1]) + *res, err = am.coreAPI.HasKey(reqKey[0], reqKey[1]) return err } // PendingExtrinsics Returns all pending extrinsics -func (cm *AuthorModule) PendingExtrinsics(r *http.Request, req *EmptyRequest, res *PendingExtrinsicsResponse) error { - pending := cm.txStateAPI.Pending() +func (am *AuthorModule) PendingExtrinsics(r *http.Request, req *EmptyRequest, res *PendingExtrinsicsResponse) error { + pending := am.txStateAPI.Pending() resp := make([]string, len(pending)) for idx, tx := range pending { resp[idx] = common.BytesToHex(tx.Extrinsic) @@ -145,29 +145,30 @@ func (cm *AuthorModule) PendingExtrinsics(r *http.Request, req *EmptyRequest, re } // RemoveExtrinsic Remove given extrinsic from the pool and temporarily ban it to prevent reimporting -func (cm *AuthorModule) RemoveExtrinsic(r *http.Request, req *ExtrinsicOrHashRequest, res *RemoveExtrinsicsResponse) error { +func (am *AuthorModule) RemoveExtrinsic(r *http.Request, req *ExtrinsicOrHashRequest, res *RemoveExtrinsicsResponse) error { return nil } // RotateKeys Generate new session keys and returns the corresponding public keys -func (cm *AuthorModule) RotateKeys(r *http.Request, req *EmptyRequest, res *KeyRotateResponse) error { +func (am *AuthorModule) RotateKeys(r *http.Request, req *EmptyRequest, res *KeyRotateResponse) error { return nil } // SubmitAndWatchExtrinsic Submit and subscribe to watch an extrinsic until unsubscribed -func (cm *AuthorModule) SubmitAndWatchExtrinsic(r *http.Request, req *Extrinsic, res *ExtrinsicStatus) error { +func (am *AuthorModule) SubmitAndWatchExtrinsic(r *http.Request, req *Extrinsic, res *ExtrinsicStatus) error { return nil } // SubmitExtrinsic Submit a fully formatted extrinsic for block inclusion -func (cm *AuthorModule) SubmitExtrinsic(r *http.Request, req *Extrinsic, res *ExtrinsicHashResponse) error { +func (am *AuthorModule) SubmitExtrinsic(r *http.Request, req *Extrinsic, res *ExtrinsicHashResponse) error { extBytes, err := common.HexToBytes(req.Data) if err != nil { return err } - ext := types.Extrinsic(extBytes) - err = cm.coreAPI.HandleSubmittedExtrinsic(ext) + am.logger.Crit("[rpc]", "extrinsic", ext) + *res = ExtrinsicHashResponse(ext.Hash().String()) + err = am.coreAPI.HandleSubmittedExtrinsic(ext) return err } diff --git a/dot/rpc/modules/author_integration_test.go b/dot/rpc/modules/author_integration_test.go new file mode 100644 index 0000000000..be641b4f54 --- /dev/null +++ b/dot/rpc/modules/author_integration_test.go @@ -0,0 +1,268 @@ +// +build integration + +package modules + +import ( + "fmt" + "os" + "reflect" + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/runtime" + "github.com/ChainSafe/gossamer/lib/runtime/wasmer" + "github.com/ChainSafe/gossamer/lib/transaction" + log "github.com/ChainSafe/log15" + "github.com/stretchr/testify/require" +) + +// https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/transaction-pool/src/tests.rs#L95 +var testExt = common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670") + +// invalid transaction (above tx, with last byte changed) +//nolint +var testInvalidExt = []byte{1, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 5, 113, 87, 87, 40, 221, 120, 247, 252, 137, 201, 74, 231, 222, 101, 85, 108, 102, 39, 31, 190, 210, 14, 215, 124, 19, 160, 180, 203, 54, 110, 167, 163, 149, 45, 12, 108, 80, 221, 65, 238, 57, 237, 199, 16, 10, 33, 185, 8, 244, 184, 243, 139, 5, 87, 252, 245, 24, 225, 37, 154, 163, 143} + +func TestMain(m *testing.M) { + wasmFilePaths, err := runtime.GenerateRuntimeWasmFile() + if err != nil { + log.Error("failed to generate runtime wasm file", err) + os.Exit(1) + } + + // Start all tests + code := m.Run() + + runtime.RemoveFiles(wasmFilePaths) + os.Exit(code) +} + +func TestAuthorModule_Pending(t *testing.T) { + txQueue := state.NewTransactionState() + auth := NewAuthorModule(nil, nil, nil, txQueue) + + res := new(PendingExtrinsicsResponse) + err := auth.PendingExtrinsics(nil, nil, res) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{})) { + t.Errorf("Fail: expected: %+v got: %+v\n", *res, PendingExtrinsicsResponse([]string{})) + } + + vtx := &transaction.ValidTransaction{ + Extrinsic: types.NewExtrinsic(testExt), + Validity: new(transaction.Validity), + } + + _, err = txQueue.Push(vtx) + require.NoError(t, err) + + err = auth.PendingExtrinsics(nil, nil, res) + if err != nil { + t.Fatal(err) + } + + expected := common.BytesToHex(vtx.Extrinsic) + if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{expected})) { + t.Errorf("Fail: expected: %+v got: %+v\n", res, PendingExtrinsicsResponse([]string{expected})) + } +} + +func TestAuthorModule_SubmitExtrinsic_Integration(t *testing.T) { + t.Skip() + // setup auth module + txQueue := state.NewTransactionState() + + auth := setupAuthModule(t, txQueue) + + // create and submit extrinsic + ext := Extrinsic{fmt.Sprintf("0x%x", testExt)} + + res := new(ExtrinsicHashResponse) + + err := auth.SubmitExtrinsic(nil, &ext, res) + require.Nil(t, err) + + // setup expected results + val := &transaction.Validity{ + Priority: 69, + Requires: [][]byte{}, + Provides: [][]byte{{146, 157, 61, 99, 63, 98, 30, 242, 128, 49, 150, 90, 140, 165, 187, 249}}, + Longevity: 64, + Propagate: true, + } + expected := &transaction.ValidTransaction{ + Extrinsic: types.NewExtrinsic(testExt), + Validity: val, + } + expectedHash := ExtrinsicHashResponse("0xb20777f4db60ea55b1aeedde2d7b7aff3efeda736b7e2a840b5713348f766078") + + inQueue := txQueue.Pop() + + // compare results + require.Equal(t, expected, inQueue) + require.Equal(t, expectedHash, *res) +} + +func TestAuthorModule_SubmitExtrinsic_invalid(t *testing.T) { + t.Skip() + // setup service + // setup auth module + txQueue := state.NewTransactionState() + auth := setupAuthModule(t, txQueue) + + // create and submit extrinsic + ext := Extrinsic{fmt.Sprintf("0x%x", testInvalidExt)} + + res := new(ExtrinsicHashResponse) + + err := auth.SubmitExtrinsic(nil, &ext, res) + require.EqualError(t, err, runtime.ErrInvalidTransaction.Message) +} + +func TestAuthorModule_SubmitExtrinsic_invalid_input(t *testing.T) { + // setup service + // setup auth module + txQueue := state.NewTransactionState() + auth := setupAuthModule(t, txQueue) + + // create and submit extrinsic + ext := Extrinsic{fmt.Sprintf("%x", "1")} + + res := new(ExtrinsicHashResponse) + err := auth.SubmitExtrinsic(nil, &ext, res) + require.EqualError(t, err, "could not byteify non 0x prefixed string") +} + +func TestAuthorModule_SubmitExtrinsic_InQueue(t *testing.T) { + t.Skip() + // setup auth module + txQueue := state.NewTransactionState() + + auth := setupAuthModule(t, txQueue) + + // create and submit extrinsic + ext := Extrinsic{fmt.Sprintf("0x%x", testExt)} + + res := new(ExtrinsicHashResponse) + + // setup expected results + val := &transaction.Validity{ + Priority: 69, + Requires: [][]byte{}, + Provides: [][]byte{{146, 157, 61, 99, 63, 98, 30, 242, 128, 49, 150, 90, 140, 165, 187, 249}}, + Longevity: 64, + Propagate: true, + } + expected := &transaction.ValidTransaction{ + Extrinsic: types.NewExtrinsic(testExt), + Validity: val, + } + + _, err := txQueue.Push(expected) + require.Nil(t, err) + + // this should cause error since transaction is already in txQueue + err = auth.SubmitExtrinsic(nil, &ext, res) + require.EqualError(t, err, transaction.ErrTransactionExists.Error()) + +} + +func TestAuthorModule_InsertKey_Valid(t *testing.T) { + auth := setupAuthModule(t, nil) + req := &KeyInsertRequest{"babe", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a"} + res := &KeyInsertResponse{} + err := auth.InsertKey(nil, req, res) + require.Nil(t, err) + require.Len(t, *res, 0) // zero len result on success +} + +func TestAuthorModule_InsertKey_Valid_gran_keytype(t *testing.T) { + auth := setupAuthModule(t, nil) + req := &KeyInsertRequest{"gran", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309b7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309"} + res := &KeyInsertResponse{} + err := auth.InsertKey(nil, req, res) + require.Nil(t, err) + + require.Len(t, *res, 0) // zero len result on success +} + +func TestAuthorModule_InsertKey_InValid(t *testing.T) { + auth := setupAuthModule(t, nil) + req := &KeyInsertRequest{"babe", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x0000000000000000000000000000000000000000000000000000000000000000"} + res := &KeyInsertResponse{} + err := auth.InsertKey(nil, req, res) + require.EqualError(t, err, "generated public key does not equal provide public key") +} + +func TestAuthorModule_InsertKey_UnknownKeyType(t *testing.T) { + auth := setupAuthModule(t, nil) + req := &KeyInsertRequest{"mack", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a"} + res := &KeyInsertResponse{} + err := auth.InsertKey(nil, req, res) + require.EqualError(t, err, "cannot decode key: invalid key type") + +} + +func TestAuthorModule_HasKey_Integration(t *testing.T) { + auth := setupAuthModule(t, nil) + kr, err := keystore.NewSr25519Keyring() + require.Nil(t, err) + + var res bool + req := []string{kr.Alice().Public().Hex(), "babe"} + err = auth.HasKey(nil, &req, &res) + require.NoError(t, err) + require.True(t, res) +} + +func TestAuthorModule_HasKey_NotFound(t *testing.T) { + auth := setupAuthModule(t, nil) + kr, err := keystore.NewSr25519Keyring() + require.Nil(t, err) + + var res bool + req := []string{kr.Bob().Public().Hex(), "babe"} + err = auth.HasKey(nil, &req, &res) + require.NoError(t, err) + require.False(t, res) +} + +func TestAuthorModule_HasKey_InvalidKey(t *testing.T) { + auth := setupAuthModule(t, nil) + + var res bool + req := []string{"0xaa11", "babe"} + err := auth.HasKey(nil, &req, &res) + require.EqualError(t, err, "cannot create public key: input is not 32 bytes") + require.False(t, res) +} + +func TestAuthorModule_HasKey_InvalidKeyType(t *testing.T) { + auth := setupAuthModule(t, nil) + kr, err := keystore.NewSr25519Keyring() + require.Nil(t, err) + + var res bool + req := []string{kr.Alice().Public().Hex(), "xxxx"} + err = auth.HasKey(nil, &req, &res) + require.EqualError(t, err, "unknown key type: xxxx") + require.False(t, res) +} + +func setupAuthModule(t *testing.T, txq *state.TransactionState) *AuthorModule { + fmt.Println("calling setupAuthModule") + cs := newCoreService(t, nil) + fmt.Println("called newCoreService") + rt := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) + t.Cleanup(func() { + rt.Stop() + }) + return NewAuthorModule(nil, cs, rt, txq) +} diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index 7755cb405b..7ba6dfc732 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -2,303 +2,358 @@ package modules import ( "fmt" - "os" - "reflect" + "net/http" "testing" - "github.com/ChainSafe/gossamer/dot/core" - "github.com/ChainSafe/gossamer/dot/network" - "github.com/ChainSafe/gossamer/dot/state" + apimocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/runtime" - "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/transaction" - "github.com/ChainSafe/gossamer/lib/trie" log "github.com/ChainSafe/log15" - "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/mock" ) -// https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/transaction-pool/src/tests.rs#L95 -var testExt = common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670") - -// invalid transaction (above tx, with last byte changed) -//nolint -var testInvalidExt = []byte{1, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 5, 113, 87, 87, 40, 221, 120, 247, 252, 137, 201, 74, 231, 222, 101, 85, 108, 102, 39, 31, 190, 210, 14, 215, 124, 19, 160, 180, 203, 54, 110, 167, 163, 149, 45, 12, 108, 80, 221, 65, 238, 57, 237, 199, 16, 10, 33, 185, 8, 244, 184, 243, 139, 5, 87, 252, 245, 24, 225, 37, 154, 163, 143} - -func TestMain(m *testing.M) { - wasmFilePaths, err := runtime.GenerateRuntimeWasmFile() - if err != nil { - log.Error("failed to generate runtime wasm file", err) - os.Exit(1) - } +func TestAuthorModule_SubmitExtrinsic(t *testing.T) { + errMockCoreAPI := &apimocks.MockCoreAPI{} + errMockCoreAPI.On("HandleSubmittedExtrinsic", mock.AnythingOfType("types.Extrinsic")).Return(fmt.Errorf("some error")) - // Start all tests - code := m.Run() + mockCoreAPI := &apimocks.MockCoreAPI{} + mockCoreAPI.On("HandleSubmittedExtrinsic", mock.AnythingOfType("types.Extrinsic")).Return(nil) - runtime.RemoveFiles(wasmFilePaths) - os.Exit(code) -} + // https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/transaction-pool/src/tests.rs#L95 + var testExt = common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670") -func TestAuthorModule_Pending(t *testing.T) { - txQueue := state.NewTransactionState() - auth := NewAuthorModule(nil, nil, nil, txQueue) + // invalid transaction (above tx, with last byte changed) + //nolint + var testInvalidExt = []byte{1, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 5, 113, 87, 87, 40, 221, 120, 247, 252, 137, 201, 74, 231, 222, 101, 85, 108, 102, 39, 31, 190, 210, 14, 215, 124, 19, 160, 180, 203, 54, 110, 167, 163, 149, 45, 12, 108, 80, 221, 65, 238, 57, 237, 199, 16, 10, 33, 185, 8, 244, 184, 243, 139, 5, 87, 252, 245, 24, 225, 37, 154, 163, 143} - res := new(PendingExtrinsicsResponse) - err := auth.PendingExtrinsics(nil, nil, res) - if err != nil { - t.Fatal(err) + type fields struct { + logger log.Logger + coreAPI CoreAPI + runtimeAPI RuntimeAPI + txStateAPI TransactionStateAPI } - - if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{})) { - t.Errorf("Fail: expected: %+v got: %+v\n", *res, PendingExtrinsicsResponse([]string{})) + type args struct { + r *http.Request + req *Extrinsic + res *ExtrinsicHashResponse } - - vtx := &transaction.ValidTransaction{ - Extrinsic: types.NewExtrinsic(testExt), - Validity: new(transaction.Validity), + tests := []struct { + name string + fields fields + args args + wantErr bool + wantRes ExtrinsicHashResponse + }{ + { + name: "HexToBytes error", + args: args{ + req: &Extrinsic{fmt.Sprintf("%x", "1")}, + res: new(ExtrinsicHashResponse), + }, + wantErr: true, + wantRes: ExtrinsicHashResponse(""), + }, + { + name: "HandleSubmittedExtrinsic error", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: errMockCoreAPI, + }, + args: args{ + req: &Extrinsic{fmt.Sprintf("0x%x", testInvalidExt)}, + res: new(ExtrinsicHashResponse), + }, + wantErr: true, + wantRes: ExtrinsicHashResponse(types.Extrinsic(testInvalidExt).Hash().String()), + }, + { + name: "happy path", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: mockCoreAPI, + }, + args: args{ + req: &Extrinsic{fmt.Sprintf("0x%x", testExt)}, + res: new(ExtrinsicHashResponse), + }, + wantRes: ExtrinsicHashResponse(types.Extrinsic(testExt).Hash().String()), + }, } - - _, err = txQueue.Push(vtx) - require.NoError(t, err) - - err = auth.PendingExtrinsics(nil, nil, res) - if err != nil { - t.Fatal(err) - } - - expected := common.BytesToHex(vtx.Extrinsic) - if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{expected})) { - t.Errorf("Fail: expected: %+v got: %+v\n", res, PendingExtrinsicsResponse([]string{expected})) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am := &AuthorModule{ + logger: tt.fields.logger, + coreAPI: tt.fields.coreAPI, + runtimeAPI: tt.fields.runtimeAPI, + txStateAPI: tt.fields.txStateAPI, + } + if err := am.SubmitExtrinsic(tt.args.r, tt.args.req, tt.args.res); (err != nil) != tt.wantErr { + t.Errorf("AuthorModule.SubmitExtrinsic() error = %v, wantErr %v", err, tt.wantErr) + } + if diff := cmp.Diff(tt.wantRes, *tt.args.res); diff != "" { + t.Errorf("unexpected response: %s", diff) + } + }) } } -func TestAuthorModule_SubmitExtrinsic(t *testing.T) { - t.Skip() - // setup auth module - txQueue := state.NewTransactionState() - - auth := setupAuthModule(t, txQueue) - - // create and submit extrinsic - ext := Extrinsic{fmt.Sprintf("0x%x", testExt)} - - res := new(ExtrinsicHashResponse) - - err := auth.SubmitExtrinsic(nil, &ext, res) - require.Nil(t, err) +func TestAuthorModule_PendingExtrinsics(t *testing.T) { + emptyMockTransactionStateAPI := &apimocks.TransactionStateAPI{} + emptyMockTransactionStateAPI.On("Pending").Return([]*transaction.ValidTransaction{}) + + mockTransactionStateAPI := &apimocks.TransactionStateAPI{} + mockTransactionStateAPI.On("Pending").Return([]*transaction.ValidTransaction{ + { + Extrinsic: types.NewExtrinsic([]byte("someExtrinsic")), + }, + { + Extrinsic: types.NewExtrinsic([]byte("someExtrinsic1")), + }, + }) - // setup expected results - val := &transaction.Validity{ - Priority: 69, - Requires: [][]byte{}, - Provides: [][]byte{{146, 157, 61, 99, 63, 98, 30, 242, 128, 49, 150, 90, 140, 165, 187, 249}}, - Longevity: 64, - Propagate: true, + type fields struct { + logger log.Logger + coreAPI CoreAPI + runtimeAPI RuntimeAPI + txStateAPI TransactionStateAPI } - expected := &transaction.ValidTransaction{ - Extrinsic: types.NewExtrinsic(testExt), - Validity: val, + type args struct { + r *http.Request + req *EmptyRequest + res *PendingExtrinsicsResponse } - expectedHash := ExtrinsicHashResponse("0xb20777f4db60ea55b1aeedde2d7b7aff3efeda736b7e2a840b5713348f766078") - - inQueue := txQueue.Pop() - - // compare results - require.Equal(t, expected, inQueue) - require.Equal(t, expectedHash, *res) -} - -func TestAuthorModule_SubmitExtrinsic_invalid(t *testing.T) { - t.Skip() - // setup service - // setup auth module - txQueue := state.NewTransactionState() - auth := setupAuthModule(t, txQueue) - - // create and submit extrinsic - ext := Extrinsic{fmt.Sprintf("0x%x", testInvalidExt)} - - res := new(ExtrinsicHashResponse) - - err := auth.SubmitExtrinsic(nil, &ext, res) - require.EqualError(t, err, runtime.ErrInvalidTransaction.Message) -} - -func TestAuthorModule_SubmitExtrinsic_invalid_input(t *testing.T) { - // setup service - // setup auth module - txQueue := state.NewTransactionState() - auth := setupAuthModule(t, txQueue) - - // create and submit extrinsic - ext := Extrinsic{fmt.Sprintf("%x", "1")} - - res := new(ExtrinsicHashResponse) - err := auth.SubmitExtrinsic(nil, &ext, res) - require.EqualError(t, err, "could not byteify non 0x prefixed string") -} - -func TestAuthorModule_SubmitExtrinsic_InQueue(t *testing.T) { - t.Skip() - // setup auth module - txQueue := state.NewTransactionState() - - auth := setupAuthModule(t, txQueue) - - // create and submit extrinsic - ext := Extrinsic{fmt.Sprintf("0x%x", testExt)} - - res := new(ExtrinsicHashResponse) - - // setup expected results - val := &transaction.Validity{ - Priority: 69, - Requires: [][]byte{}, - Provides: [][]byte{{146, 157, 61, 99, 63, 98, 30, 242, 128, 49, 150, 90, 140, 165, 187, 249}}, - Longevity: 64, - Propagate: true, + tests := []struct { + name string + fields fields + args args + wantErr bool + wantRes PendingExtrinsicsResponse + }{ + { + name: "no pending", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + txStateAPI: emptyMockTransactionStateAPI, + }, + args: args{ + res: new(PendingExtrinsicsResponse), + }, + wantRes: PendingExtrinsicsResponse{}, + }, + { + name: "two pending", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + txStateAPI: mockTransactionStateAPI, + }, + args: args{ + res: new(PendingExtrinsicsResponse), + }, + wantRes: PendingExtrinsicsResponse{ + common.BytesToHex([]byte("someExtrinsic")), + common.BytesToHex([]byte("someExtrinsic1")), + }, + }, } - expected := &transaction.ValidTransaction{ - Extrinsic: types.NewExtrinsic(testExt), - Validity: val, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cm := &AuthorModule{ + logger: tt.fields.logger, + coreAPI: tt.fields.coreAPI, + runtimeAPI: tt.fields.runtimeAPI, + txStateAPI: tt.fields.txStateAPI, + } + if err := cm.PendingExtrinsics(tt.args.r, tt.args.req, tt.args.res); (err != nil) != tt.wantErr { + t.Errorf("AuthorModule.PendingExtrinsics() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(tt.wantRes, *tt.args.res); diff != "" { + t.Errorf("unexpected response: %s", diff) + } + }) } - - _, err := txQueue.Push(expected) - require.Nil(t, err) - - // this should cause error since transaction is already in txQueue - err = auth.SubmitExtrinsic(nil, &ext, res) - require.EqualError(t, err, transaction.ErrTransactionExists.Error()) - -} - -func TestAuthorModule_InsertKey_Valid(t *testing.T) { - auth := setupAuthModule(t, nil) - req := &KeyInsertRequest{"babe", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a"} - res := &KeyInsertResponse{} - err := auth.InsertKey(nil, req, res) - require.Nil(t, err) - require.Len(t, *res, 0) // zero len result on success -} - -func TestAuthorModule_InsertKey_Valid_gran_keytype(t *testing.T) { - auth := setupAuthModule(t, nil) - req := &KeyInsertRequest{"gran", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309b7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309"} - res := &KeyInsertResponse{} - err := auth.InsertKey(nil, req, res) - require.Nil(t, err) - - require.Len(t, *res, 0) // zero len result on success } -func TestAuthorModule_InsertKey_InValid(t *testing.T) { - auth := setupAuthModule(t, nil) - req := &KeyInsertRequest{"babe", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x0000000000000000000000000000000000000000000000000000000000000000"} - res := &KeyInsertResponse{} - err := auth.InsertKey(nil, req, res) - require.EqualError(t, err, "generated public key does not equal provide public key") -} - -func TestAuthorModule_InsertKey_UnknownKeyType(t *testing.T) { - auth := setupAuthModule(t, nil) - req := &KeyInsertRequest{"mack", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a"} - res := &KeyInsertResponse{} - err := auth.InsertKey(nil, req, res) - require.EqualError(t, err, "cannot decode key: invalid key type") +func TestAuthorModule_InsertKey(t *testing.T) { + mockCoreAPI := &apimocks.MockCoreAPI{} + mockCoreAPI.On("InsertKey", mock.Anything).Return(nil) + type fields struct { + logger log.Logger + coreAPI CoreAPI + runtimeAPI RuntimeAPI + txStateAPI TransactionStateAPI + } + type args struct { + r *http.Request + req *KeyInsertRequest + res *KeyInsertResponse + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: mockCoreAPI, + }, + args: args{ + req: &KeyInsertRequest{ + "babe", + "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a", + }, + }, + }, + { + name: "happy path, gran keytype", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: mockCoreAPI, + }, + args: args{ + req: &KeyInsertRequest{"gran", + "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309b7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + }, + }, + }, + { + name: "invalid key", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: mockCoreAPI, + }, + args: args{ + req: &KeyInsertRequest{"babe", + "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + wantErr: true, + }, + { + name: "unknown key", + fields: fields{ + logger: log.New("service", "RPC", "module", "author"), + coreAPI: mockCoreAPI, + }, + args: args{ + req: &KeyInsertRequest{ + "mack", + "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", + "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a", + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am := &AuthorModule{ + logger: tt.fields.logger, + coreAPI: tt.fields.coreAPI, + runtimeAPI: tt.fields.runtimeAPI, + txStateAPI: tt.fields.txStateAPI, + } + if err := am.InsertKey(tt.args.r, tt.args.req, tt.args.res); (err != nil) != tt.wantErr { + t.Errorf("AuthorModule.InsertKey() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } } func TestAuthorModule_HasKey(t *testing.T) { - auth := setupAuthModule(t, nil) - kr, err := keystore.NewSr25519Keyring() - require.Nil(t, err) + mockCoreAPITrue := &apimocks.MockCoreAPI{} + mockCoreAPITrue.On("HasKey", mock.Anything, mock.Anything).Return(true, nil) - var res bool - req := []string{kr.Alice().Public().Hex(), "babe"} - err = auth.HasKey(nil, &req, &res) - require.NoError(t, err) - require.True(t, res) -} - -func TestAuthorModule_HasKey_NotFound(t *testing.T) { - auth := setupAuthModule(t, nil) - kr, err := keystore.NewSr25519Keyring() - require.Nil(t, err) - - var res bool - req := []string{kr.Bob().Public().Hex(), "babe"} - err = auth.HasKey(nil, &req, &res) - require.NoError(t, err) - require.False(t, res) -} + mockCoreAPIFalse := &apimocks.MockCoreAPI{} + mockCoreAPIFalse.On("HasKey", mock.Anything, mock.Anything).Return(false, nil) -func TestAuthorModule_HasKey_InvalidKey(t *testing.T) { - auth := setupAuthModule(t, nil) + mockCoreAPIErr := &apimocks.MockCoreAPI{} + mockCoreAPIErr.On("HasKey", mock.Anything, mock.Anything).Return(false, fmt.Errorf("some error")) - var res bool - req := []string{"0xaa11", "babe"} - err := auth.HasKey(nil, &req, &res) - require.EqualError(t, err, "cannot create public key: input is not 32 bytes") - require.False(t, res) -} - -func TestAuthorModule_HasKey_InvalidKeyType(t *testing.T) { - auth := setupAuthModule(t, nil) kr, err := keystore.NewSr25519Keyring() - require.Nil(t, err) - var res bool - req := []string{kr.Alice().Public().Hex(), "xxxx"} - err = auth.HasKey(nil, &req, &res) - require.EqualError(t, err, "unknown key type: xxxx") - require.False(t, res) -} - -func newCoreService(t *testing.T, srvc *state.Service) *core.Service { - // setup service - tt := trie.NewEmptyTrie() - rt := wasmer.NewTestInstanceWithTrie(t, runtime.NODE_RUNTIME, tt, log.LvlInfo) - ks := keystore.NewGlobalKeystore() - t.Cleanup(func() { - rt.Stop() - }) - - // insert alice key for testing - kr, err := keystore.NewSr25519Keyring() - require.NoError(t, err) - ks.Acco.Insert(kr.Alice()) - - if srvc == nil { - srvc = newTestStateService(t) + if err != nil { + panic(err) } - cfg := &core.Config{ - Runtime: rt, - Keystore: ks, - TransactionState: srvc.Transaction, - BlockState: srvc.Block, - StorageState: srvc.Storage, - EpochState: srvc.Epoch, - Network: &mockNetwork{}, - CodeSubstitutedState: srvc.Base, + type fields struct { + logger log.Logger + coreAPI CoreAPI + runtimeAPI RuntimeAPI + txStateAPI TransactionStateAPI + } + type args struct { + r *http.Request + req *[]string + res *bool + } + tests := []struct { + name string + fields fields + args args + wantErr bool + wantRes bool + }{ + { + name: "HasKey true", + fields: fields{ + coreAPI: mockCoreAPITrue, + }, + args: args{ + req: &[]string{kr.Alice().Public().Hex(), "babe"}, + res: new(bool), + }, + wantRes: true, + }, + { + name: "HasKey false", + fields: fields{ + coreAPI: mockCoreAPIFalse, + }, + args: args{ + req: &[]string{kr.Alice().Public().Hex(), "babe"}, + res: new(bool), + }, + wantRes: false, + }, + { + name: "HasKey error", + fields: fields{ + coreAPI: mockCoreAPIErr, + }, + args: args{ + req: &[]string{kr.Alice().Public().Hex(), "babe"}, + res: new(bool), + }, + wantRes: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cm := &AuthorModule{ + logger: tt.fields.logger, + coreAPI: tt.fields.coreAPI, + runtimeAPI: tt.fields.runtimeAPI, + txStateAPI: tt.fields.txStateAPI, + } + if err := cm.HasKey(tt.args.r, tt.args.req, tt.args.res); (err != nil) != tt.wantErr { + t.Errorf("AuthorModule.HasKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(tt.wantRes, *tt.args.res); diff != "" { + t.Errorf("unexpected response: %s", diff) + } + }) } - - return core.NewTestService(t, cfg) -} - -func setupAuthModule(t *testing.T, txq *state.TransactionState) *AuthorModule { - fmt.Println("calling setupAuthModule") - cs := newCoreService(t, nil) - fmt.Println("called newCoreService") - rt := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) - t.Cleanup(func() { - rt.Stop() - }) - return NewAuthorModule(nil, cs, rt, txq) } - -type mockNetwork struct{} - -func (n *mockNetwork) SendMessage(_ network.NotificationsMessage) {} diff --git a/dot/rpc/modules/mocks/TransactionStateAPI.go b/dot/rpc/modules/mocks/TransactionStateAPI.go new file mode 100644 index 0000000000..b241ab8bdb --- /dev/null +++ b/dot/rpc/modules/mocks/TransactionStateAPI.go @@ -0,0 +1,79 @@ +// Code generated by mockery 2.9.0. DO NOT EDIT. + +package mocks + +import ( + common "github.com/ChainSafe/gossamer/lib/common" + mock "github.com/stretchr/testify/mock" + + transaction "github.com/ChainSafe/gossamer/lib/transaction" +) + +// TransactionStateAPI is an autogenerated mock type for the TransactionStateAPI type +type TransactionStateAPI struct { + mock.Mock +} + +// AddToPool provides a mock function with given fields: _a0 +func (_m *TransactionStateAPI) AddToPool(_a0 *transaction.ValidTransaction) common.Hash { + ret := _m.Called(_a0) + + var r0 common.Hash + if rf, ok := ret.Get(0).(func(*transaction.ValidTransaction) common.Hash); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + return r0 +} + +// Peek provides a mock function with given fields: +func (_m *TransactionStateAPI) Peek() *transaction.ValidTransaction { + ret := _m.Called() + + var r0 *transaction.ValidTransaction + if rf, ok := ret.Get(0).(func() *transaction.ValidTransaction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*transaction.ValidTransaction) + } + } + + return r0 +} + +// Pending provides a mock function with given fields: +func (_m *TransactionStateAPI) Pending() []*transaction.ValidTransaction { + ret := _m.Called() + + var r0 []*transaction.ValidTransaction + if rf, ok := ret.Get(0).(func() []*transaction.ValidTransaction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*transaction.ValidTransaction) + } + } + + return r0 +} + +// Pop provides a mock function with given fields: +func (_m *TransactionStateAPI) Pop() *transaction.ValidTransaction { + ret := _m.Called() + + var r0 *transaction.ValidTransaction + if rf, ok := ret.Get(0).(func() *transaction.ValidTransaction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*transaction.ValidTransaction) + } + } + + return r0 +} diff --git a/dot/rpc/modules/mocks/block_api.go b/dot/rpc/modules/mocks/block_api.go index bdde4b1be2..71c7f5a864 100644 --- a/dot/rpc/modules/mocks/block_api.go +++ b/dot/rpc/modules/mocks/block_api.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package modules +package mocks import ( big "math/big" diff --git a/dot/rpc/modules/mocks/core_api.go b/dot/rpc/modules/mocks/core_api.go index 6a3c1da8d1..380de34353 100644 --- a/dot/rpc/modules/mocks/core_api.go +++ b/dot/rpc/modules/mocks/core_api.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package modules +package mocks import ( common "github.com/ChainSafe/gossamer/lib/common" diff --git a/dot/rpc/modules/mocks/storage_api.go b/dot/rpc/modules/mocks/storage_api.go index 6f0611a351..db3bb22a4e 100644 --- a/dot/rpc/modules/mocks/storage_api.go +++ b/dot/rpc/modules/mocks/storage_api.go @@ -1,6 +1,6 @@ // Code generated by mockery v2.8.0. DO NOT EDIT. -package modules +package mocks import ( common "github.com/ChainSafe/gossamer/lib/common" diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index d47cf95bb2..3d42590e48 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -24,13 +24,19 @@ import ( "testing" "time" + "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/runtime" + "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/scale" "github.com/ChainSafe/gossamer/lib/transaction" + "github.com/ChainSafe/gossamer/lib/trie" + log "github.com/ChainSafe/log15" "github.com/stretchr/testify/require" ) @@ -323,3 +329,39 @@ func setupSystemModule(t *testing.T) *SystemModule { txQueue := state.NewTransactionState() return NewSystemModule(net, nil, core, chain.Storage, txQueue) } + +type mockNetwork struct{} + +func (n *mockNetwork) SendMessage(_ network.NotificationsMessage) {} + +func newCoreService(t *testing.T, srvc *state.Service) *core.Service { + // setup service + tt := trie.NewEmptyTrie() + rt := wasmer.NewTestInstanceWithTrie(t, runtime.NODE_RUNTIME, tt, log.LvlInfo) + ks := keystore.NewGlobalKeystore() + t.Cleanup(func() { + rt.Stop() + }) + + // insert alice key for testing + kr, err := keystore.NewSr25519Keyring() + require.NoError(t, err) + ks.Acco.Insert(kr.Alice()) + + if srvc == nil { + srvc = newTestStateService(t) + } + + cfg := &core.Config{ + Runtime: rt, + Keystore: ks, + TransactionState: srvc.Transaction, + BlockState: srvc.Block, + StorageState: srvc.Storage, + EpochState: srvc.Epoch, + Network: &mockNetwork{}, + CodeSubstitutedState: srvc.Base, + } + + return core.NewTestService(t, cfg) +}