Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

miner/worker: speculatively apply next transaction in parallel #635

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/ronin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ var (
utils.MinerNoVerifyFlag,
utils.MinerBlockProduceLeftoverFlag,
utils.MinerBlockSizeReserveFlag,
utils.MinerNoSpeculation,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
Expand Down
6 changes: 6 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,11 @@ var (
Value: ethconfig.Defaults.Miner.BlockSizeReserve,
Category: flags.MinerCategory,
}
MinerNoSpeculation = &cli.BoolFlag{
Name: "miner.nospeculation",
Usage: "Disable speculatively committing next transaction",
Category: flags.MinerCategory,
}
// Account settings
UnlockedAccountFlag = &cli.StringFlag{
Name: "unlock",
Expand Down Expand Up @@ -1775,6 +1780,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(LegacyMinerGasTargetFlag.Name) {
log.Warn("The generic --miner.gastarget flag is deprecated and will be removed in the future!")
}
cfg.NoSpeculation = ctx.Bool(MinerNoSpeculation.Name)
}

func setWhitelist(ctx *cli.Context, cfg *ethconfig.Config) {
Expand Down
30 changes: 30 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,36 @@ func NewMessage(
}
}

func NewMessageWithExpiredTimeAndPayer(
from common.Address,
to *common.Address,
nonce uint64,
amount *big.Int,
gasLimit uint64,
gasPrice, gasFeeCap, gasTipCap *big.Int,
data []byte,
accessList AccessList,
isFake bool,
expiredTime uint64,
payer common.Address,
) Message {
return Message{
from: from,
to: to,
nonce: nonce,
amount: amount,
gasLimit: gasLimit,
gasPrice: gasPrice,
gasFeeCap: gasFeeCap,
gasTipCap: gasTipCap,
data: data,
accessList: accessList,
isFake: isFake,
expiredTime: expiredTime,
payer: payer,
}
}

// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
msg := Message{
Expand Down
1 change: 1 addition & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Config struct {
Noverify bool // Disable remote mining solution verification(only useful in ethash).
BlockProduceLeftOver time.Duration
BlockSizeReserve uint64
NoSpeculation bool
}

// Miner creates blocks and searches for proof-of-work values.
Expand Down
59 changes: 52 additions & 7 deletions miner/ordering.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ type TxByPriceAndTime []*TxWithMinerFee

func (s TxByPriceAndTime) Len() int { return len(s) }
func (s TxByPriceAndTime) Less(i, j int) bool {
// If the prices are equal, use the time the transaction was first seen for
// deterministic sorting
cmp := s[i].minerFee.Cmp(s[j].minerFee)
if cmp == 0 {
return s[i].tx.Time.Before(s[j].tx.Time)
}
return cmp > 0
return cmpPriceAndTime(s[i], s[j])
}
func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

Expand All @@ -69,6 +63,20 @@ func (s *TxByPriceAndTime) Pop() interface{} {
return x
}

// cmpPriceAndTime compares 2 transactions by their miner fee and
// time first seen in txpool.
// Returns true if `a` has higher miner fee or appears in txpool
// before `b`.
func cmpPriceAndTime(a *TxWithMinerFee, b *TxWithMinerFee) bool {
// If the prices are equal, use the time the transaction was first seen for
// deterministic sorting
cmp := a.minerFee.Cmp(b.minerFee)
if cmp == 0 {
return a.tx.Time.Before(b.tx.Time)
}
return cmp > 0
}

// TransactionsByPriceAndNonce represents a set of transactions that can return
// transactions in a profit-maximizing sorted order, while supporting removing
// entire batches of transactions for non-executable accounts.
Expand Down Expand Up @@ -142,3 +150,40 @@ func (t *TransactionsByPriceAndNonce) Size() int {
func (t *TransactionsByPriceAndNonce) Clear() {
t.heads = TxByPriceAndTime{}
}

// Next return the potential next committed transaction so that we can speculative
// execute that transaction. As we don't know the result of current transaction yet,
// we don't know that the next operation is shift or pop. This function returns the
// largest among right, left children and the next transaction from the account at
// the head node.
func (t *TransactionsByPriceAndNonce) Next() (*txpool.LazyTransaction, *big.Int) {
heapSize := len(t.heads)
acc := t.heads[0].from

var candidateTx *TxWithMinerFee
if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
if tx, err := newTxWithMinerFee(txs[0], acc, t.baseFee); err == nil {
candidateTx = tx
}
}

if heapSize >= 2 {
// left child
if candidateTx == nil || cmpPriceAndTime(t.heads[1], candidateTx) {
candidateTx = t.heads[1]
}

if heapSize >= 3 {
// right child
if cmpPriceAndTime(t.heads[2], candidateTx) {
candidateTx = t.heads[2]
}
}
}

if candidateTx != nil {
return candidateTx.tx, candidateTx.minerFee
} else {
return nil, nil
}
}
93 changes: 93 additions & 0 deletions miner/ordering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,96 @@ func TestTransactionTimeSort(t *testing.T) {
}
}
}

