From b6da2f827349ceb6712ff4582a623517637d7246 Mon Sep 17 00:00:00 2001 From: gop Date: Wed, 14 Aug 2024 14:19:30 -0500 Subject: [PATCH] Added stateGas value into the contract, but needs to be calculated properly --- core/block_validator.go | 5 +- core/evm.go | 1 + core/state_processor.go | 78 ++++++++------- core/state_transition.go | 7 ++ core/types.go | 2 +- core/types/transaction.go | 2 + core/vm/contract.go | 13 ++- core/vm/evm.go | 13 +-- core/vm/gas_table.go | 196 +++++++++++++------------------------ core/vm/interpreter.go | 7 +- core/vm/jump_table.go | 2 +- core/vm/operations_acl.go | 69 +++++++------ core/vm/runtime/env.go | 19 ++-- core/vm/runtime/runtime.go | 36 ++++--- core/worker.go | 3 +- params/protocol_params.go | 37 ++++--- 16 files changed, 236 insertions(+), 254 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index fe1f9f4bd6..072264c709 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -261,13 +261,16 @@ func (v *BlockValidator) SanityCheckWorkObjectShareViewBody(wo *types.WorkObject // transition, such as amount of used gas, the receipt roots and the state root // itself. ValidateState returns a database batch if the validation was a success // otherwise nil and an error is returned. -func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64) error { +func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64, usedState uint64) error { start := time.Now() header := types.CopyHeader(block.Header()) time1 := common.PrettyDuration(time.Since(start)) if block.GasUsed() != usedGas { return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) } + if block.StateUsed() != usedState { + return fmt.Errorf("invalid state used (remote: %d local: %d)", block.StateUsed(), usedState) + } time2 := common.PrettyDuration(time.Since(start)) time3 := common.PrettyDuration(time.Since(start)) // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) diff --git a/core/evm.go b/core/evm.go index 14f8cb4f1f..5272804fcd 100644 --- a/core/evm.go +++ b/core/evm.go @@ -112,6 +112,7 @@ func NewEVMBlockContext(header *types.WorkObject, parent *types.WorkObject, chai GasLimit: header.GasLimit(), CheckIfEtxEligible: chain.CheckIfEtxIsEligible, EtxEligibleSlices: etxEligibleSlices, + QuaiStateSize: parent.QuaiStateSize(), // using the state size at the parent for all the gas calculations }, nil } diff --git a/core/state_processor.go b/core/state_processor.go index a6b4b7afba..c232857439 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -207,10 +207,11 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, error) { +func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) + usedState = new(uint64) header = types.CopyWorkObject(block) blockHash = block.Hash() nodeLocation = p.hc.NodeLocation() @@ -222,7 +223,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty start := time.Now() parent := p.hc.GetBlock(block.ParentHash(nodeCtx), block.NumberU64(nodeCtx)-1) if parent == nil { - return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, errors.New("parent block is nil for the block given to process") + return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, 0, errors.New("parent block is nil for the block given to process") } time1 := common.PrettyDuration(time.Since(start)) @@ -237,13 +238,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty // Initialize a statedb statedb, err := state.New(parentEvmRoot, parentUtxoRoot, parentEtxSetRoot, parent.QuaiStateSize(), p.stateCache, p.utxoCache, p.etxCache, p.snaps, nodeLocation, p.logger) if err != nil { - return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, err + return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, 0, err } // Apply the previous inbound ETXs to the ETX set state prevInboundEtxs := rawdb.ReadInboundEtxs(p.hc.bc.db, header.ParentHash(nodeCtx)) if len(prevInboundEtxs) > 0 { if err := statedb.PushETXs(prevInboundEtxs); err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not push prev inbound etxs: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not push prev inbound etxs: %w", err) } } time2 := common.PrettyDuration(time.Since(start)) @@ -273,7 +274,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty blockContext, err := NewEVMBlockContext(header, parent, p.hc, nil) if err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, p.vmConfig) time3 := common.PrettyDuration(time.Since(start)) @@ -328,7 +329,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } fees, etxs, err, timing := ProcessQiTx(tx, p.hc, true, checkSig, header, statedb, gp, usedGas, p.hc.pool.signer, p.hc.NodeLocation(), *p.config.ChainID, &etxRLimit, &etxPLimit) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } startEtxAppend := time.Now() for _, etx := range etxs { @@ -341,7 +342,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QiToQuai(primeTerminus.WorkObjectHeader(), fees)) } @@ -358,7 +359,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty msg, err := tx.AsMessageWithSender(types.MakeSigner(p.config, header.Number(nodeCtx)), header.BaseFee(), senders[tx.Hash()]) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } timeSignDelta := time.Since(startProcess) timeSign += timeSignDelta @@ -376,13 +377,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty // ETXs MUST be included in order, so popping the first from the queue must equal the first in the block etx, err := statedb.PopETX() if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not pop etx from statedb: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not pop etx from statedb: %w", err) } if etx == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("etx %x is nil", tx.Hash()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("etx %x is nil", tx.Hash()) } if etx.Hash() != tx.Hash() { - return nil, nil, nil, nil, 0, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) } // check if the tx is a coinbase tx // coinbase tx @@ -393,7 +394,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty if types.IsCoinBaseTx(tx) { iAddr, err := tx.To().InternalAddress() if err != nil { - return nil, nil, nil, nil, 0, errors.New("coinbase address is not in the chain scope") + return nil, nil, nil, nil, 0, 0, errors.New("coinbase address is not in the chain scope") } if tx.To().IsInQiLedgerScope() { value := tx.Value() @@ -412,7 +413,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } // the ETX hash is guaranteed to be unique if err := statedb.CreateUTXO(etx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), block.Number(nodeCtx)))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } p.logger.Debugf("Creating UTXO for coinbase %032x with denomination %d index %d\n", tx.Hash(), denomination, outputIndex) outputIndex++ @@ -431,7 +432,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty lock := new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } value := misc.QuaiToQi(primeTerminus.WorkObjectHeader(), etx.Value()) // convert Quai to Qi txGas := etx.Gas() @@ -440,7 +441,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } txGas -= params.TxGas if err := gp.SubGas(params.TxGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.TxGas totalEtxGas += params.TxGas @@ -459,13 +460,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } txGas -= params.CallValueTransferGas if err := gp.SubGas(params.CallValueTransferGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is // the ETX hash is guaranteed to be unique if err := statedb.CreateUTXO(etx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), etx.To().Bytes(), lock))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } p.logger.Infof("Converting Quai to Qi %032x with denomination %d index %d lock %d\n", tx.Hash(), denomination, outputIndex, lock) outputIndex++ @@ -474,11 +475,11 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { // There are no more checks to be made as the ETX is worked so add it to the set if err := statedb.CreateUTXO(etx.OriginatingTxHash(), etx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(etx.Value().Uint64()), etx.To().Bytes(), big.NewInt(0)))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } // This Qi ETX should cost more gas if err := gp.SubGas(params.CallValueTransferGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is @@ -491,7 +492,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod))) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } // Convert Qi to Quai msg.SetValue(misc.QiToQuai(primeTerminus.WorkObjectHeader(), etx.Value())) @@ -499,10 +500,10 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty p.logger.Infof("Converting Qi to Quai for ETX %032x with value %d lock %d\n", tx.Hash(), msg.Value().Uint64(), msg.Lock().Uint64()) } prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation) - receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, etx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) + receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, etx, usedGas, usedState, vmenv, &etxRLimit, &etxPLimit, p.logger) statedb.SetBalance(common.ZeroInternal(nodeLocation), prevZeroBal) // Reset the balance to what it previously was. Residual balance will be lost if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } addReceipt = true if block.Coinbase().IsInQuaiLedgerScope() { @@ -510,7 +511,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QuaiToQi(primeTerminus.WorkObjectHeader(), quaiFees)) } @@ -521,9 +522,9 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else if tx.Type() == types.QuaiTxType { startTimeTx := time.Now() - receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) + receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, usedState, vmenv, &etxRLimit, &etxPLimit, p.logger) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } addReceipt = true timeTxDelta := time.Since(startTimeTx) @@ -533,12 +534,12 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QuaiToQi(primeTerminus.WorkObjectHeader(), quaiFees)) } } else { - return nil, nil, nil, nil, 0, ErrTxTypeNotSupported + return nil, nil, nil, nil, 0, 0, ErrTxTypeNotSupported } for _, etx := range receipt.Etxs { if receipt.Status == types.ReceiptStatusSuccessful { @@ -555,19 +556,19 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty etxAvailable := false oldestIndex, err := statedb.GetOldestIndex() if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not get oldest index: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not get oldest index: %w", err) } // Check if there is at least one ETX in the set etx, err := statedb.ReadETX(oldestIndex) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not read etx: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not read etx: %w", err) } if etx != nil { etxAvailable = true } if (etxAvailable && totalEtxGas < minimumEtxGas) || totalEtxGas > maximumEtxGas { p.logger.Errorf("prevInboundEtxs: %d, oldestIndex: %d, etxHash: %s", len(prevInboundEtxs), oldestIndex.Int64(), etx.Hash().Hex()) - return nil, nil, nil, nil, 0, fmt.Errorf("total gas used by ETXs %d is not within the range %d to %d", totalEtxGas, minimumEtxGas, maximumEtxGas) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("total gas used by ETXs %d is not within the range %d to %d", totalEtxGas, minimumEtxGas, maximumEtxGas) } coinbaseReward := misc.CalculateReward(block.WorkObjectHeader()) @@ -620,10 +621,10 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty "numTxs": len(block.Transactions()), }).Info("Total Tx Processing Time") - return receipts, emittedEtxs, allLogs, statedb, *usedGas, nil + return receipts, emittedEtxs, allLogs, statedb, *usedGas, *usedState, nil } -func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { +func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, usedState *uint64, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { nodeLocation := config.Location // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) @@ -661,6 +662,7 @@ func applyTransaction(msg types.Message, parent *types.WorkObject, config *param statedb.Finalise(true) *usedGas += result.UsedGas + *usedState += result.UsedState // Create a new receipt for the transaction, storing the intermediate root and gas used // by the tx. @@ -1184,7 +1186,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t time1 := common.PrettyDuration(time.Since(start)) time2 := common.PrettyDuration(time.Since(start)) // Process our block - receipts, etxs, logs, statedb, usedGas, err := p.Process(block) + receipts, etxs, logs, statedb, usedGas, usedState, err := p.Process(block) if err != nil { return nil, err } @@ -1195,7 +1197,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t }).Warn("Block hash changed after Processing the block") } time3 := common.PrettyDuration(time.Since(start)) - err = p.validator.ValidateState(block, statedb, receipts, etxs, usedGas) + err = p.validator.ValidateState(block, statedb, receipts, etxs, usedGas, usedState) if err != nil { return nil, err } @@ -1255,7 +1257,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { +func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, usedState *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { nodeCtx := config.Location.Context() msg, err := tx.AsMessage(types.MakeSigner(config, header.Number(nodeCtx)), header.BaseFee()) if err != nil { @@ -1284,11 +1286,11 @@ func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, pare vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) if tx.Type() == types.ExternalTxType { prevZeroBal := prepareApplyETX(statedb, msg.Value(), config.Location) - receipt, quaiFees, err := applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, vmenv, etxRLimit, etxPLimit, logger) + receipt, quaiFees, err := applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, usedState, vmenv, etxRLimit, etxPLimit, logger) statedb.SetBalance(common.ZeroInternal(config.Location), prevZeroBal) // Reset the balance to what it previously was (currently a failed external transaction removes all the sent coins from the supply and any residual balance is gone as well) return receipt, quaiFees, err } - return applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, vmenv, etxRLimit, etxPLimit, logger) + return applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, usedState, vmenv, etxRLimit, etxPLimit, logger) } // GetVMConfig returns the block chain VM config. @@ -1493,7 +1495,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba if currentBlock == nil { return nil, errors.New("detached block found trying to regenerate state") } - _, _, _, _, _, err := p.Process(currentBlock) + _, _, _, _, _, _, err := p.Process(currentBlock) if err != nil { return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(nodeCtx), err) } diff --git a/core/state_transition.go b/core/state_transition.go index 2ad9b62ea9..932c4b999f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -55,6 +55,7 @@ type StateTransition struct { gp *types.GasPool msg Message gas uint64 + stateGas uint64 gasPrice *big.Int gasFeeCap *big.Int gasTipCap *big.Int @@ -90,6 +91,7 @@ type Message interface { // message no matter the execution itself is successful or not. type ExecutionResult struct { UsedGas uint64 // Total used gas but include the refunded gas + UsedState uint64 // Total used state 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) Etxs []*types.Transaction // External transactions generated from opETX @@ -317,6 +319,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if strings.Contains(err.Error(), ErrEtxGasLimitReached.Error()) { return &ExecutionResult{ UsedGas: params.TxGas, + UsedState: params.EtxStateUsed, Err: err, ReturnData: []byte{}, Etxs: nil, @@ -433,3 +436,7 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gas } + +func (st *StateTransition) stateUsed() uint64 { + return 0 +} diff --git a/core/types.go b/core/types.go index 34f962c4ec..ef76f92cd3 100644 --- a/core/types.go +++ b/core/types.go @@ -37,7 +37,7 @@ type Validator interface { // ValidateState validates the given statedb and optionally the receipts and // gas used. - ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64) error + ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64, usedState uint64) error } // Prefetcher is an interface for pre-caching transaction signatures and state. diff --git a/core/types/transaction.go b/core/types/transaction.go index ece15acbe8..27c9102de9 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -1092,6 +1092,7 @@ type Message struct { nonce uint64 amount *big.Int gasLimit uint64 + stateLimit uint64 gasPrice *big.Int gasFeeCap *big.Int gasTipCap *big.Int @@ -1156,6 +1157,7 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { func (tx *Transaction) AsMessageWithSender(s Signer, baseFee *big.Int, sender *common.InternalAddress) (Message, error) { msg := Message{ gasLimit: tx.Gas(), + stateLimit: tx.Gas(), gasPrice: new(big.Int).Set(tx.GasPrice()), gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), gasTipCap: new(big.Int).Set(tx.GasTipCap()), diff --git a/core/vm/contract.go b/core/vm/contract.go index c97bb0ca2e..20da322ddb 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -58,8 +58,9 @@ type Contract struct { CodeAddr *common.Address Input []byte - Gas uint64 - value *big.Int + StateGas uint64 + Gas uint64 + value *big.Int } // NewContract returns a new contract environment for the execution of EVM. @@ -79,6 +80,9 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin // ensures a value is set c.value = value + // Create a state gas + c.StateGas = 0 + return c } @@ -172,6 +176,11 @@ func (c *Contract) UseGas(gas uint64) (ok bool) { return true } +// UseState keeps a counter on the gas used for state operation +func (c *Contract) UseState(gas uint64) { + c.StateGas += gas +} + // Address returns the contracts address func (c *Contract) Address() common.Address { return c.self.Address() diff --git a/core/vm/evm.go b/core/vm/evm.go index 1f4bf8ee51..e584432af2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -78,12 +78,13 @@ type BlockContext struct { CheckIfEtxEligible CheckIfEtxEligibleFunc // Block information - Coinbase common.Address // Provides information for COINBASE - GasLimit uint64 // Provides information for GASLIMIT - BlockNumber *big.Int // Provides information for NUMBER - Time *big.Int // Provides information for TIME - Difficulty *big.Int // Provides information for DIFFICULTY - BaseFee *big.Int // Provides information for BASEFEE + Coinbase common.Address // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time *big.Int // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE + QuaiStateSize *big.Int // Provides information for QUAISTATESIZE // Prime Terminus information for the given block EtxEligibleSlices common.Hash diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7610c8a9e0..e028f6febc 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -17,8 +17,6 @@ package vm import ( - "errors" - "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" "github.com/dominant-strategies/go-quai/params" @@ -26,9 +24,9 @@ import ( // memoryGasCost calculates the quadratic gas for memory expansion. It does so // only for the memory region that is expanded, not the total memory. -func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { +func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, uint64, error) { if newMemSize == 0 { - return 0, nil + return 0, 0, nil } // The maximum that will fit in a uint64 is max_word_count - 1. Anything above // that will result in an overflow. Additionally, a newMemSize which results in @@ -36,7 +34,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // without overflowing the gas calculation. if newMemSize > 0x1FFFFFFFE0 { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -50,9 +48,9 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { fee := newTotalFee - mem.lastGasCost mem.lastGasCost = newTotalFee - return fee, nil + return fee, 0, nil } - return 0, nil + return 0, 0, nil } // memoryCopierGas creates the gas functions for the following opcodes, and takes @@ -63,26 +61,26 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // EXTCODECOPY (stack poition 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // Gas for expanding the memory - gas, err := memoryGasCost(mem, memorySize) + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } // And gas for copying data, charged per word at param.CopyGas words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } } @@ -92,117 +90,59 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) -// 0. If *gasleft* is less than or equal to 2300, fail the current call. -// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted. -// 2. If current value does not equal new value: -// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context): -// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. -// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. -// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: -// 2.2.1. If original value is not 0: -// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. -// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. -// 2.2.2. If original value equals new value (this storage slot is reset): -// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. -// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. -func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGas { - return 0, errors.New("not enough gas for reentrancy sentry") - } - // Gas sentry honoured, do the actual gas calculation based on the stored value - var ( - y, x = stack.Back(1), stack.Back(0) - internalContractAddr, err = contract.Address().InternalAndQuaiAddress() - ) - if err != nil { - return 0, err - } - current := evm.StateDB.GetState(internalContractAddr, x.Bytes32()) - value := common.Hash(y.Bytes32()) - - if current == value { // noop (1) - return params.SloadGas, nil - } - original := evm.StateDB.GetCommittedState(internalContractAddr, x.Bytes32()) - if original == current { - if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreSetGas, nil - } - if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefund) - } - return params.SstoreResetGas, nil // write existing slot (2.1.2) - } - if original != (common.Hash{}) { - if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearsScheduleRefund) - } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefund) - } - } - if original == value { - if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreSetGas - params.SloadGas) - } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund(params.SstoreResetGas - params.SloadGas) - } - } - return params.SloadGas, nil // dirty update (2.2) -} - func makeGasLog(n uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - gas, err := memoryGasCost(mem, memorySize) + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + // make gas log doesnt use any state operation, so return 0 for stateGas used + return gas, 0, nil } } -func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } // pureMemoryGascost is used by several operations, which aside from their // static cost have a dynamic cost which is solely based on the memory // expansion -func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { return memoryGasCost(mem, memorySize) } @@ -215,25 +155,25 @@ var ( gasCreate = pureMemoryGascost ) -func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } -func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -241,19 +181,20 @@ func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } -func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { var ( gas uint64 + stateGas uint64 transfersValue = !stack.Back(2).IsZero() address, err = common.Bytes20ToAddress(stack.Back(1).Bytes20(), evm.chainConfig.Location).InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, 0, err } if transfersValue && evm.StateDB.Empty(address) { gas += params.CallNewAccountGas @@ -261,80 +202,81 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if transfersValue { gas += params.CallValueTransferGas } - memoryGas, err := memoryGasCost(mem, memorySize) + memoryGas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, stateGas, nil } -func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - memoryGas, err := memoryGasCost(mem, memorySize) +func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + memoryGas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } var ( gas uint64 + stateGas uint64 overflow bool ) if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, stateGas, nil } -func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil // TODO: Need to check if this method acceses state } -func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil // TODO: check of this method needs state gas } func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index b0e7435f31..6b4dbe4fbf 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -212,11 +212,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // cost is explicitly set so that the capture state defer method can get the proper cost if operation.dynamicGas != nil { var dynamicCost uint64 - dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) + var stateCost uint64 + dynamicCost, stateCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // total cost, for debug tracing + if err != nil || !contract.UseGas(dynamicCost) { return nil, ErrOutOfGas } + + // Add the stateGas used into the contract.StateGas + contract.UseState(stateCost) } if memorySize > 0 { mem.Resize(memorySize) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e292dae62f..ceae5ace54 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -22,7 +22,7 @@ import ( type ( executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) ) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index a196e9cc1f..094bdb9087 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -25,20 +25,22 @@ import ( ) func makeGasSStoreFunc(clearingRefund uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGas { - return 0, errors.New("not enough gas for reentrancy sentry") - } + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() slot = common.Hash(x.Bytes32()) cost = uint64(0) + stateGas = uint64(0) // TODO: Need to measure this internalAddr, err = contract.Address().InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, stateGas, err + } + storageSizeOfContract := evm.StateDB.GetSize(internalAddr) + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGas(evm.Context.QuaiStateSize, storageSizeOfContract) { + return 0, 0, errors.New("not enough gas for reentrancy sentry") } current := evm.StateDB.GetState(internalAddr, slot) // Check slot presence in the access list @@ -56,17 +58,17 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return cost + params.WarmStorageReadCost, nil // SLOAD_GAS + return cost + params.WarmStorageReadCost, stateGas, nil // SLOAD_GAS } original := evm.StateDB.GetCommittedState(internalAddr, x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return cost + params.SstoreSetGas, nil + return cost + params.SstoreSetGas, stateGas, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(clearingRefund) } - return cost + (params.SstoreResetGas - params.ColdSloadCost), nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGas - params.ColdSloadCost), stateGas, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) @@ -82,7 +84,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { evm.StateDB.AddRefund((params.SstoreResetGas - params.ColdSloadCost) - params.WarmStorageReadCost) } } - return cost + params.WarmStorageReadCost, nil // dirty update (2.2) + return cost + params.WarmStorageReadCost, stateGas, nil // dirty update (2.2) } } @@ -91,28 +93,30 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // whose storage is being read) is not yet in accessed_storage_keys, // charge 2100 gas and add the pair to accessed_storage_keys. // If the pair is already in accessed_storage_keys, charge 100 gas. -func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { loc := stack.peek() slot := common.Hash(loc.Bytes32()) + var stateGas uint64 // TODO: check the calculation // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - return params.ColdSloadCost, nil + return params.ColdSloadCost, stateGas, nil } - return params.WarmStorageReadCost, nil + return params.WarmStorageReadCost, stateGas, nil } // gasExtCodeCopy implements extcodecopy gas calculation // > If the target is not in accessed_addresses, // > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. // > Otherwise, charge WARM_STORAGE_READ_COST gas. -func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // memory expansion first - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + // TODO: check the stateGas value + gas, stateGas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } addr := common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) // Check slot presence in the access list @@ -121,11 +125,11 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCost-params.WarmStorageReadCost); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, stateGas, nil } - return gas, nil + return gas, stateGas, nil } // gasAccountCheck checks whether the first stack item (as address) is present in the access list. @@ -135,20 +139,21 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem // - extcodehash, // - extcodesize, // - (ext) balance -func gasAccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasAccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { addr := common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) + var stateGas uint64 // TODO: measure this properly // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) // The warm storage read cost is already charged as constantGas - return params.ColdAccountAccessCost - params.WarmStorageReadCost, nil + return params.ColdAccountAccessCost - params.WarmStorageReadCost, stateGas, nil } - return 0, nil + return 0, stateGas, nil } func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { addr := common.Bytes20ToAddress(stack.Back(1).Bytes20(), evm.chainConfig.Location) // Check slot presence in the access list warmAccess := evm.StateDB.AddressInAccessList(addr) @@ -160,7 +165,7 @@ func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(coldCost) { - return 0, ErrOutOfGas + return 0, 0, ErrOutOfGas } } // Now call the old calculator, which takes into account @@ -168,16 +173,17 @@ func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + gas, stateGas, err := oldCalculator(evm, contract, stack, mem, memorySize) if warmAccess || err != nil { - return gas, err + return gas, stateGas, err } // In case of a cold access, we temporarily add the cold charge back, and also // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. contract.Gas += coldCost - return gas + coldCost, nil + contract.StateGas += 0 // TODO: figure out the correct value + return gas + coldCost, stateGas, nil } } @@ -196,18 +202,19 @@ var ( // makeSelfdestructGasFn can create the selfdestruct dynamic gas function func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { - gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { var ( gas uint64 + stateGas uint64 address = common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) internalAddress, err = address.InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, 0, err } contractAddress, err := contract.Address().InternalAndQuaiAddress() if err != nil { - return 0, err + return 0, 0, err } if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back @@ -221,7 +228,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if refundsEnabled && !evm.StateDB.HasSuicided(contractAddress) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return gas, nil + return gas, stateGas, nil } return gasFunc } diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 462f6fa959..6588a7b750 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -27,15 +27,16 @@ func NewEnv(cfg *Config) *vm.EVM { GasPrice: cfg.GasPrice, } blockContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: cfg.GetHashFn, - Coinbase: cfg.Coinbase, - BlockNumber: cfg.BlockNumber, - Time: cfg.Time, - Difficulty: cfg.Difficulty, - GasLimit: cfg.GasLimit, - BaseFee: cfg.BaseFee, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: cfg.GetHashFn, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + BaseFee: cfg.BaseFee, + QuaiStateSize: cfg.QuaiStateSize, } return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 23994d1f23..a9ca580551 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -35,20 +35,21 @@ import ( // Config is a basic type specifying certain configuration flags for running // the EVM. type Config struct { - ChainConfig *params.ChainConfig - Difficulty *big.Int - Origin common.Address - Coinbase common.Address - BlockNumber *big.Int - Time *big.Int - GasLimit uint64 - GasPrice *big.Int - Value *big.Int - Lock *big.Int - Debug bool - EVMConfig vm.Config - BaseFee *big.Int - Logger *logrus.Logger + ChainConfig *params.ChainConfig + Difficulty *big.Int + Origin common.Address + Coinbase common.Address + BlockNumber *big.Int + Time *big.Int + GasLimit uint64 + GasPrice *big.Int + Value *big.Int + Lock *big.Int + Debug bool + EVMConfig vm.Config + BaseFee *big.Int + QuaiStateSize *big.Int + Logger *logrus.Logger State *state.StateDB GetHashFn func(n uint64) common.Hash @@ -71,6 +72,9 @@ func setDefaults(cfg *Config) { if cfg.GasLimit == 0 { cfg.GasLimit = math.MaxUint64 } + if cfg.QuaiStateSize == nil { + cfg.QuaiStateSize = new(big.Int) + } if cfg.GasPrice == nil { cfg.GasPrice = new(big.Int) } @@ -109,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { setDefaults(cfg) if cfg.State == nil { - cfg.State, _ = state.New(common.Hash{}, common.Hash{}, common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), &snapshot.Tree{}, cfg.ChainConfig.Location, cfg.Logger) + cfg.State, _ = state.New(common.Hash{}, common.Hash{}, common.Hash{}, new(big.Int), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), &snapshot.Tree{}, cfg.ChainConfig.Location, cfg.Logger) } var ( address = common.BytesToAddress([]byte("contract"), cfg.ChainConfig.Location) @@ -147,7 +151,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { setDefaults(cfg) if cfg.State == nil { - cfg.State, _ = state.New(common.Hash{}, common.Hash{}, common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), &snapshot.Tree{}, cfg.ChainConfig.Location, cfg.Logger) + cfg.State, _ = state.New(common.Hash{}, common.Hash{}, common.Hash{}, new(big.Int), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), state.NewDatabase(rawdb.NewMemoryDatabase(cfg.Logger)), &snapshot.Tree{}, cfg.ChainConfig.Location, cfg.Logger) } var ( vmenv = NewEnv(cfg) diff --git a/core/worker.go b/core/worker.go index f627089a90..adeda69ea1 100644 --- a/core/worker.go +++ b/core/worker.go @@ -840,7 +840,8 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t snap := env.state.Snapshot() // retrieve the gas used int and pass in the reference to the ApplyTransaction gasUsed := env.wo.GasUsed() - receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) + stateUsed := env.wo.StateUsed() + receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, &stateUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) if err != nil { w.logger.WithFields(log.Fields{ "err": err, diff --git a/params/protocol_params.go b/params/protocol_params.go index efa4b5aa84..0f167bfc41 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -17,6 +17,7 @@ package params import ( + "math" "math/big" "github.com/dominant-strategies/go-quai/common" @@ -31,7 +32,8 @@ const ( MinGasLimit uint64 = 40000000 // Minimum the gas limit may ever be. GenesisGasLimit uint64 = 5000000 // Gas limit of the Genesis block. - StateCeil uint64 = 40000000 // Maximum the StateCeil may ever be + StateCeil uint64 = 40000000 // Maximum the StateCeil may ever be + EtxStateUsed uint64 = 10000 // state used by a simple etx MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. @@ -54,21 +56,8 @@ const ( Sha3Gas uint64 = 30 // Once per SHA3 operation. Sha3WordGas uint64 = 6 // Once per word of the SHA3 operation's data. - SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change. - SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero. - - NetSstoreNoopGas uint64 = 200 // Once per SSTORE operation if the value doesn't change. - NetSstoreInitGas uint64 = 20000 // Once per SSTORE operation from clean zero. - NetSstoreCleanGas uint64 = 5000 // Once per SSTORE operation from clean non-zero. - NetSstoreDirtyGas uint64 = 200 // Once per SSTORE operation from dirty. - - NetSstoreClearRefund uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot - NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value - NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value - - SstoreSentryGas uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed - SstoreSetGas uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero - SstoreResetGas uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else + SstoreSetGas uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero + SstoreResetGas uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else ColdAccountAccessCost = uint64(2600) // COLD_ACCOUNT_ACCESS_COST ColdSloadCost = uint64(2100) // COLD_SLOAD_COST @@ -99,11 +88,7 @@ const ( TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in access list // These have been changed during the course of the chain - CallGas uint64 = 700 // Static portion of gas for CALL-derivates - BalanceGas uint64 = 700 // The cost of a BALANCE operation - ExtcodeSizeGas uint64 = 700 // Cost of EXTCODESIZE SloadGas uint64 = 800 - ExtcodeHashGas uint64 = 700 // Cost of EXTCODEHASH SelfdestructGas uint64 = 5000 // Cost of SELFDESTRUCT // EXP has a dynamic portion depending on the size of the exponent @@ -221,3 +206,15 @@ func RegionEntropyTarget(expansionNum uint8) *big.Int { timeFactor := int64(max(numZones, 2)) return new(big.Int).Mul(big.NewInt(timeFactor), new(big.Int).SetUint64(numZones)) } + +func SstoreSentryGas(stateSize, contractSize *big.Int) uint64 { + return 2300 * CalculateGasScalingFactor(stateSize, contractSize) // Minimum gas required to be present for an SSTORE call, not consumed +} + +func CalculateGasScalingFactor(stateSize, contractSize *big.Int) uint64 { + stateSizeFloat, _ := stateSize.Float64() + contractSizeFloat, _ := contractSize.Float64() + scalingFactor := math.Log(stateSizeFloat) + math.Log(contractSizeFloat) + // If we can assume that the gas price constants is correct for level 7 trie + return uint64(scalingFactor) / 7 +}