Skip to content

Commit

Permalink
fix: handle submitBlockReward correctly when processing system transa…
Browse files Browse the repository at this point in the history
…ctions

When getting over the DPoS hardfork, we get "insufficient funds for gas * price
+ value" error when using debug API.

As the submitBlockReward is a system transaction, it's handled differently from
the normal transactions. When consensus processes submitBlockReward and there
are rewards from transaction fee in block, consensus transfers balance from
SystemAddress to miner address through the statedb.SetBalance directly. Later,
when the system transaction is created, this reward will be transfer from miner
to staking contract.

Currently, in debug API and state accessor, we treat all transactions as normal
transactions.  Without special handling, we miss the state transaction to
transfer balance from SystemAddress to miner. Consequently, when the system
transaction is processed, we get the error because there is a transfer from
miner to staking contract but we see that miner's balance is not sufficient for
the transfer.

This commit checks whether the transaction is submitBlockReward with non-zero
msg.value then adds the state transition to transfer from SystemAddress to miner
to fix the above error.
  • Loading branch information
minh-bq committed Apr 13, 2023
1 parent c62b2ba commit 3b40591
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 17 deletions.
24 changes: 22 additions & 2 deletions consensus/consortium/main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package consortium

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
consortiumCommon "github.com/ethereum/go-ethereum/consensus/consortium/common"
v1 "github.com/ethereum/go-ethereum/consensus/consortium/v1"
v2 "github.com/ethereum/go-ethereum/consensus/consortium/v2"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"math/big"
)

// Consortium is a proxy that decides the consensus version will be called
Expand Down Expand Up @@ -184,10 +186,28 @@ func (c *Consortium) SetGetFenixValidators(fn func() ([]common.Address, error))

// IsSystemTransaction implements consensus.PoSA. It is only available on v2 since v1 doesn't have system contract
func (c *Consortium) IsSystemTransaction(tx *types.Transaction, header *types.Header) (bool, error) {
return c.v2.IsSystemTransaction(tx, header)
msg, err := tx.AsMessage(types.MakeSigner(c.chainConfig, header.Number), header.BaseFee)
if err != nil {
return false, err
}
return c.v2.IsSystemMessage(msg, header)
}

// IsSystemContract implements consensus.PoSA. It is only available on v2 since v1 doesn't have system contract
func (c *Consortium) IsSystemContract(to *common.Address) bool {
return c.v2.IsSystemContract(to)
}

// Determine if the transaction is submitBlockReward transaction with
// non-zero msg.value. This function based on the fact that
// submitBlockReward is the only system transaction that may have
// non-zero msg.value
func (c *Consortium) IsSubmitBlockRewardNonZeroAmount(msg core.Message, header *types.Header) bool {
if c.chainConfig.IsConsortiumV2(new(big.Int).Add(header.Number, common.Big1)) {
isSystemMsg, _ := c.v2.IsSystemMessage(msg, header)
if isSystemMsg && msg.Value().Cmp(common.Big0) > 0 {
return true
}
}
return false
}
14 changes: 5 additions & 9 deletions consensus/consortium/v2/consortium.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,25 +143,21 @@ func New(
return &consortium
}

