cmd, core, eth, miner: remove txpool gas price limits (#14442)
This commit is contained in:
parent
e20158176d
commit
a2f23ca9b1
@ -237,7 +237,7 @@ var (
|
|||||||
GasPriceFlag = BigFlag{
|
GasPriceFlag = BigFlag{
|
||||||
Name: "gasprice",
|
Name: "gasprice",
|
||||||
Usage: "Minimal gas price to accept for mining a transactions",
|
Usage: "Minimal gas price to accept for mining a transactions",
|
||||||
Value: big.NewInt(20 * params.Shannon),
|
Value: eth.DefaultConfig.GasPrice,
|
||||||
}
|
}
|
||||||
ExtraDataFlag = cli.StringFlag{
|
ExtraDataFlag = cli.StringFlag{
|
||||||
Name: "extradata",
|
Name: "extradata",
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
@ -67,8 +65,6 @@ type ChainUncleEvent struct {
|
|||||||
|
|
||||||
type ChainHeadEvent struct{ Block *types.Block }
|
type ChainHeadEvent struct{ Block *types.Block }
|
||||||
|
|
||||||
type GasPriceChanged struct{ Price *big.Int }
|
|
||||||
|
|
||||||
// Mining operation events
|
// Mining operation events
|
||||||
type StartMining struct{}
|
type StartMining struct{}
|
||||||
type TopMining struct{}
|
type TopMining struct{}
|
||||||
|
166
core/tx_list.go
166
core/tx_list.go
@ -22,7 +22,9 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for
|
// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for
|
||||||
@ -53,11 +55,11 @@ type txSortedMap struct {
|
|||||||
cache types.Transactions // Cache of the transactions already sorted
|
cache types.Transactions // Cache of the transactions already sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTxSortedMap creates a new sorted transaction map.
|
// newTxSortedMap creates a new nonce-sorted transaction map.
|
||||||
func newTxSortedMap() *txSortedMap {
|
func newTxSortedMap() *txSortedMap {
|
||||||
return &txSortedMap{
|
return &txSortedMap{
|
||||||
items: make(map[uint64]*types.Transaction),
|
items: make(map[uint64]*types.Transaction),
|
||||||
index: &nonceHeap{},
|
index: new(nonceHeap),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +235,12 @@ func newTxList(strict bool) *txList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overlaps returns whether the transaction specified has the same nonce as one
|
||||||
|
// already contained within the list.
|
||||||
|
func (l *txList) Overlaps(tx *types.Transaction) bool {
|
||||||
|
return l.txs.Get(tx.Nonce()) != nil
|
||||||
|
}
|
||||||
|
|
||||||
// Add tries to insert a new transaction into the list, returning whether the
|
// Add tries to insert a new transaction into the list, returning whether the
|
||||||
// transaction was accepted, and if yes, any previous transaction it replaced.
|
// transaction was accepted, and if yes, any previous transaction it replaced.
|
||||||
//
|
//
|
||||||
@ -241,8 +249,11 @@ func newTxList(strict bool) *txList {
|
|||||||
func (l *txList) Add(tx *types.Transaction) (bool, *types.Transaction) {
|
func (l *txList) Add(tx *types.Transaction) (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.GasPrice().Cmp(tx.GasPrice()) >= 0 {
|
if old != nil {
|
||||||
return false, nil
|
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+minPriceBumpPercent)), big.NewInt(100))
|
||||||
|
if threshold.Cmp(tx.GasPrice()) >= 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Otherwise overwrite the old transaction with the current one
|
// Otherwise overwrite the old transaction with the current one
|
||||||
l.txs.Put(tx)
|
l.txs.Put(tx)
|
||||||
@ -340,3 +351,150 @@ func (l *txList) Empty() bool {
|
|||||||
func (l *txList) Flatten() types.Transactions {
|
func (l *txList) Flatten() types.Transactions {
|
||||||
return l.txs.Flatten()
|
return l.txs.Flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// priceHeap is a heap.Interface implementation over transactions for retrieving
|
||||||
|
// price-sorted transactions to discard when the pool fills up.
|
||||||
|
type priceHeap []*types.Transaction
|
||||||
|
|
||||||
|
func (h priceHeap) Len() int { return len(h) }
|
||||||
|
func (h priceHeap) Less(i, j int) bool { return h[i].GasPrice().Cmp(h[j].GasPrice()) < 0 }
|
||||||
|
func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
|
|
||||||
|
func (h *priceHeap) Push(x interface{}) {
|
||||||
|
*h = append(*h, x.(*types.Transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *priceHeap) Pop() interface{} {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
x := old[n-1]
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// txPricedList is a price-sorted heap to allow operating on transactions pool
|
||||||
|
// contents in a price-incrementing way.
|
||||||
|
type txPricedList struct {
|
||||||
|
all *map[common.Hash]*types.Transaction // Pointer to the map of all transactions
|
||||||
|
items *priceHeap // Heap of prices of all the stored transactions
|
||||||
|
stales int // Number of stale price points to (re-heap trigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTxPricedList creates a new price-sorted transaction heap.
|
||||||
|
func newTxPricedList(all *map[common.Hash]*types.Transaction) *txPricedList {
|
||||||
|
return &txPricedList{
|
||||||
|
all: all,
|
||||||
|
items: new(priceHeap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put inserts a new transaction into the heap.
|
||||||
|
func (l *txPricedList) Put(tx *types.Transaction) {
|
||||||
|
heap.Push(l.items, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// the heap if a large enough ratio of transactions go stale.
|
||||||
|
func (l *txPricedList) Removed() {
|
||||||
|
// Bump the stale counter, but exit if still too low (< 25%)
|
||||||
|
l.stales++
|
||||||
|
if l.stales <= len(*l.items)/4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Seems we've reached a critical number of stale transactions, reheap
|
||||||
|
reheap := make(priceHeap, 0, len(*l.all))
|
||||||
|
|
||||||
|
l.stales, l.items = 0, &reheap
|
||||||
|
for _, tx := range *l.all {
|
||||||
|
*l.items = append(*l.items, tx)
|
||||||
|
}
|
||||||
|
heap.Init(l.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard finds all the transactions below the given price threshold, drops them
|
||||||
|
// from the priced list and returs them for further removal from the entire pool.
|
||||||
|
func (l *txPricedList) Cap(threshold *big.Int, local *txSet) types.Transactions {
|
||||||
|
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
|
||||||
|
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
||||||
|
|
||||||
|
for len(*l.items) > 0 {
|
||||||
|
// Discard stale transactions if found during cleanup
|
||||||
|
tx := heap.Pop(l.items).(*types.Transaction)
|
||||||
|
|
||||||
|
hash := tx.Hash()
|
||||||
|
if _, ok := (*l.all)[hash]; !ok {
|
||||||
|
l.stales--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Stop the discards if we've reached the threshold
|
||||||
|
if tx.GasPrice().Cmp(threshold) >= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Non stale transaction found, discard unless local
|
||||||
|
if local.contains(hash) {
|
||||||
|
save = append(save, tx)
|
||||||
|
} else {
|
||||||
|
drop = append(drop, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tx := range save {
|
||||||
|
heap.Push(l.items, tx)
|
||||||
|
}
|
||||||
|
return drop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underpriced checks whether a transaction is cheaper than (or as cheap as) the
|
||||||
|
// lowest priced transaction currently being tracked.
|
||||||
|
func (l *txPricedList) Underpriced(tx *types.Transaction, local *txSet) bool {
|
||||||
|
// Local transactions cannot be underpriced
|
||||||
|
if local.contains(tx.Hash()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Discard stale price points if found at the heap start
|
||||||
|
for len(*l.items) > 0 {
|
||||||
|
head := []*types.Transaction(*l.items)[0]
|
||||||
|
if _, ok := (*l.all)[head.Hash()]; !ok {
|
||||||
|
l.stales--
|
||||||
|
heap.Pop(l.items)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Check if the transaction is underpriced or not
|
||||||
|
if len(*l.items) == 0 {
|
||||||
|
log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cheapest := []*types.Transaction(*l.items)[0]
|
||||||
|
return cheapest.GasPrice().Cmp(tx.GasPrice()) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard finds a number of most underpriced transactions, removes them from the
|
||||||
|
// priced list and returs them for further removal from the entire pool.
|
||||||
|
func (l *txPricedList) Discard(count int, local *txSet) types.Transactions {
|
||||||
|
drop := make(types.Transactions, 0, count) // Remote underpriced transactions to drop
|
||||||
|
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
||||||
|
|
||||||
|
for len(*l.items) > 0 && count > 0 {
|
||||||
|
// Discard stale transactions if found during cleanup
|
||||||
|
tx := heap.Pop(l.items).(*types.Transaction)
|
||||||
|
|
||||||
|
hash := tx.Hash()
|
||||||
|
if _, ok := (*l.all)[hash]; !ok {
|
||||||
|
l.stales--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Non stale transaction found, discard unless local
|
||||||
|
if local.contains(hash) {
|
||||||
|
save = append(save, tx)
|
||||||
|
} else {
|
||||||
|
drop = append(drop, tx)
|
||||||
|
count--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tx := range save {
|
||||||
|
heap.Push(l.items, tx)
|
||||||
|
}
|
||||||
|
return drop
|
||||||
|
}
|
||||||
|
269
core/tx_pool.go
269
core/tx_pool.go
@ -36,23 +36,26 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Transaction Pool Errors
|
// Transaction Pool Errors
|
||||||
ErrInvalidSender = errors.New("Invalid sender")
|
ErrInvalidSender = errors.New("invalid sender")
|
||||||
ErrNonce = errors.New("Nonce too low")
|
ErrNonce = errors.New("nonce too low")
|
||||||
ErrCheap = errors.New("Gas price too low for acceptance")
|
ErrUnderpriced = errors.New("transaction underpriced")
|
||||||
ErrBalance = errors.New("Insufficient balance")
|
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||||
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
|
ErrBalance = errors.New("insufficient balance")
|
||||||
ErrIntrinsicGas = errors.New("Intrinsic gas too low")
|
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
||||||
ErrGasLimit = errors.New("Exceeds block gas limit")
|
ErrIntrinsicGas = errors.New("intrinsic gas too low")
|
||||||
ErrNegativeValue = errors.New("Negative value")
|
ErrGasLimit = errors.New("exceeds block gas limit")
|
||||||
|
ErrNegativeValue = errors.New("negative value")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
minPendingPerAccount = uint64(16) // Min number of guaranteed transaction slots per address
|
minPendingPerAccount = uint64(16) // Min number of guaranteed transaction slots per address
|
||||||
maxPendingTotal = uint64(4096) // Max limit of pending transactions from all accounts (soft)
|
maxPendingTotal = uint64(4096) // Max limit of pending transactions from all accounts (soft)
|
||||||
maxQueuedPerAccount = uint64(64) // Max limit of queued transactions per address
|
maxQueuedPerAccount = uint64(64) // Max limit of queued transactions per address
|
||||||
maxQueuedInTotal = uint64(1024) // Max limit of queued transactions from all accounts
|
maxQueuedTotal = uint64(1024) // Max limit of queued transactions from all accounts
|
||||||
maxQueuedLifetime = 3 * time.Hour // Max amount of time transactions from idle accounts are queued
|
maxQueuedLifetime = 3 * time.Hour // Max amount of time transactions from idle accounts are queued
|
||||||
evictionInterval = time.Minute // Time interval to check for evictable transactions
|
minPriceBumpPercent = int64(10) // Minimum price bump needed to replace an old transaction
|
||||||
|
evictionInterval = time.Minute // Time interval to check for evictable transactions
|
||||||
|
statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -69,7 +72,8 @@ var (
|
|||||||
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
|
queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
|
||||||
|
|
||||||
// General tx metrics
|
// General tx metrics
|
||||||
invalidTxCounter = metrics.NewCounter("txpool/invalid")
|
invalidTxCounter = metrics.NewCounter("txpool/invalid")
|
||||||
|
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
||||||
)
|
)
|
||||||
|
|
||||||
type stateFn func() (*state.StateDB, error)
|
type stateFn func() (*state.StateDB, error)
|
||||||
@ -86,17 +90,18 @@ type TxPool struct {
|
|||||||
currentState stateFn // The state function which will allow us to do some pre checks
|
currentState stateFn // The state function which will allow us to do some pre checks
|
||||||
pendingState *state.ManagedState
|
pendingState *state.ManagedState
|
||||||
gasLimit func() *big.Int // The current gas limit function callback
|
gasLimit func() *big.Int // The current gas limit function callback
|
||||||
minGasPrice *big.Int
|
gasPrice *big.Int
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
events *event.TypeMuxSubscription
|
events *event.TypeMuxSubscription
|
||||||
localTx *txSet
|
locals *txSet
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
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
|
||||||
all map[common.Hash]*types.Transaction // All transactions to allow lookups
|
|
||||||
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
|
||||||
|
priced *txPricedList // All transactions sorted by price
|
||||||
|
|
||||||
wg sync.WaitGroup // for shutdown sync
|
wg sync.WaitGroup // for shutdown sync
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
@ -110,18 +115,18 @@ func NewTxPool(config *params.ChainConfig, eventMux *event.TypeMux, currentState
|
|||||||
signer: types.NewEIP155Signer(config.ChainId),
|
signer: types.NewEIP155Signer(config.ChainId),
|
||||||
pending: make(map[common.Address]*txList),
|
pending: make(map[common.Address]*txList),
|
||||||
queue: make(map[common.Address]*txList),
|
queue: make(map[common.Address]*txList),
|
||||||
all: make(map[common.Hash]*types.Transaction),
|
|
||||||
beats: make(map[common.Address]time.Time),
|
beats: make(map[common.Address]time.Time),
|
||||||
|
all: make(map[common.Hash]*types.Transaction),
|
||||||
eventMux: eventMux,
|
eventMux: eventMux,
|
||||||
currentState: currentStateFn,
|
currentState: currentStateFn,
|
||||||
gasLimit: gasLimitFn,
|
gasLimit: gasLimitFn,
|
||||||
minGasPrice: new(big.Int),
|
gasPrice: big.NewInt(1),
|
||||||
pendingState: nil,
|
pendingState: nil,
|
||||||
localTx: newTxSet(),
|
locals: newTxSet(),
|
||||||
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
|
events: eventMux.Subscribe(ChainHeadEvent{}, RemovedTransactionEvent{}),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
pool.priced = newTxPricedList(&pool.all)
|
||||||
pool.resetState()
|
pool.resetState()
|
||||||
|
|
||||||
pool.wg.Add(2)
|
pool.wg.Add(2)
|
||||||
@ -134,27 +139,48 @@ func NewTxPool(config *params.ChainConfig, eventMux *event.TypeMux, currentState
|
|||||||
func (pool *TxPool) eventLoop() {
|
func (pool *TxPool) eventLoop() {
|
||||||
defer pool.wg.Done()
|
defer pool.wg.Done()
|
||||||
|
|
||||||
|
// Start a ticker and keep track of interesting pool stats to report
|
||||||
|
var prevPending, prevQueued, prevStales int
|
||||||
|
|
||||||
|
report := time.NewTicker(statsReportInterval)
|
||||||
|
defer report.Stop()
|
||||||
|
|
||||||
// Track chain events. When a chain events occurs (new chain canon block)
|
// Track chain events. When a chain events occurs (new chain canon block)
|
||||||
// we need to know the new state. The new state will help us determine
|
// we need to know the new state. The new state will help us determine
|
||||||
// the nonces in the managed state
|
// the nonces in the managed state
|
||||||
for ev := range pool.events.Chan() {
|
for {
|
||||||
switch ev := ev.Data.(type) {
|
select {
|
||||||
case ChainHeadEvent:
|
// Handle any events fired by the system
|
||||||
pool.mu.Lock()
|
case ev, ok := <-pool.events.Chan():
|
||||||
if ev.Block != nil {
|
if !ok {
|
||||||
if pool.config.IsHomestead(ev.Block.Number()) {
|
return
|
||||||
pool.homestead = true
|
}
|
||||||
|
switch ev := ev.Data.(type) {
|
||||||
|
case ChainHeadEvent:
|
||||||
|
pool.mu.Lock()
|
||||||
|
if ev.Block != nil {
|
||||||
|
if pool.config.IsHomestead(ev.Block.Number()) {
|
||||||
|
pool.homestead = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pool.resetState()
|
||||||
|
pool.mu.Unlock()
|
||||||
|
|
||||||
|
case RemovedTransactionEvent:
|
||||||
|
pool.AddBatch(ev.Txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.resetState()
|
// Handle stats reporting ticks
|
||||||
pool.mu.Unlock()
|
case <-report.C:
|
||||||
case GasPriceChanged:
|
pool.mu.RLock()
|
||||||
pool.mu.Lock()
|
pending, queued := pool.stats()
|
||||||
pool.minGasPrice = ev.Price
|
stales := pool.priced.stales
|
||||||
pool.mu.Unlock()
|
pool.mu.RUnlock()
|
||||||
case RemovedTransactionEvent:
|
|
||||||
pool.AddBatch(ev.Txs)
|
if pending != prevPending || queued != prevQueued || stales != prevStales {
|
||||||
|
log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales)
|
||||||
|
prevPending, prevQueued, prevStales = pending, queued, stales
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,6 +217,27 @@ func (pool *TxPool) Stop() {
|
|||||||
log.Info("Transaction pool stopped")
|
log.Info("Transaction pool stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GasPrice returns the current gas price enforced by the transaction pool.
|
||||||
|
func (pool *TxPool) GasPrice() *big.Int {
|
||||||
|
pool.mu.RLock()
|
||||||
|
defer pool.mu.RUnlock()
|
||||||
|
|
||||||
|
return new(big.Int).Set(pool.gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGasPrice updates the minimum price required by the transaction pool for a
|
||||||
|
// new transaction, and drops all transactions below this threshold.
|
||||||
|
func (pool *TxPool) SetGasPrice(price *big.Int) {
|
||||||
|
pool.mu.Lock()
|
||||||
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
|
pool.gasPrice = price
|
||||||
|
for _, tx := range pool.priced.Cap(price, pool.locals) {
|
||||||
|
pool.removeTx(tx.Hash())
|
||||||
|
}
|
||||||
|
log.Info("Transaction pool price threshold updated", "price", price)
|
||||||
|
}
|
||||||
|
|
||||||
func (pool *TxPool) State() *state.ManagedState {
|
func (pool *TxPool) State() *state.ManagedState {
|
||||||
pool.mu.RLock()
|
pool.mu.RLock()
|
||||||
defer pool.mu.RUnlock()
|
defer pool.mu.RUnlock()
|
||||||
@ -200,17 +247,25 @@ func (pool *TxPool) State() *state.ManagedState {
|
|||||||
|
|
||||||
// Stats retrieves the current pool stats, namely the number of pending and the
|
// Stats retrieves the current pool stats, namely the number of pending and the
|
||||||
// number of queued (non-executable) transactions.
|
// number of queued (non-executable) transactions.
|
||||||
func (pool *TxPool) Stats() (pending int, queued int) {
|
func (pool *TxPool) Stats() (int, int) {
|
||||||
pool.mu.RLock()
|
pool.mu.RLock()
|
||||||
defer pool.mu.RUnlock()
|
defer pool.mu.RUnlock()
|
||||||
|
|
||||||
|
return pool.stats()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stats retrieves the current pool stats, namely the number of pending and the
|
||||||
|
// number of queued (non-executable) transactions.
|
||||||
|
func (pool *TxPool) stats() (int, int) {
|
||||||
|
pending := 0
|
||||||
for _, list := range pool.pending {
|
for _, list := range pool.pending {
|
||||||
pending += list.Len()
|
pending += list.Len()
|
||||||
}
|
}
|
||||||
|
queued := 0
|
||||||
for _, list := range pool.queue {
|
for _, list := range pool.queue {
|
||||||
queued += list.Len()
|
queued += list.Len()
|
||||||
}
|
}
|
||||||
return
|
return pending, queued
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content retrieves the data content of the transaction pool, returning all the
|
// Content retrieves the data content of the transaction pool, returning all the
|
||||||
@ -260,16 +315,16 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
|
|||||||
func (pool *TxPool) SetLocal(tx *types.Transaction) {
|
func (pool *TxPool) SetLocal(tx *types.Transaction) {
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
pool.localTx.add(tx.Hash())
|
pool.locals.add(tx.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateTx checks whether a transaction is valid according
|
// validateTx checks whether a transaction is valid according
|
||||||
// to the consensus rules.
|
// to the consensus rules.
|
||||||
func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
||||||
local := pool.localTx.contains(tx.Hash())
|
local := pool.locals.contains(tx.Hash())
|
||||||
// Drop transactions under our own minimal accepted gas price
|
// Drop transactions under our own minimal accepted gas price
|
||||||
if !local && pool.minGasPrice.Cmp(tx.GasPrice()) > 0 {
|
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
|
||||||
return ErrCheap
|
return ErrUnderpriced
|
||||||
}
|
}
|
||||||
|
|
||||||
currentState, err := pool.currentState()
|
currentState, err := pool.currentState()
|
||||||
@ -314,31 +369,72 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add validates a transaction and inserts it into the non-executable queue for
|
// add validates a transaction and inserts it into the non-executable queue for
|
||||||
// later pending promotion and execution.
|
// later pending promotion and execution. If the transaction is a replacement for
|
||||||
func (pool *TxPool) add(tx *types.Transaction) error {
|
// an already pending or queued one, it overwrites the previous and returns this
|
||||||
|
// so outer code doesn't uselessly call promote.
|
||||||
|
func (pool *TxPool) add(tx *types.Transaction) (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[hash] != nil {
|
||||||
log.Trace("Discarding already known transaction", "hash", hash)
|
log.Trace("Discarding already known transaction", "hash", hash)
|
||||||
return fmt.Errorf("known transaction: %x", hash)
|
return false, fmt.Errorf("known transaction: %x", hash)
|
||||||
}
|
}
|
||||||
// Otherwise ensure basic validation passes and queue it up
|
// If the transaction fails basic validation, discard it
|
||||||
if err := pool.validateTx(tx); err != nil {
|
if err := pool.validateTx(tx); err != nil {
|
||||||
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
|
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
|
||||||
invalidTxCounter.Inc(1)
|
invalidTxCounter.Inc(1)
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
pool.enqueueTx(hash, tx)
|
// If the transaction pool is full, discard underpriced transactions
|
||||||
|
if uint64(len(pool.all)) >= maxPendingTotal+maxQueuedTotal {
|
||||||
|
// If the new transaction is underpriced, don't accept it
|
||||||
|
if pool.priced.Underpriced(tx, pool.locals) {
|
||||||
|
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
|
||||||
|
underpricedTxCounter.Inc(1)
|
||||||
|
return false, ErrUnderpriced
|
||||||
|
}
|
||||||
|
// New transaction is better than our worse ones, make room for it
|
||||||
|
drop := pool.priced.Discard(len(pool.all)-int(maxPendingTotal+maxQueuedTotal-1), pool.locals)
|
||||||
|
for _, tx := range drop {
|
||||||
|
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
|
||||||
|
underpricedTxCounter.Inc(1)
|
||||||
|
pool.removeTx(tx.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the transaction is replacing an already pending one, do directly
|
||||||
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
|
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
|
||||||
|
// Nonce already pending, check if required price bump is met
|
||||||
|
inserted, old := list.Add(tx)
|
||||||
|
if !inserted {
|
||||||
|
pendingDiscardCounter.Inc(1)
|
||||||
|
return false, ErrReplaceUnderpriced
|
||||||
|
}
|
||||||
|
// New transaction is better, replace old one
|
||||||
|
if old != nil {
|
||||||
|
delete(pool.all, old.Hash())
|
||||||
|
pool.priced.Removed()
|
||||||
|
pendingReplaceCounter.Inc(1)
|
||||||
|
}
|
||||||
|
pool.all[tx.Hash()] = tx
|
||||||
|
pool.priced.Put(tx)
|
||||||
|
|
||||||
// Print a log message if low enough level is set
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To())
|
return old != nil, nil
|
||||||
return nil
|
}
|
||||||
|
// New transaction isn't replacing a pending one, push into queue
|
||||||
|
replace, err := pool.enqueueTx(hash, tx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
return replace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enqueueTx inserts a new transaction into the non-executable transaction queue.
|
// enqueueTx inserts a new transaction into the non-executable transaction queue.
|
||||||
//
|
//
|
||||||
// Note, this method assumes the pool lock is held!
|
// Note, this method assumes the pool lock is held!
|
||||||
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) {
|
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
|
||||||
// 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 {
|
||||||
@ -346,15 +442,19 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) {
|
|||||||
}
|
}
|
||||||
inserted, old := pool.queue[from].Add(tx)
|
inserted, old := pool.queue[from].Add(tx)
|
||||||
if !inserted {
|
if !inserted {
|
||||||
|
// An older transaction was better, discard this
|
||||||
queuedDiscardCounter.Inc(1)
|
queuedDiscardCounter.Inc(1)
|
||||||
return // An older transaction was better, discard this
|
return false, ErrReplaceUnderpriced
|
||||||
}
|
}
|
||||||
// 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())
|
delete(pool.all, old.Hash())
|
||||||
|
pool.priced.Removed()
|
||||||
queuedReplaceCounter.Inc(1)
|
queuedReplaceCounter.Inc(1)
|
||||||
}
|
}
|
||||||
pool.all[hash] = tx
|
pool.all[hash] = tx
|
||||||
|
pool.priced.Put(tx)
|
||||||
|
return old != nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// promoteTx adds a transaction to the pending (processable) list of transactions.
|
// promoteTx adds a transaction to the pending (processable) list of transactions.
|
||||||
@ -371,16 +471,23 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
|||||||
if !inserted {
|
if !inserted {
|
||||||
// An older transaction was better, discard this
|
// An older transaction was better, discard this
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
|
|
||||||
pendingDiscardCounter.Inc(1)
|
pendingDiscardCounter.Inc(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 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())
|
delete(pool.all, old.Hash())
|
||||||
|
pool.priced.Removed()
|
||||||
|
|
||||||
pendingReplaceCounter.Inc(1)
|
pendingReplaceCounter.Inc(1)
|
||||||
}
|
}
|
||||||
pool.all[hash] = tx // Failsafe to work around direct pending inserts (tests)
|
// Failsafe to work around direct pending inserts (tests)
|
||||||
|
if pool.all[hash] == nil {
|
||||||
|
pool.all[hash] = 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
|
||||||
pool.beats[addr] = time.Now()
|
pool.beats[addr] = time.Now()
|
||||||
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
|
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
|
||||||
@ -392,16 +499,19 @@ func (pool *TxPool) Add(tx *types.Transaction) error {
|
|||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
if err := pool.add(tx); err != nil {
|
// Try to inject the transaction and update any state
|
||||||
|
replace, err := pool.add(tx)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := pool.currentState()
|
state, err := pool.currentState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pool.promoteExecutables(state)
|
// If we added a new transaction, run promotion checks and return
|
||||||
|
if !replace {
|
||||||
|
pool.promoteExecutables(state)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,10 +521,13 @@ func (pool *TxPool) AddBatch(txs []*types.Transaction) error {
|
|||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
// Add the batch of transaction, tracking the accepted ones
|
// Add the batch of transaction, tracking the accepted ones
|
||||||
added := 0
|
replaced, added := true, 0
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
if err := pool.add(tx); err == nil {
|
if replace, err := pool.add(tx); err == nil {
|
||||||
added++
|
added++
|
||||||
|
if !replace {
|
||||||
|
replaced = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only reprocess the internal state if something was actually added
|
// Only reprocess the internal state if something was actually added
|
||||||
@ -423,7 +536,9 @@ func (pool *TxPool) AddBatch(txs []*types.Transaction) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pool.promoteExecutables(state)
|
if !replaced {
|
||||||
|
pool.promoteExecutables(state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -467,6 +582,7 @@ func (pool *TxPool) removeTx(hash common.Hash) {
|
|||||||
|
|
||||||
// Remove it from the list of known transactions
|
// Remove it from the list of known transactions
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
|
|
||||||
// Remove the transaction from the pending lists and reset the account nonce
|
// Remove the transaction from the pending lists and reset the account nonce
|
||||||
if pending := pool.pending[addr]; pending != nil {
|
if pending := pool.pending[addr]; pending != nil {
|
||||||
@ -506,28 +622,31 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
|
|||||||
// Drop all transactions that are deemed too old (low nonce)
|
// Drop all transactions that are deemed too old (low nonce)
|
||||||
for _, tx := range list.Forward(state.GetNonce(addr)) {
|
for _, tx := range list.Forward(state.GetNonce(addr)) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Removed old queued transaction", "hash", hash)
|
log.Trace("Removed old queued transaction", "hash", hash)
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
}
|
}
|
||||||
// Drop all transactions that are too costly (low balance)
|
// Drop all transactions that are too costly (low balance)
|
||||||
drops, _ := list.Filter(state.GetBalance(addr))
|
drops, _ := list.Filter(state.GetBalance(addr))
|
||||||
for _, tx := range drops {
|
for _, tx := range drops {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Removed unpayable queued transaction", "hash", hash)
|
log.Trace("Removed unpayable queued transaction", "hash", hash)
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
queuedNofundsCounter.Inc(1)
|
queuedNofundsCounter.Inc(1)
|
||||||
}
|
}
|
||||||
// Gather all executable transactions and promote them
|
// Gather all executable transactions and promote them
|
||||||
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
|
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Promoting queued transaction", "hash", hash)
|
log.Trace("Promoting queued transaction", "hash", hash)
|
||||||
pool.promoteTx(addr, hash, tx)
|
pool.promoteTx(addr, hash, tx)
|
||||||
}
|
}
|
||||||
// Drop all transactions over the allowed limit
|
// Drop all transactions over the allowed limit
|
||||||
for _, tx := range list.Cap(int(maxQueuedPerAccount)) {
|
for _, tx := range list.Cap(int(maxQueuedPerAccount)) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Removed cap-exceeding queued transaction", "hash", hash)
|
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
queuedRLCounter.Inc(1)
|
queuedRLCounter.Inc(1)
|
||||||
}
|
}
|
||||||
queued += uint64(list.Len())
|
queued += uint64(list.Len())
|
||||||
@ -551,7 +670,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
|
|||||||
if uint64(list.Len()) > minPendingPerAccount {
|
if uint64(list.Len()) > minPendingPerAccount {
|
||||||
// Skip local accounts as pools should maintain backlogs for themselves
|
// Skip local accounts as pools should maintain backlogs for themselves
|
||||||
for _, tx := range list.txs.items {
|
for _, tx := range list.txs.items {
|
||||||
if !pool.localTx.contains(tx.Hash()) {
|
if !pool.locals.contains(tx.Hash()) {
|
||||||
spammers.Push(addr, float32(list.Len()))
|
spammers.Push(addr, float32(list.Len()))
|
||||||
}
|
}
|
||||||
break // Checking on transaction for locality is enough
|
break // Checking on transaction for locality is enough
|
||||||
@ -593,7 +712,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
|
|||||||
pendingRLCounter.Inc(int64(pendingBeforeCap - pending))
|
pendingRLCounter.Inc(int64(pendingBeforeCap - pending))
|
||||||
}
|
}
|
||||||
// If we've queued more transactions than the hard limit, drop oldest ones
|
// If we've queued more transactions than the hard limit, drop oldest ones
|
||||||
if queued > maxQueuedInTotal {
|
if queued > maxQueuedTotal {
|
||||||
// Sort all accounts with queued transactions by heartbeat
|
// Sort all accounts with queued transactions by heartbeat
|
||||||
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
|
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
|
||||||
for addr := range pool.queue {
|
for addr := range pool.queue {
|
||||||
@ -602,7 +721,7 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
|
|||||||
sort.Sort(addresses)
|
sort.Sort(addresses)
|
||||||
|
|
||||||
// Drop transactions until the total is below the limit
|
// Drop transactions until the total is below the limit
|
||||||
for drop := queued - maxQueuedInTotal; drop > 0; {
|
for drop := queued - maxQueuedTotal; drop > 0; {
|
||||||
addr := addresses[len(addresses)-1]
|
addr := addresses[len(addresses)-1]
|
||||||
list := pool.queue[addr.address]
|
list := pool.queue[addr.address]
|
||||||
|
|
||||||
@ -639,20 +758,22 @@ func (pool *TxPool) demoteUnexecutables(state *state.StateDB) {
|
|||||||
// Drop all transactions that are deemed too old (low nonce)
|
// Drop all transactions that are deemed too old (low nonce)
|
||||||
for _, tx := range list.Forward(nonce) {
|
for _, tx := range list.Forward(nonce) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Removed old pending transaction", "hash", hash)
|
log.Trace("Removed old pending transaction", "hash", hash)
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
}
|
}
|
||||||
// Drop all transactions that are too costly (low balance), and queue any invalids back for later
|
// Drop all transactions that are too costly (low balance), and queue any invalids back for later
|
||||||
drops, invalids := list.Filter(state.GetBalance(addr))
|
drops, invalids := list.Filter(state.GetBalance(addr))
|
||||||
for _, tx := range drops {
|
for _, tx := range drops {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Removed unpayable pending transaction", "hash", hash)
|
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||||
delete(pool.all, hash)
|
delete(pool.all, hash)
|
||||||
|
pool.priced.Removed()
|
||||||
pendingNofundsCounter.Inc(1)
|
pendingNofundsCounter.Inc(1)
|
||||||
}
|
}
|
||||||
for _, tx := range invalids {
|
for _, tx := range invalids {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
log.Debug("Demoting pending transaction", "hash", hash)
|
log.Trace("Demoting pending transaction", "hash", hash)
|
||||||
pool.enqueueTx(hash, tx)
|
pool.enqueueTx(hash, tx)
|
||||||
}
|
}
|
||||||
// Delete the entire queue entry if it became empty.
|
// Delete the entire queue entry if it became empty.
|
||||||
|
@ -33,7 +33,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func transaction(nonce uint64, gaslimit *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
func transaction(nonce uint64, gaslimit *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
return pricedTransaction(nonce, gaslimit, big.NewInt(1), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricedTransaction(nonce uint64, gaslimit, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key)
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,9 +155,9 @@ func TestInvalidTransactions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx = transaction(1, big.NewInt(100000), key)
|
tx = transaction(1, big.NewInt(100000), key)
|
||||||
pool.minGasPrice = big.NewInt(1000)
|
pool.gasPrice = big.NewInt(1000)
|
||||||
if err := pool.Add(tx); err != ErrCheap {
|
if err := pool.Add(tx); err != ErrUnderpriced {
|
||||||
t.Error("expected", ErrCheap, "got", err)
|
t.Error("expected", ErrUnderpriced, "got", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.SetLocal(tx)
|
pool.SetLocal(tx)
|
||||||
@ -262,14 +266,14 @@ func TestTransactionChainFork(t *testing.T) {
|
|||||||
resetState()
|
resetState()
|
||||||
|
|
||||||
tx := transaction(0, big.NewInt(100000), key)
|
tx := transaction(0, big.NewInt(100000), key)
|
||||||
if err := pool.add(tx); err != nil {
|
if _, err := pool.add(tx); err != nil {
|
||||||
t.Error("didn't expect error", err)
|
t.Error("didn't expect error", err)
|
||||||
}
|
}
|
||||||
pool.RemoveBatch([]*types.Transaction{tx})
|
pool.RemoveBatch([]*types.Transaction{tx})
|
||||||
|
|
||||||
// reset the pool's internal state
|
// reset the pool's internal state
|
||||||
resetState()
|
resetState()
|
||||||
if err := pool.add(tx); err != nil {
|
if _, err := pool.add(tx); err != nil {
|
||||||
t.Error("didn't expect error", err)
|
t.Error("didn't expect error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,11 +297,11 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
|||||||
tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), big.NewInt(1000000), big.NewInt(1), nil), signer, key)
|
tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), big.NewInt(1000000), big.NewInt(1), nil), signer, key)
|
||||||
|
|
||||||
// Add the first two transaction, ensure higher priced stays only
|
// Add the first two transaction, ensure higher priced stays only
|
||||||
if err := pool.add(tx1); err != nil {
|
if replace, err := pool.add(tx1); err != nil || replace {
|
||||||
t.Error("didn't expect error", err)
|
t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace)
|
||||||
}
|
}
|
||||||
if err := pool.add(tx2); err != nil {
|
if replace, err := pool.add(tx2); err != nil || !replace {
|
||||||
t.Error("didn't expect error", err)
|
t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace)
|
||||||
}
|
}
|
||||||
state, _ := pool.currentState()
|
state, _ := pool.currentState()
|
||||||
pool.promoteExecutables(state)
|
pool.promoteExecutables(state)
|
||||||
@ -308,9 +312,7 @@ 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())
|
||||||
}
|
}
|
||||||
// Add the thid transaction and ensure it's not saved (smaller price)
|
// Add the thid transaction and ensure it's not saved (smaller price)
|
||||||
if err := pool.add(tx3); err != nil {
|
pool.add(tx3)
|
||||||
t.Error("didn't expect error", err)
|
|
||||||
}
|
|
||||||
pool.promoteExecutables(state)
|
pool.promoteExecutables(state)
|
||||||
if pool.pending[addr].Len() != 1 {
|
if pool.pending[addr].Len() != 1 {
|
||||||
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
|
||||||
@ -330,7 +332,7 @@ func TestMissingNonce(t *testing.T) {
|
|||||||
currentState, _ := pool.currentState()
|
currentState, _ := pool.currentState()
|
||||||
currentState.AddBalance(addr, big.NewInt(100000000000000))
|
currentState.AddBalance(addr, big.NewInt(100000000000000))
|
||||||
tx := transaction(1, big.NewInt(100000), key)
|
tx := transaction(1, big.NewInt(100000), key)
|
||||||
if err := pool.add(tx); err != nil {
|
if _, err := pool.add(tx); err != nil {
|
||||||
t.Error("didn't expect error", err)
|
t.Error("didn't expect error", err)
|
||||||
}
|
}
|
||||||
if len(pool.pending) != 0 {
|
if len(pool.pending) != 0 {
|
||||||
@ -557,8 +559,8 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
|
|||||||
// some threshold, the higher transactions are dropped to prevent DOS attacks.
|
// some threshold, the higher transactions are dropped to prevent DOS attacks.
|
||||||
func TestTransactionQueueGlobalLimiting(t *testing.T) {
|
func TestTransactionQueueGlobalLimiting(t *testing.T) {
|
||||||
// Reduce the queue limits to shorten test time
|
// Reduce the queue limits to shorten test time
|
||||||
defer func(old uint64) { maxQueuedInTotal = old }(maxQueuedInTotal)
|
defer func(old uint64) { maxQueuedTotal = old }(maxQueuedTotal)
|
||||||
maxQueuedInTotal = maxQueuedPerAccount * 3
|
maxQueuedTotal = maxQueuedPerAccount * 3
|
||||||
|
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
@ -578,7 +580,7 @@ func TestTransactionQueueGlobalLimiting(t *testing.T) {
|
|||||||
// Generate and queue a batch of transactions
|
// Generate and queue a batch of transactions
|
||||||
nonces := make(map[common.Address]uint64)
|
nonces := make(map[common.Address]uint64)
|
||||||
|
|
||||||
txs := make(types.Transactions, 0, 3*maxQueuedInTotal)
|
txs := make(types.Transactions, 0, 3*maxQueuedTotal)
|
||||||
for len(txs) < cap(txs) {
|
for len(txs) < cap(txs) {
|
||||||
key := keys[rand.Intn(len(keys))]
|
key := keys[rand.Intn(len(keys))]
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
@ -596,8 +598,8 @@ func TestTransactionQueueGlobalLimiting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
queued += list.Len()
|
queued += list.Len()
|
||||||
}
|
}
|
||||||
if queued > int(maxQueuedInTotal) {
|
if queued > int(maxQueuedTotal) {
|
||||||
t.Fatalf("total transactions overflow allowance: %d > %d", queued, maxQueuedInTotal)
|
t.Fatalf("total transactions overflow allowance: %d > %d", queued, maxQueuedTotal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -791,6 +793,227 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that setting the transaction pool gas price to a higher value correctly
|
||||||
|
// discards everything cheaper than that and moves any gapped transactions back
|
||||||
|
// from the pending pool to the queue.
|
||||||
|
//
|
||||||
|
// Note, local transactions are never allowed to be dropped.
|
||||||
|
func TestTransactionPoolRepricing(t *testing.T) {
|
||||||
|
// Create the pool to test the pricing enforcement with
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ := state.New(common.Hash{}, db)
|
||||||
|
|
||||||
|
pool := NewTxPool(params.TestChainConfig, new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) })
|
||||||
|
pool.resetState()
|
||||||
|
|
||||||
|
// Create a number of test accounts and fund them
|
||||||
|
state, _ := pool.currentState()
|
||||||
|
|
||||||
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
keys[i], _ = crypto.GenerateKey()
|
||||||
|
state.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||||
|
}
|
||||||
|
// Generate and queue a batch of transactions, both pending and queued
|
||||||
|
txs := types.Transactions{}
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(2), keys[0]))
|
||||||
|
txs = append(txs, pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[0]))
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(2), keys[0]))
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(1, big.NewInt(100000), big.NewInt(2), keys[1]))
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1]))
|
||||||
|
txs = append(txs, pricedTransaction(3, big.NewInt(100000), big.NewInt(2), keys[1]))
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[2]))
|
||||||
|
pool.SetLocal(txs[len(txs)-1]) // prevent this one from ever being dropped
|
||||||
|
|
||||||
|
// Import the batch and that both pending and queued transactions match up
|
||||||
|
pool.AddBatch(txs)
|
||||||
|
|
||||||
|
pending, queued := pool.stats()
|
||||||
|
if pending != 4 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4)
|
||||||
|
}
|
||||||
|
if queued != 3 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||||
|
}
|
||||||
|
// Reprice the pool and check that underpriced transactions get dropped
|
||||||
|
pool.SetGasPrice(big.NewInt(2))
|
||||||
|
|
||||||
|
pending, queued = pool.stats()
|
||||||
|
if pending != 2 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||||
|
}
|
||||||
|
if queued != 3 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||||
|
}
|
||||||
|
// Check that we can't add the old transactions back
|
||||||
|
if err := pool.Add(pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[0])); err != ErrUnderpriced {
|
||||||
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
||||||
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||||
|
}
|
||||||
|
// However we can add local underpriced transactions
|
||||||
|
tx := pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[2])
|
||||||
|
|
||||||
|
pool.SetLocal(tx) // prevent this one from ever being dropped
|
||||||
|
if err := pool.Add(tx); err != nil {
|
||||||
|
t.Fatalf("failed to add underpriced local transaction: %v", err)
|
||||||
|
}
|
||||||
|
if pending, _ = pool.stats(); pending != 3 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that when the pool reaches its global transaction limit, underpriced
|
||||||
|
// transactions are gradually shifted out for more expensive ones and any gapped
|
||||||
|
// pending transactions are moved into te queue.
|
||||||
|
//
|
||||||
|
// Note, local transactions are never allowed to be dropped.
|
||||||
|
func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||||
|
// Reduce the queue limits to shorten test time
|
||||||
|
defer func(old uint64) { maxPendingTotal = old }(maxPendingTotal)
|
||||||
|
maxPendingTotal = 2
|
||||||
|
|
||||||
|
defer func(old uint64) { maxQueuedTotal = old }(maxQueuedTotal)
|
||||||
|
maxQueuedTotal = 2
|
||||||
|
|
||||||
|
// Create the pool to test the pricing enforcement with
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ := state.New(common.Hash{}, db)
|
||||||
|
|
||||||
|
pool := NewTxPool(params.TestChainConfig, new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) })
|
||||||
|
pool.resetState()
|
||||||
|
|
||||||
|
// Create a number of test accounts and fund them
|
||||||
|
state, _ := pool.currentState()
|
||||||
|
|
||||||
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
keys[i], _ = crypto.GenerateKey()
|
||||||
|
state.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||||
|
}
|
||||||
|
// Generate and queue a batch of transactions, both pending and queued
|
||||||
|
txs := types.Transactions{}
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[0]))
|
||||||
|
txs = append(txs, pricedTransaction(1, big.NewInt(100000), big.NewInt(2), keys[0]))
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(1, big.NewInt(100000), big.NewInt(1), keys[1]))
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[2]))
|
||||||
|
pool.SetLocal(txs[len(txs)-1]) // prevent this one from ever being dropped
|
||||||
|
|
||||||
|
// Import the batch and that both pending and queued transactions match up
|
||||||
|
pool.AddBatch(txs)
|
||||||
|
|
||||||
|
pending, queued := pool.stats()
|
||||||
|
if pending != 3 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||||
|
}
|
||||||
|
if queued != 1 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||||
|
}
|
||||||
|
// Ensure that adding an underpriced transaction on block limit fails
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
||||||
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||||
|
}
|
||||||
|
// Ensure that adding high priced transactions drops cheap ones, but not own
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(3), keys[1])); err != nil {
|
||||||
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(4), keys[1])); err != nil {
|
||||||
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(3, big.NewInt(100000), big.NewInt(5), keys[1])); err != nil {
|
||||||
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
|
}
|
||||||
|
pending, queued = pool.stats()
|
||||||
|
if pending != 2 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||||
|
}
|
||||||
|
if queued != 2 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
|
}
|
||||||
|
// Ensure that adding local transactions can push out even higher priced ones
|
||||||
|
tx := pricedTransaction(1, big.NewInt(100000), big.NewInt(0), keys[2])
|
||||||
|
|
||||||
|
pool.SetLocal(tx) // prevent this one from ever being dropped
|
||||||
|
if err := pool.Add(tx); err != nil {
|
||||||
|
t.Fatalf("failed to add underpriced local transaction: %v", err)
|
||||||
|
}
|
||||||
|
pending, queued = pool.stats()
|
||||||
|
if pending != 2 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||||
|
}
|
||||||
|
if queued != 2 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the pool rejects replacement transactions that don't meet the minimum
|
||||||
|
// price bump required.
|
||||||
|
func TestTransactionReplacement(t *testing.T) {
|
||||||
|
// Create the pool to test the pricing enforcement with
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ := state.New(common.Hash{}, db)
|
||||||
|
|
||||||
|
pool := NewTxPool(params.TestChainConfig, new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) })
|
||||||
|
pool.resetState()
|
||||||
|
|
||||||
|
// Create a a test account to add transactions with
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
|
||||||
|
state, _ := pool.currentState()
|
||||||
|
state.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||||
|
|
||||||
|
// Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||||
|
price := int64(100)
|
||||||
|
threshold := (price * (100 + minPriceBumpPercent)) / 100
|
||||||
|
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(1), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100001), big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
||||||
|
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
||||||
|
t.Fatalf("failed to replace original cheap pending transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(threshold), key)); err != ErrReplaceUnderpriced {
|
||||||
|
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(0, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
|
||||||
|
t.Fatalf("failed to replace original proper pending transaction: %v", err)
|
||||||
|
}
|
||||||
|
// Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(1), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add original queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100001), big.NewInt(1), key)); err != ErrReplaceUnderpriced {
|
||||||
|
t.Fatalf("original queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(2), key)); err != nil {
|
||||||
|
t.Fatalf("failed to replace original queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(price), key)); err != nil {
|
||||||
|
t.Fatalf("failed to add original queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100001), big.NewInt(threshold), key)); err != ErrReplaceUnderpriced {
|
||||||
|
t.Fatalf("original queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced)
|
||||||
|
}
|
||||||
|
if err := pool.Add(pricedTransaction(2, big.NewInt(100000), big.NewInt(threshold+1), key)); err != nil {
|
||||||
|
t.Fatalf("failed to replace original queued transaction: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Benchmarks the speed of validating the contents of the pending queue of the
|
// Benchmarks the speed of validating the contents of the pending queue of the
|
||||||
// transaction pool.
|
// transaction pool.
|
||||||
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
||||||
|
@ -153,6 +153,8 @@ func (api *PrivateMinerAPI) Start(threads *int) error {
|
|||||||
}
|
}
|
||||||
// Start the miner and return
|
// Start the miner and return
|
||||||
if !api.e.IsMining() {
|
if !api.e.IsMining() {
|
||||||
|
// Propagate the initial price point to the transaction pool
|
||||||
|
api.e.txPool.SetGasPrice(api.e.gasPrice)
|
||||||
return api.e.StartMining(true)
|
return api.e.StartMining(true)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -180,7 +182,7 @@ func (api *PrivateMinerAPI) SetExtra(extra string) (bool, error) {
|
|||||||
|
|
||||||
// SetGasPrice sets the minimum accepted gas price for the miner.
|
// SetGasPrice sets the minimum accepted gas price for the miner.
|
||||||
func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
|
func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
|
||||||
api.e.Miner().SetGasPrice((*big.Int)(&gasPrice))
|
api.e.txPool.SetGasPrice((*big.Int)(&gasPrice))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ package eth
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -76,6 +77,7 @@ type Ethereum struct {
|
|||||||
ApiBackend *EthApiBackend
|
ApiBackend *EthApiBackend
|
||||||
|
|
||||||
miner *miner.Miner
|
miner *miner.Miner
|
||||||
|
gasPrice *big.Int
|
||||||
Mining bool
|
Mining bool
|
||||||
MinerThreads int
|
MinerThreads int
|
||||||
etherbase common.Address
|
etherbase common.Address
|
||||||
@ -167,7 +169,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
||||||
eth.miner.SetGasPrice(config.GasPrice)
|
eth.gasPrice = config.GasPrice
|
||||||
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
||||||
|
|
||||||
eth.ApiBackend = &EthApiBackend{eth, nil}
|
eth.ApiBackend = &EthApiBackend{eth, nil}
|
||||||
|
@ -42,7 +42,7 @@ var DefaultConfig = Config{
|
|||||||
NetworkId: 1,
|
NetworkId: 1,
|
||||||
LightPeers: 20,
|
LightPeers: 20,
|
||||||
DatabaseCache: 128,
|
DatabaseCache: 128,
|
||||||
GasPrice: big.NewInt(20 * params.Shannon),
|
GasPrice: big.NewInt(18 * params.Shannon),
|
||||||
|
|
||||||
GPO: gasprice.Config{
|
GPO: gasprice.Config{
|
||||||
Blocks: 10,
|
Blocks: 10,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package ethstats
|
package ethstats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -639,7 +640,8 @@ func (s *Service) reportStats(conn *websocket.Conn) 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
|
||||||
|
|
||||||
gasprice = int(s.eth.Miner().GasPrice().Uint64())
|
price, _ := s.eth.ApiBackend.SuggestPrice(context.Background())
|
||||||
|
gasprice = int(price.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
|
||||||
|
@ -19,7 +19,6 @@ package miner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
@ -104,18 +103,6 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Miner) GasPrice() *big.Int {
|
|
||||||
return new(big.Int).Set(m.worker.gasPrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Miner) SetGasPrice(price *big.Int) {
|
|
||||||
// FIXME block tests set a nil gas price. Quick dirty fix
|
|
||||||
if price == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.worker.setGasPrice(price)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Miner) Start(coinbase common.Address) {
|
func (self *Miner) Start(coinbase common.Address) {
|
||||||
atomic.StoreInt32(&self.shouldStart, 1)
|
atomic.StoreInt32(&self.shouldStart, 1)
|
||||||
self.worker.setEtherbase(coinbase)
|
self.worker.setEtherbase(coinbase)
|
||||||
|
@ -59,14 +59,12 @@ type Work struct {
|
|||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
|
|
||||||
state *state.StateDB // apply state changes here
|
state *state.StateDB // apply state changes here
|
||||||
ancestors *set.Set // ancestor set (used for checking uncle parent validity)
|
ancestors *set.Set // ancestor set (used for checking uncle parent validity)
|
||||||
family *set.Set // family set (used for checking uncle invalidity)
|
family *set.Set // family set (used for checking uncle invalidity)
|
||||||
uncles *set.Set // uncle set
|
uncles *set.Set // uncle set
|
||||||
tcount int // tx count in cycle
|
tcount int // tx count in cycle
|
||||||
ownedAccounts *set.Set
|
failedTxs types.Transactions
|
||||||
lowGasTxs types.Transactions
|
|
||||||
failedTxs types.Transactions
|
|
||||||
|
|
||||||
Block *types.Block // the new block
|
Block *types.Block // the new block
|
||||||
|
|
||||||
@ -103,7 +101,6 @@ type worker struct {
|
|||||||
chainDb ethdb.Database
|
chainDb ethdb.Database
|
||||||
|
|
||||||
coinbase common.Address
|
coinbase common.Address
|
||||||
gasPrice *big.Int
|
|
||||||
extra []byte
|
extra []byte
|
||||||
|
|
||||||
currentMu sync.Mutex
|
currentMu sync.Mutex
|
||||||
@ -132,7 +129,6 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase com
|
|||||||
mux: mux,
|
mux: mux,
|
||||||
chainDb: eth.ChainDb(),
|
chainDb: eth.ChainDb(),
|
||||||
recv: make(chan *Result, resultQueueSize),
|
recv: make(chan *Result, resultQueueSize),
|
||||||
gasPrice: new(big.Int),
|
|
||||||
chain: eth.BlockChain(),
|
chain: eth.BlockChain(),
|
||||||
proc: eth.BlockChain().Validator(),
|
proc: eth.BlockChain().Validator(),
|
||||||
possibleUncles: make(map[common.Hash]*types.Block),
|
possibleUncles: make(map[common.Hash]*types.Block),
|
||||||
@ -252,7 +248,7 @@ func (self *worker) update() {
|
|||||||
txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
|
txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
|
||||||
txset := types.NewTransactionsByPriceAndNonce(txs)
|
txset := types.NewTransactionsByPriceAndNonce(txs)
|
||||||
|
|
||||||
self.current.commitTransactions(self.mux, txset, self.gasPrice, self.chain, self.coinbase)
|
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
|
||||||
self.currentMu.Unlock()
|
self.currentMu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,22 +371,10 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error
|
|||||||
}
|
}
|
||||||
// Keep track of transactions which return errors so they can be removed
|
// Keep track of transactions which return errors so they can be removed
|
||||||
work.tcount = 0
|
work.tcount = 0
|
||||||
work.ownedAccounts = accountAddressesSet(accounts)
|
|
||||||
self.current = work
|
self.current = work
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) setGasPrice(p *big.Int) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
// calculate the minimal gas price the miner accepts when sorting out transactions.
|
|
||||||
const pct = int64(90)
|
|
||||||
w.gasPrice = gasprice(p, pct)
|
|
||||||
|
|
||||||
w.mux.Post(core.GasPriceChanged{Price: w.gasPrice})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *worker) commitNewWork() {
|
func (self *worker) commitNewWork() {
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
defer self.mu.Unlock()
|
defer self.mu.Unlock()
|
||||||
@ -460,9 +444,8 @@ func (self *worker) commitNewWork() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
txs := types.NewTransactionsByPriceAndNonce(pending)
|
txs := types.NewTransactionsByPriceAndNonce(pending)
|
||||||
work.commitTransactions(self.mux, txs, self.gasPrice, self.chain, self.coinbase)
|
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
|
||||||
|
|
||||||
self.eth.TxPool().RemoveBatch(work.lowGasTxs)
|
|
||||||
self.eth.TxPool().RemoveBatch(work.failedTxs)
|
self.eth.TxPool().RemoveBatch(work.failedTxs)
|
||||||
|
|
||||||
// compute uncles for the new block.
|
// compute uncles for the new block.
|
||||||
@ -515,7 +498,7 @@ func (self *worker) commitUncle(work *Work, uncle *types.Header) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, gasPrice *big.Int, bc *core.BlockChain, coinbase common.Address) {
|
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
|
||||||
gp := new(core.GasPool).AddGas(env.header.GasLimit)
|
gp := new(core.GasPool).AddGas(env.header.GasLimit)
|
||||||
|
|
||||||
var coalescedLogs []*types.Log
|
var coalescedLogs []*types.Log
|
||||||
@ -539,17 +522,6 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
|
|||||||
txs.Pop()
|
txs.Pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore any transactions (and accounts subsequently) with low gas limits
|
|
||||||
if tx.GasPrice().Cmp(gasPrice) < 0 && !env.ownedAccounts.Has(from) {
|
|
||||||
// Pop the current low-priced transaction without shifting in the next from the account
|
|
||||||
log.Warn("Transaction below gas price", "sender", from, "hash", tx.Hash(), "have", tx.GasPrice(), "want", gasPrice)
|
|
||||||
|
|
||||||
env.lowGasTxs = append(env.lowGasTxs, tx)
|
|
||||||
txs.Pop()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Start executing the transaction
|
// Start executing the transaction
|
||||||
env.state.StartRecord(tx.Hash(), common.Hash{}, env.tcount)
|
env.state.StartRecord(tx.Hash(), common.Hash{}, env.tcount)
|
||||||
|
|
||||||
@ -607,25 +579,3 @@ func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, c
|
|||||||
|
|
||||||
return nil, receipt.Logs
|
return nil, receipt.Logs
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove or use
|
|
||||||
func (self *worker) HashRate() int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// gasprice calculates a reduced gas price based on the pct
|
|
||||||
// XXX Use big.Rat?
|
|
||||||
func gasprice(price *big.Int, pct int64) *big.Int {
|
|
||||||
p := new(big.Int).Set(price)
|
|
||||||
p.Div(p, big.NewInt(100))
|
|
||||||
p.Mul(p, big.NewInt(pct))
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func accountAddressesSet(accounts []accounts.Account) *set.Set {
|
|
||||||
accountSet := set.New()
|
|
||||||
for _, account := range accounts {
|
|
||||||
accountSet.Add(account.Address)
|
|
||||||
}
|
|
||||||
return accountSet
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user