Skip to content

Commit

Permalink
Added stateGas value into the contract, but is calculated
Browse files Browse the repository at this point in the history
  • Loading branch information
gameofpointers committed Aug 20, 2024
1 parent 6d9e284 commit 9af3174
Show file tree
Hide file tree
Showing 19 changed files with 607 additions and 497 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
2 changes: 2 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()),
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 9af3174

Please sign in to comment.