Merge pull request #19810 from karalabe/txpool-noncer

core: kill off managed state, use own tiny noncer for txpool
pull/19814/head
Péter Szilágyi 5 years ago committed by GitHub
commit 8c249cb82f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 143
      core/state/managed_state.go
  2. 123
      core/state/managed_state_test.go
  3. 53
      core/tx_noncer.go
  4. 35
      core/tx_pool.go
  5. 10
      core/tx_pool_test.go
  6. 2
      eth/api_backend.go

@ -1,143 +0,0 @@
// Copyright 2015 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 state
import (
"sync"
"github.com/ethereum/go-ethereum/common"
)
type account struct {
stateObject *stateObject
nstart uint64
nonces []bool
}
type ManagedState struct {
*StateDB
mu sync.RWMutex
accounts map[common.Address]*account
}
// ManagedState returns a new managed state with the statedb as it's backing layer
func ManageState(statedb *StateDB) *ManagedState {
return &ManagedState{
StateDB: statedb.Copy(),
accounts: make(map[common.Address]*account),
}
}
// SetState sets the backing layer of the managed state
func (ms *ManagedState) SetState(statedb *StateDB) {
ms.mu.Lock()
defer ms.mu.Unlock()
ms.StateDB = statedb
}
// RemoveNonce removed the nonce from the managed state and all future pending nonces
func (ms *ManagedState) RemoveNonce(addr common.Address, n uint64) {
if ms.hasAccount(addr) {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
if n-account.nstart <= uint64(len(account.nonces)) {
reslice := make([]bool, n-account.nstart)
copy(reslice, account.nonces[:n-account.nstart])
account.nonces = reslice
}
}
}
// NewNonce returns the new canonical nonce for the managed account
func (ms *ManagedState) NewNonce(addr common.Address) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
for i, nonce := range account.nonces {
if !nonce {
return account.nstart + uint64(i)
}
}
account.nonces = append(account.nonces, true)
return uint64(len(account.nonces)-1) + account.nstart
}
// GetNonce returns the canonical nonce for the managed or unmanaged account.
//
// Because GetNonce mutates the DB, we must take a write lock.
func (ms *ManagedState) GetNonce(addr common.Address) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.hasAccount(addr) {
account := ms.getAccount(addr)
return uint64(len(account.nonces)) + account.nstart
} else {
return ms.StateDB.GetNonce(addr)
}
}
// SetNonce sets the new canonical nonce for the managed state
func (ms *ManagedState) SetNonce(addr common.Address, nonce uint64) {
ms.mu.Lock()
defer ms.mu.Unlock()
so := ms.GetOrNewStateObject(addr)
so.SetNonce(nonce)
ms.accounts[addr] = newAccount(so)
}
// HasAccount returns whether the given address is managed or not
func (ms *ManagedState) HasAccount(addr common.Address) bool {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.hasAccount(addr)
}
func (ms *ManagedState) hasAccount(addr common.Address) bool {
_, ok := ms.accounts[addr]
return ok
}
// populate the managed state
func (ms *ManagedState) getAccount(addr common.Address) *account {
if account, ok := ms.accounts[addr]; !ok {
so := ms.GetOrNewStateObject(addr)
ms.accounts[addr] = newAccount(so)
} else {
// Always make sure the state account nonce isn't actually higher
// than the tracked one.
so := ms.StateDB.getStateObject(addr)
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
ms.accounts[addr] = newAccount(so)
}
}
return ms.accounts[addr]
}
func newAccount(so *stateObject) *account {
return &account{so, so.Nonce(), nil}
}

