Skip to content

Commit

Permalink
State processor checks locks, added lock to snapshot, convert confirm…
Browse files Browse the repository at this point in the history
…ation ctx
  • Loading branch information
jdowning100 committed Mar 25, 2024
1 parent 0082b93 commit c0978b5
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 23 deletions.
5 changes: 4 additions & 1 deletion core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,14 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash

// CanTransfer checks whether there are enough funds in the address' account to make a transfer.
// This does not take the necessary gas in to account to make the transfer valid.
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int, blockheight *big.Int) bool {
internalAddr, err := addr.InternalAndQuaiAddress()
if err != nil {
return false
}
if db.GetLock(internalAddr) != nil && db.GetLock(internalAddr).Cmp(blockheight) > 0 {
return false
}
return db.GetBalance(internalAddr).Cmp(amount) >= 0
}

Expand Down
8 changes: 5 additions & 3 deletions core/state/snapshot/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@ import (
// byte slice.
type Account struct {
Nonce uint64
Lock *big.Int
Balance *big.Int
Root []byte
CodeHash []byte
}

// SlimAccount converts a state.Account content into a slim snapshot account
func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account {
func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte, lock *big.Int) Account {
slim := Account{
Nonce: nonce,
Balance: balance,
Lock: lock,
}
if root != emptyRoot {
slim.Root = root[:]
Expand All @@ -52,8 +54,8 @@ func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []by

// SlimAccountRLP converts a state.Account content into a slim snapshot
// version RLP encoded.
func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte {
data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash))
func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte, lock *big.Int) []byte {
data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash, lock))
if err != nil {
panic(err)
}
Expand Down
3 changes: 2 additions & 1 deletion core/state/snapshot/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
// Retrieve the current account and flatten it into the internal format
var acc struct {
Nonce uint64
Lock *big.Int
Balance *big.Int
Root common.Hash
CodeHash []byte
Expand All @@ -571,7 +572,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
dataLen -= 32
}
} else {
data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash, acc.Lock)
dataLen = len(data)
rawdb.WriteAccountSnapshot(batch, accountHash, data)
}
Expand Down
3 changes: 2 additions & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
// enough to track account updates at commit time, deletions need tracking
// at transaction boundary level to ensure we capture state clearing.
if s.snap != nil {
s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash)
s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash, obj.data.Lock)
}
}