// IsSystemTransaction implements consensus.PoSA, checking whether a transaction is a system
// IsSystemMessage implements consensus.PoSA, checking whether a transaction is a system
// transaction or not.
// A system transaction is a transaction that has the recipient of the contract address
// is defined in params.ConsortiumV2Contracts
func (c *Consortium) IsSystemTransaction(tx *types.Transaction, header *types.Header) (bool, error) {
func (c *Consortium) IsSystemMessage(msg core.Message, header *types.Header) (bool, error) {
// deploy a contract
if tx.To() == nil {
if msg.To() == nil {
return false, nil
}
sender, err := types.Sender(c.signer, tx)
if err != nil {
return false, errors.New("UnAuthorized transaction")
}
if c.chainConfig.IsBuba(header.Number) {
if sender == header.Coinbase && c.IsSystemContract(tx.To()) {
if msg.From() == header.Coinbase && c.IsSystemContract(msg.To()) {
return true, nil
}
} else {
if sender == header.Coinbase && c.IsSystemContract(tx.To()) && tx.GasPrice().Cmp(big.NewInt(0)) == 0 {
if msg.From() == header.Coinbase && c.IsSystemContract(msg.To()) && msg.GasPrice().Cmp(big.NewInt(0)) == 0 {
return true, nil
}
}
Expand Down
18 changes: 18 additions & 0 deletions eth/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ package eth
import (
"errors"
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/consortium"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -158,6 +161,20 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state
return statedb, nil
}

func (eth *Ethereum) handleSubmitBlockReward(statedb *state.StateDB, msg core.Message, block *types.Block) {
engine := eth.engine
consortium, ok := engine.(*consortium.Consortium)
if !ok {
return
}

if consortium.IsSubmitBlockRewardNonZeroAmount(msg, block.Header()) {
balance := statedb.GetBalance(consensus.SystemAddress)
statedb.SetBalance(consensus.SystemAddress, big.NewInt(0))
statedb.SetBalance(block.Coinbase(), balance)
}
}

// stateAtTransaction returns the execution environment of a certain transaction.
func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
// Short circuit if it's genesis block.
Expand Down Expand Up @@ -191,6 +208,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
statedb.Prepare(tx.Hash(), idx)
eth.handleSubmitBlockReward(statedb, msg, block)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
Expand Down
41 changes: 35 additions & 6 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"runtime"
"sync"
Expand All @@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/consortium"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
Expand Down Expand Up @@ -115,6 +117,20 @@ func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.H
return header
}

func (api *API) handleSubmitBlockReward(statedb *state.StateDB, msg core.Message, block *types.Block) {
engine := api.backend.Engine()
consortium, ok := engine.(*consortium.Consortium)
if !ok {
return
}

if consortium.IsSubmitBlockRewardNonZeroAmount(msg, block.Header()) {
balance := statedb.GetBalance(consensus.SystemAddress)
statedb.SetBalance(consensus.SystemAddress, big.NewInt(0))
statedb.SetBalance(block.Coinbase(), balance)
}
}

// chainContext construts the context reader which is used by the evm for reading
// the necessary chain context.
func (api *API) chainContext(ctx context.Context) core.ChainContext {
Expand Down Expand Up @@ -292,7 +308,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
TxIndex: i,
TxHash: tx.Hash(),
}
res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config)
res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config, task.block)
if err != nil {
task.results[i] = &txTraceResult{Error: err.Error()}
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
Expand Down Expand Up @@ -553,6 +569,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{})
)
statedb.Prepare(tx.Hash(), i)
api.handleSubmitBlockReward(statedb, msg, block)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
// We intentionally don't return the error here: if we do, then the RPC server will not
Expand Down Expand Up @@ -627,7 +644,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
TxIndex: task.index,
TxHash: txs[task.index].Hash(),
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, block)
if err != nil {
results[task.index] = &txTraceResult{Error: err.Error()}
continue
Expand All @@ -646,6 +663,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
msg, _ := tx.AsMessage(signer, block.BaseFee())
statedb.Prepare(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
api.handleSubmitBlockReward(statedb, msg, block)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
failed = err
break
Expand Down Expand Up @@ -714,7 +732,7 @@ func (api *API) traceInternalsAndAccounts(ctx context.Context, block *types.Bloc
TxIndex: task.index,
TxHash: txHash,
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, block)
if err != nil {
results.InternalTxs[task.index] = &txTraceResult{
TransactionHash: txHash,
Expand All @@ -739,6 +757,7 @@ func (api *API) traceInternalsAndAccounts(ctx context.Context, block *types.Bloc
msg, _ := tx.AsMessage(signer, block.BaseFee())
statedb.Prepare(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
api.handleSubmitBlockReward(statedb, msg, block)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
failed = err
break
Expand Down Expand Up @@ -865,6 +884,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.Prepare(tx.Hash(), i)
api.handleSubmitBlockReward(statedb, msg, block)
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil {
writer.Flush()
Expand Down Expand Up @@ -927,7 +947,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index),
TxHash: hash,
}
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config, block)
}

// TraceCall lets you trace a given eth_call. It collects the structured logs
Expand Down Expand Up @@ -981,13 +1001,21 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
Reexec: config.Reexec,
}
}
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig)
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig, block)
}

// traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
func (api *API) traceTx(
ctx context.Context,
message core.Message,
txctx *Context,
vmctx vm.BlockContext,
statedb *state.StateDB,
config *TraceConfig,
block *types.Block,
) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
tracer vm.EVMLogger
Expand Down Expand Up @@ -1027,6 +1055,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Call Prepare to clear out the statedb access list
statedb.Prepare(txctx.TxHash, txctx.TxIndex)

api.handleSubmitBlockReward(statedb, message, block)
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
Expand Down
18 changes: 18 additions & 0 deletions les/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
"context"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/consortium"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -33,6 +36,20 @@ func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block,
return light.NewState(ctx, block.Header(), leth.odr), nil
}

func (leth *LightEthereum) handleSubmitBlockReward(statedb *state.StateDB, msg core.Message, block *types.Block) {
engine := leth.engine
consortium, ok := engine.(*consortium.Consortium)
if !ok {
return
}

if consortium.IsSubmitBlockRewardNonZeroAmount(msg, block.Header()) {
balance := statedb.GetBalance(consensus.SystemAddress)
statedb.SetBalance(consensus.SystemAddress, big.NewInt(0))
statedb.SetBalance(block.Coinbase(), balance)
}
}

// stateAtTransaction returns the execution environment of a certain transaction.
func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
// Short circuit if it's genesis block.
Expand Down Expand Up @@ -64,6 +81,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
}
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
leth.handleSubmitBlockReward(statedb, msg, block)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
Expand Down

0 comments on commit 3b40591

Please sign in to comment.