@ -1,123 +0,0 @@
// Copyright 2015 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 state
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
)
var addr = common.BytesToAddress([]byte("test"))
func create() (*ManagedState, *account) {
statedb, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
ms := ManageState(statedb)
ms.StateDB.SetNonce(addr, 100)
ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr))
return ms, ms.accounts[addr]
}
func TestNewNonce(t *testing.T) {
ms, _ := create()
nonce := ms.NewNonce(addr)
if nonce != 100 {
t.Error("expected nonce 100. got", nonce)
}
nonce = ms.NewNonce(addr)
if nonce != 101 {
t.Error("expected nonce 101. got", nonce)
}
}
func TestRemove(t *testing.T) {
ms, account := create()
nn := make([]bool, 10)
for i := range nn {
nn[i] = true
}
account.nonces = append(account.nonces, nn...)
i := uint64(5)
ms.RemoveNonce(addr, account.nstart+i)
if len(account.nonces) != 5 {
t.Error("expected", i, "'th index to be false")
}
}
func TestReuse(t *testing.T) {
ms, account := create()
nn := make([]bool, 10)
for i := range nn {
nn[i] = true
}
account.nonces = append(account.nonces, nn...)
i := uint64(5)
ms.RemoveNonce(addr, account.nstart+i)
nonce := ms.NewNonce(addr)
if nonce != 105 {
t.Error("expected nonce to be 105. got", nonce)
}
}
func TestRemoteNonceChange(t *testing.T) {
ms, account := create()
nn := make([]bool, 10)
for i := range nn {
nn[i] = true
}
account.nonces = append(account.nonces, nn...)
ms.NewNonce(addr)
ms.StateDB.stateObjects[addr].data.Nonce = 200
nonce := ms.NewNonce(addr)
if nonce != 200 {
t.Error("expected nonce after remote update to be", 200, "got", nonce)
}
ms.NewNonce(addr)
ms.NewNonce(addr)
ms.NewNonce(addr)
ms.StateDB.stateObjects[addr].data.Nonce = 200
nonce = ms.NewNonce(addr)
if nonce != 204 {
t.Error("expected nonce after remote update to be", 204, "got", nonce)
}
}
func TestSetNonce(t *testing.T) {
ms, _ := create()
var addr common.Address
ms.SetNonce(addr, 10)
if ms.GetNonce(addr) != 10 {
t.Error("Expected nonce of 10, got", ms.GetNonce(addr))
}
addr[0] = 1
ms.StateDB.SetNonce(addr, 1)
if ms.GetNonce(addr) != 1 {
t.Error("Expected nonce of 1, got", ms.GetNonce(addr))
}
}

@ -0,0 +1,53 @@
// 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 core
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
)
// txNoncer 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
// an account is unknown.
type txNoncer struct {
fallback *state.StateDB
nonces map[common.Address]uint64
}
// newTxNoncer creates a new virtual state database to track the pool nonces.
func newTxNoncer(statedb *state.StateDB) *txNoncer {
return &txNoncer{
fallback: statedb.Copy(),
nonces: make(map[common.Address]uint64),
}
}
// get returns the current nonce of an account, falling back to a real state
// database if the account is unknown.
func (txn *txNoncer) get(addr common.Address) uint64 {
if _, ok := txn.nonces[addr]; !ok {
txn.nonces[addr] = txn.fallback.GetNonce(addr)
}
return txn.nonces[addr]
}
// 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.
func (txn *txNoncer) set(addr common.Address, nonce uint64) {
txn.nonces[addr] = nonce
}

