diff --git a/dot/core/messages_test.go b/dot/core/messages_test.go index fbce4ecf0dc..dcc7bce2da6 100644 --- a/dot/core/messages_test.go +++ b/dot/core/messages_test.go @@ -46,7 +46,7 @@ func TestService_ProcessBlockRequestMessage(t *testing.T) { MsgSend: msgSend, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) addTestBlocksToState(t, 2, s.blockState) @@ -267,7 +267,7 @@ func TestService_ProcessBlockResponseMessage(t *testing.T) { MsgSend: msgSend, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) hash := common.NewHash([]byte{0}) body := optional.CoreBody{0xa, 0xb, 0xc, 0xd} @@ -341,7 +341,7 @@ func TestService_ProcessBlockAnnounceMessage(t *testing.T) { IsBabeAuthority: false, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) err := s.Start() require.Nil(t, err) @@ -396,7 +396,7 @@ func TestService_ProcessTransactionMessage(t *testing.T) { IsBabeAuthority: true, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) // https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/transaction-pool/src/tests.rs#L95 ext := []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, 142} diff --git a/dot/core/runtime_test.go b/dot/core/runtime_test.go index 6175ce65e6f..5943a70280d 100644 --- a/dot/core/runtime_test.go +++ b/dot/core/runtime_test.go @@ -73,7 +73,7 @@ func TestRetrieveAuthorityData(t *testing.T) { } func TestValidateTransaction(t *testing.T) { - s := newTestService(t, nil) + s := NewTestService(t, nil) // https://github.com/paritytech/substrate/blob/5420de3face1349a97eb954ae71c5b0b940c31de/core/transaction-pool/src/tests.rs#L95 tx := []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, 142} @@ -115,7 +115,7 @@ func TestCheckForRuntimeChanges(t *testing.T) { IsBabeAuthority: false, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) _, err = runtime.GetRuntimeBlob(runtime.TESTS_FP, runtime.TEST_WASM_URL) require.Nil(t, err) diff --git a/dot/core/service.go b/dot/core/service.go index 8058c26a302..e00bffb1f68 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -27,6 +27,7 @@ import ( "github.com/ChainSafe/gossamer/lib/babe" "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/database" "github.com/ChainSafe/gossamer/lib/keystore" @@ -500,3 +501,8 @@ func (s *Service) checkForRuntimeChanges() error { return nil } + +// InsertKey inserts keypair into keystore +func (s *Service) InsertKey(kp crypto.Keypair) { + s.keys.Insert(kp) +} diff --git a/dot/core/service_test.go b/dot/core/service_test.go index c8dac55c2df..ca00fcaf556 100644 --- a/dot/core/service_test.go +++ b/dot/core/service_test.go @@ -22,12 +22,10 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" - "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/babe" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" - "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/trie" @@ -39,67 +37,6 @@ import ( // testMessageTimeout is the wait time for messages to be exchanged var testMessageTimeout = time.Second -// testGenesisHeader is a test block header -var testGenesisHeader = &types.Header{ - Number: big.NewInt(0), - StateRoot: trie.EmptyHash, -} - -// newTestService creates a new test core service -func newTestService(t *testing.T, cfg *Config) *Service { - if cfg == nil { - rt := runtime.NewTestRuntime(t, runtime.POLKADOT_RUNTIME_c768a7e4c70e) - cfg = &Config{ - Runtime: rt, - IsBabeAuthority: false, - } - } - - if cfg.Keystore == nil { - cfg.Keystore = keystore.NewKeystore() - } - - if cfg.NewBlocks == nil { - cfg.NewBlocks = make(chan types.Block) - } - - if cfg.MsgRec == nil { - cfg.MsgRec = make(chan network.Message, 10) - } - - if cfg.MsgSend == nil { - cfg.MsgSend = make(chan network.Message, 10) - } - - if cfg.SyncChan == nil { - cfg.SyncChan = make(chan *big.Int, 10) - } - - stateSrvc := state.NewService("") - stateSrvc.UseMemDB() - - genesisData := new(genesis.Data) - - err := stateSrvc.Initialize(genesisData, testGenesisHeader, trie.NewEmptyTrie()) - require.Nil(t, err) - - err = stateSrvc.Start() - require.Nil(t, err) - - if cfg.BlockState == nil { - cfg.BlockState = stateSrvc.Block - } - - if cfg.StorageState == nil { - cfg.StorageState = stateSrvc.Storage - } - - s, err := NewService(cfg) - require.Nil(t, err) - - return s -} - // newTestServiceWithFirstBlock creates a new test service with a test block func newTestServiceWithFirstBlock(t *testing.T) *Service { tt := trie.NewEmptyTrie() @@ -120,7 +57,7 @@ func newTestServiceWithFirstBlock(t *testing.T) *Service { IsBabeAuthority: true, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) preDigest, err := common.HexToBytes("0x014241424538e93dcef2efc275b72b4fa748332dc4c9f13be1125909cf90c8e9109c45da16b04bc5fdf9fe06a4f35e4ae4ed7e251ff9ee3d0d840c8237c9fb9057442dbf00f210d697a7b4959f792a81b948ff88937e30bf9709a8ab1314f71284da89a40000000000000000001100000000000000") require.Nil(t, err) @@ -177,7 +114,7 @@ func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) { } func TestStartService(t *testing.T) { - s := newTestService(t, nil) + s := NewTestService(t, nil) // TODO: improve dot tests #687 require.NotNil(t, s) @@ -194,7 +131,7 @@ func TestNotAuthority(t *testing.T) { IsBabeAuthority: false, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) if s.bs != nil { t.Fatal("Fail: should not have babe session") } @@ -209,7 +146,7 @@ func TestAnnounceBlock(t *testing.T) { MsgSend: msgSend, } - s := newTestService(t, cfg) + s := NewTestService(t, cfg) err := s.Start() require.Nil(t, err) defer s.Stop() diff --git a/dot/core/syncer_test.go b/dot/core/syncer_test.go index 49fc9b8662c..74d9323d77b 100644 --- a/dot/core/syncer_test.go +++ b/dot/core/syncer_test.go @@ -276,7 +276,7 @@ func TestWatchForResponses(t *testing.T) { syncer.highestSeenBlock = big.NewInt(16) - coreSrv := newTestService(t, nil) + coreSrv := NewTestService(t, nil) addTestBlocksToState(t, 16, coreSrv.blockState) startNum := 1 @@ -354,7 +354,7 @@ func TestWatchForResponses_MissingBlocks(t *testing.T) { syncer.highestSeenBlock = big.NewInt(16) - coreSrv := newTestService(t, nil) + coreSrv := NewTestService(t, nil) addTestBlocksToState(t, 16, coreSrv.blockState) startNum := 16 diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go new file mode 100644 index 00000000000..f0e9807fd87 --- /dev/null +++ b/dot/core/test_helpers.go @@ -0,0 +1,92 @@ +// Copyright 2020 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package core + +import ( + "math/big" + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/runtime" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/stretchr/testify/require" +) + +// testGenesisHeader is a test block header +var testGenesisHeader = &types.Header{ + Number: big.NewInt(0), + StateRoot: trie.EmptyHash, +} + +// NewTestService creates a new test core service +func NewTestService(t *testing.T, cfg *Config) *Service { + if cfg == nil { + rt := runtime.NewTestRuntime(t, runtime.POLKADOT_RUNTIME_c768a7e4c70e) + cfg = &Config{ + Runtime: rt, + IsBabeAuthority: false, + } + } + + if cfg.Keystore == nil { + cfg.Keystore = keystore.NewKeystore() + } + + if cfg.NewBlocks == nil { + cfg.NewBlocks = make(chan types.Block) + } + + if cfg.MsgRec == nil { + cfg.MsgRec = make(chan network.Message, 10) + } + + if cfg.MsgSend == nil { + cfg.MsgSend = make(chan network.Message, 10) + } + + if cfg.SyncChan == nil { + cfg.SyncChan = make(chan *big.Int, 10) + } + + stateSrvc := state.NewService("") + stateSrvc.UseMemDB() + + genesisData := new(genesis.Data) + + err := stateSrvc.Initialize(genesisData, testGenesisHeader, trie.NewEmptyTrie()) + require.Nil(t, err) + + err = stateSrvc.Start() + require.Nil(t, err) + + if cfg.BlockState == nil { + cfg.BlockState = stateSrvc.Block + } + + if cfg.StorageState == nil { + cfg.StorageState = stateSrvc.Storage + } + + s, err := NewService(cfg) + require.Nil(t, err) + + return s +} diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 3492b4d87cd..6f44baeb35e 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -3,6 +3,8 @@ package modules import ( "math/big" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/transaction" @@ -35,4 +37,6 @@ type TransactionQueueAPI interface { } // CoreAPI is the interface for the core methods -type CoreAPI interface{} +type CoreAPI interface { + InsertKey(kp crypto.Keypair) +} diff --git a/dot/rpc/modules/author.go b/dot/rpc/modules/author.go index 7b7c2882c63..74546b57727 100644 --- a/dot/rpc/modules/author.go +++ b/dot/rpc/modules/author.go @@ -17,7 +17,12 @@ package modules import ( + "fmt" "net/http" + "reflect" + + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" @@ -33,11 +38,7 @@ type AuthorModule struct { } // KeyInsertRequest is used as model for the JSON -type KeyInsertRequest struct { - KeyType string `json:"keyType"` - Suri string `json:"suri"` - PublicKey []byte `json:"publicKey"` -} +type KeyInsertRequest []string // Extrinsic represents a hex-encoded extrinsic type Extrinsic string @@ -52,7 +53,6 @@ type ExtrinsicOrHash struct { type ExtrinsicOrHashRequest []ExtrinsicOrHash // KeyInsertResponse []byte -// TODO: Waiting on Block type defined here https://github.com/ChainSafe/gossamer/pull/233 type KeyInsertResponse []byte // PendingExtrinsicsResponse is a bi-dimensional array of bytes for allocating the pending extrisics @@ -91,7 +91,29 @@ func NewAuthorModule(coreAPI CoreAPI, txQueueAPI TransactionQueueAPI) *AuthorMod // InsertKey inserts a key into the keystore func (cm *AuthorModule) InsertKey(r *http.Request, req *KeyInsertRequest, res *KeyInsertResponse) error { - _ = cm.coreAPI + keyReq := *req + + pkDec, err := common.HexToHash(keyReq[1]) + if err != nil { + return err + } + + privateKey, err := keystore.DecodePrivateKey(pkDec.ToBytes(), determineKeyType(keyReq[0])) + if err != nil { + return err + } + + keyPair, err := keystore.PrivateKeyToKeypair(privateKey) + if err != nil { + return err + } + + if !reflect.DeepEqual(keyPair.Public().Hex(), keyReq[2]) { + return fmt.Errorf("generated public key does not equal provide public key") + } + + cm.coreAPI.InsertKey(keyPair) + log.Info("[rpc] inserted key into keystore", "key", keyPair.Public().Hex()) return nil } @@ -155,3 +177,26 @@ func (cm *AuthorModule) SubmitExtrinsic(r *http.Request, req *Extrinsic, res *Ex log.Info("[rpc] submitted extrinsic", "tx", vtx, "hash", hash.String()) return nil } + +// determineKeyType takes string as defined in https://github.com/w3f/PSPs/blob/psp-rpc-api/psp-002.md#Key-types +// and returns the crypto.KeyType +func determineKeyType(t string) crypto.KeyType { + // TODO: create separate keystores for different key types, issue #768 + switch t { + case "babe": + return crypto.Sr25519Type + case "gran": + return crypto.Sr25519Type + case "acco": + return crypto.Sr25519Type + case "aura": + return crypto.Sr25519Type + case "imon": + return crypto.Sr25519Type + case "audi": + return crypto.Sr25519Type + case "dumy": + return crypto.Sr25519Type + } + return "unknown keytype" +} diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index a3765efd5a7..855228f7d74 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -5,9 +5,11 @@ import ( "reflect" "testing" + "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/transaction" + "github.com/stretchr/testify/require" ) var testExt = []byte{3, 16, 110, 111, 111, 116, 1, 64, 103, 111, 115, 115, 97, 109, 101, 114, 95, 105, 115, 95, 99, 111, 111, 108} @@ -70,3 +72,36 @@ func TestAuthorModule_SubmitExtrinsic(t *testing.T) { t.Fatalf("Fail: got %v expected %v", inQueue, expected) } } + +func TestAuthorModule_InsertKey_Valid(t *testing.T) { + cs := core.NewTestService(t, nil) + + auth := NewAuthorModule(cs, 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_InValid(t *testing.T) { + cs := core.NewTestService(t, nil) + + auth := NewAuthorModule(cs, 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) { + cs := core.NewTestService(t, nil) + + auth := NewAuthorModule(cs, nil) + req := &KeyInsertRequest{"mack", "0xb7e9185065667390d2ad952a5324e8c365c9bf503dcf97c67a5ce861afe97309", "0x6246ddf254e0b4b4e7dffefc8adf69d212b98ac2b579c362b473fec8c40b4c0a"} + res := &KeyInsertResponse{} + err := auth.InsertKey(nil, req, res) + require.EqualError(t, err, "cannot decode key: invalid key type") + +}