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

Add CallWithState method to fetch last state ID from incoming chain #883

Merged
merged 1 commit into from
Jun 1, 2023
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
2 changes: 2 additions & 0 deletions consensus/bor/api/caller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)

//go:generate mockgen -destination=./caller_mock.go -package=api . Caller
type Caller interface {
Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
CallWithState(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *ethapi.StateOverride) (hexutil.Bytes, error)
}
16 changes: 16 additions & 0 deletions consensus/bor/api/caller_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 22 additions & 10 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1184,30 +1184,42 @@ func (c *Bor) CommitStates(
fetchStart := time.Now()
number := header.Number.Uint64()

_lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1)
if err != nil {
return nil, err
}

var to time.Time
var (
_lastStateID *big.Int
from uint64
to time.Time
err error
)

if c.config.IsIndore(header.Number) {
stateSyncDelay := c.config.FetchStateSyncDelay(number)
// Fetch the LastStateId from contract via current state instance
_lastStateID, err = c.GenesisContractsClient.LastStateId(state, number-1, header.ParentHash)
if err != nil {
return nil, err
}

stateSyncDelay := c.config.CalculateStateSyncDelay(number)
to = time.Unix(int64(header.Time-stateSyncDelay), 0)
} else {
_lastStateID, err = c.GenesisContractsClient.LastStateId(nil, number-1, header.ParentHash)
if err != nil {
return nil, err
}

to = time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.CalculateSprint(number)).Time), 0)
}

lastStateID := _lastStateID.Uint64()
from = lastStateID + 1

log.Info(
"Fetching state updates from Heimdall",
"fromID", lastStateID+1,
"fromID", from,
"to", to.Format(time.RFC3339))

eventRecords, err := c.HeimdallClient.StateSyncEvents(ctx, lastStateID+1, to.Unix())
eventRecords, err := c.HeimdallClient.StateSyncEvents(ctx, from, to.Unix())
if err != nil {
log.Error("Error occurred when fetching state sync events", "stateID", lastStateID+1, "error", err)
log.Error("Error occurred when fetching state sync events", "fromID", from, "to", to.Unix(), "err", err)
}

if c.config.OverrideStateSyncRecords != nil {
Expand Down
10 changes: 6 additions & 4 deletions consensus/bor/contract/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ func (gc *GenesisContractsClient) CommitState(
return gasUsed, nil
}

func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) {
blockNr := rpc.BlockNumber(snapshotNumber)
func (gc *GenesisContractsClient) LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error) {
blockNr := rpc.BlockNumber(number)

const method = "lastStateId"

Expand All @@ -116,11 +116,13 @@ func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int,
toAddress := common.HexToAddress(gc.StateReceiverContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))

result, err := gc.ethAPI.Call(context.Background(), ethapi.TransactionArgs{
// Do a call with state so that we can fetch the last state ID from a given (incoming)
// state instead of local(canonical) chain.
result, err := gc.ethAPI.CallWithState(context.Background(), ethapi.TransactionArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
}, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil)
}, rpc.BlockNumberOrHash{BlockNumber: &blockNr, BlockHash: &hash}, state, nil)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion consensus/bor/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bor
import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/core/state"
Expand All @@ -12,5 +13,5 @@ import (
//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
type GenesisContract interface {
CommitState(event *clerk.EventRecordWithTime, state *state.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error)
LastStateId(snapshotNumber uint64) (*big.Int, error)
LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error)
}
9 changes: 5 additions & 4 deletions consensus/bor/genesis_contract_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 17 additions & 17 deletions consensus/bor/span_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,8 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err
}
}
result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, b.backend.RPCEVMTimeout(), b.backend.RPCGasCap())

result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, nil, b.backend.RPCEVMTimeout(), b.backend.RPCGasCap())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1076,7 +1077,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.TransactionArgs
}) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, p.backend.RPCEVMTimeout(), p.backend.RPCGasCap())
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, nil, p.backend.RPCEVMTimeout(), p.backend.RPCGasCap())
if err != nil {
return nil, err
}
Expand Down
62 changes: 56 additions & 6 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,16 +987,40 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
return nil
}

func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
var (
header *types.Header
err error
)

// Fetch the state and header from blockNumberOrHash if it's coming from normal eth_call path.
if state == nil {
state, header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
} else {
// Fetch the header from the given blockNumberOrHash. Note that this path is only taken
// when we're doing a call from bor consensus to fetch data from genesis contracts. It's
// necessary to fetch header using header hash as we might be experiencing a reorg and there
// can be multiple headers with same number.
header, err = b.HeaderByHash(ctx, *blockNrOrHash.BlockHash)
if header == nil || err != nil {
log.Warn("Error fetching header on CallWithState", "err", err)
return nil, err
}
}

if err := overrides.Apply(state); err != nil {
return nil, err
}

return doCallWithState(ctx, b, args, header, state, timeout, globalGasCap)
}

func doCallWithState(ctx context.Context, b Backend, args TransactionArgs, header *types.Header, state *state.StateDB, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
Expand Down Expand Up @@ -1080,7 +1104,32 @@ func (e *revertError) ErrorData() interface{} {
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
result, err := DoCall(ctx, s.b, args, blockNrOrHash, nil, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
if err != nil {
return nil, err
}

if int(s.b.RPCRpcReturnDataLimit()) > 0 && len(result.ReturnData) > int(s.b.RPCRpcReturnDataLimit()) {
return nil, fmt.Errorf("call returned result of length %d exceeding limit %d", len(result.ReturnData), int(s.b.RPCRpcReturnDataLimit()))
}

// If the result contains a revert reason, try to unpack and return it.
if len(result.Revert()) > 0 {
return nil, newRevertError(result)
}

return result.Return(), result.Err
}

// CallWithState executes the given transaction on the given state for
// the given block number.
//
// Additionally, the caller can specify a batch of contract for fields overriding.
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *PublicBlockChainAPI) CallWithState(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, state, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
if err != nil {
return nil, err
}
Expand All @@ -1093,6 +1142,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
if len(result.Revert()) > 0 {
return nil, newRevertError(result)
}

return result.Return(), result.Err
}

Expand Down Expand Up @@ -1170,7 +1220,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)

result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, nil, 0, gasCap)
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand Down
Loading