Expand Down Expand Up @@ -655,6 +655,7 @@ func (s *StateDB) getDeletedStateObject(addr common.InternalAddress) *stateObjec
}
data = &Account{
Nonce: acc.Nonce,
Lock: acc.Lock,
Balance: acc.Balance,
CodeHash: acc.CodeHash,
Root: common.BytesToHash(acc.Root),
Expand Down
31 changes: 29 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,12 @@ func (p *StateProcessor) Process(block *types.Block, etxSet *types.EtxSet) (type
}
} else if tx.Type() == types.QuaiTxType {
startTimeTx := time.Now()

from, _ := msg.From().InternalAndQuaiAddress()
if lock := statedb.GetLock(from); lock != nil && lock.Cmp(blockNumber) > 0 {
return nil, nil, nil, nil, 0, fmt.Errorf("tx %v is from an address with a lock that has not expired, lock height %s", tx.Hash().Hex(), lock.String())
} else if lock != nil && lock.Cmp(blockNumber) <= 0 {
statedb.SetLock(from, nil)
}
receipt, err = applyTransaction(msg, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, 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)
Expand Down Expand Up @@ -521,6 +526,9 @@ func ProcessQiTx(tx *types.Transaction, updateState bool, currentHeader *types.H
if utxo == nil {
return nil, nil, fmt.Errorf("tx %032x spends non-existent UTXO %032x:%d", tx.Hash(), txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index)
}
if utxo.Lock != nil && utxo.Lock.Cmp(currentHeader.Number(location.Context())) > 0 {
return nil, nil, fmt.Errorf("tx %032x spends locked UTXO %032x:%d locked until %s", tx.Hash(), txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index, utxo.Lock.String())
}
// Verify the pubkey
address := crypto.PubkeyBytesToAddress(txIn.PubKey, location)
entryAddr := common.BytesToAddress(utxo.Address, location)
Expand Down Expand Up @@ -597,6 +605,19 @@ func ProcessQiTx(tx *types.Transaction, updateState bool, currentHeader *types.H
if ETXPCount > *etxPLimit {
return nil, nil, fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, etxPLimit)
}
if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai Conversion
if txOut.Denomination < params.MinQiConversionDenomination {
return nil, nil, fmt.Errorf("tx %v emits UTXO with value %d less than minimum denomination %d", tx.Hash().Hex(), txOut.Denomination, params.MinQiConversionDenomination)
}
internal, _ := toAddr.InternalAndQuaiAddress()
if statedb.Exist(internal) {
return nil, nil, fmt.Errorf("tx %v emits UTXO with address %v that already exists in the Quai ledger", tx.Hash().Hex(), internal.String())
}
// We should require some kind of extra fee here
statedb.CreateAccount(internal)
// lock it almost indefinitely, until the ETX plays in the destination
statedb.SetLock(internal, new(big.Int).Add(currentHeader.Number(location.Context()), big.NewInt(params.ConversionLockPeriod*1000)))
}

// We should require some kind of extra fee here
etxInner := types.ExternalTx{Value: big.NewInt(int64(txOut.Denomination)), To: &toAddr, Sender: common.ZeroAddress(location), OriginatingTxHash: tx.Hash(), ETXIndex: uint16(txOutIdx), Gas: params.TxGas}
Expand Down Expand Up @@ -749,12 +770,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
if err != nil {
return nil, err
}
if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Qi->Quai Conversion
if tx.Type() == types.ExternalTxType && tx.ETXSender().Location().Equal(*tx.To().Location()) { // Qi->Quai Conversion
parent := bc.GetHeaderOrCandidate(header.ParentHash(nodeCtx), header.NumberU64(nodeCtx)-1)
msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)))
msg.SetValue(misc.QiToQuai(parent, tx.Value())) // change to prime terminus
msg.SetData([]byte{}) // data is not used in conversion
}
from, _ := msg.From().InternalAndQuaiAddress()
if lock := statedb.GetLock(from); lock != nil && lock.Cmp(header.Number(nodeCtx)) > 0 {
return nil, fmt.Errorf("tx %v is from an address with a lock that has not expired, lock height %s", tx.Hash().Hex(), lock.String())
} else if lock != nil && lock.Cmp(header.Number(nodeCtx)) <= 0 {
statedb.SetLock(from, nil)
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
Expand Down
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.gas -= gas

// Check clause 6
if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) {
if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value(), st.evm.Context.BlockNumber) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
}

Expand Down
4 changes: 4 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,10 @@ func (tx *Transaction) ConfirmationCtx(nodeLocation common.Location) int {
if ctx := tx.confirmCtx.Load(); ctx != nil {
return ctx.(int)
}
if tx.ETXSender().Location().Equal(*tx.To().Location()) {
// If the ETX sender and the destination chain are the same, the ETX is a conversion tx
return common.REGION_CTX
}

ctx := tx.To().Location().CommonDom(tx.FromChain(nodeLocation)).Context()
tx.confirmCtx.Store(ctx)
Expand Down
12 changes: 7 additions & 5 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var emptyCodeHash = crypto.Keccak256Hash(nil)

type (
// CanTransferFunc is the signature of a transfer guard function
CanTransferFunc func(StateDB, common.Address, *big.Int) bool
CanTransferFunc func(StateDB, common.Address, *big.Int, *big.Int) bool
// TransferFunc is the signature of a transfer function
TransferFunc func(StateDB, common.Address, common.Address, *big.Int) error
// GetHashFunc returns the n'th block hash in the blockchain
Expand Down Expand Up @@ -185,7 +185,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value, evm.Context.BlockNumber) {
return nil, gas, ErrInsufficientBalance
}
snapshot := evm.StateDB.Snapshot()
Expand Down Expand Up @@ -272,7 +272,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// Note although it's noop to transfer X ether to caller itself. But
// if caller doesn't have enough balance, it would be an error to allow
// over-charging itself. So the check here is necessary.
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value, evm.Context.BlockNumber) {
return nil, gas, ErrInsufficientBalance
}
var snapshot = evm.StateDB.Snapshot()
Expand Down Expand Up @@ -417,7 +417,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Zero, gas, ErrDepth
}
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value, evm.Context.BlockNumber) {
return nil, common.Zero, gas, ErrInsufficientBalance
}

