193 lines
6.5 KiB
Go
193 lines
6.5 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package miner
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"math/big"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/txpool"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
)
|
|
|
|
func TestTransactionPriceNonceSortLegacy(t *testing.T) {
|
|
testTransactionPriceNonceSort(t, nil)
|
|
}
|
|
|
|
func TestTransactionPriceNonceSort1559(t *testing.T) {
|
|
testTransactionPriceNonceSort(t, big.NewInt(0))
|
|
testTransactionPriceNonceSort(t, big.NewInt(5))
|
|
testTransactionPriceNonceSort(t, big.NewInt(50))
|
|
}
|
|
|
|
// Tests that transactions can be correctly sorted according to their price in
|
|
// decreasing order, but at the same time with increasing nonces when issued by
|
|
// the same account.
|
|
func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
|
|
// Generate a batch of accounts to start with
|
|
keys := make([]*ecdsa.PrivateKey, 25)
|
|
for i := 0; i < len(keys); i++ {
|
|
keys[i], _ = crypto.GenerateKey()
|
|
}
|
|
signer := types.LatestSignerForChainID(common.Big1)
|
|
|
|
// Generate a batch of transactions with overlapping values, but shifted nonces
|
|
groups := map[common.Address][]*txpool.LazyTransaction{}
|
|
expectedCount := 0
|
|
for start, key := range keys {
|
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
|
count := 25
|
|
for i := 0; i < 25; i++ {
|
|
var tx *types.Transaction
|
|
gasFeeCap := rand.Intn(50)
|
|
if baseFee == nil {
|
|
tx = types.NewTx(&types.LegacyTx{
|
|
Nonce: uint64(start + i),
|
|
To: &common.Address{},
|
|
Value: big.NewInt(100),
|
|
Gas: 100,
|
|
GasPrice: big.NewInt(int64(gasFeeCap)),
|
|
Data: nil,
|
|
})
|
|
} else {
|
|
tx = types.NewTx(&types.DynamicFeeTx{
|
|
Nonce: uint64(start + i),
|
|
To: &common.Address{},
|
|
Value: big.NewInt(100),
|
|
Gas: 100,
|
|
GasFeeCap: big.NewInt(int64(gasFeeCap)),
|
|
GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))),
|
|
Data: nil,
|
|
})
|
|
if count == 25 && int64(gasFeeCap) < baseFee.Int64() {
|
|
count = i
|
|
}
|
|
}
|
|
tx, err := types.SignTx(tx, signer, key)
|
|
if err != nil {
|
|
t.Fatalf("failed to sign tx: %s", err)
|
|
}
|
|
groups[addr] = append(groups[addr], &txpool.LazyTransaction{
|
|
Hash: tx.Hash(),
|
|
Tx: tx,
|
|
Time: tx.Time(),
|
|
GasFeeCap: tx.GasFeeCap(),
|
|
GasTipCap: tx.GasTipCap(),
|
|
Gas: tx.Gas(),
|
|
BlobGas: tx.BlobGas(),
|
|
})
|
|
}
|
|
expectedCount += count
|
|
}
|
|
// Sort the transactions and cross check the nonce ordering
|
|
txset := newTransactionsByPriceAndNonce(signer, groups, baseFee)
|
|
|
|
txs := types.Transactions{}
|
|
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
|
|
txs = append(txs, tx.Tx)
|
|
txset.Shift()
|
|
}
|
|
if len(txs) != expectedCount {
|
|
t.Errorf("expected %d transactions, found %d", expectedCount, len(txs))
|
|
}
|
|
for i, txi := range txs {
|
|
fromi, _ := types.Sender(signer, txi)
|
|
|
|
// Make sure the nonce order is valid
|
|
for j, txj := range txs[i+1:] {
|
|
fromj, _ := types.Sender(signer, txj)
|
|
if fromi == fromj && txi.Nonce() > txj.Nonce() {
|
|
t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
|
|
}
|
|
}
|
|
// If the next tx has different from account, the price must be lower than the current one
|
|
if i+1 < len(txs) {
|
|
next := txs[i+1]
|
|
fromNext, _ := types.Sender(signer, next)
|
|
tip, err := txi.EffectiveGasTip(baseFee)
|
|
nextTip, nextErr := next.EffectiveGasTip(baseFee)
|
|
if err != nil || nextErr != nil {
|
|
t.Errorf("error calculating effective tip: %v, %v", err, nextErr)
|
|
}
|
|
if fromi != fromNext && tip.Cmp(nextTip) < 0 {
|
|
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that if multiple transactions have the same price, the ones seen earlier
|
|
// are prioritized to avoid network spam attacks aiming for a specific ordering.
|
|
func TestTransactionTimeSort(t *testing.T) {
|
|
// Generate a batch of accounts to start with
|
|
keys := make([]*ecdsa.PrivateKey, 5)
|
|
for i := 0; i < len(keys); i++ {
|
|
keys[i], _ = crypto.GenerateKey()
|
|
}
|
|
signer := types.HomesteadSigner{}
|
|
|
|
// Generate a batch of transactions with overlapping prices, but different creation times
|
|
groups := map[common.Address][]*txpool.LazyTransaction{}
|
|
for start, key := range keys {
|
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
|
|
|
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
|
|
tx.SetTime(time.Unix(0, int64(len(keys)-start)))
|
|
|
|
groups[addr] = append(groups[addr], &txpool.LazyTransaction{
|
|
Hash: tx.Hash(),
|
|
Tx: tx,
|
|
Time: tx.Time(),
|
|
GasFeeCap: tx.GasFeeCap(),
|
|
GasTipCap: tx.GasTipCap(),
|
|
Gas: tx.Gas(),
|
|
BlobGas: tx.BlobGas(),
|
|
})
|
|
}
|
|
// Sort the transactions and cross check the nonce ordering
|
|
txset := newTransactionsByPriceAndNonce(signer, groups, nil)
|
|
|
|
txs := types.Transactions{}
|
|
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
|
|
txs = append(txs, tx.Tx)
|
|
txset.Shift()
|
|
}
|
|
if len(txs) != len(keys) {
|
|
t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
|
|
}
|
|
for i, txi := range txs {
|
|
fromi, _ := types.Sender(signer, txi)
|
|
if i+1 < len(txs) {
|
|
next := txs[i+1]
|
|
fromNext, _ := types.Sender(signer, next)
|
|
|
|
if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
|
|
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
|
}
|
|
// Make sure time order is ascending if the txs have the same gas price
|
|
if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.Time().After(next.Time()) {
|
|
t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.Time(), i+1, fromNext[:4], next.Time())
|
|
}
|
|
}
|
|
}
|
|
}
|