Merge pull request #527 from gzliudan/eip1559

implement EIP-1559
This commit is contained in:
Daniel Liu 2024-11-01 11:56:18 +08:00 committed by GitHub
commit a3bd485bd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
129 changed files with 6198 additions and 1769 deletions

View file

@ -32,12 +32,12 @@ var (
// have any code associated with it (i.e. suicided). // have any code associated with it (i.e. suicided).
ErrNoCode = errors.New("no contract code at given address") ErrNoCode = errors.New("no contract code at given address")
// This error is raised when attempting to perform a pending state action // ErrNoPendingState is raised when attempting to perform a pending state action
// on a backend that doesn't implement PendingContractCaller. // on a backend that doesn't implement PendingContractCaller.
ErrNoPendingState = errors.New("backend does not support pending state") ErrNoPendingState = errors.New("backend does not support pending state")
// This error is returned by WaitDeployed if contract creation leaves an // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// empty contract behind. // an empty contract behind.
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
) )
@ -47,7 +47,8 @@ type ContractCaller interface {
// CodeAt returns the code of the given account. This is needed to differentiate // CodeAt returns the code of the given account. This is needed to differentiate
// between contract internal errors and the local chain being out of sync. // between contract internal errors and the local chain being out of sync.
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
// ContractCall executes an Ethereum contract call with the specified data as the
// CallContract executes an Ethereum contract call with the specified data as the
// input. // input.
CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error) CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error)
} }
@ -58,6 +59,7 @@ type ContractCaller interface {
type PendingContractCaller interface { type PendingContractCaller interface {
// PendingCodeAt returns the code of the given account in the pending state. // PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
// PendingCallContract executes an Ethereum contract call against the pending state. // PendingCallContract executes an Ethereum contract call against the pending state.
PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error) PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error)
} }
@ -67,19 +69,31 @@ type PendingContractCaller interface {
// used when the user does not provide some needed values, but rather leaves it up // used when the user does not provide some needed values, but rather leaves it up
// to the transactor to decide. // to the transactor to decide.
type ContractTransactor interface { type ContractTransactor interface {
// HeaderByNumber returns a block header from the current canonical chain. If
// number is nil, the latest known header is returned.
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
// PendingCodeAt returns the code of the given account in the pending state. // PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
// PendingNonceAt retrieves the current pending nonce associated with an account. // PendingNonceAt retrieves the current pending nonce associated with an account.
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely // SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction. // execution of a transaction.
SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
// a timely execution of a transaction.
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// EstimateGas tries to estimate the gas needed to execute a specific // EstimateGas tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain. // transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other // There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis // transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default. // for setting a reasonable default.
EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error) EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error)
// SendTransaction injects the transaction into the pending pool for execution. // SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error SendTransaction(ctx context.Context, tx *types.Transaction) error
} }

View file

@ -256,6 +256,19 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common
return receipt, nil return receipt, nil
} }
// HeaderByNumber returns a block header from the current canonical chain. If number is
// nil, the latest known header is returned.
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
b.mu.Lock()
defer b.mu.Unlock()
if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 {
return b.blockchain.CurrentHeader(), nil
}
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
}
// PendingCodeAt returns the code associated with an account in the pending state. // PendingCodeAt returns the code associated with an account in the pending state.
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
b.mu.Lock() b.mu.Lock()
@ -300,8 +313,17 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
} }
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated // SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
// chain doens't have miners, we just return a gas price of 1 for any call. // chain doesn't have miners, we just return a gas price of 1 for any call.
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
if b.pendingBlock.Header().BaseFee != nil {
return b.pendingBlock.Header().BaseFee, nil
}
return big.NewInt(1), nil
}
// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated
// chain doesn't have miners, we just return a gas tip of 1 for any call.
func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return big.NewInt(1), nil return big.NewInt(1), nil
} }
@ -358,10 +380,38 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.Call
// callContract implements common code between normal and pending contract calls. // callContract implements common code between normal and pending contract calls.
// state is modified during execution, make sure to copy it if necessary. // state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) (ret []byte, usedGas uint64, failed bool, err error) { func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) (ret []byte, usedGas uint64, failed bool, err error) {
// Ensure message is initialized properly. // Gas prices post 1559 need to be initialized
if call.GasPrice == nil { if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
call.GasPrice = big.NewInt(1) return nil, 0, false, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
} }
head := b.blockchain.CurrentHeader()
if !b.blockchain.Config().IsEIP1559(head.Number) {
// If there's no basefee, then it must be a non-1559 execution
if call.GasPrice == nil {
call.GasPrice = new(big.Int)
}
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if call.GasPrice != nil {
// User specified the legacy gas field, convert to 1559 gas typing
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
} else {
// User specified 1559 gas feilds (or none), use those
if call.GasFeeCap == nil {
call.GasFeeCap = new(big.Int)
}
if call.GasTipCap == nil {
call.GasTipCap = new(big.Int)
}
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
call.GasPrice = new(big.Int)
if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 {
call.GasPrice = math.BigMin(new(big.Int).Add(call.GasTipCap, head.BaseFee), call.GasFeeCap)
}
}
}
// Ensure message is initialized properly.
if call.Gas == 0 { if call.Gas == 0 {
call.Gas = 50000000 call.Gas = 50000000
} }
@ -384,7 +434,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.Cal
evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil) evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil)
// Create a new environment which holds all relevant information // Create a new environment which holds all relevant information
// about the transaction and calling mechanisms. // about the transaction and calling mechanisms.
vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, b.config, vm.Config{}) vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, b.config, vm.Config{NoBaseFee: true})
gaspool := new(core.GasPool).AddGas(math.MaxUint64) gaspool := new(core.GasPool).AddGas(math.MaxUint64)
owner := common.Address{} owner := common.Address{}
ret, usedGas, failed, err, _ = core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner) ret, usedGas, failed, err, _ = core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
@ -522,9 +572,11 @@ type callMsg struct {
func (m callMsg) From() common.Address { return m.CallMsg.From } func (m callMsg) From() common.Address { return m.CallMsg.From }
func (m callMsg) Nonce() uint64 { return 0 } func (m callMsg) Nonce() uint64 { return 0 }
func (m callMsg) CheckNonce() bool { return false } func (m callMsg) IsFake() bool { return true }
func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) To() *common.Address { return m.CallMsg.To }
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data } func (m callMsg) Data() []byte { return m.CallMsg.Data }
@ -554,7 +606,11 @@ func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*t
} }
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil number := rawdb.ReadHeaderNumber(fb.db, hash)
if number == nil {
return nil, nil
}
return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil
} }
func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {

View file

@ -53,11 +53,15 @@ type TransactOpts struct {
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
Signer SignerFn // Method to use for signing the transaction (mandatory) Signer SignerFn // Method to use for signing the transaction (mandatory)
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
NoSend bool // Do all transact steps but do not send the transaction
} }
// FilterOpts is the collection of options to fine tune filtering for events // FilterOpts is the collection of options to fine tune filtering for events
@ -184,57 +188,158 @@ func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error)
return c.transact(opts, &c.address, nil) return c.transact(opts, &c.address, nil)
} }
// transact executes an actual transaction invocation, first deriving any missing func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) {
// authorization fields, and then scheduling the transaction for execution. // Normalize value
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
var err error
// Ensure a valid value field and resolve the account nonce
value := opts.Value value := opts.Value
if value == nil { if value == nil {
value = new(big.Int) value = new(big.Int)
} }
var nonce uint64 // Estimate TipCap
if opts.Nonce == nil { gasTipCap := opts.GasTipCap
nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) if gasTipCap == nil {
tip, err := c.transactor.SuggestGasTipCap(ensureContext(opts.Context))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) return nil, err
} }
} else { gasTipCap = tip
nonce = opts.Nonce.Uint64()
} }
// Figure out the gas allowance and gas price values // Estimate FeeCap
gasFeeCap := opts.GasFeeCap
if gasFeeCap == nil {
gasFeeCap = new(big.Int).Add(
gasTipCap,
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
)
}
if gasFeeCap.Cmp(gasTipCap) < 0 {
return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap)
}
// Estimate GasLimit
gasLimit := opts.GasLimit
if opts.GasLimit == 0 {
var err error
gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value)
if err != nil {
return nil, err
}
}
// create the transaction
nonce, err := c.getNonce(opts)
if err != nil {
return nil, err
}
baseTx := &types.DynamicFeeTx{
To: contract,
Nonce: nonce,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Gas: gasLimit,
Value: value,
Data: input,
}
return types.NewTx(baseTx), nil
}
func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
if opts.GasFeeCap != nil || opts.GasTipCap != nil {
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but EIP-1559 is not active yet")
}
// Normalize value
value := opts.Value
if value == nil {
value = new(big.Int)
}
// Estimate GasPrice
gasPrice := opts.GasPrice gasPrice := opts.GasPrice
if gasPrice == nil { if gasPrice == nil {
gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context)) price, err := c.transactor.SuggestGasPrice(ensureContext(opts.Context))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to suggest gas price: %v", err) return nil, err
} }
gasPrice = price
} }
// Estimate GasLimit
gasLimit := opts.GasLimit gasLimit := opts.GasLimit
if gasLimit == 0 { if opts.GasLimit == 0 {
// Gas estimation cannot succeed without code for method invocations var err error
if contract != nil { gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value)
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
return nil, err
} else if len(code) == 0 {
return nil, ErrNoCode
}
}
// If the contract surely has code (or code is not needed), estimate the transaction
msg := XDPoSChain.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to estimate gas needed: %v", err) return nil, err
} }
} }
// Create the transaction, sign it and schedule it for execution // create the transaction
var rawTx *types.Transaction nonce, err := c.getNonce(opts)
if contract == nil { if err != nil {
rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) return nil, err
} else {
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input)
} }
baseTx := &types.LegacyTx{
To: contract,
Nonce: nonce,
GasPrice: gasPrice,
Gas: gasLimit,
Value: value,
Data: input,
}
return types.NewTx(baseTx), nil
}
func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) {
if contract != nil {
// Gas estimation cannot succeed without code for method invocations.
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
return 0, err
} else if len(code) == 0 {
return 0, ErrNoCode
}
}
msg := XDPoSChain.CallMsg{
From: opts.From,
To: contract,
GasPrice: gasPrice,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: value,
Data: input,
}
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
}
func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) {
if opts.Nonce == nil {
return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
} else {
return opts.Nonce.Uint64(), nil
}
}
// transact executes an actual transaction invocation, first deriving any missing
// authorization fields, and then scheduling the transaction for execution.
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) {
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// Create the transaction
var (
rawTx *types.Transaction
err error
)
if opts.GasPrice != nil {
rawTx, err = c.createLegacyTx(opts, contract, input)
} else {
// Only query for basefee if gasPrice not specified
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil {
return nil, errHead
} else if head.BaseFee != nil {
rawTx, err = c.createDynamicTx(opts, contract, input, head)
} else {
// Chain is not London ready -> use legacy transaction
rawTx, err = c.createLegacyTx(opts, contract, input)
}
}
if err != nil {
return nil, err
}
// Sign the transaction and schedule it for execution
if opts.Signer == nil { if opts.Signer == nil {
return nil, errors.New("no signer to authorize the transaction with") return nil, errors.New("no signer to authorize the transaction with")
} }
@ -242,6 +347,9 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opts.NoSend {
return signedTx, nil
}
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,177 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bind_test
import (
"context"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/stretchr/testify/assert"
)
func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil }
type mockTransactor struct {
baseFee *big.Int
gasTipCap *big.Int
gasPrice *big.Int
suggestGasTipCapCalled bool
suggestGasPriceCalled bool
}
func (mt *mockTransactor) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return &types.Header{BaseFee: mt.baseFee}, nil
}
func (mt *mockTransactor) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
return []byte{1}, nil
}
func (mt *mockTransactor) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
return 0, nil
}
func (mt *mockTransactor) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
mt.suggestGasPriceCalled = true
return mt.gasPrice, nil
}
func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
mt.suggestGasTipCapCalled = true
return mt.gasTipCap, nil
}
func (mt *mockTransactor) EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error) {
return 0, nil
}
func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transaction) error {
return nil
}
type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
pendingCodeAtCalled bool
pendingCallContractCalled bool
}
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
mc.codeAtBlockNumber = blockNumber
return []byte{1, 2, 3}, nil
}
func (mc *mockCaller) CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error) {
mc.callContractBlockNumber = blockNumber
return nil, nil
}
func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
mc.pendingCodeAtCalled = true
return nil, nil
}
func (mc *mockCaller) PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error) {
mc.pendingCallContractCalled = true
return nil, nil
}
func TestPassingBlockNumber(t *testing.T) {
mc := &mockCaller{}
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
Methods: map[string]abi.Method{
"something": {
Name: "something",
Outputs: abi.Arguments{},
},
},
}, mc, nil, nil)
bc.Call(&bind.CallOpts{}, nil, "something")
bc.Call(&bind.CallOpts{}, nil, "something")
if mc.callContractBlockNumber != nil {
t.Fatalf("CallContract() was passed a block number when it should not have been")
}
if mc.codeAtBlockNumber != nil {
t.Fatalf("CodeAt() was passed a block number when it should not have been")
}
bc.Call(&bind.CallOpts{Pending: true}, nil, "something")
if !mc.pendingCallContractCalled {
t.Fatalf("CallContract() was not passed the block number")
}
if !mc.pendingCodeAtCalled {
t.Fatalf("CodeAt() was not passed the block number")
}
}
func TestTransactGasFee(t *testing.T) {
assert := assert.New(t)
// GasTipCap and GasFeeCap
// When opts.GasTipCap and opts.GasFeeCap are nil
mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)}
bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
opts := &bind.TransactOpts{Signer: mockSign}
tx, err := bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(5), tx.GasTipCap())
assert.Equal(big.NewInt(205), tx.GasFeeCap())
assert.Nil(opts.GasTipCap)
assert.Nil(opts.GasFeeCap)
assert.True(mt.suggestGasTipCapCalled)
// Second call to Transact should use latest suggested GasTipCap
mt.gasTipCap = big.NewInt(6)
mt.suggestGasTipCapCalled = false
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(6), tx.GasTipCap())
assert.Equal(big.NewInt(206), tx.GasFeeCap())
assert.True(mt.suggestGasTipCapCalled)
// GasPrice
// When opts.GasPrice is nil
mt = &mockTransactor{gasPrice: big.NewInt(5)}
bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
opts = &bind.TransactOpts{Signer: mockSign}
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(5), tx.GasPrice())
assert.Nil(opts.GasPrice)
assert.True(mt.suggestGasPriceCalled)
// Second call to Transact should use latest suggested GasPrice
mt.gasPrice = big.NewInt(6)
mt.suggestGasPriceCalled = false
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(6), tx.GasPrice())
assert.True(mt.suggestGasPriceCalled)
}

View file

@ -229,7 +229,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy an interaction tester contract and call a transaction on it // Deploy an interaction tester contract and call a transaction on it
_, _, interactor, err := DeployInteractor(auth, sim, "Deploy string") _, _, interactor, err := DeployInteractor(auth, sim, "Deploy string")
@ -270,7 +270,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a tuple tester contract and execute a structured call on it // Deploy a tuple tester contract and execute a structured call on it
_, _, getter, err := DeployGetter(auth, sim) _, _, getter, err := DeployGetter(auth, sim)
@ -302,7 +302,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a tuple tester contract and execute a structured call on it // Deploy a tuple tester contract and execute a structured call on it
_, _, tupler, err := DeployTupler(auth, sim) _, _, tupler, err := DeployTupler(auth, sim)
@ -344,7 +344,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a slice tester contract and execute a n array call on it // Deploy a slice tester contract and execute a n array call on it
_, _, slicer, err := DeploySlicer(auth, sim) _, _, slicer, err := DeploySlicer(auth, sim)
@ -378,7 +378,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a default method invoker contract and execute its default method // Deploy a default method invoker contract and execute its default method
_, _, defaulter, err := DeployDefaulter(auth, sim) _, _, defaulter, err := DeployDefaulter(auth, sim)
@ -447,7 +447,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a funky gas pattern contract // Deploy a funky gas pattern contract
_, _, limiter, err := DeployFunkyGasPattern(auth, sim) _, _, limiter, err := DeployFunkyGasPattern(auth, sim)
@ -482,7 +482,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a sender tester contract and execute a structured call on it // Deploy a sender tester contract and execute a structured call on it
_, _, callfrom, err := DeployCallFrom(auth, sim) _, _, callfrom, err := DeployCallFrom(auth, sim)
@ -542,7 +542,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy a underscorer tester contract and execute a structured call on it // Deploy a underscorer tester contract and execute a structured call on it
_, _, underscorer, err := DeployUnderscorer(auth, sim) _, _, underscorer, err := DeployUnderscorer(auth, sim)
@ -612,7 +612,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
// Deploy an eventer contract // Deploy an eventer contract
_, _, eventer, err := DeployEventer(auth, sim) _, _, eventer, err := DeployEventer(auth, sim)
@ -761,7 +761,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator // Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000, params.TestXDPoSMockChainConfig) sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
//deploy the test contract //deploy the test contract
_, _, testContract, err := DeployDeeplyNestedArray(auth, sim) _, _, testContract, err := DeployDeeplyNestedArray(auth, sim)

View file

@ -53,15 +53,20 @@ var waitDeployedTests = map[string]struct {
} }
func TestWaitDeployed(t *testing.T) { func TestWaitDeployed(t *testing.T) {
config := *params.TestXDPoSMockChainConfig
config.Eip1559Block = big.NewInt(0)
for name, test := range waitDeployedTests { for name, test := range waitDeployedTests {
backend := backends.NewXDCSimulatedBackend( backend := backends.NewXDCSimulatedBackend(
core.GenesisAlloc{ core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(100000000000000000)},
}, 10000000, params.TestXDPoSMockChainConfig, }, 10000000, &config,
) )
// Create the transaction. // Create the transaction
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code)) head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code))
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
// Wait for it to get mined in the background. // Wait for it to get mined in the background.

View file

@ -38,6 +38,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
@ -261,12 +262,12 @@ var (
TxPoolJournalFlag = cli.StringFlag{ TxPoolJournalFlag = cli.StringFlag{
Name: "txpool.journal", Name: "txpool.journal",
Usage: "Disk journal for local transaction to survive node restarts", Usage: "Disk journal for local transaction to survive node restarts",
Value: core.DefaultTxPoolConfig.Journal, Value: txpool.DefaultConfig.Journal,
} }
TxPoolRejournalFlag = cli.DurationFlag{ TxPoolRejournalFlag = cli.DurationFlag{
Name: "txpool.rejournal", Name: "txpool.rejournal",
Usage: "Time interval to regenerate the local transaction journal", Usage: "Time interval to regenerate the local transaction journal",
Value: core.DefaultTxPoolConfig.Rejournal, Value: txpool.DefaultConfig.Rejournal,
} }
TxPoolPriceLimitFlag = cli.Uint64Flag{ TxPoolPriceLimitFlag = cli.Uint64Flag{
Name: "txpool.pricelimit", Name: "txpool.pricelimit",
@ -1016,8 +1017,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
// If we are running the light client, apply another group // If we are running the light client, apply another group
// settings for gas oracle. // settings for gas oracle.
if light { if light {
cfg.Blocks = ethconfig.LightClientGPO.Blocks *cfg = ethconfig.LightClientGPO
cfg.Percentile = ethconfig.LightClientGPO.Percentile
} }
if ctx.GlobalIsSet(GpoBlocksFlag.Name) { if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name) cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
@ -1033,7 +1033,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
} }
} }
func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { func setTxPool(ctx *cli.Context, cfg *txpool.Config) {
if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) { if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) {
cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name) cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name)
} }

View file

@ -51,7 +51,7 @@ var BerlinBlock = big.NewInt(16832700)
var LondonBlock = big.NewInt(16832700) var LondonBlock = big.NewInt(16832700)
var MergeBlock = big.NewInt(16832700) var MergeBlock = big.NewInt(16832700)
var ShanghaiBlock = big.NewInt(16832700) var ShanghaiBlock = big.NewInt(16832700)
var Eip1559Block = big.NewInt(9999999999) var Eip1559Block = big.NewInt(23580000)
var TIPXDCXTestnet = big.NewInt(0) var TIPXDCXTestnet = big.NewInt(0)
var IsTestnet bool = false var IsTestnet bool = false

View file

@ -4,6 +4,7 @@ import "math/big"
var MinGasPrice50x = big.NewInt(12500000000) var MinGasPrice50x = big.NewInt(12500000000)
var GasPrice50x = big.NewInt(12500000000) var GasPrice50x = big.NewInt(12500000000)
var BaseFee = big.NewInt(12500000000)
func GetGasFee(blockNumber, gas uint64) *big.Int { func GetGasFee(blockNumber, gas uint64) *big.Int {
fee := new(big.Int).SetUint64(gas) fee := new(big.Int).SetUint64(gas)

View file

@ -19,6 +19,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/consensus/clique"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -241,6 +242,10 @@ func (x *XDPoS_v1) verifyCascadingFields(chain consensus.ChainReader, header *ty
if parent.Time.Uint64()+x.config.Period > header.Time.Uint64() { if parent.Time.Uint64()+x.config.Period > header.Time.Uint64() {
return utils.ErrInvalidTimestamp return utils.ErrInvalidTimestamp
} }
// Verify the header's EIP-1559 attributes.
if err := eip1559.VerifyEip1559Header(chain.Config(), header); err != nil {
return err
}
if number%x.config.Epoch != 0 { if number%x.config.Epoch != 0 {
return x.verifySeal(chain, header, parents, fullVerify) return x.verifySeal(chain, header, parents, fullVerify)

View file

@ -8,7 +8,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
@ -62,7 +61,7 @@ func getM1M2(masternodes []common.Address, validators []int64, currentHeader *ty
func sigHash(header *types.Header) (hash common.Hash) { func sigHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256() hasher := sha3.NewKeccak256()
err := rlp.Encode(hasher, []interface{}{ enc := []interface{}{
header.ParentHash, header.ParentHash,
header.UncleHash, header.UncleHash,
header.Coinbase, header.Coinbase,
@ -78,10 +77,11 @@ func sigHash(header *types.Header) (hash common.Hash) {
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
header.MixDigest, header.MixDigest,
header.Nonce, header.Nonce,
})
if err != nil {
log.Debug("Fail to encode", err)
} }
if header.BaseFee != nil {
enc = append(enc, header.BaseFee)
}
rlp.Encode(hasher, enc)
hasher.Sum(hash[:0]) hasher.Sum(hash[:0])
return hash return hash
} }

View file

@ -20,7 +20,7 @@ import (
func sigHash(header *types.Header) (hash common.Hash) { func sigHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256() hasher := sha3.NewKeccak256()
err := rlp.Encode(hasher, []interface{}{ enc := []interface{}{
header.ParentHash, header.ParentHash,
header.UncleHash, header.UncleHash,
header.Coinbase, header.Coinbase,
@ -38,10 +38,11 @@ func sigHash(header *types.Header) (hash common.Hash) {
header.Nonce, header.Nonce,
header.Validators, header.Validators,
header.Penalties, header.Penalties,
})
if err != nil {
log.Debug("Fail to encode", err)
} }
if header.BaseFee != nil {
enc = append(enc, header.BaseFee)
}
rlp.Encode(hasher, enc)
hasher.Sum(hash[:0]) hasher.Sum(hash[:0])
return hash return hash
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
) )
@ -94,7 +95,10 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade
if header.UncleHash != utils.UncleHash { if header.UncleHash != utils.UncleHash {
return utils.ErrInvalidUncleHash return utils.ErrInvalidUncleHash
} }
// Verify the header's EIP-1559 attributes.
if err := eip1559.VerifyEip1559Header(chain.Config(), header); err != nil {
return err
}
if header.Difficulty.Cmp(big.NewInt(1)) != 0 { if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
return utils.ErrInvalidDifficulty return utils.ErrInvalidDifficulty
} }

View file

@ -147,7 +147,7 @@ type SignerFn func(accounts.Account, []byte) ([]byte, error)
func sigHash(header *types.Header) (hash common.Hash) { func sigHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256() hasher := sha3.NewKeccak256()
rlp.Encode(hasher, []interface{}{ enc := []interface{}{
header.ParentHash, header.ParentHash,
header.UncleHash, header.UncleHash,
header.Coinbase, header.Coinbase,
@ -163,7 +163,11 @@ func sigHash(header *types.Header) (hash common.Hash) {
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
header.MixDigest, header.MixDigest,
header.Nonce, header.Nonce,
}) }
if header.BaseFee != nil {
enc = append(enc, header.BaseFee)
}
rlp.Encode(hasher, enc)
hasher.Sum(hash[:0]) hasher.Sum(hash[:0])
return hash return hash
} }

View file

@ -28,6 +28,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -253,6 +254,10 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
if header.GasUsed > header.GasLimit { if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
} }
// Verify the header's EIP-1559 attributes.
if err := eip1559.VerifyEip1559Header(chain.Config(), header); err != nil {
return err
}
// Verify that the gas limit remains within allowed bounds // Verify that the gas limit remains within allowed bounds
diff := int64(parent.GasLimit) - int64(header.GasLimit) diff := int64(parent.GasLimit) - int64(header.GasLimit)

View file

@ -0,0 +1,62 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eip1559
import (
"fmt"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
)
// VerifyEip1559Header verifies some header attributes which were changed in EIP-1559,
// - gas limit check
// - basefee check
func VerifyEip1559Header(config *params.ChainConfig, header *types.Header) error {
if !config.IsEIP1559(header.Number) {
if header.BaseFee != nil {
return fmt.Errorf("invalid baseFee: have %s, want <nil>",
header.BaseFee)
}
return nil
}
// Verify the header is not malformed
if header.BaseFee == nil {
return fmt.Errorf("header is missing baseFee")
}
// Verify the baseFee is correct based on the current header.
expectedBaseFee := CalcBaseFee(config, header)
if header.BaseFee.Cmp(expectedBaseFee) != 0 {
return fmt.Errorf("invalid baseFee: have %s, want %s",
header.BaseFee, expectedBaseFee)
}
return nil
}
// CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(config *params.ChainConfig, header *types.Header) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
if config.IsEIP1559(header.Number) {
return new(big.Int).Set(common.BaseFee)
} else {
return nil
}
}

View file

@ -41,6 +41,7 @@ import (
randomizeContract "github.com/XinFinOrg/XDPoSChain/contracts/randomize/contract" randomizeContract "github.com/XinFinOrg/XDPoSChain/contracts/randomize/contract"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -60,7 +61,7 @@ type RewardLog struct {
var TxSignMu sync.RWMutex var TxSignMu sync.RWMutex
// Send tx sign for block number to smart contract blockSigner. // Send tx sign for block number to smart contract blockSigner.
func CreateTransactionSign(chainConfig *params.ChainConfig, pool *core.TxPool, manager *accounts.Manager, block *types.Block, chainDb ethdb.Database, eb common.Address) error { func CreateTransactionSign(chainConfig *params.ChainConfig, pool *txpool.TxPool, manager *accounts.Manager, block *types.Block, chainDb ethdb.Database, eb common.Address) error {
TxSignMu.Lock() TxSignMu.Lock()
defer TxSignMu.Unlock() defer TxSignMu.Unlock()
if chainConfig.XDPoS != nil { if chainConfig.XDPoS != nil {

View file

@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
data := make([]byte, nbytes) data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false) gas, _ := IntrinsicGas(data, nil, false, false, false)
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey)
gen.AddTx(tx) gen.AddTx(tx)
} }

View file

@ -60,15 +60,15 @@ var (
CheckpointCh = make(chan int) CheckpointCh = make(chan int)
ErrNoGenesis = errors.New("Genesis not found in chain") ErrNoGenesis = errors.New("Genesis not found in chain")
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
blockReorgInvalidatedTx = metrics.NewRegisteredMeter("chain/reorg/invalidTx", nil)
) )
const ( const (
bodyCacheLimit = 256 bodyCacheLimit = 256
blockCacheLimit = 256 blockCacheLimit = 256
receiptsCacheLimit = 32
maxFutureBlocks = 256 maxFutureBlocks = 256
maxTimeFutureBlocks = 30 maxTimeFutureBlocks = 30
badBlockLimit = 10 badBlockLimit = 10
@ -142,6 +142,7 @@ type BlockChain struct {
bodyCache *lru.Cache[common.Hash, *types.Body] // Cache for the most recent block bodies bodyCache *lru.Cache[common.Hash, *types.Body] // Cache for the most recent block bodies
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] // Cache for the most recent block bodies in RLP encoded format bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] // Cache for the most recent block bodies in RLP encoded format
receiptsCache *lru.Cache[common.Hash, types.Receipts] // Cache for the most recent block receipts
blockCache *lru.Cache[common.Hash, *types.Block] // Cache for the most recent entire blocks blockCache *lru.Cache[common.Hash, *types.Block] // Cache for the most recent entire blocks
resultProcess *lru.Cache[common.Hash, *ResultProcessBlock] // Cache for processed blocks resultProcess *lru.Cache[common.Hash, *ResultProcessBlock] // Cache for processed blocks
calculatingBlock *lru.Cache[common.Hash, *CalculatedBlock] // Cache for processing blocks calculatingBlock *lru.Cache[common.Hash, *CalculatedBlock] // Cache for processing blocks
@ -196,6 +197,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
quit: make(chan struct{}), quit: make(chan struct{}),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit), bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
receiptsCache: lru.NewCache[common.Hash, types.Receipts](receiptsCacheLimit),
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
resultProcess: lru.NewCache[common.Hash, *ResultProcessBlock](blockCacheLimit), resultProcess: lru.NewCache[common.Hash, *ResultProcessBlock](blockCacheLimit),
@ -397,6 +399,7 @@ func (bc *BlockChain) SetHead(head uint64) error {
// Clear out any stale content from the caches // Clear out any stale content from the caches
bc.bodyCache.Purge() bc.bodyCache.Purge()
bc.bodyRLPCache.Purge() bc.bodyRLPCache.Purge()
bc.receiptsCache.Purge()
bc.blockCache.Purge() bc.blockCache.Purge()
bc.futureBlocks.Purge() bc.futureBlocks.Purge()
bc.blocksHashCache.Purge() bc.blocksHashCache.Purge()
@ -809,7 +812,19 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
// GetReceiptsByHash retrieves the receipts for all transactions in a given block. // GetReceiptsByHash retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return GetBlockReceipts(bc.db, hash, GetBlockNumber(bc.db, hash)) if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts
}
number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil {
return nil
}
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
if receipts == nil {
return nil
}
bc.receiptsCache.Add(hash, receipts)
return receipts
} }
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors. // GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
@ -1029,49 +1044,6 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
} }
} }
// SetReceiptsData computes all the non-consensus fields of the receipts
func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) error {
signer := types.MakeSigner(config, block.Number())
transactions, logIndex := block.Transactions(), uint(0)
if len(transactions) != len(receipts) {
return errors.New("transaction and receipt count mismatch")
}
for j := 0; j < len(receipts); j++ {
// The transaction hash can be retrieved from the transaction itself
receipts[j].TxHash = transactions[j].Hash()
// block location fields
receipts[j].BlockHash = block.Hash()
receipts[j].BlockNumber = block.Number()
receipts[j].TransactionIndex = uint(j)
// The contract address can be derived from the transaction itself
if transactions[j].To() == nil {
// Deriving the signer is expensive, only do if it's actually needed
from, _ := types.Sender(signer, transactions[j])
receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce())
}
// The used gas can be calculated based on previous receipts
if j == 0 {
receipts[j].GasUsed = receipts[j].CumulativeGasUsed
} else {
receipts[j].GasUsed = receipts[j].CumulativeGasUsed - receipts[j-1].CumulativeGasUsed
}
// The derived log fields can simply be set from the block and transaction
for k := 0; k < len(receipts[j].Logs); k++ {
receipts[j].Logs[k].BlockNumber = block.NumberU64()
receipts[j].Logs[k].BlockHash = block.Hash()
receipts[j].Logs[k].TxHash = receipts[j].TxHash
receipts[j].Logs[k].TxIndex = uint(j)
receipts[j].Logs[k].Index = logIndex
logIndex++
}
}
return nil
}
// InsertReceiptChain attempts to complete an already existing header chain with // InsertReceiptChain attempts to complete an already existing header chain with
// transaction and receipt data. // transaction and receipt data.
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
@ -1100,22 +1072,23 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
if atomic.LoadInt32(&bc.procInterrupt) == 1 { if atomic.LoadInt32(&bc.procInterrupt) == 1 {
return 0, nil return 0, nil
} }
blockHash, blockNumber := block.Hash(), block.NumberU64()
// Short circuit if the owner header is unknown // Short circuit if the owner header is unknown
if !bc.HasHeader(block.Hash(), block.NumberU64()) { if !bc.HasHeader(blockHash, blockNumber) {
return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4]) return i, fmt.Errorf("containing header #%d [%x…] unknown", blockNumber, blockHash.Bytes()[:4])
} }
// Skip if the entire data is already known // Skip if the entire data is already known
if bc.HasBlock(block.Hash(), block.NumberU64()) { if bc.HasBlock(blockHash, blockNumber) {
stats.ignored++ stats.ignored++
continue continue
} }
// Compute all the non-consensus fields of the receipts // Compute all the non-consensus fields of the receipts
if err := SetReceiptsData(bc.chainConfig, block, receipts); err != nil { if err := receipts.DeriveFields(bc.chainConfig, blockHash, blockNumber, block.BaseFee(), block.Transactions()); err != nil {
return i, fmt.Errorf("failed to set receipts data: %v", err) return i, fmt.Errorf("failed to set receipts data: %v", err)
} }
// Write all the data out into the database // Write all the data out into the database
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()) rawdb.WriteBody(batch, blockHash, blockNumber, block.Body())
if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil { if err := WriteBlockReceipts(batch, blockHash, blockNumber, receipts); err != nil {
return i, fmt.Errorf("failed to write block receipts: %v", err) return i, fmt.Errorf("failed to write block receipts: %v", err)
} }
if err := WriteTxLookupEntries(batch, block); err != nil { if err := WriteTxLookupEntries(batch, block); err != nil {
@ -1482,7 +1455,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
defer close(abort) defer close(abort)
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain) SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
// Iterate over the blocks and insert when the verifier permits // Iterate over the blocks and insert when the verifier permits
for i, block := range chain { for i, block := range chain {
@ -2131,6 +2104,25 @@ func countTransactions(chain []*types.Block) (c int) {
return c return c
} }
// collectLogs collects the logs that were generated or removed during
// the processing of a block. These logs are later announced as deleted or reborn.
func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64())
if err := receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.BaseFee(), b.Transactions()); err != nil {
log.Error("Failed to derive block receipts fields", "hash", b.Hash(), "number", b.NumberU64(), "err", err)
}
var logs []*types.Log
for _, receipt := range receipts {
for _, log := range receipt.Logs {
l := *log
l.Removed = removed
logs = append(logs, &l)
}
}
return logs
}
// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them // reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
// to be part of the new canonical chain and accumulates potential missing transactions and post an // to be part of the new canonical chain and accumulates potential missing transactions and post an
// event about them // event about them
@ -2141,20 +2133,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
commonBlock *types.Block commonBlock *types.Block
deletedTxs types.Transactions deletedTxs types.Transactions
deletedLogs []*types.Log deletedLogs []*types.Log
// collectLogs collects the logs that were generated during the
// processing of the block that corresponds with the given hash.
// These logs are later announced as deleted.
collectLogs = func(h common.Hash) {
// Coalesce logs and set 'Removed'.
receipts := GetBlockReceipts(bc.db, h, bc.hc.GetBlockNumber(h))
for _, receipt := range receipts {
for _, log := range receipt.Logs {
del := *log
del.Removed = true
deletedLogs = append(deletedLogs, &del)
}
}
}
) )
log.Warn("Reorg", "oldBlock hash", oldBlock.Hash().Hex(), "number", oldBlock.NumberU64(), "newBlock hash", newBlock.Hash().Hex(), "number", newBlock.NumberU64()) log.Warn("Reorg", "oldBlock hash", oldBlock.Hash().Hex(), "number", oldBlock.NumberU64(), "newBlock hash", newBlock.Hash().Hex(), "number", newBlock.NumberU64())
@ -2164,8 +2142,9 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) { for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) {
oldChain = append(oldChain, oldBlock) oldChain = append(oldChain, oldBlock)
deletedTxs = append(deletedTxs, oldBlock.Transactions()...) deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
if logs := bc.collectLogs(oldBlock, true); len(logs) > 0 {
collectLogs(oldBlock.Hash()) deletedLogs = append(deletedLogs, logs...)
}
} }
} else { } else {
// reduce new chain and append new chain blocks for inserting later on // reduce new chain and append new chain blocks for inserting later on
@ -2189,7 +2168,9 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
oldChain = append(oldChain, oldBlock) oldChain = append(oldChain, oldBlock)
newChain = append(newChain, newBlock) newChain = append(newChain, newBlock)
deletedTxs = append(deletedTxs, oldBlock.Transactions()...) deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
collectLogs(oldBlock.Hash()) if logs := bc.collectLogs(oldBlock, true); len(logs) > 0 {
deletedLogs = append(deletedLogs, logs...)
}
oldBlock, newBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1), bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) oldBlock, newBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1), bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1)
if oldBlock == nil { if oldBlock == nil {

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"math/rand" "math/rand"
@ -555,10 +556,11 @@ func TestFastVsFullChains(t *testing.T) {
gendb = rawdb.NewMemoryDatabase() gendb = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000) funds = big.NewInt(1000000000000000)
gspec = &Genesis{ gspec = &Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: GenesisAlloc{address: {Balance: funds}}, Alloc: GenesisAlloc{address: {Balance: funds}},
BaseFee: big.NewInt(params.InitialBaseFee),
} }
genesis = gspec.MustCommit(gendb) genesis = gspec.MustCommit(gendb)
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
@ -569,7 +571,7 @@ func TestFastVsFullChains(t *testing.T) {
// If the block number is multiple of 3, send a few bonus transactions to the miner // If the block number is multiple of 3, send a few bonus transactions to the miner
if i%3 == 2 { if i%3 == 2 {
for j := 0; j < i%4+1; j++ { for j := 0; j < i%4+1; j++ {
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -643,8 +645,12 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
gendb = rawdb.NewMemoryDatabase() gendb = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000) funds = big.NewInt(1000000000000000)
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} gspec = &Genesis{
Config: params.TestChainConfig,
Alloc: GenesisAlloc{address: {Balance: funds}},
BaseFee: big.NewInt(params.InitialBaseFee),
}
genesis = gspec.MustCommit(gendb) genesis = gspec.MustCommit(gendb)
) )
height := uint64(1024) height := uint64(1024)
@ -730,9 +736,9 @@ func TestChainTxReorgs(t *testing.T) {
Config: params.TestChainConfig, Config: params.TestChainConfig,
GasLimit: 3141592, GasLimit: 3141592,
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{
addr1: {Balance: big.NewInt(1000000)}, addr1: {Balance: big.NewInt(1000000000000000)},
addr2: {Balance: big.NewInt(1000000)}, addr2: {Balance: big.NewInt(1000000000000000)},
addr3: {Balance: big.NewInt(1000000)}, addr3: {Balance: big.NewInt(1000000000000000)},
}, },
} }
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
@ -742,8 +748,8 @@ func TestChainTxReorgs(t *testing.T) {
// Create two transactions shared between the chains: // Create two transactions shared between the chains:
// - postponed: transaction included at a later block in the forked chain // - postponed: transaction included at a later block in the forked chain
// - swapped: transaction included at the same block number in the forked chain // - swapped: transaction included at the same block number in the forked chain
postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil), signer, key1) postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, key1)
swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil), signer, key1) swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, key1)
// Create two transactions that will be dropped by the forked chain: // Create two transactions that will be dropped by the forked chain:
// - pastDrop: transaction dropped retroactively from a past block // - pastDrop: transaction dropped retroactively from a past block
@ -759,13 +765,13 @@ func TestChainTxReorgs(t *testing.T) {
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) { chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil), signer, key2) pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key2)
gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
case 2: case 2:
freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil), signer, key2) freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key2)
gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
gen.AddTx(swapped) // This transaction will be swapped out at the exact height gen.AddTx(swapped) // This transaction will be swapped out at the exact height
@ -784,18 +790,18 @@ func TestChainTxReorgs(t *testing.T) {
chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key3) pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3)
gen.AddTx(pastAdd) // This transaction needs to be injected during reorg gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
case 2: case 2:
gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key3) freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3)
gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
case 3: case 3:
futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key3) futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3)
gen.AddTx(futureAdd) // This transaction will be added after a full reorg gen.AddTx(futureAdd) // This transaction will be added after a full reorg
} }
}) })
@ -840,7 +846,7 @@ func TestLogReorgs(t *testing.T) {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
// this code generates a log // this code generates a log
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}}
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
) )
@ -852,7 +858,7 @@ func TestLogReorgs(t *testing.T) {
blockchain.SubscribeRemovedLogsEvent(rmLogsCh) blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, code), signer, key1)
if err != nil { if err != nil {
t.Fatalf("failed to create tx: %v", err) t.Fatalf("failed to create tx: %v", err)
} }
@ -886,7 +892,7 @@ func TestLogReorgs(t *testing.T) {
// addr1 = crypto.PubkeyToAddress(key1.PublicKey) // addr1 = crypto.PubkeyToAddress(key1.PublicKey)
// gspec = &Genesis{ // gspec = &Genesis{
// Config: params.TestChainConfig, // Config: params.TestChainConfig,
// Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}, // Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}},
// } // }
// genesis = gspec.MustCommit(db) // genesis = gspec.MustCommit(db)
// signer = types.LatestSigner(gspec.Config) // signer = types.LatestSigner(gspec.Config)
@ -901,7 +907,7 @@ func TestLogReorgs(t *testing.T) {
// } // }
// //
// replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) { // replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) {
// tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), nil), signer, key1) // tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, nil), signer, key1)
// if i == 2 { // if i == 2 {
// gen.OffsetTime(-9) // gen.OffsetTime(-9)
// } // }
@ -1107,8 +1113,8 @@ func TestEIP155Transition(t *testing.T) {
} }
}) })
_, err := blockchain.InsertChain(blocks) _, err := blockchain.InsertChain(blocks)
if err != types.ErrInvalidChainId { if have, want := err, types.ErrInvalidChainId; !errors.Is(have, want) {
t.Error("expected error:", types.ErrInvalidChainId) t.Errorf("have %v, want %v", have, want)
} }
} }
@ -1188,7 +1194,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
engine := ethash.NewFaker() engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
genesis := new(Genesis).MustCommit(db) genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
// Generate a bunch of fork blocks, each side forking from the canonical chain // Generate a bunch of fork blocks, each side forking from the canonical chain
@ -1204,7 +1210,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
// Import the canonical and fork chain side by side, verifying the current block // Import the canonical and fork chain side by side, verifying the current block
// and current header consistency // and current header consistency
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
new(Genesis).MustCommit(diskdb) (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb)
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
if err != nil { if err != nil {
@ -1233,7 +1239,7 @@ func TestTrieForkGC(t *testing.T) {
engine := ethash.NewFaker() engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
genesis := new(Genesis).MustCommit(db) genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
// Generate a bunch of fork blocks, each side forking from the canonical chain // Generate a bunch of fork blocks, each side forking from the canonical chain
@ -1248,7 +1254,7 @@ func TestTrieForkGC(t *testing.T) {
} }
// Import the canonical and fork chain side by side, forcing the trie cache to cache both // Import the canonical and fork chain side by side, forcing the trie cache to cache both
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
new(Genesis).MustCommit(diskdb) (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb)
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
if err != nil { if err != nil {
@ -1279,7 +1285,7 @@ func TestLargeReorgTrieGC(t *testing.T) {
engine := ethash.NewFaker() engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
genesis := new(Genesis).MustCommit(db) genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
original, _ := GenerateChain(params.TestChainConfig, shared[len(shared)-1], engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) original, _ := GenerateChain(params.TestChainConfig, shared[len(shared)-1], engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) })
@ -1287,7 +1293,7 @@ func TestLargeReorgTrieGC(t *testing.T) {
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
new(Genesis).MustCommit(diskdb) (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb)
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
if err != nil { if err != nil {
@ -1431,8 +1437,9 @@ func TestEIP2718Transition(t *testing.T) {
// A sender who makes transactions, has some funds // A sender who makes transactions, has some funds
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000) // funds = big.NewInt(math.MaxInt64)
gspec = &Genesis{ funds = big.NewInt(1000000000000000)
gspec = &Genesis{
Config: &params.ChainConfig{ Config: &params.ChainConfig{
ChainId: new(big.Int).SetBytes([]byte("eip1559")), ChainId: new(big.Int).SetBytes([]byte("eip1559")),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
@ -1458,7 +1465,7 @@ func TestEIP2718Transition(t *testing.T) {
byte(vm.SLOAD), byte(vm.SLOAD),
}, },
Nonce: 0, Nonce: 0,
Balance: big.NewInt(0), Balance: big.NewInt(50000000000),
}, },
}, },
} }
@ -1475,7 +1482,7 @@ func TestEIP2718Transition(t *testing.T) {
Nonce: 0, Nonce: 0,
To: &aa, To: &aa,
Gas: 30000, Gas: 30000,
GasPrice: big.NewInt(1), GasPrice: b.header.BaseFee,
AccessList: types.AccessList{{ AccessList: types.AccessList{{
Address: aa, Address: aa,
StorageKeys: []common.Hash{{0}}, StorageKeys: []common.Hash{{0}},
@ -1499,7 +1506,8 @@ func TestEIP2718Transition(t *testing.T) {
block := chain.GetBlockByNumber(1) block := chain.GetBlockByNumber(1)
// Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list
expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + vm.GasQuickStep*2 + vm.WarmStorageReadCostEIP2929 + vm.ColdSloadCostEIP2929 expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas +
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
if block.GasUsed() != expected { if block.GasUsed() != expected {
t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed())
} }

View file

@ -20,11 +20,11 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
@ -127,6 +127,11 @@ func (b *BlockGen) Number() *big.Int {
return new(big.Int).Set(b.header.Number) return new(big.Int).Set(b.header.Number)
} }
// BaseFee returns the EIP-1559 base fee of the block being generated.
func (b *BlockGen) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee)
}
// AddUncheckedReceipt forcefully adds a receipts to the block without a // AddUncheckedReceipt forcefully adds a receipts to the block without a
// backing transaction. // backing transaction.
// //
@ -265,7 +270,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
time = new(big.Int).Add(parent.Time(), big.NewInt(10)) // block time is fixed at 10 seconds time = new(big.Int).Add(parent.Time(), big.NewInt(10)) // block time is fixed at 10 seconds
} }
return &types.Header{ header := &types.Header{
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
ParentHash: parent.Hash(), ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(), Coinbase: parent.Coinbase(),
@ -279,6 +284,10 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
Number: new(big.Int).Add(parent.Number(), common.Big1), Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time, Time: time,
} }
header.BaseFee = eip1559.CalcBaseFee(chain.Config(), header)
return header
} }
// newCanonical creates a chain database, and injects a deterministic canonical // newCanonical creates a chain database, and injects a deterministic canonical
@ -324,3 +333,18 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
}) })
return blocks return blocks
} }
type fakeChainReader struct {
config *params.ChainConfig
}
// Config returns the chain configuration.
func (cr *fakeChainReader) Config() *params.ChainConfig {
return cr.config
}
func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil }
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }

