Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ func (m callmsg) GatewayFee() *big.Int { return m.CallMsg.Gatewa
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data }
func (m callmsg) EthCompatible() bool { return m.CallMsg.EthCompatible }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
24 changes: 13 additions & 11 deletions cmd/geth/retesteth_copypaste.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type RPCTransaction struct {
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
EthCompatible bool `json:"ethCompatible"`
}

// newRPCTransaction returns a transaction that will serialize to the RPC
Expand All @@ -53,17 +54,18 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
v, r, s := tx.RawSignatureValues()

result := &RPCTransaction{
From: from,
Gas: hexutil.Uint64(tx.Gas()),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
Hash: tx.Hash(),
Input: hexutil.Bytes(tx.Data()),
Nonce: hexutil.Uint64(tx.Nonce()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
From: from,
Gas: hexutil.Uint64(tx.Gas()),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
Hash: tx.Hash(),
Input: hexutil.Bytes(tx.Data()),
Nonce: hexutil.Uint64(tx.Nonce()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
EthCompatible: tx.EthCompatible(),
}
if blockHash != (common.Hash{}) {
result.BlockHash = blockHash
Expand Down
2 changes: 1 addition & 1 deletion contract_comm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
)

var (
emptyMessage = types.NewMessage(common.HexToAddress("0x0"), nil, 0, common.Big0, 0, common.Big0, nil, nil, common.Big0, []byte{}, false)
emptyMessage = types.NewMessage(common.HexToAddress("0x0"), nil, 0, common.Big0, 0, common.Big0, nil, nil, common.Big0, []byte{}, false, false)
internalEvmHandlerSingleton *InternalEVMHandler
)

Expand Down
8 changes: 8 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,12 @@ var (
// 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")

// ErrEthCompatibleTransactionsNotSupported is returned if the transaction omits the 3 Celo-only
// fields (FeeCurrency & co.) but support for this kind of transaction is not enabled.
ErrEthCompatibleTransactionsNotSupported = errors.New("support for eth-compatible transactions is not enabled")

// ErrUnprotectedTransaction is returned if replay protection is required (post-Donut) but the transaction doesn't
// use it.
ErrUnprotectedTransaction = errors.New("replay protection is required")
)
3 changes: 3 additions & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc vm.ChainContext, txFeeRecipient *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
if config.IsDonut(header.Number) && !tx.Protected() {
return nil, ErrUnprotectedTransaction
}
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, err
Expand Down
10 changes: 10 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
// 0. If the message is from an eth-compatible transaction, that we support those
// and that none of the non-eth-compatible fields are present
// 1. the nonce of the message caller is correct
// 2. the gas price meets the minimum gas price
// 3. caller has enough balance (in the right currency) to cover transaction fee
Expand All @@ -357,6 +359,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// 6. there is no overflow when calculating intrinsic gas
// 7. caller has enough balance to cover asset transfer for **topmost** call

// Clause 0
if st.msg.EthCompatible() && !st.evm.ChainConfig().IsDonut(st.evm.BlockNumber) {
return nil, ErrEthCompatibleTransactionsNotSupported
}
if err := vm.CheckEthCompatibility(st.msg); err != nil {
return nil, err
}

// Check clauses 1-2
if err := st.preCheck(); err != nil {
return nil, err
Expand Down
29 changes: 29 additions & 0 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ type TxPool struct {
mu sync.RWMutex

istanbul bool // Fork indicator whether we are in the istanbul stage.
donut bool // Fork indicator for the Donut fork.

currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces
Expand Down Expand Up @@ -487,6 +488,20 @@ func (pool *TxPool) SetGasLimit(gasLimit uint64) {
}
}

// handleDonutActivation removes from the pool all transactions without EIP-155 replay protection
func (pool *TxPool) handleDonutActivation() {
toRemove := make(map[common.Hash]struct{})
pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
if !tx.Protected() {
toRemove[hash] = struct{}{}
}
return true
})
for hash := range toRemove {
pool.removeTx(hash, true)
}
}

// Nonce returns the next nonce of an account, with all transactions executable
// by the pool already applied on top.
func (pool *TxPool) Nonce(addr common.Address) uint64 {
Expand Down Expand Up @@ -577,6 +592,15 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.donut && !tx.Protected() {
return ErrUnprotectedTransaction
}
if tx.EthCompatible() && !pool.donut {
return ErrEthCompatibleTransactionsNotSupported
}
if err := tx.CheckEthCompatibility(); err != nil {
return err
}
// Reject transactions over defined size to prevent DOS attacks
if uint64(tx.Size()) > txMaxSize {
return ErrOversizedData
Expand Down Expand Up @@ -1272,6 +1296,11 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// Update all fork indicator by next pending block number.
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
pool.istanbul = pool.chainconfig.IsIstanbul(next)
wasDonut := pool.donut
pool.donut = pool.chainconfig.IsDonut(next)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we call validateTx after this is enabled? Or old tx will continue to existt on the txpool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! validateTx() is only called when adding a transaction. For later on it only checks a subset of what validateTx() checks, and this should be checked as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a commit making it so that unprotected transactions are removed when Donut is activated. I still need to add a test for it, though.

if pool.donut && !wasDonut {
pool.handleDonutActivation()
}
}

// promoteExecutables moves transactions that have become processable from the
Expand Down
63 changes: 60 additions & 3 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ import (
"github.com/celo-org/celo-blockchain/params"
)

// testTxPoolConfig is a transaction pool configuration without stateful disk
// sideeffects used during testing.
var testTxPoolConfig TxPoolConfig
var (
// testTxPoolConfig is a transaction pool configuration without stateful disk
// sideeffects used during testing.
testTxPoolConfig TxPoolConfig
// eip155Signer to use for generating replay-protected transactions
eip155Signer = types.NewEIP155Signer(params.TestChainConfig.ChainID)
)

func init() {
testTxPoolConfig = DefaultTxPoolConfig
Expand Down Expand Up @@ -103,6 +107,11 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key
return tx
}

func protectedTransaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Transaction {
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, big.NewInt(1), nil, nil, nil, nil), eip155Signer, key)
return tx
}

