Move fee burning and tipping out of state transition to reduce read/write dependencies between transactions

This commit is contained in:
Jerry 2022-07-26 11:36:33 -07:00
parent bbcc6dd0ad
commit 61accb021a
2 changed files with 127 additions and 47 deletions

View file

@ -55,16 +55,17 @@ type ExecutionTask struct {
msg types.Message msg types.Message
config *params.ChainConfig config *params.ChainConfig
gasLimit uint64 gasLimit uint64
blockNumber *big.Int blockNumber *big.Int
blockHash common.Hash blockHash common.Hash
blockContext vm.BlockContext blockContext vm.BlockContext
tx *types.Transaction tx *types.Transaction
index int index int
statedb *state.StateDB // State database that stores the modified values after tx execution. statedb *state.StateDB // State database that stores the modified values after tx execution.
cleanStateDB *state.StateDB // A clean copy of the initial statedb. It should not be modified. cleanStateDB *state.StateDB // A clean copy of the initial statedb. It should not be modified.
evmConfig vm.Config evmConfig vm.Config
result *ExecutionResult result *ExecutionResult
shouldDelayFeeCal *bool
} }
func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (err error) { func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (err error) {
@ -91,7 +92,11 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
}() }()
// Apply the transaction to the current state (included in the env). // Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, task.msg, new(GasPool).AddGas(task.gasLimit)) if *task.shouldDelayFeeCal {
task.result, err = ApplyMessageNoFeeBurnOrTip(evm, task.msg, new(GasPool).AddGas(task.gasLimit))
} else {
task.result, err = ApplyMessage(evm, task.msg, new(GasPool).AddGas(task.gasLimit))
}
if task.statedb.HadInvalidRead() || err != nil { if task.statedb.HadInvalidRead() || err != nil {
err = blockstm.ErrExecAbort err = blockstm.ErrExecAbort
@ -100,8 +105,6 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
task.statedb.Finalise(false) task.statedb.Finalise(false)
task.result = result
return return
} }
@ -140,6 +143,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
tasks := make([]blockstm.ExecTask, 0, len(block.Transactions())) tasks := make([]blockstm.ExecTask, 0, len(block.Transactions()))
shouldDelayFeeCal := true
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee) msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
@ -148,18 +153,26 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
bc := NewEVMBlockContext(header, p.bc, nil)
cleansdb := statedb.Copy() cleansdb := statedb.Copy()
if msg.From() == bc.Coinbase {
shouldDelayFeeCal = false
}
task := &ExecutionTask{ task := &ExecutionTask{
msg: msg, msg: msg,
config: p.config, config: p.config,
gasLimit: block.GasLimit(), gasLimit: block.GasLimit(),
blockNumber: blockNumber, blockNumber: blockNumber,
blockHash: blockHash, blockHash: blockHash,
tx: tx, tx: tx,
index: i, index: i,
cleanStateDB: cleansdb, cleanStateDB: cleansdb,
blockContext: NewEVMBlockContext(header, p.bc, nil), blockContext: bc,
evmConfig: cfg,
shouldDelayFeeCal: &shouldDelayFeeCal,
} }
tasks = append(tasks, task) tasks = append(tasks, task)
@ -172,15 +185,45 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
return nil, nil, 0, err return nil, nil, 0, err
} }
london := p.config.IsLondon(blockNumber)
for _, task := range tasks { for _, task := range tasks {
task := task.(*ExecutionTask) task := task.(*ExecutionTask)
statedb.Prepare(task.tx.Hash(), task.index) statedb.Prepare(task.tx.Hash(), task.index)
coinbaseBalance := statedb.GetBalance(task.blockContext.Coinbase)
statedb.ApplyMVWriteSet(task.MVWriteList()) statedb.ApplyMVWriteSet(task.MVWriteList())
for _, l := range task.statedb.GetLogs(task.tx.Hash(), blockHash) { for _, l := range task.statedb.GetLogs(task.tx.Hash(), blockHash) {
statedb.AddLog(l) statedb.AddLog(l)
} }
if shouldDelayFeeCal {
if london {
statedb.AddBalance(task.result.BurntContractAddress, task.result.FeeBurnt)
}
statedb.AddBalance(task.blockContext.Coinbase, task.result.FeeTipped)
output1 := new(big.Int).SetBytes(task.result.senderInitBalance.Bytes())
output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
// Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
// add transfer log
AddFeeTransferLog(
statedb,
task.msg.From(),
task.blockContext.Coinbase,
task.result.FeeTipped,
task.result.senderInitBalance,
coinbaseBalance,
output1.Sub(output1, task.result.FeeTipped),
output2.Add(output2, task.result.FeeTipped),
)
}
for k, v := range task.statedb.Preimages() { for k, v := range task.statedb.Preimages() {
statedb.AddPreimage(k, v) statedb.AddPreimage(k, v)
} }

View file

@ -62,6 +62,11 @@ type StateTransition struct {
data []byte data []byte
state vm.StateDB state vm.StateDB
evm *vm.EVM evm *vm.EVM
// If true, fee burning and tipping won't happen during transition. Instead, their values will be included in the
// ExecutionResult, which caller can use the values to update the balance of burner and coinbase account.
// This is useful during parallel state transition, where the common account read/write should be minimized.
noFeeBurnAndTip bool
} }
// Message represents a message sent to a contract. // Message represents a message sent to a contract.
@ -84,9 +89,13 @@ type Message interface {
// ExecutionResult includes all output after executing given evm // ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not. // message no matter the execution itself is successful or not.
type ExecutionResult struct { type ExecutionResult struct {
UsedGas uint64 // Total used gas but include the refunded gas UsedGas uint64 // Total used gas but include the refunded gas
Err error // Any error encountered during the execution(listed in core/vm/errors.go) Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
senderInitBalance *big.Int
FeeBurnt *big.Int
BurntContractAddress common.Address
FeeTipped *big.Int
} }
// Unwrap returns the internal evm error which allows us for further // Unwrap returns the internal evm error which allows us for further
@ -183,6 +192,13 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, erro
return NewStateTransition(evm, msg, gp).TransitionDb() return NewStateTransition(evm, msg, gp).TransitionDb()
} }
func ApplyMessageNoFeeBurnOrTip(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
st := NewStateTransition(evm, msg, gp)
st.noFeeBurnAndTip = true
return st.TransitionDb()
}
// to returns the recipient of the message. // to returns the recipient of the message.
func (st *StateTransition) to() common.Address { func (st *StateTransition) to() common.Address {
if st.msg == nil || st.msg.To() == nil /* contract creation */ { if st.msg == nil || st.msg.To() == nil /* contract creation */ {
@ -276,7 +292,12 @@ func (st *StateTransition) preCheck() error {
// nil evm execution result. // nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
input1 := st.state.GetBalance(st.msg.From()) input1 := st.state.GetBalance(st.msg.From())
input2 := st.state.GetBalance(st.evm.Context.Coinbase)
var input2 *big.Int
if !st.noFeeBurnAndTip {
input2 = st.state.GetBalance(st.evm.Context.Coinbase)
}
// First check this message satisfies all consensus rules before // First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses // applying the message. The rules include these clauses
@ -342,34 +363,50 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
} }
amount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip) amount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)
var burnAmount *big.Int
var burntContractAddress common.Address
if london { if london {
burntContractAddress := common.HexToAddress(st.evm.ChainConfig().Bor.CalculateBurntContract(st.evm.Context.BlockNumber.Uint64())) burntContractAddress = common.HexToAddress(st.evm.ChainConfig().Bor.CalculateBurntContract(st.evm.Context.BlockNumber.Uint64()))
burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee) burnAmount = new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)
st.state.AddBalance(burntContractAddress, burnAmount)
if !st.noFeeBurnAndTip {
st.state.AddBalance(burntContractAddress, burnAmount)
}
} }
st.state.AddBalance(st.evm.Context.Coinbase, amount)
output1 := new(big.Int).SetBytes(input1.Bytes())
output2 := new(big.Int).SetBytes(input2.Bytes())
// Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559 if !st.noFeeBurnAndTip {
// add transfer log st.state.AddBalance(st.evm.Context.Coinbase, amount)
AddFeeTransferLog(
st.state,
msg.From(), output1 := new(big.Int).SetBytes(input1.Bytes())
st.evm.Context.Coinbase, output2 := new(big.Int).SetBytes(input2.Bytes())
amount, // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
input1, // add transfer log
input2, AddFeeTransferLog(
output1.Sub(output1, amount), st.state,
output2.Add(output2, amount),
) msg.From(),
st.evm.Context.Coinbase,
amount,
input1,
input2,
output1.Sub(output1, amount),
output2.Add(output2, amount),
)
}
return &ExecutionResult{ return &ExecutionResult{
UsedGas: st.gasUsed(), UsedGas: st.gasUsed(),
Err: vmerr, Err: vmerr,
ReturnData: ret, ReturnData: ret,
senderInitBalance: input1,
FeeBurnt: burnAmount,
BurntContractAddress: burntContractAddress,
FeeTipped: amount,
}, nil }, nil
} }