View file

@ -17,11 +17,11 @@
package core package core
import ( import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big" "math/big"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -33,7 +33,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
// Generate a common prefix for both pro-forkers and non-forkers // Generate a common prefix for both pro-forkers and non-forkers
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
gspec := new(Genesis) gspec := &Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {}) prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})

View file

@ -26,13 +26,13 @@ var (
// ErrKnownBlock is returned when a block to import is already known locally. // ErrKnownBlock is returned when a block to import is already known locally.
ErrKnownBlock = errors.New("block already known") ErrKnownBlock = errors.New("block already known")
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
// ErrBlacklistedHash is returned if a block to import is on the blacklist. // ErrBlacklistedHash is returned if a block to import is on the blacklist.
ErrBlacklistedHash = errors.New("blacklisted hash") ErrBlacklistedHash = errors.New("blacklisted hash")
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
// one present in the local chain.
ErrNonceTooLow = errors.New("nonce too low")
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
// next one expected based on the local chain. // next one expected based on the local chain.
ErrNonceTooHigh = errors.New("nonce too high") ErrNonceTooHigh = errors.New("nonce too high")
@ -41,16 +41,51 @@ var (
// maximum allowed value and would become invalid if incremented. // maximum allowed value and would become invalid if incremented.
ErrNonceMax = errors.New("nonce has max value") ErrNonceMax = errors.New("nonce has max value")
ErrNotXDPoS = errors.New("XDPoS not found in config") // ErrGasLimitReached is returned by the gas pool if the amount of gas required
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
ErrNotFoundM1 = errors.New("list M1 not found ") // ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
// than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
ErrStopPreparingBlock = errors.New("stop calculating a block not verified by M2") // ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
// ErrGasUintOverflow is returned when calculating gas usage.
ErrGasUintOverflow = errors.New("gas uint64 overflow")
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
// than required to start the invocation.
ErrIntrinsicGas = errors.New("intrinsic gas too low")
// ErrTxTypeNotSupported is returned if a transaction is not supported in the // ErrTxTypeNotSupported is returned if a transaction is not supported in the
// current network configuration. // current network configuration.
ErrTxTypeNotSupported = types.ErrTxTypeNotSupported ErrTxTypeNotSupported = types.ErrTxTypeNotSupported
// ErrGasUintOverflow is returned when calculating gas usage. // ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a
ErrGasUintOverflow = errors.New("gas uint64 overflow") // transaction with a tip higher than the total fee cap.
ErrTipAboveFeeCap = errors.New("max priority fee per gas higher than max fee per gas")
// ErrTipVeryHigh is a sanity error to avoid extremely big numbers specified
// in the tip field.
ErrTipVeryHigh = errors.New("max priority fee per gas higher than 2^256-1")
// ErrFeeCapVeryHigh is a sanity error to avoid extremely big numbers specified
// in the fee cap field.
ErrFeeCapVeryHigh = errors.New("max fee per gas higher than 2^256-1")
// ErrFeeCapTooLow is returned if the transaction fee cap is less than the
// the base fee of the block.
ErrFeeCapTooLow = errors.New("max fee per gas less than block base fee")
// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")
ErrNotXDPoS = errors.New("XDPoS not found in config")
ErrNotFoundM1 = errors.New("list M1 not found ")
ErrStopPreparingBlock = errors.New("stop calculating a block not verified by M2")
) )

View file

