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 26, 2024
1 parent efff83b commit 63df695
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 44 deletions.
15 changes: 12 additions & 3 deletions consensus/misc/rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"math/big"

"github.com/dominant-strategies/go-quai/core/types"
"github.com/dominant-strategies/go-quai/params"
)

func CalculateReward(header *types.Header) *big.Int {
Expand All @@ -17,23 +18,31 @@ func CalculateReward(header *types.Header) *big.Int {
// Calculate the amount of Quai that Qi can be converted to. Expect the current Header and the Qi amount in "qits", returns the quai amount in "its"
func QiToQuai(currentHeader *types.Header, qiAmt *big.Int) *big.Int {
quaiPerQi := new(big.Int).Div(calculateQuaiReward(currentHeader), calculateQiReward(currentHeader))
result := new(big.Int).Mul(qiAmt, quaiPerQi)
if result.Cmp(big.NewInt(0)) == 0 {
return big.NewInt(1)
}
return new(big.Int).Mul(qiAmt, quaiPerQi)
}

// Calculate the amount of Qi that Quai can be converted to. Expect the current Header and the Quai amount in "its", returns the Qi amount in "qits"
func QuaiToQi(currentHeader *types.Header, quaiAmt *big.Int) *big.Int {
qiPerQuai := new(big.Int).Div(calculateQiReward(currentHeader), calculateQuaiReward(currentHeader))
result := new(big.Int).Mul(quaiAmt, qiPerQuai)
if result.Cmp(types.Denominations[0]) < 0 {
return types.Denominations[0]
}
return new(big.Int).Mul(quaiAmt, qiPerQuai)
}

// CalculateQuaiReward calculates the quai that can be recieved for mining a block and returns value in its
func calculateQuaiReward(header *types.Header) *big.Int {
return big.NewInt(1000000000000000000)
return big.NewInt(params.Ether)
}

// CalculateQiReward caculates the qi that can be received for mining a block and returns value in qits
func calculateQiReward(header *types.Header) *big.Int {
return big.NewInt(1000)
return types.Denominations[types.MaxDenomination]
}

// FindMinDenominations finds the minimum number of denominations to make up the reward
Expand All @@ -43,7 +52,7 @@ func FindMinDenominations(reward *big.Int) map[uint8]uint8 {
amount := new(big.Int).Set(reward)

// Iterate over the denominations in descending order (by key)
for i := 15; i >= 0; i-- {
for i := types.MaxDenomination; i >= 0; i-- {
denom := types.Denominations[uint8(i)]

// Calculate the number of times the denomination fits into the remaining amount
Expand Down
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
62 changes: 50 additions & 12 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ func (p *StateProcessor) Process(block *types.Block, etxSet *types.EtxSet) (type
}
// the ETX hash is guaranteed to be unique
statedb.CreateUTXO(tx.Hash(), uint16(i), types.NewUtxoEntry(types.NewTxOut(denomination, tx.To().Bytes(), lock)))
log.Global.Infof("Created UTXO for ETX %032x with denomination %d index %d lock %d", tx.Hash(), denomination, i, lock)
if err := gp.SubGas(params.CallValueTransferGas); err != nil {
return nil, nil, nil, nil, 0, err
}
Expand All @@ -331,8 +332,12 @@ func (p *StateProcessor) Process(block *types.Block, etxSet *types.EtxSet) (type
} else {
if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Qi->Quai Conversion
msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)))
msg.SetValue(misc.QiToQuai(parent.Header(), tx.Value())) // change to prime terminus
msg.SetData([]byte{}) // data is not used in conversion
if tx.Value().Uint64() > types.MaxDenomination { // sanity check
return nil, nil, nil, nil, 0, fmt.Errorf("etx %032x emits conversion UTXO with value %d greater than max denomination", tx.Hash(), tx.Value().Int64())
}
msg.SetValue(misc.QiToQuai(parent.Header(), types.Denominations[uint8(tx.Value().Uint64())])) // change to prime terminus
msg.SetData([]byte{}) // data is not used in conversion
log.Global.Infof("Converting Qi to Quai for ETX %032x with value %d lock %d", tx.Hash(), msg.Value().Int64(), msg.Lock().Int64())
}
prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation)
receipt, err = applyTransaction(msg, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger)
Expand All @@ -346,7 +351,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 +531,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 @@ -573,9 +586,7 @@ func ProcessQiTx(tx *types.Transaction, updateState bool, currentHeader *types.H
totalQitOut.Add(totalQitOut, types.Denominations[txOut.Denomination])

toAddr := common.BytesToAddress(txOut.Address, location)
if !toAddr.IsInQiLedgerScope() {
return nil, nil, fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex())
}

// Enforce no address reuse
if _, exists := addresses[toAddr.Bytes20()]; exists {
return nil, nil, errors.New("Duplicate address in QiTx outputs: " + toAddr.String())
Expand All @@ -597,6 +608,23 @@ 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
if updateState {
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)))
}
} else if !toAddr.IsInQiLedgerScope() {
return nil, nil, fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex())
}

// 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 @@ -640,10 +668,11 @@ func ProcessQiTx(tx *types.Transaction, updateState bool, currentHeader *types.H
// the fee to pay the basefee/miner is the difference between inputs and outputs
txFeeInQit := new(big.Int).Sub(totalQitIn, totalQitOut)
// Check tx against required base fee and gas
baseFeeInQi := misc.QuaiToQi(currentHeader, currentHeader.BaseFee())
minimumFee := new(big.Int).Mul(baseFeeInQi, big.NewInt(int64(txGas)))
minimumFeeInQuai := new(big.Int).Mul(big.NewInt(int64(txGas)), currentHeader.BaseFee())
minimumFee := misc.QuaiToQi(currentHeader, minimumFeeInQuai)
log.Global.Infof("Minimum fee for tx %032x is %d", tx.Hash(), minimumFee.Uint64())
if txFeeInQit.Cmp(minimumFee) < 0 {
return nil, nil, fmt.Errorf("tx %032x has insufficient fee for base fee of %d and gas of %d", tx.Hash(), baseFeeInQi.Uint64(), txGas)
return nil, nil, fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQit.Uint64(), minimumFee.Uint64())
}
// Miner gets remainder of fee after base fee
txFeeInQit.Sub(txFeeInQit, minimumFee)
Expand Down Expand Up @@ -749,11 +778,20 @@ 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
if tx.Value().Uint64() > types.MaxDenomination { // sanity check
return nil, fmt.Errorf("tx %v emits conversion UTXO with value %d greater than max denomination", tx.Hash().Hex(), tx.Value().Int64())
}
msg.SetValue(misc.QiToQuai(parent, types.Denominations[uint8(tx.Value().Uint64())])) // 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)
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
2 changes: 1 addition & 1 deletion core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ func (pool *TxPool) addUtxoTx(tx *types.Transaction) error {
pool.logger.WithFields(logrus.Fields{
"tx": tx.Hash().String(),
"err": err,
}).Error("Invalid utxo tx")
}).Error("Invalid Qi transaction")
return err
}
pool.mu.RUnlock()
Expand Down
5 changes: 5 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/dominant-strategies/go-quai/common"
"github.com/dominant-strategies/go-quai/common/math"
"github.com/dominant-strategies/go-quai/params"
"google.golang.org/protobuf/proto"

"github.com/dominant-strategies/go-quai/crypto"
Expand Down Expand Up @@ -700,6 +701,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 params.ConversionConfirmationContext
}

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
Loading

0 comments on commit 63df695

Please sign in to comment.