@ -217,9 +217,9 @@ type TxPool struct {
signer types.Signer
mu sync.RWMutex
currentState *state.StateDB // Current state in the blockchain head
pendingState *state.ManagedState // Pending state tracking virtual nonces
currentMaxGas uint64 // Current gas limit for transaction caps
currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces
currentMaxGas uint64 // Current gas limit for transaction caps
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
@ -417,12 +417,13 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
log.Info("Transaction pool price threshold updated", "price", price)
}
// State returns the virtual managed state of the transaction pool.
func (pool *TxPool) State() *state.ManagedState {
// 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 {
pool.mu.RLock()
defer pool.mu.RUnlock()
return pool.pendingState
return pool.pendingNonces.get(addr)
}
// Stats retrieves the current pool stats, namely the number of pending and the
@ -713,7 +714,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
}
// Set the potentially new pending nonce and notify any subsystems of the new tx
pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
pool.pendingNonces.set(addr, tx.Nonce()+1)
return true
}
@ -853,8 +854,8 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
pool.enqueueTx(tx.Hash(), tx)
}
// Update the account nonce if needed
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
if nonce := tx.Nonce(); pool.pendingNonces.get(addr) > nonce {
pool.pendingNonces.set(addr, nonce)
}
// Reduce the pending counter
pendingCounter.Dec(int64(1 + len(invalids)))
@ -990,7 +991,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
// Nonces were reset, discard any events that became stale
for addr := range events {
events[addr].Forward(pool.pendingState.GetNonce(addr))
events[addr].Forward(pool.pendingNonces.get(addr))
if events[addr].Len() == 0 {
delete(events, addr)
}
@ -1023,7 +1024,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
// Update all accounts to the latest known pending nonce
for addr, list := range pool.pending {
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
pool.pendingState.SetNonce(addr, txs[len(txs)-1].Nonce()+1)
pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1)
}
pool.mu.Unlock()
@ -1112,7 +1113,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
return
}
pool.currentState = statedb
pool.pendingState = state.ManageState(statedb)
pool.pendingNonces = newTxNoncer(statedb)
pool.currentMaxGas = newHead.GasLimit
// Inject any transactions discarded due to reorgs
@ -1151,7 +1152,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
queuedNofundsMeter.Mark(int64(len(drops)))
// Gather all executable transactions and promote them
readies := list.Ready(pool.pendingState.GetNonce(addr))
readies := list.Ready(pool.pendingNonces.get(addr))
for _, tx := range readies {
hash := tx.Hash()
if pool.promoteTx(addr, hash, tx) {
@ -1231,8 +1232,8 @@ func (pool *TxPool) truncatePending() {
pool.all.Remove(hash)
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
pool.pendingState.SetNonce(offenders[i], nonce)
if nonce := tx.Nonce(); pool.pendingNonces.get(offenders[i]) > nonce {
pool.pendingNonces.set(offenders[i], nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
@ -1260,8 +1261,8 @@ func (pool *TxPool) truncatePending() {
pool.all.Remove(hash)
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
if nonce := tx.Nonce(); pool.pendingNonces.get(addr) > nonce {
pool.pendingNonces.set(addr, nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}

@ -109,7 +109,7 @@ func validateTxPoolInternals(pool *TxPool) error {
last = nonce
}
}
if nonce := pool.pendingState.GetNonce(addr); nonce != last+1 {
if nonce := pool.Nonce(addr); nonce != last+1 {
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
}
}
@ -195,14 +195,14 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
defer pool.Stop()
nonce := pool.State().GetNonce(address)
nonce := pool.Nonce(address)
if nonce != 0 {
t.Fatalf("Invalid nonce, want 0, got %d", nonce)
}
pool.addRemotesSync([]*types.Transaction{tx0, tx1})
nonce = pool.State().GetNonce(address)
nonce = pool.Nonce(address)
if nonce != 2 {
t.Fatalf("Invalid nonce, want 2, got %d", nonce)
}
@ -215,7 +215,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
if err != nil {
t.Fatalf("Could not fetch pending transactions: %v", err)
}
nonce = pool.State().GetNonce(address)
nonce = pool.Nonce(address)
if nonce != 2 {
t.Fatalf("Invalid nonce, want 2, got %d", nonce)
}
@ -451,7 +451,7 @@ func TestTransactionNonceRecovery(t *testing.T) {
// simulate some weird re-order of transactions and missing nonce(s)
pool.currentState.SetNonce(addr, n-1)
<-pool.requestReset(nil, nil)
if fn := pool.pendingState.GetNonce(addr); fn != n-1 {
if fn := pool.Nonce(addr); fn != n-1 {
t.Errorf("expected nonce to be %d, got %d", n-1, fn)
}
}

@ -185,7 +185,7 @@ func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash)
}
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return b.eth.txPool.State().GetNonce(addr), nil
return b.eth.txPool.Nonce(addr), nil
}
func (b *EthAPIBackend) Stats() (pending int, queued int) {

Loading…
Cancel
Save