@ -31,6 +31,7 @@ func NewEVMBlockContext(header *types.Header, chain consensus.ChainContext, auth
// If we don't have an explicit author (i.e. not mining), extract from the header // If we don't have an explicit author (i.e. not mining), extract from the header
var ( var (
beneficiary common.Address beneficiary common.Address
baseFee *big.Int
random common.Hash random common.Hash
) )
if author == nil { if author == nil {
@ -38,6 +39,9 @@ func NewEVMBlockContext(header *types.Header, chain consensus.ChainContext, auth
} else { } else {
beneficiary = *author beneficiary = *author
} }
if header.BaseFee != nil {
baseFee = new(big.Int).Set(header.BaseFee)
}
// since xdpos chain do not use difficulty and mixdigest, we use hash of the block number as random // since xdpos chain do not use difficulty and mixdigest, we use hash of the block number as random
random = crypto.Keccak256Hash(header.Number.Bytes()) random = crypto.Keccak256Hash(header.Number.Bytes())
return vm.BlockContext{ return vm.BlockContext{
@ -48,6 +52,7 @@ func NewEVMBlockContext(header *types.Header, chain consensus.ChainContext, auth
BlockNumber: new(big.Int).Set(header.Number), BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).Set(header.Time), Time: new(big.Int).Set(header.Time),
Difficulty: new(big.Int).Set(header.Difficulty), Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Random: &random, Random: &random,
} }

View file

@ -15,6 +15,7 @@ import (
var _ = (*genesisSpecMarshaling)(nil) var _ = (*genesisSpecMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (g Genesis) MarshalJSON() ([]byte, error) { func (g Genesis) MarshalJSON() ([]byte, error) {
type Genesis struct { type Genesis struct {
Config *params.ChainConfig `json:"config"` Config *params.ChainConfig `json:"config"`
@ -29,6 +30,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
Number math.HexOrDecimal64 `json:"number"` Number math.HexOrDecimal64 `json:"number"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
} }
var enc Genesis var enc Genesis
enc.Config = g.Config enc.Config = g.Config
@ -48,9 +50,11 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
enc.Number = math.HexOrDecimal64(g.Number) enc.Number = math.HexOrDecimal64(g.Number)
enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.GasUsed = math.HexOrDecimal64(g.GasUsed)
enc.ParentHash = g.ParentHash enc.ParentHash = g.ParentHash
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (g *Genesis) UnmarshalJSON(input []byte) error { func (g *Genesis) UnmarshalJSON(input []byte) error {
type Genesis struct { type Genesis struct {
Config *params.ChainConfig `json:"config"` Config *params.ChainConfig `json:"config"`
@ -65,6 +69,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
Number *math.HexOrDecimal64 `json:"number"` Number *math.HexOrDecimal64 `json:"number"`
GasUsed *math.HexOrDecimal64 `json:"gasUsed"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
ParentHash *common.Hash `json:"parentHash"` ParentHash *common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
} }
var dec Genesis var dec Genesis
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -112,5 +117,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
if dec.ParentHash != nil { if dec.ParentHash != nil {
g.ParentHash = *dec.ParentHash g.ParentHash = *dec.ParentHash
} }
if dec.BaseFee != nil {
g.BaseFee = (*big.Int)(dec.BaseFee)
}
return nil return nil
} }

View file

@ -14,6 +14,7 @@ import (
var _ = (*genesisAccountMarshaling)(nil) var _ = (*genesisAccountMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (g GenesisAccount) MarshalJSON() ([]byte, error) { func (g GenesisAccount) MarshalJSON() ([]byte, error) {
type GenesisAccount struct { type GenesisAccount struct {
Code hexutil.Bytes `json:"code,omitempty"` Code hexutil.Bytes `json:"code,omitempty"`
@ -36,6 +37,7 @@ func (g GenesisAccount) MarshalJSON() ([]byte, error) {
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (g *GenesisAccount) UnmarshalJSON(input []byte) error { func (g *GenesisAccount) UnmarshalJSON(input []byte) error {
type GenesisAccount struct { type GenesisAccount struct {
Code *hexutil.Bytes `json:"code,omitempty"` Code *hexutil.Bytes `json:"code,omitempty"`

View file

@ -61,6 +61,7 @@ type Genesis struct {
Number uint64 `json:"number"` Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"` GasUsed uint64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"`
} }
// GenesisAlloc specifies the initial state that is part of the genesis block. // GenesisAlloc specifies the initial state that is part of the genesis block.
@ -96,6 +97,7 @@ type genesisSpecMarshaling struct {
GasUsed math.HexOrDecimal64 GasUsed math.HexOrDecimal64
Number math.HexOrDecimal64 Number math.HexOrDecimal64
Difficulty *math.HexOrDecimal256 Difficulty *math.HexOrDecimal256
BaseFee *math.HexOrDecimal256
Alloc map[common.UnprefixedAddress]GenesisAccount Alloc map[common.UnprefixedAddress]GenesisAccount
} }
@ -253,6 +255,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
Extra: g.ExtraData, Extra: g.ExtraData,
GasLimit: g.GasLimit, GasLimit: g.GasLimit,
GasUsed: g.GasUsed, GasUsed: g.GasUsed,
BaseFee: g.BaseFee,
Difficulty: g.Difficulty, Difficulty: g.Difficulty,
MixDigest: g.Mixhash, MixDigest: g.Mixhash,
Coinbase: g.Coinbase, Coinbase: g.Coinbase,
@ -264,6 +267,13 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if g.Difficulty == nil { if g.Difficulty == nil {
head.Difficulty = params.GenesisDifficulty head.Difficulty = params.GenesisDifficulty
} }
if g.Config != nil && g.Config.IsEIP1559(common.Big0) {
if g.BaseFee != nil {
head.BaseFee = g.BaseFee
} else {
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
}
}
statedb.Commit(false) statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true) statedb.Database().TrieDB().Commit(root, true)
@ -308,7 +318,10 @@ func (g *Genesis) MustCommit(db ethdb.Database) *types.Block {
// GenesisBlockForTesting creates and writes a block in which addr has the given wei balance. // GenesisBlockForTesting creates and writes a block in which addr has the given wei balance.
func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block {
g := Genesis{Alloc: GenesisAlloc{addr: {Balance: balance}}} g := Genesis{
Alloc: GenesisAlloc{addr: {Balance: balance}},
BaseFee: big.NewInt(params.InitialBaseFee),
}
return g.MustCommit(db) return g.MustCommit(db)
} }
@ -365,6 +378,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
Config: &config, Config: &config,
ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, 65)...), ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, 65)...),
GasLimit: 6283185, GasLimit: 6283185,
BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(1), Difficulty: big.NewInt(1),
Alloc: map[common.Address]GenesisAccount{ Alloc: map[common.Address]GenesisAccount{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover

View file

@ -1,91 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"container/list"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event"
)
// Implement our EthTest Manager
type TestManager struct {
// stateManager *StateManager
eventMux *event.TypeMux
db ethdb.Database
txPool *TxPool
blockChain *BlockChain
Blocks []*types.Block
}
func (tm *TestManager) IsListening() bool {
return false
}
func (tm *TestManager) IsMining() bool {
return false
}
func (tm *TestManager) PeerCount() int {
return 0
}
func (tm *TestManager) Peers() *list.List {
return list.New()
}
func (tm *TestManager) BlockChain() *BlockChain {
return tm.blockChain
}
func (tm *TestManager) TxPool() *TxPool {
return tm.txPool
}
// func (tm *TestManager) StateManager() *StateManager {
// return tm.stateManager
// }
func (tm *TestManager) EventMux() *event.TypeMux {
return tm.eventMux
}
// func (tm *TestManager) KeyManager() *crypto.KeyManager {
// return nil
// }
func (tm *TestManager) Db() ethdb.Database {
return tm.db
}
func NewTestManager() *TestManager {
db := rawdb.NewMemoryDatabase()
testManager := &TestManager{}
testManager.eventMux = new(event.TypeMux)
testManager.db = db
// testManager.txPool = NewTxPool(testManager)
// testManager.blockChain = NewBlockChain(testManager)
// testManager.stateManager = NewStateManager(testManager)
return testManager
}

View file

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
@ -36,15 +37,6 @@ func WriteCanonicalHash(db ethdb.KeyValueWriter, hash common.Hash, number uint64
} }
} }
// WriteHeaderNumber stores the hash->number mapping.
func WriteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
key := headerNumberKey(hash)
enc := encodeBlockNumber(number)
if err := db.Put(key, enc); err != nil {
log.Crit("Failed to store hash to number mapping", "err", err)
}
}
// ReadHeaderNumber returns the header number assigned to a hash. // ReadHeaderNumber returns the header number assigned to a hash.
func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 { func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
data, _ := db.Get(headerNumberKey(hash)) data, _ := db.Get(headerNumberKey(hash))
@ -55,6 +47,15 @@ func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
return &number return &number
} }
// WriteHeaderNumber stores the hash->number mapping.
func WriteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
key := headerNumberKey(hash)
enc := encodeBlockNumber(number)
if err := db.Put(key, enc); err != nil {
log.Crit("Failed to store hash to number mapping", "err", err)
}
}
// WriteHeadBlockHash stores the head block's hash. // WriteHeadBlockHash stores the head block's hash.
func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) { func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Put(headBlockKey, hash.Bytes()); err != nil { if err := db.Put(headBlockKey, hash.Bytes()); err != nil {
@ -62,6 +63,26 @@ func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
} }
} }
// ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
data, _ := db.Get(headerKey(number, hash))
return data
}
// ReadHeader retrieves the block header corresponding to the hash.
func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header {
data := ReadHeaderRLP(db, hash, number)
if len(data) == 0 {
return nil
}
header := new(types.Header)
if err := rlp.Decode(bytes.NewReader(data), header); err != nil {
log.Error("Invalid block header RLP", "hash", hash, "err", err)
return nil
}
return header
}
// WriteHeader stores a block header into the database and also stores the hash- // WriteHeader stores a block header into the database and also stores the hash-
// to-number mapping. // to-number mapping.
func WriteHeader(db ethdb.KeyValueWriter, header *types.Header) { func WriteHeader(db ethdb.KeyValueWriter, header *types.Header) {
@ -193,6 +214,9 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec
receipts := make(types.Receipts, len(storageReceipts)) receipts := make(types.Receipts, len(storageReceipts))
for i, storageReceipt := range storageReceipts { for i, storageReceipt := range storageReceipts {
receipts[i] = (*types.Receipt)(storageReceipt) receipts[i] = (*types.Receipt)(storageReceipt)
receipts[i].BlockHash = hash
receipts[i].BlockNumber = new(big.Int).SetUint64(number)
receipts[i].TransactionIndex = uint(i)
} }
return receipts return receipts
} }
@ -215,7 +239,14 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para
log.Error("Missing body but have receipt", "hash", hash, "number", number) log.Error("Missing body but have receipt", "hash", hash, "number", number)
return nil return nil
} }
if err := receipts.DeriveFields(config, hash, number, body.Transactions); err != nil { header := ReadHeader(db, hash, number)
var baseFee *big.Int
if header == nil {
baseFee = big.NewInt(0)
} else {
baseFee = header.BaseFee
}
if err := receipts.DeriveFields(config, hash, number, baseFee, body.Transactions); err != nil {
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
return nil return nil
} }

View file

@ -25,7 +25,9 @@ import (
// The fields below define the low level database schema prefixing. // The fields below define the low level database schema prefixing.
var ( var (
// headBlockKey tracks the latest known full block's hash.
headBlockKey = []byte("LastBlock") headBlockKey = []byte("LastBlock")
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash

View file

@ -22,8 +22,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
) )
// senderCacher is a concurrent tranaction sender recoverer anc cacher. // SenderCacher is a concurrent transaction sender recoverer and cacher.
var senderCacher = newTxSenderCacher(runtime.NumCPU()) var SenderCacher = newTxSenderCacher(runtime.NumCPU())
// txSenderCacherRequest is a request for recovering transaction senders with a // txSenderCacherRequest is a request for recovering transaction senders with a
// specific signature scheme and caching it into the transactions themselves. // specific signature scheme and caching it into the transactions themselves.
@ -67,10 +67,10 @@ func (cacher *txSenderCacher) cache() {
} }
} }
// recover recovers the senders from a batch of transactions and caches them // Recover recovers the senders from a batch of transactions and caches them
// back into the same data structures. There is no validation being done, nor // back into the same data structures. There is no validation being done, nor
// any reaction to invalid signatures. That is up to calling code later. // any reaction to invalid signatures. That is up to calling code later.
func (cacher *txSenderCacher) recover(signer types.Signer, txs []*types.Transaction) { func (cacher *txSenderCacher) Recover(signer types.Signer, txs []*types.Transaction) {
// If there's nothing to recover, abort // If there's nothing to recover, abort
if len(txs) == 0 { if len(txs) == 0 {
return return
@ -89,10 +89,10 @@ func (cacher *txSenderCacher) recover(signer types.Signer, txs []*types.Transact
} }
} }
// recoverFromBlocks recovers the senders from a batch of blocks and caches them // RecoverFromBlocks recovers the senders from a batch of blocks and caches them
// back into the same data structures. There is no validation being done, nor // back into the same data structures. There is no validation being done, nor
// any reaction to invalid signatures. That is up to calling code later. // any reaction to invalid signatures. That is up to calling code later.
func (cacher *txSenderCacher) recoverFromBlocks(signer types.Signer, blocks []*types.Block) { func (cacher *txSenderCacher) RecoverFromBlocks(signer types.Signer, blocks []*types.Block) {
count := 0 count := 0
for _, block := range blocks { for _, block := range blocks {
count += len(block.Transactions()) count += len(block.Transactions())
@ -101,5 +101,5 @@ func (cacher *txSenderCacher) recoverFromBlocks(signer types.Signer, blocks []*t
for _, block := range blocks { for _, block := range blocks {
txs = append(txs, block.Transactions()...) txs = append(txs, block.Transactions()...)
} }
cacher.recover(signer, txs) cacher.Recover(signer, txs)
} }

View file

@ -643,7 +643,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
func (s *StateDB) Prepare(thash common.Hash, ti int) { func (s *StateDB) Prepare(thash common.Hash, ti int) {
s.thash = thash s.thash = thash
s.txIndex = ti s.txIndex = ti
s.accessList = newAccessList()
} }
// DeleteSuicides flags the suicided objects for deletion so that it // DeleteSuicides flags the suicided objects for deletion so that it
@ -728,6 +727,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
// //
// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. // This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
// Clear out any leftover from previous executions
s.accessList = newAccessList()
s.AddAddressToAccessList(sender) s.AddAddressToAccessList(sender)
if dst != nil { if dst != nil {
s.AddAddressToAccessList(*dst) s.AddAddressToAccessList(*dst)

View file

@ -118,7 +118,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
} }
} }
statedb.Prepare(tx.Hash(), i) statedb.Prepare(tx.Hash(), i)
receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, blockHash, tx, usedGas, vmenv) receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, header.BaseFee, blockHash, tx, usedGas, vmenv)
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, 0, err
} }
@ -198,7 +198,7 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
} }
} }
statedb.Prepare(tx.Hash(), i) statedb.Prepare(tx.Hash(), i)
receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, blockHash, tx, usedGas, vmenv) receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, header.BaseFee, blockHash, tx, usedGas, vmenv)
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, 0, err
} }
@ -220,7 +220,7 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }
func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*big.Int, gp *GasPool, statedb *state.StateDB, coinbaseOwner common.Address, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, uint64, error, bool) { func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*big.Int, gp *GasPool, statedb *state.StateDB, coinbaseOwner common.Address, blockNumber, baseFee *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, uint64, error, bool) {
to := tx.To() to := tx.To()
if to != nil { if to != nil {
if *to == common.BlockSignersBinary && config.IsTIPSigning(blockNumber) { if *to == common.BlockSignersBinary && config.IsTIPSigning(blockNumber) {
@ -246,7 +246,8 @@ func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
balanceFee = value balanceFee = value
} }
} }
msg, err := tx.AsMessage(types.MakeSigner(config, blockNumber), balanceFee, blockNumber) // msg, err := tx.AsMessage(types.MakeSigner(config, blockNumber), balanceFee, blockNumber)
msg, err := tx.AsMessage(types.MakeSigner(config, blockNumber), balanceFee, blockNumber, baseFee)
if err != nil { if err != nil {
return nil, 0, err, false return nil, 0, err, false
} }
@ -465,7 +466,7 @@ func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
blockContext := NewEVMBlockContext(header, bc, author) blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, XDCxState, config, cfg) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, XDCxState, config, cfg)
coinbaseOwner := getCoinbaseOwner(bc, statedb, header, author) coinbaseOwner := getCoinbaseOwner(bc, statedb, header, author)
return applyTransaction(config, tokensFee, gp, statedb, coinbaseOwner, header.Number, header.Hash(), tx, usedGas, vmenv) return applyTransaction(config, tokensFee, gp, statedb, coinbaseOwner, header.Number, header.BaseFee, header.Hash(), tx, usedGas, vmenv)
} }
func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error, bool) { func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error, bool) {

View file

@ -0,0 +1,290 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/params"
"golang.org/x/crypto/sha3"
)
// TestStateProcessorErrors tests the output from the 'core' errors
// as defined in core/error.go. These errors are generated when the
// blockchain imports bad blocks, meaning blocks which have valid headers but
// contain invalid transactions
func TestStateProcessorErrors(t *testing.T) {
var (
config = &params.ChainConfig{
ChainId: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Eip1559Block: big.NewInt(0),
Ethash: new(params.EthashConfig),
}
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
)
var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTx, err := types.SignTx(tx, signer, testKey)
if err != nil {
t.Fatalf("fail to sign tx: %v, err: %v", tx, err)
}
return signedTx
}
var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: gasLimit,
To: &to,
Value: big.NewInt(0),
}), signer, testKey)
return tx
}
{ // Tests against a 'recent' chain definition
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: config,
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
)
defer blockchain.Stop()
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
tooBigNumber := new(big.Int).Set(bigNumber)
tooBigNumber.Add(tooBigNumber, common.Big1)
for i, tt := range []struct {
txs []*types.Transaction
want string
}{
{ // ErrNonceTooLow
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil),
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil),
},
want: "nonce too low: address xdc71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1",
},
{ // ErrNonceTooHigh
txs: []*types.Transaction{
makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil),
},
want: "nonce too high: address xdc71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0",
},
{ // ErrGasLimitReached
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), 21000000, big.NewInt(875000000), nil),
},
want: "gas limit reached",
},
{ // ErrInsufficientFundsForTransfer
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(1000000000000000000), params.TxGas, big.NewInt(875000000), nil),
},
want: "insufficient funds for gas * price + value: address xdc71562b71999873DB5b286dF957af199Ec94617F7",
},
{ // ErrInsufficientFunds
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil),
},
want: "insufficient funds for gas * price + value: address xdc71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 18900000000000000000000",
},
// ErrGasUintOverflow
// One missing 'core' error is ErrGasUintOverflow: "gas uint64 overflow",
// In order to trigger that one, we'd have to allocate a _huge_ chunk of data, such that the
// multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment
{ // ErrIntrinsicGas
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), nil),
},
want: "intrinsic gas too low: have 20000, want 21000",
},
{ // ErrGasLimitReached
txs: []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas*1000, big.NewInt(875000000), nil),
},
want: "gas limit reached",
},
{ // ErrFeeCapTooLow
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(0), big.NewInt(0)),
},
want: "fee cap less than block base fee: address xdc71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 0 baseFee: 875000000",
},
{ // ErrTipVeryHigh
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, tooBigNumber, big.NewInt(1)),
},
want: "max priority fee per gas higher than 2^256-1: address xdc71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas bit length: 257",
},
{ // ErrFeeCapVeryHigh
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), tooBigNumber),
},
want: "max fee per gas higher than 2^256-1: address xdc71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas bit length: 257",
},
{ // ErrTipAboveFeeCap
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(2), big.NewInt(1)),
},
want: "max priority fee per gas higher than max fee per gas: address xdc71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas: 2, maxFeePerGas: 1",
},
{ // ErrInsufficientFunds
// Available balance: 1000000000000000000
// Effective cost: 18375000021000
// FeeCap * gas: 1050000000000000000
// This test is designed to have the effective cost be covered by the balance, but
// the extended requirement on FeeCap*gas < balance to fail
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(50000000000000)),
},
want: "insufficient funds for gas * price + value: address xdc71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 1050000000000000000",
},
{ // Another ErrInsufficientFunds, this one to ensure that feecap/tip of max u256 is allowed
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas, bigNumber, bigNumber),
},
want: "insufficient funds for gas * price + value: address xdc71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
},
}[8:] {
block := GenerateBadBlock(t, genesis, ethash.NewFaker(), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
if err == nil {
t.Fatal("block imported without errors")
}
if have, want := err.Error(), tt.want; have != want {
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
}
}
}
// One final error is ErrTxTypeNotSupported. For this, we need an older chain
{
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainId: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
},
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
)
defer blockchain.Stop()
for i, tt := range []struct {
txs []*types.Transaction
want string
}{
{ // ErrTxTypeNotSupported
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
},
want: "transaction type not supported",
},
} {
block := GenerateBadBlock(t, genesis, ethash.NewFaker(), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
if err == nil {
t.Fatal("block imported without errors")
}
if have, want := err.Error(), tt.want; have != want {
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
}
}
}
}
// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be
// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently
// valid to be considered for import:
// - valid pow (fake), ancestry, difficulty, gaslimit etc
func GenerateBadBlock(t *testing.T, parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block {
header := &types.Header{
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time().Uint64()+10, &types.Header{
Number: parent.Number(),
Time: parent.Time(),
Difficulty: parent.Difficulty(),
UncleHash: parent.UncleHash(),
}),
GasLimit: parent.GasLimit(),
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: new(big.Int).SetUint64(parent.Time().Uint64() + 10),
UncleHash: types.EmptyUncleHash,
}
if config.IsEIP1559(header.Number) {
header.BaseFee = common.BaseFee
}
var receipts []*types.Receipt
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
// Preferably something unique. So let's use a combo of blocknum + txhash
hasher := sha3.NewLegacyKeccak256()
hasher.Write(header.Number.Bytes())
var cumulativeGas uint64
for _, tx := range txs {
txh := tx.Hash()
hasher.Write(txh[:])
receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas())
receipt.TxHash = tx.Hash()
receipt.GasUsed = tx.Gas()
receipts = append(receipts, receipt)
cumulativeGas += tx.Gas()
}
header.Root = common.BytesToHash(hasher.Sum(nil))
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts)
}

View file

@ -23,12 +23,16 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
cmath "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
var emptyCodeHash = crypto.Keccak256Hash(nil)
var ( var (
errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas")
) )
@ -57,6 +61,8 @@ type StateTransition struct {
msg Message msg Message
gas uint64 gas uint64
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
initialGas uint64 initialGas uint64
value *big.Int value *big.Int
data []byte data []byte
@ -71,18 +77,20 @@ type Message interface {
To() *common.Address To() *common.Address
GasPrice() *big.Int GasPrice() *big.Int
GasFeeCap() *big.Int
GasTipCap() *big.Int
Gas() uint64 Gas() uint64
Value() *big.Int Value() *big.Int
Nonce() uint64 Nonce() uint64
CheckNonce() bool IsFake() bool
Data() []byte Data() []byte
BalanceTokenFee() *big.Int BalanceTokenFee() *big.Int
AccessList() types.AccessList AccessList() types.AccessList
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool) (uint64, error) { func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
var gas uint64 var gas uint64
if isContractCreation && isHomestead { if isContractCreation && isHomestead {
@ -90,8 +98,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
} else { } else {
gas = params.TxGas gas = params.TxGas
} }
dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional data // Bump the required gas by the amount of transactional data
if len(data) > 0 { if dataLen > 0 {
// Zero and non-zero bytes are priced differently // Zero and non-zero bytes are priced differently
var nz uint64 var nz uint64
for _, byt := range data { for _, byt := range data {
@ -105,11 +114,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
} }
gas += nz * params.TxDataNonZeroGas gas += nz * params.TxDataNonZeroGas
z := uint64(len(data)) - nz z := dataLen - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
gas += z * params.TxDataZeroGas gas += z * params.TxDataZeroGas
if isContractCreation && isEIP3860 {
lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
return 0, ErrGasUintOverflow
}
gas += lenWords * params.InitCodeWordGas
}
} }
if accessList != nil { if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(len(accessList)) * params.TxAccessListAddressGas
@ -118,16 +135,27 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
return gas, nil return gas, nil
} }
// toWordSize returns the ceiled word size required for init code payment calculation.
func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {
return math.MaxUint64/32 + 1
}
return (size + 31) / 32
}
// NewStateTransition initialises and returns a new state transition object. // NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{ return &StateTransition{
gp: gp, gp: gp,
evm: evm, evm: evm,
msg: msg, msg: msg,
gasPrice: msg.GasPrice(), gasPrice: msg.GasPrice(),
value: msg.Value(), gasFeeCap: msg.GasFeeCap(),
data: msg.Data(), gasTipCap: msg.GasTipCap(),
state: evm.StateDB, value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
} }
} }
@ -171,15 +199,18 @@ func (st *StateTransition) to() vm.AccountRef {
} }
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
var ( mgval := new(big.Int).SetUint64(st.msg.Gas())
state = st.state mgval = mgval.Mul(mgval, st.gasPrice)
balanceTokenFee = st.balanceTokenFee() balanceTokenFee := st.balanceTokenFee()
from = st.from()
)
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if balanceTokenFee == nil { if balanceTokenFee == nil {
if state.GetBalance(from.Address()).Cmp(mgval) < 0 { balanceCheck := mgval
return errInsufficientBalanceForGas if st.gasFeeCap != nil {
balanceCheck = new(big.Int).SetUint64(st.msg.Gas())
balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap)
balanceCheck.Add(balanceCheck, st.value)
}
if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
} }
} else if balanceTokenFee.Cmp(mgval) < 0 { } else if balanceTokenFee.Cmp(mgval) < 0 {
return errInsufficientBalanceForGas return errInsufficientBalanceForGas
@ -191,25 +222,55 @@ func (st *StateTransition) buyGas() error {
st.initialGas = st.msg.Gas() st.initialGas = st.msg.Gas()
if balanceTokenFee == nil { if balanceTokenFee == nil {
state.SubBalance(from.Address(), mgval) st.state.SubBalance(st.msg.From(), mgval)
} }
return nil return nil
} }
func (st *StateTransition) preCheck() error { func (st *StateTransition) preCheck() error {
// Make sure this transaction's nonce is correct // Only check transactions that are not fake
if st.msg.CheckNonce() { msg := st.msg
if !msg.IsFake() {
// Make sure this transaction's nonce is correct. // Make sure this transaction's nonce is correct.
stNonce := st.state.GetNonce(st.from().Address()) stNonce := st.state.GetNonce(msg.From())
if msgNonce := st.msg.Nonce(); stNonce < msgNonce { if msgNonce := msg.Nonce(); stNonce < msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
st.msg.From().Hex(), msgNonce, stNonce) msg.From().Hex(), msgNonce, stNonce)
} else if stNonce > msgNonce { } else if stNonce > msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
st.msg.From().Hex(), msgNonce, stNonce) msg.From().Hex(), msgNonce, stNonce)
} else if stNonce+1 < stNonce { } else if stNonce+1 < stNonce {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
st.msg.From().Hex(), stNonce) msg.From().Hex(), stNonce)
}
// Make sure the sender is an EOA
if codeHash := st.state.GetCodeHash(msg.From()); codeHash != emptyCodeHash && codeHash != (common.Hash{}) {
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
msg.From().Hex(), codeHash)
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber) {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
if !st.evm.Config.NoBaseFee || st.gasFeeCap.BitLen() > 0 || st.gasTipCap.BitLen() > 0 {
if l := st.gasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
msg.From().Hex(), l)
}
if l := st.gasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
msg.From().Hex(), l)
}
if st.gasFeeCap.Cmp(st.gasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From().Hex(), st.gasTipCap, st.gasFeeCap)
}
// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
if st.gasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s baseFee: %s", ErrFeeCapTooLow,
msg.From().Hex(), st.gasFeeCap, st.evm.Context.BaseFee)
}
} }
} }
return st.buyGas() return st.buyGas()
@ -241,16 +302,20 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
// Check clauses 1-3, buy gas if everything is correct // Check clauses 1-3, buy gas if everything is correct
if err = st.preCheck(); err != nil { if err = st.preCheck(); err != nil {
return return nil, 0, false, err, nil
} }
msg := st.msg
sender := st.from() // err checked in preCheck
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) var (
contractCreation := msg.To() == nil msg = st.msg
sender = st.from() // err checked in preCheck
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber)
homestead = rules.IsHomestead
eip3529 = rules.IsEIP1559
contractCreation = msg.To() == nil
)
// Pay intrinsic gas // Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead) gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, rules.IsEIP1559)
if err != nil { if err != nil {
return nil, 0, false, err, nil return nil, 0, false, err, nil
} }
@ -259,7 +324,12 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
} }
st.gas -= gas st.gas -= gas
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsEIP1559 { // Check whether the init code size has been exceeded.
if rules.IsEIP1559 && contractCreation && len(st.data) > params.MaxInitCodeSize {
return nil, 0, false, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize), nil
}
if rules.IsEIP1559 {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
} }
@ -293,22 +363,32 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
return nil, 0, false, vmerr, nil return nil, 0, false, vmerr, nil
} }
} }
st.refundGas() if !eip3529 {
// Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient)
} else {
// After EIP-3529: refunds are capped to gasUsed / 5
st.refundGas(params.RefundQuotientEIP3529)
}
if st.evm.Context.BlockNumber.Cmp(common.TIPTRC21Fee) > 0 { if st.evm.Context.BlockNumber.Cmp(common.TIPTRC21Fee) > 0 {
if (owner != common.Address{}) { if (owner != common.Address{}) {
st.state.AddBalance(owner, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) st.state.AddBalance(owner, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
} }
} else { } else {
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) effectiveTip := st.gasPrice
if st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber) {
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
}
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip))
} }
return ret, st.gasUsed(), vmerr != nil, nil, vmerr return ret, st.gasUsed(), vmerr != nil, nil, vmerr
} }
func (st *StateTransition) refundGas() { func (st *StateTransition) refundGas(refundQuotient uint64) {
// Apply refund counter, capped to half of the used gas. // Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / 2 refund := st.gasUsed() / refundQuotient
if refund > st.state.GetRefund() { if refund > st.state.GetRefund() {
refund = st.state.GetRefund() refund = st.state.GetRefund()
} }

View file

@ -45,9 +45,11 @@ type callMsg struct {
func (m callMsg) From() common.Address { return m.CallMsg.From } func (m callMsg) From() common.Address { return m.CallMsg.From }
func (m callMsg) Nonce() uint64 { return 0 } func (m callMsg) Nonce() uint64 { return 0 }
func (m callMsg) CheckNonce() bool { return false } func (m callMsg) IsFake() bool { return true }
func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) To() *common.Address { return m.CallMsg.To }
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data } func (m callMsg) Data() []byte { return m.CallMsg.Data }

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"errors" "errors"
@ -40,23 +40,23 @@ type devNull struct{}
func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil } func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil }
func (*devNull) Close() error { return nil } func (*devNull) Close() error { return nil }
// txJournal is a rotating log of transactions with the aim of storing locally // journal is a rotating log of transactions with the aim of storing locally
// created transactions to allow non-executed ones to survive node restarts. // created transactions to allow non-executed ones to survive node restarts.
type txJournal struct { type journal struct {
path string // Filesystem path to store the transactions at path string // Filesystem path to store the transactions at
writer io.WriteCloser // Output stream to write new transactions into writer io.WriteCloser // Output stream to write new transactions into
} }
// newTxJournal creates a new transaction journal to // newTxJournal creates a new transaction journal to
func newTxJournal(path string) *txJournal { func newTxJournal(path string) *journal {
return &txJournal{ return &journal{
path: path, path: path,
} }
} }
// load parses a transaction journal dump from disk, loading its contents into // load parses a transaction journal dump from disk, loading its contents into
// the specified pool. // the specified pool.
func (journal *txJournal) load(add func([]*types.Transaction) []error) error { func (journal *journal) load(add func([]*types.Transaction) []error) error {
// Skip the parsing if the journal file doens't exist at all // Skip the parsing if the journal file doens't exist at all
if _, err := os.Stat(journal.path); os.IsNotExist(err) { if _, err := os.Stat(journal.path); os.IsNotExist(err) {
return nil return nil
@ -116,7 +116,7 @@ func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
} }
// insert adds the specified transaction to the local disk journal. // insert adds the specified transaction to the local disk journal.
func (journal *txJournal) insert(tx *types.Transaction) error { func (journal *journal) insert(tx *types.Transaction) error {
if journal.writer == nil { if journal.writer == nil {
return errNoActiveJournal return errNoActiveJournal
} }
@ -128,7 +128,7 @@ func (journal *txJournal) insert(tx *types.Transaction) error {
// rotate regenerates the transaction journal based on the current contents of // rotate regenerates the transaction journal based on the current contents of
// the transaction pool. // the transaction pool.
func (journal *txJournal) rotate(all map[common.Address]types.Transactions) error { func (journal *journal) rotate(all map[common.Address]types.Transactions) error {
// Close the current journal (if any is open) // Close the current journal (if any is open)
if journal.writer != nil { if journal.writer != nil {
if err := journal.writer.Close(); err != nil { if err := journal.writer.Close(); err != nil {
@ -168,7 +168,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro
} }
// close flushes the transaction journal contents to disk and closes the file. // close flushes the transaction journal contents to disk and closes the file.
func (journal *txJournal) close() error { func (journal *journal) close() error {
var err error var err error
if journal.writer != nil { if journal.writer != nil {

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"errors" "errors"
@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/prque" "github.com/XinFinOrg/XDPoSChain/common/prque"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
@ -75,7 +76,7 @@ type blockChainLending interface {
GetBlock(hash common.Hash, number uint64) *types.Block GetBlock(hash common.Hash, number uint64) *types.Block
LendingStateAt(block *types.Block) (*lendingstate.LendingStateDB, error) LendingStateAt(block *types.Block) (*lendingstate.LendingStateDB, error)
StateAt(root common.Hash) (*state.StateDB, error) StateAt(root common.Hash) (*state.StateDB, error)
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
Engine() consensus.Engine Engine() consensus.Engine
// GetHeader returns the hash corresponding to their hash. // GetHeader returns the hash corresponding to their hash.
GetHeader(common.Hash, uint64) *types.Header GetHeader(common.Hash, uint64) *types.Header
@ -124,7 +125,7 @@ type LendingPool struct {
txFeed event.Feed txFeed event.Feed
scope event.SubscriptionScope scope event.SubscriptionScope
chainHeadCh chan ChainHeadEvent chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription chainHeadSub event.Subscription
signer types.LendingSigner signer types.LendingSigner
mu sync.RWMutex mu sync.RWMutex
@ -161,7 +162,7 @@ func NewLendingPool(chainconfig *params.ChainConfig, chain blockChainLending) *L
queue: make(map[common.Address]*lendingtxList), queue: make(map[common.Address]*lendingtxList),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
all: make(map[common.Hash]*types.LendingTransaction), all: make(map[common.Hash]*types.LendingTransaction),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
} }
pool.locals = newLendingAccountSet(pool.signer) pool.locals = newLendingAccountSet(pool.signer)
pool.reset(nil, chain.CurrentBlock()) pool.reset(nil, chain.CurrentBlock())
@ -334,7 +335,7 @@ func (pool *LendingPool) Stop() {
// SubscribeTxPreEvent registers a subscription of TxPreEvent and // SubscribeTxPreEvent registers a subscription of TxPreEvent and
// starts sending event to the given channel. // starts sending event to the given channel.
func (pool *LendingPool) SubscribeTxPreEvent(ch chan<- LendingTxPreEvent) event.Subscription { func (pool *LendingPool) SubscribeTxPreEvent(ch chan<- core.LendingTxPreEvent) event.Subscription {
return pool.scope.Track(pool.txFeed.Subscribe(ch)) return pool.scope.Track(pool.txFeed.Subscribe(ch))
} }
@ -514,7 +515,7 @@ func (pool *LendingPool) validateTopupLending(cloneStateDb *state.StateDB, clone
func (pool *LendingPool) validateBalance(cloneStateDb *state.StateDB, cloneLendingStateDb *lendingstate.LendingStateDB, tx *types.LendingTransaction, collateralToken common.Address) error { func (pool *LendingPool) validateBalance(cloneStateDb *state.StateDB, cloneLendingStateDb *lendingstate.LendingStateDB, tx *types.LendingTransaction, collateralToken common.Address) error {
XDPoSEngine, ok := pool.chain.Engine().(*XDPoS.XDPoS) XDPoSEngine, ok := pool.chain.Engine().(*XDPoS.XDPoS)
if !ok { if !ok {
return ErrNotXDPoS return core.ErrNotXDPoS
} }
XDCXServ := XDPoSEngine.GetXDCXService() XDCXServ := XDPoSEngine.GetXDCXService()
lendingServ := XDPoSEngine.GetLendingService() lendingServ := XDPoSEngine.GetLendingService()
@ -641,10 +642,10 @@ func (pool *LendingPool) validateTx(tx *types.LendingTransaction, local bool) er
} }
// Ensure the transaction adheres to nonce lending // Ensure the transaction adheres to nonce lending
if pool.currentLendingState.GetNonce(from.Hash()) > tx.Nonce() { if pool.currentLendingState.GetNonce(from.Hash()) > tx.Nonce() {
return ErrNonceTooLow return core.ErrNonceTooLow
} }
if pool.pendingState.GetNonce(from.Hash())+common.LimitThresholdNonceInQueue < tx.Nonce() { if pool.pendingState.GetNonce(from.Hash())+common.LimitThresholdNonceInQueue < tx.Nonce() {
return ErrNonceTooHigh return core.ErrNonceTooHigh
} }
return nil return nil
@ -778,7 +779,7 @@ func (pool *LendingPool) promoteTx(addr common.Address, hash common.Hash, tx *ty
pool.beats[addr] = time.Now() pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr.Hash(), tx.Nonce()+1) pool.pendingState.SetNonce(addr.Hash(), tx.Nonce()+1)
go pool.txFeed.Send(LendingTxPreEvent{tx}) go pool.txFeed.Send(core.LendingTxPreEvent{Tx: tx})
} }
// AddLocal enqueues a single transaction into the pool if it is valid, marking // AddLocal enqueues a single transaction into the pool if it is valid, marking

View file

@ -1,4 +1,4 @@
package core package txpool
import ( import (
"context" "context"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"io" "io"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"container/heap" "container/heap"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"container/heap" "container/heap"
@ -23,6 +23,7 @@ import (
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
@ -48,30 +49,30 @@ func (h *nonceHeap) Pop() interface{} {
return x return x
} }
// txSortedMap is a nonce->transaction hash map with a heap based index to allow // sortedMap is a nonce->transaction hash map with a heap based index to allow
// iterating over the contents in a nonce-incrementing way. // iterating over the contents in a nonce-incrementing way.
type txSortedMap struct { type sortedMap struct {
items map[uint64]*types.Transaction // Hash map storing the transaction data items map[uint64]*types.Transaction // Hash map storing the transaction data
index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode)
cache types.Transactions // Cache of the transactions already sorted cache types.Transactions // Cache of the transactions already sorted
} }
// newTxSortedMap creates a new nonce-sorted transaction map. // newSortedMap creates a new nonce-sorted transaction map.
func newTxSortedMap() *txSortedMap { func newSortedMap() *sortedMap {
return &txSortedMap{ return &sortedMap{
items: make(map[uint64]*types.Transaction), items: make(map[uint64]*types.Transaction),
index: new(nonceHeap), index: new(nonceHeap),
} }
} }
// Get retrieves the current transactions associated with the given nonce. // Get retrieves the current transactions associated with the given nonce.
func (m *txSortedMap) Get(nonce uint64) *types.Transaction { func (m *sortedMap) Get(nonce uint64) *types.Transaction {
return m.items[nonce] return m.items[nonce]
} }
// Put inserts a new transaction into the map, also updating the map's nonce // Put inserts a new transaction into the map, also updating the map's nonce
// index. If a transaction already exists with the same nonce, it's overwritten. // index. If a transaction already exists with the same nonce, it's overwritten.
func (m *txSortedMap) Put(tx *types.Transaction) { func (m *sortedMap) Put(tx *types.Transaction) {
nonce := tx.Nonce() nonce := tx.Nonce()
if m.items[nonce] == nil { if m.items[nonce] == nil {
heap.Push(m.index, nonce) heap.Push(m.index, nonce)
@ -82,7 +83,7 @@ func (m *txSortedMap) Put(tx *types.Transaction) {
// Forward removes all transactions from the map with a nonce lower than the // Forward removes all transactions from the map with a nonce lower than the
// provided threshold. Every removed transaction is returned for any post-removal // provided threshold. Every removed transaction is returned for any post-removal
// maintenance. // maintenance.
func (m *txSortedMap) Forward(threshold uint64) types.Transactions { func (m *sortedMap) Forward(threshold uint64) types.Transactions {
var removed types.Transactions var removed types.Transactions
// Pop off heap items until the threshold is reached // Pop off heap items until the threshold is reached
@ -103,7 +104,7 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
// Filter, as opposed to 'filter', re-initialises the heap after the operation is done. // Filter, as opposed to 'filter', re-initialises the heap after the operation is done.
// If you want to do several consecutive filterings, it's therefore better to first // If you want to do several consecutive filterings, it's therefore better to first
// do a .filter(func1) followed by .Filter(func2) or reheap() // do a .filter(func1) followed by .Filter(func2) or reheap()
func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions { func (m *sortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
removed := m.filter(filter) removed := m.filter(filter)
// If transactions were removed, the heap and cache are ruined // If transactions were removed, the heap and cache are ruined
if len(removed) > 0 { if len(removed) > 0 {
@ -112,7 +113,7 @@ func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transac
return removed return removed
} }
func (m *txSortedMap) reheap() { func (m *sortedMap) reheap() {
*m.index = make([]uint64, 0, len(m.items)) *m.index = make([]uint64, 0, len(m.items))
for nonce := range m.items { for nonce := range m.items {
*m.index = append(*m.index, nonce) *m.index = append(*m.index, nonce)
@ -123,7 +124,7 @@ func (m *txSortedMap) reheap() {
// filter is identical to Filter, but **does not** regenerate the heap. This method // filter is identical to Filter, but **does not** regenerate the heap. This method
// should only be used if followed immediately by a call to Filter or reheap() // should only be used if followed immediately by a call to Filter or reheap()
func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions { func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
var removed types.Transactions var removed types.Transactions
// Collect all the transactions to filter out // Collect all the transactions to filter out
@ -141,7 +142,7 @@ func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transac
// Cap places a hard limit on the number of items, returning all transactions // Cap places a hard limit on the number of items, returning all transactions
// exceeding that limit. // exceeding that limit.
func (m *txSortedMap) Cap(threshold int) types.Transactions { func (m *sortedMap) Cap(threshold int) types.Transactions {
// Short circuit if the number of items is under the limit // Short circuit if the number of items is under the limit
if len(m.items) <= threshold { if len(m.items) <= threshold {
return nil return nil
@ -166,7 +167,7 @@ func (m *txSortedMap) Cap(threshold int) types.Transactions {
// Remove deletes a transaction from the maintained map, returning whether the // Remove deletes a transaction from the maintained map, returning whether the
// transaction was found. // transaction was found.
func (m *txSortedMap) Remove(nonce uint64) bool { func (m *sortedMap) Remove(nonce uint64) bool {
// Short circuit if no transaction is present // Short circuit if no transaction is present
_, ok := m.items[nonce] _, ok := m.items[nonce]
if !ok { if !ok {
@ -192,7 +193,7 @@ func (m *txSortedMap) Remove(nonce uint64) bool {
// Note, all transactions with nonces lower than start will also be returned to // Note, all transactions with nonces lower than start will also be returned to
// prevent getting into and invalid state. This is not something that should ever // prevent getting into and invalid state. This is not something that should ever
// happen but better to be self correcting than failing! // happen but better to be self correcting than failing!
func (m *txSortedMap) Ready(start uint64) types.Transactions { func (m *sortedMap) Ready(start uint64) types.Transactions {
// Short circuit if no transactions are available // Short circuit if no transactions are available
if m.index.Len() == 0 || (*m.index)[0] > start { if m.index.Len() == 0 || (*m.index)[0] > start {
return nil return nil
@ -210,11 +211,11 @@ func (m *txSortedMap) Ready(start uint64) types.Transactions {
} }
// Len returns the length of the transaction map. // Len returns the length of the transaction map.
func (m *txSortedMap) Len() int { func (m *sortedMap) Len() int {
return len(m.items) return len(m.items)
} }
func (m *txSortedMap) flatten() types.Transactions { func (m *sortedMap) flatten() types.Transactions {
// If the sorting was not cached yet, create and cache it // If the sorting was not cached yet, create and cache it
if m.cache == nil { if m.cache == nil {
m.cache = make(types.Transactions, 0, len(m.items)) m.cache = make(types.Transactions, 0, len(m.items))
@ -229,7 +230,7 @@ func (m *txSortedMap) flatten() types.Transactions {
// Flatten creates a nonce-sorted slice of transactions based on the loosely // Flatten creates a nonce-sorted slice of transactions based on the loosely
// sorted internal representation. The result of the sorting is cached in case // sorted internal representation. The result of the sorting is cached in case
// it's requested again before any modifications are made to the contents. // it's requested again before any modifications are made to the contents.
func (m *txSortedMap) Flatten() types.Transactions { func (m *sortedMap) Flatten() types.Transactions {
// Copy the cache to prevent accidental modifications // Copy the cache to prevent accidental modifications
cache := m.flatten() cache := m.flatten()
txs := make(types.Transactions, len(cache)) txs := make(types.Transactions, len(cache))
@ -239,36 +240,36 @@ func (m *txSortedMap) Flatten() types.Transactions {
// LastElement returns the last element of a flattened list, thus, the // LastElement returns the last element of a flattened list, thus, the
// transaction with the highest nonce // transaction with the highest nonce
func (m *txSortedMap) LastElement() *types.Transaction { func (m *sortedMap) LastElement() *types.Transaction {
cache := m.flatten() cache := m.flatten()
return cache[len(cache)-1] return cache[len(cache)-1]
} }
// txList is a "list" of transactions belonging to an account, sorted by account // list is a "list" of transactions belonging to an account, sorted by account
// nonce. The same type can be used both for storing contiguous transactions for // nonce. The same type can be used both for storing contiguous transactions for
// the executable/pending queue; and for storing gapped transactions for the non- // the executable/pending queue; and for storing gapped transactions for the non-
// executable/future queue, with minor behavioral changes. // executable/future queue, with minor behavioral changes.
type txList struct { type list struct {
strict bool // Whether nonces are strictly continuous or not strict bool // Whether nonces are strictly continuous or not
txs *txSortedMap // Heap indexed sorted hash map of the transactions txs *sortedMap // Heap indexed sorted hash map of the transactions
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance) costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit) gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
} }
// newTxList create a new transaction list for maintaining nonce-indexable fast, // newList create a new transaction list for maintaining nonce-indexable fast,
// gapped, sortable transaction lists. // gapped, sortable transaction lists.
func newTxList(strict bool) *txList { func newList(strict bool) *list {
return &txList{ return &list{
strict: strict, strict: strict,
txs: newTxSortedMap(), txs: newSortedMap(),
costcap: new(big.Int), costcap: new(big.Int),
} }
} }
// Overlaps returns whether the transaction specified has the same nonce as one // Overlaps returns whether the transaction specified has the same nonce as one
// already contained within the list. // already contained within the list.
func (l *txList) Overlaps(tx *types.Transaction) bool { func (l *list) Overlaps(tx *types.Transaction) bool {
return l.txs.Get(tx.Nonce()) != nil return l.txs.Get(tx.Nonce()) != nil
} }
@ -277,22 +278,30 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
// //
// If the new transaction is accepted into the list, the lists' cost and gas // If the new transaction is accepted into the list, the lists' cost and gas
// thresholds are also potentially updated. // thresholds are also potentially updated.
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
// If there's an older better transaction, abort // If there's an older better transaction, abort
old := l.txs.Get(tx.Nonce()) old := l.txs.Get(tx.Nonce())
if old != nil && old.IsSpecialTransaction() { if old != nil && old.IsSpecialTransaction() {
return false, nil return false, nil
} }
if old != nil { if old != nil {
// threshold = oldGP * (100 + priceBump) / 100 if old.GasFeeCapCmp(tx) >= 0 || old.GasTipCapCmp(tx) >= 0 {
return false, nil
}
// thresholdFeeCap = oldFC * (100 + priceBump) / 100
a := big.NewInt(100 + int64(priceBump)) a := big.NewInt(100 + int64(priceBump))
a = a.Mul(a, old.GasPrice()) aFeeCap := new(big.Int).Mul(a, old.GasFeeCap())
aTip := a.Mul(a, old.GasTipCap())
// thresholdTip = oldTip * (100 + priceBump) / 100
b := big.NewInt(100) b := big.NewInt(100)
threshold := a.Div(a, b) thresholdFeeCap := aFeeCap.Div(aFeeCap, b)
// Have to ensure that the new gas price is higher than the old gas thresholdTip := aTip.Div(aTip, b)
// price as well as checking the percentage threshold to ensure that
// Have to ensure that either the new fee cap or tip is higher than the
// old ones as well as checking the percentage threshold to ensure that
// this is accurate for low (Wei-level) gas price replacements // this is accurate for low (Wei-level) gas price replacements
if old.GasPriceCmp(tx) >= 0 || tx.GasPriceIntCmp(threshold) < 0 { if tx.GasFeeCapIntCmp(thresholdFeeCap) < 0 || tx.GasTipCapIntCmp(thresholdTip) < 0 {
return false, nil return false, nil
} }
} }
@ -310,7 +319,7 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
// Forward removes all transactions from the list with a nonce lower than the // Forward removes all transactions from the list with a nonce lower than the
// provided threshold. Every removed transaction is returned for any post-removal // provided threshold. Every removed transaction is returned for any post-removal
// maintenance. // maintenance.
func (l *txList) Forward(threshold uint64) types.Transactions { func (l *list) Forward(threshold uint64) types.Transactions {
return l.txs.Forward(threshold) return l.txs.Forward(threshold)
} }
@ -323,7 +332,7 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
// a point in calculating all the costs or if the balance covers all. If the threshold // a point in calculating all the costs or if the balance covers all. If the threshold
// is lower than the costgas cap, the caps will be reset to a new high after removing // is lower than the costgas cap, the caps will be reset to a new high after removing
// the newly invalidated transactions. // the newly invalidated transactions.
func (l *txList) Filter(costLimit *big.Int, gasLimit uint64, trc21Issuers map[common.Address]*big.Int, number *big.Int) (types.Transactions, types.Transactions) { func (l *list) Filter(costLimit *big.Int, gasLimit uint64, trc21Issuers map[common.Address]*big.Int, number *big.Int) (types.Transactions, types.Transactions) {
// If all transactions are below the threshold, short circuit // If all transactions are below the threshold, short circuit
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit { if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
return nil, nil return nil, nil
@ -362,14 +371,14 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64, trc21Issuers map[co
// Cap places a hard limit on the number of items, returning all transactions // Cap places a hard limit on the number of items, returning all transactions
// exceeding that limit. // exceeding that limit.
func (l *txList) Cap(threshold int) types.Transactions { func (l *list) Cap(threshold int) types.Transactions {
return l.txs.Cap(threshold) return l.txs.Cap(threshold)
} }
// Remove deletes a transaction from the maintained list, returning whether the // Remove deletes a transaction from the maintained list, returning whether the
// transaction was found, and also returning any transaction invalidated due to // transaction was found, and also returning any transaction invalidated due to
// the deletion (strict mode only). // the deletion (strict mode only).
func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) { func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) {
// Remove the transaction from the set // Remove the transaction from the set
nonce := tx.Nonce() nonce := tx.Nonce()
if removed := l.txs.Remove(nonce); !removed { if removed := l.txs.Remove(nonce); !removed {
@ -389,172 +398,206 @@ func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
// Note, all transactions with nonces lower than start will also be returned to // Note, all transactions with nonces lower than start will also be returned to
// prevent getting into and invalid state. This is not something that should ever // prevent getting into and invalid state. This is not something that should ever
// happen but better to be self correcting than failing! // happen but better to be self correcting than failing!
func (l *txList) Ready(start uint64) types.Transactions { func (l *list) Ready(start uint64) types.Transactions {
return l.txs.Ready(start) return l.txs.Ready(start)
} }
// Len returns the length of the transaction list. // Len returns the length of the transaction list.
func (l *txList) Len() int { func (l *list) Len() int {
return l.txs.Len() return l.txs.Len()
} }
// Empty returns whether the list of transactions is empty or not. // Empty returns whether the list of transactions is empty or not.
func (l *txList) Empty() bool { func (l *list) Empty() bool {
return l.Len() == 0 return l.Len() == 0
} }
// Flatten creates a nonce-sorted slice of transactions based on the loosely // Flatten creates a nonce-sorted slice of transactions based on the loosely
// sorted internal representation. The result of the sorting is cached in case // sorted internal representation. The result of the sorting is cached in case
// it's requested again before any modifications are made to the contents. // it's requested again before any modifications are made to the contents.
func (l *txList) Flatten() types.Transactions { func (l *list) Flatten() types.Transactions {
return l.txs.Flatten() return l.txs.Flatten()
} }
// LastElement returns the last element of a flattened list, thus, the // LastElement returns the last element of a flattened list, thus, the
// transaction with the highest nonce // transaction with the highest nonce
func (l *txList) LastElement() *types.Transaction { func (l *list) LastElement() *types.Transaction {
return l.txs.LastElement() return l.txs.LastElement()
} }
// priceHeap is a heap.Interface implementation over transactions for retrieving // priceHeap is a heap.Interface implementation over transactions for retrieving
// price-sorted transactions to discard when the pool fills up. // price-sorted transactions to discard when the pool fills up. If baseFee is set
type priceHeap []*types.Transaction // then the heap is sorted based on the effective tip based on the given base fee.
// If baseFee is nil then the sorting is based on gasFeeCap.
type priceHeap struct {
baseFee *big.Int // heap should always be re-sorted after baseFee is changed
list []*types.Transaction
}
func (h priceHeap) Len() int { return len(h) } func (h *priceHeap) Len() int { return len(h.list) }
func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *priceHeap) Swap(i, j int) { h.list[i], h.list[j] = h.list[j], h.list[i] }
func (h priceHeap) Less(i, j int) bool { func (h *priceHeap) Less(i, j int) bool {
// Sort primarily by price, returning the cheaper one switch h.cmp(h.list[i], h.list[j]) {
switch h[i].GasPriceCmp(h[j]) {
case -1: case -1:
return true return true
case 1: case 1:
return false return false
default:
return h.list[i].Nonce() > h.list[j].Nonce()
} }
// If the prices match, stabilize via nonces (high nonce is worse) }
return h[i].Nonce() > h[j].Nonce()
func (h *priceHeap) cmp(a, b *types.Transaction) int {
if h.baseFee != nil {
// Compare effective tips if baseFee is specified
if c := a.EffectiveGasTipCmp(b, h.baseFee); c != 0 {
return c
}
}
// Compare fee caps if baseFee is not specified or effective tips are equal
if c := a.GasFeeCapCmp(b); c != 0 {
return c
}
// Compare tips if effective tips and fee caps are equal
return a.GasTipCapCmp(b)
} }
func (h *priceHeap) Push(x interface{}) { func (h *priceHeap) Push(x interface{}) {
*h = append(*h, x.(*types.Transaction)) tx := x.(*types.Transaction)
h.list = append(h.list, tx)
} }
func (h *priceHeap) Pop() interface{} { func (h *priceHeap) Pop() interface{} {
old := *h old := h.list
n := len(old) n := len(old)
x := old[n-1] x := old[n-1]
*h = old[0 : n-1] old[n-1] = nil
h.list = old[0 : n-1]
return x return x
} }
// txPricedList is a price-sorted heap to allow operating on transactions pool // pricedList is a price-sorted heap to allow operating on transactions pool
// contents in a price-incrementing way. It's built opon the all transactions // contents in a price-incrementing way. It's built opon the all transactions
// in txpool but only interested in the remote part. It means only remote transactions // in txpool but only interested in the remote part. It means only remote transactions
// will be considered for tracking, sorting, eviction, etc. // will be considered for tracking, sorting, eviction, etc.
type txPricedList struct { //
all *txLookup // Pointer to the map of all transactions // Two heaps are used for sorting: the urgent heap (based on effective tip in the next
remotes *priceHeap // Heap of prices of all the stored **remote** transactions // block) and the floating heap (based on gasFeeCap). Always the bigger heap is chosen for
stales int64 // Number of stale price points to (re-heap trigger) // eviction. Transactions evicted from the urgent heap are first demoted into the floating heap.
reheapMu sync.Mutex // Mutex asserts that only one routine is reheaping the list // In some cases (during a congestion, when blocks are full) the urgent heap can provide
// better candidates for inclusion while in other cases (at the top of the baseFee peak)
// the floating heap is better. When baseFee is decreasing they behave similarly.
type pricedList struct {
all *lookup // Pointer to the map of all transactions
urgent, floating priceHeap // Heaps of prices of all the stored **remote** transactions
stales int64 // Number of stale price points to (re-heap trigger)
reheapMu sync.Mutex // Mutex asserts that only one routine is reheaping the list
} }
// newTxPricedList creates a new price-sorted transaction heap. const (
func newTxPricedList(all *txLookup) *txPricedList { // urgentRatio : floatingRatio is the capacity ratio of the two queues
return &txPricedList{ urgentRatio = 4
all: all, floatingRatio = 1
remotes: new(priceHeap), )
// newPricedList creates a new price-sorted transaction heap.
func newPricedList(all *lookup) *pricedList {
return &pricedList{
all: all,
} }
} }
// Put inserts a new transaction into the heap. // Put inserts a new transaction into the heap.
func (l *txPricedList) Put(tx *types.Transaction, local bool) { func (l *pricedList) Put(tx *types.Transaction, local bool) {
if local { if local {
return return
} }
heap.Push(l.remotes, tx) // Insert every new transaction to the urgent heap first; Discard will balance the heaps
heap.Push(&l.urgent, tx)
} }
// Removed notifies the prices transaction list that an old transaction dropped // Removed notifies the prices transaction list that an old transaction dropped
// from the pool. The list will just keep a counter of stale objects and update // from the pool. The list will just keep a counter of stale objects and update
// the heap if a large enough ratio of transactions go stale. // the heap if a large enough ratio of transactions go stale.
func (l *txPricedList) Removed(count int) { func (l *pricedList) Removed(count int) {
// Bump the stale counter, but exit if still too low (< 25%) // Bump the stale counter, but exit if still too low (< 25%)
stales := atomic.AddInt64(&l.stales, int64(count)) stales := atomic.AddInt64(&l.stales, int64(count))
if int(stales) <= len(*l.remotes)/4 { if int(stales) <= (len(l.urgent.list)+len(l.floating.list))/4 {
return return
} }
// Seems we've reached a critical number of stale transactions, reheap // Seems we've reached a critical number of stale transactions, reheap
l.Reheap() l.Reheap()
} }
// Cap finds all the transactions below the given price threshold, drops them
// from the priced list and returns them for further removal from the entire pool.
//
// Note: only remote transactions will be considered for eviction.
func (l *txPricedList) Cap(threshold *big.Int) types.Transactions {
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
for len(*l.remotes) > 0 {
// Discard stale transactions if found during cleanup
cheapest := (*l.remotes)[0]
if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated
heap.Pop(l.remotes)
l.stales--
continue
}
// Stop the discards if we've reached the threshold
if cheapest.GasPriceIntCmp(threshold) >= 0 {
break
}
heap.Pop(l.remotes)
drop = append(drop, cheapest)
}
return drop
}
// Underpriced checks whether a transaction is cheaper than (or as cheap as) the // Underpriced checks whether a transaction is cheaper than (or as cheap as) the
// lowest priced (remote) transaction currently being tracked. // lowest priced (remote) transaction currently being tracked.
func (l *txPricedList) Underpriced(tx *types.Transaction) bool { func (l *pricedList) Underpriced(tx *types.Transaction) bool {
// Note: with two queues, being underpriced is defined as being worse than the worst item
// in all non-empty queues if there is any. If both queues are empty then nothing is underpriced.
return (l.underpricedFor(&l.urgent, tx) || len(l.urgent.list) == 0) &&
(l.underpricedFor(&l.floating, tx) || len(l.floating.list) == 0) &&
(len(l.urgent.list) != 0 || len(l.floating.list) != 0)
}
// underpricedFor checks whether a transaction is cheaper than (or as cheap as) the
// lowest priced (remote) transaction in the given heap.
func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool {
// Discard stale price points if found at the heap start // Discard stale price points if found at the heap start
for len(*l.remotes) > 0 { for len(h.list) > 0 {
head := []*types.Transaction(*l.remotes)[0] head := h.list[0]
if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated
atomic.AddInt64(&l.stales, -1) atomic.AddInt64(&l.stales, -1)
heap.Pop(l.remotes) heap.Pop(h)
continue continue
} }
break break
} }
// Check if the transaction is underpriced or not // Check if the transaction is underpriced or not
if len(*l.remotes) == 0 { if len(h.list) == 0 {
return false // There is no remote transaction at all. return false // There is no remote transaction at all.
} }
// If the remote transaction is even cheaper than the // If the remote transaction is even cheaper than the
// cheapest one tracked locally, reject it. // cheapest one tracked locally, reject it.
cheapest := []*types.Transaction(*l.remotes)[0] return h.cmp(h.list[0], tx) >= 0
return cheapest.GasPriceCmp(tx) >= 0
} }
// Discard finds a number of most underpriced transactions, removes them from the // Discard finds a number of most underpriced transactions, removes them from the
// priced list and returns them for further removal from the entire pool. // priced list and returns them for further removal from the entire pool.
// //
// Note local transaction won't be considered for eviction. // Note local transaction won't be considered for eviction.
func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) { func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) {
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
for len(*l.remotes) > 0 && slots > 0 { for slots > 0 {
// Discard stale transactions if found during cleanup if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio || floatingRatio == 0 {
tx := heap.Pop(l.remotes).(*types.Transaction) // Discard stale transactions if found during cleanup
if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated tx := heap.Pop(&l.urgent).(*types.Transaction)
atomic.AddInt64(&l.stales, -1) if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
continue atomic.AddInt64(&l.stales, -1)
continue
}
// Non stale transaction found, move to floating heap
heap.Push(&l.floating, tx)
} else {
if len(l.floating.list) == 0 {
// Stop if both heaps are empty
break
}
// Discard stale transactions if found during cleanup
tx := heap.Pop(&l.floating).(*types.Transaction)
if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
atomic.AddInt64(&l.stales, -1)
continue
}
// Non stale transaction found, discard it
drop = append(drop, tx)
slots -= numSlots(tx)
} }
// Non stale transaction found, discard it
drop = append(drop, tx)
slots -= numSlots(tx)
} }
// If we still can't make enough room for the new transaction // If we still can't make enough room for the new transaction
if slots > 0 && !force { if slots > 0 && !force {
for _, tx := range drop { for _, tx := range drop {
heap.Push(l.remotes, tx) heap.Push(&l.urgent, tx)
} }
return nil, false return nil, false
} }
@ -562,16 +605,35 @@ func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool)
} }
// Reheap forcibly rebuilds the heap based on the current remote transaction set. // Reheap forcibly rebuilds the heap based on the current remote transaction set.
func (l *txPricedList) Reheap() { func (l *pricedList) Reheap() {
l.reheapMu.Lock() l.reheapMu.Lock()
defer l.reheapMu.Unlock() defer l.reheapMu.Unlock()
reheap := make(priceHeap, 0, l.all.RemoteCount()) start := time.Now()
atomic.StoreInt64(&l.stales, 0) atomic.StoreInt64(&l.stales, 0)
l.remotes = &reheap l.urgent.list = make([]*types.Transaction, 0, l.all.RemoteCount())
l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
*l.remotes = append(*l.remotes, tx) l.urgent.list = append(l.urgent.list, tx)
return true return true
}, false, true) // Only iterate remotes }, false, true) // Only iterate remotes
heap.Init(l.remotes) heap.Init(&l.urgent)
// balance out the two heaps by moving the worse half of transactions into the
// floating heap
// Note: Discard would also do this before the first eviction but Reheap can do
// is more efficiently. Also, Underpriced would work suboptimally the first time
// if the floating queue was empty.
floatingCount := len(l.urgent.list) * floatingRatio / (urgentRatio + floatingRatio)
l.floating.list = make([]*types.Transaction, floatingCount)
for i := 0; i < floatingCount; i++ {
l.floating.list[i] = heap.Pop(&l.urgent).(*types.Transaction)
}
heap.Init(&l.floating)
reheapTimer.Update(time.Since(start))
}
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
// necessary to call right before SetBaseFee when processing a new block.
func (l *pricedList) SetBaseFee(baseFee *big.Int) {
l.urgent.baseFee = baseFee
l.Reheap()
} }

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"math/big" "math/big"
@ -27,7 +27,7 @@ import (
// Tests that transactions can be added to strict lists and list contents and // Tests that transactions can be added to strict lists and list contents and
// nonce boundaries are correctly maintained. // nonce boundaries are correctly maintained.
func TestStrictTxListAdd(t *testing.T) { func TestStrictListAdd(t *testing.T) {
// Generate a list of transactions to insert // Generate a list of transactions to insert
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
@ -36,9 +36,9 @@ func TestStrictTxListAdd(t *testing.T) {
txs[i] = transaction(uint64(i), 0, key) txs[i] = transaction(uint64(i), 0, key)
} }
// Insert the transactions in a random order // Insert the transactions in a random order
list := newTxList(true) list := newList(true)
for _, v := range rand.Perm(len(txs)) { for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump) list.Add(txs[v], DefaultConfig.PriceBump)
} }
// Verify internal state // Verify internal state
if len(list.txs.items) != len(txs) { if len(list.txs.items) != len(txs) {
@ -51,7 +51,7 @@ func TestStrictTxListAdd(t *testing.T) {
} }
} }
func BenchmarkTxListAdd(t *testing.B) { func BenchmarkListAdd(t *testing.B) {
// Generate a list of transactions to insert // Generate a list of transactions to insert
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
@ -60,11 +60,11 @@ func BenchmarkTxListAdd(t *testing.B) {
txs[i] = transaction(uint64(i), 0, key) txs[i] = transaction(uint64(i), 0, key)
} }
// Insert the transactions in a random order // Insert the transactions in a random order
list := newTxList(true) list := newList(true)
priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit)) priceLimit := big.NewInt(int64(DefaultConfig.PriceLimit))
t.ResetTimer() t.ResetTimer()
for _, v := range rand.Perm(len(txs)) { for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump) list.Add(txs[v], DefaultConfig.PriceBump)
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump, nil, nil) list.Filter(priceLimit, DefaultConfig.PriceBump, nil, nil)
} }
} }

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"sync" "sync"
@ -23,18 +23,18 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
) )
// txNoncer is a tiny virtual state database to manage the executable nonces of // noncer is a tiny virtual state database to manage the executable nonces of
// accounts in the pool, falling back to reading from a real state database if // accounts in the pool, falling back to reading from a real state database if
// an account is unknown. // an account is unknown.
type txNoncer struct { type noncer struct {
fallback *state.StateDB fallback *state.StateDB
nonces map[common.Address]uint64 nonces map[common.Address]uint64
lock sync.Mutex lock sync.Mutex
} }
// newTxNoncer creates a new virtual state database to track the pool nonces. // newNoncer creates a new virtual state database to track the pool nonces.
func newTxNoncer(statedb *state.StateDB) *txNoncer { func newNoncer(statedb *state.StateDB) *noncer {
return &txNoncer{ return &noncer{
fallback: statedb.Copy(), fallback: statedb.Copy(),
nonces: make(map[common.Address]uint64), nonces: make(map[common.Address]uint64),
} }
@ -42,21 +42,23 @@ func newTxNoncer(statedb *state.StateDB) *txNoncer {
// get returns the current nonce of an account, falling back to a real state // get returns the current nonce of an account, falling back to a real state
// database if the account is unknown. // database if the account is unknown.
func (txn *txNoncer) get(addr common.Address) uint64 { func (txn *noncer) get(addr common.Address) uint64 {
// We use mutex for get operation is the underlying // We use mutex for get operation is the underlying
// state will mutate db even for read access. // state will mutate db even for read access.
txn.lock.Lock() txn.lock.Lock()
defer txn.lock.Unlock() defer txn.lock.Unlock()
if _, ok := txn.nonces[addr]; !ok { if _, ok := txn.nonces[addr]; !ok {
txn.nonces[addr] = txn.fallback.GetNonce(addr) if nonce := txn.fallback.GetNonce(addr); nonce != 0 {
txn.nonces[addr] = nonce
}
} }
return txn.nonces[addr] return txn.nonces[addr]
} }
// set inserts a new virtual nonce into the virtual state database to be returned // set inserts a new virtual nonce into the virtual state database to be returned
// whenever the pool requests it instead of reaching into the real state database. // whenever the pool requests it instead of reaching into the real state database.
func (txn *txNoncer) set(addr common.Address, nonce uint64) { func (txn *noncer) set(addr common.Address, nonce uint64) {
txn.lock.Lock() txn.lock.Lock()
defer txn.lock.Unlock() defer txn.lock.Unlock()
@ -65,15 +67,25 @@ func (txn *txNoncer) set(addr common.Address, nonce uint64) {
// setIfLower updates a new virtual nonce into the virtual state database if the // setIfLower updates a new virtual nonce into the virtual state database if the
// the new one is lower. // the new one is lower.
func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) { func (txn *noncer) setIfLower(addr common.Address, nonce uint64) {
txn.lock.Lock() txn.lock.Lock()
defer txn.lock.Unlock() defer txn.lock.Unlock()
if _, ok := txn.nonces[addr]; !ok { if _, ok := txn.nonces[addr]; !ok {
txn.nonces[addr] = txn.fallback.GetNonce(addr) if nonce := txn.fallback.GetNonce(addr); nonce != 0 {
txn.nonces[addr] = nonce
}
} }
if txn.nonces[addr] <= nonce { if txn.nonces[addr] <= nonce {
return return
} }
txn.nonces[addr] = nonce txn.nonces[addr] = nonce
} }
// setAll sets the nonces for all accounts to the given map.
func (txn *noncer) setAll(all map[common.Address]uint64) {
txn.lock.Lock()
defer txn.lock.Unlock()
txn.nonces = all
}

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"errors" "errors"
@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/prque" "github.com/XinFinOrg/XDPoSChain/common/prque"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
@ -84,7 +85,7 @@ type blockChainXDCx interface {
GetBlock(hash common.Hash, number uint64) *types.Block GetBlock(hash common.Hash, number uint64) *types.Block
OrderStateAt(block *types.Block) (*tradingstate.TradingStateDB, error) OrderStateAt(block *types.Block) (*tradingstate.TradingStateDB, error)
StateAt(root common.Hash) (*state.StateDB, error) StateAt(root common.Hash) (*state.StateDB, error)
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
Engine() consensus.Engine Engine() consensus.Engine
// GetHeader returns the hash corresponding to their hash. // GetHeader returns the hash corresponding to their hash.
GetHeader(common.Hash, uint64) *types.Header GetHeader(common.Hash, uint64) *types.Header
@ -133,7 +134,7 @@ type OrderPool struct {
txFeed event.Feed txFeed event.Feed
scope event.SubscriptionScope scope event.SubscriptionScope
chainHeadCh chan ChainHeadEvent chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription chainHeadSub event.Subscription
signer types.OrderSigner signer types.OrderSigner
mu sync.RWMutex mu sync.RWMutex
@ -170,7 +171,7 @@ func NewOrderPool(chainconfig *params.ChainConfig, chain blockChainXDCx) *OrderP
queue: make(map[common.Address]*ordertxList), queue: make(map[common.Address]*ordertxList),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
all: make(map[common.Hash]*types.OrderTransaction), all: make(map[common.Hash]*types.OrderTransaction),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
} }
pool.locals = newOrderAccountSet(pool.signer) pool.locals = newOrderAccountSet(pool.signer)
pool.reset(nil, chain.CurrentBlock()) pool.reset(nil, chain.CurrentBlock())
@ -340,7 +341,7 @@ func (pool *OrderPool) Stop() {
// SubscribeTxPreEvent registers a subscription of TxPreEvent and // SubscribeTxPreEvent registers a subscription of TxPreEvent and
// starts sending event to the given channel. // starts sending event to the given channel.
func (pool *OrderPool) SubscribeTxPreEvent(ch chan<- OrderTxPreEvent) event.Subscription { func (pool *OrderPool) SubscribeTxPreEvent(ch chan<- core.OrderTxPreEvent) event.Subscription {
return pool.scope.Track(pool.txFeed.Subscribe(ch)) return pool.scope.Track(pool.txFeed.Subscribe(ch))
} }
@ -464,7 +465,7 @@ func (pool *OrderPool) validateOrder(tx *types.OrderTransaction) error {
if orderType == OrderTypeLimit { if orderType == OrderTypeLimit {
XDPoSEngine, ok := pool.chain.Engine().(*XDPoS.XDPoS) XDPoSEngine, ok := pool.chain.Engine().(*XDPoS.XDPoS)
if !ok { if !ok {
return ErrNotXDPoS return core.ErrNotXDPoS
} }
XDCXServ := XDPoSEngine.GetXDCXService() XDCXServ := XDPoSEngine.GetXDCXService()
if XDCXServ == nil { if XDCXServ == nil {
@ -550,10 +551,10 @@ func (pool *OrderPool) validateTx(tx *types.OrderTransaction, local bool) error
} }
// Ensure the transaction adheres to nonce ordering // Ensure the transaction adheres to nonce ordering
if pool.currentOrderState.GetNonce(from.Hash()) > tx.Nonce() { if pool.currentOrderState.GetNonce(from.Hash()) > tx.Nonce() {
return ErrNonceTooLow return core.ErrNonceTooLow
} }
if pool.pendingState.GetNonce(from.Hash())+common.LimitThresholdNonceInQueue < tx.Nonce() { if pool.pendingState.GetNonce(from.Hash())+common.LimitThresholdNonceInQueue < tx.Nonce() {
return ErrNonceTooHigh return core.ErrNonceTooHigh
} }
return nil return nil
@ -603,7 +604,7 @@ func (pool *OrderPool) add(tx *types.OrderTransaction, local bool) (bool, error)
pool.journalTx(from, tx) pool.journalTx(from, tx)
log.Debug("Pooled new executable transaction", "hash", hash, "useraddress", tx.UserAddress().Hex(), "nonce", tx.Nonce(), "status", tx.Status(), "orderid", tx.OrderID()) log.Debug("Pooled new executable transaction", "hash", hash, "useraddress", tx.UserAddress().Hex(), "nonce", tx.Nonce(), "status", tx.Status(), "orderid", tx.OrderID())
go pool.txFeed.Send(OrderTxPreEvent{tx}) go pool.txFeed.Send(core.OrderTxPreEvent{Tx: tx})
return old != nil, nil return old != nil, nil
} }
@ -690,7 +691,7 @@ func (pool *OrderPool) promoteTx(addr common.Address, hash common.Hash, tx *type
pool.beats[addr] = time.Now() pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr.Hash(), tx.Nonce()+1) pool.pendingState.SetNonce(addr.Hash(), tx.Nonce()+1)
log.Debug("promoteTx txFeed.Send", "addr", tx.UserAddress().Hex(), "nonce", tx.Nonce(), "ohash", tx.OrderHash().Hex(), "status", tx.Status(), "orderid", tx.OrderID()) log.Debug("promoteTx txFeed.Send", "addr", tx.UserAddress().Hex(), "nonce", tx.Nonce(), "ohash", tx.OrderHash().Hex(), "status", tx.Status(), "orderid", tx.OrderID())
go pool.txFeed.Send(OrderTxPreEvent{tx}) go pool.txFeed.Send(core.OrderTxPreEvent{Tx: tx})
} }
// AddLocal enqueues a single transaction into the pool if it is valid, marking // AddLocal enqueues a single transaction into the pool if it is valid, marking

View file

@ -1,4 +1,4 @@
package core package txpool
import ( import (
"context" "context"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"io" "io"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"container/heap" "container/heap"

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package txpool
import ( import (
"errors" "errors"
@ -29,6 +29,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/prque" "github.com/XinFinOrg/XDPoSChain/common/prque"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
@ -62,10 +64,6 @@ var (
// ErrInvalidSender is returned if the transaction contains an invalid signature. // ErrInvalidSender is returned if the transaction contains an invalid signature.
ErrInvalidSender = errors.New("invalid sender") ErrInvalidSender = errors.New("invalid sender")
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
// one present in the local chain.
ErrNonceTooLow = errors.New("nonce too low")
// ErrUnderpriced is returned if a transaction's gas price is below the minimum // ErrUnderpriced is returned if a transaction's gas price is below the minimum
// configured for the transaction pool. // configured for the transaction pool.
ErrUnderpriced = errors.New("transaction underpriced") ErrUnderpriced = errors.New("transaction underpriced")
@ -78,14 +76,6 @@ var (
// with a different one without the required price bump. // with a different one without the required price bump.
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
// than required to start the invocation.
ErrIntrinsicGas = errors.New("intrinsic gas too low")
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the // ErrGasLimit is returned if a transaction's requested gas limit exceeds the
// maximum allowance of the current block. // maximum allowance of the current block.
ErrGasLimit = errors.New("exceeds block gas limit") ErrGasLimit = errors.New("exceeds block gas limit")
@ -134,10 +124,21 @@ var (
underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil)
overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil)
// throttleTxMeter counts how many transactions are rejected due to too-many-changes between
// txpool reorgs.
throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil)
// reorgDurationTimer measures how long time a txpool reorg takes.
reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil)
// dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected
// that this number is pretty low, since txpool reorgs happen very frequently.
dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015))
pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil)
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
localGauge = metrics.NewRegisteredGauge("txpool/local", nil) localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
) )
// TxStatus is the current status of a transaction as seen by the pool. // TxStatus is the current status of a transaction as seen by the pool.
@ -156,7 +157,7 @@ type blockChain interface {
CurrentBlock() *types.Block CurrentBlock() *types.Block
GetBlock(hash common.Hash, number uint64) *types.Block GetBlock(hash common.Hash, number uint64) *types.Block
StateAt(root common.Hash) (*state.StateDB, error) StateAt(root common.Hash) (*state.StateDB, error)
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
// Engine retrieves the chain's consensus engine. // Engine retrieves the chain's consensus engine.
Engine() consensus.Engine Engine() consensus.Engine
@ -171,8 +172,8 @@ type blockChain interface {
Config() *params.ChainConfig Config() *params.ChainConfig
} }
// TxPoolConfig are the configuration parameters of the transaction pool. // Config are the configuration parameters of the transaction pool.
type TxPoolConfig struct { type Config struct {
Locals []common.Address // Addresses that should be treated by default as local Locals []common.Address // Addresses that should be treated by default as local
NoLocals bool // Whether local transaction handling should be disabled NoLocals bool // Whether local transaction handling should be disabled
Journal string // Journal of local transactions to survive node restarts Journal string // Journal of local transactions to survive node restarts
@ -189,9 +190,9 @@ type TxPoolConfig struct {
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
} }
// DefaultTxPoolConfig contains the default configurations for the transaction // DefaultConfig contains the default configurations for the transaction
// pool. // pool.
var DefaultTxPoolConfig = TxPoolConfig{ var DefaultConfig = Config{
Journal: "transactions.rlp", Journal: "transactions.rlp",
Rejournal: time.Hour, Rejournal: time.Hour,
@ -199,7 +200,7 @@ var DefaultTxPoolConfig = TxPoolConfig{
PriceBump: 10, PriceBump: 10,
AccountSlots: 16, AccountSlots: 16,
GlobalSlots: 4096, GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio
AccountQueue: 64, AccountQueue: 64,
GlobalQueue: 1024, GlobalQueue: 1024,
@ -208,39 +209,39 @@ var DefaultTxPoolConfig = TxPoolConfig{
// sanitize checks the provided user configurations and changes anything that's // sanitize checks the provided user configurations and changes anything that's
// unreasonable or unworkable. // unreasonable or unworkable.
func (config *TxPoolConfig) sanitize() TxPoolConfig { func (config *Config) sanitize() Config {
conf := *config conf := *config
if conf.Rejournal < time.Second { if conf.Rejournal < time.Second {
log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second)
conf.Rejournal = time.Second conf.Rejournal = time.Second
} }
if conf.PriceLimit < 1 { if conf.PriceLimit < 1 {
log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultTxPoolConfig.PriceLimit) log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit)
conf.PriceLimit = DefaultTxPoolConfig.PriceLimit conf.PriceLimit = DefaultConfig.PriceLimit
} }
if conf.PriceBump < 1 { if conf.PriceBump < 1 {
log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultTxPoolConfig.PriceBump) log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
conf.PriceBump = DefaultTxPoolConfig.PriceBump conf.PriceBump = DefaultConfig.PriceBump
} }
if conf.AccountSlots < 1 { if conf.AccountSlots < 1 {
log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultTxPoolConfig.AccountSlots) log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots)
conf.AccountSlots = DefaultTxPoolConfig.AccountSlots conf.AccountSlots = DefaultConfig.AccountSlots
} }
if conf.GlobalSlots < 1 { if conf.GlobalSlots < 1 {
log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultTxPoolConfig.GlobalSlots) log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots)
conf.GlobalSlots = DefaultTxPoolConfig.GlobalSlots conf.GlobalSlots = DefaultConfig.GlobalSlots
} }
if conf.AccountQueue < 1 { if conf.AccountQueue < 1 {
log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultTxPoolConfig.AccountQueue) log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue)
conf.AccountQueue = DefaultTxPoolConfig.AccountQueue conf.AccountQueue = DefaultConfig.AccountQueue
} }
if conf.GlobalQueue < 1 { if conf.GlobalQueue < 1 {
log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultTxPoolConfig.GlobalQueue) log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue)
conf.GlobalQueue = DefaultTxPoolConfig.GlobalQueue conf.GlobalQueue = DefaultConfig.GlobalQueue
} }
if conf.Lifetime < 1 { if conf.Lifetime < 1 {
log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultTxPoolConfig.Lifetime) log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime)
conf.Lifetime = DefaultTxPoolConfig.Lifetime conf.Lifetime = DefaultConfig.Lifetime
} }
return conf return conf
} }
@ -253,7 +254,7 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
// current state) and future transactions. Transactions move between those // current state) and future transactions. Transactions move between those
// two states over time as they are received and processed. // two states over time as they are received and processed.
type TxPool struct { type TxPool struct {
config TxPoolConfig config Config
chainconfig *params.ChainConfig chainconfig *params.ChainConfig
chain blockChain chain blockChain
gasPrice *big.Int gasPrice *big.Int
@ -262,20 +263,23 @@ type TxPool struct {
signer types.Signer signer types.Signer
mu sync.RWMutex mu sync.RWMutex
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
currentState *state.StateDB // Current state in the blockchain head currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces pendingNonces *noncer // Pending state tracking virtual nonces
currentMaxGas uint64 // Current gas limit for transaction caps currentMaxGas uint64 // Current gas limit for transaction caps
locals *accountSet // Set of local transaction to exempt from eviction rules locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk journal *journal // Journal of local transaction to back up to disk
pending map[common.Address]*txList // All currently processable transactions pending map[common.Address]*list // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions queue map[common.Address]*list // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account beats map[common.Address]time.Time // Last heartbeat from each known account
all *txLookup // All transactions to allow lookups all *lookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price priced *pricedList // All transactions sorted by price
chainHeadCh chan ChainHeadEvent chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription chainHeadSub event.Subscription
reqResetCh chan *txpoolResetRequest reqResetCh chan *txpoolResetRequest
reqPromoteCh chan *accountSet reqPromoteCh chan *accountSet
@ -285,7 +289,8 @@ type TxPool struct {
wg sync.WaitGroup // tracks loop, scheduleReorgLoop wg sync.WaitGroup // tracks loop, scheduleReorgLoop
initDoneCh chan struct{} // is closed once the pool is initialized (for tests) initDoneCh chan struct{} // is closed once the pool is initialized (for tests)
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. changesSinceReorg int // A counter for how many drops we've performed in-between reorg.
IsSigner func(address common.Address) bool IsSigner func(address common.Address) bool
trc21FeeCapacity map[common.Address]*big.Int trc21FeeCapacity map[common.Address]*big.Int
} }
@ -296,7 +301,7 @@ type txpoolResetRequest struct {
// NewTxPool creates a new transaction pool to gather, sort and filter inbound // NewTxPool creates a new transaction pool to gather, sort and filter inbound
// transactions from the network. // transactions from the network.
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool { func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
// Sanitize the input to ensure no vulnerable gas prices are set // Sanitize the input to ensure no vulnerable gas prices are set
config = (&config).sanitize() config = (&config).sanitize()
@ -306,11 +311,11 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
chainconfig: chainconfig, chainconfig: chainconfig,
chain: chain, chain: chain,
signer: types.LatestSigner(chainconfig), signer: types.LatestSigner(chainconfig),
pending: make(map[common.Address]*txList), pending: make(map[common.Address]*list),
queue: make(map[common.Address]*txList), queue: make(map[common.Address]*list),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
all: newTxLookup(), all: newLookup(),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
reqResetCh: make(chan *txpoolResetRequest), reqResetCh: make(chan *txpoolResetRequest),
reqPromoteCh: make(chan *accountSet), reqPromoteCh: make(chan *accountSet),
queueTxEventCh: make(chan *types.Transaction), queueTxEventCh: make(chan *types.Transaction),
@ -325,7 +330,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
log.Info("Setting new local account", "address", addr) log.Info("Setting new local account", "address", addr)
pool.locals.add(addr) pool.locals.add(addr)
} }
pool.priced = newTxPricedList(pool.all) pool.priced = newPricedList(pool.all)
pool.reset(nil, chain.CurrentBlock().Header()) pool.reset(nil, chain.CurrentBlock().Header())
// Start the reorg loop early so it can handle requests generated during journal loading. // Start the reorg loop early so it can handle requests generated during journal loading.
@ -448,7 +453,7 @@ func (pool *TxPool) Stop() {
// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and // SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
// starts sending event to the given channel. // starts sending event to the given channel.
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription { func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return pool.scope.Track(pool.txFeed.Subscribe(ch)) return pool.scope.Track(pool.txFeed.Subscribe(ch))
} }
@ -466,10 +471,18 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
old := pool.gasPrice
pool.gasPrice = price pool.gasPrice = price
for _, tx := range pool.priced.Cap(price) { // if the min miner fee increased, remove transactions below the new threshold
pool.removeTx(tx.Hash(), false) if price.Cmp(old) > 0 {
// pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead
drop := pool.all.RemotesBelowTip(price)
for _, tx := range drop {
pool.removeTx(tx.Hash(), false)
}
pool.priced.Removed(len(drop))
} }
log.Info("Transaction pool price threshold updated", "price", price) log.Info("Transaction pool price threshold updated", "price", price)
} }
@ -511,29 +524,63 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
pending := make(map[common.Address]types.Transactions) pending := make(map[common.Address]types.Transactions, len(pool.pending))
for addr, list := range pool.pending { for addr, list := range pool.pending {
pending[addr] = list.Flatten() pending[addr] = list.Flatten()
} }
queued := make(map[common.Address]types.Transactions) queued := make(map[common.Address]types.Transactions, len(pool.queue))
for addr, list := range pool.queue { for addr, list := range pool.queue {
queued[addr] = list.Flatten() queued[addr] = list.Flatten()
} }
return pending, queued return pending, queued
} }
// ContentFrom retrieves the data content of the transaction pool, returning the
// pending as well as queued transactions of this address, grouped by nonce.
func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
pool.mu.RLock()
defer pool.mu.RUnlock()
var pending types.Transactions
if list, ok := pool.pending[addr]; ok {
pending = list.Flatten()
}
var queued types.Transactions
if list, ok := pool.queue[addr]; ok {
queued = list.Flatten()
}
return pending, queued
}
// Pending retrieves all currently processable transactions, grouped by origin // Pending retrieves all currently processable transactions, grouped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be // account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code. // freely modified by calling code.
func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) { //
// The enforceTips parameter can be used to do an extra filtering on the pending
// transactions and only return those whose **effective** tip is large enough in
// the next pending execution environment.
func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transactions {
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
pending := make(map[common.Address]types.Transactions) pending := make(map[common.Address]types.Transactions)
for addr, list := range pool.pending { for addr, list := range pool.pending {
pending[addr] = list.Flatten() txs := list.Flatten()
// If the miner requests tip enforcement, cap the lists now
if enforceTips && !pool.locals.contains(addr) {
for i, tx := range txs {
if tx.EffectiveGasTipIntCmp(pool.gasPrice, pool.priced.urgent.baseFee) < 0 {
txs = txs[:i]
break
}
}
}
if len(txs) > 0 {
pending[addr] = txs
}
} }
return pending, nil return pending
} }
// Locals retrieves the accounts currently considered local by the pool. // Locals retrieves the accounts currently considered local by the pool.
@ -573,7 +620,11 @@ func (pool *TxPool) GetSender(tx *types.Transaction) (common.Address, error) {
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Accept only legacy transactions until EIP-2718/2930 activates. // Accept only legacy transactions until EIP-2718/2930 activates.
if !pool.eip2718 && tx.Type() != types.LegacyTxType { if !pool.eip2718 && tx.Type() != types.LegacyTxType {
return ErrTxTypeNotSupported return core.ErrTxTypeNotSupported
}
// Reject dynamic fee transactions until EIP-1559 activates.
if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
return core.ErrTxTypeNotSupported
} }
// Reject transactions over defined size to prevent DOS attacks // Reject transactions over defined size to prevent DOS attacks
if uint64(tx.Size()) > txMaxSize { if uint64(tx.Size()) > txMaxSize {
@ -596,23 +647,34 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.currentMaxGas < tx.Gas() { if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit return ErrGasLimit
} }
// Sanity check for extremely large numbers
if tx.GasFeeCap().BitLen() > 256 {
return core.ErrFeeCapVeryHigh
}
if tx.GasTipCap().BitLen() > 256 {
return core.ErrTipVeryHigh
}
// Ensure gasFeeCap is greater than or equal to gasTipCap.
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
return core.ErrTipAboveFeeCap
}
// Make sure the transaction is signed properly. // Make sure the transaction is signed properly.
from, err := types.Sender(pool.signer, tx) from, err := types.Sender(pool.signer, tx)
if err != nil { if err != nil {
return ErrInvalidSender return ErrInvalidSender
} }
// Drop non-local transactions under our own minimal accepted gas price // Drop non-local transactions under our own minimal accepted gas price or tip
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { if !local && tx.GasTipCapIntCmp(pool.gasPrice) < 0 {
if !tx.IsSpecialTransaction() || (pool.IsSigner != nil && !pool.IsSigner(from)) { if !tx.IsSpecialTransaction() || (pool.IsSigner != nil && !pool.IsSigner(from)) {
return ErrUnderpriced return ErrUnderpriced
} }
} }
// Ensure the transaction adheres to nonce ordering // Ensure the transaction adheres to nonce ordering
if pool.currentState.GetNonce(from) > tx.Nonce() { if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow return core.ErrNonceTooLow
} }
if pool.pendingNonces.get(from)+common.LimitThresholdNonceInQueue < tx.Nonce() { if pool.pendingNonces.get(from)+common.LimitThresholdNonceInQueue < tx.Nonce() {
return ErrNonceTooHigh return core.ErrNonceTooHigh
} }
// Transactor should have enough funds to cover the costs // Transactor should have enough funds to cover the costs
// cost == V + GP * GL // cost == V + GP * GL
@ -629,24 +691,24 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if value, ok := pool.trc21FeeCapacity[*tx.To()]; ok { if value, ok := pool.trc21FeeCapacity[*tx.To()]; ok {
feeCapacity = value feeCapacity = value
if !state.ValidateTRC21Tx(pool.currentState, from, *tx.To(), tx.Data()) { if !state.ValidateTRC21Tx(pool.currentState, from, *tx.To(), tx.Data()) {
return ErrInsufficientFunds return core.ErrInsufficientFunds
} }
cost = tx.TxCost(number) cost = tx.TxCost(number)
} }
} }
if new(big.Int).Add(balance, feeCapacity).Cmp(cost) < 0 { if new(big.Int).Add(balance, feeCapacity).Cmp(cost) < 0 {
return ErrInsufficientFunds return core.ErrInsufficientFunds
} }
if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) { if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) {
// Ensure the transaction has more gas than the basic tx fee. // Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true) intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.eip1559)
if err != nil { if err != nil {
return err return err
} }
// Exclude check smart contract sign address. // Exclude check smart contract sign address.
if tx.Gas() < intrGas { if tx.Gas() < intrGas {
return ErrIntrinsicGas return core.ErrIntrinsicGas
} }
// Check zero gas price. // Check zero gas price.
@ -670,13 +732,13 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// validate minFee slot for XDCZ // validate minFee slot for XDCZ
if tx.IsXDCZApplyTransaction() { if tx.IsXDCZApplyTransaction() {
copyState := pool.currentState.Copy() copyState := pool.currentState.Copy()
return ValidateXDCZApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:])) return core.ValidateXDCZApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:]))
} }
// validate balance slot, token decimal for XDCX // validate balance slot, token decimal for XDCX
if tx.IsXDCXApplyTransaction() { if tx.IsXDCXApplyTransaction() {
copyState := pool.currentState.Copy() copyState := pool.currentState.Copy()
return ValidateXDCXApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:])) return core.ValidateXDCXApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:]))
} }
return nil return nil
} }
@ -686,8 +748,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// pending or queued one, it overwrites the previous transaction if its price is higher. // pending or queued one, it overwrites the previous transaction if its price is higher.
// //
// If a newly added transaction is marked as local, its sending account will be // If a newly added transaction is marked as local, its sending account will be
// whitelisted, preventing any associated transaction from being dropped out of the pool // be added to the allowlist, preventing any associated transaction from being dropped
// due to pricing constraints. // out of the pool due to pricing constraints.
func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) {
// If the transaction is already known, discard it // If the transaction is already known, discard it
hash := tx.Hash() hash := tx.Hash()
@ -711,14 +773,22 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
return pool.promoteSpecialTx(from, tx, isLocal) return pool.promoteSpecialTx(from, tx, isLocal)
} }
// If the transaction pool is full, discard underpriced transactions // If the transaction pool is full, discard underpriced transactions
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue {
log.Debug("Add transaction to pool full", "hash", hash, "nonce", tx.Nonce())
// If the new transaction is underpriced, don't accept it // If the new transaction is underpriced, don't accept it
if !isLocal && pool.priced.Underpriced(tx) { if !isLocal && pool.priced.Underpriced(tx) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
underpricedTxMeter.Mark(1) underpricedTxMeter.Mark(1)
return false, ErrUnderpriced return false, ErrUnderpriced
} }
// We're about to replace a transaction. The reorg does a more thorough
// analysis of what to remove and how, but it runs async. We don't want to
// do too many replacements between reorg-runs, so we cap the number of
// replacements to 25% of the slots
if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) {
throttleTxMeter.Mark(1)
return false, ErrTxPoolOverflow
}
// New transaction is better than our worse ones, make room for it. // New transaction is better than our worse ones, make room for it.
// If it's a local transaction, forcibly discard all available transactions. // If it's a local transaction, forcibly discard all available transactions.
// Otherwise if we can't make enough room for new one, abort the operation. // Otherwise if we can't make enough room for new one, abort the operation.
@ -730,9 +800,11 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
overflowedTxMeter.Mark(1) overflowedTxMeter.Mark(1)
return false, ErrTxPoolOverflow return false, ErrTxPoolOverflow
} }
// Bump the counter of rejections-since-reorg
pool.changesSinceReorg += len(drop)
// Kick out the underpriced remote transactions. // Kick out the underpriced remote transactions.
for _, tx := range drop { for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
underpricedTxMeter.Mark(1) underpricedTxMeter.Mark(1)
pool.removeTx(tx.Hash(), false) pool.removeTx(tx.Hash(), false)
} }
@ -788,7 +860,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
// Try to insert the transaction into the future queue // Try to insert the transaction into the future queue
from, _ := types.Sender(pool.signer, tx) // already validated from, _ := types.Sender(pool.signer, tx) // already validated
if pool.queue[from] == nil { if pool.queue[from] == nil {
pool.queue[from] = newTxList(false) pool.queue[from] = newList(false)
} }
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
if !inserted { if !inserted {
@ -840,7 +912,7 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
// Try to insert the transaction into the pending queue // Try to insert the transaction into the pending queue
if pool.pending[addr] == nil { if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true) pool.pending[addr] = newList(true)
} }
list := pool.pending[addr] list := pool.pending[addr]
@ -873,7 +945,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
func (pool *TxPool) promoteSpecialTx(addr common.Address, tx *types.Transaction, isLocal bool) (bool, error) { func (pool *TxPool) promoteSpecialTx(addr common.Address, tx *types.Transaction, isLocal bool) (bool, error) {
// Try to insert the transaction into the pending queue // Try to insert the transaction into the pending queue
if pool.pending[addr] == nil { if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true) pool.pending[addr] = newList(true)
} }
list := pool.pending[addr] list := pool.pending[addr]
@ -904,7 +976,7 @@ func (pool *TxPool) promoteSpecialTx(addr common.Address, tx *types.Transaction,
// Set the potentially new pending nonce and notify any subsystems of the new tx // Set the potentially new pending nonce and notify any subsystems of the new tx
pool.beats[addr] = time.Now() pool.beats[addr] = time.Now()
pool.pendingNonces.set(addr, tx.Nonce()+1) pool.pendingNonces.set(addr, tx.Nonce()+1)
go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}}) go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}})
return true, nil return true, nil
} }
@ -1146,7 +1218,7 @@ func (pool *TxPool) scheduleReorgLoop() {
launchNextRun bool launchNextRun bool
reset *txpoolResetRequest reset *txpoolResetRequest
dirtyAccounts *accountSet dirtyAccounts *accountSet
queuedEvents = make(map[common.Address]*txSortedMap) queuedEvents = make(map[common.Address]*sortedMap)
) )
for { for {
// Launch next background reorg if needed // Launch next background reorg if needed
@ -1159,7 +1231,7 @@ func (pool *TxPool) scheduleReorgLoop() {
launchNextRun = false launchNextRun = false
reset, dirtyAccounts = nil, nil reset, dirtyAccounts = nil, nil
queuedEvents = make(map[common.Address]*txSortedMap) queuedEvents = make(map[common.Address]*sortedMap)
} }
select { select {
@ -1188,7 +1260,7 @@ func (pool *TxPool) scheduleReorgLoop() {
// request one later if they want the events sent. // request one later if they want the events sent.
addr, _ := types.Sender(pool.signer, tx) addr, _ := types.Sender(pool.signer, tx)
if _, ok := queuedEvents[addr]; !ok { if _, ok := queuedEvents[addr]; !ok {
queuedEvents[addr] = newTxSortedMap() queuedEvents[addr] = newSortedMap()
} }
queuedEvents[addr].Put(tx) queuedEvents[addr].Put(tx)
@ -1207,7 +1279,10 @@ func (pool *TxPool) scheduleReorgLoop() {
} }
// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. // runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*txSortedMap) { func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) {
defer func(t0 time.Time) {
reorgDurationTimer.Update(time.Since(t0))
}(time.Now())
defer close(done) defer close(done)
var promoteAddrs []common.Address var promoteAddrs []common.Address
@ -1243,23 +1318,31 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
// because of another transaction (e.g. higher gas price). // because of another transaction (e.g. higher gas price).
if reset != nil { if reset != nil {
pool.demoteUnexecutables() pool.demoteUnexecutables()
if reset.newHead != nil && pool.chainconfig.IsEIP1559(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) {
pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead)
pool.priced.SetBaseFee(pendingBaseFee)
}
// Update all accounts to the latest known pending nonce
nonces := make(map[common.Address]uint64, len(pool.pending))
for addr, list := range pool.pending {
highestPending := list.LastElement()
nonces[addr] = highestPending.Nonce() + 1
}
pool.pendingNonces.setAll(nonces)
} }
// Ensure pool.queue and pool.pending sizes stay within the configured limits. // Ensure pool.queue and pool.pending sizes stay within the configured limits.
pool.truncatePending() pool.truncatePending()
pool.truncateQueue() pool.truncateQueue()
// Update all accounts to the latest known pending nonce dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg))
for addr, list := range pool.pending { pool.changesSinceReorg = 0 // Reset change counter
highestPending := list.LastElement()
pool.pendingNonces.set(addr, highestPending.Nonce()+1)
}
pool.mu.Unlock() pool.mu.Unlock()
// Notify subsystems for newly added transactions // Notify subsystems for newly added transactions
for _, tx := range promoted { for _, tx := range promoted {
addr, _ := types.Sender(pool.signer, tx) addr, _ := types.Sender(pool.signer, tx)
if _, ok := events[addr]; !ok { if _, ok := events[addr]; !ok {
events[addr] = newTxSortedMap() events[addr] = newSortedMap()
} }
events[addr].Put(tx) events[addr].Put(tx)
} }
@ -1268,7 +1351,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
for _, set := range events { for _, set := range events {
txs = append(txs, set.Flatten()...) txs = append(txs, set.Flatten()...)
} }
pool.txFeed.Send(NewTxsEvent{txs}) pool.txFeed.Send(core.NewTxsEvent{Txs: txs})
} }
} }
@ -1295,7 +1378,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
if rem == nil { if rem == nil {
// This can happen if a setHead is performed, where we simply discard the old // This can happen if a setHead is performed, where we simply discard the old
// head from the chain. // head from the chain.
// If that is the case, we don't have the lost transactions any more, and // If that is the case, we don't have the lost transactions anymore, and
// there's nothing to add // there's nothing to add
if newNum >= oldNum { if newNum >= oldNum {
// If we reorged to a same or higher number, then it's not a case of setHead // If we reorged to a same or higher number, then it's not a case of setHead
@ -1349,17 +1432,18 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
} }
pool.currentState = statedb pool.currentState = statedb
pool.trc21FeeCapacity = state.GetTRC21FeeCapacityFromStateWithCache(newHead.Root, statedb) pool.trc21FeeCapacity = state.GetTRC21FeeCapacityFromStateWithCache(newHead.Root, statedb)
pool.pendingNonces = newTxNoncer(statedb) pool.pendingNonces = newNoncer(statedb)
pool.currentMaxGas = newHead.GasLimit pool.currentMaxGas = newHead.GasLimit
// Inject any transactions discarded due to reorgs // Inject any transactions discarded due to reorgs
log.Debug("Reinjecting stale transactions", "count", len(reinject)) log.Debug("Reinjecting stale transactions", "count", len(reinject))
senderCacher.recover(pool.signer, reinject) core.SenderCacher.Recover(pool.signer, reinject)
pool.addTxsLocked(reinject, false) pool.addTxsLocked(reinject, false)
// Update all fork indicator by next pending block number. // Update all fork indicator by next pending block number.
next := new(big.Int).Add(newHead.Number, big.NewInt(1)) next := new(big.Int).Add(newHead.Number, big.NewInt(1))
pool.eip2718 = pool.chainconfig.IsEIP1559(next) pool.eip2718 = pool.chainconfig.IsEIP1559(next)
pool.eip1559 = pool.chainconfig.IsEIP1559(next)
} }
// promoteExecutables moves transactions that have become processable from the // promoteExecutables moves transactions that have become processable from the
@ -1524,7 +1608,7 @@ func (pool *TxPool) truncatePending() {
pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending))
} }
// truncateQueue drops the oldes transactions in the queue if the pool is above the global queue limit. // truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
func (pool *TxPool) truncateQueue() { func (pool *TxPool) truncateQueue() {
queued := uint64(0) queued := uint64(0)
for _, list := range pool.queue { for _, list := range pool.queue {
@ -1541,7 +1625,7 @@ func (pool *TxPool) truncateQueue() {
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
} }
} }
sort.Sort(addresses) sort.Sort(sort.Reverse(addresses))
// Drop transactions until the total is below the limit or only locals remain // Drop transactions until the total is below the limit or only locals remain
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
@ -1572,6 +1656,10 @@ func (pool *TxPool) truncateQueue() {
// demoteUnexecutables removes invalid and processed transactions from the pools // demoteUnexecutables removes invalid and processed transactions from the pools
// executable/pending queue and any subsequent transactions that become unexecutable // executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue. // are moved back into the future queue.
//
// Note: transactions are not marked as removed in the priced list because re-heaping
// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful
// to trigger a re-heap is this function
func (pool *TxPool) demoteUnexecutables() { func (pool *TxPool) demoteUnexecutables() {
// Iterate over all accounts and demote any non-executable transactions // Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending { for addr, list := range pool.pending {
@ -1620,8 +1708,6 @@ func (pool *TxPool) demoteUnexecutables() {
pool.enqueueTx(hash, tx, false, false) pool.enqueueTx(hash, tx, false, false)
} }
pendingGauge.Dec(int64(len(gapped))) pendingGauge.Dec(int64(len(gapped)))
// This might happen in a reorg, so log it to the metering
blockReorgInvalidatedTx.Mark(int64(len(gapped)))
} }
// Delete the entire pending entry if it became empty. // Delete the entire pending entry if it became empty.
if list.Empty() { if list.Empty() {
@ -1654,7 +1740,7 @@ type accountSet struct {
// derivations. // derivations.
func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet {
as := &accountSet{ as := &accountSet{
accounts: make(map[common.Address]struct{}), accounts: make(map[common.Address]struct{}, len(addrs)),
signer: signer, signer: signer,
} }
for _, addr := range addrs { for _, addr := range addrs {
@ -1716,7 +1802,7 @@ func (as *accountSet) merge(other *accountSet) {
as.cache = nil as.cache = nil
} }
// txLookup is used internally by TxPool to track transactions while allowing // lookup is used internally by TxPool to track transactions while allowing
// lookup without mutex contention. // lookup without mutex contention.
// //
// Note, although this type is properly protected against concurrent access, it // Note, although this type is properly protected against concurrent access, it
@ -1728,16 +1814,16 @@ func (as *accountSet) merge(other *accountSet) {
// //
// This lookup set combines the notion of "local transactions", which is useful // This lookup set combines the notion of "local transactions", which is useful
// to build upper-level structure. // to build upper-level structure.
type txLookup struct { type lookup struct {
slots int slots int
lock sync.RWMutex lock sync.RWMutex
locals map[common.Hash]*types.Transaction locals map[common.Hash]*types.Transaction
remotes map[common.Hash]*types.Transaction remotes map[common.Hash]*types.Transaction
} }
// newTxLookup returns a new txLookup structure. // newLookup returns a new lookup structure.
func newTxLookup() *txLookup { func newLookup() *lookup {
return &txLookup{ return &lookup{
locals: make(map[common.Hash]*types.Transaction), locals: make(map[common.Hash]*types.Transaction),
remotes: make(map[common.Hash]*types.Transaction), remotes: make(map[common.Hash]*types.Transaction),
} }
@ -1746,7 +1832,7 @@ func newTxLookup() *txLookup {
// Range calls f on each key and value present in the map. The callback passed // Range calls f on each key and value present in the map. The callback passed
// should return the indicator whether the iteration needs to be continued. // should return the indicator whether the iteration needs to be continued.
// Callers need to specify which set (or both) to be iterated. // Callers need to specify which set (or both) to be iterated.
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1767,7 +1853,7 @@ func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local b
} }
// Get returns a transaction if it exists in the lookup, or nil if not found. // Get returns a transaction if it exists in the lookup, or nil if not found.
func (t *txLookup) Get(hash common.Hash) *types.Transaction { func (t *lookup) Get(hash common.Hash) *types.Transaction {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1778,7 +1864,7 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction {
} }
// GetLocal returns a transaction if it exists in the lookup, or nil if not found. // GetLocal returns a transaction if it exists in the lookup, or nil if not found.
func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction { func (t *lookup) GetLocal(hash common.Hash) *types.Transaction {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1786,7 +1872,7 @@ func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction {
} }
// GetRemote returns a transaction if it exists in the lookup, or nil if not found. // GetRemote returns a transaction if it exists in the lookup, or nil if not found.
func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction { func (t *lookup) GetRemote(hash common.Hash) *types.Transaction {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1794,7 +1880,7 @@ func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction {
} }
// Count returns the current number of transactions in the lookup. // Count returns the current number of transactions in the lookup.
func (t *txLookup) Count() int { func (t *lookup) Count() int {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1802,7 +1888,7 @@ func (t *txLookup) Count() int {
} }
// LocalCount returns the current number of local transactions in the lookup. // LocalCount returns the current number of local transactions in the lookup.
func (t *txLookup) LocalCount() int { func (t *lookup) LocalCount() int {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1810,7 +1896,7 @@ func (t *txLookup) LocalCount() int {
} }
// RemoteCount returns the current number of remote transactions in the lookup. // RemoteCount returns the current number of remote transactions in the lookup.
func (t *txLookup) RemoteCount() int { func (t *lookup) RemoteCount() int {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1818,7 +1904,7 @@ func (t *txLookup) RemoteCount() int {
} }
// Slots returns the current number of slots used in the lookup. // Slots returns the current number of slots used in the lookup.
func (t *txLookup) Slots() int { func (t *lookup) Slots() int {
t.lock.RLock() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()
@ -1826,7 +1912,7 @@ func (t *txLookup) Slots() int {
} }
// Add adds a transaction to the lookup. // Add adds a transaction to the lookup.
func (t *txLookup) Add(tx *types.Transaction, local bool) { func (t *lookup) Add(tx *types.Transaction, local bool) {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
@ -1841,7 +1927,7 @@ func (t *txLookup) Add(tx *types.Transaction, local bool) {
} }
// Remove removes a transaction from the lookup. // Remove removes a transaction from the lookup.
func (t *txLookup) Remove(hash common.Hash) { func (t *lookup) Remove(hash common.Hash) {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
@ -1862,7 +1948,7 @@ func (t *txLookup) Remove(hash common.Hash) {
// RemoteToLocals migrates the transactions belongs to the given locals to locals // RemoteToLocals migrates the transactions belongs to the given locals to locals
// set. The assumption is held the locals set is thread-safe to be used. // set. The assumption is held the locals set is thread-safe to be used.
func (t *txLookup) RemoteToLocals(locals *accountSet) int { func (t *lookup) RemoteToLocals(locals *accountSet) int {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
@ -1877,6 +1963,18 @@ func (t *txLookup) RemoteToLocals(locals *accountSet) int {
return migrated return migrated
} }
// RemotesBelowTip finds all remote transactions below the given tip threshold.
func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
found := make(types.Transactions, 0, 128)
t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
if tx.GasTipCapIntCmp(threshold) < 0 {
found = append(found, tx)
}
return true
}, false, true) // Only iterate remotes
return found
}
// numSlots calculates the number of slots needed for a single transaction. // numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int { func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize) return int((tx.Size() + txSlotSize - 1) / txSlotSize)

File diff suppressed because it is too large Load diff

View file

@ -81,6 +81,9 @@ type Header struct {
Validators []byte `json:"validators" gencodec:"required"` Validators []byte `json:"validators" gencodec:"required"`
Validator []byte `json:"validator" gencodec:"required"` Validator []byte `json:"validator" gencodec:"required"`
Penalties []byte `json:"penalties" gencodec:"required"` Penalties []byte `json:"penalties" gencodec:"required"`
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -91,6 +94,7 @@ type headerMarshaling struct {
GasUsed hexutil.Uint64 GasUsed hexutil.Uint64
Time *hexutil.Big Time *hexutil.Big
Extra hexutil.Bytes Extra hexutil.Bytes
BaseFee *hexutil.Big
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
} }
@ -264,6 +268,9 @@ func CopyHeader(h *Header) *Header {
if cpy.Number = new(big.Int); h.Number != nil { if cpy.Number = new(big.Int); h.Number != nil {
cpy.Number.Set(h.Number) cpy.Number.Set(h.Number)
} }
if h.BaseFee != nil {
cpy.BaseFee = new(big.Int).Set(h.BaseFee)
}
if len(h.Extra) > 0 { if len(h.Extra) > 0 {
cpy.Extra = make([]byte, len(h.Extra)) cpy.Extra = make([]byte, len(h.Extra))
copy(cpy.Extra, h.Extra) copy(cpy.Extra, h.Extra)
@ -340,6 +347,13 @@ func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Ext
func (b *Block) Penalties() []byte { return common.CopyBytes(b.header.Penalties) } func (b *Block) Penalties() []byte { return common.CopyBytes(b.header.Penalties) }
func (b *Block) Validator() []byte { return common.CopyBytes(b.header.Validator) } func (b *Block) Validator() []byte { return common.CopyBytes(b.header.Validator) }
func (b *Block) BaseFee() *big.Int {
if b.header.BaseFee == nil {
return nil
}
return new(big.Int).Set(b.header.BaseFee)
}
func (b *Block) Header() *Header { return CopyHeader(b.header) } func (b *Block) Header() *Header { return CopyHeader(b.header) }
// Body returns the non-header content of the block. // Body returns the non-header content of the block.

View file

@ -13,6 +13,7 @@ import (
var _ = (*headerMarshaling)(nil) var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (h Header) MarshalJSON() ([]byte, error) { func (h Header) MarshalJSON() ([]byte, error) {
type Header struct { type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
@ -30,6 +31,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
Extra hexutil.Bytes `json:"extraData" gencodec:"required"` Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash" gencodec:"required"` MixDigest common.Hash `json:"mixHash" gencodec:"required"`
Nonce BlockNonce `json:"nonce" gencodec:"required"` Nonce BlockNonce `json:"nonce" gencodec:"required"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -48,10 +50,12 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.Extra = h.Extra enc.Extra = h.Extra
enc.MixDigest = h.MixDigest enc.MixDigest = h.MixDigest
enc.Nonce = h.Nonce enc.Nonce = h.Nonce
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (h *Header) UnmarshalJSON(input []byte) error { func (h *Header) UnmarshalJSON(input []byte) error {
type Header struct { type Header struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
@ -69,6 +73,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest *common.Hash `json:"mixHash" gencodec:"required"` MixDigest *common.Hash `json:"mixHash" gencodec:"required"`
Nonce *BlockNonce `json:"nonce" gencodec:"required"` Nonce *BlockNonce `json:"nonce" gencodec:"required"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -134,5 +139,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'nonce' for Header") return errors.New("missing required field 'nonce' for Header")
} }
h.Nonce = *dec.Nonce h.Nonce = *dec.Nonce
if dec.BaseFee != nil {
h.BaseFee = (*big.Int)(dec.BaseFee)
}
return nil return nil
} }

View file

@ -25,6 +25,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
TxHash common.Hash `json:"transactionHash" gencodec:"required"` TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress common.Address `json:"contractAddress"` ContractAddress common.Address `json:"contractAddress"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"`
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex hexutil.Uint `json:"transactionIndex"` TransactionIndex hexutil.Uint `json:"transactionIndex"`
@ -39,6 +40,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
enc.TxHash = r.TxHash enc.TxHash = r.TxHash
enc.ContractAddress = r.ContractAddress enc.ContractAddress = r.ContractAddress
enc.GasUsed = hexutil.Uint64(r.GasUsed) enc.GasUsed = hexutil.Uint64(r.GasUsed)
enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice)
enc.BlockHash = r.BlockHash enc.BlockHash = r.BlockHash
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
@ -57,6 +59,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
TxHash *common.Hash `json:"transactionHash" gencodec:"required"` TxHash *common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress *common.Address `json:"contractAddress"` ContractAddress *common.Address `json:"contractAddress"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"`
BlockHash *common.Hash `json:"blockHash,omitempty"` BlockHash *common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex *hexutil.Uint `json:"transactionIndex"` TransactionIndex *hexutil.Uint `json:"transactionIndex"`
@ -97,6 +100,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'gasUsed' for Receipt") return errors.New("missing required field 'gasUsed' for Receipt")
} }
r.GasUsed = uint64(*dec.GasUsed) r.GasUsed = uint64(*dec.GasUsed)
if dec.EffectiveGasPrice != nil {
r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice)
}
if dec.BlockHash != nil { if dec.BlockHash != nil {
r.BlockHash = *dec.BlockHash r.BlockHash = *dec.BlockHash
} }

View file

@ -38,8 +38,7 @@ var (
receiptStatusSuccessfulRLP = []byte{0x01} receiptStatusSuccessfulRLP = []byte{0x01}
) )
// This error is returned when a typed receipt is decoded, but the string is empty. var errShortTypedReceipt = errors.New("typed receipt too short")
var errEmptyTypedReceipt = errors.New("empty typed receipt bytes")
const ( const (
// ReceiptStatusFailed is the status code of a transaction if execution failed. // ReceiptStatusFailed is the status code of a transaction if execution failed.
@ -60,10 +59,10 @@ type Receipt struct {
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
// Implementation fields: These fields are added by geth when processing a transaction. // Implementation fields: These fields are added by geth when processing a transaction.
// They are stored in the chain database. TxHash common.Hash `json:"transactionHash" gencodec:"required"`
TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"`
ContractAddress common.Address `json:"contractAddress"` GasUsed uint64 `json:"gasUsed" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"` EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility
// Inclusion information: These fields provide information about the inclusion of the // Inclusion information: These fields provide information about the inclusion of the
// transaction corresponding to this receipt. // transaction corresponding to this receipt.
@ -78,6 +77,7 @@ type receiptMarshaling struct {
Status hexutil.Uint64 Status hexutil.Uint64
CumulativeGasUsed hexutil.Uint64 CumulativeGasUsed hexutil.Uint64
GasUsed hexutil.Uint64 GasUsed hexutil.Uint64
EffectiveGasPrice *hexutil.Big
BlockNumber *hexutil.Big BlockNumber *hexutil.Big
TransactionIndex hexutil.Uint TransactionIndex hexutil.Uint
} }
@ -90,18 +90,8 @@ type receiptRLP struct {
Logs []*Log Logs []*Log
} }
// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4. // receiptStorageRLP is the original storage encoding of a receipt including some unnecessary fields.
type v4StoredReceiptRLP struct { type receiptStorageRLP struct {
PostStateOrStatus []byte
CumulativeGasUsed uint64
TxHash common.Hash
ContractAddress common.Address
Logs []*LogForStorage
GasUsed uint64
}
// v3StoredReceiptRLP is the original storage encoding of a receipt including some unnecessary fields.
type v3StoredReceiptRLP struct {
PostStateOrStatus []byte PostStateOrStatus []byte
CumulativeGasUsed uint64 CumulativeGasUsed uint64
Bloom Bloom Bloom Bloom
@ -134,20 +124,33 @@ func (r *Receipt) EncodeRLP(w io.Writer) error {
if r.Type == LegacyTxType { if r.Type == LegacyTxType {
return rlp.Encode(w, data) return rlp.Encode(w, data)
} }
// It's an EIP-2718 typed TX receipt.
if r.Type != AccessListTxType {
return ErrTxTypeNotSupported
}
buf := encodeBufferPool.Get().(*bytes.Buffer) buf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(buf) defer encodeBufferPool.Put(buf)
buf.Reset() buf.Reset()
buf.WriteByte(r.Type) buf.WriteByte(r.Type)
if err := rlp.Encode(buf, data); err != nil { if err := r.encodeTyped(data, buf); err != nil {
return err return err
} }
return rlp.Encode(w, buf.Bytes()) return rlp.Encode(w, buf.Bytes())
} }
// encodeTyped writes the canonical encoding of a typed receipt to w.
func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error {
w.WriteByte(r.Type)
return rlp.Encode(w, data)
}
// MarshalBinary returns the consensus encoding of the receipt.
func (r *Receipt) MarshalBinary() ([]byte, error) {
if r.Type == LegacyTxType {
return rlp.EncodeToBytes(r)
}
data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
var buf bytes.Buffer
err := r.encodeTyped(data, &buf)
return buf.Bytes(), err
}
// DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt
// from an RLP stream. // from an RLP stream.
func (r *Receipt) DecodeRLP(s *rlp.Stream) error { func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
@ -163,26 +166,49 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
} }
r.Type = LegacyTxType r.Type = LegacyTxType
return r.setFromRLP(dec) return r.setFromRLP(dec)
case kind == rlp.String: default:
// It's an EIP-2718 typed tx receipt. // It's an EIP-2718 typed tx receipt.
b, err := s.Bytes() b, err := s.Bytes()
if err != nil { if err != nil {
return err return err
} }
if len(b) == 0 { return r.decodeTyped(b)
return errEmptyTypedReceipt }
}
// UnmarshalBinary decodes the consensus encoding of receipts.
// It supports legacy RLP receipts and EIP-2718 typed receipts.
func (r *Receipt) UnmarshalBinary(b []byte) error {
if len(b) > 0 && b[0] > 0x7f {
// It's a legacy receipt decode the RLP
var data receiptRLP
err := rlp.DecodeBytes(b, &data)
if err != nil {
return err
}
r.Type = LegacyTxType
return r.setFromRLP(data)
}
// It's an EIP2718 typed transaction envelope.
return r.decodeTyped(b)
}
// decodeTyped decodes a typed receipt from the canonical format.
func (r *Receipt) decodeTyped(b []byte) error {
if len(b) <= 1 {
return errShortTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
return err
} }
r.Type = b[0] r.Type = b[0]
if r.Type == AccessListTxType { return r.setFromRLP(data)
var dec receiptRLP
if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
return err
}
return r.setFromRLP(dec)
}
return ErrTxTypeNotSupported
default: default:
return rlp.ErrExpectedList return ErrTxTypeNotSupported
} }
} }
@ -241,7 +267,7 @@ type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
// into an RLP stream. // into an RLP stream.
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
enc := &v3StoredReceiptRLP{ enc := &receiptStorageRLP{
PostStateOrStatus: (*Receipt)(r).statusEncoding(), PostStateOrStatus: (*Receipt)(r).statusEncoding(),
CumulativeGasUsed: r.CumulativeGasUsed, CumulativeGasUsed: r.CumulativeGasUsed,
Bloom: r.Bloom, Bloom: r.Bloom,
@ -264,17 +290,11 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
if err != nil { if err != nil {
return err return err
} }
// Try decoding from the newest format for future proofness, then the older one return decodeStoredReceiptRLP(r, blob)
// for old nodes that just upgraded. V4 was an intermediate unreleased format so
// we do need to decode it, but it's not common (try last).
if err := decodeV3StoredReceiptRLP(r, blob); err == nil {
return nil
}
return decodeV4StoredReceiptRLP(r, blob)
} }
func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
var stored v3StoredReceiptRLP var stored receiptStorageRLP
if err := rlp.DecodeBytes(blob, &stored); err != nil { if err := rlp.DecodeBytes(blob, &stored); err != nil {
return err return err
} }
@ -295,36 +315,15 @@ func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
return nil return nil
} }
func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
var stored v4StoredReceiptRLP
if err := rlp.DecodeBytes(blob, &stored); err != nil {
return err
}
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
return err
}
r.CumulativeGasUsed = stored.CumulativeGasUsed
r.TxHash = stored.TxHash
r.ContractAddress = stored.ContractAddress
r.GasUsed = stored.GasUsed
r.Logs = make([]*Log, len(stored.Logs))
for i, log := range stored.Logs {
r.Logs[i] = (*Log)(log)
}
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
return nil
}
// Receipts implements DerivableList for receipts. // Receipts implements DerivableList for receipts.
type Receipts []*Receipt type Receipts []*Receipt
// Len returns the number of receipts in this list. // Len returns the number of receipts in this list.
func (r Receipts) Len() int { return len(r) } func (rs Receipts) Len() int { return len(rs) }
// GetRlp returns the RLP encoding of one receipt from the list. // GetRlp returns the RLP encoding of one receipt from the list.
func (r Receipts) GetRlp(i int) []byte { func (rs Receipts) GetRlp(i int) []byte {
bytes, err := rlp.EncodeToBytes(r[i]) bytes, err := rlp.EncodeToBytes(rs[i])
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -333,42 +332,48 @@ func (r Receipts) GetRlp(i int) []byte {
// DeriveFields fills the receipts with their computed fields based on consensus // DeriveFields fills the receipts with their computed fields based on consensus
// data and contextual infos like containing block and transactions. // data and contextual infos like containing block and transactions.
func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error { func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, baseFee *big.Int, txs []*Transaction) error {
signer := MakeSigner(config, new(big.Int).SetUint64(number)) signer := MakeSigner(config, new(big.Int).SetUint64(number))
logIndex := uint(0) logIndex := uint(0)
if len(txs) != len(r) { if len(txs) != len(rs) {
return errors.New("transaction and receipt count mismatch") return errors.New("transaction and receipt count mismatch")
} }
for i := 0; i < len(r); i++ { for i := 0; i < len(rs); i++ {
// The transaction type and hash can be retrieved from the transaction itself // The transaction type and hash can be retrieved from the transaction itself
r[i].Type = txs[i].Type() rs[i].Type = txs[i].Type()
r[i].TxHash = txs[i].Hash() rs[i].TxHash = txs[i].Hash()
rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
// block location fields // block location fields
r[i].BlockHash = hash rs[i].BlockHash = hash
r[i].BlockNumber = new(big.Int).SetUint64(number) rs[i].BlockNumber = new(big.Int).SetUint64(number)
r[i].TransactionIndex = uint(i) rs[i].TransactionIndex = uint(i)
// The contract address can be derived from the transaction itself // The contract address can be derived from the transaction itself
if txs[i].To() == nil { if txs[i].To() == nil {
// Deriving the signer is expensive, only do if it's actually needed // Deriving the signer is expensive, only do if it's actually needed
from, _ := Sender(signer, txs[i]) from, _ := Sender(signer, txs[i])
r[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce())
} else {
rs[i].ContractAddress = common.Address{}
} }
// The used gas can be calculated based on previous r // The used gas can be calculated based on previous r
if i == 0 { if i == 0 {
r[i].GasUsed = r[i].CumulativeGasUsed rs[i].GasUsed = rs[i].CumulativeGasUsed
} else { } else {
r[i].GasUsed = r[i].CumulativeGasUsed - r[i-1].CumulativeGasUsed rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed
} }
// The derived log fields can simply be set from the block and transaction // The derived log fields can simply be set from the block and transaction
for j := 0; j < len(r[i].Logs); j++ { for j := 0; j < len(rs[i].Logs); j++ {
r[i].Logs[j].BlockNumber = number rs[i].Logs[j].BlockNumber = number
r[i].Logs[j].BlockHash = hash rs[i].Logs[j].BlockHash = hash
r[i].Logs[j].TxHash = r[i].TxHash rs[i].Logs[j].TxHash = rs[i].TxHash
r[i].Logs[j].TxIndex = uint(i) rs[i].Logs[j].TxIndex = uint(i)
r[i].Logs[j].Index = logIndex rs[i].Logs[j].Index = logIndex
logIndex++ logIndex++
} }
} }

View file

@ -18,35 +18,278 @@ package types
import ( import (
"bytes" "bytes"
"encoding/json"
"math" "math"
"math/big" "math/big"
"reflect" "reflect"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/kylelemons/godebug/diff"
)
var (
legacyReceipt = &Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
},
}
accessListReceipt = &Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
},
Type: AccessListTxType,
}
eip1559Receipt = &Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
Data: []byte{0x01, 0x00, 0xff},
},
},
Type: DynamicFeeTxType,
}
// Create a few transactions to have receipts for
to2 = common.HexToAddress("0x2")
to3 = common.HexToAddress("0x3")
to4 = common.HexToAddress("0x4")
to5 = common.HexToAddress("0x5")
to6 = common.HexToAddress("0x6")
to7 = common.HexToAddress("0x7")
txs = Transactions{
NewTx(&LegacyTx{
Nonce: 1,
Value: big.NewInt(1),
Gas: 1,
GasPrice: big.NewInt(11),
}),
NewTx(&LegacyTx{
To: &to2,
Nonce: 2,
Value: big.NewInt(2),
Gas: 2,
GasPrice: big.NewInt(22),
}),
NewTx(&AccessListTx{
To: &to3,
Nonce: 3,
Value: big.NewInt(3),
Gas: 3,
GasPrice: big.NewInt(33),
}),
// EIP-1559 transactions.
NewTx(&DynamicFeeTx{
To: &to4,
Nonce: 4,
Value: big.NewInt(4),
Gas: 4,
GasTipCap: big.NewInt(44),
GasFeeCap: big.NewInt(1044),
}),
NewTx(&DynamicFeeTx{
To: &to5,
Nonce: 5,
Value: big.NewInt(5),
Gas: 5,
GasTipCap: big.NewInt(55),
GasFeeCap: big.NewInt(1055),
}),
}
blockNumber = big.NewInt(1)
blockTime = uint64(2)
blockHash = common.BytesToHash([]byte{0x03, 0x14})
// Create the corresponding receipts
receipts = Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
// derived fields:
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
},
&Receipt{
Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6,
Logs: []*Log{},
// derived fields:
TxHash: txs[2].Hash(),
GasUsed: 3,
EffectiveGasPrice: big.NewInt(33),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
},
}
) )
func TestDecodeEmptyTypedReceipt(t *testing.T) { func TestDecodeEmptyTypedReceipt(t *testing.T) {
input := []byte{0x80} input := []byte{0x80}
var r Receipt var r Receipt
err := rlp.DecodeBytes(input, &r) err := rlp.DecodeBytes(input, &r)
if err != errEmptyTypedReceipt { if err != errShortTypedReceipt {
t.Fatal("wrong error:", err) t.Fatal("wrong error:", err)
} }
} }
// Test that we can marshal/unmarshal receipts to/from json without errors.
// This also confirms that our test receipts contain all the required fields.
func TestReceiptJSON(t *testing.T) {
for i := range receipts {
b, err := receipts[i].MarshalJSON()
if err != nil {
t.Fatal("error marshaling receipt to json:", err)
}
r := Receipt{}
err = r.UnmarshalJSON(b)
if err != nil {
t.Fatal("error unmarshaling receipt from json:", err)
}
}
}
// Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even
// though it is required per the spec.
func TestEffectiveGasPriceNotRequired(t *testing.T) {
r := *receipts[0]
r.EffectiveGasPrice = nil
b, err := r.MarshalJSON()
if err != nil {
t.Fatal("error marshaling receipt to json:", err)
}
r2 := Receipt{}
err = r2.UnmarshalJSON(b)
if err != nil {
t.Fatal("error unmarshaling receipt from json:", err)
}
}
func TestLegacyReceiptDecoding(t *testing.T) { func TestLegacyReceiptDecoding(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
encode func(*Receipt) ([]byte, error) encode func(*Receipt) ([]byte, error)
}{ }{
{
"V4StoredReceiptRLP",
encodeAsV4StoredReceiptRLP,
},
{ {
"V3StoredReceiptRLP", "V3StoredReceiptRLP",
encodeAsV3StoredReceiptRLP, encodeAsV3StoredReceiptRLP,
@ -113,23 +356,8 @@ func TestLegacyReceiptDecoding(t *testing.T) {
} }
} }
func encodeAsV4StoredReceiptRLP(want *Receipt) ([]byte, error) {
stored := &v4StoredReceiptRLP{
PostStateOrStatus: want.statusEncoding(),
CumulativeGasUsed: want.CumulativeGasUsed,
TxHash: want.TxHash,
ContractAddress: want.ContractAddress,
Logs: make([]*LogForStorage, len(want.Logs)),
GasUsed: want.GasUsed,
}
for i, log := range want.Logs {
stored.Logs[i] = (*LogForStorage)(log)
}
return rlp.EncodeToBytes(stored)
}
func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) { func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
stored := &v3StoredReceiptRLP{ stored := &receiptStorageRLP{
PostStateOrStatus: want.statusEncoding(), PostStateOrStatus: want.statusEncoding(),
CumulativeGasUsed: want.CumulativeGasUsed, CumulativeGasUsed: want.CumulativeGasUsed,
Bloom: want.Bloom, Bloom: want.Bloom,
@ -149,162 +377,212 @@ func TestDeriveFields(t *testing.T) {
// Create a few transactions to have receipts for // Create a few transactions to have receipts for
to2 := common.HexToAddress("0x2") to2 := common.HexToAddress("0x2")
to3 := common.HexToAddress("0x3") to3 := common.HexToAddress("0x3")
to4 := common.HexToAddress("0x4")
to5 := common.HexToAddress("0x5")
txs := Transactions{ txs := Transactions{
NewTx(&LegacyTx{ NewTx(&LegacyTx{
Nonce: 1, Nonce: 1,
Value: big.NewInt(1), Value: big.NewInt(1),
Gas: 1, Gas: 1,
GasPrice: big.NewInt(1), GasPrice: big.NewInt(11),
}), }),
NewTx(&LegacyTx{ NewTx(&LegacyTx{
To: &to2, To: &to2,
Nonce: 2, Nonce: 2,
Value: big.NewInt(2), Value: big.NewInt(2),
Gas: 2, Gas: 2,
GasPrice: big.NewInt(2), GasPrice: big.NewInt(22),
}), }),
NewTx(&AccessListTx{ NewTx(&AccessListTx{
To: &to3, To: &to3,
Nonce: 3, Nonce: 3,
Value: big.NewInt(3), Value: big.NewInt(3),
Gas: 3, Gas: 3,
GasPrice: big.NewInt(3), GasPrice: big.NewInt(33),
}),
// EIP-1559 transactions.
NewTx(&DynamicFeeTx{
To: &to4,
Nonce: 4,
Value: big.NewInt(4),
Gas: 4,
GasTipCap: big.NewInt(44),
GasFeeCap: big.NewInt(1045),
}),
NewTx(&DynamicFeeTx{
To: &to5,
Nonce: 5,
Value: big.NewInt(5),
Gas: 5,
GasTipCap: big.NewInt(56),
GasFeeCap: big.NewInt(1055),
}), }),
} }
blockNumber := big.NewInt(1)
blockHash := common.BytesToHash([]byte{0x03, 0x14})
// Create the corresponding receipts // Create the corresponding receipts
receipts := Receipts{ receipts := Receipts{
&Receipt{ &Receipt{
Status: ReceiptStatusFailed, Status: ReceiptStatusFailed,
CumulativeGasUsed: 1, CumulativeGasUsed: 1,
Logs: []*Log{ Logs: []*Log{
{Address: common.BytesToAddress([]byte{0x11})}, {
{Address: common.BytesToAddress([]byte{0x01, 0x11})}, Address: common.BytesToAddress([]byte{0x11}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
}, },
TxHash: txs[0].Hash(), // derived fields:
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), TxHash: txs[0].Hash(),
GasUsed: 1, ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
}, },
&Receipt{ &Receipt{
PostState: common.Hash{2}.Bytes(), PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3, CumulativeGasUsed: 3,
Logs: []*Log{ Logs: []*Log{
{Address: common.BytesToAddress([]byte{0x22})}, {
{Address: common.BytesToAddress([]byte{0x02, 0x22})}, Address: common.BytesToAddress([]byte{0x22}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
}, },
TxHash: txs[1].Hash(), // derived fields:
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), TxHash: txs[1].Hash(),
GasUsed: 2, GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
}, },
&Receipt{ &Receipt{
Type: AccessListTxType, Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(), PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6, CumulativeGasUsed: 6,
Logs: []*Log{ Logs: []*Log{},
{Address: common.BytesToAddress([]byte{0x33})}, // derived fields:
{Address: common.BytesToAddress([]byte{0x03, 0x33})}, TxHash: txs[2].Hash(),
}, GasUsed: 3,
TxHash: txs[2].Hash(), EffectiveGasPrice: big.NewInt(33),
ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}), BlockHash: blockHash,
GasUsed: 3, BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
}, },
} }
// Clear all the computed fields and re-derive them
number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14})
clearComputedFieldsOnReceipts(t, receipts) // Re-derive receipts.
if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil { basefee := big.NewInt(1000)
derivedReceipts := clearComputedFieldsOnReceipts(receipts)
err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), basefee, txs)
if err != nil {
t.Fatalf("DeriveFields(...) = %v, want <nil>", err) t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
} }
// Iterate over all the computed fields and check that they're correct
signer := MakeSigner(params.TestChainConfig, number)
logIndex := uint(0) // Check diff of receipts against derivedReceipts.
for i := range receipts { r1, err := json.MarshalIndent(receipts, "", " ")
if receipts[i].Type != txs[i].Type() { if err != nil {
t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type()) t.Fatal("error marshaling input receipts:", err)
} }
if receipts[i].TxHash != txs[i].Hash() { r2, err := json.MarshalIndent(derivedReceipts, "", " ")
t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String()) if err != nil {
} t.Fatal("error marshaling derived receipts:", err)
if receipts[i].BlockHash != hash { }
t.Errorf("receipts[%d].BlockHash = %s, want %s", i, receipts[i].BlockHash.String(), hash.String()) d := diff.Diff(string(r1), string(r2))
} if d != "" {
if receipts[i].BlockNumber.Cmp(number) != 0 { t.Fatal("receipts differ:", d)
t.Errorf("receipts[%c].BlockNumber = %s, want %s", i, receipts[i].BlockNumber.String(), number.String())
}
if receipts[i].TransactionIndex != uint(i) {
t.Errorf("receipts[%d].TransactionIndex = %d, want %d", i, receipts[i].TransactionIndex, i)
}
if receipts[i].GasUsed != txs[i].Gas() {
t.Errorf("receipts[%d].GasUsed = %d, want %d", i, receipts[i].GasUsed, txs[i].Gas())
}
if txs[i].To() != nil && receipts[i].ContractAddress != (common.Address{}) {
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), (common.Address{}).String())
}
from, _ := Sender(signer, txs[i])
contractAddress := crypto.CreateAddress(from, txs[i].Nonce())
if txs[i].To() == nil && receipts[i].ContractAddress != contractAddress {
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), contractAddress.String())
}
for j := range receipts[i].Logs {
if receipts[i].Logs[j].BlockNumber != number.Uint64() {
t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64())
}
if receipts[i].Logs[j].BlockHash != hash {
t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
}
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
}
if receipts[i].Logs[j].TxIndex != uint(i) {
t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
}
if receipts[i].Logs[j].Index != logIndex {
t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex)
}
logIndex++
}
} }
} }
func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) { func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt {
t.Helper() r := make([]*Receipt, len(receipts))
for i, receipt := range receipts {
for _, receipt := range receipts { r[i] = clearComputedFieldsOnReceipt(receipt)
clearComputedFieldsOnReceipt(t, receipt)
} }
return r
} }
func clearComputedFieldsOnReceipt(t *testing.T, receipt *Receipt) { func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt {
t.Helper() cpy := *receipt
cpy.TxHash = common.Hash{0xff, 0xff, 0x11}
receipt.TxHash = common.Hash{} cpy.BlockHash = common.Hash{0xff, 0xff, 0x22}
receipt.BlockHash = common.Hash{} cpy.BlockNumber = big.NewInt(math.MaxUint32)
receipt.BlockNumber = big.NewInt(math.MaxUint32) cpy.TransactionIndex = math.MaxUint32
receipt.TransactionIndex = math.MaxUint32 cpy.ContractAddress = common.Address{0xff, 0xff, 0x33}
receipt.ContractAddress = common.Address{} cpy.GasUsed = 0xffffffff
receipt.GasUsed = 0 cpy.Logs = clearComputedFieldsOnLogs(receipt.Logs)
return &cpy
clearComputedFieldsOnLogs(t, receipt.Logs)
} }
func clearComputedFieldsOnLogs(t *testing.T, logs []*Log) { func clearComputedFieldsOnLogs(logs []*Log) []*Log {
t.Helper() l := make([]*Log, len(logs))
for i, log := range logs {
for _, log := range logs { cpy := *log
clearComputedFieldsOnLog(t, log) cpy.BlockNumber = math.MaxUint32
cpy.BlockHash = common.Hash{}
cpy.TxHash = common.Hash{}
cpy.TxIndex = math.MaxUint32
cpy.Index = math.MaxUint32
l[i] = &cpy
} }
} return l
func clearComputedFieldsOnLog(t *testing.T, log *Log) {
t.Helper()
log.BlockNumber = math.MaxUint32
log.BlockHash = common.Hash{}
log.TxHash = common.Hash{}
log.TxIndex = math.MaxUint32
log.Index = math.MaxUint32
} }
// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt // TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt
@ -334,3 +612,38 @@ func TestTypedReceiptEncodingDecoding(t *testing.T) {
check(bundle) check(bundle)
} }
} }
func TestReceiptUnmarshalBinary(t *testing.T) {
// Legacy Receipt
legacyBinary := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
gotLegacyReceipt := new(Receipt)
if err := gotLegacyReceipt.UnmarshalBinary(legacyBinary); err != nil {
t.Fatalf("unmarshal binary error: %v", err)
}
legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt})
if !reflect.DeepEqual(gotLegacyReceipt, legacyReceipt) {
t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotLegacyReceipt, legacyReceipt)
}
// 2930 Receipt
accessListBinary := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
gotAccessListReceipt := new(Receipt)
if err := gotAccessListReceipt.UnmarshalBinary(accessListBinary); err != nil {
t.Fatalf("unmarshal binary error: %v", err)
}
accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt})
if !reflect.DeepEqual(gotAccessListReceipt, accessListReceipt) {
t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotAccessListReceipt, accessListReceipt)
}
// 1559 Receipt
eip1559RctBinary := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
got1559Receipt := new(Receipt)
if err := got1559Receipt.UnmarshalBinary(eip1559RctBinary); err != nil {
t.Fatalf("unmarshal binary error: %v", err)
}
eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt})
if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) {
t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt)
}
}

