// 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 . package miner import ( "crypto/ecdsa" "math/big" "math/rand" "testing" "time" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/holiman/uint256" ) 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: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap()), }) } expectedCount += count } // Sort the transactions and cross check the nonce ordering txset, _ := newTransactionsByPriceAndNonce(signer, groups, map[common.Address]*big.Int{}, 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: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap()), }) } // Sort the transactions and cross check the nonce ordering txset, _ := newTransactionsByPriceAndNonce(signer, groups, map[common.Address]*big.Int{}, 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()) } } } } // TestNewTransactionsByPriceAndNonce_SpecialSeparation uses table-driven tests to verify separation of special and normal transactions. func TestNewTransactionsByPriceAndNonce_SpecialSeparation(t *testing.T) { signer := types.HomesteadSigner{} genNormalTx := func(nonce uint64, key *ecdsa.PrivateKey) *txpool.LazyTransaction { tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0x1234567890123456789012345678901234567890"), big.NewInt(1), 21000, big.NewInt(1), nil), signer, key) return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap())} } genSpecialTx := func(nonce uint64, key *ecdsa.PrivateKey) *txpool.LazyTransaction { tx, _ := types.SignTx(types.NewTransaction(nonce, common.BlockSignersBinary, big.NewInt(1), 21000, big.NewInt(1), nil), signer, key) return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap())} } testCases := []struct { name string normalCount int specialCount int expectNormal int expectSpecial int }{ {"no transactions", 0, 0, 0, 0}, {"only 1 normal", 1, 0, 1, 0}, {"only 2 normal", 2, 0, 2, 0}, {"only 3 normal", 3, 0, 3, 0}, {"only 1 special", 0, 1, 0, 1}, {"only 2 special", 0, 2, 0, 2}, {"only 3 special", 0, 3, 0, 3}, {"1 normal, 1 special", 1, 1, 1, 1}, {"2 normal, 2 special", 2, 2, 2, 2}, {"3 normal, 3 special", 3, 3, 3, 3}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) txs := make([]*txpool.LazyTransaction, 0, tc.normalCount+tc.specialCount) for i := 0; i < tc.normalCount; i++ { txs = append(txs, genNormalTx(uint64(i), key)) } for i := 0; i < tc.specialCount; i++ { txs = append(txs, genSpecialTx(uint64(tc.normalCount+i), key)) } group := map[common.Address][]*txpool.LazyTransaction{} if len(txs) > 0 { group[addr] = txs } txset, specialTxs := newTransactionsByPriceAndNonce(signer, group, map[common.Address]*big.Int{}, nil) // Check special transactions if len(specialTxs) != tc.expectSpecial { t.Errorf("expected %d special txs, got %d", tc.expectSpecial, len(specialTxs)) } for _, tx := range specialTxs { if tx.To() == nil || *tx.To() != common.BlockSignersBinary { t.Errorf("specialTxs contains non-special tx: %v", tx) } } // Check normal transactions normalCount := 0 for tx := txset.Peek(); tx != nil; tx = txset.Peek() { resolved := tx.Resolve() if resolved == nil || resolved.To() == nil || *resolved.To() == common.BlockSignersBinary { t.Errorf("txset contains special or nil-to tx: %v", resolved) } normalCount++ txset.Shift() } if normalCount != tc.expectNormal { t.Errorf("expected %d normal txs, got %d", tc.expectNormal, normalCount) } }) } }