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 methods chain_getHeader and chain_getBlock #745

Merged
merged 22 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3117a8f
refactor to use gorilla/rpc lib
edwardmack Apr 1, 2020
4809384
introduce gorilla rpc package
edwardmack Apr 1, 2020
2f0e03a
address lint issues
edwardmack Apr 1, 2020
724d71e
Merge branch 'development' into ed/309-rpc-chain-methods
edwardmack Apr 1, 2020
2b5f822
implement GetHeader chain rpc func
edwardmack Apr 2, 2020
2a0a162
added getBlock rpc call
edwardmack Apr 2, 2020
5d08ad4
add test for chain getBlock and getHeader
edwardmack Apr 3, 2020
5adb544
added TODO issue #744 to comments
edwardmack Apr 3, 2020
78e6dde
fix spelling, remove extra logging, add chain to default
edwardmack Apr 5, 2020
ac73bde
go fmt-ed
edwardmack Apr 5, 2020
abfbe6e
Merge branch 'development' into ed/309_implement-rpc-chain-methods
edwardmack Apr 6, 2020
d02b6c3
refactor to use gorilla/rpc lib
edwardmack Apr 1, 2020
04cbec0
address lint issues
edwardmack Apr 1, 2020
78d51da
implement GetHeader chain rpc func
edwardmack Apr 2, 2020
9e378cc
added getBlock rpc call
edwardmack Apr 2, 2020
1297f47
add test for chain getBlock and getHeader
edwardmack Apr 3, 2020
244096a
added TODO issue #744 to comments
edwardmack Apr 3, 2020
433e388
fix spelling, remove extra logging, add chain to default
edwardmack Apr 5, 2020
8d0c718
go fmt-ed
edwardmack Apr 5, 2020
b57fffc
Merge branch 'ed/309_implement-rpc-chain-methods' of https://github.c…
edwardmack Apr 6, 2020
495bc40
Merge branch 'development' into ed/309_implement-rpc-chain-methods
edwardmack Apr 6, 2020
677a917
fix merge issues
edwardmack Apr 6, 2020
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
8 changes: 4 additions & 4 deletions dot/rpc/dot_up_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"unicode/utf8"

"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json"
"github.com/gorilla/rpc/v2/json2"
)

