Skip to content

Commit

Permalink
Added stateGas value into the contract and dynamic and constant gas
Browse files Browse the repository at this point in the history
calculation is done based on the size of the state and contract storage
trie
  • Loading branch information
gameofpointers committed Aug 23, 2024
1 parent 1f33c90 commit e517cdb
Show file tree
Hide file tree
Showing 18 changed files with 681 additions and 554 deletions.
5 changes: 4 additions & 1 deletion core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]]))
Expand Down
1 change: 1 addition & 0 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
78 changes: 40 additions & 38 deletions core/state_processor.go

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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
Expand Down Expand Up @@ -317,6 +318,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,
Expand Down Expand Up @@ -359,6 +361,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
contractAddr *common.Address
)
var stateUsed uint64
if contractCreation {
var contract common.Address
ret, contract, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
Expand All @@ -374,7 +377,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, err
}
st.state.SetNonce(from, st.state.GetNonce(addr)+1)
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, st.msg.Lock())
ret, st.gas, stateUsed, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, st.msg.Lock())
}

// At this point, the execution completed, so the ETX cache can be dumped and reset
Expand All @@ -400,6 +403,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {

return &ExecutionResult{
UsedGas: st.gasUsed(),
UsedState: stateUsed,
Err: vmerr,
ReturnData: ret,
Etxs: etxs,
Expand Down
2 changes: 1 addition & 1 deletion core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion core/types/qi_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func CalculateQiTxGas(transaction *Transaction, location common.Location) uint64
txGas += params.ETXGas + params.TxGas
} else if location.Equal(*toAddr.Location()) && toAddr.IsInQuaiLedgerScope() {
// This output creates a conversion
txGas += params.ETXGas + params.TxGas + params.ColdSloadCost + params.ColdSloadCost + params.SstoreSetGas + params.SstoreSetGas
txGas += params.ETXGas + params.TxGas + params.ColdSloadCost(big.NewInt(0), big.NewInt(0)) + params.ColdSloadCost(big.NewInt(0), big.NewInt(0)) + params.SstoreSetGas(big.NewInt(0), big.NewInt(0)) + params.SstoreSetGas(big.NewInt(0), big.NewInt(0))
}
}
return txGas
Expand Down
13 changes: 11 additions & 2 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}

Expand Down Expand Up @@ -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()
Expand Down
124 changes: 72 additions & 52 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,165 +495,185 @@ func RequiredGas(input []byte) uint64 {
}