View file

@ -27,23 +27,25 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go //go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
var ( var (
ErrInvalidSig = errors.New("invalid transaction v, r, s values") ErrInvalidSig = errors.New("invalid transaction v, r, s values")
ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures")
ErrInvalidTxType = errors.New("transaction type not valid in this context") ErrInvalidTxType = errors.New("transaction type not valid in this context")
ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errShortTypedTx = errors.New("typed transaction too short") errShortTypedTx = errors.New("typed transaction too short")
errInvalidYParity = errors.New("'yParity' field must be 0 or 1") errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match") errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction") errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction")
errEmptyTypedTx = errors.New("empty typed transaction bytes") errNoSigner = errors.New("missing signing methods")
errNoSigner = errors.New("missing signing methods") ErrFeeCapTooLow = errors.New("fee cap less than base fee")
skipNonceDestinationAddress = map[common.Address]bool{ skipNonceDestinationAddress = map[common.Address]bool{
common.XDCXAddrBinary: true, common.XDCXAddrBinary: true,
common.TradingStateAddrBinary: true, common.TradingStateAddrBinary: true,
@ -56,6 +58,7 @@ var (
const ( const (
LegacyTxType = iota LegacyTxType = iota
AccessListTxType AccessListTxType
DynamicFeeTxType
) )
// Transaction is an Ethereum transaction. // Transaction is an Ethereum transaction.
@ -88,12 +91,22 @@ type TxData interface {
data() []byte data() []byte
gas() uint64 gas() uint64
gasPrice() *big.Int gasPrice() *big.Int
gasTipCap() *big.Int
gasFeeCap() *big.Int
value() *big.Int value() *big.Int
nonce() uint64 nonce() uint64
to() *common.Address to() *common.Address
rawSignatureValues() (v, r, s *big.Int) rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int)
// effectiveGasPrice computes the gas price paid by the transaction, given
// the inclusion block baseFee.
//
// Unlike other TxData methods, the returned *big.Int should be an independent
// copy of the computed value, i.e. callers are allowed to mutate the result.
// Method implementations can use 'dst' to store the result.
effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int
} }
// EncodeRLP implements rlp.Encoder // EncodeRLP implements rlp.Encoder
@ -143,7 +156,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, int(rlp.ListSize(size))) tx.setDecoded(&inner, int(rlp.ListSize(size)))
} }
return err return err
case kind == rlp.String: default:
// It's an EIP-2718 typed TX envelope. // It's an EIP-2718 typed TX envelope.
var b []byte var b []byte
if b, err = s.Bytes(); err != nil { if b, err = s.Bytes(); err != nil {
@ -154,8 +167,6 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(inner, len(b)) tx.setDecoded(inner, len(b))
} }
return err return err
default:
return rlp.ErrExpectedList
} }
} }
@ -183,14 +194,18 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
// decodeTyped decodes a typed transaction from the canonical format. // decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 { if len(b) <= 1 {
return nil, errEmptyTypedTx return nil, errShortTypedTx
} }
switch b[0] { switch b[0] {
case AccessListTxType: case AccessListTxType:
var inner AccessListTx var inner AccessListTx
err := rlp.DecodeBytes(b[1:], &inner) err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err return &inner, err
case DynamicFeeTxType:
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default: default:
return nil, ErrTxTypeNotSupported return nil, ErrTxTypeNotSupported
} }
@ -274,6 +289,12 @@ func (tx *Transaction) Gas() uint64 { return tx.inner.gas() }
// GasPrice returns the gas price of the transaction. // GasPrice returns the gas price of the transaction.
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) } func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) }
// GasTipCap returns the gasTipCap per gas of the transaction.
func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.gasTipCap()) }
// GasFeeCap returns the fee cap per gas of the transaction.
func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) }
// Value returns the ether amount of the transaction. // Value returns the ether amount of the transaction.
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) }
@ -283,13 +304,7 @@ func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() }
// To returns the recipient address of the transaction. // To returns the recipient address of the transaction.
// For contract-creation transactions, To returns nil. // For contract-creation transactions, To returns nil.
func (tx *Transaction) To() *common.Address { func (tx *Transaction) To() *common.Address {
// Copy the pointed-to address. return copyAddressPtr(tx.inner.to())
ito := tx.inner.to()
if ito == nil {
return nil
}
cpy := *ito
return &cpy
} }
func (tx *Transaction) From() *common.Address { func (tx *Transaction) From() *common.Address {
@ -306,20 +321,75 @@ func (tx *Transaction) From() *common.Address {
return &from return &from
} }
// Cost returns gas * gasPrice + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
total.Add(total, tx.Value())
return total
}
// RawSignatureValues returns the V, R, S signature values of the transaction. // RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller. // The return values should not be modified by the caller.
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
return tx.inner.rawSignatureValues() return tx.inner.rawSignatureValues()
} }
// GasPriceCmp compares the gas prices of two transactions. // GasFeeCapCmp compares the fee cap of two transactions.
func (tx *Transaction) GasPriceCmp(other *Transaction) int { func (tx *Transaction) GasFeeCapCmp(other *Transaction) int {
return tx.inner.gasPrice().Cmp(other.inner.gasPrice()) return tx.inner.gasFeeCap().Cmp(other.inner.gasFeeCap())
} }
// GasPriceIntCmp compares the gas price of the transaction against the given price. // GasFeeCapIntCmp compares the fee cap of the transaction against the given fee cap.
func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { func (tx *Transaction) GasFeeCapIntCmp(other *big.Int) int {
return tx.inner.gasPrice().Cmp(other) return tx.inner.gasFeeCap().Cmp(other)
}
// GasTipCapCmp compares the gasTipCap of two transactions.
func (tx *Transaction) GasTipCapCmp(other *Transaction) int {
return tx.inner.gasTipCap().Cmp(other.inner.gasTipCap())
}
// GasTipCapIntCmp compares the gasTipCap of the transaction against the given gasTipCap.
func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
return tx.inner.gasTipCap().Cmp(other)
}
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
// Note: if the effective gasTipCap is negative, this method returns both error
// the actual negative value, _and_ ErrGasFeeCapTooLow
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
if baseFee == nil {
return tx.GasTipCap(), nil
}
var err error
gasFeeCap := tx.GasFeeCap()
if gasFeeCap.Cmp(baseFee) == -1 {
err = ErrGasFeeCapTooLow
}
return math.BigMin(tx.GasTipCap(), gasFeeCap.Sub(gasFeeCap, baseFee)), err
}
// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an
// error in case the effective gasTipCap is negative
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
effectiveTip, _ := tx.EffectiveGasTip(baseFee)
return effectiveTip
}
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
if baseFee == nil {
return tx.GasTipCapCmp(other)
}
return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee))
}
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int {
if baseFee == nil {
return tx.GasTipCapIntCmp(other)
}
return tx.EffectiveGasTipValue(baseFee).Cmp(other)
} }
// Hash returns the transaction hash. // Hash returns the transaction hash.
@ -350,31 +420,43 @@ func (tx *Transaction) Size() common.StorageSize {
return common.StorageSize(c) return common.StorageSize(c)
} }
func (tx *Transaction) EffectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return tx.inner.effectiveGasPrice(dst, baseFee)
}
// AsMessage returns the transaction as a core.Message. // AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, balanceFee *big.Int, number *big.Int) (Message, error) { func (tx *Transaction) AsMessage(s Signer, balanceFee, blockNumber, baseFee *big.Int) (Message, error) {
msg := Message{ msg := Message{
nonce: tx.Nonce(), nonce: tx.Nonce(),
gasLimit: tx.Gas(), gasLimit: tx.Gas(),
gasPrice: new(big.Int).Set(tx.GasPrice()), gasPrice: new(big.Int).Set(tx.GasPrice()),
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
to: tx.To(), to: tx.To(),
amount: tx.Value(), amount: tx.Value(),
data: tx.Data(), data: tx.Data(),
accessList: tx.AccessList(), accessList: tx.AccessList(),
checkNonce: true, isFake: false,
balanceTokenFee: balanceFee, balanceTokenFee: balanceFee,
} }
if balanceFee != nil {
if blockNumber != nil {
if blockNumber.Cmp(common.BlockNumberGas50x) >= 0 {
msg.gasPrice = common.GasPrice50x
} else if blockNumber.Cmp(common.TIPTRC21Fee) > 0 {
msg.gasPrice = common.TRC21GasPrice
} else {
msg.gasPrice = common.TRC21GasPriceBefore
}
}
} else if baseFee != nil {
// If baseFee provided, set gasPrice to effectiveGasPrice.
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
}
var err error var err error
msg.from, err = Sender(s, tx) msg.from, err = Sender(s, tx)
if balanceFee != nil {
if number.Cmp(common.BlockNumberGas50x) >= 0 {
msg.gasPrice = common.GasPrice50x
} else if number.Cmp(common.TIPTRC21Fee) > 0 {
msg.gasPrice = common.TRC21GasPrice
} else {
msg.gasPrice = common.TRC21GasPriceBefore
}
}
return msg, err return msg, err
} }
@ -390,13 +472,6 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
return &Transaction{inner: cpy, time: tx.time}, nil return &Transaction{inner: cpy, time: tx.time}, nil
} }
// Cost returns gas * gasPrice + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
total.Add(total, tx.Value())
return total
}
// TxCost returns gas * gasPrice + value. // TxCost returns gas * gasPrice + value.
func (tx *Transaction) TxCost(number *big.Int) *big.Int { func (tx *Transaction) TxCost(number *big.Int) *big.Int {
total := new(big.Int).Mul(common.GetGasPrice(number), new(big.Int).SetUint64(tx.Gas())) total := new(big.Int).Mul(common.GetGasPrice(number), new(big.Int).SetUint64(tx.Gas()))
@ -742,13 +817,15 @@ type Message struct {
amount *big.Int amount *big.Int
gasLimit uint64 gasLimit uint64
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
data []byte data []byte
accessList AccessList accessList AccessList
checkNonce bool isFake bool
balanceTokenFee *big.Int balanceTokenFee *big.Int
} }
func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, accessList AccessList, checkNonce bool, balanceTokenFee *big.Int, number *big.Int) Message { func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool, balanceTokenFee *big.Int, number *big.Int) Message {
if balanceTokenFee != nil { if balanceTokenFee != nil {
gasPrice = common.GetGasPrice(number) gasPrice = common.GetGasPrice(number)
} }
@ -759,9 +836,11 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
amount: amount, amount: amount,
gasLimit: gasLimit, gasLimit: gasLimit,
gasPrice: gasPrice, gasPrice: gasPrice,
gasFeeCap: gasFeeCap,
gasTipCap: gasTipCap,
data: data, data: data,
accessList: accessList, accessList: accessList,
checkNonce: checkNonce, isFake: isFake,
balanceTokenFee: balanceTokenFee, balanceTokenFee: balanceTokenFee,
} }
} }
@ -770,11 +849,13 @@ func (m Message) From() common.Address { return m.from }
func (m Message) BalanceTokenFee() *big.Int { return m.balanceTokenFee } func (m Message) BalanceTokenFee() *big.Int { return m.balanceTokenFee }
func (m Message) To() *common.Address { return m.to } func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice } func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
func (m Message) Value() *big.Int { return m.amount } func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data } func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce } func (m Message) IsFake() bool { return m.isFake }
func (m Message) AccessList() AccessList { return m.accessList } func (m Message) AccessList() AccessList { return m.accessList }
func (m *Message) SetNonce(nonce uint64) { m.nonce = nonce } func (m *Message) SetNonce(nonce uint64) { m.nonce = nonce }
@ -783,3 +864,16 @@ func (m *Message) SetBalanceTokenFeeForCall() {
m.balanceTokenFee = new(big.Int).SetUint64(m.gasLimit) m.balanceTokenFee = new(big.Int).SetUint64(m.gasLimit)
m.balanceTokenFee.Mul(m.balanceTokenFee, m.gasPrice) m.balanceTokenFee.Mul(m.balanceTokenFee, m.gasPrice)
} }
func (m *Message) SetBalanceTokenFee(balanceTokenFee *big.Int) {
m.balanceTokenFee = balanceTokenFee
}
// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
if a == nil {
return nil
}
cpy := *a
return &cpy
}