// DotUpCodec for overridding default jsonCodec
Expand All @@ -37,15 +37,15 @@ func NewDotUpCodec() *DotUpCodec {
// NewRequest is overridden to inject our codec handler
func (c *DotUpCodec) NewRequest(r *http.Request) rpc.CodecRequest {
outerCR := &DotUpCodecRequest{} // Our custom CR
jsonC := json.NewCodec() // json Codec to create json CR
jsonC := json2.NewCodec() // json Codec to create json CR
innerCR := jsonC.NewRequest(r) // create the json CR, sort of.

// NOTE - innerCR is of the interface type rpc.CodecRequest.
// Because innerCR is of the rpc.CR interface type, we need a
// type assertion in order to assign it to our struct field's type.
// We defined the source of the interface implementation here, so
// we can be confident that innerCR will be of the correct underlying type
outerCR.CodecRequest = innerCR.(*json.CodecRequest)
outerCR.CodecRequest = innerCR.(*json2.CodecRequest)
return outerCR
}

Expand All @@ -56,7 +56,7 @@ func (c *DotUpCodec) NewRequest(r *http.Request) rpc.CodecRequest {
// while maintaining all the other remaining CodecRequest methods from
// gorilla's rpc/json implementation
type DotUpCodecRequest struct {
*json.CodecRequest
*json2.CodecRequest
}

// Method returns the decoded method as a string of the form "Service.Method"
Expand Down
2 changes: 2 additions & 0 deletions dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI)
case "author":
srvc = modules.NewAuthorModule(h.serverConfig.CoreAPI, h.serverConfig.TransactionQueueAPI)
case "chain":
srvc = modules.NewChainModule(h.serverConfig.BlockAPI)
default:
log.Warn("[rpc] Unrecognized module", "module", mod)
continue
Expand Down
2 changes: 1 addition & 1 deletion dot/rpc/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestNewHTTPServer(t *testing.T) {
require.Nil(t, err)
defer res.Body.Close()

require.Equal(t, "400 Bad Request", res.Status)
require.Equal(t, "200 OK", res.Status)

// GET
req, err = http.NewRequest("GET", "http://localhost:8545/", nil)
Expand Down
10 changes: 9 additions & 1 deletion dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package modules

import (
"math/big"

"github.com/ChainSafe/gossamer/dot/core/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/transaction"
)
Expand All @@ -9,7 +12,12 @@ import (
type StorageAPI interface{}

// BlockAPI is the interface for the block state
type BlockAPI interface{}
type BlockAPI interface {
GetHeader(hash common.Hash) (*types.Header, error)
HighestBlockHash() common.Hash
GetBlockByHash(hash common.Hash) (*types.Block, error)
GetBlockHash(blockNumber *big.Int) (*common.Hash, error)
}

// NetworkAPI interface for network state methods
type NetworkAPI interface {
Expand Down
90 changes: 79 additions & 11 deletions dot/rpc/modules/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,39 @@
package modules

import (
"fmt"
"math/big"
"net/http"

"github.com/ChainSafe/gossamer/lib/common"
)

// ChainHashRequest Hash
type ChainHashRequest common.Hash
//type ChainHashRequest common.Hash
type ChainHashRequest string

// ChainBlockNumberRequest Int
type ChainBlockNumberRequest *big.Int
type ChainBlockNumberRequest interface{}

// ChainBlockResponse struct
// TODO: Waiting on Block type defined here https://github.com/ChainSafe/gossamer/pull/233
type ChainBlockResponse struct{}
type ChainBlockResponse struct {
Block ChainBlock `json:"block"`
}

// ChainBlock struct to hold json instance of a block
type ChainBlock struct {
Header ChainBlockHeaderResponse `json:"header"`
Body []string `json:"extrinsics"`
}

// ChainBlockHeaderResponse struct
type ChainBlockHeaderResponse struct{}
type ChainBlockHeaderResponse struct {
ParentHash string `json:"parentHash"`
Number *big.Int `json:"number"`
StateRoot string `json:"stateRoot"`
ExtrinsicsRoot string `json:"extrinsicsRoot"`
Digest [][]byte `json:"digest"`
}

// ChainHashResponse struct
type ChainHashResponse struct {
Expand All @@ -53,21 +68,66 @@ func NewChainModule(api BlockAPI) *ChainModule {
}
}

// GetBlock assigns the ChainModule api to nothing
func (cm *ChainModule) GetBlock(r *http.Request, req *ChainHashRequest, res *ChainBlockResponse) {
_ = cm.blockAPI
// GetBlock Get header and body of a relay chain block. If no block hash is provided,
// the latest block body will be returned.
func (cm *ChainModule) GetBlock(r *http.Request, req *ChainHashRequest, res *ChainBlockResponse) error {
hash, err := cm.hashLookup(req)
if err != nil {
return err
}

block, err := cm.blockAPI.GetBlockByHash(hash)
if err != nil {
return err
}

res.Block.Header.ParentHash = block.Header.ParentHash.String()
res.Block.Header.Number = block.Header.Number
res.Block.Header.StateRoot = block.Header.StateRoot.String()
res.Block.Header.ExtrinsicsRoot = block.Header.ExtrinsicsRoot.String()
res.Block.Header.Digest = block.Header.Digest // TODO: figure out how to get Digest to be a json object (Issue #744)
if *block.Body != nil {
ext, err := block.Body.AsExtrinsics()
if err != nil {
return err
}
for _, e := range ext {
res.Block.Body = append(res.Block.Body, string(e))
}
}
return nil
}

// GetBlockHash isn't implemented properly yet.
func (cm *ChainModule) GetBlockHash(r *http.Request, req *ChainBlockNumberRequest, res *ChainHashResponse) {
// TODO finish this
func (cm *ChainModule) GetBlockHash(r *http.Request, req *ChainBlockNumberRequest, res *ChainHashResponse) error {
// TODO get values from req
return fmt.Errorf("not implemented yet")
}

// GetFinalizedHead isn't implemented properly yet.
func (cm *ChainModule) GetFinalizedHead(r *http.Request, req *EmptyRequest, res *ChainHashResponse) {
}

//GetHeader DB isn't implemented properly yet. Doesn't return block headers
func (cm *ChainModule) GetHeader(r *http.Request, req *ChainHashRequest, res *ChainBlockHeaderResponse) {
//GetHeader Get header of a relay chain block. If no block hash is provided, the latest block header will be returned.
func (cm *ChainModule) GetHeader(r *http.Request, req *ChainHashRequest, res *ChainBlockHeaderResponse) error {
hash, err := cm.hashLookup(req)
if err != nil {
return err
}

header, err := cm.blockAPI.GetHeader(hash)
if err != nil {
return err
}

res.ParentHash = header.ParentHash.String()
res.Number = header.Number
res.StateRoot = header.StateRoot.String()
res.ExtrinsicsRoot = header.ExtrinsicsRoot.String()
res.Digest = header.Digest // TODO: figure out how to get Digest to be a json object (Issue #744)

return nil
}

// SubscribeFinalizedHeads isn't implemented properly yet.
Expand All @@ -77,3 +137,11 @@ func (cm *ChainModule) SubscribeFinalizedHeads(r *http.Request, req *EmptyReques
// SubscribeNewHead isn't implemented properly yet.
func (cm *ChainModule) SubscribeNewHead(r *http.Request, req *EmptyRequest, res *ChainBlockHeaderResponse) {
}

func (cm *ChainModule) hashLookup(req *ChainHashRequest) (common.Hash, error) {
if len(*req) == 0 {
hash := cm.blockAPI.HighestBlockHash()
return hash, nil
}
return common.HexToHash(string(*req))
}
161 changes: 161 additions & 0 deletions dot/rpc/modules/chain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package modules

import (
"math/big"
"testing"

"github.com/ChainSafe/gossamer/dot/core/types"
"github.com/ChainSafe/gossamer/dot/state"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/ChainSafe/gossamer/lib/utils"
"github.com/stretchr/testify/require"
)

func newChainService(t *testing.T) *state.Service {
testDir := utils.NewTestDir(t)
defer utils.RemoveTestDir(t)
stateSrvc := state.NewService(testDir)
genesisHeader, err := types.NewHeader(common.NewHash([]byte{0}), big.NewInt(0), trie.EmptyHash, trie.EmptyHash, [][]byte{})
if err != nil {
t.Fatal(err)
}

tr := trie.NewEmptyTrie()

err = stateSrvc.Initialize(genesisHeader, tr)
if err != nil {
t.Fatal(err)
}
err = stateSrvc.Start()
if err != nil {
t.Fatal(err)
}
return stateSrvc
}

func TestChainGetHeader_Genesis(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)
expected := &ChainBlockHeaderResponse{
ParentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
Number: big.NewInt(0),
StateRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
ExtrinsicsRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
Digest: [][]byte{},
}
res := &ChainBlockHeaderResponse{}
req := ChainHashRequest("0xc375f478c6887dbcc2d1a4dbcc25f330b3df419325ece49cddfe5a0555663b7e")
err := svc.GetHeader(nil, &req, res)
require.Nil(t, err)

require.Equal(t, expected, res)
}

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",
Digest: [][]byte{},
}
res := &ChainBlockHeaderResponse{}
req := ChainHashRequest("") // empty request should return latest hash
err := svc.GetHeader(nil, &req, res)
require.Nil(t, err)

require.Equal(t, expected, res)
}

func TestChainGetHeader_NotFound(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)

res := &ChainBlockHeaderResponse{}
req := ChainHashRequest("0xea374832a2c3997280d2772c10e6e5b0b493ccd3d09c0ab14050320e34076c2c")
err := svc.GetHeader(nil, &req, res)
require.EqualError(t, err, "Key not found")
}

func TestChainGetHeader_Error(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)

res := &ChainBlockHeaderResponse{}
req := ChainHashRequest("zz")
err := svc.GetHeader(nil, &req, res)
require.EqualError(t, err, "could not byteify non 0x prefixed string")
}

func TestChainGetBlock_Genesis(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)
header := &ChainBlockHeaderResponse{
ParentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
Number: big.NewInt(0),
StateRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
ExtrinsicsRoot: "0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
Digest: [][]byte{},
}
expected := &ChainBlockResponse{
Block: ChainBlock{
Header: *header,
Body: nil,
},
}

res := &ChainBlockResponse{}
req := ChainHashRequest("0xc375f478c6887dbcc2d1a4dbcc25f330b3df419325ece49cddfe5a0555663b7e")
err := svc.GetBlock(nil, &req, res)
require.Nil(t, err)

require.Equal(t, expected, res)
}

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",
Digest: [][]byte{},
}
expected := &ChainBlockResponse{
Block: ChainBlock{
Header: *header,
Body: nil,
},
}

res := &ChainBlockResponse{}
req := ChainHashRequest("") // empty request should return latest block
err := svc.GetBlock(nil, &req, res)
require.Nil(t, err)

require.Equal(t, expected, res)
}

