Skip to content

Commit

Permalink
dot/rpc; implement RPC chain_getBlockHash (#752)
Browse files Browse the repository at this point in the history
* implement chain_getBlockHash

* added tests for chain_getBlockHash

* lint cleanup

* cleanup comments
  • Loading branch information
edwardmack authored and ryanchristo committed Jun 24, 2020
1 parent f239476 commit 5706b65
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 19 deletions.
3 changes: 3 additions & 0 deletions dot/rpc/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bytes"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/require"
)
Expand All @@ -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}`)
Expand Down
94 changes: 83 additions & 11 deletions dot/rpc/modules/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
129 changes: 121 additions & 8 deletions dot/rpc/modules/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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{}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}

0 comments on commit 5706b65

Please sign in to comment.