Merge branch 'master' of https://github.com/ethereum/go-ethereum into ethGolint

This commit is contained in:
William Schwartz 2018-05-23 09:28:43 -07:00
commit 8671b353b6
8 changed files with 293 additions and 126 deletions

View file

@ -397,13 +397,13 @@ func (h *priceHeap) Pop() interface{} {
// txPricedList is a price-sorted heap to allow operating on transactions pool // txPricedList is a price-sorted heap to allow operating on transactions pool
// contents in a price-incrementing way. // contents in a price-incrementing way.
type txPricedList struct { type txPricedList struct {
all *map[common.Hash]*types.Transaction // Pointer to the map of all transactions all *txLookup // Pointer to the map of all transactions
items *priceHeap // Heap of prices of all the stored transactions items *priceHeap // Heap of prices of all the stored transactions
stales int // Number of stale price points to (re-heap trigger) stales int // Number of stale price points to (re-heap trigger)
} }
// newTxPricedList creates a new price-sorted transaction heap. // newTxPricedList creates a new price-sorted transaction heap.
func newTxPricedList(all *map[common.Hash]*types.Transaction) *txPricedList { func newTxPricedList(all *txLookup) *txPricedList {
return &txPricedList{ return &txPricedList{
all: all, all: all,
items: new(priceHeap), items: new(priceHeap),
@ -425,12 +425,13 @@ func (l *txPricedList) Removed() {
return return
} }
// Seems we've reached a critical number of stale transactions, reheap // Seems we've reached a critical number of stale transactions, reheap
reheap := make(priceHeap, 0, len(*l.all)) reheap := make(priceHeap, 0, l.all.Count())
l.stales, l.items = 0, &reheap l.stales, l.items = 0, &reheap
for _, tx := range *l.all { l.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
*l.items = append(*l.items, tx) *l.items = append(*l.items, tx)
} return true
})
heap.Init(l.items) heap.Init(l.items)
} }
@ -443,7 +444,7 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact
for len(*l.items) > 0 { for len(*l.items) > 0 {
// Discard stale transactions if found during cleanup // Discard stale transactions if found during cleanup
tx := heap.Pop(l.items).(*types.Transaction) tx := heap.Pop(l.items).(*types.Transaction)
if _, ok := (*l.all)[tx.Hash()]; !ok { if l.all.Get(tx.Hash()) == nil {
l.stales-- l.stales--
continue continue
} }
@ -475,7 +476,7 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
// Discard stale price points if found at the heap start // Discard stale price points if found at the heap start
for len(*l.items) > 0 { for len(*l.items) > 0 {
head := []*types.Transaction(*l.items)[0] head := []*types.Transaction(*l.items)[0]
if _, ok := (*l.all)[head.Hash()]; !ok { if l.all.Get(head.Hash()) == nil {
l.stales-- l.stales--
heap.Pop(l.items) heap.Pop(l.items)
continue continue
@ -500,7 +501,7 @@ func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions
for len(*l.items) > 0 && count > 0 { for len(*l.items) > 0 && count > 0 {
// Discard stale transactions if found during cleanup // Discard stale transactions if found during cleanup
tx := heap.Pop(l.items).(*types.Transaction) tx := heap.Pop(l.items).(*types.Transaction)
if _, ok := (*l.all)[tx.Hash()]; !ok { if l.all.Get(tx.Hash()) == nil {
l.stales-- l.stales--
continue continue
} }

View file

@ -200,11 +200,11 @@ type TxPool struct {
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 *txJournal // Journal of local transaction to back up to disk
pending map[common.Address]*txList // All currently processable transactions pending map[common.Address]*txList // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions queue map[common.Address]*txList // 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 map[common.Hash]*types.Transaction // All transactions to allow lookups all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price priced *txPricedList // All transactions sorted by price
wg sync.WaitGroup // for shutdown sync wg sync.WaitGroup // for shutdown sync
@ -226,12 +226,12 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
pending: make(map[common.Address]*txList), pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList), queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
all: make(map[common.Hash]*types.Transaction), all: newTxLookup(),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
gasPrice: new(big.Int).SetUint64(config.PriceLimit), gasPrice: new(big.Int).SetUint64(config.PriceLimit),
} }
pool.locals = newAccountSet(pool.signer) pool.locals = newAccountSet(pool.signer)
pool.priced = newTxPricedList(&pool.all) pool.priced = newTxPricedList(pool.all)
pool.reset(nil, chain.CurrentBlock().Header()) pool.reset(nil, chain.CurrentBlock().Header())
// If local transactions and journaling is enabled, load from disk // If local transactions and journaling is enabled, load from disk
@ -605,7 +605,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
// If the transaction is already known, discard it // If the transaction is already known, discard it
hash := tx.Hash() hash := tx.Hash()
if pool.all[hash] != nil { if pool.all.Get(hash) != nil {
log.Trace("Discarding already known transaction", "hash", hash) log.Trace("Discarding already known transaction", "hash", hash)
return false, fmt.Errorf("known transaction: %x", hash) return false, fmt.Errorf("known transaction: %x", hash)
} }
@ -616,7 +616,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
return false, err return false, err
} }
// If the transaction pool is full, discard underpriced transactions // If the transaction pool is full, discard underpriced transactions
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue { if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it // If the new transaction is underpriced, don't accept it
if !local && pool.priced.Underpriced(tx, pool.locals) { if !local && pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
@ -624,7 +624,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
return false, ErrUnderpriced return false, ErrUnderpriced
} }
// New transaction is better than our worse ones, make room for it // New transaction is better than our worse ones, make room for it
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals) drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
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(), "price", tx.GasPrice())
underpricedTxCounter.Inc(1) underpricedTxCounter.Inc(1)
@ -642,11 +642,11 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
} }
// New transaction is better, replace old one // New transaction is better, replace old one
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
pendingReplaceCounter.Inc(1) pendingReplaceCounter.Inc(1)
} }
pool.all[tx.Hash()] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
pool.journalTx(from, tx) pool.journalTx(from, tx)
@ -689,12 +689,12 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
} }
// Discard any previous transaction and mark this // Discard any previous transaction and mark this
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
queuedReplaceCounter.Inc(1) queuedReplaceCounter.Inc(1)
} }
if pool.all[hash] == nil { if pool.all.Get(hash) == nil {
pool.all[hash] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
} }
return old != nil, nil return old != nil, nil
@ -726,7 +726,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
inserted, old := list.Add(tx, pool.config.PriceBump) inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted { if !inserted {
// An older transaction was better, discard this // An older transaction was better, discard this
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
pendingDiscardCounter.Inc(1) pendingDiscardCounter.Inc(1)
@ -734,14 +734,14 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
} }
// Otherwise discard any previous transaction and mark this // Otherwise discard any previous transaction and mark this
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
pendingReplaceCounter.Inc(1) pendingReplaceCounter.Inc(1)
} }
// Failsafe to work around direct pending inserts (tests) // Failsafe to work around direct pending inserts (tests)
if pool.all[hash] == nil { if pool.all.Get(hash) == nil {
pool.all[hash] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
} }
// 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
@ -840,7 +840,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
status := make([]TxStatus, len(hashes)) status := make([]TxStatus, len(hashes))
for i, hash := range hashes { for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil { if tx := pool.all.Get(hash); tx != nil {
from, _ := types.Sender(pool.signer, tx) // already validated from, _ := types.Sender(pool.signer, tx) // already validated
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil { if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
status[i] = TxStatusPending status[i] = TxStatusPending
@ -855,24 +855,21 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
// Get returns a transaction if it is contained in the pool // Get returns a transaction if it is contained in the pool
// and nil otherwise. // and nil otherwise.
func (pool *TxPool) Get(hash common.Hash) *types.Transaction { func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
pool.mu.RLock() return pool.all.Get(hash)
defer pool.mu.RUnlock()
return pool.all[hash]
} }
// removeTx removes a single transaction from the queue, moving all subsequent // removeTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue. // transactions back to the future queue.
func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
// Fetch the transaction we wish to delete // Fetch the transaction we wish to delete
tx, ok := pool.all[hash] tx := pool.all.Get(hash)
if !ok { if tx == nil {
return return
} }
addr, _ := types.Sender(pool.signer, tx) // already validated during insertion addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
// Remove it from the list of known transactions // Remove it from the list of known transactions
delete(pool.all, hash) pool.all.Remove(hash)
if outofbound { if outofbound {
pool.priced.Removed() pool.priced.Removed()
} }
@ -928,7 +925,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) { for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed old queued transaction", "hash", hash) log.Trace("Removed old queued transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance or out of gas) // Drop all transactions that are too costly (low balance or out of gas)
@ -936,7 +933,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash) log.Trace("Removed unpayable queued transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
queuedNofundsCounter.Inc(1) queuedNofundsCounter.Inc(1)
} }
@ -952,7 +949,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
if !pool.locals.contains(addr) { if !pool.locals.contains(addr) {
for _, tx := range list.Cap(int(pool.config.AccountQueue)) { for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
queuedRateLimitCounter.Inc(1) queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash) log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
@ -1001,7 +998,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Cap(list.Len() - 1) { for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
@ -1023,7 +1020,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Cap(list.Len() - 1) { for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
@ -1092,7 +1089,7 @@ func (pool *TxPool) demoteUnexecutables() {
for _, tx := range list.Forward(nonce) { for _, tx := range list.Forward(nonce) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed old pending transaction", "hash", hash) log.Trace("Removed old pending transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
@ -1100,7 +1097,7 @@ func (pool *TxPool) demoteUnexecutables() {
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash) log.Trace("Removed unpayable pending transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
pendingNofundsCounter.Inc(1) pendingNofundsCounter.Inc(1)
} }
@ -1172,3 +1169,68 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
func (as *accountSet) add(addr common.Address) { func (as *accountSet) add(addr common.Address) {
as.accounts[addr] = struct{}{} as.accounts[addr] = struct{}{}
} }
// txLookup is used internally by TxPool to track transactions while allowing lookup without
// mutex contention.
//
// Note, although this type is properly protected against concurrent access, it
// is **not** a type that should ever be mutated or even exposed outside of the
// transaction pool, since its internal state is tightly coupled with the pools
// internal mechanisms. The sole purpose of the type is to permit out-of-bound
// peeking into the pool in TxPool.Get without having to acquire the widely scoped
// TxPool.mu mutex.
type txLookup struct {
all map[common.Hash]*types.Transaction
lock sync.RWMutex
}
// newTxLookup returns a new txLookup structure.
func newTxLookup() *txLookup {
return &txLookup{
all: make(map[common.Hash]*types.Transaction),
}
}
// Range calls f on each key and value present in the map.
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) {
t.lock.RLock()
defer t.lock.RUnlock()
for key, value := range t.all {
if !f(key, value) {
break
}
}
}
// Get returns a transaction if it exists in the lookup, or nil if not found.
func (t *txLookup) Get(hash common.Hash) *types.Transaction {
t.lock.RLock()
defer t.lock.RUnlock()
return t.all[hash]
}
// Count returns the current number of items in the lookup.
func (t *txLookup) Count() int {
t.lock.RLock()
defer t.lock.RUnlock()
return len(t.all)
}
// Add adds a transaction to the lookup.
func (t *txLookup) Add(tx *types.Transaction) {
t.lock.Lock()
defer t.lock.Unlock()
t.all[tx.Hash()] = tx
}
// Remove removes a transaction from the lookup.
func (t *txLookup) Remove(hash common.Hash) {
t.lock.Lock()
defer t.lock.Unlock()
delete(t.all, hash)
}

View file

@ -94,7 +94,7 @@ func validateTxPoolInternals(pool *TxPool) error {
// Ensure the total transaction set is consistent with pending + queued // Ensure the total transaction set is consistent with pending + queued
pending, queued := pool.stats() pending, queued := pool.stats()
if total := len(pool.all); total != pending+queued { if total := pool.all.Count(); total != pending+queued {
return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
} }
if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued { if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
@ -401,8 +401,8 @@ func TestTransactionDoubleNonce(t *testing.T) {
t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash())
} }
// Ensure the total transaction count is correct // Ensure the total transaction count is correct
if len(pool.all) != 1 { if pool.all.Count() != 1 {
t.Error("expected 1 total transactions, got", len(pool.all)) t.Error("expected 1 total transactions, got", pool.all.Count())
} }
} }
@ -424,8 +424,8 @@ func TestTransactionMissingNonce(t *testing.T) {
if pool.queue[addr].Len() != 1 { if pool.queue[addr].Len() != 1 {
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
} }
if len(pool.all) != 1 { if pool.all.Count() != 1 {
t.Error("expected 1 total transactions, got", len(pool.all)) t.Error("expected 1 total transactions, got", pool.all.Count())
} }
} }
@ -488,8 +488,8 @@ func TestTransactionDropping(t *testing.T) {
if pool.queue[account].Len() != 3 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
} }
pool.lockedReset(nil, nil) pool.lockedReset(nil, nil)
if pool.pending[account].Len() != 3 { if pool.pending[account].Len() != 3 {
@ -498,8 +498,8 @@ func TestTransactionDropping(t *testing.T) {
if pool.queue[account].Len() != 3 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
} }
// Reduce the balance of the account, and check that invalidated transactions are dropped // Reduce the balance of the account, and check that invalidated transactions are dropped
pool.currentState.AddBalance(account, big.NewInt(-650)) pool.currentState.AddBalance(account, big.NewInt(-650))
@ -523,8 +523,8 @@ func TestTransactionDropping(t *testing.T) {
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
t.Errorf("out-of-fund queued transaction present: %v", tx11) t.Errorf("out-of-fund queued transaction present: %v", tx11)
} }
if len(pool.all) != 4 { if pool.all.Count() != 4 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4)
} }
// Reduce the block gas limit, check that invalidated transactions are dropped // Reduce the block gas limit, check that invalidated transactions are dropped
pool.chain.(*testBlockChain).gasLimit = 100 pool.chain.(*testBlockChain).gasLimit = 100
@ -542,8 +542,8 @@ func TestTransactionDropping(t *testing.T) {
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
t.Errorf("over-gased queued transaction present: %v", tx11) t.Errorf("over-gased queued transaction present: %v", tx11)
} }
if len(pool.all) != 2 { if pool.all.Count() != 2 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 2) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2)
} }
} }
@ -596,8 +596,8 @@ func TestTransactionPostponing(t *testing.T) {
if len(pool.queue) != 0 { if len(pool.queue) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
} }
if len(pool.all) != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
} }
pool.lockedReset(nil, nil) pool.lockedReset(nil, nil)
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
@ -606,8 +606,8 @@ func TestTransactionPostponing(t *testing.T) {
if len(pool.queue) != 0 { if len(pool.queue) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
} }
if len(pool.all) != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
} }
// Reduce the balance of the account, and check that transactions are reorganised // Reduce the balance of the account, and check that transactions are reorganised
for _, addr := range accs { for _, addr := range accs {
@ -656,8 +656,8 @@ func TestTransactionPostponing(t *testing.T) {
} }
} }
} }
if len(pool.all) != len(txs)/2 { if pool.all.Count() != len(txs)/2 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)/2) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2)
} }
} }
@ -748,8 +748,8 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
} }
} }
} }
if len(pool.all) != int(testTxPoolConfig.AccountQueue) { if pool.all.Count() != int(testTxPoolConfig.AccountQueue) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue)
} }
} }
@ -942,8 +942,8 @@ func TestTransactionPendingLimiting(t *testing.T) {
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
} }
} }
if len(pool.all) != int(testTxPoolConfig.AccountQueue+5) { if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue+5) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5)
} }
if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil {
t.Fatalf("event firing failed: %v", err) t.Fatalf("event firing failed: %v", err)
@ -993,8 +993,8 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
if len(pool1.queue) != len(pool2.queue) { if len(pool1.queue) != len(pool2.queue) {
t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue)) t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue))
} }
if len(pool1.all) != len(pool2.all) { if pool1.all.Count() != pool2.all.Count() {
t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", len(pool1.all), len(pool2.all)) t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", pool1.all.Count(), pool2.all.Count())
} }
if err := validateTxPoolInternals(pool1); err != nil { if err := validateTxPoolInternals(pool1); err != nil {
t.Errorf("pool 1 internal state corrupted: %v", err) t.Errorf("pool 1 internal state corrupted: %v", err)

View file

@ -12,10 +12,11 @@ import (
var _ = (*receiptMarshaling)(nil) var _ = (*receiptMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (r Receipt) MarshalJSON() ([]byte, error) { func (r Receipt) MarshalJSON() ([]byte, error) {
type Receipt struct { type Receipt struct {
PostState hexutil.Bytes `json:"root"` PostState hexutil.Bytes `json:"root"`
Status hexutil.Uint `json:"status"` Status hexutil.Uint64 `json:"status"`
CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -25,7 +26,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
} }
var enc Receipt var enc Receipt
enc.PostState = r.PostState enc.PostState = r.PostState
enc.Status = hexutil.Uint(r.Status) enc.Status = hexutil.Uint64(r.Status)
enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
enc.Bloom = r.Bloom enc.Bloom = r.Bloom
enc.Logs = r.Logs enc.Logs = r.Logs
@ -35,10 +36,11 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (r *Receipt) UnmarshalJSON(input []byte) error { func (r *Receipt) UnmarshalJSON(input []byte) error {
type Receipt struct { type Receipt struct {
PostState *hexutil.Bytes `json:"root"` PostState *hexutil.Bytes `json:"root"`
Status *hexutil.Uint `json:"status"` Status *hexutil.Uint64 `json:"status"`
CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -54,7 +56,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
r.PostState = *dec.PostState r.PostState = *dec.PostState
} }
if dec.Status != nil { if dec.Status != nil {
r.Status = uint(*dec.Status) r.Status = uint64(*dec.Status)
} }
if dec.CumulativeGasUsed == nil { if dec.CumulativeGasUsed == nil {
return errors.New("missing required field 'cumulativeGasUsed' for Receipt") return errors.New("missing required field 'cumulativeGasUsed' for Receipt")

View file

@ -36,17 +36,17 @@ var (
const ( const (
// ReceiptStatusFailed is the status code of a transaction if execution failed. // ReceiptStatusFailed is the status code of a transaction if execution failed.
ReceiptStatusFailed = uint(0) ReceiptStatusFailed = uint64(0)
// ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded.
ReceiptStatusSuccessful = uint(1) ReceiptStatusSuccessful = uint64(1)
) )
// Receipt represents the results of a transaction. // Receipt represents the results of a transaction.
type Receipt struct { type Receipt struct {
// Consensus fields // Consensus fields
PostState []byte `json:"root"` PostState []byte `json:"root"`
Status uint `json:"status"` Status uint64 `json:"status"`
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -59,7 +59,7 @@ type Receipt struct {
type receiptMarshaling struct { type receiptMarshaling struct {
PostState hexutil.Bytes PostState hexutil.Bytes
Status hexutil.Uint Status hexutil.Uint64
CumulativeGasUsed hexutil.Uint64 CumulativeGasUsed hexutil.Uint64
GasUsed hexutil.Uint64 GasUsed hexutil.Uint64
} }

View file

@ -850,7 +850,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
} }
} }
// make push instruction function // make dup instruction function
func makeDup(size int64) executionFunc { func makeDup(size int64) executionFunc {
return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.dup(evm.interpreter.intPool, int(size)) stack.dup(evm.interpreter.intPool, int(size))

View file

@ -22,6 +22,7 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
// Iterator is a key-value trie iterator that traverses a Trie. // Iterator is a key-value trie iterator that traverses a Trie.
@ -55,31 +56,50 @@ func (it *Iterator) Next() bool {
return false return false
} }
// Prove generates the Merkle proof for the leaf node the iterator is currently
// positioned on.
func (it *Iterator) Prove() [][]byte {
return it.nodeIt.LeafProof()
}
// NodeIterator is an iterator to traverse the trie pre-order. // NodeIterator is an iterator to traverse the trie pre-order.
type NodeIterator interface { type NodeIterator interface {
// Next moves the iterator to the next node. If the parameter is false, any child // Next moves the iterator to the next node. If the parameter is false, any child
// nodes will be skipped. // nodes will be skipped.
Next(bool) bool Next(bool) bool
// Error returns the error status of the iterator. // Error returns the error status of the iterator.
Error() error Error() error
// Hash returns the hash of the current node. // Hash returns the hash of the current node.
Hash() common.Hash Hash() common.Hash
// Parent returns the hash of the parent of the current node. The hash may be the one // Parent returns the hash of the parent of the current node. The hash may be the one
// grandparent if the immediate parent is an internal node with no hash. // grandparent if the immediate parent is an internal node with no hash.
Parent() common.Hash Parent() common.Hash
// Path returns the hex-encoded path to the current node. // Path returns the hex-encoded path to the current node.
// Callers must not retain references to the return value after calling Next. // Callers must not retain references to the return value after calling Next.
// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
Path() []byte Path() []byte
// Leaf returns true iff the current node is a leaf node. // Leaf returns true iff the current node is a leaf node.
// LeafBlob, LeafKey return the contents and key of the leaf node. These
// method panic if the iterator is not positioned at a leaf.
// Callers must not retain references to their return value after calling Next
Leaf() bool Leaf() bool
LeafBlob() []byte
// LeafKey returns the key of the leaf. The method panics if the iterator is not
// positioned at a leaf. Callers must not retain references to the value after
// calling Next.
LeafKey() []byte LeafKey() []byte
// LeafBlob returns the content of the leaf. The method panics if the iterator
// is not positioned at a leaf. Callers must not retain references to the value
// after calling Next.
LeafBlob() []byte
// LeafProof returns the Merkle proof of the leaf. The method panics if the
// iterator is not positioned at a leaf. Callers must not retain references
// to the value after calling Next.
LeafProof() [][]byte
} }
// nodeIteratorState represents the iteration state at one particular node of the // nodeIteratorState represents the iteration state at one particular node of the
@ -139,6 +159,15 @@ func (it *nodeIterator) Leaf() bool {
return hasTerm(it.path) return hasTerm(it.path)
} }
func (it *nodeIterator) LeafKey() []byte {
if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
return hexToKeybytes(it.path)
}
}
panic("not at leaf")
}
func (it *nodeIterator) LeafBlob() []byte { func (it *nodeIterator) LeafBlob() []byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
@ -148,10 +177,22 @@ func (it *nodeIterator) LeafBlob() []byte {
panic("not at leaf") panic("not at leaf")
} }
func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafProof() [][]byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
return hexToKeybytes(it.path) hasher := newHasher(0, 0, nil)
proofs := make([][]byte, 0, len(it.stack))
for i, item := range it.stack[:len(it.stack)-1] {
// Gather nodes that end up as hash nodes (or the root)
node, _, _ := hasher.hashChildren(item.node, nil)
hashed, _ := hasher.store(node, nil, false)
if _, ok := hashed.(hashNode); ok || i == 0 {
enc, _ := rlp.EncodeToBytes(node)
proofs = append(proofs, enc)
}
}
return proofs
} }
} }
panic("not at leaf") panic("not at leaf")
@ -361,12 +402,16 @@ func (it *differenceIterator) Leaf() bool {
return it.b.Leaf() return it.b.Leaf()
} }
func (it *differenceIterator) LeafKey() []byte {
return it.b.LeafKey()
}
func (it *differenceIterator) LeafBlob() []byte { func (it *differenceIterator) LeafBlob() []byte {
return it.b.LeafBlob() return it.b.LeafBlob()
} }
func (it *differenceIterator) LeafKey() []byte { func (it *differenceIterator) LeafProof() [][]byte {
return it.b.LeafKey() return it.b.LeafProof()
} }
func (it *differenceIterator) Path() []byte { func (it *differenceIterator) Path() []byte {
@ -464,12 +509,16 @@ func (it *unionIterator) Leaf() bool {
return (*it.items)[0].Leaf() return (*it.items)[0].Leaf()
} }
func (it *unionIterator) LeafKey() []byte {
return (*it.items)[0].LeafKey()
}
func (it *unionIterator) LeafBlob() []byte { func (it *unionIterator) LeafBlob() []byte {
return (*it.items)[0].LeafBlob() return (*it.items)[0].LeafBlob()
} }
func (it *unionIterator) LeafKey() []byte { func (it *unionIterator) LeafProof() [][]byte {
return (*it.items)[0].LeafKey() return (*it.items)[0].LeafProof()
} }
func (it *unionIterator) Path() []byte { func (it *unionIterator) Path() []byte {
@ -509,12 +558,10 @@ func (it *unionIterator) Next(descend bool) bool {
heap.Push(it.items, skipped) heap.Push(it.items, skipped)
} }
} }
if least.Next(descend) { if least.Next(descend) {
it.count++ it.count++
heap.Push(it.items, least) heap.Push(it.items, least)
} }
return len(*it.items) > 0 return len(*it.items) > 0
} }

View file

@ -32,20 +32,46 @@ func init() {
mrand.Seed(time.Now().Unix()) mrand.Seed(time.Now().Unix())
} }
// makeProvers creates Merkle trie provers based on different implementations to
// test all variations.
func makeProvers(trie *Trie) []func(key []byte) *ethdb.MemDatabase {
var provers []func(key []byte) *ethdb.MemDatabase
// Create a direct trie based Merkle prover
provers = append(provers, func(key []byte) *ethdb.MemDatabase {
proof := ethdb.NewMemDatabase()
trie.Prove(key, 0, proof)
return proof
})
// Create a leaf iterator based Merkle prover
provers = append(provers, func(key []byte) *ethdb.MemDatabase {
proof := ethdb.NewMemDatabase()
if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) {
for _, p := range it.Prove() {
proof.Put(crypto.Keccak256(p), p)
}
}
return proof
})
return provers
}
func TestProof(t *testing.T) { func TestProof(t *testing.T) {
trie, vals := randomTrie(500) trie, vals := randomTrie(500)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proofs := ethdb.NewMemDatabase() for _, kv := range vals {
if trie.Prove(kv.k, 0, proofs) != nil { proof := prover(kv.k)
t.Fatalf("missing key %x while constructing proof", kv.k) if proof == nil {
} t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k)
val, _, err := VerifyProof(root, kv.k, proofs) }
if err != nil { val, _, err := VerifyProof(root, kv.k, proof)
t.Fatalf("VerifyProof error for key %x: %v\nraw proof: %v", kv.k, err, proofs) if err != nil {
} t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof)
if !bytes.Equal(val, kv.v) { }
t.Fatalf("VerifyProof returned wrong value for key %x: got %x, want %x", kv.k, val, kv.v) if !bytes.Equal(val, kv.v) {
t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v)
}
} }
} }
} }
@ -53,37 +79,66 @@ func TestProof(t *testing.T) {
func TestOneElementProof(t *testing.T) { func TestOneElementProof(t *testing.T) {
trie := new(Trie) trie := new(Trie)
updateString(trie, "k", "v") updateString(trie, "k", "v")
proofs := ethdb.NewMemDatabase() for i, prover := range makeProvers(trie) {
trie.Prove([]byte("k"), 0, proofs) proof := prover([]byte("k"))
if len(proofs.Keys()) != 1 { if proof == nil {
t.Error("proof should have one element") t.Fatalf("prover %d: nil proof", i)
} }
val, _, err := VerifyProof(trie.Hash(), []byte("k"), proofs) if proof.Len() != 1 {
if err != nil { t.Errorf("prover %d: proof should have one element", i)
t.Fatalf("VerifyProof error: %v\nproof hashes: %v", err, proofs.Keys()) }
} val, _, err := VerifyProof(trie.Hash(), []byte("k"), proof)
if !bytes.Equal(val, []byte("v")) { if err != nil {
t.Fatalf("VerifyProof returned wrong value: got %x, want 'k'", val) t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
}
if !bytes.Equal(val, []byte("v")) {
t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val)
}
} }
} }
func TestVerifyBadProof(t *testing.T) { func TestBadProof(t *testing.T) {
trie, vals := randomTrie(800) trie, vals := randomTrie(800)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proofs := ethdb.NewMemDatabase() for _, kv := range vals {
trie.Prove(kv.k, 0, proofs) proof := prover(kv.k)
if len(proofs.Keys()) == 0 { if proof == nil {
t.Fatal("zero length proof") t.Fatalf("prover %d: nil proof", i)
}
key := proof.Keys()[mrand.Intn(proof.Len())]
val, _ := proof.Get(key)
proof.Delete(key)
mutateByte(val)
proof.Put(crypto.Keccak256(val), val)
if _, _, err := VerifyProof(root, kv.k, proof); err == nil {
t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k)
}
} }
keys := proofs.Keys() }
key := keys[mrand.Intn(len(keys))] }
node, _ := proofs.Get(key)
proofs.Delete(key) // Tests that missing keys can also be proven. The test explicitly uses a single
mutateByte(node) // entry trie and checks for missing keys both before and after the single entry.
proofs.Put(crypto.Keccak256(node), node) func TestMissingKeyProof(t *testing.T) {
if _, _, err := VerifyProof(root, kv.k, proofs); err == nil { trie := new(Trie)
t.Fatalf("expected proof to fail for key %x", kv.k) updateString(trie, "k", "v")
for i, key := range []string{"a", "j", "l", "z"} {
proof := ethdb.NewMemDatabase()
trie.Prove([]byte(key), 0, proof)
if proof.Len() != 1 {
t.Errorf("test %d: proof should have one element", i)
}
val, _, err := VerifyProof(trie.Hash(), []byte(key), proof)
if err != nil {
t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
}
if val != nil {
t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val)
} }
} }
} }