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 1 commit
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
17 changes: 17 additions & 0 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 @@ -114,6 +130,7 @@ type BlockNumberOrHash struct {
}

func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
fmt.Println("UnmarshalJSON: bnh")
type erased BlockNumberOrHash
e := erased{}
err := json.Unmarshal(data, &e)
Expand Down
31 changes: 31 additions & 0 deletions 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 @@ -122,3 +123,33 @@ 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)
}
})
}
}
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
24 changes: 8 additions & 16 deletions rpc/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,19 @@ func (s *PublicContractService) wait(limiter *rate.Limiter, ctx context.Context)
// Call executes the given transaction on the state for the given block number.
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
func (s *PublicContractService) Call(
ctx context.Context, args CallArgs, blockNumber BlockNumber,
ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash,
) (hexutil.Bytes, error) {
timer := DoMetricRPCRequest(Call)
defer DoRPCRequestDuration(Call, timer)

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

err := s.wait(s.limiterCall, ctx)
if err != nil {
DoMetricRPCQueryInfo(Call, RateLimitedNumber)
return nil, err
}

// Execute call
result, err := DoEVMCall(ctx, s.hmy, args, blockNum, CallTimeout)
result, err := DoEVMCall(ctx, s.hmy, args, blockNrOrHash, CallTimeout)
if err != nil {
return nil, err
}
Expand All @@ -93,21 +90,18 @@ func (s *PublicContractService) Call(

// GetCode returns the code stored at the given address in the state for the given block number.
func (s *PublicContractService) GetCode(
ctx context.Context, addr string, blockNumber BlockNumber,
ctx context.Context, addr string, blockNrOrHash rpc.BlockNumberOrHash,
) (hexutil.Bytes, error) {
timer := DoMetricRPCRequest(GetCode)
defer DoRPCRequestDuration(GetCode, timer)

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

// Fetch state
address, err := hmyCommon.ParseAddr(addr)
if err != nil {
DoMetricRPCQueryInfo(GetCode, FailedNumber)
return nil, err
}
state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum)
state, _, err := s.hmy.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
DoMetricRPCQueryInfo(GetCode, FailedNumber)
return nil, err
Expand All @@ -122,15 +116,13 @@ func (s *PublicContractService) GetCode(
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (s *PublicContractService) GetStorageAt(
ctx context.Context, addr string, key string, blockNumber BlockNumber,
ctx context.Context, addr string, key string, blockNrOrHash rpc.BlockNumberOrHash,
) (hexutil.Bytes, error) {
timer := DoMetricRPCRequest(GetStorageAt)
defer DoRPCRequestDuration(GetStorageAt, timer)
// Process number based on version
blockNum := blockNumber.EthBlockNumber()

// Fetch state
state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum)
state, _, err := s.hmy.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
DoMetricRPCQueryInfo(GetStorageAt, FailedNumber)
return nil, err
Expand All @@ -148,7 +140,7 @@ func (s *PublicContractService) GetStorageAt(

// DoEVMCall executes an EVM call
func DoEVMCall(
ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNum rpc.BlockNumber,
ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash,
timeout time.Duration,
) (core.ExecutionResult, error) {
defer func(start time.Time) {
Expand All @@ -158,7 +150,7 @@ func DoEVMCall(
}(time.Now())

// Fetch state
state, header, err := hmy.StateAndHeaderByNumber(ctx, blockNum)
state, header, err := hmy.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
DoMetricRPCQueryInfo(DoEvmCall, FailedNumber)
return core.ExecutionResult{}, err
Expand Down
4 changes: 2 additions & 2 deletions rpc/eth/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ func NewPublicEthService(hmy *hmy.Harmony, namespace string) rpc.API {
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (s *PublicEthService) GetBalance(
ctx context.Context, address string, blockNr rpc.BlockNumber,
ctx context.Context, address string, blockNrOrHash rpc.BlockNumberOrHash,
) (*hexutil.Big, error) {
addr, err := internal_common.ParseAddr(address)
if err != nil {
return nil, err
}
balance, err := s.hmy.GetBalance(ctx, addr, blockNr)
balance, err := s.hmy.GetBalance(ctx, addr, blockNrOrHash)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion rpc/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (s *PublicStakingService) 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
Loading