diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index 8630bea4b..c07e23fbf 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -286,7 +286,7 @@ func initAccountServer(t *testing.T, enabledDeprecated bool) { genesisBlock = thorChain.GenesisBlock() claTransfer := tx.NewClause(&addr).WithValue(value) claDeploy := tx.NewClause(nil).WithData(bytecode) - transaction := buildTxWithClauses(thorChain.Repo().ChainTag(), claTransfer, claDeploy) + transaction := buildTxWithClauses(tx.LegacyTxType, thorChain.Repo().ChainTag(), claTransfer, claDeploy) contractAddr = thor.CreateContractAddress(transaction.ID(), 1, 0) method := "set" abi, _ := ABI.New([]byte(abiJSON)) @@ -296,7 +296,7 @@ func initAccountServer(t *testing.T, enabledDeprecated bool) { t.Fatal(err) } claCall := tx.NewClause(&contractAddr).WithData(input) - transactionCall := buildTxWithClauses(thorChain.Repo().ChainTag(), claCall) + transactionCall := buildTxWithClauses(tx.DynamicFeeTxType, thorChain.Repo().ChainTag(), claCall) require.NoError(t, thorChain.MintTransactions( genesis.DevAccounts()[0], @@ -312,17 +312,13 @@ func initAccountServer(t *testing.T, enabledDeprecated bool) { ts = httptest.NewServer(router) } -func buildTxWithClauses(chaiTag byte, clauses ...*tx.Clause) *tx.Transaction { - builder := new(tx.Builder). - ChainTag(chaiTag). +func buildTxWithClauses(txType int, chainTag byte, clauses ...*tx.Clause) *tx.Transaction { + trx, _ := tx.NewTxBuilder(txType). + ChainTag(chainTag). Expiration(10). - Gas(1000000) - for _, c := range clauses { - builder.Clause(c) - } - - trx := builder.Build() - + Gas(1000000). + Clauses(clauses). + Build() return tx.MustSign(trx, genesis.DevAccounts()[0].PrivateKey) } diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index 8c0439e59..ac520d343 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -230,20 +230,30 @@ func initBlockServer(t *testing.T) { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - trx := tx.MustSign( - new(tx.Builder). - ChainTag(thorChain.Repo().ChainTag()). - GasPriceCoef(1). - Expiration(10). - Gas(21000). - Nonce(1). - Clause(cla). - BlockRef(tx.NewBlockRef(0)). - Build(), - genesis.DevAccounts()[0].PrivateKey, - ) - - require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], trx)) + legacyTx, _ := tx.NewTxBuilder(tx.LegacyTxType). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(10). + Gas(21000). + Nonce(1). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + legacyTx = tx.MustSign(legacyTx, genesis.DevAccounts()[0].PrivateKey) + + dynFeeTx, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(thorChain.Repo().ChainTag()). + MaxFeePerGas(big.NewInt(100000)). + MaxPriorityFeePerGas(big.NewInt(100)). + Expiration(10). + Gas(21000). + Nonce(2). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + dynFeeTx = tx.MustSign(dynFeeTx, genesis.DevAccounts()[0].PrivateKey) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], legacyTx, dynFeeTx)) allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) diff --git a/api/blocks/types.go b/api/blocks/types.go index 989b63041..4a34d01da 100644 --- a/api/blocks/types.go +++ b/api/blocks/types.go @@ -6,6 +6,8 @@ package blocks import ( + "math/big" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/vechain/thor/v2/chain" @@ -67,18 +69,20 @@ type JSONOutput struct { } type JSONEmbeddedTx struct { - ID thor.Bytes32 `json:"id"` - ChainTag byte `json:"chainTag"` - BlockRef string `json:"blockRef"` - Expiration uint32 `json:"expiration"` - Clauses []*JSONClause `json:"clauses"` - GasPriceCoef uint8 `json:"gasPriceCoef"` - Gas uint64 `json:"gas"` - Origin thor.Address `json:"origin"` - Delegator *thor.Address `json:"delegator"` - Nonce math.HexOrDecimal64 `json:"nonce"` - DependsOn *thor.Bytes32 `json:"dependsOn"` - Size uint32 `json:"size"` + ID thor.Bytes32 `json:"id"` + ChainTag byte `json:"chainTag"` + BlockRef string `json:"blockRef"` + Expiration uint32 `json:"expiration"` + Clauses []*JSONClause `json:"clauses"` + GasPriceCoef uint8 `json:"gasPriceCoef"` + MaxFeePerGas *big.Int `json:"maxFeePerGas,omitempty"` + MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas,omitempty"` + Gas uint64 `json:"gas"` + Origin thor.Address `json:"origin"` + Delegator *thor.Address `json:"delegator"` + Nonce math.HexOrDecimal64 `json:"nonce"` + DependsOn *thor.Bytes32 `json:"dependsOn"` + Size uint32 `json:"size"` // receipt part GasUsed uint64 `json:"gasUsed"` @@ -148,13 +152,13 @@ func buildJSONOutput(txID thor.Bytes32, index uint32, c *tx.Clause, o *tx.Output func buildJSONEmbeddedTxs(txs tx.Transactions, receipts tx.Receipts) []*JSONEmbeddedTx { jTxs := make([]*JSONEmbeddedTx, 0, len(txs)) - for itx, tx := range txs { + for itx, trx := range txs { receipt := receipts[itx] - clauses := tx.Clauses() - blockRef := tx.BlockRef() - origin, _ := tx.Origin() - delegator, _ := tx.Delegator() + clauses := trx.Clauses() + blockRef := trx.BlockRef() + origin, _ := trx.Origin() + delegator, _ := trx.Delegator() jcs := make([]*JSONClause, 0, len(clauses)) jos := make([]*JSONOutput, 0, len(receipt.Outputs)) @@ -166,23 +170,22 @@ func buildJSONEmbeddedTxs(txs tx.Transactions, receipts tx.Receipts) []*JSONEmbe hexutil.Encode(c.Data()), }) if !receipt.Reverted { - jos = append(jos, buildJSONOutput(tx.ID(), uint32(i), c, receipt.Outputs[i])) + jos = append(jos, buildJSONOutput(trx.ID(), uint32(i), c, receipt.Outputs[i])) } } - jTxs = append(jTxs, &JSONEmbeddedTx{ - ID: tx.ID(), - ChainTag: tx.ChainTag(), - BlockRef: hexutil.Encode(blockRef[:]), - Expiration: tx.Expiration(), - Clauses: jcs, - GasPriceCoef: tx.GasPriceCoef(), - Gas: tx.Gas(), - Origin: origin, - Delegator: delegator, - Nonce: math.HexOrDecimal64(tx.Nonce()), - DependsOn: tx.DependsOn(), - Size: uint32(tx.Size()), + embedTx := &JSONEmbeddedTx{ + ID: trx.ID(), + ChainTag: trx.ChainTag(), + BlockRef: hexutil.Encode(blockRef[:]), + Expiration: trx.Expiration(), + Clauses: jcs, + Gas: trx.Gas(), + Origin: origin, + Delegator: delegator, + Nonce: math.HexOrDecimal64(trx.Nonce()), + DependsOn: trx.DependsOn(), + Size: uint32(trx.Size()), GasUsed: receipt.GasUsed, GasPayer: receipt.GasPayer, @@ -190,7 +193,14 @@ func buildJSONEmbeddedTxs(txs tx.Transactions, receipts tx.Receipts) []*JSONEmbe Reward: (*math.HexOrDecimal256)(receipt.Reward), Reverted: receipt.Reverted, Outputs: jos, - }) + } + if trx.Type() == tx.LegacyTxType { + embedTx.GasPriceCoef = trx.GasPriceCoef() + } else { + embedTx.MaxFeePerGas = trx.MaxFeePerGas() + embedTx.MaxPriorityFeePerGas = trx.MaxPriorityFeePerGas() + } + jTxs = append(jTxs, embedTx) } return jTxs } diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index d56718143..7bc2c7c92 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -541,7 +541,7 @@ func initDebugServer(t *testing.T) { // Adding an empty clause transaction to the block to cover the case of // scanning multiple txs before getting the right one - noClausesTx := new(tx.Builder). + noClausesTx, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). Expiration(10). Gas(21000). @@ -550,7 +550,7 @@ func initDebugServer(t *testing.T) { cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) cla2 := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - transaction = new(tx.Builder). + transaction, _ = tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). @@ -562,7 +562,22 @@ func initDebugServer(t *testing.T) { Build() transaction = tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) - require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx)) + dynFeeTx, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(thorChain.Repo().ChainTag()). + Expiration(10). + Gas(21000). + MaxFeePerGas(big.NewInt(1000)). + MaxPriorityFeePerGas(big.NewInt(100000)). + Nonce(1). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + dynFeeTx = tx.MustSign( + dynFeeTx, + genesis.DevAccounts()[0].PrivateKey, + ) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx, dynFeeTx)) require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0])) allBlocks, err := thorChain.GetAllBlocks() diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index 29a866963..550a89319 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -64,7 +64,7 @@ func initChain(t *testing.T) *testchain.Chain { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - tr := new(tx.Builder). + tr, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). @@ -75,9 +75,9 @@ func initChain(t *testing.T) *testchain.Chain { Build() tr = tx.MustSign(tr, genesis.DevAccounts()[0].PrivateKey) - txDeploy := new(tx.Builder). + txDeploy, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). ChainTag(thorChain.Repo().ChainTag()). - GasPriceCoef(1). + MaxFeePerGas(big.NewInt(1)). Expiration(100). Gas(1_000_000). Nonce(3). diff --git a/api/subscriptions/pending_tx_test.go b/api/subscriptions/pending_tx_test.go index f7fdbf2bd..d60127c4d 100644 --- a/api/subscriptions/pending_tx_test.go +++ b/api/subscriptions/pending_tx_test.go @@ -95,7 +95,7 @@ func TestPendingTx_DispatchLoop(t *testing.T) { p.Subscribe(txCh) // Add a new tx to the mempool - transaction := createTx(repo, 0) + transaction := createTx(repo, 0, tx.LegacyTxType) txPool.AddLocal(transaction) // Start the dispatch loop @@ -113,7 +113,7 @@ func TestPendingTx_DispatchLoop(t *testing.T) { p.Unsubscribe(txCh) // Add another tx to the mempool - tx2 := createTx(repo, 1) + tx2 := createTx(repo, 1, tx.DynamicFeeTxType) txPool.AddLocal(tx2) // Assert that the channel did not receive the second transaction @@ -147,24 +147,6 @@ func addNewBlock(repo *chain.Repository, stater *state.Stater, b0 *block.Block, } } -func createTx(repo *chain.Repository, addressNumber uint) *tx.Transaction { - addr := thor.BytesToAddress([]byte("to")) - cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - - return tx.MustSign( - new(tx.Builder). - ChainTag(repo.ChainTag()). - GasPriceCoef(1). - Expiration(1000). - Gas(21000). - Nonce(uint64(datagen.RandInt())). - Clause(cla). - BlockRef(tx.NewBlockRef(0)). - Build(), - genesis.DevAccounts()[addressNumber].PrivateKey, - ) -} - func TestPendingTx_NoWriteAfterUnsubscribe(t *testing.T) { // Arrange thorChain := initChain(t) @@ -183,7 +165,7 @@ func TestPendingTx_NoWriteAfterUnsubscribe(t *testing.T) { done := make(chan struct{}) // Attempt to write a new transaction - trx := createTx(thorChain.Repo(), 0) + trx := createTx(thorChain.Repo(), 0, tx.LegacyTxType) assert.NotPanics(t, func() { p.dispatch(trx, done) // dispatch should not panic after unsubscribe }, "Dispatching after unsubscribe should not panic") @@ -221,7 +203,7 @@ func TestPendingTx_UnsubscribeOnWebSocketClose(t *testing.T) { defer ws.Close() // Add a transaction - trx := createTx(thorChain.Repo(), 0) + trx := createTx(thorChain.Repo(), 0, tx.LegacyTxType) txPool.AddLocal(trx) // Wait to receive transaction @@ -242,3 +224,22 @@ func TestPendingTx_UnsubscribeOnWebSocketClose(t *testing.T) { require.Equal(t, len(sub.pendingTx.listeners), 0) sub.pendingTx.mu.Unlock() } + +func createTx(repo *chain.Repository, addressNumber uint, txType int) *tx.Transaction { + addr := thor.BytesToAddress([]byte("to")) + cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + + trx, _ := tx.NewTxBuilder(txType). + ChainTag(repo.ChainTag()). + GasPriceCoef(1). + Expiration(1000). + Gas(21000). + Nonce(uint64(datagen.RandInt())). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + return tx.MustSign( + trx, + genesis.DevAccounts()[addressNumber].PrivateKey, + ) +} diff --git a/api/subscriptions/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index 8cfb55f7f..ba972fcbd 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -239,9 +239,9 @@ func initSubscriptionsServer(t *testing.T, enabledDeprecated bool) { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - tr := new(tx.Builder). + tr, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). ChainTag(thorChain.Repo().ChainTag()). - GasPriceCoef(1). + MaxFeePerGas(big.NewInt(1)). Expiration(10). Gas(21000). Nonce(1). @@ -255,7 +255,7 @@ func initSubscriptionsServer(t *testing.T, enabledDeprecated bool) { } tr = tr.WithSignature(sig) - txDeploy := new(tx.Builder). + txDeploy, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(100). @@ -291,7 +291,7 @@ func TestSubscriptionsBacktrace(t *testing.T) { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - tr := new(tx.Builder). + tr, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). @@ -307,7 +307,7 @@ func TestSubscriptionsBacktrace(t *testing.T) { } tr = tr.WithSignature(sig) - txDeploy := new(tx.Builder). + txDeploy, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(100). diff --git a/api/subscriptions/types_test.go b/api/subscriptions/types_test.go index 8cf1bbd07..299512310 100644 --- a/api/subscriptions/types_test.go +++ b/api/subscriptions/types_test.go @@ -95,7 +95,7 @@ func TestConvertTransfer(t *testing.T) { repo, _ := chain.NewRepository(db, b) // New tx - transaction := new(tx.Builder). + legacyTx, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(repo.ChainTag()). GasPriceCoef(1). Expiration(10). @@ -103,11 +103,22 @@ func TestConvertTransfer(t *testing.T) { Nonce(1). BlockRef(tx.NewBlockRef(0)). Build() - transaction = tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) + legacyTx = tx.MustSign(legacyTx, genesis.DevAccounts()[0].PrivateKey) + + dynFeeTx, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(repo.ChainTag()). + MaxFeePerGas(big.NewInt(1)). + Expiration(10). + Gas(21000). + Nonce(1). + BlockRef(tx.NewBlockRef(0)). + Build() + dynFeeTx = tx.MustSign(dynFeeTx, genesis.DevAccounts()[0].PrivateKey) // New block blk := new(block.Builder). - Transaction(transaction). + Transaction(legacyTx). + Transaction(dynFeeTx). Build() transfer := &tx.Transfer{ @@ -117,25 +128,46 @@ func TestConvertTransfer(t *testing.T) { } // Act - transferMessage, err := convertTransfer(blk.Header(), transaction, 0, transfer, false) + transferLegacyMessage, errL := convertTransfer(blk.Header(), legacyTx, 0, transfer, false) + transferDynFeeMessage, errD := convertTransfer(blk.Header(), dynFeeTx, 1, transfer, false) // Assert - assert.NoError(t, err) - assert.Equal(t, transfer.Sender, transferMessage.Sender) - assert.Equal(t, transfer.Recipient, transferMessage.Recipient) + assert.NoError(t, errL) + assert.NoError(t, errD) + + assert.Equal(t, transfer.Sender, transferLegacyMessage.Sender) + assert.Equal(t, transfer.Sender, transferDynFeeMessage.Sender) + + assert.Equal(t, transfer.Recipient, transferLegacyMessage.Recipient) + assert.Equal(t, transfer.Recipient, transferDynFeeMessage.Recipient) + amount := (*math.HexOrDecimal256)(transfer.Amount) - assert.Equal(t, amount, transferMessage.Amount) - assert.Equal(t, blk.Header().ID(), transferMessage.Meta.BlockID) - assert.Equal(t, blk.Header().Number(), transferMessage.Meta.BlockNumber) - assert.Equal(t, blk.Header().Timestamp(), transferMessage.Meta.BlockTimestamp) - assert.Equal(t, transaction.ID(), transferMessage.Meta.TxID) - origin, err := transaction.Origin() - if err != nil { - t.Fatal(err) - } - assert.Equal(t, origin, transferMessage.Meta.TxOrigin) - assert.Equal(t, uint32(0), transferMessage.Meta.ClauseIndex) - assert.Equal(t, false, transferMessage.Obsolete) + assert.Equal(t, amount, transferLegacyMessage.Amount) + assert.Equal(t, amount, transferDynFeeMessage.Amount) + + assert.Equal(t, blk.Header().ID(), transferLegacyMessage.Meta.BlockID) + assert.Equal(t, blk.Header().ID(), transferDynFeeMessage.Meta.BlockID) + + assert.Equal(t, blk.Header().Number(), transferLegacyMessage.Meta.BlockNumber) + assert.Equal(t, blk.Header().Number(), transferDynFeeMessage.Meta.BlockNumber) + + assert.Equal(t, blk.Header().Timestamp(), transferLegacyMessage.Meta.BlockTimestamp) + assert.Equal(t, blk.Header().Timestamp(), transferDynFeeMessage.Meta.BlockTimestamp) + + assert.Equal(t, legacyTx.ID(), transferLegacyMessage.Meta.TxID) + assert.Equal(t, dynFeeTx.ID(), transferDynFeeMessage.Meta.TxID) + + origin, err := legacyTx.Origin() + assert.NoError(t, err) + assert.Equal(t, origin, transferLegacyMessage.Meta.TxOrigin) + assert.Equal(t, uint32(0), transferLegacyMessage.Meta.ClauseIndex) + assert.Equal(t, false, transferLegacyMessage.Obsolete) + + origin, err = dynFeeTx.Origin() + assert.NoError(t, err) + assert.Equal(t, origin, transferDynFeeMessage.Meta.TxOrigin) + assert.Equal(t, uint32(1), transferDynFeeMessage.Meta.ClauseIndex) + assert.Equal(t, false, transferDynFeeMessage.Obsolete) } func TestConvertEventWithBadSignature(t *testing.T) { @@ -143,14 +175,15 @@ func TestConvertEventWithBadSignature(t *testing.T) { badSig := bytes.Repeat([]byte{0xf}, 65) // New tx - transaction := new(tx.Builder). + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(1). GasPriceCoef(1). Expiration(10). Gas(21000). Nonce(1). BlockRef(tx.NewBlockRef(0)). - Build(). + Build() + transaction = transaction. WithSignature(badSig[:]) // New block @@ -182,15 +215,16 @@ func TestConvertEvent(t *testing.T) { repo, _ := chain.NewRepository(db, b) // New tx - transaction := tx.MustSign( - new(tx.Builder). - ChainTag(repo.ChainTag()). - GasPriceCoef(1). - Expiration(10). - Gas(21000). - Nonce(1). - BlockRef(tx.NewBlockRef(0)). - Build(), + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). + ChainTag(repo.ChainTag()). + GasPriceCoef(1). + Expiration(10). + Gas(21000). + Nonce(1). + BlockRef(tx.NewBlockRef(0)). + Build() + transaction = tx.MustSign( + transaction, genesis.DevAccounts()[0].PrivateKey, ) diff --git a/api/transactions/transactions_benchmark_test.go b/api/transactions/transactions_benchmark_test.go index f6f1fccd6..c6eb55820 100644 --- a/api/transactions/transactions_benchmark_test.go +++ b/api/transactions/transactions_benchmark_test.go @@ -233,7 +233,7 @@ func createOneClausePerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Chain for gasUsed < 9_500_000 { toAddr := datagen.RandAddress() cla := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) - transaction := new(tx.Builder). + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(math.MaxUint32 - 1). @@ -260,7 +260,7 @@ func createManyClausesPerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Cha gasUsed := uint64(0) txGas := uint64(42_000) - transactionBuilder := new(tx.Builder). + transactionBuilder := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(math.MaxUint32 - 1). @@ -272,7 +272,7 @@ func createManyClausesPerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Cha transactionBuilder.Clause(tx.NewClause(&toAddr).WithValue(big.NewInt(10000))) } - transaction := transactionBuilder.Gas(gasUsed).Build() + transaction, _ := transactionBuilder.Gas(gasUsed).Build() sig, err := crypto.Sign(transaction.SigningHash().Bytes(), signerPK) if err != nil { diff --git a/api/transactions/transactions_converter.go b/api/transactions/transactions_converter.go new file mode 100644 index 000000000..86a118d02 --- /dev/null +++ b/api/transactions/transactions_converter.go @@ -0,0 +1,79 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package transactions + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/vechain/thor/v2/block" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" +) + +type Transaction struct { + ID thor.Bytes32 `json:"id"` + ChainTag byte `json:"chainTag"` + BlockRef string `json:"blockRef"` + Expiration uint32 `json:"expiration"` + Clauses Clauses `json:"clauses"` + GasPriceCoef uint8 `json:"gasPriceCoef"` + Gas uint64 `json:"gas"` + MaxFeePerGas *big.Int `json:"maxFeePerGas,omitempty"` + MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas,omitempty"` + TxType math.HexOrDecimal64 `json:"txType"` + Origin thor.Address `json:"origin"` + Delegator *thor.Address `json:"delegator"` + Nonce math.HexOrDecimal64 `json:"nonce"` + DependsOn *thor.Bytes32 `json:"dependsOn"` + Size uint32 `json:"size"` + Meta *TxMeta `json:"meta"` +} + +// convertTransaction convert a raw transaction into a json format transaction +func convertTransaction(trx *tx.Transaction, header *block.Header) *Transaction { + //tx origin + origin, _ := trx.Origin() + delegator, _ := trx.Delegator() + + cls := make(Clauses, len(trx.Clauses())) + for i, c := range trx.Clauses() { + cls[i] = convertClause(c) + } + br := trx.BlockRef() + t := &Transaction{ + ChainTag: trx.ChainTag(), + TxType: math.HexOrDecimal64(trx.Type()), + ID: trx.ID(), + Origin: origin, + BlockRef: hexutil.Encode(br[:]), + Expiration: trx.Expiration(), + Nonce: math.HexOrDecimal64(trx.Nonce()), + Size: uint32(trx.Size()), + Gas: trx.Gas(), + DependsOn: trx.DependsOn(), + Clauses: cls, + Delegator: delegator, + } + + switch trx.Type() { + case tx.LegacyTxType: + t.GasPriceCoef = trx.GasPriceCoef() + default: + t.MaxFeePerGas = trx.MaxFeePerGas() + t.MaxPriorityFeePerGas = trx.MaxPriorityFeePerGas() + } + + if header != nil { + t.Meta = &TxMeta{ + BlockID: header.ID(), + BlockNumber: header.Number(), + BlockTimestamp: header.Timestamp(), + } + } + return t +} diff --git a/api/transactions/transactions_coverter_test.go b/api/transactions/transactions_coverter_test.go new file mode 100644 index 000000000..ea042f969 --- /dev/null +++ b/api/transactions/transactions_coverter_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package transactions + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/assert" + "github.com/vechain/thor/v2/block" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" +) + +func TestConvertLegacyTransaction_Success(t *testing.T) { + addr := thor.BytesToAddress([]byte("to")) + cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + cla2 := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + br := tx.NewBlockRef(0) + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). + ChainTag(123). + GasPriceCoef(1). + Expiration(10). + Gas(37000). + Nonce(1). + Clause(cla). + Clause(cla2). + BlockRef(br). + Build() + + header := new(block.Builder).Build().Header() + + result := convertTransaction(transaction, header) + // Common fields + assert.Equal(t, hexutil.Encode(br[:]), result.BlockRef) + assert.Equal(t, transaction.ChainTag(), result.ChainTag) + assert.Equal(t, transaction.Expiration(), result.Expiration) + assert.Equal(t, transaction.Gas(), result.Gas) + assert.Equal(t, math.HexOrDecimal64(transaction.Nonce()), result.Nonce) + assert.Equal(t, 2, len(result.Clauses)) + assert.Equal(t, addr, *result.Clauses[0].To) + assert.Equal(t, convertClause(cla), result.Clauses[0]) + assert.Equal(t, addr, *result.Clauses[1].To) + assert.Equal(t, convertClause(cla2), result.Clauses[1]) + // Legacy fields + assert.Equal(t, uint8(1), result.GasPriceCoef) + // Non legacy fields + assert.Empty(t, result.MaxFeePerGas) + assert.Empty(t, result.MaxPriorityFeePerGas) +} + +func TestConvertDynTransaction_Success(t *testing.T) { + addr := thor.BytesToAddress([]byte("to")) + cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + cla2 := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + br := tx.NewBlockRef(0) + maxFeePerGas := big.NewInt(25000) + maxPriorityFeePerGas := big.NewInt(100) + transaction, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(123). + MaxFeePerGas(maxFeePerGas). + MaxPriorityFeePerGas(maxPriorityFeePerGas). + Expiration(10). + Gas(37000). + Nonce(1). + Clause(cla). + Clause(cla2). + BlockRef(br). + Build() + + header := new(block.Builder).Build().Header() + + result := convertTransaction(transaction, header) + // Common fields + assert.Equal(t, hexutil.Encode(br[:]), result.BlockRef) + assert.Equal(t, transaction.ChainTag(), result.ChainTag) + assert.Equal(t, transaction.Expiration(), result.Expiration) + assert.Equal(t, transaction.Gas(), result.Gas) + assert.Equal(t, math.HexOrDecimal64(transaction.Nonce()), result.Nonce) + assert.Equal(t, 2, len(result.Clauses)) + assert.Equal(t, addr, *result.Clauses[0].To) + assert.Equal(t, convertClause(cla), result.Clauses[0]) + assert.Equal(t, addr, *result.Clauses[1].To) + assert.Equal(t, convertClause(cla2), result.Clauses[1]) + // DynFee fields + assert.Equal(t, maxFeePerGas, result.MaxFeePerGas) + assert.Equal(t, maxPriorityFeePerGas, result.MaxPriorityFeePerGas) + // Non dynFee fields + assert.Empty(t, result.GasPriceCoef) +} diff --git a/api/transactions/transactions_test.go b/api/transactions/transactions_test.go index 68c9d535d..21586b73a 100644 --- a/api/transactions/transactions_test.go +++ b/api/transactions/transactions_test.go @@ -29,11 +29,12 @@ import ( ) var ( - ts *httptest.Server - transaction *tx.Transaction - mempoolTx *tx.Transaction - tclient *thorclient.Client - chainTag byte + ts *httptest.Server + legacyTx *tx.Transaction + dynFeeTx *tx.Transaction + mempoolTx *tx.Transaction + tclient *thorclient.Client + chainTag byte ) func TestTransaction(t *testing.T) { @@ -43,16 +44,18 @@ func TestTransaction(t *testing.T) { // Send tx tclient = thorclient.New(ts.URL) for name, tt := range map[string]func(*testing.T){ - "sendTx": sendTx, - "sendTxWithBadFormat": sendTxWithBadFormat, + "sendLegacyTx": sendLegacyTx, + "sendTxWithBadFormat": sendTxWithBadFormat, "sendTxThatCannotBeAcceptedInLocalMempool": sendTxThatCannotBeAcceptedInLocalMempool, + + "sendDynamicFeeTx": sendDynamicFeeTx, } { t.Run(name, tt) } // Get tx for name, tt := range map[string]func(*testing.T){ - "getTx": getTx, + "getLegacyTx": getLegacyTx, "getTxWithBadID": getTxWithBadID, "txWithBadHeader": txWithBadHeader, "getNonExistingRawTransactionWhenTxStillInMempool": getNonExistingRawTransactionWhenTxStillInMempool, @@ -62,6 +65,8 @@ func TestTransaction(t *testing.T) { "getTransactionByIDPendingTxNotFound": getTransactionByIDPendingTxNotFound, "handleGetTransactionByIDWithBadQueryParams": handleGetTransactionByIDWithBadQueryParams, "handleGetTransactionByIDWithNonExistingHead": handleGetTransactionByIDWithNonExistingHead, + + "getDynamicFeeTx": getDynamicFeeTx, } { t.Run(name, tt) } @@ -76,20 +81,40 @@ func TestTransaction(t *testing.T) { } } -func getTx(t *testing.T) { - res := httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String(), 200) +func getLegacyTx(t *testing.T) { + res := httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String(), 200) var rtx *transactions.Transaction if err := json.Unmarshal(res, &rtx); err != nil { t.Fatal(err) } - checkMatchingTx(t, transaction, rtx) + checkMatchingTx(t, legacyTx, rtx) - res = httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String()+"?raw=true", 200) + res = httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String()+"?raw=true", 200) var rawTx map[string]interface{} if err := json.Unmarshal(res, &rawTx); err != nil { t.Fatal(err) } - rlpTx, err := rlp.EncodeToBytes(transaction) + rlpTx, err := rlp.EncodeToBytes(legacyTx) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hexutil.Encode(rlpTx), rawTx["raw"], "should be equal raw") +} + +func getDynamicFeeTx(t *testing.T) { + res := httpGetAndCheckResponseStatus(t, "/transactions/"+dynFeeTx.ID().String(), 200) + var rtx *transactions.Transaction + if err := json.Unmarshal(res, &rtx); err != nil { + t.Fatal(err) + } + checkMatchingTx(t, dynFeeTx, rtx) + + res = httpGetAndCheckResponseStatus(t, "/transactions/"+dynFeeTx.ID().String()+"?raw=true", 200) + var rawTx map[string]interface{} + if err := json.Unmarshal(res, &rawTx); err != nil { + t.Fatal(err) + } + rlpTx, err := rlp.EncodeToBytes(dynFeeTx) if err != nil { t.Fatal(err) } @@ -97,30 +122,68 @@ func getTx(t *testing.T) { } func getTxReceipt(t *testing.T) { - r := httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String()+"/receipt", 200) + r := httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String()+"/receipt", 200) var receipt *transactions.Receipt if err := json.Unmarshal(r, &receipt); err != nil { t.Fatal(err) } - assert.Equal(t, receipt.GasUsed, transaction.Gas(), "receipt gas used not equal to transaction gas") + assert.Equal(t, receipt.GasUsed, legacyTx.Gas(), "receipt gas used not equal to transaction gas") + + r = httpGetAndCheckResponseStatus(t, "/transactions/"+dynFeeTx.ID().String()+"/receipt", 200) + if err := json.Unmarshal(r, &receipt); err != nil { + t.Fatal(err) + } + assert.Equal(t, receipt.GasUsed, legacyTx.Gas(), "receipt gas used not equal to transaction gas") } -func sendTx(t *testing.T) { +func sendLegacyTx(t *testing.T) { var blockRef = tx.NewBlockRef(0) var expiration = uint32(10) var gas = uint64(21000) - trx := tx.MustSign( - new(tx.Builder). - BlockRef(blockRef). - ChainTag(chainTag). - Expiration(expiration). - Gas(gas). - Build(), + trx, _ := tx.NewTxBuilder(tx.LegacyTxType). + BlockRef(blockRef). + ChainTag(chainTag). + Expiration(expiration). + Gas(gas). + Build() + trx = tx.MustSign( + trx, genesis.DevAccounts()[0].PrivateKey, ) - rlpTx, err := rlp.EncodeToBytes(trx) + rlpTx, err := trx.MarshalBinary() + if err != nil { + t.Fatal(err) + } + + res := httpPostAndCheckResponseStatus(t, "/transactions", transactions.RawTx{Raw: hexutil.Encode(rlpTx)}, 200) + var txObj map[string]string + if err = json.Unmarshal(res, &txObj); err != nil { + t.Fatal(err) + } + assert.Equal(t, trx.ID().String(), txObj["id"], "should be the same transaction id") +} + +func sendDynamicFeeTx(t *testing.T) { + var blockRef = tx.NewBlockRef(0) + var expiration = uint32(10) + var gas = uint64(21000) + var maxFeePerGas = big.NewInt(128) + + trx, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). + BlockRef(blockRef). + ChainTag(chainTag). + Expiration(expiration). + Gas(gas). + MaxFeePerGas(maxFeePerGas). + Build() + trx = tx.MustSign( + trx, + genesis.DevAccounts()[0].PrivateKey, + ) + + rlpTx, err := trx.MarshalBinary() if err != nil { t.Fatal(err) } @@ -143,8 +206,10 @@ func getTxWithBadID(t *testing.T) { func txWithBadHeader(t *testing.T) { badHeaderURL := []string{ - "/transactions/" + transaction.ID().String() + "?head=badHead", - "/transactions/" + transaction.ID().String() + "/receipt?head=badHead", + "/transactions/" + legacyTx.ID().String() + "?head=badHead", + "/transactions/" + legacyTx.ID().String() + "/receipt?head=badHead", + "/transactions/" + dynFeeTx.ID().String() + "?head=badHead", + "/transactions/" + dynFeeTx.ID().String() + "/receipt?head=badHead", } for _, url := range badHeaderURL { @@ -223,7 +288,7 @@ func sendTxWithBadFormat(t *testing.T) { } func sendTxThatCannotBeAcceptedInLocalMempool(t *testing.T) { - tx := new(tx.Builder).Build() + tx, _ := tx.NewTxBuilder(tx.LegacyTxType).Build() rlpTx, err := rlp.EncodeToBytes(tx) if err != nil { t.Fatal(err) @@ -242,18 +307,18 @@ func handleGetTransactionByIDWithBadQueryParams(t *testing.T) { } for _, badQueryParam := range badQueryParams { - res := httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String()+badQueryParam, 400) + res := httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String()+badQueryParam, 400) assert.Contains(t, string(res), "should be boolean") } } func handleGetTransactionByIDWithNonExistingHead(t *testing.T) { - res := httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String()+"?head=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 400) + res := httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String()+"?head=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 400) assert.Equal(t, "head: leveldb: not found", strings.TrimSpace(string(res))) } func handleGetTransactionReceiptByIDWithNonExistingHead(t *testing.T) { - res := httpGetAndCheckResponseStatus(t, "/transactions/"+transaction.ID().String()+"/receipt?head=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 400) + res := httpGetAndCheckResponseStatus(t, "/transactions/"+legacyTx.ID().String()+"/receipt?head=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 400) assert.Equal(t, "head: leveldb: not found", strings.TrimSpace(string(res))) } @@ -273,7 +338,7 @@ func initTransactionServer(t *testing.T) { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - transaction = new(tx.Builder). + legacyTx, _ = tx.NewTxBuilder(tx.LegacyTxType). ChainTag(chainTag). GasPriceCoef(1). Expiration(10). @@ -282,13 +347,25 @@ func initTransactionServer(t *testing.T) { Clause(cla). BlockRef(tx.NewBlockRef(0)). Build() - transaction = tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) + legacyTx = tx.MustSign(legacyTx, genesis.DevAccounts()[0].PrivateKey) - require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction)) + dynFeeTx, _ = tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(chainTag). + MaxFeePerGas(new(big.Int).SetInt64(1)). + MaxPriorityFeePerGas(new(big.Int).SetInt64(10)). + Expiration(10). + Gas(21000). + Nonce(1). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + dynFeeTx = tx.MustSign(dynFeeTx, genesis.DevAccounts()[0].PrivateKey) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], legacyTx, dynFeeTx)) mempool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{Limit: 10000, LimitPerAccount: 16, MaxLifetime: 10 * time.Minute}) - mempoolTx = new(tx.Builder). + mempoolTx, _ = tx.NewTxBuilder(tx.LegacyTxType). ChainTag(chainTag). Expiration(10). Gas(21000). @@ -315,13 +392,22 @@ func checkMatchingTx(t *testing.T, expectedTx *tx.Transaction, actualTx *transac } assert.Equal(t, origin, actualTx.Origin) assert.Equal(t, expectedTx.ID(), actualTx.ID) - assert.Equal(t, expectedTx.GasPriceCoef(), actualTx.GasPriceCoef) assert.Equal(t, expectedTx.Gas(), actualTx.Gas) for i, c := range expectedTx.Clauses() { assert.Equal(t, hexutil.Encode(c.Data()), actualTx.Clauses[i].Data) assert.Equal(t, *c.Value(), big.Int(actualTx.Clauses[i].Value)) assert.Equal(t, c.To(), actualTx.Clauses[i].To) } + switch expectedTx.Type() { + case tx.LegacyTxType: + assert.Equal(t, expectedTx.GasPriceCoef(), actualTx.GasPriceCoef) + assert.Empty(t, actualTx.MaxFeePerGas) + assert.Empty(t, actualTx.MaxPriorityFeePerGas) + case tx.DynamicFeeTxType: + assert.Empty(t, actualTx.GasPriceCoef) + assert.Equal(t, expectedTx.MaxFeePerGas(), actualTx.MaxFeePerGas) + assert.Equal(t, expectedTx.MaxPriorityFeePerGas(), actualTx.MaxPriorityFeePerGas) + } } func httpGetAndCheckResponseStatus(t *testing.T, url string, responseStatusCode int) []byte { diff --git a/api/transactions/types.go b/api/transactions/types.go index 7c3a892ac..c2f676ca4 100644 --- a/api/transactions/types.go +++ b/api/transactions/types.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/rlp" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" @@ -45,23 +44,6 @@ func (c *Clause) String() string { c.Data) } -// Transaction transaction -type Transaction struct { - ID thor.Bytes32 `json:"id"` - ChainTag byte `json:"chainTag"` - BlockRef string `json:"blockRef"` - Expiration uint32 `json:"expiration"` - Clauses Clauses `json:"clauses"` - GasPriceCoef uint8 `json:"gasPriceCoef"` - Gas uint64 `json:"gas"` - Origin thor.Address `json:"origin"` - Delegator *thor.Address `json:"delegator"` - Nonce math.HexOrDecimal64 `json:"nonce"` - DependsOn *thor.Bytes32 `json:"dependsOn"` - Size uint32 `json:"size"` - Meta *TxMeta `json:"meta"` -} - type RawTx struct { Raw string `json:"raw"` } @@ -71,8 +53,9 @@ func (rtx *RawTx) decode() (*tx.Transaction, error) { if err != nil { return nil, err } - var tx *tx.Transaction - if err := rlp.DecodeBytes(data, &tx); err != nil { + + tx := new(tx.Transaction) + if err := tx.UnmarshalBinary(data); err != nil { return nil, err } return tx, nil @@ -83,42 +66,6 @@ type RawTransaction struct { Meta *TxMeta `json:"meta"` } -// convertTransaction convert a raw transaction into a json format transaction -func convertTransaction(tx *tx.Transaction, header *block.Header) *Transaction { - //tx origin - origin, _ := tx.Origin() - delegator, _ := tx.Delegator() - - cls := make(Clauses, len(tx.Clauses())) - for i, c := range tx.Clauses() { - cls[i] = convertClause(c) - } - br := tx.BlockRef() - t := &Transaction{ - ChainTag: tx.ChainTag(), - ID: tx.ID(), - Origin: origin, - BlockRef: hexutil.Encode(br[:]), - Expiration: tx.Expiration(), - Nonce: math.HexOrDecimal64(tx.Nonce()), - Size: uint32(tx.Size()), - GasPriceCoef: tx.GasPriceCoef(), - Gas: tx.Gas(), - DependsOn: tx.DependsOn(), - Clauses: cls, - Delegator: delegator, - } - - if header != nil { - t.Meta = &TxMeta{ - BlockID: header.ID(), - BlockNumber: header.Number(), - BlockTimestamp: header.Timestamp(), - } - } - return t -} - type TxMeta struct { BlockID thor.Bytes32 `json:"blockID"` BlockNumber uint32 `json:"blockNumber"` diff --git a/api/transactions/types_test.go b/api/transactions/types_test.go index b5e6c5eb7..5cfec0c87 100644 --- a/api/transactions/types_test.go +++ b/api/transactions/types_test.go @@ -21,55 +21,70 @@ import ( ) func TestErrorWhileRetrievingTxOriginInConvertReceipt(t *testing.T) { - tr := &tx.Transaction{} - header := &block.Header{} - receipt := &tx.Receipt{ - Reward: big.NewInt(100), - Paid: big.NewInt(10), - } + txTypes := []int{tx.LegacyTxType, tx.DynamicFeeTxType} - convRec, err := convertReceipt(receipt, header, tr) + for _, txType := range txTypes { + tr, _ := tx.NewTxBuilder(txType).Build() + header := &block.Header{} + receipt := &tx.Receipt{ + Reward: big.NewInt(100), + Paid: big.NewInt(10), + } - assert.Error(t, err) - assert.Equal(t, err, secp256k1.ErrInvalidSignatureLen) - assert.Nil(t, convRec) + convRec, err := convertReceipt(receipt, header, tr) + + assert.Error(t, err) + assert.Equal(t, err, secp256k1.ErrInvalidSignatureLen) + assert.Nil(t, convRec) + } } func TestConvertReceiptWhenTxHasNoClauseTo(t *testing.T) { value := big.NewInt(100) - tr := newTx(tx.NewClause(nil).WithValue(value)) - b := new(block.Builder).Build() - header := b.Header() - receipt := newReceipt() - expectedOutputAddress := thor.CreateContractAddress(tr.ID(), uint32(0), 0) + txs := []*tx.Transaction{ + newLegacyTx(tx.NewClause(nil).WithValue(value)), + newDynFeeTx(tx.NewClause(nil).WithValue(value)), + } + for _, tr := range txs { + b := new(block.Builder).Build() + header := b.Header() + receipt := newReceipt() + expectedOutputAddress := thor.CreateContractAddress(tr.ID(), uint32(0), 0) - convRec, err := convertReceipt(receipt, header, tr) + convRec, err := convertReceipt(receipt, header, tr) - assert.NoError(t, err) - assert.Equal(t, 1, len(convRec.Outputs)) - assert.Equal(t, &expectedOutputAddress, convRec.Outputs[0].ContractAddress) + assert.NoError(t, err) + assert.Equal(t, 1, len(convRec.Outputs)) + assert.Equal(t, &expectedOutputAddress, convRec.Outputs[0].ContractAddress) + } } func TestConvertReceipt(t *testing.T) { value := big.NewInt(100) addr := randAddress() - tr := newTx(tx.NewClause(&addr).WithValue(value)) - b := new(block.Builder).Build() - header := b.Header() - receipt := newReceipt() - - convRec, err := convertReceipt(receipt, header, tr) - - assert.NoError(t, err) - assert.Equal(t, 1, len(convRec.Outputs)) - assert.Equal(t, 1, len(convRec.Outputs[0].Events)) - assert.Equal(t, 1, len(convRec.Outputs[0].Transfers)) - assert.Nil(t, convRec.Outputs[0].ContractAddress) - assert.Equal(t, receipt.Outputs[0].Events[0].Address, convRec.Outputs[0].Events[0].Address) - assert.Equal(t, hexutil.Encode(receipt.Outputs[0].Events[0].Data), convRec.Outputs[0].Events[0].Data) - assert.Equal(t, receipt.Outputs[0].Transfers[0].Sender, convRec.Outputs[0].Transfers[0].Sender) - assert.Equal(t, receipt.Outputs[0].Transfers[0].Recipient, convRec.Outputs[0].Transfers[0].Recipient) - assert.Equal(t, (*math.HexOrDecimal256)(receipt.Outputs[0].Transfers[0].Amount), convRec.Outputs[0].Transfers[0].Amount) + + txs := []*tx.Transaction{ + newLegacyTx(tx.NewClause(&addr).WithValue(value)), + newDynFeeTx(tx.NewClause(&addr).WithValue(value)), + } + for _, tr := range txs { + b := new(block.Builder).Build() + header := b.Header() + receipt := newReceipt() + + convRec, err := convertReceipt(receipt, header, tr) + + assert.NoError(t, err) + assert.Equal(t, 1, len(convRec.Outputs)) + assert.Equal(t, 1, len(convRec.Outputs[0].Events)) + assert.Equal(t, 1, len(convRec.Outputs[0].Transfers)) + assert.Nil(t, convRec.Outputs[0].ContractAddress) + assert.Equal(t, receipt.Outputs[0].Events[0].Address, convRec.Outputs[0].Events[0].Address) + assert.Equal(t, hexutil.Encode(receipt.Outputs[0].Events[0].Data), convRec.Outputs[0].Events[0].Data) + assert.Equal(t, receipt.Outputs[0].Transfers[0].Sender, convRec.Outputs[0].Transfers[0].Sender) + assert.Equal(t, receipt.Outputs[0].Transfers[0].Recipient, convRec.Outputs[0].Transfers[0].Recipient) + assert.Equal(t, (*math.HexOrDecimal256)(receipt.Outputs[0].Transfers[0].Amount), convRec.Outputs[0].Transfers[0].Amount) + } } // Utilities functions @@ -99,8 +114,17 @@ func newReceipt() *tx.Receipt { } } -func newTx(clause *tx.Clause) *tx.Transaction { - tx := new(tx.Builder). +func newLegacyTx(clause *tx.Clause) *tx.Transaction { + tx, _ := tx.NewTxBuilder(tx.LegacyTxType). + Clause(clause). + Build() + pk, _ := crypto.GenerateKey() + sig, _ := crypto.Sign(tx.SigningHash().Bytes(), pk) + return tx.WithSignature(sig) +} + +func newDynFeeTx(clause *tx.Clause) *tx.Transaction { + tx, _ := tx.NewTxBuilder(tx.DynamicFeeTxType). Clause(clause). Build() pk, _ := crypto.GenerateKey() diff --git a/block/block_test.go b/block/block_test.go index 547aede6e..ba83d876b 100644 --- a/block/block_test.go +++ b/block/block_test.go @@ -18,8 +18,8 @@ import ( ) func TestBlock(t *testing.T) { - tx1 := new(tx.Builder).Clause(tx.NewClause(&thor.Address{})).Clause(tx.NewClause(&thor.Address{})).Build() - tx2 := new(tx.Builder).Clause(tx.NewClause(nil)).Build() + tx1, _ := tx.NewTxBuilder(tx.LegacyTxType).Clause(tx.NewClause(&thor.Address{})).Clause(tx.NewClause(&thor.Address{})).Build() + tx2, _ := tx.NewTxBuilder(tx.DynamicFeeTxType).Clause(tx.NewClause(nil)).Build() privKey := string("dce1443bd2ef0c2631adc1c67e5c93f13dc23a41c18b536effbbdcbcdb96fb65") diff --git a/chain/chain_test.go b/chain/chain_test.go index d61b38c52..2645cbe1c 100644 --- a/chain/chain_test.go +++ b/chain/chain_test.go @@ -16,71 +16,73 @@ import ( "github.com/vechain/thor/v2/tx" ) -func newTx() *tx.Transaction { - tx := new(tx.Builder).Build() +func newTx(txType int) *tx.Transaction { + tx, _ := tx.NewTxBuilder(txType).Build() pk, _ := crypto.GenerateKey() sig, _ := crypto.Sign(tx.SigningHash().Bytes(), pk) return tx.WithSignature(sig) } func TestChain(t *testing.T) { - tx1 := newTx() - _, repo := newTestRepo() + txTypes := []int{tx.LegacyTxType, tx.DynamicFeeTxType} - b1 := newBlock(repo.GenesisBlock(), 10, tx1) - tx1Meta := &chain.TxMeta{BlockID: b1.Header().ID(), Index: 0, Reverted: false} - tx1Receipt := &tx.Receipt{} - repo.AddBlock(b1, tx.Receipts{tx1Receipt}, 0) + for _, txType := range txTypes { + tr := newTx(txType) + b1 := newBlock(repo.GenesisBlock(), 10, tr) + tx1Meta := &chain.TxMeta{BlockID: b1.Header().ID(), Index: 0, Reverted: false} + tx1Receipt := &tx.Receipt{} + repo.AddBlock(b1, tx.Receipts{tx1Receipt}, 0) - b2 := newBlock(b1, 20) - repo.AddBlock(b2, nil, 0) + b2 := newBlock(b1, 20) + repo.AddBlock(b2, nil, 0) - b3 := newBlock(b2, 30) - repo.AddBlock(b3, nil, 0) + b3 := newBlock(b2, 30) + repo.AddBlock(b3, nil, 0) - b3x := newBlock(b2, 30) - repo.AddBlock(b3x, nil, 1) + b3x := newBlock(b2, 30) + repo.AddBlock(b3x, nil, 1) - c := repo.NewChain(b3.Header().ID()) + c := repo.NewChain(b3.Header().ID()) - assert.Equal(t, b3.Header().ID(), c.HeadID()) - assert.Equal(t, M(b3.Header().ID(), nil), M(c.GetBlockID(3))) - assert.Equal(t, M(b3.Header(), nil), M(c.GetBlockHeader(3))) - assert.Equal(t, M(block.Compose(b3.Header(), b3.Transactions()), nil), M(c.GetBlock(3))) + assert.Equal(t, b3.Header().ID(), c.HeadID()) + assert.Equal(t, M(b3.Header().ID(), nil), M(c.GetBlockID(3))) + assert.Equal(t, M(b3.Header(), nil), M(c.GetBlockHeader(3))) + assert.Equal(t, M(block.Compose(b3.Header(), b3.Transactions()), nil), M(c.GetBlock(3))) - _, err := c.GetBlockID(4) - assert.True(t, c.IsNotFound(err)) + _, err := c.GetBlockID(4) + assert.True(t, c.IsNotFound(err)) - assert.Equal(t, M(tx1Meta, nil), M(c.GetTransactionMeta(tx1.ID()))) - assert.Equal(t, M(tx1, tx1Meta, nil), M(c.GetTransaction(tx1.ID()))) - assert.Equal(t, M(tx1Receipt, nil), M(c.GetTransactionReceipt(tx1.ID()))) - _, err = c.GetTransactionMeta(thor.Bytes32{}) - assert.True(t, c.IsNotFound(err)) + assert.Equal(t, M(tx1Meta, nil), M(c.GetTransactionMeta(tr.ID()))) + assert.Equal(t, M(tr, tx1Meta, nil), M(c.GetTransaction(tr.ID()))) + assert.Equal(t, M(tx1Receipt, nil), M(c.GetTransactionReceipt(tr.ID()))) + _, err = c.GetTransactionMeta(thor.Bytes32{}) + assert.True(t, c.IsNotFound(err)) - assert.Equal(t, M(true, nil), M(c.HasTransaction(tx1.ID(), tx1.BlockRef().Number()))) - assert.Equal(t, M(false, nil), M(c.HasTransaction(tx1.ID(), block.Number(c.HeadID())))) - assert.Equal(t, M(false, nil), M(c.HasTransaction(thor.Bytes32{}, 0))) + assert.Equal(t, M(true, nil), M(c.HasTransaction(tr.ID(), tr.BlockRef().Number()))) + assert.Equal(t, M(false, nil), M(c.HasTransaction(tr.ID(), block.Number(c.HeadID())))) + assert.Equal(t, M(false, nil), M(c.HasTransaction(thor.Bytes32{}, 0))) - assert.Equal(t, M(true, nil), M(c.HasBlock(b1.Header().ID()))) - assert.Equal(t, M(false, nil), M(c.HasBlock(b3x.Header().ID()))) + assert.Equal(t, M(true, nil), M(c.HasBlock(b1.Header().ID()))) + assert.Equal(t, M(false, nil), M(c.HasBlock(b3x.Header().ID()))) - assert.Equal(t, M(b3.Header(), nil), M(c.FindBlockHeaderByTimestamp(25, 1))) - assert.Equal(t, M(b2.Header(), nil), M(c.FindBlockHeaderByTimestamp(25, -1))) - _, err = c.FindBlockHeaderByTimestamp(25, 0) - assert.True(t, c.IsNotFound(err)) + assert.Equal(t, M(b3.Header(), nil), M(c.FindBlockHeaderByTimestamp(25, 1))) + assert.Equal(t, M(b2.Header(), nil), M(c.FindBlockHeaderByTimestamp(25, -1))) + _, err = c.FindBlockHeaderByTimestamp(25, 0) + assert.True(t, c.IsNotFound(err)) - c1, c2 := repo.NewChain(b3.Header().ID()), repo.NewChain(b3x.Header().ID()) + c1, c2 := repo.NewChain(b3.Header().ID()), repo.NewChain(b3x.Header().ID()) - assert.Equal(t, M([]thor.Bytes32{b3.Header().ID()}, nil), M(c1.Exclude(c2))) - assert.Equal(t, M([]thor.Bytes32{b3x.Header().ID()}, nil), M(c2.Exclude(c1))) + assert.Equal(t, M([]thor.Bytes32{b3.Header().ID()}, nil), M(c1.Exclude(c2))) + assert.Equal(t, M([]thor.Bytes32{b3x.Header().ID()}, nil), M(c2.Exclude(c1))) - dangleID := thor.Bytes32{0, 0, 0, 4} - dangleChain := repo.NewChain(dangleID) + dangleID := thor.Bytes32{0, 0, 0, 4} + dangleChain := repo.NewChain(dangleID) - _, err = c1.Exclude(dangleChain) - assert.Error(t, err) + _, err = c1.Exclude(dangleChain) + assert.Error(t, err) - _, err = dangleChain.Exclude(c1) - assert.Error(t, err) + _, err = dangleChain.Exclude(c1) + assert.Error(t, err) + } } diff --git a/chain/repository_test.go b/chain/repository_test.go index 1391acb8d..3e7688371 100644 --- a/chain/repository_test.go +++ b/chain/repository_test.go @@ -71,7 +71,7 @@ func TestRepository(t *testing.T) { assert.Equal(t, b0summary, repo1.BestBlockSummary()) assert.Equal(t, repo1.GenesisBlock().Header().ID()[31], repo1.ChainTag()) - tx1 := new(tx.Builder).Build() + tx1, _ := tx.NewTxBuilder(tx.LegacyTxType).Build() receipt1 := &tx.Receipt{} b1 := newBlock(repo1.GenesisBlock(), 10, tx1) @@ -97,6 +97,26 @@ func TestRepository(t *testing.T) { assert.Equal(t, tx.Receipts{receipt1}.RootHash(), gotReceipts.RootHash()) } + + tx2, _ := tx.NewTxBuilder(tx.DynamicFeeTxType).Build() + receipt2 := &tx.Receipt{} + + b2 := newBlock(b1, 20, tx2) + assert.Nil(t, repo1.AddBlock(b2, tx.Receipts{receipt2}, 0)) + + repo1.SetBestBlockID(b2.Header().ID()) + repo2, _ = chain.NewRepository(db, b0) + for _, repo := range []*chain.Repository{repo1, repo2} { + assert.Equal(t, b2.Header().ID(), repo.BestBlockSummary().Header.ID()) + s, err := repo.GetBlockSummary(b2.Header().ID()) + assert.Nil(t, err) + assert.Equal(t, b2.Header().ID(), s.Header.ID()) + assert.Equal(t, 1, len(s.Txs)) + assert.Equal(t, tx2.ID(), s.Txs[0]) + + gotb, _ := repo.GetBlock(b2.Header().ID()) + assert.Equal(t, b2.Transactions().RootHash(), gotb.Transactions().RootHash()) + } } func TestConflicts(t *testing.T) { diff --git a/cmd/thor/node/node_benchmark_test.go b/cmd/thor/node/node_benchmark_test.go index 0f6d1f1f0..f4012a253 100644 --- a/cmd/thor/node/node_benchmark_test.go +++ b/cmd/thor/node/node_benchmark_test.go @@ -262,7 +262,7 @@ func createOneClausePerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Chain for gasUsed < 9_500_000 { toAddr := datagen.RandAddress() cla := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) - transaction := new(tx.Builder). + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(math.MaxUint32 - 1). @@ -289,7 +289,7 @@ func createManyClausesPerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Cha gasUsed := uint64(0) txGas := uint64(42_000) - transactionBuilder := new(tx.Builder). + transactionBuilder := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(math.MaxUint32 - 1). @@ -301,7 +301,7 @@ func createManyClausesPerTx(signerPK *ecdsa.PrivateKey, thorChain *testchain.Cha transactionBuilder.Clause(tx.NewClause(&toAddr).WithValue(big.NewInt(10000))) } - transaction := transactionBuilder.Gas(gasUsed).Build() + transaction, _ := transactionBuilder.Gas(gasUsed).Build() sig, err := crypto.Sign(transaction.SigningHash().Bytes(), signerPK) if err != nil { diff --git a/cmd/thor/node/tx_stash.go b/cmd/thor/node/tx_stash.go index db7423c17..5adfcc1c3 100644 --- a/cmd/thor/node/tx_stash.go +++ b/cmd/thor/node/tx_stash.go @@ -9,7 +9,6 @@ import ( "bytes" "container/list" - "github.com/ethereum/go-ethereum/rlp" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/util" "github.com/vechain/thor/v2/thor" @@ -37,7 +36,7 @@ func (ts *txStash) Save(tx *tx.Transaction) error { return nil } - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return err } @@ -66,7 +65,7 @@ func (ts *txStash) LoadAll() tx.Transactions { for it.Next() { var tx tx.Transaction - if err := rlp.DecodeBytes(it.Value(), &tx); err != nil { + if err := tx.UnmarshalBinary(it.Value()); err != nil { logger.Warn("decode stashed tx", "err", err) batch.Delete(it.Key()) } else { diff --git a/cmd/thor/node/tx_stash_test.go b/cmd/thor/node/tx_stash_test.go index f34e5d249..58357b377 100644 --- a/cmd/thor/node/tx_stash_test.go +++ b/cmd/thor/node/tx_stash_test.go @@ -18,9 +18,10 @@ import ( "github.com/vechain/thor/v2/tx" ) -func newTx() *tx.Transaction { +func newTx(txType int) *tx.Transaction { + trx, _ := tx.NewTxBuilder(txType).Nonce(rand.Uint64()).Build() //#nosec return tx.MustSign( - new(tx.Builder).Nonce(rand.Uint64()).Build(), //#nosec + trx, genesis.DevAccounts()[0].PrivateKey, ) } @@ -29,18 +30,24 @@ func TestTxStash(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) defer db.Close() - stash := newTxStash(db, 10) + stash := newTxStash(db, 20) var saved tx.Transactions for i := 0; i < 11; i++ { - tx := newTx() + tx := newTx(tx.LegacyTxType) assert.Nil(t, stash.Save(tx)) saved = append(saved, tx) } - loaded := newTxStash(db, 10).LoadAll() + for i := 0; i < 11; i++ { + tx := newTx(tx.DynamicFeeTxType) + assert.Nil(t, stash.Save(tx)) + saved = append(saved, tx) + } + + loaded := newTxStash(db, 20).LoadAll() - saved = saved[1:] + saved = saved[2:] sort.Slice(saved, func(i, j int) bool { return bytes.Compare(saved[i].ID().Bytes(), saved[j].ID().Bytes()) < 0 }) diff --git a/cmd/thor/solo/solo.go b/cmd/thor/solo/solo.go index 638aa74ff..d071cc51f 100644 --- a/cmd/thor/solo/solo.go +++ b/cmd/thor/solo/solo.go @@ -255,16 +255,19 @@ func (s *Solo) init(ctx context.Context) error { // newTx builds and signs a new transaction from the given clauses func (s *Solo) newTx(clauses []*tx.Clause, from genesis.DevAccount) (*tx.Transaction, error) { - builder := new(tx.Builder).ChainTag(s.repo.ChainTag()) + builder := tx.NewTxBuilder(tx.LegacyTxType).ChainTag(s.repo.ChainTag()) for _, c := range clauses { builder.Clause(c) } - trx := builder.BlockRef(tx.NewBlockRef(0)). + trx, err := builder.BlockRef(tx.NewBlockRef(0)). Expiration(math.MaxUint32). Nonce(rand.Uint64()). //#nosec G404 DependsOn(nil). Gas(1_000_000). Build() + if err != nil { + return nil, err + } return tx.Sign(trx, from.PrivateKey) } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 5bac09763..a0d8b69cd 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -27,9 +27,9 @@ import ( "github.com/vechain/thor/v2/vrf" ) -func txBuilder(tag byte) *tx.Builder { +func txBuilder(tag byte, txType int) *tx.Builder { address := thor.BytesToAddress([]byte("addr")) - return new(tx.Builder). + return tx.NewTxBuilder(txType). GasPriceCoef(1). Gas(1000000). Expiration(100). @@ -39,7 +39,7 @@ func txBuilder(tag byte) *tx.Builder { } func txSign(builder *tx.Builder) *tx.Transaction { - transaction := builder.Build() + transaction, _ := builder.Build() sig, _ := crypto.Sign(transaction.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) return transaction.WithSignature(sig) } @@ -99,7 +99,7 @@ func newTestConsensus() (*testConsensus, error) { addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) - txBuilder := txBuilder(repo.ChainTag()).Clause(cla) + txBuilder := txBuilder(repo.ChainTag(), tx.LegacyTxType).Clause(cla) transaction := txSign(txBuilder) err = flow.Adopt(transaction) @@ -466,8 +466,8 @@ func TestVerifyBlock(t *testing.T) { }{ { "TxDepBroken", func(t *testing.T) { - txID := txSign(txBuilder(tc.tag)).ID() - tx := txSign(txBuilder(tc.tag).DependsOn(&txID)) + txID := txSign(txBuilder(tc.tag, tx.LegacyTxType)).ID() + tx := txSign(txBuilder(tc.tag, tx.LegacyTxType).DependsOn(&txID)) blk, err := tc.sign(tc.builder(tc.original.Header()).Transaction(tx)) if err != nil { @@ -481,7 +481,7 @@ func TestVerifyBlock(t *testing.T) { }, { "TxAlreadyExists", func(t *testing.T) { - tx := txSign(txBuilder(tc.tag)) + tx := txSign(txBuilder(tc.tag, tx.LegacyTxType)) blk, err := tc.sign(tc.builder(tc.original.Header()).Transaction(tx).Transaction(tx)) if err != nil { t.Fatal(err) @@ -578,7 +578,7 @@ func TestValidateBlockBody(t *testing.T) { }{ { "ErrTxsRootMismatch", func(t *testing.T) { - transaction := txSign(txBuilder(tc.tag)) + transaction := txSign(txBuilder(tc.tag, tx.LegacyTxType)) transactions := tx.Transactions{transaction} blk := block.Compose(tc.original.Header(), transactions) expected := consensusError( @@ -594,7 +594,7 @@ func TestValidateBlockBody(t *testing.T) { }, { "ErrChainTagMismatch", func(t *testing.T) { - blk, err := tc.sign(tc.builder(tc.original.Header()).Transaction(txSign(txBuilder(tc.tag + 1)))) + blk, err := tc.sign(tc.builder(tc.original.Header()).Transaction(txSign(txBuilder(tc.tag+1, tx.LegacyTxType)))) if err != nil { t.Fatal(err) } @@ -613,7 +613,7 @@ func TestValidateBlockBody(t *testing.T) { "ErrRefFutureBlock", func(t *testing.T) { blk, err := tc.sign( tc.builder(tc.original.Header()).Transaction( - txSign(txBuilder(tc.tag).BlockRef(tx.NewBlockRef(100))), + txSign(txBuilder(tc.tag, tx.LegacyTxType).BlockRef(tx.NewBlockRef(100))), )) if err != nil { t.Fatal(err) @@ -626,7 +626,7 @@ func TestValidateBlockBody(t *testing.T) { { "TxOriginBlocked", func(t *testing.T) { thor.MockBlocklist([]string{genesis.DevAccounts()[9].Address.String()}) - trx := txBuilder(tc.tag).Build() + trx, _ := txBuilder(tc.tag, tx.LegacyTxType).Build() trx = tx.MustSign(trx, genesis.DevAccounts()[9].PrivateKey) blk, err := tc.sign( @@ -644,7 +644,7 @@ func TestValidateBlockBody(t *testing.T) { }, { "TxSignerUnavailable", func(t *testing.T) { - tx := txBuilder(tc.tag).Build() + tx, _ := txBuilder(tc.tag, tx.LegacyTxType).Build() var sig [65]byte tx = tx.WithSignature(sig[:]) @@ -663,7 +663,7 @@ func TestValidateBlockBody(t *testing.T) { }, { "UnsupportedFeatures", func(t *testing.T) { - tx := txBuilder(tc.tag).Features(tx.Features(2)).Build() + tx, _ := txBuilder(tc.tag, tx.LegacyTxType).Features(tx.Features(2)).Build() sig, _ := crypto.Sign(tx.SigningHash().Bytes(), genesis.DevAccounts()[2].PrivateKey) tx = tx.WithSignature(sig) @@ -680,7 +680,7 @@ func TestValidateBlockBody(t *testing.T) { }, { "TxExpired", func(t *testing.T) { - tx := txSign(txBuilder(tc.tag).BlockRef(tx.NewBlockRef(0)).Expiration(0)) + tx := txSign(txBuilder(tc.tag, tx.LegacyTxType).BlockRef(tx.NewBlockRef(0)).Expiration(0)) blk, err := tc.sign(tc.builder(tc.original.Header()).Transaction(tx).Transaction(tx)) if err != nil { t.Fatal(err) @@ -700,7 +700,7 @@ func TestValidateBlockBody(t *testing.T) { }, { "ZeroGasTx", func(t *testing.T) { - txBuilder := new(tx.Builder). + txBuilder := tx.NewTxBuilder(tx.LegacyTxType). GasPriceCoef(0). Gas(0). Expiration(100). diff --git a/logdb/logdb_bench_test.go b/logdb/logdb_bench_test.go index e421ffce3..e4d9a64cd 100644 --- a/logdb/logdb_bench_test.go +++ b/logdb/logdb_bench_test.go @@ -48,7 +48,7 @@ func BenchmarkFakeDB_NewestBlockID(t *testing.B) { b := new(block.Builder). ParentID(new(block.Builder).Build().Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newReceipt()} @@ -113,7 +113,7 @@ func BenchmarkFakeDB_WriteBlocks(t *testing.B) { for i := 0; i < writeCount; i++ { blk = new(block.Builder). ParentID(blk.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newReceipt(), newReceipt()} require.NoError(t, w.Write(blk, receipts)) @@ -127,7 +127,7 @@ func BenchmarkFakeDB_WriteBlocks(t *testing.B) { for i := 0; i < writeCount; i++ { blk = new(block.Builder). ParentID(blk.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newReceipt(), newReceipt()} require.NoError(t, w.Write(blk, receipts)) diff --git a/logdb/logdb_test.go b/logdb/logdb_test.go index 7ffdd59b1..d67b46bbd 100644 --- a/logdb/logdb_test.go +++ b/logdb/logdb_test.go @@ -19,12 +19,13 @@ import ( "github.com/vechain/thor/v2/tx" ) -func newTx() *tx.Transaction { - tx := new(tx.Builder).Build() +func newTx(txType int) *tx.Transaction { + trx, _ := tx.NewTxBuilder(txType).Build() + pk, _ := crypto.GenerateKey() - sig, _ := crypto.Sign(tx.Hash().Bytes(), pk) - return tx.WithSignature(sig) + sig, _ := crypto.Sign(trx.Hash().Bytes(), pk) + return trx.WithSignature(sig) } func randAddress() (addr thor.Address) { @@ -120,6 +121,16 @@ func (logs transferLogs) Reverse() (ret transferLogs) { return } +func TestErrTxTypeNotSupported(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected to panic") + } + }() + nonExistingTxType := 100 + newTx(nonExistingTxType) +} + func TestEvents(t *testing.T) { db, err := logdb.NewMem() if err != nil { @@ -135,8 +146,8 @@ func TestEvents(t *testing.T) { for i := 0; i < 100; i++ { b = new(block.Builder). ParentID(b.Header().ID()). - Transaction(newTx()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). + Transaction(newTx(tx.DynamicFeeTxType)). Build() receipts := tx.Receipts{newReceipt(), newReceipt()} @@ -257,7 +268,8 @@ func TestLogDB_NewestBlockID(t *testing.T) { b = new(block.Builder). ParentID(b.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). + Transaction(newTx(tx.DynamicFeeTxType)). Build() receipts := tx.Receipts{newReceipt()} @@ -300,7 +312,7 @@ func TestLogDB_NewestBlockID(t *testing.T) { func() (thor.Bytes32, error) { b = new(block.Builder). ParentID(b.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newReceipt()} @@ -319,7 +331,7 @@ func TestLogDB_NewestBlockID(t *testing.T) { func() (thor.Bytes32, error) { b = new(block.Builder). ParentID(b.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newEventOnlyReceipt()} @@ -338,7 +350,7 @@ func TestLogDB_NewestBlockID(t *testing.T) { func() (thor.Bytes32, error) { b = new(block.Builder). ParentID(b.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() receipts := tx.Receipts{newTransferOnlyReceipt()} @@ -380,7 +392,8 @@ func TestLogDB_HasBlockID(t *testing.T) { b := new(block.Builder). ParentID(b0.Header().ID()). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). + Transaction(newTx(tx.DynamicFeeTxType)). Build() b1 := b.Header().ID() receipts := tx.Receipts{newReceipt()} @@ -397,7 +410,7 @@ func TestLogDB_HasBlockID(t *testing.T) { b = new(block.Builder). ParentID(b2). - Transaction(newTx()). + Transaction(newTx(tx.LegacyTxType)). Build() b3 := b.Header().ID() receipts = tx.Receipts{newEventOnlyReceipt()} diff --git a/packer/flow_test.go b/packer/flow_test.go index d9baac228..1023cd0c0 100644 --- a/packer/flow_test.go +++ b/packer/flow_test.go @@ -21,8 +21,8 @@ import ( "github.com/vechain/thor/v2/tx" ) -func createTx(chainTag byte, gasPriceCoef uint8, expiration uint32, gas uint64, nonce uint64, dependsOn *thor.Bytes32, clause *tx.Clause, br tx.BlockRef) *tx.Transaction { - builder := new(tx.Builder). +func createTx(txType int, chainTag byte, gasPriceCoef uint8, expiration uint32, gas uint64, nonce uint64, dependsOn *thor.Bytes32, clause *tx.Clause, br tx.BlockRef) *tx.Transaction { + builder := tx.NewTxBuilder(txType). ChainTag(chainTag). GasPriceCoef(gasPriceCoef). Expiration(expiration). @@ -32,7 +32,7 @@ func createTx(chainTag byte, gasPriceCoef uint8, expiration uint32, gas uint64, Clause(clause). BlockRef(br) - transaction := builder.Build() + transaction, _ := builder.Build() signature, _ := crypto.Sign(transaction.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) @@ -66,12 +66,12 @@ func TestAdopt(t *testing.T) { t.Fatal("Error scheduling:", err) } - tx1 := createTx(chainTag, 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(0)) + tx1 := createTx(tx.LegacyTxType, chainTag, 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(0)) if err := flow.Adopt(tx1); err != nil { t.Fatal("Error adopting tx1:", err) } - tx2 := createTx(chainTag, 1, 10, 21000, 2, (*thor.Bytes32)(tx1.ID().Bytes()), clause, tx.NewBlockRef(0)) + tx2 := createTx(tx.LegacyTxType, chainTag, 1, 10, 21000, 2, (*thor.Bytes32)(tx1.ID().Bytes()), clause, tx.NewBlockRef(0)) if err := flow.Adopt(tx2); err != nil { t.Fatal("Error adopting tx2:", err) } @@ -83,7 +83,7 @@ func TestAdopt(t *testing.T) { } // Test dependency that does not exist - tx3 := createTx(chainTag, 1, 10, 21000, 2, (*thor.Bytes32)((thor.Bytes32{0x1}).Bytes()), clause, tx.NewBlockRef(0)) + tx3 := createTx(tx.LegacyTxType, chainTag, 1, 10, 21000, 2, (*thor.Bytes32)((thor.Bytes32{0x1}).Bytes()), clause, tx.NewBlockRef(0)) expectedErrorMessage = "tx not adoptable now" if err := flow.Adopt(tx3); err.Error() != expectedErrorMessage { t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) @@ -95,6 +95,57 @@ func TestAdopt(t *testing.T) { flow.TotalScore() } +func TestAdoptTypedTxs(t *testing.T) { + // Setup environment + db := muxdb.NewMem() + stater := state.NewStater(db) + g := genesis.NewDevnet() + + // Build genesis block + b, _, _, _ := g.Build(stater) + repo, _ := chain.NewRepository(db, b) + + // Common transaction setup + chainTag := repo.ChainTag() + addr := thor.BytesToAddress([]byte("to")) + clause := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + + // Create and adopt two transactions + pkr := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) + sum, err := repo.GetBlockSummary(b.Header().ID()) + if err != nil { + t.Fatal("Error getting block summary:", err) + } + + flow, err := pkr.Schedule(sum, uint64(time.Now().Unix())) + if err != nil { + t.Fatal("Error scheduling:", err) + } + + tx1 := createTx(tx.LegacyTxType, chainTag, 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(0)) + if err := flow.Adopt(tx1); err != nil { + t.Fatal("Error adopting tx1:", err) + } + + tx2 := createTx(tx.DynamicFeeTxType, chainTag, 1, 10, 21000, 2, (*thor.Bytes32)(tx1.ID().Bytes()), clause, tx.NewBlockRef(0)) + if err := flow.Adopt(tx2); err != nil { + t.Fatal("Error adopting tx2:", err) + } + + //Repeat transaction + expectedErrorMessage := "known tx" + if err := flow.Adopt(tx2); err.Error() != expectedErrorMessage { + t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) + } + + // Test dependency that does not exist + tx3 := createTx(tx.DynamicFeeTxType, chainTag, 1, 10, 21000, 2, (*thor.Bytes32)((thor.Bytes32{0x1}).Bytes()), clause, tx.NewBlockRef(0)) + expectedErrorMessage = "tx not adoptable now" + if err := flow.Adopt(tx3); err.Error() != expectedErrorMessage { + t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) + } +} + func TestPack(t *testing.T) { db := muxdb.NewMem() g := genesis.NewDevnet() @@ -160,21 +211,21 @@ func TestAdoptErr(t *testing.T) { flow, _ := pkr.Schedule(sum, uint64(time.Now().Unix())) // Test chain tag mismatch - tx1 := createTx(byte(0xFF), 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(0)) + tx1 := createTx(tx.LegacyTxType, byte(0xFF), 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(0)) expectedErrorMessage := "bad tx: chain tag mismatch" if err := flow.Adopt(tx1); err.Error() != expectedErrorMessage { t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) } // Test wrong block reference - tx2 := createTx(repo.ChainTag(), 1, 10, 1, 21000, nil, clause, tx.NewBlockRef(1000)) + tx2 := createTx(tx.LegacyTxType, repo.ChainTag(), 1, 10, 21000, 1, nil, clause, tx.NewBlockRef(1000)) expectedErrorMessage = "tx not adoptable now" if err := flow.Adopt(tx2); err.Error() != expectedErrorMessage { t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) } // Test exceeded gas limit - tx3 := createTx(repo.ChainTag(), 1, 0, 1, 1, nil, clause, tx.NewBlockRef(1)) + tx3 := createTx(tx.LegacyTxType, repo.ChainTag(), 1, 0, 1, 1, nil, clause, tx.NewBlockRef(1)) expectedErrorMessage = "gas limit reached" if err := flow.Adopt(tx3); err.Error() != expectedErrorMessage { t.Fatalf("Expected error message: '%s', but got: '%s'", expectedErrorMessage, err.Error()) diff --git a/packer/packer_test.go b/packer/packer_test.go index da8078379..5ad1a688d 100644 --- a/packer/packer_test.go +++ b/packer/packer_test.go @@ -50,7 +50,7 @@ func (ti *txIterator) Next() *tx.Transaction { data, _ := method.EncodeInput(a1.Address, big.NewInt(1)) - trx := new(tx.Builder). + trx, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(ti.chainTag). Clause(tx.NewClause(&builtin.Energy.Address).WithData(data)). Gas(300000).GasPriceCoef(0).Nonce(nonce).Expiration(math.MaxUint32).Build() @@ -207,7 +207,7 @@ func TestBlocklist(t *testing.T) { t.Fatal(err) } - tx0 := new(tx.Builder). + tx0, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(repo.ChainTag()). Clause(tx.NewClause(&a1.Address)). Gas(300000).GasPriceCoef(0).Nonce(0).Expiration(math.MaxUint32).Build() diff --git a/runtime/resolved_tx_test.go b/runtime/resolved_tx_test.go index 37a0e7425..89343029b 100644 --- a/runtime/resolved_tx_test.go +++ b/runtime/resolved_tx_test.go @@ -76,33 +76,60 @@ func (tr *testResolvedTransaction) currentState() *state.State { } func (tr *testResolvedTransaction) TestResolveTransaction() { - txBuild := func() *tx.Builder { - return txBuilder(tr.repo.ChainTag()) + legacyTxBuild := func() *tx.Builder { + return txBuilder(tr.repo.ChainTag(), tx.LegacyTxType) + } + dynFeeTxBuild := func() *tx.Builder { + return txBuilder(tr.repo.ChainTag(), tx.DynamicFeeTxType) } - _, err := runtime.ResolveTransaction(txBuild().Build()) + trx, _ := legacyTxBuild().Build() + _, err := runtime.ResolveTransaction(trx) + tr.assert.Equal(secp256k1.ErrInvalidSignatureLen.Error(), err.Error()) + trx, _ = dynFeeTxBuild().Build() + _, err = runtime.ResolveTransaction(trx) tr.assert.Equal(secp256k1.ErrInvalidSignatureLen.Error(), err.Error()) - _, err = runtime.ResolveTransaction(txSign(txBuild().Gas(21000 - 1))) + trx, _ = legacyTxBuild().Gas(21000 - 1).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) + tr.assert.NotNil(err) + trx, _ = dynFeeTxBuild().Gas(21000 - 1).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) tr.assert.NotNil(err) address := thor.BytesToAddress([]byte("addr")) - _, err = runtime.ResolveTransaction(txSign(txBuild().Clause(tx.NewClause(&address).WithValue(big.NewInt(-10)).WithData(nil)))) + trx, _ = legacyTxBuild().Clause(tx.NewClause(&address).WithValue(big.NewInt(-10)).WithData(nil)).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) + tr.assert.NotNil(err) + trx, _ = dynFeeTxBuild().Clause(tx.NewClause(&address).WithValue(big.NewInt(-10)).WithData(nil)).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) tr.assert.NotNil(err) - _, err = runtime.ResolveTransaction(txSign(txBuild(). + trx, _ = legacyTxBuild(). + Clause(tx.NewClause(&address).WithValue(math.MaxBig256).WithData(nil)). + Clause(tx.NewClause(&address).WithValue(math.MaxBig256).WithData(nil)).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) + tr.assert.NotNil(err) + trx, _ = dynFeeTxBuild(). Clause(tx.NewClause(&address).WithValue(math.MaxBig256).WithData(nil)). - Clause(tx.NewClause(&address).WithValue(math.MaxBig256).WithData(nil)), - )) + Clause(tx.NewClause(&address).WithValue(math.MaxBig256).WithData(nil)).Build() + _, err = runtime.ResolveTransaction(txSign(trx)) tr.assert.NotNil(err) - _, err = runtime.ResolveTransaction(txSign(txBuild())) + trx, _ = legacyTxBuild().Build() + _, err = runtime.ResolveTransaction(txSign(trx)) + tr.assert.Nil(err) + trx, _ = dynFeeTxBuild().Build() + _, err = runtime.ResolveTransaction(txSign(trx)) tr.assert.Nil(err) } func (tr *testResolvedTransaction) TestCommonTo() { - txBuild := func() *tx.Builder { - return txBuilder(tr.repo.ChainTag()) + legacyTxBuild := func() *tx.Builder { + return txBuilder(tr.repo.ChainTag(), tx.LegacyTxType) + } + dynFeeTxBuild := func() *tx.Builder { + return txBuilder(tr.repo.ChainTag(), tx.DynamicFeeTxType) } commonTo := func(tx *tx.Transaction, assert func(interface{}, ...interface{}) bool) { @@ -114,26 +141,43 @@ func (tr *testResolvedTransaction) TestCommonTo() { assert(to) } - commonTo(txSign(txBuild()), tr.assert.Nil) + legacyTx, _ := legacyTxBuild().Build() + dynFeeTx, _ := dynFeeTxBuild().Build() + commonTo(txSign(legacyTx), tr.assert.Nil) + commonTo(txSign(dynFeeTx), tr.assert.Nil) - commonTo(txSign(txBuild().Clause(tx.NewClause(nil))), tr.assert.Nil) + legacyTx, _ = legacyTxBuild().Clause(tx.NewClause(nil)).Build() + dynFeeTx, _ = dynFeeTxBuild().Clause(tx.NewClause(nil)).Build() + commonTo(txSign(legacyTx), tr.assert.Nil) + commonTo(txSign(dynFeeTx), tr.assert.Nil) - commonTo(txSign(txBuild().Clause(clause()).Clause(tx.NewClause(nil))), tr.assert.Nil) + legacyTx, _ = legacyTxBuild().Clause(clause()).Clause(tx.NewClause(nil)).Build() + dynFeeTx, _ = dynFeeTxBuild().Clause(clause()).Clause(tx.NewClause(nil)).Build() + commonTo(txSign(legacyTx), tr.assert.Nil) + commonTo(txSign(dynFeeTx), tr.assert.Nil) address := thor.BytesToAddress([]byte("addr1")) - commonTo(txSign(txBuild(). + legacyTx, _ = legacyTxBuild(). + Clause(clause()). + Clause(tx.NewClause(&address)).Build() + commonTo(txSign(legacyTx), tr.assert.Nil) + + dynFeeTx, _ = dynFeeTxBuild(). Clause(clause()). - Clause(tx.NewClause(&address)), - ), tr.assert.Nil) + Clause(tx.NewClause(&address)).Build() + commonTo(txSign(dynFeeTx), tr.assert.Nil) - commonTo(txSign(txBuild().Clause(clause())), tr.assert.NotNil) + legacyTx, _ = legacyTxBuild().Clause(clause()).Build() + dynFeeTx, _ = dynFeeTxBuild().Clause(clause()).Build() + commonTo(txSign(legacyTx), tr.assert.NotNil) + commonTo(txSign(dynFeeTx), tr.assert.NotNil) } func (tr *testResolvedTransaction) TestBuyGas() { state := tr.currentState() txBuild := func() *tx.Builder { - return txBuilder(tr.repo.ChainTag()) + return txBuilder(tr.repo.ChainTag(), tx.LegacyTxType) } targetTime := tr.repo.BestBlockSummary().Header.Timestamp() + thor.BlockInterval @@ -149,24 +193,27 @@ func (tr *testResolvedTransaction) TestBuyGas() { return payer } + trx, _ := txBuild().Clause(clause().WithValue(big.NewInt(100))).Build() tr.assert.Equal( genesis.DevAccounts()[0].Address, - buyGas(txSign(txBuild().Clause(clause().WithValue(big.NewInt(100))))), + buyGas(txSign(trx)), ) bind := builtin.Prototype.Native(state).Bind(genesis.DevAccounts()[1].Address) bind.SetCreditPlan(math.MaxBig256, big.NewInt(1000)) bind.AddUser(genesis.DevAccounts()[0].Address, targetTime) + trx, _ = txBuild().Clause(clause().WithValue(big.NewInt(100))).Build() tr.assert.Equal( genesis.DevAccounts()[1].Address, - buyGas(txSign(txBuild().Clause(clause().WithValue(big.NewInt(100))))), + buyGas(txSign(trx)), ) bind.Sponsor(genesis.DevAccounts()[2].Address, true) bind.SelectSponsor(genesis.DevAccounts()[2].Address) + trx, _ = txBuild().Clause(clause().WithValue(big.NewInt(100))).Build() tr.assert.Equal( genesis.DevAccounts()[2].Address, - buyGas(txSign(txBuild().Clause(clause().WithValue(big.NewInt(100))))), + buyGas(txSign(trx)), ) } @@ -175,8 +222,8 @@ func clause() *tx.Clause { return tx.NewClause(&address).WithData(nil) } -func txBuilder(tag byte) *tx.Builder { - return new(tx.Builder). +func txBuilder(tag byte, txType int) *tx.Builder { + return tx.NewTxBuilder(txType). GasPriceCoef(1). Gas(1000000). Expiration(100). @@ -184,7 +231,6 @@ func txBuilder(tag byte) *tx.Builder { ChainTag(tag) } -func txSign(builder *tx.Builder) *tx.Transaction { - transaction := builder.Build() - return tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) +func txSign(trx *tx.Transaction) *tx.Transaction { + return tx.MustSign(trx, genesis.DevAccounts()[0].PrivateKey) } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 38645620e..3543c3ef1 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -656,14 +656,14 @@ func TestCall(t *testing.T) { assert.Nil(t, err) } -func GetMockTx(repo *chain.Repository, t *testing.T) tx.Transaction { +func getMockTx(repo *chain.Repository, txType int, t *testing.T) *tx.Transaction { var blockRef = tx.NewBlockRef(0) var chainTag = repo.ChainTag() var expiration = uint32(10) var gas = uint64(210000) to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed") - tx := new(tx.Builder). + tx, _ := tx.NewTxBuilder(txType). BlockRef(blockRef). ChainTag(chainTag). Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). @@ -677,12 +677,12 @@ func GetMockTx(repo *chain.Repository, t *testing.T) tx.Transaction { } tx = tx.WithSignature(sig) - return *tx + return tx } -func GetMockFailedTx() tx.Transaction { +func GetMockFailedTx(txType int) tx.Transaction { to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed") - trx := new(tx.Builder).ChainTag(1). + trx, _ := tx.NewTxBuilder(txType).ChainTag(1). BlockRef(tx.BlockRef{0, 0, 0, 0, 0xaa, 0xbb, 0xcc, 0xdd}). Expiration(32). Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). @@ -733,15 +733,20 @@ func TestExecuteTransaction(t *testing.T) { originEnergy.SetString("9000000000000000000000000000000000000", 10) state.SetEnergy(origin.Address, originEnergy, 0) - tx := GetMockTx(repo, t) + txs := []*tx.Transaction{ + getMockTx(repo, tx.LegacyTxType, t), + getMockTx(repo, tx.DynamicFeeTxType, t), + } - rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, thor.NoFork) + for _, trx := range txs { + rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, thor.NoFork) - receipt, err := rt.ExecuteTransaction(&tx) - if err != nil { - t.Fatal(err) + receipt, err := rt.ExecuteTransaction(trx) + if err != nil { + t.Fatal(err) + } + _ = receipt } - _ = receipt } func TestExecuteTransactionFailure(t *testing.T) { @@ -761,7 +766,7 @@ func TestExecuteTransactionFailure(t *testing.T) { originEnergy.SetString("9000000000000000000000000000000000000", 10) state.SetEnergy(origin.Address, originEnergy, 0) - tx := GetMockFailedTx() + tx := GetMockFailedTx(tx.LegacyTxType) rt := runtime.New(repo.NewChain(b0.Header().ID()), state, &xenv.BlockContext{}, thor.NoFork) diff --git a/thorclient/api_test.go b/thorclient/api_test.go index df72b1296..fdb0252c7 100644 --- a/thorclient/api_test.go +++ b/thorclient/api_test.go @@ -87,7 +87,7 @@ func initAPIServer(t *testing.T) (*testchain.Chain, *httptest.Server) { func mintTransactions(t *testing.T, thorChain *testchain.Chain) { toAddr := datagen.RandAddress() - noClausesTx := new(tx.Builder). + noClausesTx, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). Expiration(10). Gas(21000). @@ -100,7 +100,7 @@ func mintTransactions(t *testing.T, thorChain *testchain.Chain) { cla := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) cla2 := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) - transaction := new(tx.Builder). + transaction, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). @@ -218,7 +218,7 @@ func testTransactionsEndpoint(t *testing.T, thorChain *testchain.Chain, ts *http t.Run("SendTransaction", func(t *testing.T) { toAddr := thor.MustParseAddress("0x0123456789abcdef0123456789abcdef01234567") clause := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) - trx := new(tx.Builder). + trx, _ := tx.NewTxBuilder(tx.LegacyTxType). ChainTag(thorChain.Repo().ChainTag()). Expiration(10). Gas(21000). @@ -230,6 +230,19 @@ func testTransactionsEndpoint(t *testing.T, thorChain *testchain.Chain, ts *http require.NoError(t, err) require.NotNil(t, sendResult) require.Equal(t, trx.ID().String(), sendResult.ID.String()) // Ensure transaction was successful + + trx, _ = tx.NewTxBuilder(tx.DynamicFeeTxType). + ChainTag(thorChain.Repo().ChainTag()). + Expiration(10). + Gas(21000). + Clause(clause). + Build() + + trx = tx.MustSign(trx, genesis.DevAccounts()[0].PrivateKey) + sendResult, err = c.SendTransaction(trx) + require.NoError(t, err) + require.NotNil(t, sendResult) + require.Equal(t, trx.ID().String(), sendResult.ID.String()) // Ensure transaction was successful }) // 3. Test retrieving the transaction receipt @@ -367,7 +380,7 @@ func testEventsEndpoint(t *testing.T, _ *testchain.Chain, ts *httptest.Server) { // Define the payload for filtering events payload := &events.EventFilter{ CriteriaSet: []*events.EventCriteria{ - &events.EventCriteria{ + { Address: &address, TopicSet: events.TopicSet{ Topic0: &topic, diff --git a/thorclient/thorclient.go b/thorclient/thorclient.go index d2e035145..b0dfcfd15 100644 --- a/thorclient/thorclient.go +++ b/thorclient/thorclient.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/rlp" "github.com/vechain/thor/v2/api/accounts" "github.com/vechain/thor/v2/api/blocks" "github.com/vechain/thor/v2/api/events" @@ -174,7 +173,7 @@ func (c *Client) TransactionReceipt(id *thor.Bytes32, opts ...Option) (*transact // SendTransaction sends a signed transaction to the blockchain. func (c *Client) SendTransaction(tx *tx.Transaction) (*transactions.SendTxResult, error) { - rlpTx, err := rlp.EncodeToBytes(tx) + rlpTx, err := tx.MarshalBinary() if err != nil { return nil, fmt.Errorf("unable to encode transaction - %w", err) } diff --git a/thorclient/thorclient_test.go b/thorclient/thorclient_test.go index e106fa443..e4e5acdd6 100644 --- a/thorclient/thorclient_test.go +++ b/thorclient/thorclient_test.go @@ -22,7 +22,7 @@ import ( func TestConvertToBatchCallData(t *testing.T) { // Test case 1: Empty transaction - tx1 := &tx.Transaction{} + tx1 := tx.NewTx(&tx.LegacyTransaction{}) addr1 := &thor.Address{} expected1 := &accounts.BatchCallData{ Clauses: make(accounts.Clauses, 0), diff --git a/tx/builder.go b/tx/builder.go index 64f98868f..a441a56f6 100644 --- a/tx/builder.go +++ b/tx/builder.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018 The VeChainThor developers +// Copyright (c) 2024 The VeChainThor developers // Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying // file LICENSE or @@ -7,76 +7,144 @@ package tx import ( "encoding/binary" + "math/big" "github.com/vechain/thor/v2/thor" ) // Builder to make it easy to build transaction. type Builder struct { - body body + txType int + chainTag byte + clauses []*Clause + gasPriceCoef uint8 + maxFeePerGas *big.Int + maxPriorityFeePerGas *big.Int + gas uint64 + blockRef uint64 + expiration uint32 + nonce uint64 + dependsOn *thor.Bytes32 + reserved reserved +} + +func NewTxBuilder(txType int) *Builder { + return &Builder{txType: txType} } // ChainTag set chain tag. func (b *Builder) ChainTag(tag byte) *Builder { - b.body.ChainTag = tag + b.chainTag = tag return b } // Clause add a clause. func (b *Builder) Clause(c *Clause) *Builder { - b.body.Clauses = append(b.body.Clauses, c) + b.clauses = append(b.clauses, c) + return b +} + +func (b *Builder) Clauses(clauses []*Clause) *Builder { + for _, c := range clauses { + b.Clause(c) + } return b } // GasPriceCoef set gas price coef. func (b *Builder) GasPriceCoef(coef uint8) *Builder { - b.body.GasPriceCoef = coef + b.gasPriceCoef = coef + return b +} + +// MaxFeePerGas set max fee per gas. +func (b *Builder) MaxFeePerGas(maxFeePerGas *big.Int) *Builder { + b.maxFeePerGas = maxFeePerGas + return b +} + +// MaxPriorityFeePerGas set max priority fee per gas. +func (b *Builder) MaxPriorityFeePerGas(maxPriorityFeePerGas *big.Int) *Builder { + b.maxPriorityFeePerGas = maxPriorityFeePerGas return b } // Gas set gas provision for tx. func (b *Builder) Gas(gas uint64) *Builder { - b.body.Gas = gas + b.gas = gas return b } // BlockRef set block reference. func (b *Builder) BlockRef(br BlockRef) *Builder { - b.body.BlockRef = binary.BigEndian.Uint64(br[:]) + b.blockRef = binary.BigEndian.Uint64(br[:]) return b } // Expiration set expiration. func (b *Builder) Expiration(exp uint32) *Builder { - b.body.Expiration = exp + b.expiration = exp return b } // Nonce set nonce. func (b *Builder) Nonce(nonce uint64) *Builder { - b.body.Nonce = nonce + b.nonce = nonce return b } // DependsOn set depended tx. func (b *Builder) DependsOn(txID *thor.Bytes32) *Builder { if txID == nil { - b.body.DependsOn = nil + b.dependsOn = nil } else { cpy := *txID - b.body.DependsOn = &cpy + b.dependsOn = &cpy } return b } // Features set features. func (b *Builder) Features(feat Features) *Builder { - b.body.Reserved.Features = feat + b.reserved.Features = feat return b } -// Build build tx object. -func (b *Builder) Build() *Transaction { - tx := Transaction{body: b.body} - return &tx +// BuildLegacy builds legacy tx object. +func (b *Builder) Build() (*Transaction, error) { + var tx *Transaction + switch b.txType { + case LegacyTxType: + tx = &Transaction{ + body: &LegacyTransaction{ + ChainTag: b.chainTag, + Clauses: b.clauses, + GasPriceCoef: b.gasPriceCoef, + Gas: b.gas, + BlockRef: b.blockRef, + Expiration: b.expiration, + Nonce: b.nonce, + DependsOn: b.dependsOn, + Reserved: b.reserved, + }, + } + case DynamicFeeTxType: + tx = &Transaction{ + body: &DynamicFeeTransaction{ + ChainTag: b.chainTag, + Clauses: b.clauses, + MaxFeePerGas: b.maxFeePerGas, + MaxPriorityFeePerGas: b.maxPriorityFeePerGas, + Gas: b.gas, + BlockRef: b.blockRef, + Expiration: b.expiration, + Nonce: b.nonce, + DependsOn: b.dependsOn, + Reserved: b.reserved, + }, + } + default: + return nil, ErrTxTypeNotSupported + } + return tx, nil } diff --git a/tx/builder_test.go b/tx/builder_test.go index 2c3b2d452..fdd669e3c 100644 --- a/tx/builder_test.go +++ b/tx/builder_test.go @@ -1,14 +1,141 @@ -// Copyright (c) 2018 The VeChainThor developers +// Copyright (c) 2024 The VeChainThor developers // Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying // file LICENSE or -package tx_test +package tx import ( + "math/big" "testing" + + "github.com/stretchr/testify/assert" + "github.com/vechain/thor/v2/thor" ) -func TestBuilder(t *testing.T) { +func TestNewTxBuilder(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + assert.NotNil(t, builder) + assert.Equal(t, LegacyTxType, builder.txType) +} + +func TestBuilder_ChainTag(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + builder.ChainTag(0x4a) + assert.Equal(t, byte(0x4a), builder.chainTag) +} + +func TestBuilder_Clause(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + addr := thor.BytesToAddress([]byte("to")) + clause := NewClause(&addr) + builder.Clause(clause) + assert.Equal(t, 1, len(builder.clauses)) + assert.Equal(t, clause, builder.clauses[0]) +} + +func TestBuilder_GasPriceCoef(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + builder.GasPriceCoef(10) + assert.Equal(t, uint8(10), builder.gasPriceCoef) +} + +func TestBuilder_MaxFeePerGas(t *testing.T) { + builder := NewTxBuilder(DynamicFeeTxType) + maxFee := big.NewInt(1000000000) + builder.MaxFeePerGas(maxFee) + assert.Equal(t, maxFee, builder.maxFeePerGas) +} + +func TestBuilder_MaxPriorityFeePerGas(t *testing.T) { + builder := NewTxBuilder(DynamicFeeTxType) + maxPriorityFee := big.NewInt(2000000000) + builder.MaxPriorityFeePerGas(maxPriorityFee) + assert.Equal(t, maxPriorityFee, builder.maxPriorityFeePerGas) +} + +func TestBuilder_Gas(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + builder.Gas(21000) + assert.Equal(t, uint64(21000), builder.gas) +} + +func TestBuilder_BlockRef(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + blockRef := BlockRef{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + builder.BlockRef(blockRef) + assert.Equal(t, uint64(0x0102030405060708), builder.blockRef) +} + +func TestBuilder_Expiration(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + builder.Expiration(100) + assert.Equal(t, uint32(100), builder.expiration) +} + +func TestBuilder_Nonce(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + builder.Nonce(12345) + assert.Equal(t, uint64(12345), builder.nonce) +} + +func TestBuilder_DependsOn(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + txID := thor.Bytes32{0x01, 0x02, 0x03, 0x04} + builder.DependsOn(&txID) + assert.Equal(t, &txID, builder.dependsOn) + builder.DependsOn(nil) + assert.Nil(t, builder.dependsOn) +} + +func TestBuilder_Features(t *testing.T) { + builder := NewTxBuilder(LegacyTxType) + features := Features(0x01) + builder.Features(features) + assert.Equal(t, features, builder.reserved.Features) +} + +func TestBuilder_Build_Legacy(t *testing.T) { + builder := NewTxBuilder(LegacyTxType). + ChainTag(0x4a). + Clause(&Clause{}). + GasPriceCoef(10). + Gas(21000). + BlockRef(BlockRef{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}). + Expiration(100). + Nonce(12345). + DependsOn(&thor.Bytes32{0x01, 0x02, 0x03, 0x04}). + Features(0x01) + + tx, err := builder.Build() + assert.NoError(t, err) + assert.NotNil(t, tx) + assert.IsType(t, &LegacyTransaction{}, tx.body) +} + +func TestBuilder_Build_DynamicFee(t *testing.T) { + builder := NewTxBuilder(DynamicFeeTxType). + ChainTag(0x4a). + Clause(&Clause{}). + MaxFeePerGas(big.NewInt(1000000000)). + MaxPriorityFeePerGas(big.NewInt(2000000000)). + Gas(21000). + BlockRef(BlockRef{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}). + Expiration(100). + Nonce(12345). + DependsOn(&thor.Bytes32{0x01, 0x02, 0x03, 0x04}). + Features(0x01) + + tx, err := builder.Build() + assert.NoError(t, err) + assert.NotNil(t, tx) + assert.IsType(t, &DynamicFeeTransaction{}, tx.body) +} +func TestBuilder_Build_InvalidType(t *testing.T) { + builder := NewTxBuilder(0xff) + tx, err := builder.Build() + assert.Error(t, err) + assert.Nil(t, tx) + assert.Equal(t, ErrTxTypeNotSupported, err) } diff --git a/tx/hashing.go b/tx/hashing.go new file mode 100644 index 000000000..25c0034b3 --- /dev/null +++ b/tx/hashing.go @@ -0,0 +1,35 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package tx + +import ( + "bytes" + "io" + "sync" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/vechain/thor/v2/thor" +) + +// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +var encodeBufferPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +func rlpHash(x interface{}) thor.Bytes32 { + return thor.Blake2bFn(func(w io.Writer) { + rlp.Encode(w, x) + }) +} + +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding the +// given interface. It's used for typed transactions. +func prefixedRlpHash(prefix byte, x interface{}) thor.Bytes32 { + return thor.Blake2bFn(func(w io.Writer) { + w.Write([]byte{prefix}) + rlp.Encode(w, x) + }) +} diff --git a/tx/signer_test.go b/tx/signer_test.go index 5a11fa62c..6b3492ea8 100644 --- a/tx/signer_test.go +++ b/tx/signer_test.go @@ -19,24 +19,27 @@ func TestSign(t *testing.T) { pk, err := crypto.GenerateKey() assert.NoError(t, err) - tx := new(Builder).Build() - - // Sign the transaction - signedTx, err := Sign(tx, pk) - assert.NoError(t, err) - - // Verify the transaction was signed - assert.NotNil(t, signedTx) - - // Verify address from Origin - addr, err := signedTx.Origin() - require.NoError(t, err) - assert.Equal(t, thor.Address(crypto.PubkeyToAddress(pk.PublicKey)), addr) - - // Verify the delegator - delegator, err := signedTx.Delegator() - require.NoError(t, err) - assert.Nil(t, delegator) + txTypes := []int{LegacyTxType, DynamicFeeTxType} + + for _, txType := range txTypes { + trx, _ := NewTxBuilder(txType).Build() + // Sign the transaction + signedTx, err := Sign(trx, pk) + assert.NoError(t, err) + + // Verify the transaction was signed + assert.NotNil(t, signedTx) + + // Verify address from Origin + addr, err := signedTx.Origin() + require.NoError(t, err) + assert.Equal(t, thor.Address(crypto.PubkeyToAddress(pk.PublicKey)), addr) + + // Verify the delegator + delegator, err := signedTx.Delegator() + require.NoError(t, err) + assert.Nil(t, delegator) + } } func TestSignDelegated(t *testing.T) { @@ -47,30 +50,33 @@ func TestSignDelegated(t *testing.T) { originPK, err := crypto.GenerateKey() assert.NoError(t, err) - tx := new(Builder).Build() - - // Feature not enabled - signedTx, err := SignDelegated(tx, originPK, delegatorPK) - assert.ErrorContains(t, err, "transaction delegated feature is not enabled") - assert.Nil(t, signedTx) - - // enable the feature - var features Features - features.SetDelegated(true) - tx = new(Builder).Features(features).Build() - - // Sign the transaction as a delegator - signedTx, err = SignDelegated(tx, originPK, delegatorPK) - assert.NoError(t, err) - assert.NotNil(t, signedTx) - - // Verify address from Origin - origin, err := signedTx.Origin() - require.NoError(t, err) - assert.Equal(t, thor.Address(crypto.PubkeyToAddress(originPK.PublicKey)), origin) - - // Verify the delegator - delegator, err := signedTx.Delegator() - require.NoError(t, err) - assert.Equal(t, thor.Address(crypto.PubkeyToAddress(delegatorPK.PublicKey)), *delegator) + txTypes := []int{LegacyTxType, DynamicFeeTxType} + + for _, txType := range txTypes { + // Feature not enabled + trx, _ := NewTxBuilder(txType).Build() + signedTx, err := SignDelegated(trx, originPK, delegatorPK) + assert.ErrorContains(t, err, "transaction delegated feature is not enabled") + assert.Nil(t, signedTx) + + // enable the feature + var features Features + features.SetDelegated(true) + trx, _ = NewTxBuilder(txType).Features(features).Build() + + // Sign the transaction as a delegator + signedTx, err = SignDelegated(trx, originPK, delegatorPK) + assert.NoError(t, err) + assert.NotNil(t, signedTx) + + // Verify address from Origin + origin, err := signedTx.Origin() + require.NoError(t, err) + assert.Equal(t, thor.Address(crypto.PubkeyToAddress(originPK.PublicKey)), origin) + + // Verify the delegator + delegator, err := signedTx.Delegator() + require.NoError(t, err) + assert.Equal(t, thor.Address(crypto.PubkeyToAddress(delegatorPK.PublicKey)), *delegator) + } } diff --git a/tx/transaction.go b/tx/transaction.go index 26c53c7d7..d684bde3b 100644 --- a/tx/transaction.go +++ b/tx/transaction.go @@ -24,11 +24,19 @@ import ( var ( errIntrinsicGasOverflow = errors.New("intrinsic gas overflow") + ErrTxTypeNotSupported = errors.New("transaction type not supported") + errEmptyTypedTx = errors.New("empty typed transaction bytes") +) + +// Starting from the max value allowed to avoid ambiguity with Ethereum tx type codes. +const ( + LegacyTxType = 0x00 + DynamicFeeTxType = 0x51 ) // Transaction is an immutable tx type. type Transaction struct { - body body + body TxData cache struct { signingHash atomic.Value @@ -42,33 +50,53 @@ type Transaction struct { } } -// body describes details of a tx. -type body struct { - ChainTag byte - BlockRef uint64 - Expiration uint32 - Clauses []*Clause - GasPriceCoef uint8 - Gas uint64 - DependsOn *thor.Bytes32 `rlp:"nil"` - Nonce uint64 - Reserved reserved - Signature []byte +// TxData describes details of a tx. +type TxData interface { + txType() byte + copy() TxData + + chainTag() byte + blockRef() uint64 + expiration() uint32 + clauses() []*Clause + gasPriceCoef() uint8 + gas() uint64 + maxFeePerGas() *big.Int + maxPriorityFeePerGas() *big.Int + dependsOn() *thor.Bytes32 + nonce() uint64 + reserved() reserved + signature() []byte + setSignature(sig []byte) + + encode(w io.Writer) error +} + +// NewTx creates a new transaction. +func NewTx(body TxData) *Transaction { + tx := new(Transaction) + tx.setDecoded(body.copy(), 0) + return tx +} + +// Type returns the transaction type. +func (tx *Transaction) Type() uint8 { + return tx.body.txType() } // ChainTag returns chain tag. func (t *Transaction) ChainTag() byte { - return t.body.ChainTag + return t.body.chainTag() } // Nonce returns nonce value. func (t *Transaction) Nonce() uint64 { - return t.body.Nonce + return t.body.nonce() } // BlockRef returns block reference, which is first 8 bytes of block hash. func (t *Transaction) BlockRef() (br BlockRef) { - binary.BigEndian.PutUint64(br[:], t.body.BlockRef) + binary.BigEndian.PutUint64(br[:], t.body.blockRef()) return } @@ -76,12 +104,12 @@ func (t *Transaction) BlockRef() (br BlockRef) { // A valid transaction requires: // blockNum in [blockRef.Num... blockRef.Num + Expiration] func (t *Transaction) Expiration() uint32 { - return t.body.Expiration + return t.body.expiration() } // IsExpired returns whether the tx is expired according to the given blockNum. func (t *Transaction) IsExpired(blockNum uint32) bool { - return uint64(blockNum) > uint64(t.BlockRef().Number())+uint64(t.body.Expiration) // cast to uint64 to prevent potential overflow + return uint64(blockNum) > uint64(t.BlockRef().Number())+uint64(t.body.expiration()) // cast to uint64 to prevent potential overflow } // ID returns id of tx. @@ -107,9 +135,12 @@ func (t *Transaction) Hash() (hash thor.Bytes32) { return cached.(thor.Bytes32) } defer func() { t.cache.hash.Store(hash) }() - return thor.Blake2bFn(func(w io.Writer) { - rlp.Encode(w, t) - }) + + // Legacy tx don't have type prefix. + if t.Type() == LegacyTxType { + return rlpHash(t) + } + return prefixedRlpHash(t.Type(), t.body) } // UnprovedWork returns unproved work of this tx. @@ -126,24 +157,20 @@ func (t *Transaction) UnprovedWork() (w *big.Int) { if err != nil { return &big.Int{} } - return t.EvaluateWork(origin)(t.body.Nonce) + return t.EvaluateWork(origin)(t.body.nonce()) } // EvaluateWork try to compute work when tx origin assumed. func (t *Transaction) EvaluateWork(origin thor.Address) func(nonce uint64) *big.Int { - hashWithoutNonce := thor.Blake2bFn(func(w io.Writer) { - rlp.Encode(w, []interface{}{ - t.body.ChainTag, - t.body.BlockRef, - t.body.Expiration, - t.body.Clauses, - t.body.GasPriceCoef, - t.body.Gas, - t.body.DependsOn, - &t.body.Reserved, - origin, - }) - }) + var hashWithoutNonce *thor.Bytes32 + switch t.Type() { + case LegacyTxType: + hashWithoutNonce = t.hashWithoutNonceLegacyTx(origin) + case DynamicFeeTxType: + hashWithoutNonce = t.hashWithoutNonceDynamicFeeTx(origin) + default: + panic(ErrTxTypeNotSupported) + } return func(nonce uint64) *big.Int { var nonceBytes [8]byte @@ -154,6 +181,41 @@ func (t *Transaction) EvaluateWork(origin thor.Address) func(nonce uint64) *big. } } +func (t *Transaction) hashWithoutNonceLegacyTx(origin thor.Address) *thor.Bytes32 { + b := thor.Blake2bFn(func(w io.Writer) { + rlp.Encode(w, []interface{}{ + t.body.chainTag(), + t.body.blockRef(), + t.body.expiration(), + t.body.clauses(), + t.body.gasPriceCoef(), + t.body.dependsOn(), + t.body.nonce(), + t.body.reserved(), + origin, + }) + }) + return &b +} + +func (t *Transaction) hashWithoutNonceDynamicFeeTx(origin thor.Address) *thor.Bytes32 { + b := thor.Blake2bFn(func(w io.Writer) { + rlp.Encode(w, []interface{}{ + t.body.chainTag(), + t.body.blockRef(), + t.body.expiration(), + t.body.clauses(), + t.body.maxFeePerGas(), + t.body.maxPriorityFeePerGas(), + t.body.dependsOn(), + t.body.nonce(), + t.body.reserved(), + origin, + }) + }) + return &b +} + // SigningHash returns hash of tx excludes signature. func (t *Transaction) SigningHash() (hash thor.Bytes32) { if cached := t.cache.signingHash.Load(); cached != nil { @@ -162,53 +224,51 @@ func (t *Transaction) SigningHash() (hash thor.Bytes32) { defer func() { t.cache.signingHash.Store(hash) }() return thor.Blake2bFn(func(w io.Writer) { - rlp.Encode(w, []interface{}{ - t.body.ChainTag, - t.body.BlockRef, - t.body.Expiration, - t.body.Clauses, - t.body.GasPriceCoef, - t.body.Gas, - t.body.DependsOn, - t.body.Nonce, - &t.body.Reserved, - }) + t.body.encode(w) }) } +// Gas returns gas provision for this tx. +func (t *Transaction) Gas() uint64 { + return t.body.gas() +} + // GasPriceCoef returns gas price coef. // gas price = bgp + bgp * gpc / 255. func (t *Transaction) GasPriceCoef() uint8 { - return t.body.GasPriceCoef + return t.body.gasPriceCoef() } -// Gas returns gas provision for this tx. -func (t *Transaction) Gas() uint64 { - return t.body.Gas +func (t *Transaction) MaxFeePerGas() *big.Int { + return t.body.maxFeePerGas() +} + +func (t *Transaction) MaxPriorityFeePerGas() *big.Int { + return t.body.maxPriorityFeePerGas() } // Clauses returns clauses in tx. func (t *Transaction) Clauses() []*Clause { - return append([]*Clause(nil), t.body.Clauses...) + return append([]*Clause(nil), t.body.clauses()...) } // DependsOn returns depended tx hash. func (t *Transaction) DependsOn() *thor.Bytes32 { - if t.body.DependsOn == nil { + if t.body.dependsOn() == nil { return nil } - cpy := *t.body.DependsOn + cpy := *t.body.dependsOn() return &cpy } // Signature returns signature. func (t *Transaction) Signature() []byte { - return append([]byte(nil), t.body.Signature...) + return append([]byte(nil), t.body.signature()...) } // Features returns features. func (t *Transaction) Features() Features { - return t.body.Reserved.Features + return t.body.reserved().Features } // Origin extract address of tx originator from signature. @@ -221,7 +281,7 @@ func (t *Transaction) Origin() (thor.Address, error) { return cached.(thor.Address), nil } - pub, err := crypto.SigToPub(t.SigningHash().Bytes(), t.body.Signature[:65]) + pub, err := crypto.SigToPub(t.SigningHash().Bytes(), t.body.signature()[:65]) if err != nil { return thor.Address{}, err } @@ -256,7 +316,7 @@ func (t *Transaction) Delegator() (*thor.Address, error) { return nil, err } - pub, err := crypto.SigToPub(t.DelegatorSigningHash(origin).Bytes(), t.body.Signature[65:]) + pub, err := crypto.SigToPub(t.DelegatorSigningHash(origin).Bytes(), t.body.signature()[65:]) if err != nil { return nil, err } @@ -271,17 +331,17 @@ func (t *Transaction) Delegator() (*thor.Address, error) { // For delegated tx, sig is joined with signatures of originator and delegator. func (t *Transaction) WithSignature(sig []byte) *Transaction { newTx := Transaction{ - body: t.body, + body: t.body.copy(), } // copy sig - newTx.body.Signature = append([]byte(nil), sig...) + newTx.body.setSignature(append([]byte(nil), sig...)) return &newTx } // TestFeatures test if the tx is compatible with given supported features. // An error returned if it is incompatible. func (t *Transaction) TestFeatures(supported Features) error { - r := &t.body.Reserved + r := t.body.reserved() if r.Features&supported != r.Features { return errors.New("unsupported features") } @@ -292,22 +352,115 @@ func (t *Transaction) TestFeatures(supported Features) error { return nil } +// encodeTyped writes the canonical encoding of a typed transaction to w. +func (t *Transaction) encodeTyped(w *bytes.Buffer) error { + w.WriteByte(t.Type()) + return rlp.Encode(w, t.body) +} + +// MarshalBinary returns the canonical encoding of the transaction. +// For legacy transactions, it returns the RLP encoding. For typed +// transactions, it returns the type the RLP encoding of the tx. +func (tx *Transaction) MarshalBinary() ([]byte, error) { + if tx.Type() == LegacyTxType { + return rlp.EncodeToBytes(tx.body) + } + var buf bytes.Buffer + err := tx.encodeTyped(&buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes the canonical encoding of transactions. +// It supports legacy RLP transactions and typed transactions. +func (t *Transaction) UnmarshalBinary(b []byte) error { + if len(b) > 0 && b[0] > 0x7f { + // It's a legacy transaction. + var data LegacyTransaction + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err + } + t.setDecoded(&data, len(b)) + return nil + } + // It's a typed transaction envelope. + inner, err := t.decodeTyped(b) + if err != nil { + return err + } + t.setDecoded(inner, len(b)) + return nil +} + // EncodeRLP implements rlp.Encoder func (t *Transaction) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &t.body) + if t.Type() == LegacyTxType { + return rlp.Encode(w, &t.body) + } + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + + if err := t.encodeTyped(buf); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) } // DecodeRLP implements rlp.Decoder func (t *Transaction) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - var body body - if err := s.Decode(&body); err != nil { + kind, size, err := s.Kind() + + switch { + case err != nil: return err + case kind == rlp.List: + // It's a legacy transaction. + var body LegacyTransaction + if err := s.Decode(&body); err != nil { + return err + } + *t = Transaction{body: &body} + + t.cache.size.Store(thor.StorageSize(rlp.ListSize(size))) + return nil + case kind == rlp.String: + // It's a typed TX. + var b []byte + if b, err = s.Bytes(); err != nil { + return err + } + inner, err := t.decodeTyped(b) + if err == nil { + t.setDecoded(inner, len(b)) + } + return err + default: + return rlp.ErrExpectedList + } +} + +// decodeTyped decodes a typed transaction from the canonical format. +func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { + if len(b) == 0 { + return nil, errEmptyTypedTx + } + switch b[0] { + case DynamicFeeTxType: + var body DynamicFeeTransaction + err := rlp.DecodeBytes(b[1:], &body) + return &body, err + default: + return nil, ErrTxTypeNotSupported } - *t = Transaction{body: body} +} - t.cache.size.Store(thor.StorageSize(rlp.ListSize(size))) - return nil +// setDecoded sets the inner transaction and size after decoding. +func (t *Transaction) setDecoded(body TxData, size int) { + t.body = body + if size > 0 { + t.cache.size.Store(thor.StorageSize(rlp.ListSize(uint64(size)))) + } } // Size returns size in bytes when RLP encoded. @@ -327,7 +480,7 @@ func (t *Transaction) IntrinsicGas() (uint64, error) { return cached.(uint64), nil } - gas, err := IntrinsicGas(t.body.Clauses...) + gas, err := IntrinsicGas(t.body.clauses()...) if err != nil { return 0, err } @@ -338,7 +491,7 @@ func (t *Transaction) IntrinsicGas() (uint64, error) { // GasPrice returns gas price. // gasPrice = baseGasPrice + baseGasPrice * gasPriceCoef / 255 func (t *Transaction) GasPrice(baseGasPrice *big.Int) *big.Int { - x := big.NewInt(int64(t.body.GasPriceCoef)) + x := new(big.Int).Set(t.body.maxFeePerGas()) x.Mul(x, baseGasPrice) x.Div(x, big.NewInt(math.MaxUint8)) return x.Add(x, baseGasPrice) @@ -381,13 +534,13 @@ func (t *Transaction) OverallGasPrice(baseGasPrice *big.Int, provedWork *big.Int if wgas == 0 { return gasPrice } - if wgas > t.body.Gas { - wgas = t.body.Gas + if wgas > t.body.gas() { + wgas = t.body.gas() } x := new(big.Int).SetUint64(wgas) x.Mul(x, baseGasPrice) - x.Div(x, new(big.Int).SetUint64(t.body.Gas)) + x.Div(x, new(big.Int).SetUint64(t.body.gas())) return x.Add(x, gasPrice) } @@ -405,16 +558,15 @@ func (t *Transaction) String() string { delegatorStr = delegator.String() } - binary.BigEndian.PutUint64(br[:], t.body.BlockRef) - if t.body.DependsOn != nil { - dependsOn = t.body.DependsOn.String() + binary.BigEndian.PutUint64(br[:], t.body.blockRef()) + if t.body.dependsOn() != nil { + dependsOn = t.body.dependsOn().String() } - return fmt.Sprintf(` + s := fmt.Sprintf(` Tx(%v, %v) Origin: %v Clauses: %v - GasPriceCoef: %v Gas: %v ChainTag: %v BlockRef: %v-%x @@ -424,8 +576,19 @@ func (t *Transaction) String() string { UnprovedWork: %v Delegator: %v Signature: 0x%x -`, t.ID(), t.Size(), originStr, t.body.Clauses, t.body.GasPriceCoef, t.body.Gas, - t.body.ChainTag, br.Number(), br[4:], t.body.Expiration, dependsOn, t.body.Nonce, t.UnprovedWork(), delegatorStr, t.body.Signature) +`, t.ID(), t.Size(), originStr, t.body.clauses(), t.body.gas(), + t.body.chainTag(), br.Number(), br[4:], t.body.expiration(), dependsOn, t.body.nonce(), t.UnprovedWork(), delegatorStr, t.body.signature()) + + if t.Type() == LegacyTxType { + return fmt.Sprintf(`%v + GasPriceCoef: %v + `, s, t.body.gasPriceCoef()) + } + + return fmt.Sprintf(`%v + MaxFeePerGas: %v + MaxPriorityFeePerGas: %v + `, s, t.body.maxFeePerGas(), t.body.maxPriorityFeePerGas()) } func (t *Transaction) validateSignatureLength() error { @@ -434,7 +597,7 @@ func (t *Transaction) validateSignatureLength() error { expectedSigLen *= 2 } - if len(t.body.Signature) != expectedSigLen { + if len(t.body.signature()) != expectedSigLen { return secp256k1.ErrInvalidSignatureLen } return nil diff --git a/tx/transaction_test.go b/tx/transaction_test.go index 0d364f2f4..36b8e94b1 100644 --- a/tx/transaction_test.go +++ b/tx/transaction_test.go @@ -17,14 +17,16 @@ import ( "github.com/vechain/thor/v2/tx" ) -func GetMockTx() tx.Transaction { +func GetMockTx(txType int) tx.Transaction { to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed") - trx := new(tx.Builder).ChainTag(1). + trx, _ := tx.NewTxBuilder(txType).ChainTag(1). BlockRef(tx.BlockRef{0, 0, 0, 0, 0xaa, 0xbb, 0xcc, 0xdd}). Expiration(32). Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). Clause(tx.NewClause(&to).WithValue(big.NewInt(20000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). GasPriceCoef(128). + MaxFeePerGas(big.NewInt(10000000)). + MaxPriorityFeePerGas(big.NewInt(20000)). Gas(21000). DependsOn(nil). Nonce(12345678).Build() @@ -33,55 +35,60 @@ func GetMockTx() tx.Transaction { } func TestIsExpired(t *testing.T) { - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) res := tx.IsExpired(10) assert.Equal(t, res, false) } -func TestHash(t *testing.T) { - tx := GetMockTx() - res := tx.Hash() - assert.Equal(t, res, thor.Bytes32{0x4b, 0xff, 0x70, 0x1, 0xfe, 0xc4, 0x2, 0x84, 0xd9, 0x3b, 0x4c, 0x45, 0x61, 0x7d, 0xc7, 0x41, 0xb9, 0xa8, 0x8e, 0xd5, 0x9d, 0xf, 0x1, 0xa3, 0x76, 0x39, 0x4c, 0x7b, 0xfe, 0xa6, 0xed, 0x24}) -} - func TestDependsOn(t *testing.T) { - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) res := tx.DependsOn() var expected *thor.Bytes32 assert.Equal(t, expected, res) } func TestTestFeatures(t *testing.T) { - txx := GetMockTx() - supportedFeatures := tx.Features(1) - res := txx.TestFeatures(supportedFeatures) + tx := GetMockTx(tx.LegacyTxType) + supportedFeatures := tx.Features() + res := tx.TestFeatures(supportedFeatures) assert.Equal(t, res, nil) } func TestToString(t *testing.T) { - tx := GetMockTx() // Ensure this mock transaction has all the necessary fields populated + // Legacy transaction + trx := GetMockTx(tx.LegacyTxType) // Ensure this mock transaction has all the necessary fields populated // Construct the expected string representation of the transaction // This should match the format used in the String() method of the Transaction struct // and should reflect the actual state of the mock transaction - expectedString := "\n\tTx(0x0000000000000000000000000000000000000000000000000000000000000000, 87 B)\n\tOrigin: N/A\n\tClauses: [\n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t10000\n\t\t Data:\t0x000000606060) \n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t20000\n\t\t Data:\t0x000000606060)]\n\tGasPriceCoef: 128\n\tGas: 21000\n\tChainTag: 1\n\tBlockRef: 0-aabbccdd\n\tExpiration: 32\n\tDependsOn: nil\n\tNonce: 12345678\n\tUnprovedWork: 0\n\tDelegator: N/A\n\tSignature: 0x\n" + expectedString := "\n\tTx(0x0000000000000000000000000000000000000000000000000000000000000000, 87 B)\n\tOrigin: N/A\n\tClauses: [\n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t10000\n\t\t Data:\t0x000000606060) \n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t20000\n\t\t Data:\t0x000000606060)]\n\tGas: 21000\n\tChainTag: 1\n\tBlockRef: 0-aabbccdd\n\tExpiration: 32\n\tDependsOn: nil\n\tNonce: 12345678\n\tUnprovedWork: 0\n\tDelegator: N/A\n\tSignature: 0x\n\n\t\tGasPriceCoef: 128\n\t\t" - res := tx.String() + res := trx.String() // Use assert.Equal to compare the actual result with the expected string assert.Equal(t, expectedString, res) + + // Dynamic fee transaction + trx = GetMockTx(tx.DynamicFeeTxType) + expectedString = "\n\tTx(0x0000000000000000000000000000000000000000000000000000000000000000, 95 B)\n\tOrigin: N/A\n\tClauses: [\n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t10000\n\t\t Data:\t0x000000606060) \n\t\t(To:\t0x7567d83b7b8d80addcb281a71d54fc7b3364ffed\n\t\t Value:\t20000\n\t\t Data:\t0x000000606060)]\n\tGas: 21000\n\tChainTag: 1\n\tBlockRef: 0-aabbccdd\n\tExpiration: 32\n\tDependsOn: nil\n\tNonce: 12345678\n\tUnprovedWork: 0\n\tDelegator: N/A\n\tSignature: 0x\n\n\t\tMaxFeePerGas: 10000000\n\t\tMaxPriorityFeePerGas: 20000\n\t\t" + res = trx.String() + assert.Equal(t, expectedString, res) } func TestTxSize(t *testing.T) { - tx := GetMockTx() + trx := GetMockTx(tx.LegacyTxType) - size := tx.Size() + size := trx.Size() assert.Equal(t, size, thor.StorageSize(87)) + + trx = GetMockTx(tx.DynamicFeeTxType) + size = trx.Size() + assert.Equal(t, size, thor.StorageSize(95)) } func TestProvedWork(t *testing.T) { // Mock the transaction - tx := GetMockTx() + trx := GetMockTx(tx.LegacyTxType) // Define a head block number headBlockNum := uint32(20) @@ -92,7 +99,7 @@ func TestProvedWork(t *testing.T) { } // Call ProvedWork - provedWork, err := tx.ProvedWork(headBlockNum, getBlockID) + provedWork, err := trx.ProvedWork(headBlockNum, getBlockID) // Check for errors assert.NoError(t, err) @@ -102,20 +109,20 @@ func TestProvedWork(t *testing.T) { } func TestChainTag(t *testing.T) { - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) res := tx.ChainTag() assert.Equal(t, res, uint8(0x1)) } func TestNonce(t *testing.T) { - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) res := tx.Nonce() assert.Equal(t, res, uint64(0xbc614e)) } func TestOverallGasPrice(t *testing.T) { // Mock or create a Transaction with necessary fields initialized - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) // Define test cases testCases := []struct { @@ -154,7 +161,7 @@ func TestOverallGasPrice(t *testing.T) { func TestEvaluateWork(t *testing.T) { origin := thor.BytesToAddress([]byte("origin")) - tx := GetMockTx() + tx := GetMockTx(tx.LegacyTxType) // Returns a function evaluate := tx.EvaluateWork(origin) @@ -169,9 +176,9 @@ func TestEvaluateWork(t *testing.T) { } } -func TestTx(t *testing.T) { +func TestLegacyTx(t *testing.T) { to, _ := thor.ParseAddress("0x7567d83b7b8d80addcb281a71d54fc7b3364ffed") - trx := new(tx.Builder).ChainTag(1). + trx, _ := tx.NewTxBuilder(tx.LegacyTxType).ChainTag(1). BlockRef(tx.BlockRef{0, 0, 0, 0, 0xaa, 0xbb, 0xcc, 0xdd}). Expiration(32). Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). @@ -184,7 +191,7 @@ func TestTx(t *testing.T) { assert.Equal(t, "0x2a1c25ce0d66f45276a5f308b99bf410e2fc7d5b6ea37a49f2ab9f1da9446478", trx.SigningHash().String()) assert.Equal(t, thor.Bytes32{}, trx.ID()) - assert.Equal(t, uint64(21000), func() uint64 { g, _ := new(tx.Builder).Build().IntrinsicGas(); return g }()) + assert.Equal(t, uint64(21000), func() uint64 { t, _ := tx.NewTxBuilder(tx.LegacyTxType).Build(); g, _ := t.IntrinsicGas(); return g }()) assert.Equal(t, uint64(37432), func() uint64 { g, _ := trx.IntrinsicGas(); return g }()) assert.Equal(t, big.NewInt(150), trx.GasPrice(big.NewInt(100))) @@ -216,7 +223,7 @@ func TestDelegatedTx(t *testing.T) { var feat tx.Features feat.SetDelegated(true) - trx := new(tx.Builder).ChainTag(0xa4). + trx, _ := tx.NewTxBuilder(tx.LegacyTxType).ChainTag(0xa4). BlockRef(tx.BlockRef{0, 0, 0, 0, 0xaa, 0xbb, 0xcc, 0xdd}). Expiration(32). Clause(tx.NewClause(&to).WithValue(big.NewInt(10000)).WithData([]byte{0, 0, 0, 0x60, 0x60, 0x60})). @@ -251,8 +258,8 @@ func TestDelegatedTx(t *testing.T) { ) raw, _ := hex.DecodeString("f8db81a484aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e20860000006060608180830334508083bc614ec101b882bad4d4401b1fb1c41d61727d7fd2aeb2bb3e65a27638a5326ca98404c0209ab159eaeb37f0ac75ed1ac44d92c3d17402d7d64b4c09664ae2698e1102448040c000f043fafeaf60343248a37e4f1d2743b4ab9116df6d627b4d8a874e4f48d3ae671c4e8d136eb87c544bea1763673a5f1762c2266364d1b22166d16e3872b5a9c700") - var newTx *tx.Transaction - if err := rlp.DecodeBytes(raw, &newTx); err != nil { + newTx := new(tx.Transaction) + if err := newTx.UnmarshalBinary(raw); err != nil { t.Error(err) } assert.Equal(t, true, newTx.Features().IsDelegated()) @@ -281,10 +288,10 @@ func TestIntrinsicGas(t *testing.T) { } func BenchmarkTxMining(b *testing.B) { - tx := new(tx.Builder).Build() + trx, _ := tx.NewTxBuilder(tx.LegacyTxType).Build() signer := thor.BytesToAddress([]byte("acc1")) maxWork := &big.Int{} - eval := tx.EvaluateWork(signer) + eval := trx.EvaluateWork(signer) for i := 0; i < b.N; i++ { work := eval(uint64(i)) if work.Cmp(maxWork) > 0 { diff --git a/tx/transactions_test.go b/tx/transactions_test.go index 05a1c4858..728a4bdf0 100644 --- a/tx/transactions_test.go +++ b/tx/transactions_test.go @@ -16,7 +16,7 @@ import ( func MockTransactions(n int) tx.Transactions { txs := make(tx.Transactions, n) for i := range txs { - mockTx := GetMockTx() + mockTx := GetMockTx(tx.LegacyTxType) txs[i] = &mockTx } return txs diff --git a/tx/tx_dynamic_fee.go b/tx/tx_dynamic_fee.go new file mode 100644 index 000000000..40501df6e --- /dev/null +++ b/tx/tx_dynamic_fee.go @@ -0,0 +1,127 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package tx + +import ( + "io" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/vechain/thor/v2/thor" +) + +type DynamicFeeTransaction struct { + ChainTag byte + BlockRef uint64 + Expiration uint32 + Clauses []*Clause + Gas uint64 + MaxFeePerGas *big.Int + MaxPriorityFeePerGas *big.Int + DependsOn *thor.Bytes32 `rlp:"nil"` + Nonce uint64 + Reserved reserved + Signature []byte +} + +func (t *DynamicFeeTransaction) txType() byte { + return DynamicFeeTxType +} + +func (t *DynamicFeeTransaction) copy() TxData { + cpy := &DynamicFeeTransaction{ + ChainTag: t.ChainTag, + BlockRef: t.BlockRef, + Expiration: t.Expiration, + Clauses: make([]*Clause, len(t.Clauses)), + Gas: t.Gas, + MaxFeePerGas: new(big.Int), + MaxPriorityFeePerGas: new(big.Int), + DependsOn: t.DependsOn, + Nonce: t.Nonce, + Reserved: t.Reserved, + Signature: t.Signature, + } + copy(cpy.Clauses, t.Clauses) + if t.MaxFeePerGas != nil { + cpy.MaxFeePerGas.Set(t.MaxFeePerGas) + } + if t.MaxPriorityFeePerGas != nil { + cpy.MaxPriorityFeePerGas.Set(t.MaxPriorityFeePerGas) + } + return cpy +} + +func (t *DynamicFeeTransaction) chainTag() byte { + return t.ChainTag +} + +func (t *DynamicFeeTransaction) blockRef() uint64 { + return t.BlockRef +} + +func (t *DynamicFeeTransaction) expiration() uint32 { + return t.Expiration +} + +func (t *DynamicFeeTransaction) clauses() []*Clause { + return t.Clauses +} + +func (t *DynamicFeeTransaction) gas() uint64 { + return t.Gas +} + +func (t *DynamicFeeTransaction) gasPriceCoef() uint8 { + if t.MaxFeePerGas.Cmp(big.NewInt(math.MaxUint8)) > 0 { + return math.MaxUint8 + } + return uint8(t.MaxFeePerGas.Uint64()) +} + +func (t *DynamicFeeTransaction) maxFeePerGas() *big.Int { + return t.MaxFeePerGas +} + +func (t *DynamicFeeTransaction) maxPriorityFeePerGas() *big.Int { + return t.MaxPriorityFeePerGas +} + +func (t *DynamicFeeTransaction) dependsOn() *thor.Bytes32 { + return t.DependsOn +} + +func (t *DynamicFeeTransaction) nonce() uint64 { + return t.Nonce +} + +func (t *DynamicFeeTransaction) reserved() reserved { + return t.Reserved +} + +func (t *DynamicFeeTransaction) signature() []byte { + return t.Signature +} + +func (t *DynamicFeeTransaction) setSignature(sig []byte) { + t.Signature = sig +} + +func (t *DynamicFeeTransaction) encode(w io.Writer) error { + return rlp.Encode(w, []interface{}{ + t.ChainTag, + t.BlockRef, + t.Expiration, + t.Clauses, + t.Gas, + t.MaxFeePerGas, + t.MaxPriorityFeePerGas, + t.DependsOn, + t.Nonce, + &t.Reserved, + }) +} diff --git a/tx/tx_legacy.go b/tx/tx_legacy.go new file mode 100644 index 000000000..9b178448c --- /dev/null +++ b/tx/tx_legacy.go @@ -0,0 +1,116 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package tx + +import ( + "io" + "math/big" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/vechain/thor/v2/thor" +) + +type LegacyTransaction struct { + ChainTag byte + BlockRef uint64 + Expiration uint32 + Clauses []*Clause + GasPriceCoef uint8 + Gas uint64 + DependsOn *thor.Bytes32 `rlp:"nil"` + Nonce uint64 + Reserved reserved + Signature []byte +} + +func (t *LegacyTransaction) txType() byte { + return LegacyTxType +} + +func (t *LegacyTransaction) copy() TxData { + cpy := &LegacyTransaction{ + ChainTag: t.ChainTag, + BlockRef: t.BlockRef, + Expiration: t.Expiration, + Clauses: make([]*Clause, len(t.Clauses)), + GasPriceCoef: t.GasPriceCoef, + Gas: t.Gas, + DependsOn: t.DependsOn, + Nonce: t.Nonce, + Reserved: t.Reserved, + Signature: t.Signature, + } + copy(cpy.Clauses, t.Clauses) + return cpy +} + +func (t *LegacyTransaction) chainTag() byte { + return t.ChainTag +} + +func (t *LegacyTransaction) blockRef() uint64 { + return t.BlockRef +} + +func (t *LegacyTransaction) expiration() uint32 { + return t.Expiration +} + +func (t *LegacyTransaction) clauses() []*Clause { + return t.Clauses +} + +func (t *LegacyTransaction) gas() uint64 { + return t.Gas +} + +func (t *LegacyTransaction) gasPriceCoef() uint8 { + return t.GasPriceCoef +} + +func (t *LegacyTransaction) maxFeePerGas() *big.Int { + // For legacy transactions, maxFeePerGas is determined by GasPriceCoef + return new(big.Int).SetUint64(uint64(t.GasPriceCoef)) +} + +func (t *LegacyTransaction) maxPriorityFeePerGas() *big.Int { + // For legacy transactions, maxPriorityFeePerGas is determined by GasPriceCoef + return new(big.Int).SetUint64(uint64(t.GasPriceCoef)) +} + +func (t *LegacyTransaction) dependsOn() *thor.Bytes32 { + return t.DependsOn +} + +func (t *LegacyTransaction) nonce() uint64 { + return t.Nonce +} + +func (t *LegacyTransaction) reserved() reserved { + return t.Reserved +} + +func (t *LegacyTransaction) signature() []byte { + return t.Signature +} + +func (t *LegacyTransaction) setSignature(sig []byte) { + t.Signature = sig +} + +func (t *LegacyTransaction) encode(w io.Writer) error { + return rlp.Encode(w, []interface{}{ + t.ChainTag, + t.BlockRef, + t.Expiration, + t.Clauses, + t.GasPriceCoef, + t.Gas, + t.DependsOn, + t.Nonce, + &t.Reserved, + }) +} diff --git a/txpool/tx_object_map_test.go b/txpool/tx_object_map_test.go index 9a0b38629..c1cf02bfd 100644 --- a/txpool/tx_object_map_test.go +++ b/txpool/tx_object_map_test.go @@ -23,17 +23,20 @@ func TestGetByID(t *testing.T) { repo := newChainRepo(db) // Creating transactions - tx1 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx2 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[1]) + tx1 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx2 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[1]) + tx3 := newTx(tx.DynamicFeeTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[2]) // Resolving transactions into txObjects txObj1, _ := resolveTx(tx1, false) txObj2, _ := resolveTx(tx2, false) + txObj3, _ := resolveTx(tx3, false) // Creating a new txObjectMap and adding transactions m := newTxObjectMap() assert.Nil(t, m.Add(txObj1, 1, func(_ thor.Address, _ *big.Int) error { return nil })) assert.Nil(t, m.Add(txObj2, 1, func(_ thor.Address, _ *big.Int) error { return nil })) + assert.Nil(t, m.Add(txObj3, 1, func(_ thor.Address, _ *big.Int) error { return nil })) // Testing GetByID retrievedTxObj1 := m.GetByID(txObj1.ID()) @@ -42,10 +45,13 @@ func TestGetByID(t *testing.T) { retrievedTxObj2 := m.GetByID(txObj2.ID()) assert.Equal(t, txObj2, retrievedTxObj2, "The retrieved transaction object should match the original for tx2") + retrievedTxObj3 := m.GetByID(txObj3.ID()) + assert.Equal(t, txObj3, retrievedTxObj3, "The retrieved transaction object should match the original for tx3") + // Testing retrieval of a non-existing transaction nonExistingTxID := thor.Bytes32{} // An arbitrary non-existing ID - retrievedTxObj3 := m.GetByID(nonExistingTxID) - assert.Nil(t, retrievedTxObj3, "Retrieving a non-existing transaction should return nil") + retrievedNonExistingTxObj3 := m.GetByID(nonExistingTxID) + assert.Nil(t, retrievedNonExistingTxObj3, "Retrieving a non-existing transaction should return nil") } func TestFill(t *testing.T) { @@ -53,42 +59,48 @@ func TestFill(t *testing.T) { repo := newChainRepo(db) // Creating transactions - tx1 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx2 := newDelegatedTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) + tx1 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx2 := newDelegatedTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) + tx3 := newDelegatedTx(tx.DynamicFeeTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[3], genesis.DevAccounts()[4]) // Resolving transactions into txObjects txObj1, _ := resolveTx(tx1, false) txObj2, _ := resolveTx(tx2, false) + txObj3, _ := resolveTx(tx3, false) // Creating a new txObjectMap m := newTxObjectMap() // Filling the map with transactions - m.Fill([]*txObject{txObj1, txObj2, txObj1}) + m.Fill([]*txObject{txObj1, txObj2, txObj1, txObj3}) // Asserting the length of the map - assert.Equal(t, 2, m.Len(), "Map should contain only 2 unique transactions") + assert.Equal(t, 3, m.Len(), "Map should contain only 2 unique transactions") // Asserting the transactions are correctly added assert.True(t, m.ContainsHash(txObj1.Hash()), "Map should contain txObj1") assert.True(t, m.ContainsHash(txObj2.Hash()), "Map should contain txObj2") + assert.True(t, m.ContainsHash(txObj3.Hash()), "Map should contain txObj3") // Asserting duplicate handling assert.Equal(t, m.GetByID(txObj1.ID()), txObj1, "Duplicate tx1 should not be added again") assert.Equal(t, m.GetByID(txObj2.ID()), txObj2, "txObj2 should be retrievable by ID") + assert.Equal(t, m.GetByID(txObj3.ID()), txObj3, "txObj3 should be retrievable by ID") assert.Equal(t, 1, m.quota[genesis.DevAccounts()[0].Address], "Account quota should be 1 for account 0") assert.Equal(t, 1, m.quota[genesis.DevAccounts()[1].Address], "Account quota should be 1 for account 1") assert.Equal(t, 1, m.quota[genesis.DevAccounts()[2].Address], "Delegator quota should be 1 for account 2") + assert.Equal(t, 1, m.quota[genesis.DevAccounts()[3].Address], "Account quota should be 1 for account 3") + assert.Equal(t, 1, m.quota[genesis.DevAccounts()[4].Address], "Delegator quota should be 1 for account 4") } func TestTxObjMap(t *testing.T) { db := muxdb.NewMem() repo := newChainRepo(db) - tx1 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx2 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx3 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[1]) + tx1 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx2 := newTx(tx.DynamicFeeTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx3 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[1]) txObj1, _ := resolveTx(tx1, false) txObj2, _ := resolveTx(tx2, false) @@ -123,9 +135,9 @@ func TestLimitByDelegator(t *testing.T) { db := muxdb.NewMem() repo := newChainRepo(db) - tx1 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx2 := newDelegatedTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[0], genesis.DevAccounts()[1]) - tx3 := newDelegatedTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[2], genesis.DevAccounts()[1]) + tx1 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx2 := newDelegatedTx(tx.DynamicFeeTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[0], genesis.DevAccounts()[1]) + tx3 := newDelegatedTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[2], genesis.DevAccounts()[1]) txObj1, _ := resolveTx(tx1, false) txObj2, _ := resolveTx(tx2, false) @@ -147,9 +159,9 @@ func TestPendingCost(t *testing.T) { stater := state.NewStater(db) // Creating transactions - tx1 := newTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) - tx2 := newDelegatedTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) - tx3 := newDelegatedTx(repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) + tx1 := newTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0]) + tx2 := newDelegatedTx(tx.LegacyTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) + tx3 := newDelegatedTx(tx.DynamicFeeTxType, repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, genesis.DevAccounts()[1], genesis.DevAccounts()[2]) // Resolving transactions into txObjects txObj1, _ := resolveTx(tx1, false) diff --git a/txpool/tx_object_test.go b/txpool/tx_object_test.go index 8358f1a6d..08c0de2a7 100644 --- a/txpool/tx_object_test.go +++ b/txpool/tx_object_test.go @@ -28,39 +28,16 @@ func newChainRepo(db *muxdb.MuxDB) *chain.Repository { return repo } -func newTx(chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx.BlockRef, expiration uint32, dependsOn *thor.Bytes32, features tx.Features, from genesis.DevAccount) *tx.Transaction { - builder := new(tx.Builder).ChainTag(chainTag) - for _, c := range clauses { - builder.Clause(c) - } - - return tx.MustSign(builder.BlockRef(blockRef). - Expiration(expiration). - Nonce(rand.Uint64()). //#nosec G404 - DependsOn(dependsOn). - Features(features). - Gas(gas). - Build(), - from.PrivateKey, - ) +func newTx(txType int, chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx.BlockRef, expiration uint32, dependsOn *thor.Bytes32, features tx.Features, from genesis.DevAccount) *tx.Transaction { + trx, _ := txBuilder(txType, chainTag, clauses, gas, blockRef, expiration, dependsOn, features).Build() + return tx.MustSign(trx, from.PrivateKey) } -func newDelegatedTx(chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx.BlockRef, expiration uint32, dependsOn *thor.Bytes32, from genesis.DevAccount, delegator genesis.DevAccount) *tx.Transaction { - builder := new(tx.Builder).ChainTag(chainTag) - for _, c := range clauses { - builder.Clause(c) - } - +func newDelegatedTx(txType int, chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx.BlockRef, expiration uint32, dependsOn *thor.Bytes32, from genesis.DevAccount, delegator genesis.DevAccount) *tx.Transaction { var features tx.Features features.SetDelegated(true) - trx := builder.BlockRef(blockRef). - Expiration(expiration). - Nonce(rand.Uint64()). //#nosec G404 - DependsOn(dependsOn). - Features(features). - Gas(gas). - Build() + trx, _ := txBuilder(txType, chainTag, clauses, gas, blockRef, expiration, dependsOn, features).Build() trx = tx.MustSignDelegated( trx, @@ -71,6 +48,20 @@ func newDelegatedTx(chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx return trx } +func txBuilder(txType int, chainTag byte, clauses []*tx.Clause, gas uint64, blockRef tx.BlockRef, expiration uint32, dependsOn *thor.Bytes32, features tx.Features) *tx.Builder { + builder := tx.NewTxBuilder(txType).ChainTag(chainTag) + for _, c := range clauses { + builder.Clause(c) + } + + return builder.BlockRef(blockRef). + Expiration(expiration). + Nonce(rand.Uint64()). //#nosec G404 + DependsOn(dependsOn). + Features(features). + Gas(gas) +} + func SetupTest() (genesis.DevAccount, *chain.Repository, *block.Block, *state.State) { acc := genesis.DevAccounts()[0] @@ -92,7 +83,8 @@ func TestExecutableWithError(t *testing.T) { expected bool expectedErr string }{ - {newTx(0, nil, 21000, tx.BlockRef{0}, 100, nil, tx.Features(0), acc), false, ""}, + {newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{0}, 100, nil, tx.Features(0), acc), false, ""}, + {newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{0}, 100, nil, tx.Features(0), acc), false, ""}, } for _, tt := range tests { @@ -127,13 +119,19 @@ func TestSort(t *testing.T) { func TestResolve(t *testing.T) { acc := genesis.DevAccounts()[0] - tx := newTx(0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) + trx := newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) - txObj, err := resolveTx(tx, false) + txObj, err := resolveTx(trx, false) assert.Nil(t, err) - assert.Equal(t, tx, txObj.Transaction) + assert.Equal(t, trx, txObj.Transaction) assert.Equal(t, acc.Address, txObj.Origin()) + + trx = newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) + txObj, err = resolveTx(trx, false) + assert.Nil(t, err) + assert.Equal(t, trx, txObj.Transaction) + assert.Equal(t, acc.Address, txObj.Origin()) } func TestExecutable(t *testing.T) { @@ -144,11 +142,16 @@ func TestExecutable(t *testing.T) { expected bool expectedErr string }{ - {newTx(0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), true, ""}, - {newTx(0, nil, math.MaxUint64, tx.BlockRef{}, 100, nil, tx.Features(0), acc), false, "gas too large"}, - {newTx(0, nil, 21000, tx.BlockRef{1}, 100, nil, tx.Features(0), acc), true, "block ref out of schedule"}, - {newTx(0, nil, 21000, tx.BlockRef{0}, 0, nil, tx.Features(0), acc), true, "expired"}, - {newTx(0, nil, 21000, tx.BlockRef{0}, 100, &thor.Bytes32{}, tx.Features(0), acc), false, ""}, + {newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), true, ""}, + {newTx(tx.LegacyTxType, 0, nil, math.MaxUint64, tx.BlockRef{}, 100, nil, tx.Features(0), acc), false, "gas too large"}, + {newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{1}, 100, nil, tx.Features(0), acc), true, "block ref out of schedule"}, + {newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{0}, 0, nil, tx.Features(0), acc), true, "expired"}, + {newTx(tx.LegacyTxType, 0, nil, 21000, tx.BlockRef{0}, 100, &thor.Bytes32{}, tx.Features(0), acc), false, ""}, + {newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), true, ""}, + {newTx(tx.DynamicFeeTxType, 0, nil, math.MaxUint64, tx.BlockRef{}, 100, nil, tx.Features(0), acc), false, "gas too large"}, + {newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{1}, 100, nil, tx.Features(0), acc), true, "block ref out of schedule"}, + {newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{0}, 0, nil, tx.Features(0), acc), true, "expired"}, + {newTx(tx.DynamicFeeTxType, 0, nil, 21000, tx.BlockRef{0}, 100, &thor.Bytes32{}, tx.Features(0), acc), false, ""}, } for _, tt := range tests { diff --git a/txpool/tx_pool_test.go b/txpool/tx_pool_test.go index 73fe7db0a..ee6fe2374 100644 --- a/txpool/tx_pool_test.go +++ b/txpool/tx_pool_test.go @@ -17,7 +17,6 @@ import ( "time" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/builtin" @@ -93,9 +92,9 @@ func TestNewCloseWithServer(t *testing.T) { defer pool.Close() // Create a slice of transactions to be added to the pool. - txs := make(Tx.Transactions, 0, 15) + txs := make(Tx.Transactions, 0, 30) for i := 0; i < 15; i++ { - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + tx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) txs = append(txs, tx) } @@ -105,33 +104,80 @@ func TestNewCloseWithServer(t *testing.T) { time.Sleep(1 * time.Second) } -func FillPoolWithTxs(pool *TxPool, t *testing.T) { +func FillPoolWithLegacyTxs(pool *TxPool, t *testing.T) { + // Create a slice of transactions to be added to the pool. + txs := make(Tx.Transactions, 0, 15) + for i := 0; i < 12; i++ { + tx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + txs = append(txs, tx) + } + + // Call the Fill method + pool.Fill(txs) + + err := pool.Add(newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), devAccounts[0])) + assert.Equal(t, err.Error(), "tx rejected: pool is full") +} + +func FillPoolWithDynFeeTxs(pool *TxPool, t *testing.T) { // Create a slice of transactions to be added to the pool. txs := make(Tx.Transactions, 0, 15) for i := 0; i < 12; i++ { - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + tx := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) txs = append(txs, tx) } // Call the Fill method pool.Fill(txs) - err := pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), devAccounts[0])) + err := pool.Add(newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), devAccounts[0])) + assert.Equal(t, err.Error(), "tx rejected: pool is full") +} + +func FillPoolWithMixedTxs(pool *TxPool, t *testing.T) { + // Create a slice of transactions to be added to the pool. + txs := make(Tx.Transactions, 0, 15) + for i := 0; i < 6; i++ { + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + txs = append(txs, trx) + trx = newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + txs = append(txs, trx) + } + + // Call the Fill method + pool.Fill(txs) + + err := pool.Add(newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), devAccounts[0])) assert.Equal(t, err.Error(), "tx rejected: pool is full") } func TestAddWithFullErrorUnsyncedChain(t *testing.T) { + // First fill the pool with legacy transactions pool := newPool(LIMIT, LIMIT_PER_ACCOUNT) defer pool.Close() - FillPoolWithTxs(pool, t) + FillPoolWithLegacyTxs(pool, t) + + // Now fill the pool with dynamic fee transactions + pool = newPool(LIMIT, LIMIT_PER_ACCOUNT) + FillPoolWithDynFeeTxs(pool, t) + + // Now fill the pool with mixed transactions + pool = newPool(LIMIT, LIMIT_PER_ACCOUNT) + FillPoolWithMixedTxs(pool, t) } func TestAddWithFullErrorSyncedChain(t *testing.T) { pool := newPoolWithParams(LIMIT, LIMIT_PER_ACCOUNT, "./", "", uint64(time.Now().Unix())) defer pool.Close() - FillPoolWithTxs(pool, t) + FillPoolWithLegacyTxs(pool, t) + + pool = newPoolWithParams(LIMIT, LIMIT_PER_ACCOUNT, "./", "", uint64(time.Now().Unix())) + FillPoolWithDynFeeTxs(pool, t) + + pool = newPoolWithParams(LIMIT, LIMIT_PER_ACCOUNT, "./", "", uint64(time.Now().Unix())) + FillPoolWithMixedTxs(pool, t) } func TestNewCloseWithError(t *testing.T) { @@ -145,11 +191,15 @@ func TestDump(t *testing.T) { defer pool.Close() // Create and add transactions to the pool - txsToAdd := make(tx.Transactions, 0, 5) + txsToAdd := make(tx.Transactions, 0, 10) for i := 0; i < 5; i++ { - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) - txsToAdd = append(txsToAdd, tx) - assert.Nil(t, pool.Add(tx)) + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + txsToAdd = append(txsToAdd, trx) + assert.Nil(t, pool.Add(trx)) + + trx = newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + txsToAdd = append(txsToAdd, trx) + assert.Nil(t, pool.Add(trx)) } // Use the Dump method to retrieve all transactions in the pool @@ -175,19 +225,33 @@ func TestRemove(t *testing.T) { pool := newPool(LIMIT, LIMIT_PER_ACCOUNT) defer pool.Close() - // Create and add a transaction to the pool - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - assert.Nil(t, pool.Add(tx), "Adding transaction should not produce error") + // Create and add a legacy transaction to the pool + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + assert.Nil(t, pool.Add(trx), "Adding transaction should not produce error") // Ensure the transaction is in the pool - assert.NotNil(t, pool.Get(tx.ID()), "Transaction should exist in the pool before removal") + assert.NotNil(t, pool.Get(trx.ID()), "Transaction should exist in the pool before removal") // Remove the transaction from the pool - removed := pool.Remove(tx.Hash(), tx.ID()) + removed := pool.Remove(trx.Hash(), trx.ID()) assert.True(t, removed, "Transaction should be successfully removed") // Check that the transaction is no longer in the pool - assert.Nil(t, pool.Get(tx.ID()), "Transaction should not exist in the pool after removal") + assert.Nil(t, pool.Get(trx.ID()), "Transaction should not exist in the pool after removal") + + // Create and add a dyn fee transaction to the pool + trx = newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + assert.Nil(t, pool.Add(trx), "Adding transaction should not produce error") + + // Ensure the transaction is in the pool + assert.NotNil(t, pool.Get(trx.ID()), "Transaction should exist in the pool before removal") + + // Remove the transaction from the pool + removed = pool.Remove(trx.Hash(), trx.ID()) + assert.True(t, removed, "Transaction should be successfully removed") + + // Check that the transaction is no longer in the pool + assert.Nil(t, pool.Get(trx.ID()), "Transaction should not exist in the pool after removal") } func TestRemoveWithError(t *testing.T) { @@ -195,7 +259,7 @@ func TestRemoveWithError(t *testing.T) { defer pool.Close() // Create and add a transaction to the pool - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + tx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) // assert.Nil(t, pool.Add(tx), "Adding transaction should not produce error") // Ensure the transaction is in the pool @@ -238,7 +302,41 @@ func TestSubscribeNewTx(t *testing.T) { pool.SubscribeTxEvent(txCh) - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + tx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + assert.Nil(t, pool.Add(tx)) + + v := true + assert.Equal(t, &TxEvent{tx, &v}, <-txCh) +} + +func TestSubscribeNewTypedTx(t *testing.T) { + pool := newPool(LIMIT, LIMIT_PER_ACCOUNT) + defer pool.Close() + + st := pool.stater.NewState(pool.repo.GenesisBlock().Header().StateRoot(), 0, 0, 0) + stage, _ := st.Stage(1, 0) + root1, _ := stage.Commit() + + var sig [65]byte + rand.Read(sig[:]) + + b1 := new(block.Builder). + ParentID(pool.repo.GenesisBlock().Header().ID()). + Timestamp(uint64(time.Now().Unix())). + TotalScore(100). + GasLimit(10000000). + StateRoot(root1). + Build().WithSignature(sig[:]) + if err := pool.repo.AddBlock(b1, nil, 0); err != nil { + t.Fatal(err) + } + pool.repo.SetBestBlockID(b1.Header().ID()) + + txCh := make(chan *TxEvent) + + pool.SubscribeTxEvent(txCh) + + tx := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) assert.Nil(t, pool.Add(tx)) v := true @@ -254,7 +352,7 @@ func TestWashTxs(t *testing.T) { assert.Zero(t, len(txs)) assert.Zero(t, len(pool.Executables())) - tx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + tx1 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) assert.Nil(t, pool.AddLocal(tx1)) // this tx won't participate in the wash out. txs, _, err = pool.wash(pool.repo.BestBlockSummary()) @@ -277,11 +375,11 @@ func TestWashTxs(t *testing.T) { assert.Nil(t, err) assert.Equal(t, Tx.Transactions{tx1}, txs) - tx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[1]) + tx2 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[1]) txObj2, _ := resolveTx(tx2, false) assert.Nil(t, pool.all.Add(txObj2, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil })) // this tx will participate in the wash out. - tx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[2]) + tx3 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[2]) txObj3, _ := resolveTx(tx3, false) assert.Nil(t, pool.all.Add(txObj3, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil })) // this tx will participate in the wash out. @@ -298,7 +396,7 @@ func TestFillPool(t *testing.T) { // Create a slice of transactions to be added to the pool. txs := make(Tx.Transactions, 0, 5) for i := 0; i < 5; i++ { - tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + tx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) txs = append(txs, tx) } @@ -321,6 +419,38 @@ func TestFillPool(t *testing.T) { assert.Equal(t, len(txs), len(pool.Executables()), "Number of transactions in the pool should match the number added") } +func TestFillPoolWithLegacyAndTypedTxs(t *testing.T) { + pool := newPool(LIMIT, LIMIT_PER_ACCOUNT) + defer pool.Close() + + // Create a slice of transactions to be added to the pool. + txs := make(Tx.Transactions, 0, 10) + for i := 0; i < 5; i++ { + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + txs = append(txs, trx) + trx = newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)]) + txs = append(txs, trx) + } + + // Call the Fill method + pool.Fill(txs) + + // Check if the transactions are correctly added. + // This might require accessing internal state of TxPool or using provided methods. + for _, tx := range txs { + assert.NotNil(t, pool.Get(tx.ID()), "Transaction should exist in the pool") + } + + // Further checks can be made based on the behavior of your TxPool implementation. + // For example, checking if the pool size has increased by the expected amount. + assert.Equal(t, len(txs), pool.all.Len(), "Number of transactions in the pool should match the number added") + + // Test executables after wash + executables, _, _ := pool.wash(pool.repo.BestBlockSummary()) + pool.executables.Store(executables) + assert.Equal(t, len(txs), len(pool.Executables()), "Number of transactions in the pool should match the number added") +} + func TestAdd(t *testing.T) { pool := newPool(LIMIT, LIMIT_PER_ACCOUNT) defer pool.Close() @@ -341,13 +471,14 @@ func TestAdd(t *testing.T) { pool.repo.SetBestBlockID(b1.Header().ID()) acc := devAccounts[0] - dupTx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) + dupTx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) tests := []struct { tx *tx.Transaction errStr string }{ - {newTx(pool.repo.ChainTag()+1, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), "bad tx: chain tag mismatch"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag()+1, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), "bad tx: chain tag mismatch"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag()+1, nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc), "bad tx: chain tag mismatch"}, {dupTx, ""}, {dupTx, ""}, } @@ -364,8 +495,8 @@ func TestAdd(t *testing.T) { raw, _ := hex.DecodeString(fmt.Sprintf("f8dc81%v84aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e20860000006060608180830334508083bc614ec20108b88256e32450c1907f627d2c11fe5a9d0216be1712f4938b5feb04e37edef236c56266c3378acf97994beff22698b70023f486645d29cb23b479a7b044f7c6b104d2000584fcb3964446d4d832dcc849e2d76ea7e04a4ebdc3a4b61e7997e93277363d4e7fe9315e7f6dd8d9c0a8bff5879503f5c04adab8b08772499e74d34f67923501", hex.EncodeToString([]byte{pool.repo.ChainTag()}), )) - var badReserved *Tx.Transaction - if err := rlp.DecodeBytes(raw, &badReserved); err != nil { + badReserved := new(tx.Transaction) + if err := badReserved.UnmarshalBinary(raw); err != nil { t.Error(err) } @@ -376,12 +507,17 @@ func TestAdd(t *testing.T) { tx *Tx.Transaction errStr string }{ - {newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), acc), "tx rejected: tx is not executable"}, - {newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(100), 100, nil, Tx.Features(0), acc), "tx rejected: block ref out of schedule"}, - {newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: tx is not executable"}, - {newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(2), acc), "tx rejected: unsupported features"}, - {newTx(pool.repo.ChainTag(), []*tx.Clause{tx.NewClause(nil).WithData(data[:])}, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: size too large"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), acc), "tx rejected: tx is not executable"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(100), 100, nil, Tx.Features(0), acc), "tx rejected: block ref out of schedule"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: tx is not executable"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(2), acc), "tx rejected: unsupported features"}, + {newTx(tx.LegacyTxType, pool.repo.ChainTag(), []*tx.Clause{tx.NewClause(nil).WithData(data[:])}, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: size too large"}, {badReserved, "tx rejected: unsupported features"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), acc), "tx rejected: tx is not executable"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(100), 100, nil, Tx.Features(0), acc), "tx rejected: block ref out of schedule"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: tx is not executable"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(2), acc), "tx rejected: unsupported features"}, + {newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), []*tx.Clause{tx.NewClause(nil).WithData(data[:])}, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: size too large"}, } for _, tt := range tests { @@ -409,7 +545,7 @@ func TestBeforeVIP191Add(t *testing.T) { }) defer pool.Close() - err := pool.StrictlyAdd(newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(200), 100, nil, Tx.Features(1), acc)) + err := pool.StrictlyAdd(newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(200), 100, nil, Tx.Features(1), acc)) assert.Equal(t, "tx rejected: unsupported features", err.Error()) } @@ -419,8 +555,8 @@ func TestPoolLimit(t *testing.T) { pool := newPoolWithParams(2, 1, "", "", uint64(time.Now().Unix())) defer pool.Close() - trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx1 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) pool.add(trx1, false, false) err := pool.add(trx2, false, false) @@ -429,8 +565,8 @@ func TestPoolLimit(t *testing.T) { // not synced pool = newPool(2, 1) - trx1 = newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - trx2 = newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx1 = newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 = newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) pool.add(trx1, false, false) err = pool.add(trx2, false, false) assert.Equal(t, "tx rejected: account quota exceeded", err.Error()) @@ -449,7 +585,7 @@ func TestBlocked(t *testing.T) { <-time.After(10 * time.Millisecond) // adding blocked should return nil - trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1]) + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1]) err = pool.Add(trx) assert.Nil(t, err) @@ -475,7 +611,20 @@ func TestWash(t *testing.T) { }{ { "MaxLife", func(t *testing.T) { - trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1]) + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1]) + pool.add(trx, false, false) + + txObj := pool.all.mapByID[trx.ID()] + txObj.timeAdded = txObj.timeAdded - int64(pool.options.MaxLifetime)*2 + + pool.wash(pool.repo.BestBlockSummary()) + got := pool.Get(trx.ID()) + assert.Nil(t, got) + }, + }, + { + "MaxLife with dynFeeTx", func(t *testing.T) { + trx := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1]) pool.add(trx, false, false) txObj := pool.all.mapByID[trx.ID()] @@ -496,7 +645,28 @@ func TestWash(t *testing.T) { PrivateKey: priv, } - trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) + trx := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) + + txObj, err := resolveTx(trx, false) + assert.Nil(t, err) + pool.all.Add(txObj, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil }) + + pool.wash(pool.repo.BestBlockSummary()) + got := pool.Get(trx.ID()) + assert.Nil(t, got) + }, + }, + { + "Not enough VTHO with dynFeeTx", func(t *testing.T) { + priv, err := crypto.GenerateKey() + assert.Nil(t, err) + + acc := genesis.DevAccount{ + Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)), + PrivateKey: priv, + } + + trx := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc) txObj, err := resolveTx(trx, false) assert.Nil(t, err) @@ -520,9 +690,40 @@ func TestWash(t *testing.T) { PrivateKey: priv, } - trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - trx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) + trx1 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx3 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) + pool.add(trx1, false, false) + + txObj, err := resolveTx(trx2, false) + assert.Nil(t, err) + pool.all.Add(txObj, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil }) + + txObj, err = resolveTx(trx3, false) + assert.Nil(t, err) + pool.all.Add(txObj, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil }) + + pool.wash(pool.repo.BestBlockSummary()) + got := pool.Get(trx3.ID()) + assert.Nil(t, got) + }, + }, + { + "Future tx with dynFeeTx", func(t *testing.T) { + pool := newPool(1, LIMIT_PER_ACCOUNT) + defer pool.Close() + + priv, err := crypto.GenerateKey() + assert.Nil(t, err) + + acc := genesis.DevAccount{ + Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)), + PrivateKey: priv, + } + + trx1 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx3 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) pool.add(trx1, false, false) txObj, err := resolveTx(trx2, false) @@ -551,9 +752,43 @@ func TestWash(t *testing.T) { PrivateKey: priv, } - trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) - trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), devAccounts[0]) - trx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) + trx1 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), devAccounts[0]) + trx3 := newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) + pool.add(trx1, false, false) + + txObj, err := resolveTx(trx2, false) + assert.Nil(t, err) + pool.all.Add(txObj, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil }) + + txObj, err = resolveTx(trx3, false) + assert.Nil(t, err) + pool.all.Add(txObj, LIMIT_PER_ACCOUNT, func(_ thor.Address, _ *big.Int) error { return nil }) + + pool.wash(pool.repo.BestBlockSummary()) + // all non executable should be washed out + got := pool.Get(trx2.ID()) + assert.Nil(t, got) + got = pool.Get(trx3.ID()) + assert.Nil(t, got) + }, + }, + { + "Executable + Non executable beyond limit with dynFeeTx", func(t *testing.T) { + pool := newPool(1, LIMIT_PER_ACCOUNT) + defer pool.Close() + + priv, err := crypto.GenerateKey() + assert.Nil(t, err) + + acc := genesis.DevAccount{ + Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)), + PrivateKey: priv, + } + + trx1 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0]) + trx2 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), devAccounts[0]) + trx3 := newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc) pool.add(trx1, false, false) txObj, err := resolveTx(trx2, false) @@ -641,23 +876,29 @@ func TestAddOverPendingCost(t *testing.T) { defer pool.Close() // first and second tx should be fine - err = pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) + err = pool.Add(newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) assert.Nil(t, err) - err = pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) + err = pool.Add(newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) assert.Nil(t, err) // third tx should be rejected due to insufficient energy - err = pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) + err = pool.Add(newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) + assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") + err = pool.Add(newTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])) assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") // delegated fee should also be counted - err = pool.Add(newDelegatedTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[9], devAccounts[0])) + err = pool.Add(newDelegatedTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[9], devAccounts[0])) + assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") + err = pool.Add(newDelegatedTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[9], devAccounts[0])) assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") // first and second tx should be fine - err = pool.Add(newDelegatedTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[1], devAccounts[2])) + err = pool.Add(newDelegatedTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[1], devAccounts[2])) assert.Nil(t, err) - err = pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[2])) + err = pool.Add(newTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[2])) assert.Nil(t, err) // delegated fee should also be counted - err = pool.Add(newDelegatedTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[8], devAccounts[2])) + err = pool.Add(newDelegatedTx(tx.LegacyTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[8], devAccounts[2])) + assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") + err = pool.Add(newDelegatedTx(tx.DynamicFeeTxType, pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, devAccounts[8], devAccounts[2])) assert.EqualError(t, err, "tx rejected: insufficient energy for overall pending cost") }