// RedeemQuai executes the lockup contract to redeem the locked balance(s) for the sender
func RedeemQuai(statedb StateDB, sender common.Address, gas *types.GasPool, blockHeight *big.Int, lockupContractAddress common.Address) (uint64, error) {
func RedeemQuai(blockContext BlockContext, statedb StateDB, sender common.Address, gas *types.GasPool, blockHeight *big.Int, lockupContractAddress common.Address) (uint64, uint64, error) {
internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress()
if err != nil {
return 0, err
return 0, 0, err
}
stateSize := blockContext.QuaiStateSize
contractSize := statedb.GetSize(internalContractAddress)
// The current lock is the next available lock to redeem (in order of creation)
currentLockHash := statedb.GetState(internalContractAddress, sender.Hash())
gasUsed := params.ColdSloadCost
if gas.SubGas(params.ColdSloadCost) != nil {
coldSloadCost := params.ColdSloadCost(stateSize, contractSize)
sstoreResetGas := params.SstoreResetGas(stateSize, contractSize)
gasUsed := coldSloadCost
stateGas := coldSloadCost
if gas.SubGas(coldSloadCost) != nil {
// This contract does not revert. If the caller runs out of gas, we just stop
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
if (currentLockHash == common.Hash{}) {
return gasUsed, errors.New("lockup not found")
return gasUsed, stateGas, errors.New("lockup not found")
}
currentLockNumber := new(big.Int).SetBytes(currentLockHash[:])
if !currentLockNumber.IsUint64() {
return gasUsed, errors.New("account has locked too many times, overflows uint64")
return gasUsed, stateGas, errors.New("account has locked too many times, overflows uint64")
}

for i := int64(0); i < math.MaxInt64; i++ { // TODO: We should decide on a reasonable limit
// Ensure we have enough gas to complete this step entirely
requiredGas := params.ColdSloadCost + params.SstoreResetGas + params.ColdSloadCost + params.SstoreResetGas + params.SstoreResetGas + params.CallValueTransferGas
requiredGas := coldSloadCost + sstoreResetGas + coldSloadCost + sstoreResetGas + sstoreResetGas + params.CallValueTransferGas
if gas.Gas() < requiredGas {
return gasUsed, fmt.Errorf("insufficient gas to complete lockup redemption, required %d, have %d", requiredGas, gas.Gas())
return gasUsed, stateGas, fmt.Errorf("insufficient gas to complete lockup redemption, required %d, have %d", requiredGas, gas.Gas())
}
// The key is zero padded + sender's address + current lock pointer + 1
key := sender.Bytes()
// Append current lock pointer to the key
key = binary.BigEndian.AppendUint64(key, currentLockNumber.Uint64())
key = append(key, byte(1)) // Set the 29th byte of the key to 1 to get lock height
if len(key) > common.HashLength {
return gasUsed, errors.New("lockup key is too long, math is broken")
return gasUsed, stateGas, errors.New("lockup key is too long, math is broken")
}
lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key))
gasUsed += params.ColdSloadCost
if gas.SubGas(params.ColdSloadCost) != nil {
gasUsed += coldSloadCost
stateGas += coldSloadCost
if gas.SubGas(coldSloadCost) != nil {
// This contract does not revert. If the caller runs out of gas, we just stop
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
if (lockHash == common.Hash{}) {
// Lock doesn't exist, so we're done
return gasUsed, nil
return gasUsed, stateGas, nil
}
lock := new(big.Int).SetBytes(lockHash[:])
if lock.Cmp(blockHeight) > 0 {
// lock not ready yet. Lockups are stored in FIFO order, so we don't have to go through the rest
return gasUsed, fmt.Errorf("lockup not ready yet, lock height: %d, current block height: %d", lock, blockHeight)
return gasUsed, stateGas, fmt.Errorf("lockup not ready yet, lock height: %d, current block height: %d", lock, blockHeight)
}
// Set the lock to zero
statedb.SetState(internalContractAddress, common.BytesToHash(key), common.Hash{})
gasUsed += params.SstoreResetGas
if gas.SubGas(params.SstoreResetGas) != nil {
gasUsed += sstoreResetGas
stateGas += sstoreResetGas
if gas.SubGas(sstoreResetGas) != nil {
// This contract does not revert. If the caller runs out of gas, we just stop
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
key[28] = 0 // Set the 29th byte of the key to 0 for balance
balanceHash := statedb.GetState(internalContractAddress, common.BytesToHash(key))
gasUsed += params.ColdSloadCost
if gas.SubGas(params.ColdSloadCost) != nil {
return gasUsed, ErrOutOfGas
gasUsed += coldSloadCost
stateGas += coldSloadCost
if gas.SubGas(coldSloadCost) != nil {
return gasUsed, stateGas, ErrOutOfGas
}
if (balanceHash == common.Hash{}) {
// If locked balance after covnert is zero, either it doesn't exist or something is broken
return gasUsed, errors.New("balance not found")
return gasUsed, stateGas, errors.New("balance not found")
}
// Set the locked balance to zero
statedb.SetState(internalContractAddress, common.BytesToHash(key), common.Hash{})
gasUsed += params.SstoreResetGas
if gas.SubGas(params.SstoreResetGas) != nil {
return gasUsed, ErrOutOfGas
gasUsed += sstoreResetGas
stateGas += sstoreResetGas
if gas.SubGas(sstoreResetGas) != nil {
return gasUsed, stateGas, ErrOutOfGas
}
// Increment the current lock counter
currentLockNumber.Add(currentLockNumber, big1)
currentLockHash = common.BytesToHash(binary.BigEndian.AppendUint64([]byte{}, currentLockNumber.Uint64()))
statedb.SetState(internalContractAddress, sender.Hash(), currentLockHash)
gasUsed += params.SstoreResetGas
if gas.SubGas(params.SstoreResetGas) != nil {
return gasUsed, ErrOutOfGas
gasUsed += sstoreResetGas
stateGas += sstoreResetGas
if gas.SubGas(sstoreResetGas) != nil {
return gasUsed, stateGas, ErrOutOfGas
}

// Redeem the balance for the sender
balance := new(big.Int).SetBytes(balanceHash[:])
internal, err := sender.InternalAndQuaiAddress()
if err != nil {
return gasUsed, err
return gasUsed, stateGas, err
}
statedb.AddBalance(internal, balance)
gasUsed += params.CallValueTransferGas
if gas.SubGas(params.CallValueTransferGas) != nil {
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
}

return gasUsed, errors.New("account has locked too many times, overflows int64")
return gasUsed, stateGas, errors.New("account has locked too many times, overflows int64")
}

// AddNewLock adds a new locked balance to the lockup contract
func AddNewLock(statedb StateDB, toAddr common.Address, gas *types.GasPool, lock *big.Int, balance *big.Int, lockupContractAddress common.Address) (uint64, error) {
func AddNewLock(blockContext BlockContext, statedb StateDB, toAddr common.Address, gas *types.GasPool, lock *big.Int, balance *big.Int, lockupContractAddress common.Address) (uint64, uint64, error) {
internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress()
if err != nil {
return 0, err
return 0, 0, err
}
if len(lock.Bytes()) > common.HashLength || len(balance.Bytes()) > common.HashLength {
return 0, errors.New("lock or balance is too large")
return 0, 0, errors.New("lock or balance is too large")
}
stateSize := blockContext.QuaiStateSize
contractSize := statedb.GetSize(internalContractAddress)
currentLockHash := statedb.GetState(internalContractAddress, common.BytesToHash(toAddr.Bytes()))
gasUsed := params.ColdSloadCost
if gas.SubGas(params.ColdSloadCost) != nil {
coldSloadCost := params.ColdSloadCost(stateSize, contractSize)
sstoreSetGas := params.SstoreSetGas(stateSize, contractSize)
gasUsed := coldSloadCost
stateGas := coldSloadCost
if gas.SubGas(coldSloadCost) != nil {
// This contract does not revert. If the caller runs out of gas, we just stop
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
if (currentLockHash == common.Hash{}) {
// No lock found, create a new one
statedb.SetState(internalContractAddress, common.BytesToHash(toAddr.Bytes()), common.BytesToHash([]byte{1}))
gasUsed += sstoreSetGas
stateGas += sstoreSetGas
currentLockHash = common.BytesToHash([]byte{1})
}
currentLockNumber := new(big.Int).SetBytes(currentLockHash[:])
if !currentLockNumber.IsUint64() {
return gasUsed, errors.New("account has locked too many times, overflows uint64")
return gasUsed, stateGas, errors.New("account has locked too many times, overflows uint64")
}

for i := int64(0); i < math.MaxInt64; i++ { // TODO: We should decide on a reasonable limit
// Ensure we have enough gas to complete this step entirely
requiredGas := params.ColdSloadCost + params.SstoreSetGas + params.SstoreSetGas
requiredGas := coldSloadCost + sstoreSetGas + sstoreSetGas
if gas.Gas() < requiredGas {
return gasUsed, fmt.Errorf("insufficient gas to add new lock, required %d, got %d", requiredGas, gas.Gas())
return gasUsed, stateGas, fmt.Errorf("insufficient gas to add new lock, required %d, got %d", requiredGas, gas.Gas())
}
key := toAddr.Bytes()
// Append current lock to the key
key = binary.BigEndian.AppendUint64(key, currentLockNumber.Uint64())
key = append(key, byte(1)) // Set the 29th byte of the key to 1 for lockup
if len(key) > common.HashLength {
return gasUsed, errors.New("lockup key is too long, math is broken")
return gasUsed, stateGas, errors.New("lockup key is too long, math is broken")
}
lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key))
gasUsed += params.ColdSloadCost
if gas.SubGas(params.ColdSloadCost) != nil {
gasUsed += coldSloadCost
stateGas += coldSloadCost
if gas.SubGas(coldSloadCost) != nil {
// This contract does not revert. If the caller runs out of gas, we just stop
return gasUsed, ErrOutOfGas
return gasUsed, stateGas, ErrOutOfGas
}
if (lockHash == common.Hash{}) {
// Lock doesn't exist, so add the new one here
statedb.SetState(internalContractAddress, common.BytesToHash(key), common.BytesToHash(lock.Bytes()))
gasUsed += params.SstoreSetGas
if gas.SubGas(params.SstoreSetGas) != nil {
return gasUsed, ErrOutOfGas
gasUsed += sstoreSetGas
stateGas += sstoreSetGas
if gas.SubGas(sstoreSetGas) != nil {
return gasUsed, stateGas, ErrOutOfGas
}
key[28] = 0 // Set the 29th byte of the key to 0 for balance
statedb.SetState(internalContractAddress, common.BytesToHash(key), common.BytesToHash(balance.Bytes()))
gasUsed += params.SstoreSetGas
if gas.SubGas(params.SstoreSetGas) != nil {
return gasUsed, ErrOutOfGas
gasUsed += sstoreSetGas
stateGas += sstoreSetGas
if gas.SubGas(sstoreSetGas) != nil {
return gasUsed, stateGas, ErrOutOfGas
}
// Addition of new lock successful
return gasUsed, nil
return gasUsed, stateGas, nil
}
// Lock exists, increment the current lock counter (but don't store it)
currentLockNumber.Add(currentLockNumber, big1)
}
return gasUsed, errors.New("account has locked too many times, overflows int64")
return gasUsed, stateGas, errors.New("account has locked too many times, overflows int64")
}
Loading

0 comments on commit e517cdb

Please sign in to comment.