func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
Expand Down Expand Up @@ -759,6 +768,54 @@ func TestTransactionGapFilling(t *testing.T) {
}
}

// Tests that pool.handleDonutActivation() removes transactions without replay protection
// When we change TestChangeConfig to enable Donut this test will need:
// (a) to set pool.donut = false at its start (so we can add unprotected transactions)
// (b) different functions to generate protected vs unprotected transactions, since we will
// need to update transaction() and the others to use replay protection
func TestHandleDonutActivation(t *testing.T) {
t.Parallel()

// Create a test account and fund it
pool, key := setupTxPool()
defer pool.Stop()

account := crypto.PubkeyToAddress(key.PublicKey)
pool.currentState.AddBalance(account, big.NewInt(1000000))

pool.AddRemotesSync([]*types.Transaction{
protectedTransaction(0, 100000, key),
transaction(1, 100000, key),
protectedTransaction(2, 100000, key),
transaction(7, 100000, key),
protectedTransaction(8, 100000, key),
transaction(9, 100000, key),
transaction(10, 100000, key),
})

pending, queued := pool.Stats()
if pending != 3 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
}
if queued != 4 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 4)
}

pool.handleDonutActivation()

pending, queued = pool.Stats()
if pending != 1 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
}
if queued != 2 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
}

if err := validateTxPoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}
}

// Tests that if the transaction count belonging to a single account goes above
// some threshold, the higher transactions are dropped to prevent DOS attacks.
func TestTransactionQueueAccountLimiting(t *testing.T) {
Expand Down
10 changes: 8 additions & 2 deletions core/types/gen_tx_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading