diff --git a/core/evm.go b/core/evm.go index bcc44353ed..fd95438f80 100644 --- a/core/evm.go +++ b/core/evm.go @@ -79,11 +79,11 @@ func NewEVMBlockContext(header *types.WorkObject, chain ChainContext, author *co } } - // Prime terminus determines which location is eligible to except the etx + // Prime terminus determines which location is eligible to accept the etx primeTerminus := header.PrimeTerminus() primeTerminusHeader := chain.GetHeaderByHash(primeTerminus) if primeTerminusHeader == nil { - log.Global.Error("Prime terminus header not found", "headerHash", header.Hash(), "primeTerminus", primeTerminus) + log.Global.Error("Prime terminus header not found ", " headerHash ", header.Hash(), " primeTerminus ", primeTerminus) return vm.BlockContext{}, ErrSubNotSyncedToDom } etxEligibleSlices := primeTerminusHeader.EtxEligibleSlices() diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 501d427e51..f11ce613a9 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -965,7 +965,7 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para }).Error("Missing body but have receipt") return nil } - if err := receipts.DeriveFields(config, hash, number, body.QuaiTransactionsWithoutCoinbase()); err != nil { + if err := receipts.DeriveFields(config, hash, number, body.TransactionsWithReceipts()); err != nil { db.Logger().WithFields(log.Fields{ "hash": hash, "number": number, diff --git a/core/state_processor.go b/core/state_processor.go index 1b0acd828e..98e5c35928 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -336,6 +336,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty timePrepare += timePrepareDelta var receipt *types.Receipt + var addReceipt bool if tx.Type() == types.ExternalTxType { startTimeEtx := time.Now() // ETXs MUST be included in order, so popping the first from the queue must equal the first in the block @@ -418,6 +419,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty if err != nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } + addReceipt = true totalEtxGas += receipt.GasUsed timeEtxDelta := time.Since(startTimeEtx) timeEtx += timeEtxDelta @@ -429,6 +431,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty if err != nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } + addReceipt = true timeTxDelta := time.Since(startTimeTx) timeTx += timeTxDelta } else { @@ -439,8 +442,10 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty emittedEtxs = append(emittedEtxs, etx) } } - receipts = append(receipts, receipt) - allLogs = append(allLogs, receipt.Logs...) + if addReceipt { + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + } i++ } diff --git a/core/types/wo.go b/core/types/wo.go index 6608534d27..3cd19b3ea4 100644 --- a/core/types/wo.go +++ b/core/types/wo.go @@ -340,6 +340,22 @@ func (wo *WorkObject) QiTransactionsWithoutCoinbase() []*Transaction { return qiTxs } +func (wo *WorkObject) TransactionsWithReceipts() []*Transaction { + txs := make([]*Transaction, 0) + for i, t := range wo.Transactions() { + if i == 0 && IsCoinBaseTx(t, wo.woHeader.parentHash, wo.woHeader.location) { + // ignore the coinbase tx + continue + } + if t.Type() == QuaiTxType || (t.Type() == ExternalTxType && t.To().IsInQuaiLedgerScope()) { + txs = append(txs, t) + } + } + return txs +} + +// QuaiTransactionsWithoutCoinbase returns all Quai EVM transactions in the block, excluding the coinbase transaction. +// This also returns all transactions that have an associated receipt. func (wo *WorkObject) QuaiTransactionsWithoutCoinbase() []*Transaction { quaiTxs := make([]*Transaction, 0) for i, t := range wo.Transactions() { @@ -347,7 +363,7 @@ func (wo *WorkObject) QuaiTransactionsWithoutCoinbase() []*Transaction { // ignore the Quai coinbase tx and Quai->Qi to comply with prior functionality as it is not a normal transaction continue } - if t.Type() != QiTxType { + if t.Type() == QuaiTxType { quaiTxs = append(quaiTxs, t) } } @@ -370,16 +386,6 @@ func (wo *WorkObject) InputsAndOutputsWithoutCoinbase() (uint, uint) { return uint(inputs), uint(outputs) } -func (wo *WorkObject) QuaiTransactionsWithFees() []*Transaction { - quaiTxs := make([]*Transaction, 0) - for _, t := range wo.Transactions() { - if t.Type() == QuaiTxType { // QuaiTxType is the only type that gives Quai fees to the miner - quaiTxs = append(quaiTxs, t) - } - } - return quaiTxs -} - func (wo *WorkObject) NumberArray() []*big.Int { numArray := make([]*big.Int, common.HierarchyDepth) for i := 0; i < common.HierarchyDepth; i++ { @@ -579,7 +585,7 @@ func (wb *WorkObjectBody) QiTransactions() []*Transaction { func (wb *WorkObjectBody) QuaiTransactions() []*Transaction { quaiTxs := make([]*Transaction, 0) for _, t := range wb.Transactions() { - if t.Type() != QiTxType { + if t.Type() == QuaiTxType { quaiTxs = append(quaiTxs, t) } } diff --git a/core/worker.go b/core/worker.go index 93bb340744..46525f7bfd 100644 --- a/core/worker.go +++ b/core/worker.go @@ -732,84 +732,84 @@ func (w *worker) commitUncle(env *environment, uncle *types.WorkObjectHeader) er return nil } -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() { - 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)) - primeTerminus := w.hc.GetPrimeTerminus(env.wo) - if primeTerminus == nil { - return nil, errors.New("prime terminus not found") +func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, tx *types.Transaction) ([]*types.Log, bool, error) { + if tx == nil { + return nil, false, errors.New("nil transaction") + } + if tx.Type() == types.ExternalTxType && tx.To().IsInQiLedgerScope() { + 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)) + primeTerminus := w.hc.GetPrimeTerminus(env.wo) + if primeTerminus == nil { + return nil, false, errors.New("prime terminus not found") + } + value := misc.QuaiToQi(primeTerminus, tx.Value()) + denominations := misc.FindMinDenominations(value) + outputIndex := uint16(0) + + // Iterate over the denominations in descending order + for denomination := types.MaxDenomination; denomination >= 0; denomination-- { + // If the denomination count is zero, skip it + if denominations[uint8(denomination)] == 0 { + continue } - value := misc.QuaiToQi(primeTerminus, tx.Value()) - denominations := misc.FindMinDenominations(value) - outputIndex := uint16(0) - - // Iterate over the denominations in descending order - for denomination := types.MaxDenomination; denomination >= 0; denomination-- { - // If the denomination count is zero, skip it - if denominations[uint8(denomination)] == 0 { - continue + for j := uint8(0); j < denominations[uint8(denomination)]; j++ { + if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { + // No more gas, the rest of the denominations are lost but the tx is still valid + break } - for j := uint8(0); j < denominations[uint8(denomination)]; j++ { - if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { - // 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 - } - gasUsed += params.CallValueTransferGas - // the ETX hash is guaranteed to be unique - if err := env.state.CreateUTXO(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lock))); err != nil { - return nil, err - } - outputIndex++ + txGas -= params.CallValueTransferGas + if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { + return nil, false, err } + gasUsed += params.CallValueTransferGas + // the ETX hash is guaranteed to be unique + if err := env.state.CreateUTXO(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lock))); err != nil { + return nil, false, err + } + outputIndex++ } - } else { - // This Qi ETX should cost more gas - 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 } - 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 - } - snap := env.state.Snapshot() - // retrieve the gas used int and pass in the reference to the ApplyTransaction - gasUsed := env.wo.GasUsed() - receipt, err := ApplyTransaction(w.chainConfig, parent, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) - if err != nil { - w.logger.WithFields(log.Fields{ - "err": err, - "tx": tx.Hash().Hex(), - "block": env.wo.Number(w.hc.NodeCtx()), - "gasUsed": gasUsed, - }).Debug("Error playing transaction in worker") - env.state.RevertToSnapshot(snap) - return nil, err - } - if receipt.Status == types.ReceiptStatusSuccessful { - env.etxs = append(env.etxs, receipt.Etxs...) + } else { + // This Qi ETX should cost more gas + if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { + return nil, false, 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, false, err + } + gasUsed += params.CallValueTransferGas } - // once the gasUsed pointer is updated in the ApplyTransaction it has to be set back to the env.Header.GasUsed - // This extra step is needed because previously the GasUsed was a public method and direct update of the value - // was possible. env.wo.Header().SetGasUsed(gasUsed) env.txs = append(env.txs, tx) - env.receipts = append(env.receipts, receipt) - return receipt.Logs, nil + return []*types.Log{}, false, nil } - return nil, errors.New("error finding transaction") + snap := env.state.Snapshot() + // retrieve the gas used int and pass in the reference to the ApplyTransaction + gasUsed := env.wo.GasUsed() + receipt, err := ApplyTransaction(w.chainConfig, parent, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) + if err != nil { + w.logger.WithFields(log.Fields{ + "err": err, + "tx": tx.Hash().Hex(), + "block": env.wo.Number(w.hc.NodeCtx()), + "gasUsed": gasUsed, + }).Debug("Error playing transaction in worker") + env.state.RevertToSnapshot(snap) + return nil, false, err + } + if receipt.Status == types.ReceiptStatusSuccessful { + env.etxs = append(env.etxs, receipt.Etxs...) + } + // once the gasUsed pointer is updated in the ApplyTransaction it has to be set back to the env.Header.GasUsed + // This extra step is needed because previously the GasUsed was a public method and direct update of the value + // was possible. + env.wo.Header().SetGasUsed(gasUsed) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return receipt.Logs, true, nil } func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, txs *types.TransactionsByPriceAndNonce, interrupt *int32) bool { @@ -853,10 +853,12 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, break } env.state.Prepare(etx.Hash(), env.tcount) - logs, err := w.commitTransaction(env, parent, etx) - if err == nil { + logs, receipt, err := w.commitTransaction(env, parent, etx) + if err == nil && receipt { coalescedLogs = append(coalescedLogs, logs...) env.tcount++ + } else if err == nil && !receipt { + env.tcount++ } oldestIndex.Add(oldestIndex, big.NewInt(1)) } @@ -926,7 +928,7 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, // Start executing the transaction env.state.Prepare(tx.Hash(), env.tcount) - logs, err := w.commitTransaction(env, parent, tx) + logs, receipt, err := w.commitTransaction(env, parent, tx) switch { case errors.Is(err, types.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account @@ -956,7 +958,9 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account - coalescedLogs = append(coalescedLogs, logs...) + if receipt { + coalescedLogs = append(coalescedLogs, logs...) + } env.tcount++ txs.PopNoSort() @@ -1433,7 +1437,7 @@ func copyReceipts(receipts []*types.Receipt) []*types.Receipt { // totalFees computes total consumed miner fees in ETH. Block transactions and receipts have to have the same order. func totalFees(block *types.WorkObject, receipts []*types.Receipt) *big.Float { feesWei := new(big.Int) - for i, tx := range block.QuaiTransactionsWithoutCoinbase() { + for i, tx := range block.TransactionsWithReceipts() { minerFee, _ := tx.EffectiveGasTip(block.BaseFee()) feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee)) } diff --git a/quai/gasprice/feehistory.go b/quai/gasprice/feehistory.go index 84a3258071..d365335bbb 100644 --- a/quai/gasprice/feehistory.go +++ b/quai/gasprice/feehistory.go @@ -107,8 +107,8 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { return } - sorter := make(sortGasAndReward, len(bf.block.QuaiTransactionsWithoutCoinbase())) - for i, tx := range bf.block.QuaiTransactionsWithoutCoinbase() { + sorter := make(sortGasAndReward, len(bf.block.TransactionsWithReceipts())) + for i, tx := range bf.block.TransactionsWithReceipts() { reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} }