diff --git a/dot/rpc/http_test.go b/dot/rpc/http_test.go index 5e35b5ad71..c6fe72835c 100644 --- a/dot/rpc/http_test.go +++ b/dot/rpc/http_test.go @@ -19,6 +19,7 @@ import ( "bytes" "net/http" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -33,6 +34,8 @@ func TestNewHTTPServer(t *testing.T) { err := s.Start() require.Nil(t, err) + time.Sleep(time.Second) // give server a second to start + // Valid request client := &http.Client{} data := []byte(`{"jsonrpc":"2.0","method":"system_name","params":[],"id":1}`) diff --git a/dot/rpc/modules/chain.go b/dot/rpc/modules/chain.go index 1d355f322c..e510404893 100644 --- a/dot/rpc/modules/chain.go +++ b/dot/rpc/modules/chain.go @@ -20,15 +20,16 @@ import ( "fmt" "math/big" "net/http" + "reflect" + "regexp" "github.com/ChainSafe/gossamer/lib/common" ) -// ChainHashRequest Hash -//type ChainHashRequest common.Hash +// ChainHashRequest Hash as a string type ChainHashRequest string -// ChainBlockNumberRequest Int +// ChainBlockNumberRequest interface is it can accept string or float64 or [] type ChainBlockNumberRequest interface{} // ChainBlockResponse struct @@ -51,10 +52,8 @@ type ChainBlockHeaderResponse struct { Digest [][]byte `json:"digest"` } -// ChainHashResponse struct -type ChainHashResponse struct { - ChainHash common.Hash `json:"chainHash"` -} +// ChainHashResponse interface to handle response +type ChainHashResponse interface{} // ChainModule is an RPC module providing access to storage API points. type ChainModule struct { @@ -98,11 +97,29 @@ func (cm *ChainModule) GetBlock(r *http.Request, req *ChainHashRequest, res *Cha return nil } -// GetBlockHash isn't implemented properly yet. -// TODO finish this +// GetBlockHash Get hash of the 'n-th' block in the canon chain. If no parameters are provided, +// the latest block hash gets returned. func (cm *ChainModule) GetBlockHash(r *http.Request, req *ChainBlockNumberRequest, res *ChainHashResponse) error { - // TODO get values from req - return fmt.Errorf("not implemented yet") + // if request is empty, return highest hash + if *req == nil || reflect.ValueOf(*req).Len() == 0 { + *res = cm.blockAPI.HighestBlockHash().String() + return nil + } + + val, err := cm.unwindRequest(*req) + // if result only returns 1 value, just use that (instead of array) + if len(val) == 1 { + *res = val[0] + } else { + *res = val + } + + return err +} + +// GetHead alias for GetBlockHash +func (cm *ChainModule) GetHead(r *http.Request, req *ChainBlockNumberRequest, res *ChainHashResponse) error { + return cm.GetBlockHash(r, req, res) } // GetFinalizedHead isn't implemented properly yet. @@ -145,3 +162,58 @@ func (cm *ChainModule) hashLookup(req *ChainHashRequest) (common.Hash, error) { } return common.HexToHash(string(*req)) } + +// unwindRequest takes request interface slice and makes call for each element +func (cm *ChainModule) unwindRequest(req interface{}) ([]string, error) { + res := make([]string, 0) + switch x := (req).(type) { + case []interface{}: + for _, v := range x { + u, err := cm.unwindRequest(v) + if err != nil { + return nil, err + } + res = append(res, u[:]...) + } + case interface{}: + h, err := cm.lookupHashByInterface(x) + if err != nil { + return nil, err + } + res = append(res, h) + } + return res, nil +} + +// lookupHashByInterface parses given interface to determine block number, then +// finds hash for that block number +func (cm *ChainModule) lookupHashByInterface(i interface{}) (string, error) { + num := new(big.Int) + switch x := i.(type) { + case float64: + f := big.NewFloat(x) + f.Int(num) + case string: + // remove leading 0x (if there is one) + re, err := regexp.Compile(`0x`) + if err != nil { + return "", err + } + x = re.ReplaceAllString(x, "") + + // cast string to big.Int + _, ok := num.SetString(x, 10) + if !ok { + return "", fmt.Errorf("error setting number from string") + } + + default: + return "", fmt.Errorf("unknown request number type: %T", x) + } + + h, err := cm.blockAPI.GetBlockHash(num) + if err != nil { + return "", err + } + return h.String(), nil +} diff --git a/dot/rpc/modules/chain_test.go b/dot/rpc/modules/chain_test.go index e753ed4e99..c13ff97e0f 100644 --- a/dot/rpc/modules/chain_test.go +++ b/dot/rpc/modules/chain_test.go @@ -23,17 +23,71 @@ func newChainService(t *testing.T) *state.Service { tr := trie.NewEmptyTrie() + stateSrvc.UseMemDB() + err = stateSrvc.Initialize(genesisHeader, tr) if err != nil { t.Fatal(err) } + err = stateSrvc.Start() if err != nil { t.Fatal(err) } + + err = loadTestBlocks(genesisHeader.Hash(), stateSrvc.Block) + if err != nil { + t.Fatal(err) + } return stateSrvc } +func loadTestBlocks(gh common.Hash, bs *state.BlockState) error { + // Create header + header0 := &types.Header{ + Number: big.NewInt(0), + Digest: [][]byte{}, + ParentHash: gh, + } + // Create blockHash + blockHash0 := header0.Hash() + // BlockBody with fake extrinsics + blockBody0 := types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + + block0 := &types.Block{ + Header: header0, + Body: &blockBody0, + } + + err := bs.AddBlock(block0) + if err != nil { + return err + } + + // Create header & blockData for block 1 + header1 := &types.Header{ + Number: big.NewInt(1), + Digest: [][]byte{}, + ParentHash: blockHash0, + } + + // Create Block with fake extrinsics + blockBody1 := types.Body{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + + block1 := &types.Block{ + Header: header1, + Body: &blockBody1, + } + + // Add the block1 to the DB + err = bs.AddBlock(block1) + if err != nil { + return err + } + + return nil +} + func TestChainGetHeader_Genesis(t *testing.T) { chain := newChainService(t) svc := NewChainModule(chain.Block) @@ -56,10 +110,10 @@ func TestChainGetHeader_Latest(t *testing.T) { chain := newChainService(t) svc := NewChainModule(chain.Block) expected := &ChainBlockHeaderResponse{ - ParentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - Number: big.NewInt(0), - StateRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", - ExtrinsicsRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", + ParentHash: "0xdbfdd87392d9ee52f499610582737daceecf83dc3ad7946fcadeb01c86e1ef75", + Number: big.NewInt(1), + StateRoot: "0x0000000000000000000000000000000000000000000000000000000000000000", + ExtrinsicsRoot: "0x0000000000000000000000000000000000000000000000000000000000000000", Digest: [][]byte{}, } res := &ChainBlockHeaderResponse{} @@ -119,10 +173,10 @@ func TestChainGetBlock_Latest(t *testing.T) { chain := newChainService(t) svc := NewChainModule(chain.Block) header := &ChainBlockHeaderResponse{ - ParentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - Number: big.NewInt(0), - StateRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", - ExtrinsicsRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314", + ParentHash: "0xdbfdd87392d9ee52f499610582737daceecf83dc3ad7946fcadeb01c86e1ef75", + Number: big.NewInt(1), + StateRoot: "0x0000000000000000000000000000000000000000000000000000000000000000", + ExtrinsicsRoot: "0x0000000000000000000000000000000000000000000000000000000000000000", Digest: [][]byte{}, } expected := &ChainBlockResponse{ @@ -159,3 +213,62 @@ func TestChainGetBlock_Error(t *testing.T) { err := svc.GetBlock(nil, &req, res) require.EqualError(t, err, "could not byteify non 0x prefixed string") } + +func TestChainGetBlockHash_Latest(t *testing.T) { + chain := newChainService(t) + svc := NewChainModule(chain.Block) + + resString := string("") + res := ChainHashResponse(resString) + req := ChainBlockNumberRequest(nil) + err := svc.GetBlockHash(nil, &req, &res) + + require.Nil(t, err) + + require.Equal(t, "0x80d653de440352760f89366c302c02a92ab059f396e2bfbf7f860e6e256cd698", res) +} + +func TestChainGetBlockHash_ByNumber(t *testing.T) { + chain := newChainService(t) + svc := NewChainModule(chain.Block) + + resString := string("") + res := ChainHashResponse(resString) + req := ChainBlockNumberRequest("1") + err := svc.GetBlockHash(nil, &req, &res) + + require.Nil(t, err) + + require.Equal(t, "0x80d653de440352760f89366c302c02a92ab059f396e2bfbf7f860e6e256cd698", res) +} + +func TestChainGetBlockHash_ByHex(t *testing.T) { + chain := newChainService(t) + svc := NewChainModule(chain.Block) + + resString := string("") + res := ChainHashResponse(resString) + req := ChainBlockNumberRequest("0x01") + err := svc.GetBlockHash(nil, &req, &res) + + require.Nil(t, err) + + require.Equal(t, "0x80d653de440352760f89366c302c02a92ab059f396e2bfbf7f860e6e256cd698", res) +} + +func TestChainGetBlockHash_Array(t *testing.T) { + chain := newChainService(t) + svc := NewChainModule(chain.Block) + + resString := string("") + res := ChainHashResponse(resString) + nums := make([]interface{}, 2) + nums[0] = float64(0) // as number + nums[1] = string("0x01") // as hex string + req := ChainBlockNumberRequest(nums) + err := svc.GetBlockHash(nil, &req, &res) + + require.Nil(t, err) + + require.Equal(t, []string{"0xdbfdd87392d9ee52f499610582737daceecf83dc3ad7946fcadeb01c86e1ef75", "0x80d653de440352760f89366c302c02a92ab059f396e2bfbf7f860e6e256cd698"}, res) +}