View file

@ -1,3 +1,19 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types package types
import ( import (
@ -14,15 +30,17 @@ type txJSON struct {
Type hexutil.Uint64 `json:"type"` Type hexutil.Uint64 `json:"type"`
// Common transaction fields: // Common transaction fields:
Nonce *hexutil.Uint64 `json:"nonce"` Nonce *hexutil.Uint64 `json:"nonce"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
Gas *hexutil.Uint64 `json:"gas"` MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
Value *hexutil.Big `json:"value"` MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
Data *hexutil.Bytes `json:"input"` Gas *hexutil.Uint64 `json:"gas"`
V *hexutil.Big `json:"v"` Value *hexutil.Big `json:"value"`
R *hexutil.Big `json:"r"` Data *hexutil.Bytes `json:"input"`
S *hexutil.Big `json:"s"` V *hexutil.Big `json:"v"`
To *common.Address `json:"to"` R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
To *common.Address `json:"to"`
// Access list transaction fields: // Access list transaction fields:
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
@ -63,6 +81,19 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.V = (*hexutil.Big)(tx.V) enc.V = (*hexutil.Big)(tx.V)
enc.R = (*hexutil.Big)(tx.R) enc.R = (*hexutil.Big)(tx.R)
enc.S = (*hexutil.Big)(tx.S) enc.S = (*hexutil.Big)(tx.S)
case *DynamicFeeTx:
enc.ChainID = (*hexutil.Big)(tx.ChainID)
enc.AccessList = &tx.AccessList
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To()
enc.V = (*hexutil.Big)(tx.V)
enc.R = (*hexutil.Big)(tx.R)
enc.S = (*hexutil.Big)(tx.S)
} }
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -175,6 +206,63 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
} }
} }
case DynamicFeeTxType:
var itx DynamicFeeTx
inner = &itx
// Access list is optional for now.
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
}
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
itx.ChainID = (*big.Int)(dec.ChainID)
if dec.To != nil {
itx.To = dec.To
}
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
itx.Nonce = uint64(*dec.Nonce)
if dec.MaxPriorityFeePerGas == nil {
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
}
itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
if dec.Gas == nil {
return errors.New("missing required field 'gas' for txdata")
}
itx.Gas = uint64(*dec.Gas)
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
itx.Value = (*big.Int)(dec.Value)
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Data
if dec.V == nil {
return errors.New("missing required field 'v' in transaction")
}
itx.V = (*big.Int)(dec.V)
if dec.R == nil {
return errors.New("missing required field 'r' in transaction")
}
itx.R = (*big.Int)(dec.R)
if dec.S == nil {
return errors.New("missing required field 's' in transaction")
}
itx.S = (*big.Int)(dec.S)
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
if withSignature {
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
return err
}
}
default: default:
return ErrTxTypeNotSupported return ErrTxTypeNotSupported
} }

View file

@ -42,7 +42,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
var signer Signer var signer Signer
switch { switch {
case config.IsEIP1559(blockNumber): case config.IsEIP1559(blockNumber):
signer = NewEIP2930Signer(config.ChainId) signer = NewLondonSigner(config.ChainId)
case config.IsEIP155(blockNumber): case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainId) signer = NewEIP155Signer(config.ChainId)
case config.IsHomestead(blockNumber): case config.IsHomestead(blockNumber):
@ -63,7 +63,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
func LatestSigner(config *params.ChainConfig) Signer { func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainId != nil { if config.ChainId != nil {
if common.Eip1559Block.Uint64() != 9999999999 || config.Eip1559Block != nil { if common.Eip1559Block.Uint64() != 9999999999 || config.Eip1559Block != nil {
return NewEIP2930Signer(config.ChainId) return NewLondonSigner(config.ChainId)
} }
if config.EIP155Block != nil { if config.EIP155Block != nil {
return NewEIP155Signer(config.ChainId) return NewEIP155Signer(config.ChainId)
@ -83,7 +83,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil { if chainID == nil {
return HomesteadSigner{} return HomesteadSigner{}
} }
return NewEIP2930Signer(chainID) return NewLondonSigner(chainID)
} }
// SignTx signs the transaction using the given signer and private key. // SignTx signs the transaction using the given signer and private key.
@ -170,6 +170,72 @@ type Signer interface {
Equal(Signer) bool Equal(Signer) bool
} }
type londonSigner struct{ eip2930Signer }
// NewLondonSigner returns a signer that accepts
// - EIP-1559 dynamic fee transactions
// - EIP-2930 access list transactions,
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
func NewLondonSigner(chainId *big.Int) Signer {
return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}
}
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() != DynamicFeeTxType {
return s.eip2930Signer.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
// DynamicFee txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, ErrInvalidChainId
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}
func (s londonSigner) Equal(s2 Signer) bool {
x, ok := s2.(londonSigner)
return ok && x.chainId.Cmp(s.chainId) == 0
}
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
txdata, ok := tx.inner.(*DynamicFeeTx)
if !ok {
return s.eip2930Signer.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
return nil, nil, nil, ErrInvalidChainId
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s londonSigner) Hash(tx *Transaction) common.Hash {
if tx.Type() != DynamicFeeTxType {
return s.eip2930Signer.Hash(tx)
}
return prefixedRlpHash(
tx.Type(),
[]interface{}{
s.chainId,
tx.Nonce(),
tx.GasTipCap(),
tx.GasFeeCap(),
tx.Gas(),
tx.To(),
tx.Value(),
tx.Data(),
tx.AccessList(),
})
}
type eip2930Signer struct{ EIP155Signer } type eip2930Signer struct{ EIP155Signer }
// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, // NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions,
@ -193,8 +259,8 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
case LegacyTxType: case LegacyTxType:
return s.EIP155Signer.Sender(tx) return s.EIP155Signer.Sender(tx)
case AccessListTxType: case AccessListTxType:
// ACL txs are defined to use 0 and 1 as their recovery id, add // AL txs are defined to use 0 and 1 as their recovery
// 27 to become equivalent to unprotected Homestead signatures. // id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27)) V = new(big.Int).Add(V, big.NewInt(27))
default: default:
return common.Address{}, ErrTxTypeNotSupported return common.Address{}, ErrTxTypeNotSupported

View file

@ -76,7 +76,7 @@ func TestDecodeEmptyTypedTx(t *testing.T) {
input := []byte{0x80} input := []byte{0x80}
var tx Transaction var tx Transaction
err := rlp.DecodeBytes(input, &tx) err := rlp.DecodeBytes(input, &tx)
if err != errEmptyTypedTx { if err != errShortTypedTx {
t.Fatal("wrong error:", err) t.Fatal("wrong error:", err)
} }
} }

View file

@ -59,7 +59,7 @@ type AccessListTx struct {
func (tx *AccessListTx) copy() TxData { func (tx *AccessListTx) copy() TxData {
cpy := &AccessListTx{ cpy := &AccessListTx{
Nonce: tx.Nonce, Nonce: tx.Nonce,
To: tx.To, // TODO: copy pointed-to address To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data), Data: common.CopyBytes(tx.Data),
Gas: tx.Gas, Gas: tx.Gas,
// These are copied below. // These are copied below.
@ -94,18 +94,22 @@ func (tx *AccessListTx) copy() TxData {
} }
// accessors for innerTx. // accessors for innerTx.
func (tx *AccessListTx) txType() byte { return AccessListTxType } func (tx *AccessListTx) txType() byte { return AccessListTxType }
func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID }
func (tx *AccessListTx) protected() bool { return true }
func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } func (tx *AccessListTx) accessList() AccessList { return tx.AccessList }
func (tx *AccessListTx) data() []byte { return tx.Data } func (tx *AccessListTx) data() []byte { return tx.Data }
func (tx *AccessListTx) gas() uint64 { return tx.Gas } func (tx *AccessListTx) gas() uint64 { return tx.Gas }
func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) value() *big.Int { return tx.Value }
func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce }
func (tx *AccessListTx) to() *common.Address { return tx.To } func (tx *AccessListTx) to() *common.Address { return tx.To }
func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return dst.Set(tx.GasPrice)
}
func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S return tx.V, tx.R, tx.S
} }

View file

@ -0,0 +1,114 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
)
type DynamicFeeTx struct {
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *big.Int // a.k.a. maxFeePerGas
Gas uint64
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int
Data []byte
AccessList AccessList
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
}
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *DynamicFeeTx) copy() TxData {
cpy := &DynamicFeeTx{
Nonce: tx.Nonce,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
Value: new(big.Int),
ChainID: new(big.Int),
GasTipCap: new(big.Int),
GasFeeCap: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
copy(cpy.AccessList, tx.AccessList)
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
if tx.GasTipCap != nil {
cpy.GasTipCap.Set(tx.GasTipCap)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.V != nil {
cpy.V.Set(tx.V)
}
if tx.R != nil {
cpy.R.Set(tx.R)
}
if tx.S != nil {
cpy.S.Set(tx.S)
}
return cpy
}
// accessors for innerTx.
func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType }
func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID }
func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList }
func (tx *DynamicFeeTx) data() []byte { return tx.Data }
func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas }
func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) value() *big.Int { return tx.Value }
func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce }
func (tx *DynamicFeeTx) to() *common.Address { return tx.To }
func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
return dst.Set(tx.GasFeeCap)
}
tip := dst.Sub(tx.GasFeeCap, baseFee)
if tip.Cmp(tx.GasTipCap) > 0 {
tip.Set(tx.GasTipCap)
}
return tip.Add(tip, baseFee)
}
func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S
}
func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}

View file

@ -62,7 +62,7 @@ func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPric
func (tx *LegacyTx) copy() TxData { func (tx *LegacyTx) copy() TxData {
cpy := &LegacyTx{ cpy := &LegacyTx{
Nonce: tx.Nonce, Nonce: tx.Nonce,
To: tx.To, // TODO: copy pointed-to address To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data), Data: common.CopyBytes(tx.Data),
Gas: tx.Gas, Gas: tx.Gas,
// These are initialized below. // These are initialized below.
@ -98,10 +98,16 @@ func (tx *LegacyTx) accessList() AccessList { return nil }
func (tx *LegacyTx) data() []byte { return tx.Data } func (tx *LegacyTx) data() []byte { return tx.Data }
func (tx *LegacyTx) gas() uint64 { return tx.Gas } func (tx *LegacyTx) gas() uint64 { return tx.Gas }
func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice }
func (tx *LegacyTx) gasTipCap() *big.Int { return tx.GasPrice }
func (tx *LegacyTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) value() *big.Int { return tx.Value }
func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce }
func (tx *LegacyTx) to() *common.Address { return tx.To } func (tx *LegacyTx) to() *common.Address { return tx.To }
func (tx *LegacyTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return dst.Set(tx.GasPrice)
}
func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V, tx.R, tx.S return tx.V, tx.R, tx.S
} }

View file

@ -61,16 +61,14 @@ func (al accessList) equal(other accessList) bool {
if len(al) != len(other) { if len(al) != len(other) {
return false return false
} }
// Given that len(al) == len(other), we only need to check that
// all the items from al are in other.
for addr := range al { for addr := range al {
if _, ok := other[addr]; !ok { if _, ok := other[addr]; !ok {
return false return false
} }
} }
for addr := range other {
if _, ok := al[addr]; !ok {
return false
}
}
// Accounts match, cross reference the storage slots too // Accounts match, cross reference the storage slots too
for addr, slots := range al { for addr, slots := range al {
otherslots := other[addr] otherslots := other[addr]
@ -78,16 +76,13 @@ func (al accessList) equal(other accessList) bool {
if len(slots) != len(otherslots) { if len(slots) != len(otherslots) {
return false return false
} }
// Given that len(slots) == len(otherslots), we only need to check that
// all the items from slots are in otherslots.
for hash := range slots { for hash := range slots {
if _, ok := otherslots[hash]; !ok { if _, ok := otherslots[hash]; !ok {
return false return false
} }
} }
for hash := range otherslots {
if _, ok := slots[hash]; !ok {
return false
}
}
} }
return true return true
} }

View file

@ -26,6 +26,8 @@ import (
var activators = map[int]func(*JumpTable){ var activators = map[int]func(*JumpTable){
3855: enable3855, 3855: enable3855,
3860: enable3860,
3529: enable3529,
3198: enable3198, 3198: enable3198,
2929: enable2929, 2929: enable2929,
2200: enable2200, 2200: enable2200,
@ -104,28 +106,28 @@ func enable2929(jt *JumpTable) {
jt[SLOAD].constantGas = 0 jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929 jt[SLOAD].dynamicGas = gasSLoadEIP2929
jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929 jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929
jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929 jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck
jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929 jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck
jt[BALANCE].constantGas = WarmStorageReadCostEIP2929 jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929
jt[BALANCE].dynamicGas = gasEip2929AccountCheck jt[BALANCE].dynamicGas = gasEip2929AccountCheck
jt[CALL].constantGas = WarmStorageReadCostEIP2929 jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
jt[CALL].dynamicGas = gasCallEIP2929 jt[CALL].dynamicGas = gasCallEIP2929
jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929 jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 jt[CALLCODE].dynamicGas = gasCallCodeEIP2929
jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929 jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 jt[STATICCALL].dynamicGas = gasStaticCallEIP2929
jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929 jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929
// This was previously part of the dynamic cost, but we're using it as a constantGas // This was previously part of the dynamic cost, but we're using it as a constantGas
@ -134,6 +136,15 @@ func enable2929(jt *JumpTable) {
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
} }
// enable3529 enabled "EIP-3529: Reduction in refunds":
// - Removes refunds for selfdestructs
// - Reduces refunds for SSTORE
// - Reduces max refunds to 20% gas
func enable3529(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP3529
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529
}
// enable3198 applies EIP-3198 (BASEFEE Opcode) // enable3198 applies EIP-3198 (BASEFEE Opcode)
// - Adds an opcode that returns the current block's base fee. // - Adds an opcode that returns the current block's base fee.
func enable3198(jt *JumpTable) { func enable3198(jt *JumpTable) {
@ -148,7 +159,7 @@ func enable3198(jt *JumpTable) {
// opBaseFee implements BASEFEE opcode // opBaseFee implements BASEFEE opcode
func opBaseFee(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) { func opBaseFee(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) {
baseFee, _ := uint256.FromBig(common.MinGasPrice50x) baseFee, _ := uint256.FromBig(common.BaseFee)
callContext.Stack.push(baseFee) callContext.Stack.push(baseFee)
return nil, nil return nil, nil
} }
@ -169,3 +180,10 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext)
callContext.Stack.push(new(uint256.Int)) callContext.Stack.push(new(uint256.Int))
return nil, nil return nil, nil
} }
// ebnable3860 enables "EIP-3860: Limit and meter initcode"
// https://eips.ethereum.org/EIPS/eip-3860
func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860
}

View file

@ -29,6 +29,7 @@ var (
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrExecutionReverted = errors.New("execution reverted") ErrExecutionReverted = errors.New("execution reverted")
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
ErrInvalidJump = errors.New("invalid jump destination") ErrInvalidJump = errors.New("invalid jump destination")
ErrWriteProtection = errors.New("write protection") ErrWriteProtection = errors.New("write protection")

View file

@ -101,6 +101,7 @@ type BlockContext struct {
BlockNumber *big.Int // Provides information for NUMBER BlockNumber *big.Int // Provides information for NUMBER
Time *big.Int // Provides information for TIME Time *big.Int // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE
Random *common.Hash // Provides information for PREVRANDAO Random *common.Hash // Provides information for PREVRANDAO
} }
@ -139,7 +140,7 @@ type EVM struct {
chainRules params.Rules chainRules params.Rules
// virtual machine configuration options used to initialise the // virtual machine configuration options used to initialise the
// evm. // evm.
vmConfig Config Config Config
// global (to this context) ethereum virtual machine // global (to this context) ethereum virtual machine
// used throughout the execution of the tx. // used throughout the execution of the tx.
interpreters []Interpreter interpreters []Interpreter
@ -155,13 +156,13 @@ type EVM struct {
// NewEVM returns a new EVM. The returned EVM is not thread safe and should // NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*. // only ever be used *once*.
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, tradingStateDB *tradingstate.TradingStateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, tradingStateDB *tradingstate.TradingStateDB, chainConfig *params.ChainConfig, config Config) *EVM {
evm := &EVM{ evm := &EVM{
Context: blockCtx, Context: blockCtx,
TxContext: txCtx, TxContext: txCtx,
StateDB: statedb, StateDB: statedb,
tradingStateDB: tradingStateDB, tradingStateDB: tradingStateDB,
vmConfig: vmConfig, Config: config,
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber), chainRules: chainConfig.Rules(blockCtx.BlockNumber),
interpreters: make([]Interpreter, 0, 1), interpreters: make([]Interpreter, 0, 1),
@ -169,7 +170,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, tradingStat
// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
// as we always want to have the built-in EVM as the failover option. // as we always want to have the built-in EVM as the failover option.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig)) evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, config))
evm.interpreter = evm.interpreters[0] evm.interpreter = evm.interpreters[0]
return evm return evm
@ -217,13 +218,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug { if evm.Config.Debug {
if evm.depth == 0 { if evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
} else { } else {
evm.vmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
evm.vmConfig.Tracer.CaptureExit(ret, 0, nil) evm.Config.Tracer.CaptureExit(ret, 0, nil)
} }
} }
return nil, gas, nil return nil, gas, nil
@ -233,18 +234,18 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug { if evm.Config.Debug {
if evm.depth == 0 { if evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now()) }(gas, time.Now())
} else { } else {
// Handle tracer events for entering and exiting a call frame // Handle tracer events for entering and exiting a call frame
evm.vmConfig.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas) }(gas)
} }
} }
@ -304,10 +305,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.vmConfig.Debug { if evm.Config.Debug {
evm.vmConfig.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas) }(gas)
} }
@ -345,10 +346,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.vmConfig.Debug { if evm.Config.Debug {
evm.vmConfig.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil) evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas) }(gas)
} }
@ -395,10 +396,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
evm.StateDB.AddBalance(addr, big0) evm.StateDB.AddBalance(addr, big0)
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.vmConfig.Debug { if evm.Config.Debug {
evm.vmConfig.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.vmConfig.Tracer.CaptureExit(ret, startGas-gas, err) evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas) }(gas)
} }
@ -479,11 +480,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
contract := NewContract(caller, AccountRef(address), value, gas) contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash) contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.vmConfig.Debug { if evm.Config.Debug {
if evm.depth == 0 { if evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} else { } else {
evm.vmConfig.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
} }
} }
start := time.Now() start := time.Now()
@ -523,11 +524,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
} }
if evm.vmConfig.Debug { if evm.Config.Debug {
if evm.depth == 0 { if evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
} else { } else {
evm.vmConfig.Tracer.CaptureExit(ret, gas-contract.Gas, err) evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err)
} }
} }
return ret, address, contract.Gas, err return ret, address, contract.Gas, err

View file

@ -300,6 +300,40 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
return gas, nil return gas, nil
} }
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize {
return 0, ErrGasUintOverflow
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := params.InitCodeWordGas * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize {
return 0, ErrGasUintOverflow
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)

View file

@ -17,8 +17,10 @@
package vm package vm
import ( import (
"bytes"
"math" "math"
"math/big" "math/big"
"sort"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
@ -106,3 +108,72 @@ func TestEIP2200(t *testing.T) {
} }
} }
} }
var createGasTests = []struct {
code string
eip3860 bool
gasUsed uint64
minimumGas uint64
}{
// legacy create(0, 0, 0xc000) without 3860 used
{"0x61C00060006000f0" + "600052" + "60206000F3", false, 41237, 41237},
// legacy create(0, 0, 0xc000) _with_ 3860
{"0x61C00060006000f0" + "600052" + "60206000F3", true, 44309, 44309},
// create2(0, 0, 0xc001, 0) without 3860
{"0x600061C00160006000f5" + "600052" + "60206000F3", false, 50471, 100_000},
// create2(0, 0, 0xc001, 0) (too large), with 3860
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000},
// create2(0, 0, 0xc000, 0)
// This case is trying to deploy code at (within) the limit
{"0x600061C00060006000f5" + "600052" + "60206000F3", true, 53528, 100_000},
// create2(0, 0, 0xc001, 0)
// This case is trying to deploy code exceeding the limit
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100_000}}
func TestCreateGas(t *testing.T) {
for i, tt := range createGasTests {
var gasUsed = uint64(0)
doCheck := func(testGas int) bool {
address := common.BytesToAddress([]byte("contract"))
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.code))
statedb.Finalise(true)
vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
BlockNumber: big.NewInt(0),
}
config := Config{}
if tt.eip3860 {
config.ExtraEips = []int{3860}
}
vmenv := NewEVM(vmctx, TxContext{}, statedb, nil, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas)
ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int))
if err != nil {
return false
}
gasUsed = startGas - gas
if len(ret) != 32 {
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
}
if bytes.Equal(ret, make([]byte, 32)) {
// Failure
return false
}
return true
}
minGas := sort.Search(100_000, doCheck)
if uint64(minGas) != tt.minimumGas {
t.Fatalf("test %d: min gas error, want %d, have %d", i, tt.minimumGas, minGas)
}
// If the deployment succeeded, we also check the gas used
if minGas < 100_000 {
if gasUsed != tt.gasUsed {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed)
}
}
}
}

View file

@ -246,7 +246,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
interpreter.hasher.Read(interpreter.hasherBuf[:]) interpreter.hasher.Read(interpreter.hasherBuf[:])
evm := interpreter.evm evm := interpreter.evm
if evm.vmConfig.EnablePreimageRecording { if evm.Config.EnablePreimageRecording {
evm.StateDB.AddPreimage(interpreter.hasherBuf, data) evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
} }
@ -646,7 +646,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas gas = scope.Contract.Gas
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas)

View file

@ -194,7 +194,7 @@ func TestAddMod(t *testing.T) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
pc = uint64(0) pc = uint64(0)
) )
tests := []struct { tests := []struct {
@ -290,7 +290,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
scope = &ScopeContext{nil, stack, nil} scope = &ScopeContext{nil, stack, nil}
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -531,7 +531,7 @@ func TestOpMstore(t *testing.T) {
env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -557,7 +557,7 @@ func BenchmarkOpMstore(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -579,7 +579,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
mem.Resize(32) mem.Resize(32)
@ -683,7 +683,7 @@ func TestRandom(t *testing.T) {
env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 { if len(stack.data) != 1 {

View file

@ -27,6 +27,7 @@ import (
type Config struct { type Config struct {
Debug bool // Enables debugging Debug bool // Enables debugging
Tracer EVMLogger // Opcode logger Tracer EVMLogger // Opcode logger
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
JumpTable *JumpTable // EVM instruction table, automatically populated if unset JumpTable *JumpTable // EVM instruction table, automatically populated if unset

View file

@ -83,6 +83,8 @@ func validate(jt JumpTable) JumpTable {
func newEip1559InstructionSet() JumpTable { func newEip1559InstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet()
enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929 enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
enable3860(&instructionSet) // Limit and meter initcode
return validate(instructionSet) return validate(instructionSet)
} }
@ -107,7 +109,6 @@ func newMergeInstructionSet() JumpTable {
// constantinople, istanbul, petersburg, berlin and london instructions. // constantinople, istanbul, petersburg, berlin and london instructions.
func newLondonInstructionSet() JumpTable { func newLondonInstructionSet() JumpTable {
instructionSet := newBerlinInstructionSet() instructionSet := newBerlinInstructionSet()
// enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -24,91 +24,75 @@ import (
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
const ( func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST // If we fail the minimum gas availability invariant, fail (0)
WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST if contract.Gas <= params.SstoreSentryGasEIP2200 {
) return 0, errors.New("not enough gas for reentrancy sentry")
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929"
//
// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys.
// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys.
// Additionally, modify the parameters defined in EIP 2200 as follows:
//
// Parameter Old value New value
// SLOAD_GAS 800 = WARM_STORAGE_READ_COST
// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST
//
// The other parameters defined in EIP 2200 are unchanged.
// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified
func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 {
return 0, errors.New("not enough gas for reentrancy sentry")
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
y, x = stack.Back(1), stack.peek()
slot = common.Hash(x.Bytes32())
current = evm.StateDB.GetState(contract.Address(), slot)
cost = uint64(0)
)
// Check slot presence in the access list
if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
cost = ColdSloadCostEIP2929
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
if !addrPresent {
// Once we're done with YOLOv2 and schedule this for mainnet, might
// be good to remove this panic here, which is just really a
// canary to have during testing
panic("impossible case: address was not present in access list during sstore op")
} }
} // Gas sentry honoured, do the actual gas calculation based on the stored value
value := common.Hash(y.Bytes32()) var (
y, x = stack.Back(1), stack.peek()
if current == value { // noop (1) slot = common.Hash(x.Bytes32())
// EIP 2200 original clause: current = evm.StateDB.GetState(contract.Address(), slot)
// return params.SloadGasEIP2200, nil cost = uint64(0)
return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS )
} // Check slot presence in the access list
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
if original == current { cost = params.ColdSloadCostEIP2929
if original == (common.Hash{}) { // create slot (2.1.1) // If the caller cannot afford the cost, this change will be rolled back
return cost + params.SstoreSetGasEIP2200, nil evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
if !addrPresent {
// Once we're done with YOLOv2 and schedule this for mainnet, might
// be good to remove this panic here, which is just really a
// canary to have during testing
panic("impossible case: address was not present in access list during sstore op")
}
} }
if value == (common.Hash{}) { // delete slot (2.1.2b) value := common.Hash(y.Bytes32())
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
if current == value { // noop (1)
// EIP 2200 original clause:
// return params.SloadGasEIP2200, nil
return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS
}
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
return cost + params.SstoreSetGasEIP2200, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(clearingRefund)
}
// EIP-2200 original clause:
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(clearingRefund)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(clearingRefund)
}
}
if original == value {
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
// EIP 2200 Original clause:
//evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200)
evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929)
} else { // reset to original existing slot (2.2.2.2)
// EIP 2200 Original clause:
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
}
} }
// EIP-2200 original clause: // EIP-2200 original clause:
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) //return params.SloadGasEIP2200, nil // dirty update (2.2)
return cost + (params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929), nil // write existing slot (2.1.2) return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2)
} }
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
}
}
if original == value {
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
// EIP 2200 Original clause:
//evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200)
evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929)
} else { // reset to original existing slot (2.2.2.2)
// EIP 2200 Original clause:
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929)
}
}
// EIP-2200 original clause:
//return params.SloadGasEIP2200, nil // dirty update (2.2)
return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2)
} }
// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 // gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929
@ -124,9 +108,9 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
// If the caller cannot afford the cost, this change will be rolled back // If the caller cannot afford the cost, this change will be rolled back
// If he does afford it, we can skip checking the same thing later on, during execution // If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot) evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
return ColdSloadCostEIP2929, nil return params.ColdSloadCostEIP2929, nil
} }
return WarmStorageReadCostEIP2929, nil return params.WarmStorageReadCostEIP2929, nil
} }
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 // gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
@ -146,7 +130,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
var overflow bool var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas // We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow { if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
@ -168,7 +152,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
// If the caller cannot afford the cost, this change will be rolled back // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// The warm storage read cost is already charged as constantGas // The warm storage read cost is already charged as constantGas
return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil
} }
return 0, nil return 0, nil
} }
@ -177,10 +161,15 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
addr := common.Address(stack.Back(1).Bytes20()) addr := common.Address(stack.Back(1).Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) { warmAccess := evm.StateDB.AddressInAccessList(addr)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
// the cost to charge for cold access, if any, is Cold - Warm
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !warmAccess {
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost // Charge the remaining difference here already, to correctly calculate available
if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) { // gas for call
if !contract.UseGas(coldCost) {
return 0, ErrOutOfGas return 0, ErrOutOfGas
} }
} }
@ -189,7 +178,16 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
// - transfer value // - transfer value
// - memory expansion // - memory expansion
// - 63/64ths rule // - 63/64ths rule
return oldCalculator(evm, contract, stack, mem, memorySize) gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
if warmAccess || err != nil {
return gas, err
}
// In case of a cold access, we temporarily add the cold charge back, and also
// add it to the returned gas. By adding it to the return, it will be charged
// outside of this function, as part of the dynamic gas, and that will make it
// also become correctly reported to tracers.
contract.Gas += coldCost
return gas + coldCost, nil
} }
} }
@ -198,24 +196,49 @@ var (
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall)
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall)
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode)
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929
//
// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys.
// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys.
// Additionally, modify the parameters defined in EIP 2200 as follows:
//
// Parameter Old value New value
// SLOAD_GAS 800 = WARM_STORAGE_READ_COST
// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST
//
//The other parameters defined in EIP 2200 are unchanged.
// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified
gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200)
// gasSStoreEIP3529 implements gas cost for SSTORE according to EIP-3529
// Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800)
gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529)
) )
func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539
var ( func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gas uint64 gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address = common.Address(stack.peek().Bytes20()) var (
) gas uint64
if !evm.StateDB.AddressInAccessList(address) { address = common.Address(stack.peek().Bytes20())
// If the caller cannot afford the cost, this change will be rolled back )
evm.StateDB.AddAddressToAccessList(address) if !evm.StateDB.AddressInAccessList(address) {
gas = ColdAccountAccessCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address)
gas = params.ColdAccountAccessCostEIP2929
}
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
}
if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) {
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
}
return gas, nil
} }
// if empty and transfers value return gasFunc
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
}
if !evm.StateDB.HasSuicided(contract.Address()) {
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
}
return gas, nil
} }

View file

@ -35,6 +35,7 @@ func NewEnv(cfg *Config) *vm.EVM {
Time: cfg.Time, Time: cfg.Time,
Difficulty: cfg.Difficulty, Difficulty: cfg.Difficulty,
GasLimit: cfg.GasLimit, GasLimit: cfg.GasLimit,
BaseFee: cfg.BaseFee,
} }
return vm.NewEVM(blockContext, txContext, cfg.State, nil, cfg.ChainConfig, cfg.EVMConfig) return vm.NewEVM(blockContext, txContext, cfg.State, nil, cfg.ChainConfig, cfg.EVMConfig)

View file

@ -43,6 +43,7 @@ type Config struct {
Value *big.Int Value *big.Int
Debug bool Debug bool
EVMConfig vm.Config EVMConfig vm.Config
BaseFee *big.Int
State *state.StateDB State *state.StateDB
GetHashFn func(n uint64) common.Hash GetHashFn func(n uint64) common.Hash
@ -68,6 +69,7 @@ func setDefaults(cfg *Config) {
LondonBlock: new(big.Int), LondonBlock: new(big.Int),
MergeBlock: new(big.Int), MergeBlock: new(big.Int),
ShanghaiBlock: new(big.Int), ShanghaiBlock: new(big.Int),
Eip1559Block: new(big.Int),
} }
} }
@ -94,6 +96,9 @@ func setDefaults(cfg *Config) {
return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String())))
} }
} }
if cfg.BaseFee == nil {
cfg.BaseFee = big.NewInt(params.InitialBaseFee)
}
} }
// Execute executes the code using the input as call data during the execution. // Execute executes the code using the input as call data during the execution.

View file

@ -597,3 +597,83 @@ func TestEip2929Cases(t *testing.T) {
"account (cheap)", code) "account (cheap)", code)
} }
} }
// TestColdAccountAccessCost test that the cold account access cost is reported
// correctly
// see: https://github.com/ethereum/go-ethereum/issues/22649
func TestColdAccountAccessCost(t *testing.T) {
for i, tc := range []struct {
code []byte
step int
want uint64
}{
{ // EXTCODEHASH(0xff)
code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.EXTCODEHASH), byte(vm.POP)},
step: 1,
want: 2600,
},
{ // BALANCE(0xff)
code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.BALANCE), byte(vm.POP)},
step: 1,
want: 2600,
},
{ // CALL(0xff)
code: []byte{
byte(vm.PUSH1), 0x0,
byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP),
},
step: 7,
want: 2855,
},
{ // CALLCODE(0xff)
code: []byte{
byte(vm.PUSH1), 0x0,
byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP),
},
step: 7,
want: 2855,
},
{ // DELEGATECALL(0xff)
code: []byte{
byte(vm.PUSH1), 0x0,
byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP),
},
step: 6,
want: 2855,
},
{ // STATICCALL(0xff)
code: []byte{
byte(vm.PUSH1), 0x0,
byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP),
},
step: 6,
want: 2855,
},
{ // SELFDESTRUCT(0xff)
code: []byte{
byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT),
},
step: 1,
want: 7600,
},
} {
tracer := vm.NewStructLogger(nil)
Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{
Debug: true,
Tracer: tracer,
},
})
have := tracer.StructLogs()[tc.step].GasCost
if want := tc.want; have != want {
for ii, op := range tracer.StructLogs() {
t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
}
t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
}
}
}

View file

@ -237,7 +237,7 @@ func (b *EthApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*t
} }
func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
return core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)), nil return b.eth.blockchain.GetReceiptsByHash(blockHash), nil
} }
func (b *EthApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { func (b *EthApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
@ -298,10 +298,7 @@ func (b *EthApiBackend) SendLendingTx(ctx context.Context, signedTx *types.Lendi
} }
func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) { func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
pending, err := b.eth.txPool.Pending() pending := b.eth.txPool.Pending(false)
if err != nil {
return nil, err
}
var txs types.Transactions var txs types.Transactions
for _, batch := range pending { for _, batch := range pending {
txs = append(txs, batch...) txs = append(txs, batch...)
@ -325,6 +322,10 @@ func (b *EthApiBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.TxPool().Content() return b.eth.TxPool().Content()
} }
func (b *EthApiBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
return b.eth.TxPool().ContentFrom(addr)
}
func (b *EthApiBackend) OrderTxPoolContent() (map[common.Address]types.OrderTransactions, map[common.Address]types.OrderTransactions) { func (b *EthApiBackend) OrderTxPoolContent() (map[common.Address]types.OrderTransactions, map[common.Address]types.OrderTransactions) {
return b.eth.OrderPool().Content() return b.eth.OrderPool().Content()
} }
@ -344,8 +345,12 @@ func (b *EthApiBackend) ProtocolVersion() int {
return b.eth.EthVersion() return b.eth.EthVersion()
} }
func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { func (b *EthApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx) return b.gpo.SuggestTipCap(ctx)
}
func (b *EthApiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
} }
func (b *EthApiBackend) ChainDb() ethdb.Database { func (b *EthApiBackend) ChainDb() ethdb.Database {
@ -393,6 +398,10 @@ func (b *EthApiBackend) GetEngine() consensus.Engine {
return b.eth.engine return b.eth.engine
} }
func (b *EthApiBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
func (b *EthApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { func (b *EthApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
return b.eth.stateAtBlock(block, reexec, base, checkLive) return b.eth.stateAtBlock(block, reexec, base, checkLive)
} }

View file

@ -28,7 +28,6 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
@ -236,13 +235,14 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
feeCapacity := state.GetTRC21FeeCapacityFromState(task.statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(task.statedb)
// Trace all the transactions contained within // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { for i, tx := range task.block.Transactions() {
var balacne *big.Int var balance *big.Int
if tx.To() != nil { if tx.To() != nil {
if value, ok := feeCapacity[*tx.To()]; ok { if value, ok := feeCapacity[*tx.To()]; ok {
balacne = value balance = value
} }
} }
msg, _ := tx.AsMessage(signer, balacne, task.block.Number()) header := task.block.Header()
msg, _ := tx.AsMessage(signer, balance, header.Number, header.BaseFee)
txctx := &tracers.Context{ txctx := &tracers.Context{
BlockHash: task.block.Hash(), BlockHash: task.block.Hash(),
TxIndex: i, TxIndex: i,
@ -480,13 +480,14 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
// Fetch and execute the next transaction trace tasks // Fetch and execute the next transaction trace tasks
for task := range jobs { for task := range jobs {
feeCapacity := state.GetTRC21FeeCapacityFromState(task.statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(task.statedb)
var balacne *big.Int var balance *big.Int
if txs[task.index].To() != nil { if txs[task.index].To() != nil {
if value, ok := feeCapacity[*txs[task.index].To()]; ok { if value, ok := feeCapacity[*txs[task.index].To()]; ok {
balacne = value balance = value
} }
} }
msg, _ := txs[task.index].AsMessage(signer, balacne, block.Number()) header := block.Header()
msg, _ := txs[task.index].AsMessage(signer, balance, header.Number, header.BaseFee)
txctx := &tracers.Context{ txctx := &tracers.Context{
BlockHash: blockHash, BlockHash: blockHash,
TxIndex: task.index, TxIndex: task.index,
@ -507,18 +508,19 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
for i, tx := range txs { for i, tx := range txs {
// Send the trace task over for execution // Send the trace task over for execution
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
var balacne *big.Int var balance *big.Int
if tx.To() != nil { if tx.To() != nil {
// Bypass the validation for trading and lending transactions as their nonce are not incremented // Bypass the validation for trading and lending transactions as their nonce are not incremented
if tx.IsSkipNonceTransaction() { if tx.IsSkipNonceTransaction() {
continue continue
} }
if value, ok := feeCapacity[*tx.To()]; ok { if value, ok := feeCapacity[*tx.To()]; ok {
balacne = value balance = value
} }
} }
// Generate the next state snapshot fast without tracing // Generate the next state snapshot fast without tracing
msg, _ := tx.AsMessage(signer, balacne, block.Number()) header := block.Header()
msg, _ := tx.AsMessage(signer, balance, header.Number, header.BaseFee)
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
statedb.Prepare(tx.Hash(), i) statedb.Prepare(tx.Hash(), i)
@ -689,7 +691,12 @@ func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.Transacti
} }
} }
// Execute the trace // Execute the trace
msg := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap()) // TODO: replace block.BaseFee() with vmctx.BaseFee
// reference: https://github.com/ethereum/go-ethereum/pull/29051
msg, err := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap(), block.BaseFee())
if err != nil {
return nil, err
}
vmctx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) vmctx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil)
var traceConfig *TraceConfig var traceConfig *TraceConfig
if config != nil { if config != nil {
@ -737,7 +744,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, t
tracer = vm.NewStructLogger(config.LogConfig) tracer = vm.NewStructLogger(config.LogConfig)
} }
// Run the transaction with tracing enabled. // Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.config, vm.Config{Debug: true, Tracer: tracer}) vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.config, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
// Call Prepare to clear out the statedb access list // Call Prepare to clear out the statedb access list
statedb.Prepare(txctx.TxHash, txctx.TxIndex) statedb.Prepare(txctx.TxHash, txctx.TxIndex)
@ -800,7 +807,8 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree
balanceFee = value balanceFee = value
} }
} }
msg, err := tx.AsMessage(types.MakeSigner(api.config, block.Header().Number), balanceFee, block.Number()) header := block.Header()
msg, err := tx.AsMessage(types.MakeSigner(api.config, header.Number), balanceFee, header.Number, header.BaseFee)
if err != nil { if err != nil {
return nil, vm.BlockContext{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) return nil, vm.BlockContext{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
} }

View file

@ -37,6 +37,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/contracts" "github.com/XinFinOrg/XDPoSChain/contracts"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/bloombits" "github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
@ -72,9 +73,9 @@ type Ethereum struct {
shutdownChan chan bool // Channel for shutting down the ethereum shutdownChan chan bool // Channel for shutting down the ethereum
// Handlers // Handlers
txPool *core.TxPool txPool *txpool.TxPool
orderPool *core.OrderPool orderPool *txpool.OrderPool
lendingPool *core.LendingPool lendingPool *txpool.LendingPool
blockchain *core.BlockChain blockchain *core.BlockChain
protocolManager *ProtocolManager protocolManager *ProtocolManager
lesServer LesServer lesServer LesServer
@ -186,9 +187,9 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
if config.TxPool.Journal != "" { if config.TxPool.Journal != "" {
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
} }
eth.txPool = core.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain) eth.txPool = txpool.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
eth.orderPool = core.NewOrderPool(eth.chainConfig, eth.blockchain) eth.orderPool = txpool.NewOrderPool(eth.chainConfig, eth.blockchain)
eth.lendingPool = core.NewLendingPool(eth.chainConfig, eth.blockchain) eth.lendingPool = txpool.NewLendingPool(eth.chainConfig, eth.blockchain)
if common.RollbackHash != (common.Hash{}) { if common.RollbackHash != (common.Hash{}) {
curBlock := eth.blockchain.CurrentBlock() curBlock := eth.blockchain.CurrentBlock()
if curBlock == nil { if curBlock == nil {
@ -517,7 +518,7 @@ func (e *Ethereum) Miner() *miner.Miner { return e.miner }
func (e *Ethereum) AccountManager() *accounts.Manager { return e.accountManager } func (e *Ethereum) AccountManager() *accounts.Manager { return e.accountManager }
func (e *Ethereum) BlockChain() *core.BlockChain { return e.blockchain } func (e *Ethereum) BlockChain() *core.BlockChain { return e.blockchain }
func (e *Ethereum) TxPool() *core.TxPool { return e.txPool } func (e *Ethereum) TxPool() *txpool.TxPool { return e.txPool }
func (e *Ethereum) EventMux() *event.TypeMux { return e.eventMux } func (e *Ethereum) EventMux() *event.TypeMux { return e.eventMux }
func (e *Ethereum) Engine() consensus.Engine { return e.engine } func (e *Ethereum) Engine() consensus.Engine { return e.engine }
func (e *Ethereum) ChainDb() ethdb.Database { return e.chainDb } func (e *Ethereum) ChainDb() ethdb.Database { return e.chainDb }
@ -591,7 +592,7 @@ func (e *Ethereum) GetXDCX() *XDCx.XDCX {
return e.XDCX return e.XDCX
} }
func (e *Ethereum) OrderPool() *core.OrderPool { func (e *Ethereum) OrderPool() *txpool.OrderPool {
return e.orderPool return e.orderPool
} }
@ -600,6 +601,6 @@ func (e *Ethereum) GetXDCXLending() *XDCxlending.Lending {
} }
// LendingPool geth eth lending pool // LendingPool geth eth lending pool
func (e *Ethereum) LendingPool() *core.LendingPool { func (e *Ethereum) LendingPool() *txpool.LendingPool {
return e.lendingPool return e.lendingPool
} }

View file

@ -19,6 +19,7 @@ package downloader
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -78,7 +79,7 @@ type downloadTester struct {
// newTester creates a new downloader test mocker. // newTester creates a new downloader test mocker.
func newTester() *downloadTester { func newTester() *downloadTester {
testdb := rawdb.NewMemoryDatabase() testdb := rawdb.NewMemoryDatabase()
genesis := core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) genesis := core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(math.MaxInt64))
tester := &downloadTester{ tester := &downloadTester{
genesis: genesis, genesis: genesis,
@ -109,7 +110,8 @@ func newTester() *downloadTester {
// reassembly. // reassembly.
func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) { func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) {
// Generate the block chain // Generate the block chain
blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), dl.peerDb, n, func(i int, block *core.BlockGen) { config := dl.Config()
blocks, receipts := core.GenerateChain(config, parent, ethash.NewFaker(), dl.peerDb, n, func(i int, block *core.BlockGen) {
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// If a heavy chain is requested, delay blocks to raise difficulty // If a heavy chain is requested, delay blocks to raise difficulty
@ -118,8 +120,8 @@ func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, paren
} }
// If the block number is multiple of 3, send a bonus transaction to the miner // If the block number is multiple of 3, send a bonus transaction to the miner
if parent == dl.genesis && i%3 == 0 { if parent == dl.genesis && i%3 == 0 {
signer := types.MakeSigner(params.TestChainConfig, block.Number()) signer := types.MakeSigner(config, block.Number())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -464,7 +466,11 @@ func (dl *downloadTester) handleProposedBlock(header *types.Header) error {
} }
// Config retrieves the blockchain's chain configuration. // Config retrieves the blockchain's chain configuration.
func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig } func (dl *downloadTester) Config() *params.ChainConfig {
config := *params.TestChainConfig
config.Eip1559Block = big.NewInt((0))
return &config
}
type downloadTesterPeer struct { type downloadTesterPeer struct {
dl *downloadTester dl *downloadTester

View file

@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice" "github.com/XinFinOrg/XDPoSChain/eth/gasprice"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -36,18 +37,22 @@ import (
// FullNodeGPO contains default gasprice oracle settings for full node. // FullNodeGPO contains default gasprice oracle settings for full node.
var FullNodeGPO = gasprice.Config{ var FullNodeGPO = gasprice.Config{
Blocks: 20, Blocks: 20,
Percentile: 60, Percentile: 60,
MaxPrice: gasprice.DefaultMaxPrice, MaxHeaderHistory: 1024,
IgnorePrice: gasprice.DefaultIgnorePrice, MaxBlockHistory: 1024,
MaxPrice: gasprice.DefaultMaxPrice,
IgnorePrice: gasprice.DefaultIgnorePrice,
} }
// LightClientGPO contains default gasprice oracle settings for light client. // LightClientGPO contains default gasprice oracle settings for light client.
var LightClientGPO = gasprice.Config{ var LightClientGPO = gasprice.Config{
Blocks: 2, Blocks: 2,
Percentile: 60, Percentile: 60,
MaxPrice: gasprice.DefaultMaxPrice, MaxHeaderHistory: 300,
IgnorePrice: gasprice.DefaultIgnorePrice, MaxBlockHistory: 5,
MaxPrice: gasprice.DefaultMaxPrice,
IgnorePrice: gasprice.DefaultIgnorePrice,
} }
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum main net.
@ -68,7 +73,7 @@ var Defaults = Config{
FilterLogCacheSize: 32, FilterLogCacheSize: 32,
GasPrice: big.NewInt(0.25 * params.Shannon), GasPrice: big.NewInt(0.25 * params.Shannon),
TxPool: core.DefaultTxPoolConfig, TxPool: txpool.DefaultConfig,
RPCGasCap: 50000000, RPCGasCap: 50000000,
GPO: FullNodeGPO, GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether RPCTxFeeCap: 1, // 1 ether
@ -125,7 +130,7 @@ type Config struct {
Ethash ethash.Config Ethash ethash.Config
// Transaction pool options // Transaction pool options
TxPool core.TxPoolConfig TxPool txpool.Config
// Gas Price Oracle options // Gas Price Oracle options
GPO gasprice.Config GPO gasprice.Config

View file

@ -9,6 +9,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice" "github.com/XinFinOrg/XDPoSChain/eth/gasprice"
) )
@ -33,7 +34,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
GasPrice *big.Int GasPrice *big.Int
FilterLogCacheSize int FilterLogCacheSize int
Ethash ethash.Config Ethash ethash.Config
TxPool core.TxPoolConfig TxPool txpool.Config
GPO gasprice.Config GPO gasprice.Config
EnablePreimageRecording bool EnablePreimageRecording bool
DocRoot string `toml:"-"` DocRoot string `toml:"-"`
@ -87,7 +88,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
GasPrice *big.Int GasPrice *big.Int
FilterLogCacheSize *int FilterLogCacheSize *int
Ethash *ethash.Config Ethash *ethash.Config
TxPool *core.TxPoolConfig TxPool *txpool.Config
GPO *gasprice.Config GPO *gasprice.Config
EnablePreimageRecording *bool EnablePreimageRecording *bool
DocRoot *string `toml:"-"` DocRoot *string `toml:"-"`

View file

@ -164,7 +164,7 @@ func TestBlockSubscription(t *testing.T) {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
backend, sys = newTestFilterSystem(t, db, Config{}) backend, sys = newTestFilterSystem(t, db, Config{})
api = NewFilterAPI(sys, false) api = NewFilterAPI(sys, false)
genesis = new(core.Genesis).MustCommit(db) genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {}) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
chainEvents = []core.ChainEvent{} chainEvents = []core.ChainEvent{}
) )

333
eth/gasprice/feehistory.go Normal file
View file

@ -0,0 +1,333 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package gasprice
import (
"context"
"encoding/binary"
"errors"
"fmt"
"math"
"math/big"
"sort"
"sync/atomic"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
var (
errInvalidPercentile = errors.New("invalid reward percentile")
errRequestBeyondHead = errors.New("request beyond head block")
)
const (
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
// for the fee history calculation (mostly relevant for LES).
maxBlockFetchers = 4
)
// blockFees represents a single block for processing
type blockFees struct {
// set by the caller
blockNumber uint64
header *types.Header
block *types.Block // only set if reward percentiles are requested
receipts types.Receipts
// filled by processBlock
results processedFees
err error
}
// processedFees contains the results of a processed block and is also used for caching
type processedFees struct {
reward []*big.Int
baseFee, nextBaseFee *big.Int
gasUsedRatio float64
}
// txGasAndReward is sorted in ascending order based on reward
type (
txGasAndReward struct {
gasUsed uint64
reward *big.Int
}
sortGasAndReward []txGasAndReward
)
func (s sortGasAndReward) Len() int { return len(s) }
func (s sortGasAndReward) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortGasAndReward) Less(i, j int) bool {
return s[i].reward.Cmp(s[j].reward) < 0
}
// processBlock takes a blockFees structure with the blockNumber, the header and optionally
// the block field filled in, retrieves the block from the backend if not present yet and
// fills in the rest of the fields.
func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
chainconfig := oracle.backend.ChainConfig()
if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil {
bf.results.baseFee = new(big.Int)
}
if chainconfig.IsEIP1559(big.NewInt(int64(bf.blockNumber + 1))) {
bf.results.nextBaseFee = eip1559.CalcBaseFee(chainconfig, bf.header)
} else {
bf.results.nextBaseFee = new(big.Int)
}
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
if len(percentiles) == 0 {
// rewards were not requested, return null
return
}
if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) {
log.Error("Block or receipts are missing while reward percentiles are requested")
return
}
bf.results.reward = make([]*big.Int, len(percentiles))
if len(bf.block.Transactions()) == 0 {
// return an all zero row if there are no transactions to gather data from
for i := range bf.results.reward {
bf.results.reward[i] = new(big.Int)
}
return
}
sorter := make(sortGasAndReward, len(bf.block.Transactions()))
for i, tx := range bf.block.Transactions() {
reward, _ := tx.EffectiveGasTip(bf.block.BaseFee())
sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward}
}
sort.Stable(sorter)
var txIndex int
sumGasUsed := sorter[0].gasUsed
for i, p := range percentiles {
thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100)
for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 {
txIndex++
sumGasUsed += sorter[txIndex].gasUsed
}
bf.results.reward[i] = sorter[txIndex].reward
}
}
// resolveBlockRange resolves the specified block range to absolute block numbers while also
// enforcing backend specific limitations. The pending block and corresponding receipts are
// also returned if requested and available.
// Note: an error is only returned if retrieving the head header has failed. If there are no
// retrievable blocks in the specified range then zero block count is returned with no error.
func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNumber, blocks uint64) (*types.Block, []*types.Receipt, uint64, uint64, error) {
var (
headBlock *types.Header
pendingBlock *types.Block
pendingReceipts types.Receipts
err error
)
// Get the chain's current head.
if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil {
return nil, nil, 0, 0, err
}
head := rpc.BlockNumber(headBlock.Number.Uint64())
// Fail if request block is beyond the chain's current head.
if head < reqEnd {
return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, reqEnd, head)
}
// Resolve block tag.
if reqEnd < 0 {
var (
resolved *types.Header
err error
)
switch reqEnd {
case rpc.PendingBlockNumber:
if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
resolved = pendingBlock.Header()
} else {
// Pending block not supported by backend, process only until latest block.
resolved = headBlock
// Update total blocks to return to account for this.
blocks--
}
case rpc.LatestBlockNumber:
// Retrieved above.
resolved = headBlock
case rpc.CommittedBlockNumber:
resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.CommittedBlockNumber)
case rpc.EarliestBlockNumber:
resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber)
}
if resolved == nil || err != nil {
return nil, nil, 0, 0, err
}
// Absolute number resolved.
reqEnd = rpc.BlockNumber(resolved.Number.Uint64())
}
// If there are no blocks to return, short circuit.
if blocks == 0 {
return nil, nil, 0, 0, nil
}
// Ensure not trying to retrieve before genesis.
if uint64(reqEnd+1) < blocks {
blocks = uint64(reqEnd + 1)
}
return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil
}
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
// The range can be specified either with absolute block numbers or ending with the latest
// or pending block. Backends may or may not support gathering data from the pending block
// or blocks older than a certain age (specified in maxHistory). The first block of the
// actually processed range is returned to avoid ambiguity when parts of the requested range
// are not available or when the head has changed during processing this request.
// Three arrays are returned based on the processed blocks:
// - reward: the requested percentiles of effective priority fees per gas of transactions in each
// block, sorted in ascending order and weighted by gas used.
// - baseFee: base fee per gas in the given block
// - gasUsedRatio: gasUsed/gasLimit in the given block
//
// Note: baseFee includes the next block after the newest of the returned range, because this
// value can be derived from the newest block.
func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
if blocks < 1 {
return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
}
maxFeeHistory := oracle.maxHeaderHistory
if len(rewardPercentiles) != 0 {
maxFeeHistory = oracle.maxBlockHistory
}
if blocks > maxFeeHistory {
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
blocks = maxFeeHistory
}
for i, p := range rewardPercentiles {
if p < 0 || p > 100 {
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
}
if i > 0 && p <= rewardPercentiles[i-1] {
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
}
}
var (
pendingBlock *types.Block
pendingReceipts []*types.Receipt
err error
)
pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
if err != nil || blocks == 0 {
return common.Big0, nil, nil, nil, err
}
oldestBlock := lastBlock + 1 - blocks
var (
next = oldestBlock
results = make(chan *blockFees, blocks)
)
percentileKey := make([]byte, 8*len(rewardPercentiles))
for i, p := range rewardPercentiles {
binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p))
}
for i := 0; i < maxBlockFetchers && i < int(blocks); i++ {
go func() {
for {
// Retrieve the next block number to fetch with this goroutine
blockNumber := atomic.AddUint64(&next, 1) - 1
if blockNumber > lastBlock {
return
}
fees := &blockFees{blockNumber: blockNumber}
if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() {
fees.block, fees.receipts = pendingBlock, pendingReceipts
fees.header = fees.block.Header()
oracle.processBlock(fees, rewardPercentiles)
results <- fees
} else {
cacheKey := struct {
number uint64
percentiles string
}{blockNumber, string(percentileKey)}
if p, ok := oracle.historyCache.Get(cacheKey); ok {
fees.results = p.(processedFees)
results <- fees
} else {
if len(rewardPercentiles) != 0 {
fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
if fees.block != nil && fees.err == nil {
fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
fees.header = fees.block.Header()
}
} else {
fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
}
if fees.header != nil && fees.err == nil {
oracle.processBlock(fees, rewardPercentiles)
if fees.err == nil {
oracle.historyCache.Add(cacheKey, fees.results)
}
}
// send to results even if empty to guarantee that blocks items are sent in total
results <- fees
}
}
}
}()
}
var (
reward = make([][]*big.Int, blocks)
baseFee = make([]*big.Int, blocks+1)
gasUsedRatio = make([]float64, blocks)
firstMissing = blocks
)
for ; blocks > 0; blocks-- {
fees := <-results
if fees.err != nil {
return common.Big0, nil, nil, nil, fees.err
}
i := fees.blockNumber - oldestBlock
if fees.results.baseFee != nil {
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio
} else {
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
if i < firstMissing {
firstMissing = i
}
}
}
if firstMissing == 0 {
return common.Big0, nil, nil, nil, nil
}
if len(rewardPercentiles) != 0 {
reward = reward[:firstMissing]
} else {
reward = nil
}
baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
}

View file

@ -0,0 +1,90 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package gasprice
import (
"context"
"errors"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
func TestFeeHistory(t *testing.T) {
var cases = []struct {
pending bool
maxHeader, maxBlock uint64
count uint64
last rpc.BlockNumber
percent []float64
expFirst uint64
expCount int
expErr error
}{
{false, 1000, 1000, 10, 30, nil, 21, 10, nil},
{false, 1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil},
{false, 1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile},
{false, 1000, 1000, 1000000000, 30, nil, 0, 31, nil},
{false, 1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil},
{false, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{true, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil},
{false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil},
{false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil},
{false, 1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil},
{false, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
{true, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 2, nil},
{true, 1000, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil},
{false, 1000, 1000, 2, rpc.CommittedBlockNumber, []float64{0, 10}, 32, 1, nil},
}
for i, c := range cases {
config := Config{
MaxHeaderHistory: c.maxHeader,
MaxBlockHistory: c.maxBlock,
}
backend := newTestBackend(t, big.NewInt(16), c.pending)
oracle := NewOracle(backend, config)
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
expReward := c.expCount
if len(c.percent) == 0 {
expReward = 0
}
expBaseFee := c.expCount
if expBaseFee != 0 {
expBaseFee++
}
if first.Uint64() != c.expFirst {
t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first)
}
if len(reward) != expReward {
t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward))
}
if len(baseFee) != expBaseFee {
t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee))
}
if len(ratio) != c.expCount {
t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio))
}
if err != c.expErr && !errors.Is(err, c.expErr) {
t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err)
}
}
}

View file

@ -23,30 +23,40 @@ import (
"sync" "sync"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
lru "github.com/hashicorp/golang-lru"
) )
const sampleNumber = 3 // Number of transactions sampled in a block const sampleNumber = 3 // Number of transactions sampled in a block
var DefaultMaxPrice = big.NewInt(500 * params.GWei) var (
var DefaultIgnorePrice = big.NewInt(2 * params.Wei) DefaultMaxPrice = big.NewInt(500 * params.GWei)
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
)
type Config struct { type Config struct {
Blocks int Blocks int
Percentile int Percentile int
Default *big.Int `toml:",omitempty"` MaxHeaderHistory uint64
MaxPrice *big.Int `toml:",omitempty"` MaxBlockHistory uint64
IgnorePrice *big.Int `toml:",omitempty"` Default *big.Int `toml:",omitempty"`
MaxPrice *big.Int `toml:",omitempty"`
IgnorePrice *big.Int `toml:",omitempty"`
} }
// OracleBackend includes all necessary background APIs for oracle. // OracleBackend includes all necessary background APIs for oracle.
type OracleBackend interface { type OracleBackend interface {
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
PendingBlockAndReceipts() (*types.Block, types.Receipts)
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
} }
// Oracle recommends gas prices based on the content of recent // Oracle recommends gas prices based on the content of recent
@ -60,8 +70,9 @@ type Oracle struct {
cacheLock sync.RWMutex cacheLock sync.RWMutex
fetchLock sync.Mutex fetchLock sync.Mutex
checkBlocks int checkBlocks, percentile int
percentile int maxHeaderHistory, maxBlockHistory uint64
historyCache *lru.Cache
} }
// NewOracle returns a new gasprice oracle which can recommend suitable // NewOracle returns a new gasprice oracle which can recommend suitable
@ -93,47 +104,69 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
} else if ignorePrice.Int64() > 0 { } else if ignorePrice.Int64() > 0 {
log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
} }
cache, _ := lru.New(2048)
headEvent := make(chan core.ChainHeadEvent, 1)
backend.SubscribeChainHeadEvent(headEvent)
go func() {
var lastHead common.Hash
for ev := range headEvent {
if ev.Block.ParentHash() != lastHead {
cache.Purge()
}
lastHead = ev.Block.Hash()
}
}()
return &Oracle{ return &Oracle{
backend: backend, backend: backend,
lastPrice: params.Default, lastPrice: params.Default,
maxPrice: maxPrice, maxPrice: maxPrice,
ignorePrice: ignorePrice, ignorePrice: ignorePrice,
checkBlocks: blocks, checkBlocks: blocks,
percentile: percent, percentile: percent,
maxHeaderHistory: params.MaxHeaderHistory,
maxBlockHistory: params.MaxBlockHistory,
historyCache: cache,
} }
} }
// SuggestPrice returns the recommended gas price. // SuggestTipCap returns a tip cap so that newly created transaction can have a
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { // very high chance to be included in the following blocks.
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) //
// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
// necessary to add the basefee to the returned number to fall back to the legacy
// behavior.
func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
headHash := head.Hash() headHash := head.Hash()
// If the latest gasprice is still available, return it. // If the latest gasprice is still available, return it.
gpo.cacheLock.RLock() oracle.cacheLock.RLock()
lastHead, lastPrice := gpo.lastHead, gpo.lastPrice lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
gpo.cacheLock.RUnlock() oracle.cacheLock.RUnlock()
if headHash == lastHead { if headHash == lastHead {
return lastPrice, nil return new(big.Int).Set(lastPrice), nil
} }
gpo.fetchLock.Lock() oracle.fetchLock.Lock()
defer gpo.fetchLock.Unlock() defer oracle.fetchLock.Unlock()
// Try checking the cache again, maybe the last fetch fetched what we need // Try checking the cache again, maybe the last fetch fetched what we need
gpo.cacheLock.RLock() oracle.cacheLock.RLock()
lastHead, lastPrice = gpo.lastHead, gpo.lastPrice lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
gpo.cacheLock.RUnlock() oracle.cacheLock.RUnlock()
if headHash == lastHead { if headHash == lastHead {
return lastPrice, nil return new(big.Int).Set(lastPrice), nil
} }
var ( var (
sent, exp int sent, exp int
number = head.Number.Uint64() number = head.Number.Uint64()
result = make(chan getBlockPricesResult, gpo.checkBlocks) result = make(chan results, oracle.checkBlocks)
quit = make(chan struct{}) quit = make(chan struct{})
txPrices []*big.Int results []*big.Int
) )
for sent < gpo.checkBlocks && number > 0 { for sent < oracle.checkBlocks && number > 0 {
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
sent++ sent++
exp++ exp++
number-- number--
@ -142,93 +175,116 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
res := <-result res := <-result
if res.err != nil { if res.err != nil {
close(quit) close(quit)
return lastPrice, res.err return new(big.Int).Set(lastPrice), res.err
} }
exp-- exp--
// Nothing returned. There are two special cases here: // Nothing returned. There are two special cases here:
// - The block is empty // - The block is empty
// - All the transactions included are sent by the miner itself. // - All the transactions included are sent by the miner itself.
// In these cases, use the latest calculated price for samping. // In these cases, use the latest calculated price for samping.
if len(res.prices) == 0 { if len(res.values) == 0 {
res.prices = []*big.Int{lastPrice} res.values = []*big.Int{lastPrice}
} }
// Besides, in order to collect enough data for sampling, if nothing // Besides, in order to collect enough data for sampling, if nothing
// meaningful returned, try to query more blocks. But the maximum // meaningful returned, try to query more blocks. But the maximum
// is 2*checkBlocks. // is 2*checkBlocks.
if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 { if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
sent++ sent++
exp++ exp++
number-- number--
} }
txPrices = append(txPrices, res.prices...) results = append(results, res.values...)
} }
price := lastPrice price := lastPrice
if len(txPrices) > 0 { if len(results) > 0 {
sort.Sort(bigIntArray(txPrices)) sort.Sort(bigIntArray(results))
price = txPrices[(len(txPrices)-1)*gpo.percentile/100] price = results[(len(results)-1)*oracle.percentile/100]
} }
if price.Cmp(gpo.maxPrice) > 0 { if price.Cmp(oracle.maxPrice) > 0 {
price = new(big.Int).Set(gpo.maxPrice) price = new(big.Int).Set(oracle.maxPrice)
} }
// Check gas price min. // Check min gas price for non-eip1559 block
minGasPrice := common.GetMinGasPrice(head.Number) if head.BaseFee == nil {
if price.Cmp(minGasPrice) < 0 { minGasPrice := common.GetMinGasPrice(head.Number)
price = new(big.Int).Set(minGasPrice) if price.Cmp(minGasPrice) < 0 {
price = new(big.Int).Set(minGasPrice)
}
} }
gpo.cacheLock.Lock() oracle.cacheLock.Lock()
gpo.lastHead = headHash oracle.lastHead = headHash
gpo.lastPrice = price oracle.lastPrice = price
gpo.cacheLock.Unlock() oracle.cacheLock.Unlock()
return price, nil
return new(big.Int).Set(price), nil
} }
type getBlockPricesResult struct { type results struct {
prices []*big.Int values []*big.Int
err error err error
} }
type transactionsByGasPrice []*types.Transaction type txSorter struct {
txs []*types.Transaction
baseFee *big.Int
}
func (t transactionsByGasPrice) Len() int { return len(t) } func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } return &txSorter{
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 } txs: txs,
baseFee: baseFee,
}
}
func (s *txSorter) Len() int { return len(s.txs) }
func (s *txSorter) Swap(i, j int) {
s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
}
func (s *txSorter) Less(i, j int) bool {
// It's okay to discard the error because a tx would never be
// accepted into a block with an invalid effective tip.
tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
return tip1.Cmp(tip2) < 0
}
// getBlockPrices calculates the lowest transaction gas price in a given block // getBlockPrices calculates the lowest transaction gas price in a given block
// and sends it to the result channel. If the block is empty or all transactions // and sends it to the result channel. If the block is empty or all transactions
// are sent by the miner itself(it doesn't make any sense to include this kind of // are sent by the miner itself(it doesn't make any sense to include this kind of
// transaction prices for sampling), nil gasprice is returned. // transaction prices for sampling), nil gasprice is returned.
func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan getBlockPricesResult, quit chan struct{}) { func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil { if block == nil {
select { select {
case result <- getBlockPricesResult{nil, err}: case result <- results{nil, err}:
case <-quit: case <-quit:
} }
return return
} }
blockTxs := block.Transactions() // Sort the transaction by effective tip in ascending sort.
txs := make([]*types.Transaction, len(blockTxs)) txs := make([]*types.Transaction, len(block.Transactions()))
copy(txs, blockTxs) copy(txs, block.Transactions())
sort.Sort(transactionsByGasPrice(txs)) sorter := newSorter(txs, block.BaseFee())
sort.Sort(sorter)
var prices []*big.Int var prices []*big.Int
for _, tx := range txs { for _, tx := range sorter.txs {
if ignoreUnder != nil && tx.GasPrice().Cmp(ignoreUnder) == -1 { tip, _ := tx.EffectiveGasTip(block.BaseFee())
if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
continue continue
} }
sender, err := types.Sender(signer, tx) sender, err := types.Sender(signer, tx)
if err == nil && sender != block.Coinbase() { if err == nil && sender != block.Coinbase() {
prices = append(prices, tx.GasPrice()) prices = append(prices, tip)
if len(prices) >= limit { if len(prices) >= limit {
break break
} }
} }
} }
select { select {
case result <- getBlockPricesResult{prices, nil}: case result <- results{prices, nil}:
case <-quit: case <-quit:
} }
} }

View file

@ -0,0 +1,202 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package gasprice
import (
"context"
"math"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
const testHead = 32
type testBackend struct {
chain *core.BlockChain
pending bool // pending block available
}
func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
if number > testHead {
return nil, nil
}
if number == rpc.EarliestBlockNumber {
number = 0
}
if number == rpc.CommittedBlockNumber {
return b.chain.CurrentBlock().Header(), nil
}
if number == rpc.LatestBlockNumber {
number = testHead
}
if number == rpc.PendingBlockNumber {
if b.pending {
number = testHead + 1
} else {
return nil, nil
}
}
return b.chain.GetHeaderByNumber(uint64(number)), nil
}
func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
if number > testHead {
return nil, nil
}
if number == rpc.EarliestBlockNumber {
number = 0
}
if number == rpc.CommittedBlockNumber {
return b.chain.CurrentBlock(), nil
}
if number == rpc.LatestBlockNumber {
number = testHead
}
if number == rpc.PendingBlockNumber {
if b.pending {
number = testHead + 1
} else {
return nil, nil
}
}
return b.chain.GetBlockByNumber(uint64(number)), nil
}
func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return b.chain.GetReceiptsByHash(hash), nil
}
func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
if b.pending {
block := b.chain.GetBlockByNumber(testHead + 1)
return block, b.chain.GetReceiptsByHash(block.Hash())
}
return nil, nil
}
func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chain.Config()
}
func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
return nil
}
func newTestBackend(t *testing.T, eip1559Block *big.Int, pending bool) *testBackend {
config := *params.TestChainConfig // needs copy because it is modified below
config.Eip1559Block = eip1559Block
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
gspec = &core.Genesis{
Config: &config,
Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
}
signer = types.LatestSigner(gspec.Config)
)
engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase()
genesis, _ := gspec.Commit(db)
// Generate testing blocks
blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{1})
var txdata types.TxData
if eip1559Block != nil && b.Number().Cmp(eip1559Block) >= 0 {
txdata = &types.DynamicFeeTx{
ChainID: gspec.Config.ChainId,
Nonce: b.TxNonce(addr),
To: &common.Address{},
Gas: 30000,
GasFeeCap: big.NewInt(100 * params.GWei),
GasTipCap: big.NewInt(int64(i+1) * params.GWei),
Data: []byte{},
}
} else {
txdata = &types.LegacyTx{
Nonce: b.TxNonce(addr),
To: &common.Address{},
Gas: 21000,
GasPrice: big.NewInt(int64(i+1) * params.GWei),
Value: big.NewInt(100),
Data: []byte{},
}
}
b.AddTx(types.MustSignNewTx(key, signer, txdata))
})
// Construct testing chain
diskdb := rawdb.NewMemoryDatabase()
gspec.Commit(diskdb)
chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{})
if err != nil {
t.Fatalf("Failed to create local chain, %v", err)
}
chain.InsertChain(blocks)
return &testBackend{chain: chain, pending: pending}
}
func (b *testBackend) CurrentHeader() *types.Header {
return b.chain.CurrentHeader()
}
func (b *testBackend) GetBlockByNumber(number uint64) *types.Block {
return b.chain.GetBlockByNumber(number)
}
func TestSuggestTipCap(t *testing.T) {
config := Config{
Blocks: 3,
Percentile: 60,
Default: big.NewInt(params.GWei),
}
var cases = []struct {
fork *big.Int // Eip1559 fork number
expect *big.Int // Expected gasprice suggestion
}{
{nil, big.NewInt(params.GWei * int64(30))},
{big.NewInt(0), big.NewInt(params.GWei * int64(30))}, // Fork point in genesis
{big.NewInt(1), big.NewInt(params.GWei * int64(30))}, // Fork point in first block
{big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block
{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
}
for _, c := range cases {
backend := newTestBackend(t, c.fork, false)
oracle := NewOracle(backend, config)
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestTipCap(context.Background())
if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err)
}
if got.Cmp(c.expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got)
}
}
}

View file

@ -111,7 +111,7 @@ func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
} }
// Pending returns all the transactions known to the pool // Pending returns all the transactions known to the pool
func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) { func (p *testTxPool) Pending(enforceTips bool) map[common.Address]types.Transactions {
p.lock.RLock() p.lock.RLock()
defer p.lock.RUnlock() defer p.lock.RUnlock()
@ -123,7 +123,7 @@ func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
for _, batch := range batches { for _, batch := range batches {
sort.Sort(types.TxByNonce(batch)) sort.Sort(types.TxByNonce(batch))
} }
return batches, nil return batches
} }
func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {

View file

@ -108,7 +108,7 @@ type txPool interface {
// Pending should return pending transactions. // Pending should return pending transactions.
// The slice should be modifiable by the caller. // The slice should be modifiable by the caller.
Pending() (map[common.Address]types.Transactions, error) Pending(enforceTips bool) map[common.Address]types.Transactions
// SubscribeNewTxsEvent should return an event subscription of // SubscribeNewTxsEvent should return an event subscription of
// NewTxsEvent and send events to the given channel. // NewTxsEvent and send events to the given channel.

View file

@ -45,7 +45,7 @@ type txsync struct {
// syncTransactions starts sending all currently pending transactions to the given peer. // syncTransactions starts sending all currently pending transactions to the given peer.
func (pm *ProtocolManager) syncTransactions(p *peer) { func (pm *ProtocolManager) syncTransactions(p *peer) {
var txs types.Transactions var txs types.Transactions
pending, _ := pm.txpool.Pending() pending := pm.txpool.Pending(false)
for _, batch := range pending { for _, batch := range pending {
txs = append(txs, batch...) txs = append(txs, batch...)
} }

View file

@ -115,7 +115,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, nil, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, nil, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil, nil) msg, err := tx.AsMessage(signer, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
@ -201,7 +201,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.Fatalf("failed to parse testcase input: %v", err) b.Fatalf("failed to parse testcase input: %v", err)
} }
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
msg, err := tx.AsMessage(signer, nil, nil) msg, err := tx.AsMessage(signer, nil, nil, nil)
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)
} }

View file

@ -682,9 +682,10 @@ func (jst *JsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
// Compute intrinsic gas // Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isEIP1559 := env.ChainConfig().IsEIP1559(env.Context.BlockNumber)
// after update core.IntrinsicGas, use isIstanbul in it // after update core.IntrinsicGas, use isIstanbul in it
// isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) // isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead) intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isEIP1559)
if err != nil { if err != nil {
return return
} }

View file

@ -153,7 +153,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, nil, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, nil, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil, nil) msg, err := tx.AsMessage(signer, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
@ -239,7 +239,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
} }
evm := vm.NewEVM(context, txContext, statedb, nil, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, nil, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil, nil) msg, err := tx.AsMessage(signer, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
@ -336,7 +336,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
//EnableReturnData: false, //EnableReturnData: false,
}) })
evm := vm.NewEVM(context, txContext, statedb, nil, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, nil, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil, nil) msg, err := tx.AsMessage(signer, nil, nil, nil)
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)
} }

View file

@ -274,17 +274,6 @@ func (ec *Client) GetTransactionReceiptResult(ctx context.Context, txHash common
return r, result, err return r, result, err
} }
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
return hexutil.EncodeBig(number)
}
type rpcProgress struct { type rpcProgress struct {
StartingBlock hexutil.Uint64 StartingBlock hexutil.Uint64
CurrentBlock hexutil.Uint64 CurrentBlock hexutil.Uint64
@ -436,8 +425,6 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
return uint(num), err return uint(num), err
} }
// TODO: SubscribePendingTransactions (needs server side)
// Contract Calling // Contract Calling
// CallContract executes a message call transaction, which is directly executed in the VM // CallContract executes a message call transaction, which is directly executed in the VM
@ -476,6 +463,48 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
return (*big.Int)(&hex), nil return (*big.Int)(&hex), nil
} }
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to
// allow a timely execution of a transaction.
func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
var hex hexutil.Big
if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil {
return nil, err
}
return (*big.Int)(&hex), nil
}
type feeHistoryResultMarshaling struct {
OldestBlock *hexutil.Big `json:"oldestBlock"`
Reward [][]*hexutil.Big `json:"reward,omitempty"`
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
}
// FeeHistory retrieves the fee market history.
func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
var res feeHistoryResultMarshaling
if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), toBlockNumArg(lastBlock), rewardPercentiles); err != nil {
return nil, err
}
reward := make([][]*big.Int, len(res.Reward))
for i, r := range res.Reward {
reward[i] = make([]*big.Int, len(r))
for j, r := range r {
reward[i][j] = (*big.Int)(r)
}
}
baseFee := make([]*big.Int, len(res.BaseFee))
for i, b := range res.BaseFee {
baseFee[i] = (*big.Int)(b)
}
return &ethereum.FeeHistory{
OldestBlock: (*big.Int)(res.OldestBlock),
Reward: reward,
BaseFee: baseFee,
GasUsedRatio: res.GasUsedRatio,
}, nil
}
// EstimateGas tries to estimate the gas needed to execute a specific transaction based on // EstimateGas tries to estimate the gas needed to execute a specific transaction based on
// the current pending state of the backend blockchain. There is no guarantee that this is // the current pending state of the backend blockchain. There is no guarantee that this is
// the true gas limit requirement as other transactions may be added or removed by miners, // the true gas limit requirement as other transactions may be added or removed by miners,
@ -501,6 +530,17 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
} }
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
return hexutil.EncodeBig(number)
}
// SendOrderTransaction injects a signed transaction into the pending pool for execution. // SendOrderTransaction injects a signed transaction into the pending pool for execution.
// //
// If the transaction was a contract creation use the TransactionReceipt method to get the // If the transaction was a contract creation use the TransactionReceipt method to get the
@ -539,5 +579,14 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.GasPrice != nil { if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
} }
if msg.GasFeeCap != nil {
arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap)
}
if msg.GasTipCap != nil {
arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap)
}
if msg.AccessList != nil {
arg["accessList"] = msg.AccessList
}
return arg return arg
} }

View file

@ -0,0 +1,244 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package gethclient provides an RPC client for geth-specific APIs.
package gethclient
import (
"context"
"math/big"
"runtime"
"runtime/debug"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
// Client is a wrapper around rpc.Client that implements geth-specific functionality.
//
// If you want to use the standardized Ethereum RPC functionality, use ethclient.Client instead.
type Client struct {
c *rpc.Client
}
// New creates a client that uses the given RPC client.
func New(c *rpc.Client) *Client {
return &Client{c}
}
// CreateAccessList tries to create an access list for a specific transaction based on the
// current pending state of the blockchain.
func (ec *Client) CreateAccessList(ctx context.Context, msg ethereum.CallMsg) (*types.AccessList, uint64, string, error) {
type accessListResult struct {
Accesslist *types.AccessList `json:"accessList"`
Error string `json:"error,omitempty"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
}
var result accessListResult
if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil {
return nil, 0, "", err
}
return result.Accesslist, uint64(result.GasUsed), result.Error, nil
}
// AccountResult is the result of a GetProof operation.
type AccountResult struct {
Address common.Address `json:"address"`
AccountProof []string `json:"accountProof"`
Balance *big.Int `json:"balance"`
CodeHash common.Hash `json:"codeHash"`
Nonce uint64 `json:"nonce"`
StorageHash common.Hash `json:"storageHash"`
StorageProof []StorageResult `json:"storageProof"`
}
// StorageResult provides a proof for a key-value pair.
type StorageResult struct {
Key string `json:"key"`
Value *big.Int `json:"value"`
Proof []string `json:"proof"`
}
// GetProof returns the account and storage values of the specified account including the Merkle-proof.
// The block number can be nil, in which case the value is taken from the latest known block.
func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) {
type storageResult struct {
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
Proof []string `json:"proof"`
}
type accountResult struct {
Address common.Address `json:"address"`
AccountProof []string `json:"accountProof"`
Balance *hexutil.Big `json:"balance"`
CodeHash common.Hash `json:"codeHash"`
Nonce hexutil.Uint64 `json:"nonce"`
StorageHash common.Hash `json:"storageHash"`
StorageProof []storageResult `json:"storageProof"`
}
var res accountResult
err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber))
// Turn hexutils back to normal datatypes
storageResults := make([]StorageResult, 0, len(res.StorageProof))
for _, st := range res.StorageProof {
storageResults = append(storageResults, StorageResult{
Key: st.Key,
Value: st.Value.ToInt(),
Proof: st.Proof,
})
}
result := AccountResult{
Address: res.Address,
AccountProof: res.AccountProof,
Balance: res.Balance.ToInt(),
Nonce: uint64(res.Nonce),
CodeHash: res.CodeHash,
StorageHash: res.StorageHash,
}
return &result, err
}
// OverrideAccount specifies the state of an account to be overridden.
type OverrideAccount struct {
Nonce uint64 `json:"nonce"`
Code []byte `json:"code"`
Balance *big.Int `json:"balance"`
State map[common.Hash]common.Hash `json:"state"`
StateDiff map[common.Hash]common.Hash `json:"stateDiff"`
}
// CallContract executes a message call transaction, which is directly executed in the VM
// of the node, but never mined into the blockchain.
//
// blockNumber selects the block height at which the call runs. It can be nil, in which
// case the code is taken from the latest known block. Note that state from very old
// blocks might not be available.
//
// overrides specifies a map of contract states that should be overwritten before executing
// the message call.
// Please use ethclient.CallContract instead if you don't need the override functionality.
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount) ([]byte, error) {
var hex hexutil.Bytes
err := ec.c.CallContext(
ctx, &hex, "eth_call", toCallArg(msg),
toBlockNumArg(blockNumber), toOverrideMap(overrides),
)
return hex, err
}
// GCStats retrieves the current garbage collection stats from a geth node.
func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) {
var result debug.GCStats
err := ec.c.CallContext(ctx, &result, "debug_gcStats")
return &result, err
}
// MemStats retrieves the current memory stats from a geth node.
func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) {
var result runtime.MemStats
err := ec.c.CallContext(ctx, &result, "debug_memStats")
return &result, err
}
// SetHead sets the current head of the local chain by block number.
// Note, this is a destructive action and may severely damage your chain.
// Use with extreme caution.
func (ec *Client) SetHead(ctx context.Context, number *big.Int) error {
return ec.c.CallContext(ctx, nil, "debug_setHead", toBlockNumArg(number))
}
// GetNodeInfo retrieves the node info of a geth node.
func (ec *Client) GetNodeInfo(ctx context.Context) (*p2p.NodeInfo, error) {
var result p2p.NodeInfo
err := ec.c.CallContext(ctx, &result, "admin_nodeInfo")
return &result, err
}
// SubscribePendingTransactions subscribes to new pending transactions.
func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) {
return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions")
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
return hexutil.EncodeBig(number)
}
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
if msg.GasFeeCap != nil {
arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap)
}
if msg.GasTipCap != nil {
arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap)
}
if msg.AccessList != nil {
arg["accessList"] = msg.AccessList
}
return arg
}
func toOverrideMap(overrides *map[common.Address]OverrideAccount) interface{} {
if overrides == nil {
return nil
}
type overrideAccount struct {
Nonce hexutil.Uint64 `json:"nonce"`
Code hexutil.Bytes `json:"code"`
Balance *hexutil.Big `json:"balance"`
State map[common.Hash]common.Hash `json:"state"`
StateDiff map[common.Hash]common.Hash `json:"stateDiff"`
}
result := make(map[common.Address]overrideAccount)
for addr, override := range *overrides {
result[addr] = overrideAccount{
Nonce: hexutil.Uint64(override.Nonce),
Code: override.Code,
Balance: (*hexutil.Big)(override.Balance),
State: override.State,
StateDiff: override.StateDiff,
}
}
return &result
}

