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

[rpc] support for eip-1898 - support block number or hash on state-related methods #4181

Merged
merged 3 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,11 @@ func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []com
return bc.hc.GetBlockHashesFromHash(hash, max)
}

// GetCanonicalHash returns the canonical hash for a given block number
func (bc *BlockChain) GetCanonicalHash(number uint64) common.Hash {
return bc.hc.GetCanonicalHash(number)
}

// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
// number of blocks to be individually checked before we reach the canonical chain.
Expand Down
4 changes: 4 additions & 0 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ func (hc *HeaderChain) getHashByNumber(number uint64) common.Hash {
return hash
}

func (hc *HeaderChain) GetCanonicalHash(number uint64) common.Hash {
return rawdb.ReadCanonicalHash(hc.chainDb, number)
}

// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func (hc *HeaderChain) CurrentHeader() *block.Header {
Expand Down
27 changes: 24 additions & 3 deletions eth/rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
return nil
}

// MarshalText implements encoding.TextMarshaler. It marshals:
// - "latest", "earliest" or "pending" as strings
// - other numbers as hex
func (bn BlockNumber) MarshalText() ([]byte, error) {
switch bn {
case EarliestBlockNumber:
return []byte("earliest"), nil
case LatestBlockNumber:
return []byte("latest"), nil
case PendingBlockNumber:
return []byte("pending"), nil
default:
return hexutil.Uint64(bn).MarshalText()
}
}

func (bn BlockNumber) Int64() int64 {
return (int64)(bn)
}
Expand All @@ -127,9 +143,14 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
return nil
}
var input string
err = json.Unmarshal(data, &input)
if err != nil {
return err
if err := json.Unmarshal(data, &input); err != nil {
var numInput int64 // old hmy rpc use number type as input
if err := json.Unmarshal(data, &numInput); err != nil {
return err
}
bn := BlockNumber(numInput)
bnh.BlockNumber = &bn
return nil
}
switch input {
case "earliest":
Expand Down
60 changes: 59 additions & 1 deletion eth/rpc/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package rpc

import (
"encoding/json"
"reflect"
"testing"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -81,7 +82,7 @@ func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) {
6: {`"0x12"`, false, BlockNumberOrHashWithNumber(18)},
7: {`"0x7fffffffffffffff"`, false, BlockNumberOrHashWithNumber(math.MaxInt64)},
8: {`"0x8000000000000000"`, true, BlockNumberOrHash{}},
9: {"0", true, BlockNumberOrHash{}},
9: {"0", false, BlockNumberOrHashWithNumber(0)}, // different from eth, because we need to keep compatibility with old hmy rpc
10: {`"ff"`, true, BlockNumberOrHash{}},
11: {`"pending"`, false, BlockNumberOrHashWithNumber(PendingBlockNumber)},
12: {`"latest"`, false, BlockNumberOrHashWithNumber(LatestBlockNumber)},
Expand Down Expand Up @@ -122,3 +123,60 @@ func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) {
}
}
}

func TestBlockNumberOrHash_WithNumber_MarshalAndUnmarshal(t *testing.T) {
tests := []struct {
name string
number int64
}{
{"max", math.MaxInt64},
{"pending", int64(PendingBlockNumber)},
{"latest", int64(LatestBlockNumber)},
{"earliest", int64(EarliestBlockNumber)},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
bnh := BlockNumberOrHashWithNumber(BlockNumber(test.number))
marshalled, err := json.Marshal(bnh)
if err != nil {
t.Fatal("cannot marshal:", err)
}
var unmarshalled BlockNumberOrHash
err = json.Unmarshal(marshalled, &unmarshalled)
if err != nil {
t.Fatal("cannot unmarshal:", err)
}
if !reflect.DeepEqual(bnh, unmarshalled) {
t.Fatalf("wrong result: expected %v, got %v", bnh, unmarshalled)
}
})
}
}

