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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions eth/api_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -141,3 +142,15 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) {
}
return true, nil
}

func (api *AdminAPI) SetFeeCurrencyBlocklistStatus(enabled bool) {
api.eth.Miner().SetFeeCurrencyBlocklistStatus(enabled)
}

func (api *AdminAPI) UnblockTransaction(hash common.Hash) bool {
return api.eth.Miner().UnblockTransaction(hash)
}

func (api *AdminAPI) UnblockFeeCurrency(address common.Address) bool {
return api.eth.Miner().UnblockFeeCurrency(address)
}
26 changes: 24 additions & 2 deletions miner/currency_blocklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package miner

import (
"sync"
"sync/atomic"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -18,21 +19,40 @@ type AddressBlocklist struct {
// will get evicted when evict() is called
headerEvictionTimeoutSeconds uint64
oldestHeader *types.Header

enactFilterEnabled *atomic.Bool
}

func NewAddressBlocklist() *AddressBlocklist {
return &AddressBlocklist{
bl := &AddressBlocklist{
mux: &sync.RWMutex{},
currencies: map[common.Address]*types.Header{},
headerEvictionTimeoutSeconds: EvictionTimeoutSeconds,
oldestHeader: nil,
enactFilterEnabled: &atomic.Bool{},
}
bl.enactFilterEnabled.Store(true)
return bl
}

func (b *AddressBlocklist) GetEnableFilterStatus() bool {
return b.enactFilterEnabled.Load()
}

// This only activates / deactivates wether the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
// This only activates / deactivates wether the
// SetEnableFilterStatus only activates / deactivates whether the

// allowlist is filtered by the blocklist or not.
func (b *AddressBlocklist) SetEnableFilterStatus(enabled bool) {
b.enactFilterEnabled.Store(enabled)
}

func (b *AddressBlocklist) FilterAllowlist(allowlist common.AddressSet, latest *types.Header) common.AddressSet {
b.mux.RLock()
defer b.mux.RUnlock()

if !b.enactFilterEnabled.Load() {
return allowlist
}

filtered := common.AddressSet{}
for a := range allowlist {
if !b.isBlocked(a, latest) {
Expand Down Expand Up @@ -64,14 +84,16 @@ func (b *AddressBlocklist) Remove(currency common.Address) bool {
return ok
}

func (b *AddressBlocklist) Add(currency common.Address, head types.Header) {
func (b *AddressBlocklist) Add(currency common.Address, head types.Header) bool {
b.mux.Lock()
defer b.mux.Unlock()

_, existed := b.currencies[currency]
if b.oldestHeader == nil || b.oldestHeader.Time > head.Time {
b.oldestHeader = &head
}
b.currencies[currency] = &head
return !existed
}

func (b *AddressBlocklist) Evict(latest *types.Header) []common.Address {
Expand Down
22 changes: 22 additions & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type Miner struct {
lifeCtxCancel context.CancelFunc
lifeCtx context.Context

blockedTxRingbuffer *TxRingBuffer
feeCurrencyBlocklist *AddressBlocklist
}

Expand All @@ -121,6 +122,7 @@ func New(eth Backend, config Config, engine consensus.Engine) *Miner {
lifeCtxCancel: cancel,
lifeCtx: ctx,

blockedTxRingbuffer: NewTxRingBuffer(100000),
feeCurrencyBlocklist: NewAddressBlocklist(),
}
}
Expand Down Expand Up @@ -231,6 +233,26 @@ func (miner *Miner) getPending() *newPayloadResult {
return ret
}

func (miner *Miner) SetFeeCurrencyBlocklistStatus(enabled bool) {
miner.feeCurrencyBlocklist.SetEnableFilterStatus(enabled)
}
Comment thread
piersy marked this conversation as resolved.

func (miner *Miner) UnblockFeeCurrency(address common.Address) bool {
removed := miner.feeCurrencyBlocklist.Remove(address)
if removed {
feeCurrenciesInBlocklistCounter.Dec(1)
}
return removed
}

func (miner *Miner) UnblockTransaction(hash common.Hash) bool {
removed := miner.blockedTxRingbuffer.Remove(hash)
if removed {
transactionsInBlocklistCounter.Dec(1)
}
return removed
}

func (miner *Miner) Close() {
miner.lifeCtxCancel()
}
70 changes: 70 additions & 0 deletions miner/tx_ringbuffer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package miner

import (
"sync"

"github.com/ethereum/go-ethereum/common"
)

// TxRingBuffer is a ring buffer of transaction hashes, it has one method to add
// transaction hashes and another method to check if a transaction hash is in the buffer.
type TxRingBuffer struct {
ring []common.Hash
m map[common.Hash]struct{}
index int
mux *sync.RWMutex
}

// NewTxRingBuffer creates a new transaction ring buffer with the given size.
func NewTxRingBuffer(size int) *TxRingBuffer {
return &TxRingBuffer{
ring: make([]common.Hash, size),
m: make(map[common.Hash]struct{}),
mux: &sync.RWMutex{},
}
}

// Add adds a new transaction hash to the ring buffer, if the buffer is
// at capacity, the oldest transaction hash is overwritten.
func (b *TxRingBuffer) Add(tx common.Hash) bool {
b.mux.Lock()
defer b.mux.Unlock()

if _, exists := b.m[tx]; exists {
return false
}

// Clean up old transaction hash from map
oldHash := b.ring[b.index]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

wonder what happens when the ring is not yet full... this returns zero and delete(zero) doesn't break?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It will be the empty hash, and delete the empty hash which will be a no-op.

delete(b.m, oldHash)

// Add new transaction hash to ring and map
b.ring[b.index] = tx
b.m[tx] = struct{}{}

// Increment the index
b.index++
if b.index == len(b.ring) {
b.index = 0
}
return true
}

func (b *TxRingBuffer) Has(tx common.Hash) bool {
b.mux.RLock()
defer b.mux.RUnlock()

_, exists := b.m[tx]
return exists
}

func (b *TxRingBuffer) Remove(tx common.Hash) bool {
b.mux.Lock()
defer b.mux.Unlock()

if _, exists := b.m[tx]; !exists {
return false
}
delete(b.m, tx)
Comment thread
seolaoh marked this conversation as resolved.
return true
}
32 changes: 27 additions & 5 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ var (
txConditionalMinedTimer = metrics.NewRegisteredTimer("miner/transactionConditional/elapsedtime", nil)

txInteropRejectedCounter = metrics.NewRegisteredCounter("miner/transactionInterop/rejected", nil)

feeCurrenciesInBlocklistCounter = metrics.NewRegisteredCounter("miner/blocklist/feeCurrency/blocked", nil)
transactionsInBlocklistCounter = metrics.NewRegisteredCounter("miner/blocklist/transactions/blocked", nil)
)

// environment is the worker's current environment and holds all
Expand All @@ -87,6 +90,7 @@ type environment struct {
// Celo specific
multiGasPool *core.MultiGasPool // available per-fee-currency gas used to pack transactions
feeCurrencyAllowlist common.AddressSet
blockingAllowed bool
feeCurrencyContext *common.FeeCurrencyContext
}

Expand Down Expand Up @@ -340,11 +344,13 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
"evicted-fee-currencies", evicted,
"eviction-timeout-seconds", EvictionTimeoutSeconds,
)
feeCurrenciesInBlocklistCounter.Dec(int64(len(evicted)))
}
env.feeCurrencyAllowlist = miner.feeCurrencyBlocklist.FilterAllowlist(
common.CurrencyAllowlist(env.feeCurrencyContext.ExchangeRates),
header,
)
env.blockingAllowed = miner.feeCurrencyBlocklist.GetEnableFilterStatus()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

is this done for every block?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yep, env is constructed per block at line 331

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes, every time a new payload is requested to be built by the engine-api a new environment is created


if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)
Expand Down Expand Up @@ -418,13 +424,14 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e
receipt, err := miner.applyTransaction(env, tx)
if err != nil {
if errors.Is(err, contracts.ErrFeeCurrencyEVMCall) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change

log.Warn(
"fee-currency EVM execution error, temporarily blocking fee-currency in local txpools",
"tx-hash", tx.Hash(),
"fee-currency", tx.FeeCurrency(),
"error", err.Error(),
)
miner.blockFeeCurrency(env, *tx.FeeCurrency(), err)
miner.registerFeeCurrencyTxFailure(env, tx, err)
}
return err
}
Expand Down Expand Up @@ -596,6 +603,12 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
continue
}
}
if miner.blockedTxRingbuffer.Has(ltx.Hash) {
log.Trace("Skipping tx execution, tx in blockedTxRingbuffer", "hash", ltx.Hash)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

trace? maybe some level we had for skipping a tx due to fee currency lock

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This would be logged on every block build attempt, so every 1 second

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is the same level we had for skipping a tx due to the fee currency lock, see line 597

txs.Pop()
continue
}

