Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dot/rpc; implement RPC chain_getBlockHash #752

Merged
merged 5 commits into from
Apr 7, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's another test function in state called addBlocksToState where you can specify the depth (the branching part of that function probably isn't too relevant here) that might be useful in the future for adding more than 2 blocks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent, I thought there may be something, but I didn't look deeply enough. Will come in handy.

// 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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just a comment to explain where this and the other test hashes are coming from?

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)
}