func TestBlockNumberOrHash_Unmarshal_Compatibility(t *testing.T) {
tests := []struct {
name string
number int64
}{
{"max", math.MaxInt64},
{"pending", int64(PendingBlockNumber)},
{"latest", int64(LatestBlockNumber)},
{"earliest", int64(EarliestBlockNumber)},
}

for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
input, _ := json.Marshal(test.number)
var unmarshalled BlockNumberOrHash
err := json.Unmarshal([]byte(input), &unmarshalled)
if err != nil {
t.Fatal("cannot unmarshal:", err)
}
if number, isNumber := unmarshalled.Number(); !isNumber || int64(number) != test.number {
t.Fatalf("wrong result: expected %v, got %v", test.number, unmarshalled)
}
})
}
}
46 changes: 44 additions & 2 deletions hmy/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,30 @@ func (hmy *Harmony) GetCurrentBadBlocks() []core.BadBlock {
return hmy.BlockChain.BadBlocks()
}

func (hmy *Harmony) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return hmy.BlockByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header := hmy.BlockChain.GetHeaderByHash(hash)
if header == nil {
return nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && hmy.BlockChain.GetCanonicalHash(header.Number().Uint64()) != hash {
return nil, errors.New("hash is not currently canonical")
}
block := hmy.BlockChain.GetBlock(hash, header.Number().Uint64())
if block == nil {
return nil, errors.New("header found, but block body is missing")
}
return block, nil
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}

// GetBalance returns balance of an given address.
func (hmy *Harmony) GetBalance(ctx context.Context, address common.Address, blockNum rpc.BlockNumber) (*big.Int, error) {
s, _, err := hmy.StateAndHeaderByNumber(ctx, blockNum)
func (hmy *Harmony) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*big.Int, error) {
s, _, err := hmy.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if s == nil || err != nil {
return nil, err
}
Expand Down Expand Up @@ -268,6 +289,27 @@ func (hmy *Harmony) StateAndHeaderByNumber(ctx context.Context, blockNum rpc.Blo
return stateDb, header, err
}

func (hmy *Harmony) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.DB, *block.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return hmy.StateAndHeaderByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header, err := hmy.HeaderByHash(ctx, hash)
if err != nil {
return nil, nil, err
}
if header == nil {
return nil, nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && hmy.BlockChain.GetCanonicalHash(header.Number().Uint64()) != hash {
return nil, nil, errors.New("hash is not currently canonical")
}
stateDb, err := hmy.BlockChain.StateAt(header.Root())
return stateDb, header, err
}
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}

// GetLeaderAddress returns the one address of the leader, given the coinbaseAddr.
// Note that the coinbaseAddr is overloaded with the BLS pub key hash in staking era.
func (hmy *Harmony) GetLeaderAddress(coinbaseAddr common.Address, epoch *big.Int) string {
Expand Down
4 changes: 2 additions & 2 deletions rosetta/services/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (s *AccountAPI) AccountBalance(
})
}
blockNum := rpc.BlockNumber(block.Header().Header.Number().Int64())
balance := new(big.Int)
var balance *big.Int

if request.AccountIdentifier.SubAccount != nil {
// indicate it may be a request for delegated balance
Expand All @@ -69,7 +69,7 @@ func (s *AccountAPI) AccountBalance(
return nil, rosettaError
}
} else {
balance, err = s.hmy.GetBalance(ctx, addr, blockNum)
balance, err = s.hmy.GetBalance(ctx, addr, rpc.BlockNumberOrHashWithNumber(blockNum))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LuttyYang please help to check the change of rosetta

if err != nil {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": "invalid address",
Expand Down
6 changes: 3 additions & 3 deletions rosetta/services/call_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func (c *CallAPIService) getStorageAt(
})
}