View file

@ -816,8 +816,11 @@ func (s *Service) reportStats(conn *connWrapper) error {
sync := s.eth.Downloader().Progress() sync := s.eth.Downloader().Progress()
syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
price, _ := s.eth.ApiBackend.SuggestPrice(context.Background()) price, _ := s.eth.ApiBackend.SuggestGasTipCap(context.Background())
gasprice = int(price.Uint64()) gasprice = int(price.Uint64())
if basefee := s.eth.ApiBackend.CurrentHeader().BaseFee; basefee != nil {
gasprice += int(basefee.Uint64())
}
} else { } else {
sync := s.les.Downloader().Progress() sync := s.les.Downloader().Progress()
syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock

1
go.mod
View file

@ -63,6 +63,7 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e // indirect github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e // indirect
github.com/maruel/ut v1.0.2 // indirect github.com/maruel/ut v1.0.2 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect

View file

@ -117,10 +117,13 @@ type CallMsg struct {
To *common.Address // the destination contract (nil for contract creation) To *common.Address // the destination contract (nil for contract creation)
Gas uint64 // if 0, the call executes with near-infinite gas Gas uint64 // if 0, the call executes with near-infinite gas
GasPrice *big.Int // wei <-> gas exchange ratio GasPrice *big.Int // wei <-> gas exchange ratio
GasFeeCap *big.Int // EIP-1559 fee cap per gas.
GasTipCap *big.Int // EIP-1559 tip per gas.
Value *big.Int // amount of wei sent along with the call Value *big.Int // amount of wei sent along with the call
Data []byte // input data, usually an ABI-encoded contract method invocation Data []byte // input data, usually an ABI-encoded contract method invocation
BalanceTokenFee *big.Int BalanceTokenFee *big.Int
AccessList types.AccessList // EIP-2930 access list.
AccessList types.AccessList // EIP-2930 access list.
} }
// A ContractCaller provides contract calls, essentially transactions that are executed by // A ContractCaller provides contract calls, essentially transactions that are executed by
@ -180,6 +183,15 @@ type GasPricer interface {
SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasPrice(ctx context.Context) (*big.Int, error)
} }
// FeeHistory provides recent fee market data that consumers can use to determine
// a reasonable maxPriorityFeePerGas value.
type FeeHistory struct {
OldestBlock *big.Int // block coresponding to first response value
Reward [][]*big.Int // list every txs priority fee per block
BaseFee []*big.Int // list of each block's base fee
GasUsedRatio []float64 // ratio of gas used out of the total available limit
}
// A PendingStateReader provides access to the pending state, which is the result of all // A PendingStateReader provides access to the pending state, which is the result of all
// known executable transactions which have not yet been included in the blockchain. It is // known executable transactions which have not yet been included in the blockchain. It is
// commonly used to display the result of unconfirmed actions (e.g. wallet value // commonly used to display the result of unconfirmed actions (e.g. wallet value