// If we don't have enough space for the next transaction, skip the account.
if env.gasPool.Gas() < ltx.Gas {
log.Trace("Not enough gas left for transaction", "hash", ltx.Hash, "left", env.gasPool.Gas(), "needed", ltx.Gas)
Expand Down Expand Up @@ -834,14 +847,23 @@ func (miner *Miner) validateParams(genParams *generateParams) (time.Duration, er
return time.Duration(blockTime) * time.Second, nil
}

func (miner *Miner) blockFeeCurrency(env *environment, feeCurrency common.Address, err error) {
func (miner *Miner) registerFeeCurrencyTxFailure(env *environment, tx *types.Transaction, err error) {
// the fee-currency is still in the allowlist of this environment,
// so set the fee-currency block gas limit to 0 to prevent other
// transactions.
pool := env.multiGasPool.PoolFor(&feeCurrency)
pool.SetGas(0)
if env.blockingAllowed {
Comment thread
seolaoh marked this conversation as resolved.
pool := env.multiGasPool.PoolFor(tx.FeeCurrency())
pool.SetGas(0)
}
// also add the fee-currency to a worker-wide blocklist,
// so that they are not allowlisted in the following blocks
// (only locally in the txpool, not consensus-critical)
miner.feeCurrencyBlocklist.Add(feeCurrency, *env.header)
if miner.feeCurrencyBlocklist.Add(*tx.FeeCurrency(), *env.header) {
feeCurrenciesInBlocklistCounter.Inc(1)
}

// Add tx to block tx ringbuffer
if miner.blockedTxRingbuffer.Add(tx.Hash()) {
transactionsInBlocklistCounter.Inc(1)
}
}