func TestTransactionsByPriceAndNonceNext(t *testing.T) {
keys := make([]*ecdsa.PrivateKey, 4)
addresses := make([]common.Address, 4)
for i := range keys {
keys[i], _ = crypto.GenerateKey()
addresses[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
}

signer := types.NewMikoSigner(big.NewInt(2020))

transactions := make([]*txpool.LazyTransaction, 4)
for i := range transactions {
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, common.Big0, 21000, big.NewInt(int64(i)), nil), signer, keys[i])
transactions[i] = &txpool.LazyTransaction{
Tx: tx,
Hash: tx.Hash(),
Time: tx.Time(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
Gas: tx.Gas(),
BlobGas: tx.BlobGas(),
}
}

// There is no next transaction
groups := make(map[common.Address][]*txpool.LazyTransaction)
groups[addresses[0]] = append(groups[addresses[0]], transactions[0])

txs := NewTransactionsByPriceAndNonce(signer, groups, nil)
lazyTx, _ := txs.Next()
if lazyTx != nil {
t.Fatalf("Expect no next transaction, got %v", lazyTx)
}

// In heap, head transaction has gas price 1, child transaction has gas price 0
groups = make(map[common.Address][]*txpool.LazyTransaction)
groups[addresses[0]] = append(groups[addresses[0]], transactions[0])
groups[addresses[1]] = append(groups[addresses[1]], transactions[1])
txs = NewTransactionsByPriceAndNonce(signer, groups, nil)
lazyTx, _ = txs.Next()
if lazyTx == nil || lazyTx.GasFeeCap.ToBig().Cmp(common.Big0) != 0 {
t.Fatalf("Expect to have next transaction of gas price 0, got: %v", lazyTx)
}

// In heap, head transaction has gas price 2, children have gas price 1, 0
// Next transaction must have gas price 1
groups = make(map[common.Address][]*txpool.LazyTransaction)
groups[addresses[0]] = append(groups[addresses[0]], transactions[0])
groups[addresses[1]] = append(groups[addresses[1]], transactions[1])
groups[addresses[2]] = append(groups[addresses[2]], transactions[2])
txs = NewTransactionsByPriceAndNonce(signer, groups, nil)
lazyTx, _ = txs.Next()
if lazyTx == nil || lazyTx.GasFeeCap.ToBig().Cmp(common.Big1) != 0 {
t.Fatalf("Expect to have next transaction of gas price 1, got: %v", lazyTx)
}

// In heap, head transaction has gas price 3, children have gas price 2, 1
// Next transaction must have gas price 2
groups = make(map[common.Address][]*txpool.LazyTransaction)
groups[addresses[0]] = append(groups[addresses[0]], transactions[0])
groups[addresses[1]] = append(groups[addresses[1]], transactions[1])
groups[addresses[2]] = append(groups[addresses[2]], transactions[2])
groups[addresses[3]] = append(groups[addresses[3]], transactions[3])
txs = NewTransactionsByPriceAndNonce(signer, groups, nil)
lazyTx, _ = txs.Next()
if lazyTx == nil || lazyTx.GasFeeCap.ToBig().Cmp(common.Big2) != 0 {
t.Fatalf("Expect to have next transaction of gas price 2, got: %v", lazyTx)
}

// The next transaction of head transaction has higher price than children in heap
nextTx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, common.Big0, 21000, big.NewInt(100), nil), signer, keys[3])
tx := &txpool.LazyTransaction{
Tx: nextTx,
Hash: nextTx.Hash(),
Time: nextTx.Time(),
GasFeeCap: uint256.MustFromBig(nextTx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(nextTx.GasTipCap()),
Gas: nextTx.Gas(),
BlobGas: nextTx.BlobGas(),
}

groups = make(map[common.Address][]*txpool.LazyTransaction)
groups[addresses[0]] = append(groups[addresses[0]], transactions[0])
groups[addresses[1]] = append(groups[addresses[1]], transactions[1])
groups[addresses[2]] = append(groups[addresses[2]], transactions[2])
groups[addresses[3]] = append(groups[addresses[3]], transactions[3], tx)
txs = NewTransactionsByPriceAndNonce(signer, groups, nil)
lazyTx, _ = txs.Next()
if lazyTx == nil || lazyTx.GasFeeCap.ToBig().Cmp(big.NewInt(100)) != 0 {
t.Fatalf("Expect to have next transaction of gas price 100, got: %v", tx)
}
}
Loading
Loading