View file

@ -39,6 +39,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
@ -80,10 +81,60 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b} return &PublicEthereumAPI{b}
} }
// GasPrice returns a suggestion for a gas price. // GasPrice returns a suggestion for a gas price for legacy transactions.
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
price, err := s.b.SuggestPrice(ctx) tipcap, err := s.b.SuggestGasTipCap(ctx)
return (*hexutil.Big)(price), err if err != nil {
return nil, err
}
if head := s.b.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (*hexutil.Big)(tipcap), err
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic transactions.
func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), err
}
type feeHistoryResult struct {
OldestBlock *hexutil.Big `json:"oldestBlock"`
Reward [][]*hexutil.Big `json:"reward,omitempty"`
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
}
// FeeHistory returns the fee market history.
func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount hexutil.Uint, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
if err != nil {
return nil, err
}
results := &feeHistoryResult{
OldestBlock: (*hexutil.Big)(oldest),
GasUsedRatio: gasUsed,
}
if reward != nil {
results.Reward = make([][]*hexutil.Big, len(reward))
for i, w := range reward {
results.Reward[i] = make([]*hexutil.Big, len(w))
for j, v := range w {
results.Reward[i][j] = (*hexutil.Big)(v)
}
}
}
if baseFee != nil {
results.BaseFee = make([]*hexutil.Big, len(baseFee))
for i, v := range baseFee {
results.BaseFee[i] = (*hexutil.Big)(v)
}
}
return results, nil
} }
// ProtocolVersion returns the current Ethereum protocol version this node supports // ProtocolVersion returns the current Ethereum protocol version this node supports
@ -132,12 +183,12 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
"queued": make(map[string]map[string]*RPCTransaction), "queued": make(map[string]map[string]*RPCTransaction),
} }
pending, queue := s.b.TxPoolContent() pending, queue := s.b.TxPoolContent()
curHeader := s.b.CurrentHeader()
// Flatten the pending transactions // Flatten the pending transactions
for account, txs := range pending { for account, txs := range pending {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction)
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
} }
content["pending"][account.Hex()] = dump content["pending"][account.Hex()] = dump
} }
@ -145,13 +196,36 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
for account, txs := range queue { for account, txs := range queue {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction)
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
} }
content["queued"][account.Hex()] = dump content["queued"][account.Hex()] = dump
} }
return content return content
} }
// ContentFrom returns the transactions contained within the transaction pool.
func (s *PublicTxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
content := make(map[string]map[string]*RPCTransaction, 2)
pending, queue := s.b.TxPoolContentFrom(addr)
curHeader := s.b.CurrentHeader()
// Build the pending transactions
dump := make(map[string]*RPCTransaction, len(pending))
for _, tx := range pending {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["pending"] = dump
// Build the queued transactions
dump = make(map[string]*RPCTransaction, len(queue))
for _, tx := range queue {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["queued"] = dump
return content
}
// Status returns the number of pending and queued transaction in the pool. // Status returns the number of pending and queued transaction in the pool.
func (s *PublicTxPoolAPI) Status() map[string]hexutil.Uint { func (s *PublicTxPoolAPI) Status() map[string]hexutil.Uint {
pending, queue := s.b.Stats() pending, queue := s.b.Stats()
@ -366,7 +440,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *Transacti
return nil, err return nil, err
} }
// Set some sanity defaults and terminate on failure // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b, false); err != nil {
return nil, err return nil, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
@ -410,14 +484,15 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args Transactio
if args.Gas == nil { if args.Gas == nil {
return nil, errors.New("gas not specified") return nil, errors.New("gas not specified")
} }
if args.GasPrice == nil { if args.GasPrice == nil && (args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil) {
return nil, errors.New("gasPrice not specified") return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
} }
if args.Nonce == nil { if args.Nonce == nil {
return nil, errors.New("nonce not specified") return nil, errors.New("nonce not specified")
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // Before actually sign the transaction, ensure the transaction fee is reasonable.
if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { tx := args.toTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
signed, err := s.signTransaction(ctx, &args, passwd) signed, err := s.signTransaction(ctx, &args, passwd)
@ -1247,7 +1322,7 @@ func (s *PublicBlockChainAPI) getCandidatesFromSmartContract() ([]utils.Masterno
return candidatesWithStakeInfo, nil return candidatesWithStakeInfo, nil
} }
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) ([]byte, uint64, bool, error, error) { func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) ([]byte, uint64, bool, error, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
@ -1261,9 +1336,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
return nil, 0, false, err, nil return nil, 0, false, err, nil
} }
msg := args.ToMessage(b, header.Number, globalGasCap)
msg.SetBalanceTokenFeeForCall()
// Setup context so it may be cancelled the call has completed // Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout. // or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc var cancel context.CancelFunc
@ -1292,8 +1364,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
return nil, 0, false, err, nil return nil, 0, false, err, nil
} }
// TODO: replace header.BaseFee with blockCtx.BaseFee
// reference: https://github.com/ethereum/go-ethereum/pull/29051
msg, err := args.ToMessage(b, header.Number, globalGasCap, header.BaseFee)
if err != nil {
return nil, 0, false, err, nil
}
msg.SetBalanceTokenFeeForCall()
// Get a new instance of the EVM. // Get a new instance of the EVM.
evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vmCfg) evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true})
if err != nil { if err != nil {
return nil, 0, false, err, nil return nil, 0, false, err, nil
} }
@ -1363,7 +1443,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
if args.To != nil && *args.To == common.MasternodeVotingSMCBinary { if args.To != nil && *args.To == common.MasternodeVotingSMCBinary {
timeout = 0 timeout = 0
} }
result, _, failed, err, vmErr := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, vm.Config{}, timeout, s.b.RPCGasCap()) result, _, failed, err, vmErr := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, timeout, s.b.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1419,7 +1499,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
executable := func(gas uint64) (bool, []byte, error, error) { executable := func(gas uint64) (bool, []byte, error, error) {
args.Gas = (*hexutil.Uint64)(&gas) args.Gas = (*hexutil.Uint64)(&gas)
res, _, failed, err, vmErr := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) res, _, failed, err, vmErr := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
if err != nil { if err != nil {
if errors.Is(err, vm.ErrOutOfGas) || errors.Is(err, core.ErrIntrinsicGas) { if errors.Is(err, vm.ErrOutOfGas) || errors.Is(err, core.ErrIntrinsicGas) {
return false, nil, nil, nil // Special case, raise gas limit return false, nil, nil, nil // Special case, raise gas limit
@ -1558,14 +1638,11 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes {
return formatted return formatted
} }
// rpcOutputBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // RPCMarshalHeader converts the given header to the RPC output .
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain func RPCMarshalHeader(head *types.Header) map[string]interface{} {
// transaction hashes. result := map[string]interface{}{
func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool, ctx context.Context) (map[string]interface{}, error) {
head := b.Header() // copies the header once
fields := map[string]interface{}{
"number": (*hexutil.Big)(head.Number), "number": (*hexutil.Big)(head.Number),
"hash": b.Hash(), "hash": head.Hash(),
"parentHash": head.ParentHash, "parentHash": head.ParentHash,
"nonce": head.Nonce, "nonce": head.Nonce,
"mixHash": head.MixDigest, "mixHash": head.MixDigest,
@ -1574,9 +1651,8 @@ func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx
"stateRoot": head.Root, "stateRoot": head.Root,
"miner": head.Coinbase, "miner": head.Coinbase,
"difficulty": (*hexutil.Big)(head.Difficulty), "difficulty": (*hexutil.Big)(head.Difficulty),
"totalDifficulty": (*hexutil.Big)(s.b.GetTd(b.Hash())),
"extraData": hexutil.Bytes(head.Extra), "extraData": hexutil.Bytes(head.Extra),
"size": hexutil.Uint64(b.Size()), "size": hexutil.Uint64(head.Size()),
"gasLimit": hexutil.Uint64(head.GasLimit), "gasLimit": hexutil.Uint64(head.GasLimit),
"gasUsed": hexutil.Uint64(head.GasUsed), "gasUsed": hexutil.Uint64(head.GasUsed),
"timestamp": (*hexutil.Big)(head.Time), "timestamp": (*hexutil.Big)(head.Time),
@ -1587,6 +1663,21 @@ func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx
"penalties": hexutil.Bytes(head.Penalties), "penalties": hexutil.Bytes(head.Penalties),
} }
if head.BaseFee != nil {
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
}
return result
}
// rpcOutputBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
// transaction hashes.
func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool, ctx context.Context) (map[string]interface{}, error) {
fields := RPCMarshalHeader(b.Header())
fields["size"] = hexutil.Uint64(b.Size())
fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash()))
if inclTx { if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) { formatTx := func(tx *types.Transaction) (interface{}, error) {
return tx.Hash(), nil return tx.Hash(), nil
@ -1773,6 +1864,8 @@ type RPCTransaction struct {
From common.Address `json:"from"` From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"` Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"` Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"` Nonce hexutil.Uint64 `json:"nonce"`
@ -1789,7 +1882,7 @@ type RPCTransaction struct {
// newRPCTransaction returns a transaction that will serialize to the RPC // newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction {
// Determine the signer. For replay-protected transactions, use the most permissive // Determine the signer. For replay-protected transactions, use the most permissive
// signer, because we assume that signers are backwards-compatible with old // signer, because we assume that signers are backwards-compatible with old
// transactions. For non-protected transactions, the homestead signer signer is used // transactions. For non-protected transactions, the homestead signer signer is used
@ -1800,7 +1893,6 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
} else { } else {
signer = types.HomesteadSigner{} signer = types.HomesteadSigner{}
} }
from, _ := types.Sender(signer, tx) from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues() v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{ result := &RPCTransaction{
@ -1822,17 +1914,36 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index) result.TransactionIndex = (*hexutil.Uint64)(&index)
} }
if tx.Type() == types.AccessListTxType { switch tx.Type() {
case types.AccessListTxType:
al := tx.AccessList() al := tx.AccessList()
result.Accesses = &al result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId()) result.ChainID = (*hexutil.Big)(tx.ChainId())
case types.DynamicFeeTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
// if the transaction has been mined, compute the effective gas price
if baseFee != nil && blockHash != (common.Hash{}) {
// price = min(tip, gasFeeCap - baseFee) + baseFee
price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
result.GasPrice = (*hexutil.Big)(price)
} else {
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
}
} }
return result return result
} }
// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation // newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction {
return newRPCTransaction(tx, common.Hash{}, 0, 0) var baseFee *big.Int
if current != nil {
baseFee = eip1559.CalcBaseFee(config, current)
}
return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee)
} }
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. // newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
@ -1841,7 +1952,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
if index >= uint64(len(txs)) { if index >= uint64(len(txs)) {
return nil return nil
} }
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index, b.BaseFee())
} }
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. // newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
@ -1917,12 +2028,8 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
} }
owner := common.Address{} owner := common.Address{}
// If the gas amount is not set, extract this as it will depend on access
// lists and we'll need to reestimate every time
nogas := args.Gas == nil
// Ensure any missing fields are filled, extract the recipient and input data // Ensure any missing fields are filled, extract the recipient and input data
if err := args.setDefaults(ctx, b); err != nil { if err := args.setDefaults(ctx, b, true); err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
var to common.Address var to common.Address
@ -1944,27 +2051,25 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
accessList := prevTracer.AccessList() accessList := prevTracer.AccessList()
log.Trace("Creating access list", "input", accessList) log.Trace("Creating access list", "input", accessList)
// If no gas amount was specified, each unique access list needs it's own
// gas calculation. This is quite expensive, but we need to be accurate
// and it's convered by the sender only anyway.
if nogas {
args.Gas = nil
if err := args.setDefaults(ctx, b); err != nil {
return nil, 0, nil, err // shouldn't happen, just in case
}
}
// Copy the original db so we don't modify it // Copy the original db so we don't modify it
statedb := db.Copy() statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
msg, err := args.ToMessage(b, block.Number(), b.RPCGasCap(), header.BaseFee)
if err != nil {
return nil, 0, nil, err
}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var balanceTokenFee *big.Int var balanceTokenFee *big.Int
if value, ok := feeCapacity[to]; ok { if value, ok := feeCapacity[to]; ok {
balanceTokenFee = value balanceTokenFee = value
} }
msg := types.NewMessage(args.from(), args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), args.data(), accessList, false, balanceTokenFee, header.Number) msg.SetBalanceTokenFee(balanceTokenFee)
// Apply the transaction with the access list tracer // Apply the transaction with the access list tracer
tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles) tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, Debug: true} config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config) vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config)
if err != nil { if err != nil {
return nil, 0, nil, err return nil, 0, nil, err
@ -2077,17 +2182,23 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr
} }
// GetTransactionByHash returns the transaction for the given hash // GetTransactionByHash returns the transaction for the given hash
func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction // Try to return an already finalized transaction
if tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash); tx != nil { tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash)
return newRPCTransaction(tx, blockHash, blockNumber, index) if tx != nil {
header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return newRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee), nil
} }
// No finalized transaction, try to retrieve it from the pool // No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil { if tx := s.b.GetPoolTransaction(hash); tx != nil {
return newRPCPendingTransaction(tx) return newRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
} }
// Transaction unknown, return as such // Transaction unknown, return as such
return nil return nil, nil
} }
// GetRawTransactionByHash returns the bytes of the transaction for the given hash. // GetRawTransactionByHash returns the bytes of the transaction for the given hash.
@ -2108,13 +2219,15 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context,
func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash) tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash)
if tx == nil { if tx == nil {
// When the transaction doesn't exist, the RPC method should return JSON null
// as per specification.
return nil, nil return nil, nil
} }
receipts, err := s.b.GetReceipts(ctx, blockHash) receipts, err := s.b.GetReceipts(ctx, blockHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(receipts) <= int(index) { if uint64(len(receipts)) <= index {
return nil, nil return nil, nil
} }
receipt := receipts[index] receipt := receipts[index]
@ -2122,37 +2235,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
// Derive the sender. // Derive the sender.
bigblock := new(big.Int).SetUint64(blockNumber) bigblock := new(big.Int).SetUint64(blockNumber)
signer := types.MakeSigner(s.b.ChainConfig(), bigblock) signer := types.MakeSigner(s.b.ChainConfig(), bigblock)
from, _ := types.Sender(signer, tx) return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
fields := map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(blockNumber),
"transactionHash": hash,
"transactionIndex": hexutil.Uint64(index),
"from": from,
"to": tx.To(),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()),
}
// Assign receipt status or post state.
if len(receipt.PostState) > 0 {
fields["root"] = hexutil.Bytes(receipt.PostState)
} else {
fields["status"] = hexutil.Uint(receipt.Status)
}
if receipt.Logs == nil {
fields["logs"] = [][]*types.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}
return fields, nil
} }
// marshalReceipt marshals a transaction receipt into a JSON object. // marshalReceipt marshals a transaction receipt into a JSON object.
@ -2172,8 +2255,7 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u
"logs": receipt.Logs, "logs": receipt.Logs,
"logsBloom": receipt.Bloom, "logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()), "type": hexutil.Uint(tx.Type()),
// uncomment below line after EIP-1559 "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
// TODO: "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
} }
// Assign receipt status or post state. // Assign receipt status or post state.
@ -2279,7 +2361,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Tra
} }
// Set some sanity defaults and terminate on failure // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
@ -2296,11 +2378,12 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Tra
return SubmitTransaction(ctx, s.b, signed) return SubmitTransaction(ctx, s.b, signed)
} }
// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction, // FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
// and returns it to the caller for further processing (signing + broadcast) // on a given unsigned transaction, and returns it to the caller for further
// processing (signing + broadcast).
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b, false); err != nil {
return nil, err return nil, err
} }
// Assemble the transaction and obtain rlp // Assemble the transaction and obtain rlp
@ -3199,24 +3282,25 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Tra
if args.Gas == nil { if args.Gas == nil {
return nil, errors.New("not specify Gas") return nil, errors.New("not specify Gas")
} }
if args.GasPrice == nil { if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) {
return nil, errors.New("not specify GasPrice") return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
} }
if args.Nonce == nil { if args.Nonce == nil {
return nil, errors.New("not specify Nonce") return nil, errors.New("not specify Nonce")
} }
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b, false); err != nil {
return nil, err return nil, err
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // Before actually sign the transaction, ensure the transaction fee is reasonable.
if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { tx := args.toTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
tx, err := s.sign(args.from(), args.toTransaction()) signed, err := s.sign(args.from(), tx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := tx.MarshalBinary() data, err := signed.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3236,11 +3320,12 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
accounts[account.Address] = struct{}{} accounts[account.Address] = struct{}{}
} }
} }
curHeader := s.b.CurrentHeader()
transactions := make([]*RPCTransaction, 0, len(pending)) transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending { for _, tx := range pending {
from, _ := types.Sender(s.signer, tx) from, _ := types.Sender(s.signer, tx)
if _, exists := accounts[from]; exists { if _, exists := accounts[from]; exists {
transactions = append(transactions, newRPCPendingTransaction(tx)) transactions = append(transactions, newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
} }
} }
return transactions, nil return transactions, nil
@ -3252,7 +3337,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs Transact
if sendArgs.Nonce == nil { if sendArgs.Nonce == nil {
return common.Hash{}, errors.New("missing transaction nonce in transaction spec") return common.Hash{}, errors.New("missing transaction nonce in transaction spec")
} }
if err := sendArgs.setDefaults(ctx, s.b); err != nil { if err := sendArgs.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
matchTx := sendArgs.toTransaction() matchTx := sendArgs.toTransaction()

View file

@ -48,7 +48,8 @@ type Backend interface {
// General Ethereum API // General Ethereum API
Downloader() *downloader.Downloader Downloader() *downloader.Downloader
ProtocolVersion() int ProtocolVersion() int
SuggestPrice(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error)
ChainDb() ethdb.Database ChainDb() ethdb.Database
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
@ -61,6 +62,7 @@ type Backend interface {
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
CurrentHeader() *types.Header
BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
@ -82,6 +84,7 @@ type Backend interface {
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int) Stats() (pending int, queued int)
TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
// Order Pool Transaction // Order Pool Transaction

Some files were not shown because too many files have changed in this diff Show more