diff --git a/core/types/transaction.go b/core/types/transaction.go index 49127630ae45..c0da19062d66 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -476,6 +476,86 @@ func (t *TransactionsByPriceAndNonce) Pop() { heap.Pop(&t.heads) } +type TxByHash Transactions + +func (s TxByHash) Len() int { return len(s) } +func (s TxByHash) Less(i, j int) bool { + return s[i].Hash().Big().Cmp(s[j].Hash().Big()) > 0 +} +func (s TxByHash) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s *TxByHash) Push(x interface{}) { + *s = append(*s, x.(*Transaction)) +} + +func (s *TxByHash) Pop() interface{} { + old := *s + n := len(old) + x := old[n-1] + *s = old[0 : n-1] + return x +} + +type TxHeap interface { + Peek() *Transaction + Shift() + Pop() +} + +type TransactionsByHashAndNonce struct { + txs map[common.Address]Transactions // Per account nonce-sorted list of transactions + heads TxByHash // Next transaction for each unique account (price heap) + signer Signer // Signer for the set of transactions +} + +func NewTransactionsByHashAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByHashAndNonce { + // Initialize a price and received time based heap with the head transactions + heads := make(TxByHash, 0, len(txs)) + for from, accTxs := range txs { + // Ensure the sender address is from the signer + if acc, _ := Sender(signer, accTxs[0]); acc != from { + delete(txs, from) + continue + } + heads = append(heads, accTxs[0]) + txs[from] = accTxs[1:] + } + heap.Init(&heads) + + // Assemble and return the transaction set + return &TransactionsByHashAndNonce{ + txs: txs, + heads: heads, + signer: signer, + } +} + +// Peek returns the next transaction by price. +func (t *TransactionsByHashAndNonce) Peek() *Transaction { + if len(t.heads) == 0 { + return nil + } + return t.heads[0] +} + +// Shift replaces the current best head with the next one from the same account. +func (t *TransactionsByHashAndNonce) Shift() { + acc, _ := Sender(t.signer, t.heads[0]) + if txs, ok := t.txs[acc]; ok && len(txs) > 0 { + t.heads[0], t.txs[acc] = txs[0], txs[1:] + heap.Fix(&t.heads, 0) + } else { + heap.Pop(&t.heads) + } +} + +// Pop removes the best transaction, *not* replacing it with the next one from +// the same account. This should be used when a transaction cannot be executed +// and hence all subsequent ones should be discarded from the same account. +func (t *TransactionsByHashAndNonce) Pop() { + heap.Pop(&t.heads) +} + // Message is a fully derived transaction and implements core.Message // // NOTE: In a future PR this will be removed. diff --git a/miner/worker.go b/miner/worker.go index 2cee6af0c326..7ad8e0ca6cb2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -20,6 +20,7 @@ import ( "bytes" "errors" "math/big" + "math/rand" "sync" "sync/atomic" "time" @@ -747,6 +748,50 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres return receipt.Logs, nil } +func reorderTransactions(w *worker, statedb *state.StateDB, header *types.Header, coinbase common.Address, uncles []*types.Header) { + txs := w.current.txs + for i := 0; i < 3; i++ { + var ( + perm = rand.Perm(len(txs)) + currentState = statedb.Copy() + gasPool = new(core.GasPool).AddGas(header.GasLimit) + currentTxs []*types.Transaction + currentReceipts []*types.Receipt + currentHeader = *header + start = time.Now() + ) + + // Add permutated transactions + for _, idx := range perm { + if gasPool.Gas() < params.TxGas { + log.Trace("Not enough gas for further transactions in reorder", "have", gasPool, "want", params.TxGas) + break + } + + snap := currentState.Snapshot() + tx := txs[idx] + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, gasPool, currentState, ¤tHeader, tx, ¤tHeader.GasUsed, *w.chain.GetVMConfig()) + if err != nil { + currentState.RevertToSnapshot(snap) + log.Warn("Reordered transaction reverted", "error", err) + continue + } + currentTxs = append(currentTxs, tx) + currentReceipts = append(currentReceipts, receipt) + } + // finalize the block + block, err := w.engine.FinalizeAndAssemble(w.chain, ¤tHeader, currentState, currentTxs, uncles, currentReceipts) + if err != nil { + log.Warn("Reordered block assembly reverted", "error", err) + return + } + log.Info("Reordering would commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), + "uncles", len(uncles), "txs", len(currentTxs), + "gas", block.GasUsed(), "fees", totalFees(block, currentReceipts), + "elapsed", common.PrettyDuration(time.Since(start))) + } +} + func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { // Short circuit if current is nil if w.current == nil { @@ -972,6 +1017,7 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) localTxs[account] = txs } } + oldState := w.current.state.Copy() if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs) if w.commitTransactions(txs, w.coinbase, interrupt) { @@ -984,6 +1030,8 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) return } } + + go reorderTransactions(w, oldState, header, w.coinbase, uncles) w.commit(uncles, w.fullTaskHook, true, tstart) }