From ce3873a7e7d187f779288b4301501a8bba7eb82b Mon Sep 17 00:00:00 2001 From: Jonathan Downing Date: Thu, 21 Mar 2024 16:59:19 -0500 Subject: [PATCH] Added conversion processing with ETX to origin and destination --- common/proto_common.pb.go | 4 +- consensus/blake3pow/consensus.go | 8 +- consensus/misc/rewards.go | 10 +- consensus/progpow/consensus.go | 7 ++ core/rawdb/db.pb.go | 4 +- core/state/statedb.go | 4 +- core/state_processor.go | 143 ++++++++++++++++++------- core/state_transition.go | 13 +-- core/tx_pool.go | 13 +-- core/{ => types}/gaspool.go | 5 +- core/types/proto_block.pb.go | 38 ++++--- core/types/proto_block.proto | 1 + core/types/qi_tx.go | 1 + core/types/transaction.go | 22 +++- core/types/utxo.go | 42 +++++++- core/types/utxo_viewpoint.go | 30 ------ core/vm/contracts.go | 175 ++++++++++++++++++++++++++++++- core/vm/evm.go | 38 ++++++- core/vm/instructions.go | 2 +- core/worker.go | 98 ++++++++++++----- internal/quaiapi/api.go | 9 +- p2p/pb/quai_messages.pb.go | 4 +- params/protocol_params.go | 7 ++ trie/proto_trienode.pb.go | 4 +- 24 files changed, 530 insertions(+), 152 deletions(-) rename core/{ => types}/gaspool.go (96%) delete mode 100644 core/types/utxo_viewpoint.go diff --git a/common/proto_common.pb.go b/common/proto_common.pb.go index bb1508d011..30b8fc2958 100644 --- a/common/proto_common.pb.go +++ b/common/proto_common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.2 // source: common/proto_common.proto package common diff --git a/consensus/blake3pow/consensus.go b/consensus/blake3pow/consensus.go index 892e3f34b2..bbb146da42 100644 --- a/consensus/blake3pow/consensus.go +++ b/consensus/blake3pow/consensus.go @@ -14,6 +14,7 @@ import ( "github.com/dominant-strategies/go-quai/core" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/core/vm" "github.com/dominant-strategies/go-quai/params" "github.com/dominant-strategies/go-quai/trie" "modernc.org/mathutil" @@ -570,8 +571,13 @@ func (blake3pow *Blake3pow) Finalize(chain consensus.ChainHeaderReader, header * continue } } - core.AddGenesisUtxos(state, nodeLocation, blake3pow.logger) + // Create the lockup contract account + lockupContract, err := vm.LockupContractAddresses[[2]byte{nodeLocation[0], nodeLocation[1]}].InternalAndQuaiAddress() + if err != nil { + panic(err) + } + state.CreateAccount(lockupContract) } header.Header().SetUTXORoot(state.UTXORoot()) header.Header().SetEVMRoot(state.IntermediateRoot(true)) diff --git a/consensus/misc/rewards.go b/consensus/misc/rewards.go index 58387a7bd3..daa82956c3 100644 --- a/consensus/misc/rewards.go +++ b/consensus/misc/rewards.go @@ -17,12 +17,20 @@ func CalculateReward(header *types.WorkObject) *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.WorkObject, 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.WorkObject, 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) } @@ -43,7 +51,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 diff --git a/consensus/progpow/consensus.go b/consensus/progpow/consensus.go index 35cb1ba45e..2464cb778d 100644 --- a/consensus/progpow/consensus.go +++ b/consensus/progpow/consensus.go @@ -15,6 +15,7 @@ import ( "github.com/dominant-strategies/go-quai/core" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/core/vm" "github.com/dominant-strategies/go-quai/params" "github.com/dominant-strategies/go-quai/trie" "modernc.org/mathutil" @@ -607,6 +608,12 @@ func (progpow *Progpow) Finalize(chain consensus.ChainHeaderReader, header *type } } core.AddGenesisUtxos(state, nodeLocation, progpow.logger) + // Create the lockup contract account + lockupContract, err := vm.LockupContractAddresses[[2]byte{nodeLocation[0], nodeLocation[1]}].InternalAndQuaiAddress() + if err != nil { + panic(err) + } + state.CreateAccount(lockupContract) } header.Header().SetUTXORoot(state.UTXORoot()) header.Header().SetEVMRoot(state.IntermediateRoot(true)) diff --git a/core/rawdb/db.pb.go b/core/rawdb/db.pb.go index 30fb67b07d..0f2560b344 100644 --- a/core/rawdb/db.pb.go +++ b/core/rawdb/db.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.2 // source: core/rawdb/db.proto package rawdb diff --git a/core/state/statedb.go b/core/state/statedb.go index 5dfae106d2..6d948079b2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -297,7 +297,6 @@ func (s *StateDB) GetBalance(addr common.InternalAddress) *big.Int { } return common.Big0 } - func (s *StateDB) GetNonce(addr common.InternalAddress) uint64 { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -583,6 +582,9 @@ func (s *StateDB) CreateUTXO(txHash common.Hash, outputIndex uint16, utxo *types if err := common.CheckIfBytesAreInternalAndQiAddress(utxo.Address, s.nodeLocation); err != nil { return err } + if utxo.Denomination > types.MaxDenomination { // sanity check + return fmt.Errorf("tx %032x emits UTXO with value %d greater than max denomination", txHash, utxo.Denomination) + } data, err := rlp.EncodeToBytes(utxo) if err != nil { panic(fmt.Errorf("can't encode UTXO entry at %x: %v", txHash, err)) diff --git a/core/state_processor.go b/core/state_processor.go index ebe5e78cfa..7188294b6b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -208,7 +208,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) nodeCtx = p.hc.NodeCtx() blockNumber = block.Number(nodeCtx) allLogs []*types.Log - gp = new(GasPool).AddGas(block.GasLimit()) + gp = new(types.GasPool).AddGas(block.GasLimit()) ) start := time.Now() parent := p.hc.GetBlock(block.ParentHash(nodeCtx), block.NumberU64(nodeCtx)-1) @@ -279,7 +279,9 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) if err != nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - qiEtxs = append(qiEtxs, etxs...) + for _, etx := range etxs { + qiEtxs = append(qiEtxs, types.NewTx(etx)) + } totalFees.Add(totalFees, fees) continue } @@ -305,23 +307,57 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) 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()) } if tx.To().IsInQiLedgerScope() { - if tx.Value().Int64() > types.MaxDenomination { // sanity check - return nil, nil, nil, nil, 0, fmt.Errorf("etx %032x emits UTXO with value %d greater than max denomination", tx.Hash(), tx.Value().Int64()) - } - // There are no more checks to be made as the ETX is worked so add it to the set - if err := statedb.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))); err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not create UTXO for etx %032x: %w", tx.Hash(), err) - } - if err := gp.SubGas(params.CallValueTransferGas); err != nil { - return nil, nil, nil, nil, 0, err + if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Quai->Qi Conversion + lock := new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)) + value := misc.QuaiToQi(parent, tx.Value()) // change to prime terminus + txGas := tx.Gas() + denominations := misc.FindMinDenominations(value) + for i, denomination := range denominations { // TODO: Decide maximum number of iterations + if txGas < params.CallValueTransferGas { + // No more gas, the rest of the denominations are lost but the tx is still valid + break + } + txGas -= params.CallValueTransferGas + // the ETX hash is guaranteed to be unique + if err := statedb.CreateUTXO(tx.Hash(), uint16(i), types.NewUtxoEntry(types.NewTxOut(denomination, tx.To().Bytes(), lock))); err != nil { + return nil, nil, nil, nil, 0, err + } + 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 + } + *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 + } + } else { + // There are no more checks to be made as the ETX is worked so add it to the set + if err := statedb.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Uint64()), tx.To().Bytes(), big.NewInt(0)))); err != nil { + return nil, nil, nil, nil, 0, err + } + if err := gp.SubGas(params.CallValueTransferGas); err != nil { + return nil, nil, nil, nil, 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 } - *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 timeEtxDelta := time.Since(startTimeEtx) timeEtx += timeEtxDelta continue } else { - prevZeroBal := prepareApplyETX(statedb, tx, nodeLocation) + if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Qi->Quai Conversion + msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod))) + 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().Uint64()) + } + 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()) + } + msg.SetValue(misc.QiToQuai(primeTerminus, types.Denominations[uint8(tx.Value().Uint64())])) + 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().Uint64, msg.Lock().Uint64()) + } + prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation) receipt, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, 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 { @@ -462,7 +498,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) return receipts, qiEtxs, allLogs, statedb, *usedGas, nil } -func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *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, 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, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, error) { nodeLocation := config.Location // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) @@ -526,7 +562,7 @@ func applyTransaction(msg types.Message, parent *types.WorkObject, config *param return receipt, err } -func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, currentHeader *types.WorkObject, statedb *state.StateDB, gp *GasPool, usedGas *uint64, signer types.Signer, location common.Location, chainId big.Int, etxRLimit, etxPLimit *int) (*big.Int, []*types.Transaction, error) { +func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, currentHeader *types.WorkObject, statedb *state.StateDB, gp *types.GasPool, usedGas *uint64, signer types.Signer, location common.Location, chainId big.Int, etxRLimit, etxPLimit *int) (*big.Int, []*types.ExternalTx, error) { // Sanity checks if tx == nil || tx.Type() != types.QiTxType { return nil, nil, fmt.Errorf("tx %032x is not a QiTx", tx.Hash()) @@ -553,6 +589,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu 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) @@ -587,8 +626,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu } var ETXRCount int var ETXPCount int - etxs := make([]*types.Transaction, 0) + etxs := make([]*types.ExternalTx, 0) totalQitOut := big.NewInt(0) + conversion := false for txOutIdx, txOut := range tx.TxOut() { // It would be impossible for a tx to have this many outputs based on block gas limit, but cap it here anyways if txOutIdx > types.MaxOutputIndex { @@ -605,16 +645,14 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu 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()) } addresses[toAddr.Bytes20()] = struct{}{} - if !toAddr.Location().Equal(location) { // This output creates an ETX + if !toAddr.Location().Equal(location) || (toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope()) { // This output creates an ETX // Cross-region? if toAddr.Location().CommonDom(location).Context() == common.REGION_CTX { ETXRCount++ @@ -629,17 +667,26 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu 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) } - - // 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} - etx := types.NewTx(&etxInner) primeTerminus := currentHeader.PrimeTerminus() primeTerminusHeader := chain.GetHeaderByHash(primeTerminus) + if primeTerminusHeader == nil { + return nil, nil, fmt.Errorf("could not find prime terminus header %032x", primeTerminus) + } + if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai Conversion + conversion = true + 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) + } + } 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()) + } if !chain.CheckIfEtxIsEligible(primeTerminusHeader.EtxEligibleSlices(), *toAddr.Location()) { return nil, nil, fmt.Errorf("etx emitted by tx [%v] going to a slice that is not eligible to receive etx %v", tx.Hash().Hex(), *toAddr.Location()) } - etxs = append(etxs, etx) - log.Global.Debug("Added UTXO ETX to ETX list") + + // 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} + etxs = append(etxs, &etxInner) } else { // This output creates a normal UTXO utxo := types.NewUtxoEntry(&txOut) @@ -679,13 +726,27 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu // 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) 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) + if conversion { + // If this transaction has at least one conversion, the tx gas is divided for all ETXs + txFeeInQuai := misc.QiToQuai(currentHeader, txFeeInQit) + gas := new(big.Int).Div(txFeeInQuai, currentHeader.BaseFee()) + gas.Div(gas, big.NewInt(int64(len(etxs)))) + if gas.Uint64() > (currentHeader.GasLimit() / params.MinimumEtxGasDivisor) { + // Limit ETX gas to max ETX gas limit (the rest is burned) + gas = new(big.Int).SetUint64(currentHeader.GasLimit() / params.MinimumEtxGasDivisor) + } + for i, _ := range etxs { + etxs[i].Gas = gas.Uint64() // Give each etx a proportion of gas + } + txFeeInQit.Sub(txFeeInQit, txFeeInQit) // Fee goes entirely to gas to pay for conversion + } *etxRLimit -= ETXRCount *etxPLimit -= ETXPCount @@ -791,17 +852,26 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject, newIn // 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, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, 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, error) { nodeCtx := config.Location.Context() msg, err := tx.AsMessage(types.MakeSigner(config, header.Number(nodeCtx)), header.BaseFee()) if err != nil { return nil, err } + 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))) + 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().Uint64()) + } + msg.SetValue(misc.QiToQuai(parent, types.Denominations[uint8(tx.Value().Uint64())])) // change to prime terminus + msg.SetData([]byte{}) // data is not used in conversion + } // 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) if tx.Type() == types.ExternalTxType { - prevZeroBal := prepareApplyETX(statedb, tx, config.Location) + prevZeroBal := prepareApplyETX(statedb, msg.Value(), config.Location) receipt, err := applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, 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, err @@ -1094,7 +1164,7 @@ func (p *StateProcessor) StateAtTransaction(block *types.WorkObject, txIndex int // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, p.hc.Config(), vm.Config{}) statedb.Prepare(tx.Hash(), idx) - if _, err := ApplyMessage(vmenv, msg, new(GasPool).AddGas(tx.Gas())); err != nil { + if _, err := ApplyMessage(vmenv, msg, new(types.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state @@ -1118,12 +1188,9 @@ func (p *StateProcessor) Stop() { p.logger.Info("State Processor stopped") } -func prepareApplyETX(statedb *state.StateDB, tx *types.Transaction, nodeLocation common.Location) *big.Int { +func prepareApplyETX(statedb *state.StateDB, value *big.Int, nodeLocation common.Location) *big.Int { prevZeroBal := statedb.GetBalance(common.ZeroInternal(nodeLocation)) // Get current zero address balance - fee := big.NewInt(0).Add(tx.GasFeeCap(), tx.GasTipCap()) // Add gas price cap to miner tip cap - fee.Mul(fee, big.NewInt(int64(tx.Gas()))) // Multiply gas price by gas limit (may need to check for int64 overflow) - total := big.NewInt(0).Add(fee, tx.Value()) // Add gas fee to value - statedb.SetBalance(common.ZeroInternal(nodeLocation), total) // Use zero address at temp placeholder and set it to gas fee plus value + statedb.SetBalance(common.ZeroInternal(nodeLocation), value) // Use zero address at temp placeholder and set it to value return prevZeroBal } diff --git a/core/state_transition.go b/core/state_transition.go index c4612e9687..2e38c57d13 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -52,7 +52,7 @@ The state transitioning model does all the necessary work to work out a valid ne 6) Derive new state root */ type StateTransition struct { - gp *GasPool + gp *types.GasPool msg Message gas uint64 gasPrice *big.Int @@ -83,6 +83,7 @@ type Message interface { ETXSender() common.Address Type() byte Hash() common.Hash + Lock() *big.Int } // ExecutionResult includes all output after executing given evm @@ -161,7 +162,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { +func NewStateTransition(evm *vm.EVM, msg Message, gp *types.GasPool) *StateTransition { return &StateTransition{ gp: gp, evm: evm, @@ -182,7 +183,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) { +func ApplyMessage(evm *vm.EVM, msg Message, gp *types.GasPool) (*ExecutionResult, error) { return NewStateTransition(evm, msg, gp).TransitionDb() } @@ -372,7 +373,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) + ret, st.gas, 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 @@ -391,8 +392,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if err != nil { return nil, err } - if coinbase.IsInQuaiLedgerScope() { - st.state.AddBalance(coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) // todo: etxs no longer pay the miner a fee + if coinbase.IsInQuaiLedgerScope() && !st.msg.IsETX() { + st.state.AddBalance(coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) } return &ExecutionResult{ diff --git a/core/tx_pool.go b/core/tx_pool.go index 5fc8eb5fb8..5c2ed3fdf4 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1053,11 +1053,6 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { } continue } - if tx.To() != nil && tx.To().IsInQiLedgerScope() { - errs[i] = common.MakeErrQiAddress(tx.To().Hex()) - invalidTxMeter.Add(1) - continue - } // Exclude transactions with invalid signatures as soon as // possible and cache senders in transactions before // obtaining lock @@ -1128,7 +1123,7 @@ func (pool *TxPool) addQiTx(tx *types.Transaction, grabLock bool) error { location := pool.chainconfig.Location currentBlock := pool.chain.CurrentBlock() - gp := GasPool(currentBlock.GasLimit()) + gp := types.GasPool(currentBlock.GasLimit()) etxRLimit := len(currentBlock.Transactions()) / params.ETXRegionMaxFraction if etxRLimit < params.ETXRLimitMin { etxRLimit = params.ETXRLimitMin @@ -1142,11 +1137,13 @@ func (pool *TxPool) addQiTx(tx *types.Transaction, grabLock bool) error { } fee, _, err := ProcessQiTx(tx, pool.chain, false, pool.chain.CurrentBlock(), pool.currentState, &gp, new(uint64), pool.signer, location, *pool.chainconfig.ChainID, &etxRLimit, &etxPLimit) if err != nil { - pool.mu.RUnlock() + if grabLock { + pool.mu.RUnlock() + } pool.logger.WithFields(logrus.Fields{ "tx": tx.Hash().String(), "err": err, - }).Error("Invalid qi tx") + }).Error("Invalid Qi transaction") return err } if grabLock { diff --git a/core/gaspool.go b/core/types/gaspool.go similarity index 96% rename from core/gaspool.go rename to core/types/gaspool.go index e3795c1ee9..3ab99b7323 100644 --- a/core/gaspool.go +++ b/core/types/gaspool.go @@ -14,9 +14,10 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package core +package types import ( + "errors" "fmt" "math" ) @@ -38,7 +39,7 @@ func (gp *GasPool) AddGas(amount uint64) *GasPool { // available and returns an error otherwise. func (gp *GasPool) SubGas(amount uint64) error { if uint64(*gp) < amount { - return ErrGasLimitReached + return errors.New("gas limit reached") } *(*uint64)(gp) -= amount return nil diff --git a/core/types/proto_block.pb.go b/core/types/proto_block.pb.go index 94133d4326..7e67804a6d 100644 --- a/core/types/proto_block.pb.go +++ b/core/types/proto_block.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.2 // source: core/types/proto_block.proto package types @@ -1819,6 +1819,7 @@ type ProtoTxOut struct { Denomination *uint32 `protobuf:"varint,1,opt,name=denomination,proto3,oneof" json:"denomination,omitempty"` Address []byte `protobuf:"bytes,2,opt,name=address,proto3,oneof" json:"address,omitempty"` + Lock []byte `protobuf:"bytes,3,opt,name=lock,proto3,oneof" json:"lock,omitempty"` } func (x *ProtoTxOut) Reset() { @@ -1867,6 +1868,13 @@ func (x *ProtoTxOut) GetAddress() []byte { return nil } +func (x *ProtoTxOut) GetLock() []byte { + if x != nil { + return x.Lock + } + return nil +} + var File_core_types_proto_block_proto protoreflect.FileDescriptor var file_core_types_proto_block_proto_rawDesc = []byte{ @@ -2258,18 +2266,20 @@ var file_core_types_proto_block_proto_rawDesc = []byte{ 0x48, 0x00, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, - 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x71, 0x0a, 0x0a, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, - 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, - 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x42, - 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x33, 0x5a, 0x31, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6d, 0x69, 0x6e, - 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x2f, 0x67, - 0x6f, 0x2d, 0x71, 0x75, 0x61, 0x69, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x93, 0x01, 0x0a, 0x0a, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x6f, + 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, + 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, + 0x12, 0x17, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x02, + 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, + 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x42, + 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, + 0x6d, 0x69, 0x6e, 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x69, 0x65, + 0x73, 0x2f, 0x67, 0x6f, 0x2d, 0x71, 0x75, 0x61, 0x69, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/core/types/proto_block.proto b/core/types/proto_block.proto index 0c29643573..430b72f2e5 100644 --- a/core/types/proto_block.proto +++ b/core/types/proto_block.proto @@ -167,4 +167,5 @@ message ProtoOutPoint { message ProtoTxOut { optional uint32 denomination = 1; optional bytes address = 2; + optional bytes lock = 3; } diff --git a/core/types/qi_tx.go b/core/types/qi_tx.go index 2136c33654..4370b837cb 100644 --- a/core/types/qi_tx.go +++ b/core/types/qi_tx.go @@ -121,6 +121,7 @@ func CalculateQiTxGas(transaction *Transaction) uint64 { if transaction.Type() != QiTxType { panic("CalculateQiTxGas called on a transaction that is not a Qi transaction") } + // TODO: This should check for ETXs (and conversion) and calculate expected gas for those as well return uint64(len(transaction.TxIn()))*params.SloadGas + uint64(len(transaction.TxOut()))*params.CallValueTransferGas + params.EcrecoverGas } diff --git a/core/types/transaction.go b/core/types/transaction.go index a6515ba5a2..2d1dc74ecc 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -29,6 +29,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" "github.com/dominant-strategies/go-quai/log" + "github.com/dominant-strategies/go-quai/params" "google.golang.org/protobuf/proto" "github.com/dominant-strategies/go-quai/crypto" @@ -546,7 +547,7 @@ func (tx *Transaction) To() *common.Address { return &cpy } -func (tx *Transaction) SetTo(addr common.Address) { +func (tx *Transaction) SetGas(addr common.Address) { tx.inner.setTo(addr) } @@ -709,6 +710,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) @@ -1018,6 +1023,7 @@ type Message struct { etxsender common.Address // only used in ETX txtype byte hash common.Hash + lock *big.Int } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isETX bool) Message { @@ -1082,6 +1088,7 @@ func (tx *Transaction) AsMessageWithSender(s Signer, baseFee *big.Int, sender *c isETX: false, txtype: tx.Type(), hash: tx.Hash(), + lock: new(big.Int), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -1117,6 +1124,19 @@ func (m Message) IsETX() bool { return m.isETX } func (m Message) ETXSender() common.Address { return m.etxsender } func (m Message) Type() byte { return m.txtype } func (m Message) Hash() common.Hash { return m.hash } +func (m Message) Lock() *big.Int { return m.lock } + +func (m *Message) SetValue(v *big.Int) { + m.amount = v +} + +func (m *Message) SetLock(lock *big.Int) { + m.lock = lock +} + +func (m *Message) SetData(data []byte) { + m.data = data +} // AccessList is an access list. type AccessList []AccessTuple diff --git a/core/types/utxo.go b/core/types/utxo.go index 3a03d0b5ed..befd810c63 100644 --- a/core/types/utxo.go +++ b/core/types/utxo.go @@ -165,6 +165,7 @@ func (txOuts *TxOuts) ProtoDecode(protoTxOuts *ProtoTxOuts) error { type TxOut struct { Denomination uint8 Address []byte + Lock *big.Int // Block height the entry unlocks. 0 or nil = unlocked } func (txOut TxOut) ProtoEncode() (*ProtoTxOut, error) { @@ -173,6 +174,11 @@ func (txOut TxOut) ProtoEncode() (*ProtoTxOut, error) { denomination := uint32(txOut.Denomination) protoTxOut.Denomination = &denomination protoTxOut.Address = txOut.Address + if txOut.Lock == nil { + protoTxOut.Lock = big.NewInt(0).Bytes() + } else { + protoTxOut.Lock = txOut.Lock.Bytes() + } return protoTxOut, nil } @@ -183,14 +189,48 @@ func (txOut *TxOut) ProtoDecode(protoTxOut *ProtoTxOut) error { } txOut.Denomination = uint8(protoTxOut.GetDenomination()) txOut.Address = protoTxOut.Address + txOut.Lock = new(big.Int).SetBytes(protoTxOut.Lock) return nil } // NewTxOut returns a new Qi transaction output with the provided // transaction value and address. -func NewTxOut(denomination uint8, address []byte) *TxOut { +func NewTxOut(denomination uint8, address []byte, lock *big.Int) *TxOut { return &TxOut{ Denomination: denomination, Address: address, + Lock: lock, + } +} + +// UtxoEntry houses details about an individual transaction output in a utxo +// view such as whether or not it was contained in a coinbase tx, the height of +// the block that contains the tx, whether or not it is spent, its public key +// script, and how much it pays. +type UtxoEntry struct { + Denomination uint8 + Address []byte // The address of the output holder. + Lock *big.Int // Block height the entry unlocks. 0 = unlocked +} + +// Clone returns a shallow copy of the utxo entry. +func (entry *UtxoEntry) Clone() *UtxoEntry { + if entry == nil { + return nil + } + + return &UtxoEntry{ + Denomination: entry.Denomination, + Address: entry.Address, + Lock: new(big.Int).Set(entry.Lock), + } +} + +// NewUtxoEntry returns a new UtxoEntry built from the arguments. +func NewUtxoEntry(txOut *TxOut) *UtxoEntry { + return &UtxoEntry{ + Denomination: txOut.Denomination, + Address: txOut.Address, + Lock: txOut.Lock, } } diff --git a/core/types/utxo_viewpoint.go b/core/types/utxo_viewpoint.go deleted file mode 100644 index 5e9a79fe18..0000000000 --- a/core/types/utxo_viewpoint.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -// UtxoEntry houses details about an individual transaction output in a utxo -// view such as whether or not it was contained in a coinbase tx, the height of -// the block that contains the tx, whether or not it is spent, its public key -// script, and how much it pays. -type UtxoEntry struct { - Denomination uint8 - Address []byte // The address of the output holder. -} - -// Clone returns a shallow copy of the utxo entry. -func (entry *UtxoEntry) Clone() *UtxoEntry { - if entry == nil { - return nil - } - - return &UtxoEntry{ - Denomination: entry.Denomination, - Address: entry.Address, - } -} - -// NewUtxoEntry returns a new UtxoEntry built from the arguments. -func NewUtxoEntry(txOut *TxOut) *UtxoEntry { - return &UtxoEntry{ - Denomination: txOut.Denomination, - Address: txOut.Address, - } -} diff --git a/core/vm/contracts.go b/core/vm/contracts.go index bdc09d1b65..a685bc8192 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -25,6 +25,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" + "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" "github.com/dominant-strategies/go-quai/crypto/blake2b" "github.com/dominant-strategies/go-quai/crypto/bn256" @@ -43,8 +44,9 @@ type PrecompiledContract interface { } var ( - PrecompiledContracts map[common.AddressBytes]PrecompiledContract = make(map[common.AddressBytes]PrecompiledContract) - PrecompiledAddresses map[string][]common.Address = make(map[string][]common.Address) + PrecompiledContracts map[common.AddressBytes]PrecompiledContract = make(map[common.AddressBytes]PrecompiledContract) + PrecompiledAddresses map[string][]common.Address = make(map[string][]common.Address) + LockupContractAddresses map[[2]byte]common.Address = make(map[[2]byte]common.Address) // LockupContractAddress is not of type PrecompiledContract ) func InitializePrecompiles(nodeLocation common.Location) { @@ -57,6 +59,7 @@ func InitializePrecompiles(nodeLocation common.Location) { PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%x00000000000000000000000000000000000007", nodeLocation.BytePrefix()))] = &bn256ScalarMul{} PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%x00000000000000000000000000000000000008", nodeLocation.BytePrefix()))] = &bn256Pairing{} PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%x00000000000000000000000000000000000009", nodeLocation.BytePrefix()))] = &blake2F{} + LockupContractAddresses[[2]byte{nodeLocation[0], nodeLocation[1]}] = common.HexToAddress(fmt.Sprintf("0x%x0000000000000000000000000000000000000A", nodeLocation.BytePrefix()), nodeLocation) } // ActivePrecompiles returns the precompiles enabled with the current configuration. @@ -486,3 +489,171 @@ func intToByteArray20(n uint8) [20]byte { byteArray[19] = byte(n) // Use the last byte for the integer return byteArray } + +func RequiredGas(input []byte) uint64 { + return 0 +} + +// 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) { + internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress() + if err != nil { + return 0, err + } + // 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 { + // This contract does not revert. If the caller runs out of gas, we just stop + return gasUsed, ErrOutOfGas + } + if (currentLockHash == common.Hash{}) { + return gasUsed, 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") + } + + 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 + if gas.Gas() < requiredGas { + return gasUsed, 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") + } + lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key)) + gasUsed += params.ColdSloadCost + if gas.SubGas(params.ColdSloadCost) != nil { + // This contract does not revert. If the caller runs out of gas, we just stop + return gasUsed, ErrOutOfGas + } + if (lockHash == common.Hash{}) { + // Lock doesn't exist, so we're done + return gasUsed, 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) + } + // Set the lock to zero + statedb.SetState(internalContractAddress, common.BytesToHash(key), common.Hash{}) + gasUsed += params.SstoreResetGas + if gas.SubGas(params.SstoreResetGas) != nil { + // This contract does not revert. If the caller runs out of gas, we just stop + return gasUsed, 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 + } + 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") + } + // 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 + } + // 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 + } + + // Redeem the balance for the sender + balance := new(big.Int).SetBytes(balanceHash[:]) + internal, err := sender.InternalAndQuaiAddress() + if err != nil { + return gasUsed, err + } + statedb.AddBalance(internal, balance) + gasUsed += params.CallValueTransferGas + if gas.SubGas(params.CallValueTransferGas) != nil { + return gasUsed, ErrOutOfGas + } + } + + return gasUsed, 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) { + internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress() + if err != nil { + return 0, err + } + if len(lock.Bytes()) > common.HashLength || len(balance.Bytes()) > common.HashLength { + return 0, errors.New("lock or balance is too large") + } + currentLockHash := statedb.GetState(internalContractAddress, common.BytesToHash(toAddr.Bytes())) + gasUsed := params.ColdSloadCost + if gas.SubGas(params.ColdSloadCost) != nil { + // This contract does not revert. If the caller runs out of gas, we just stop + return gasUsed, ErrOutOfGas + } + if (currentLockHash == common.Hash{}) { + // No lock found, create a new one + statedb.SetState(internalContractAddress, common.BytesToHash(toAddr.Bytes()), common.BytesToHash([]byte{1})) + 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") + } + + 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 + if gas.Gas() < requiredGas { + return gasUsed, 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") + } + lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key)) + gasUsed += params.ColdSloadCost + if gas.SubGas(params.ColdSloadCost) != nil { + // This contract does not revert. If the caller runs out of gas, we just stop + return gasUsed, 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 + } + 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 + } + // Addition of new lock successful + return gasUsed, 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") +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 106b6e02e7..990acb82b4 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -28,6 +28,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/params" "github.com/holiman/uint256" ) @@ -184,7 +185,7 @@ func (evm *EVM) Interpreter() *EVMInterpreter { // parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. -func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, lock *big.Int) (ret []byte, leftOverGas uint64, err error) { if evm.Config.NoRecursion && evm.depth > 0 { return nil, gas, nil } @@ -196,6 +197,33 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } + lockupContractAddress := LockupContractAddresses[[2]byte{evm.chainConfig.Location[0], evm.chainConfig.Location[1]}] + if addr.Equal(lockupContractAddress) { + gasUsed, err := RedeemQuai(evm.StateDB, caller.Address(), new(types.GasPool).AddGas(gas), evm.Context.BlockNumber, lockupContractAddress) + if gas > gasUsed { + gas = gas - gasUsed + } else { + gas = 0 + } + if err != nil { + log.Global.Error("RedeemQuai failed", "err", err) + } + return []byte{}, gas, err + } else if lock != nil && lock.Sign() != 0 { + if err := evm.Context.Transfer(evm.StateDB, caller.Address(), lockupContractAddress, value); err != nil { + return nil, gas, err + } + gasUsed, err := AddNewLock(evm.StateDB, addr, new(types.GasPool).AddGas(gas), lock, evm.Context.BlockNumber, lockupContractAddress) + if gas > gasUsed { + gas = gas - gasUsed + } else { + gas = 0 + } + if err != nil { + log.Global.Error("AddNewLock failed", "err", err) + } + return []byte{}, gas, err + } snapshot := evm.StateDB.Snapshot() p, isPrecompile, addr := evm.precompile(addr) internalAddr, err := addr.InternalAndQuaiAddress() @@ -594,11 +622,13 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas uint64, value *big.Int, data []byte) (ret []byte, leftOverGas uint64, err error) { // Verify address is not in context - if common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) { + if toAddr.IsInQuaiLedgerScope() && common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) { return []byte{}, 0, fmt.Errorf("%x is in chain scope, but CreateETX was called", toAddr) } - if !toAddr.IsInQuaiLedgerScope() { - return []byte{}, 0, fmt.Errorf("%x is not in quai scope, but CreateETX was called", toAddr) + 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) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 98ea972d62..4e6ecd558a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -684,7 +684,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) + ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal, nil) if err != nil { temp.Clear() diff --git a/core/worker.go b/core/worker.go index d71ac1e32e..09977448ab 100644 --- a/core/worker.go +++ b/core/worker.go @@ -66,7 +66,7 @@ type environment struct { ancestors mapset.Set // ancestor set (used for checking uncle parent validity) family mapset.Set // family set (used for checking uncle invalidity) tcount int // tx count in cycle - gasPool *GasPool // available gas used to pack transactions + gasPool *types.GasPool // available gas used to pack transactions coinbase common.Address etxRLimit int // Remaining number of cross-region ETXs that can be included etxPLimit int // Remaining number of cross-prime ETXs that can be included @@ -736,17 +736,36 @@ func (w *worker) commitUncle(env *environment, uncle *types.WorkObjectHeader) er func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, tx *types.Transaction) ([]*types.Log, error) { if tx != nil { if tx.Type() == types.ExternalTxType && tx.To().IsInQiLedgerScope() { - if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { - return nil, err - } - if tx.Value().Int64() > types.MaxDenomination { - return nil, fmt.Errorf("tx %032x emits UTXO with value greater than max denomination", tx.Hash()) - } - if err := env.state.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Int64()), tx.To().Bytes()))); err != nil { - return nil, err + gasUsed := env.wo.GasUsed() + txGas := tx.Gas() + if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Quai->Qi conversion + lock := new(big.Int).Add(env.wo.Number(w.hc.NodeCtx()), big.NewInt(params.ConversionLockPeriod)) + value := misc.QuaiToQi(env.wo, tx.Value()) + denominations := misc.FindMinDenominations(value) + for i, denomination := range denominations { // TODO: Decide maximum number of iterations + if txGas < params.CallValueTransferGas { + // No more gas, the rest of the denominations are lost but the tx is still valid + break + } + txGas -= params.CallValueTransferGas + if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { + return nil, err + } + // the ETX hash is guaranteed to be unique + if err := env.state.CreateUTXO(tx.Hash(), uint16(i), types.NewUtxoEntry(types.NewTxOut(denomination, tx.To().Bytes(), lock))); err != nil { + return nil, err + } + gasUsed += params.CallValueTransferGas + } + } else { + if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { + return nil, err + } + if err := env.state.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Uint64()), tx.To().Bytes(), big.NewInt(0)))); err != nil { + return nil, err + } + gasUsed += params.CallValueTransferGas } - gasUsed := env.wo.Header().GasUsed() - gasUsed += params.CallValueTransferGas env.wo.Header().SetGasUsed(gasUsed) env.txs = append(env.txs, tx) return []*types.Log{}, nil // need to make sure this does not modify receipt hash @@ -782,7 +801,7 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, etxs []*types.Transaction, txs *types.TransactionsByPriceAndNonce, etxSet *types.EtxSet, interrupt *int32) bool { gasLimit := env.wo.GasLimit if env.gasPool == nil { - env.gasPool = new(GasPool).AddGas(gasLimit()) + env.gasPool = new(types.GasPool).AddGas(gasLimit()) } var coalescedLogs []*types.Log minEtxGas := gasLimit() / params.MinimumEtxGasDivisor @@ -1366,7 +1385,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { txGas := types.CalculateQiTxGas(tx) - gasUsed := env.wo.Header().GasUsed() + gasUsed := env.wo.GasUsed() gasUsed += txGas addresses := make(map[common.AddressBytes]struct{}) totalQitIn := big.NewInt(0) @@ -1376,6 +1395,9 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { 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.wo.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 @@ -1396,9 +1418,10 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { } var ETXRCount int var ETXPCount int - etxs := make([]*types.Transaction, 0) + etxs := make([]*types.ExternalTx, 0) totalQitOut := big.NewInt(0) utxosCreate := make(map[types.OutPoint]*types.UtxoEntry) + conversion := false for txOutIdx, txOut := range tx.TxOut() { if txOut.Denomination > types.MaxDenomination { @@ -1410,22 +1433,18 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { } totalQitOut.Add(totalQitOut, types.Denominations[txOut.Denomination]) toAddr := common.BytesToAddress(txOut.Address, location) - if !toAddr.IsInQiLedgerScope() { - w.logger.Error(fmt.Errorf("tx %032x emits UTXO with To address not in the Qi ledger scope", tx.Hash())) - return fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()) - } if _, exists := addresses[toAddr.Bytes20()]; exists { return errors.New("Duplicate address in QiTx outputs: " + toAddr.String()) } addresses[toAddr.Bytes20()] = struct{}{} - if !toAddr.Location().Equal(location) { // This output creates an ETX + if !toAddr.Location().Equal(location) || (toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope()) { // This output creates an ETX // Cross-region? if toAddr.Location().CommonDom(location).Context() == common.REGION_CTX { 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 { @@ -1436,15 +1455,24 @@ 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 + conversion = true + 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) + } + } else if !toAddr.IsInQiLedgerScope() { + w.logger.Error(fmt.Errorf("tx %032x emits UTXO with To address not in the Qi ledger scope", tx.Hash())) + return 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} - etx := types.NewTx(&etxInner) primeTerminus := w.hc.GetPrimeTerminus(env.wo) if !w.hc.CheckIfEtxIsEligible(primeTerminus.EtxEligibleSlices(), *toAddr.Location()) { return fmt.Errorf("etx emitted by tx [%v] going to a slice that is not eligible to receive etx %v", tx.Hash().Hex(), *toAddr.Location()) } - etxs = append(etxs, etx) + etxs = append(etxs, &etxInner) w.logger.Debug("Added UTXO ETX to block") } else { // This output creates a normal UTXO @@ -1464,19 +1492,35 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment) error { return err } // Check tx against required base fee and gas - baseFeeInQi := misc.QuaiToQi(env.wo, env.wo.Header().BaseFee()) - minimumFee := new(big.Int).Mul(baseFeeInQi, big.NewInt(int64(txGas))) + minimumFeeInQuai := new(big.Int).Mul(big.NewInt(int64(txGas)), env.wo.BaseFee()) + minimumFee := misc.QuaiToQi(env.wo, minimumFeeInQuai) if txFeeInQit.Cmp(minimumFee) < 0 { - return fmt.Errorf("tx %032x has insufficient fee for base fee of %d and gas of %d", tx.Hash(), baseFeeInQi.Uint64(), txGas) + return fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQit.Uint64(), minimumFee.Uint64()) } - log.Global.Infof("Minimum fee: %d", minimumFee.Int64()) // Miner gets remainder of fee after base fee txFeeInQit.Sub(txFeeInQit, minimumFee) + if conversion { + // If this transaction has at least one conversion, the tx gas is divided for all ETXs + txFeeInQuai := misc.QiToQuai(env.wo, txFeeInQit) + gas := new(big.Int).Div(txFeeInQuai, env.wo.BaseFee()) + gas.Div(gas, big.NewInt(int64(len(etxs)))) + if gas.Uint64() > (env.wo.GasLimit() / params.MinimumEtxGasDivisor) { + // Limit ETX gas to max ETX gas limit (the rest is burned) + gas = new(big.Int).SetUint64(env.wo.GasLimit() / params.MinimumEtxGasDivisor) + } + for i, _ := range etxs { + etxs[i].Gas = gas.Uint64() // Give each etx a proportion of gas + } + txFeeInQit.Sub(txFeeInQit, txFeeInQit) // Fee goes entirely to gas to pay for conversion + } + env.wo.Header().SetGasUsed(gasUsed) env.etxRLimit -= ETXRCount env.etxPLimit -= ETXPCount - env.etxs = append(env.etxs, etxs...) + for _, etx := range etxs { + env.etxs = append(env.etxs, types.NewTx(etx)) + } env.txs = append(env.txs, tx) env.utxoFees.Add(env.utxoFees, txFeeInQit) for _, utxo := range utxosDelete { diff --git a/internal/quaiapi/api.go b/internal/quaiapi/api.go index 5ad7397a7a..5448ab8623 100644 --- a/internal/quaiapi/api.go +++ b/internal/quaiapi/api.go @@ -634,7 +634,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash }() // Execute the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) + gp := new(types.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, err @@ -1256,7 +1256,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if err != nil { return nil, 0, nil, err } - res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + res, err := core.ApplyMessage(vmenv, msg, new(types.GasPool).AddGas(msg.Gas())) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", msg, err) } @@ -1528,11 +1528,6 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, input if err != nil { return common.Hash{}, err } - if tx.Type() != types.QiTxType { - if tx.To() != nil && tx.To().IsInQiLedgerScope() { // change after adding Quai->Qi conversion tx type - return common.Hash{}, common.MakeErrQiAddress(tx.To().Hex()) - } - } return SubmitTransaction(ctx, s.b, tx) } diff --git a/p2p/pb/quai_messages.pb.go b/p2p/pb/quai_messages.pb.go index 772ab6162c..df7cf8790d 100644 --- a/p2p/pb/quai_messages.pb.go +++ b/p2p/pb/quai_messages.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.2 // source: p2p/pb/quai_messages.proto package pb diff --git a/params/protocol_params.go b/params/protocol_params.go index cd85264ced..dffdc4ade2 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -18,6 +18,8 @@ package params import ( "math/big" + + "github.com/dominant-strategies/go-quai/common" ) const ( @@ -169,6 +171,10 @@ const ( // should be chosen to give node operators some time to adjust their // infrastructure, if needed, to account for the upcoming network change. TREE_EXPANSION_WAIT_COUNT = 1024 + + ConversionLockPeriod int64 = 10 // The number of zone blocks that a conversion output is locked for + MinQiConversionDenomination = 1 + ConversionConfirmationContext = common.PRIME_CTX // A conversion requires a single coincident Dom confirmation ) var ( @@ -194,4 +200,5 @@ var ( 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(GWei)) // 0.000000001 Quai ) diff --git a/trie/proto_trienode.pb.go b/trie/proto_trienode.pb.go index aa42437cdd..c306fde3df 100644 --- a/trie/proto_trienode.pb.go +++ b/trie/proto_trienode.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.2 // source: trie/proto_trienode.proto package trie