res, err := contractAPI.GetStorageAt(ctx, args.Addr, args.Key, rpc2.BlockNumber(args.BlockNum))
res, err := contractAPI.GetStorageAt(ctx, args.Addr, args.Key, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(args.BlockNum)))
if err != nil {
return nil, common.NewError(common.ErrCallExecute, map[string]interface{}{
"message": errors.WithMessage(err, "get storage at error").Error(),
Expand All @@ -366,7 +366,7 @@ func (c *CallAPIService) getCode(
"message": errors.WithMessage(err, "invalid parameters").Error(),
})
}
code, err := contractAPI.GetCode(ctx, args.Addr, rpc2.BlockNumber(args.BlockNum))
code, err := contractAPI.GetCode(ctx, args.Addr, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(args.BlockNum)))
if err != nil {
return nil, common.NewError(common.ErrCallExecute, map[string]interface{}{
"message": errors.WithMessage(err, "get code error").Error(),
Expand All @@ -389,7 +389,7 @@ func (c *CallAPIService) call(
"message": errors.WithMessage(err, "invalid parameters").Error(),
})
}
data, err := contractAPI.Call(ctx, args.CallArgs, rpc2.BlockNumber(args.BlockNum))
data, err := contractAPI.Call(ctx, args.CallArgs, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(args.BlockNum)))
if err != nil {
return nil, common.NewError(common.ErrCallExecute, map[string]interface{}{
"message": errors.WithMessage(err, "call smart contract error").Error(),
Expand Down
7 changes: 4 additions & 3 deletions rosetta/services/construction_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,15 @@ func (s *ConstructAPI) ConstructionMetadata(
})
}

latest := ethRpc.BlockNumberOrHashWithNumber(ethRpc.LatestBlockNumber)
var estGasUsed uint64
if !isStakingOperation(options.OperationType) {
if options.OperationType == common.ContractCreationOperation {
estGasUsed, err = rpc.EstimateGas(ctx, s.hmy, rpc.CallArgs{From: senderAddr, Data: &data}, nil)
estGasUsed, err = rpc.EstimateGas(ctx, s.hmy, rpc.CallArgs{From: senderAddr, Data: &data}, latest, nil)
estGasUsed *= 2 // HACK to account for imperfect contract creation estimation
} else {
estGasUsed, err = rpc.EstimateGas(
ctx, s.hmy, rpc.CallArgs{From: senderAddr, To: &contractAddress, Data: &data}, nil,
ctx, s.hmy, rpc.CallArgs{From: senderAddr, To: &contractAddress, Data: &data}, latest, nil,
)
}
} else {
Expand Down Expand Up @@ -269,7 +270,7 @@ func (s *ConstructAPI) ConstructionMetadata(
callArgs.To = &contractAddress
}
evmExe, err := rpc.DoEVMCall(
ctx, s.hmy, callArgs, ethRpc.LatestBlockNumber, rpc.CallTimeout,
ctx, s.hmy, callArgs, latest, rpc.CallTimeout,
)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
Expand Down
22 changes: 4 additions & 18 deletions rpc/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (s *PublicBlockchainService) getBalanceByBlockNumber(
if err != nil {
return nil, err
}
balance, err := s.hmy.GetBalance(ctx, addr, blockNum)
balance, err := s.hmy.GetBalance(ctx, addr, rpc.BlockNumberOrHashWithNumber(blockNum))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -787,7 +787,7 @@ type StorageResult struct {

// GetHeaderByNumberRLPHex returns block header at given number by `hex(rlp(header))`
func (s *PublicBlockchainService) GetProof(
ctx context.Context, address common.Address, storageKeys []string, blockNumber BlockNumber) (ret *AccountResult, err error) {
ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (ret *AccountResult, err error) {
timer := DoMetricRPCRequest(GetProof)
defer DoRPCRequestDuration(GetProof, timer)

Expand All @@ -803,23 +803,9 @@ func (s *PublicBlockchainService) GetProof(
return
}

// Process number based on version
blockNum := blockNumber.EthBlockNumber()

// Ensure valid block number
if s.version != Eth && isBlockGreaterThanLatest(s.hmy, blockNum) {
err = ErrRequestedBlockTooHigh
return
}

// Fetch Header
header, err := s.hmy.HeaderByNumber(ctx, blockNum)
if header == nil && err != nil {
return
}
state, err := s.hmy.BeaconChain.StateAt(header.Root())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlan35 do you know why here use BeaconChain but BlockChain? what's the difference between them?

state, _, err := s.hmy.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return
return nil, err
}

storageTrie := state.StorageTrie(address)
Expand Down
Loading