Expand Down Expand Up @@ -594,6 +594,8 @@ func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas ui
}
if toAddr.IsInQiLedgerScope() && !common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) {
return []byte{}, 0, fmt.Errorf("%x is in qi scope and is not in the same location, but CreateETX was called", toAddr)
} else if toAddr.IsInQiLedgerScope() && common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) && value.Cmp(params.MinQuaiConversionAmount) < 0 {
return []byte{}, 0, fmt.Errorf("CreateETX conversion error: %d is not sufficient value, required amount: %d", value, params.MinQuaiConversionAmount)
}
if gas < params.ETXGas {
return []byte{}, 0, fmt.Errorf("CreateETX error: %d is not sufficient gas, required amount: %d", gas, params.ETXGas)
Expand All @@ -614,7 +616,7 @@ func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas ui
}

// Fail if we're trying to transfer more than the available balance
if !evm.Context.CanTransfer(evm.StateDB, fromAddr, value) {
if !evm.Context.CanTransfer(evm.StateDB, fromAddr, value, evm.Context.BlockNumber) {
return []byte{}, 0, fmt.Errorf("CreateETX: %x cannot transfer %d", fromAddr, value.Uint64())
}

Expand Down
2 changes: 1 addition & 1 deletion core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ func opETX(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
total := uint256.NewInt(0)
total.Add(&value, fee)
// Fail if we're trying to transfer more than the available balance
if total.Sign() == 0 || !interpreter.evm.Context.CanTransfer(interpreter.evm.StateDB, scope.Contract.self.Address(), total.ToBig()) {
if total.Sign() == 0 || !interpreter.evm.Context.CanTransfer(interpreter.evm.StateDB, scope.Contract.self.Address(), total.ToBig(), interpreter.evm.Context.BlockNumber) {
temp.Clear()
stack.push(&temp)
fmt.Printf("%x cannot transfer %d\n", scope.Contract.self.Address(), total.Uint64())
Expand Down
28 changes: 25 additions & 3 deletions core/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1249,12 +1249,15 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error {
gasUsed += txGas
addresses := make(map[common.AddressBytes]struct{})
totalQitIn := big.NewInt(0)
utxosDelete := make([]*types.OutPoint, 0, len(tx.TxIn()))
utxosDelete := make([]types.OutPoint, 0)
for _, txIn := range tx.TxIn() {
utxo := env.state.GetUTXO(txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index)
if utxo == nil {
return fmt.Errorf("tx %032x spends non-existent UTXO %032x:%d", tx.Hash(), txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index)
}
if utxo.Lock != nil && utxo.Lock.Cmp(env.header.Number(w.hc.NodeCtx())) > 0 {
return fmt.Errorf("tx %032x spends locked UTXO %032x:%d locked until %s", tx.Hash(), txIn.PreviousOutPoint.TxHash, txIn.PreviousOutPoint.Index, utxo.Lock.String())
}

// Perform some spend processing logic
denomination := utxo.Denomination
Expand All @@ -1271,13 +1274,14 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error {
}
addresses[common.AddressBytes(utxo.Address)] = struct{}{}
totalQitIn.Add(totalQitIn, types.Denominations[denomination])
utxosDelete = append(utxosDelete, &txIn.PreviousOutPoint)
utxosDelete = append(utxosDelete, txIn.PreviousOutPoint)
}
var ETXRCount int
var ETXPCount int
etxs := make([]*types.Transaction, 0)
totalQitOut := big.NewInt(0)
utxosCreate := make(map[types.OutPoint]*types.UtxoEntry)
accountsToLock := make([]common.InternalAddress, 0)
for txOutIdx, txOut := range tx.TxOut() {

if txOut.Denomination > types.MaxDenomination {
Expand All @@ -1304,7 +1308,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error {
ETXRCount++
}
// Cross-prime?
if toAddr.Location().CommonDom(location).Context() == common.PRIME_CTX {
if toAddr.Location().CommonDom(location).Context() == common.PRIME_CTX { // fix this for conversion
ETXPCount++
}
if ETXRCount > env.etxRLimit {
Expand All @@ -1315,6 +1319,19 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error {
w.logger.Error(fmt.Errorf("tx %032x emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash(), ETXPCount, env.etxPLimit))
return fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, env.etxPLimit)
}
if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion
if txOut.Denomination < params.MinQiConversionDenomination {
w.logger.Error(fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), txOut.Denomination))
return fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), txOut.Denomination)
}
internal, _ := toAddr.InternalAndQuaiAddress()
if env.state.Exist(internal) { // this check might not be necessary in the state processor. It will fail in the destination (coins burned)
w.logger.Error(fmt.Errorf("tx %032x emits convert ETX with value %d to existing Quai address %s", tx.Hash(), txOut.Denomination, internal.String()))
return fmt.Errorf("tx %032x emits convert ETX with value %d to existing Quai address %s", tx.Hash(), txOut.Denomination, internal.String())
}
// We should require some kind of extra fee here
accountsToLock = append(accountsToLock, internal)
}

// We should require some kind of extra fee here
etxInner := types.ExternalTx{Value: big.NewInt(int64(txOut.Denomination)), To: &toAddr, Sender: common.ZeroAddress(location), OriginatingTxHash: tx.Hash(), ETXIndex: uint16(txOutIdx), Gas: params.TxGas}
Expand Down Expand Up @@ -1360,6 +1377,11 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error {
for outPoint, utxo := range utxosCreate {
env.state.CreateUTXO(outPoint.TxHash, outPoint.Index, utxo)
}
for _, account := range accountsToLock {
env.state.CreateAccount(account)
// lock it almost indefinitely, until the ETX plays in the destination
env.state.SetLock(account, new(big.Int).Add(env.header.Number(w.hc.NodeCtx()), big.NewInt(params.ConversionLockPeriod*1000)))
}
return nil
}

Expand Down
12 changes: 7 additions & 5 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ const (
MinimumEtxGasDivisor = 5 // The divisor for the minimum gas for inbound ETXs (Block gas limit / MinimumEtxGasDivisor)
MaximumEtxGasMultiplier = 2 // Multiplied with the minimum ETX gas for inbound ETXs (Block gas limit / MinimumEtxGasDivisor) * MaximumEtxGasMultiplier

ConversionLockPeriod int64 = 10 // The number of zone blocks that a conversion output is locked for
ConversionLockPeriod int64 = 10 // The number of zone blocks that a conversion output is locked for
MinQiConversionDenomination = 1
)

var (
Expand All @@ -163,8 +164,9 @@ var (
LighthouseDurationLimit = big.NewInt(7) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not.
LocalDurationLimit = big.NewInt(2) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not.
TimeFactor = big.NewInt(7)
PrimeEntropyTarget = big.NewInt(441) // This is TimeFactor*TimeFactor*common.NumZonesInRegion*common.NumRegionsInPrime
RegionEntropyTarget = big.NewInt(21) // This is TimeFactor*common.NumZonesInRegion
DifficultyAdjustmentPeriod = big.NewInt(360) // This is the number of blocks over which the average has to be taken
DifficultyAdjustmentFactor int64 = 40 // This is the factor that divides the log of the change in the difficulty
PrimeEntropyTarget = big.NewInt(441) // This is TimeFactor*TimeFactor*common.NumZonesInRegion*common.NumRegionsInPrime
RegionEntropyTarget = big.NewInt(21) // This is TimeFactor*common.NumZonesInRegion
DifficultyAdjustmentPeriod = big.NewInt(360) // This is the number of blocks over which the average has to be taken
DifficultyAdjustmentFactor int64 = 40 // This is the factor that divides the log of the change in the difficulty
MinQuaiConversionAmount = new(big.Int).Mul(big.NewInt(1), big.NewInt(Ether)) // 1000 Quai
)

0 comments on commit c0978b5

Please sign in to comment.