func TestChainGetBlock_NoFound(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)

res := &ChainBlockResponse{}
req := ChainHashRequest("0xea374832a2c3997280d2772c10e6e5b0b493ccd3d09c0ab14050320e34076c2c")
err := svc.GetBlock(nil, &req, res)
require.EqualError(t, err, "Key not found")
}

func TestChainGetBlock_Error(t *testing.T) {
chain := newChainService(t)
svc := NewChainModule(chain.Block)

res := &ChainBlockResponse{}
req := ChainHashRequest("zz")
err := svc.GetBlock(nil, &req, res)
require.EqualError(t, err, "could not byteify non 0x prefixed string")
}
11 changes: 11 additions & 0 deletions dot/state/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,17 @@ func (bs *BlockState) GetBlockByNumber(blockNumber *big.Int) (*types.Block, erro
return block, nil
}

// GetBlockHash returns block hash for a given blockNumber
func (bs *BlockState) GetBlockHash(blockNumber *big.Int) (*common.Hash, error) {
// First retrieve the block hash in a byte array based on the block number from the database
byteHash, err := bs.db.Get(headerHashKey(blockNumber.Uint64()))
if err != nil {
return nil, fmt.Errorf("cannot get block %d: %s", blockNumber, err)
}
hash := common.NewHash(byteHash)
return &hash, nil
}

// SetHeader will set the header into DB
func (bs *BlockState) SetHeader(header *types.Header) error {
bs.lock.Lock()
Expand Down
2 changes: 1 addition & 1 deletion node/gssmr/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ nomdns = false
enabled = false
port = 8545
host = "localhost"
modules = ["system", "author"]
modules = ["system", "author", "chain"]
Loading