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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions eth/api_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,22 @@ func (api *MinerAPI) SetLastBlockMiningTime(time uint64) minerconfig.MBConfig {
api.e.Miner().SetLastBlockMiningTime(time)
return api.e.Miner().MBConfig()
}

// SetForceBlobOnNonEligible sets whether to force blob txs on non-eligible blocks.
// BEP-657 chaos testing: simulate malicious validator packing blobs when N % 5 != 0.
func (api *MinerAPI) SetForceBlobOnNonEligible(on bool) minerconfig.MBConfig {
api.e.Miner().SetForceBlobOnNonEligible(on)
return api.e.Miner().MBConfig()
}

// SetCorruptBlobSidecar sets whether to corrupt blob sidecar data during P2P broadcast.
func (api *MinerAPI) SetCorruptBlobSidecar(on bool) minerconfig.MBConfig {
api.e.Miner().SetCorruptBlobSidecar(on)
return api.e.Miner().MBConfig()
}

// SetDropBlobSidecar sets whether to drop blob sidecars during P2P broadcast.
func (api *MinerAPI) SetDropBlobSidecar(on bool) minerconfig.MBConfig {
api.e.Miner().SetDropBlobSidecar(on)
return api.e.Miner().MBConfig()
}
6 changes: 6 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
eth.miner.SetPrioAddresses(config.TxPool.Locals)

// Set up malicious behavior config getter for handler (blob chaos testing)
eth.handler.SetMBConfigGetter(func() (corruptBlob, dropBlob bool) {
mbConfig := eth.miner.MBConfig()
return mbConfig.CorruptBlobSidecar, mbConfig.DropBlobSidecar
})

// Create voteManager instance
if posa, ok := eth.engine.(consensus.PoSA); ok {
// Create votePool instance
Expand Down
68 changes: 68 additions & 0 deletions eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/fetcher"
Expand Down Expand Up @@ -125,6 +126,10 @@ type votePool interface {
SubscribeNewVoteEvent(ch chan<- core.NewVoteEvent) event.Subscription
}

// MBConfigGetter is a function type for getting malicious behavior configuration.
// Used by handler to check blob chaos flags during P2P operations.
type MBConfigGetter func() (corruptBlob, dropBlob bool)

// handlerConfig is the collection of initialization parameters to create a full
// node network handler.
type handlerConfig struct {
Expand Down Expand Up @@ -158,6 +163,7 @@ type handler struct {
evnNodeIdsWhitelistMap map[enode.ID]struct{}
proxyedValidatorAddressMap map[common.Address]struct{}
proxyedNodeIdsMap map[enode.ID]struct{}
mbConfigGetter MBConfigGetter // For blob chaos testing

snapSync atomic.Bool // Flag whether snap sync is enabled (gets disabled if we already have blocks)
synced atomic.Bool // Flag whether we're considered synchronised (enables transaction processing)
Expand Down Expand Up @@ -204,6 +210,60 @@ type handler struct {
handlerDoneCh chan struct{}
}

// SetMBConfigGetter sets the malicious behavior config getter (called after miner init).
func (h *handler) SetMBConfigGetter(getter MBConfigGetter) {
h.mbConfigGetter = getter
}

// getMBConfig returns the current blob chaos config flags.
func (h *handler) getMBConfig() (corruptBlob, dropBlob bool) {
if h.mbConfigGetter != nil {
return h.mbConfigGetter()
}
return false, false
}

// processSidecarsForBroadcast processes blob sidecars based on malicious behavior config.
// Returns (processed sidecars, whether modified).
func (h *handler) processSidecarsForBroadcast(sidecars types.BlobSidecars) (types.BlobSidecars, bool) {
corruptBlob, dropBlob := h.getMBConfig()

if dropBlob {
log.Warn("Malicious behavior: dropping blob sidecars during P2P broadcast",
"originalCount", len(sidecars))
return nil, true
}

if corruptBlob && len(sidecars) > 0 {
log.Warn("Malicious behavior: corrupting blob sidecars during P2P broadcast",
"count", len(sidecars))
corrupted := make(types.BlobSidecars, len(sidecars))
for i, sc := range sidecars {
if sc != nil && len(sc.Blobs) > 0 {
newSc := &types.BlobSidecar{
BlobTxSidecar: types.BlobTxSidecar{
Blobs: make([]kzg4844.Blob, len(sc.Blobs)),
Commitments: make([]kzg4844.Commitment, len(sc.Commitments)),
Proofs: make([]kzg4844.Proof, len(sc.Proofs)),
},
TxIndex: sc.TxIndex,
TxHash: sc.TxHash,
}
copy(newSc.Blobs, sc.Blobs)
copy(newSc.Commitments, sc.Commitments)
copy(newSc.Proofs, sc.Proofs)
newSc.Blobs[0][0] ^= 0xFF // Corrupt first byte
corrupted[i] = newSc
} else {
corrupted[i] = sc
}
}
return corrupted, true
}

return sidecars, false
}

// newHandler returns a handler for all Ethereum chain management protocol.
func newHandler(config *handlerConfig) (*handler, error) {
// Create the protocol manager with the base fields
Expand Down Expand Up @@ -819,6 +879,14 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
return
}
}

// Process sidecars based on malicious behavior config (blob chaos testing)
if len(block.Sidecars()) > 0 {
if processedSidecars, modified := h.processSidecarsForBroadcast(block.Sidecars()); modified {
block = block.WithSidecars(processedSidecars)
}
}

hash := block.Hash()
peers := h.peers.peersWithoutBlock(hash)

Expand Down
14 changes: 10 additions & 4 deletions miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type bidWorker interface {
etherbase() common.Address
getPrefetcher() core.Prefetcher
fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash]) (err error)
forceBlobOnNonEligible() bool
}

// simBidReq is the request for simulating a bid
Expand Down Expand Up @@ -836,7 +837,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
break
}

err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx, bidRuntime.bid.UnRevertible.Contains(tx.Hash()))
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, tx, bidRuntime.bid.UnRevertible.Contains(tx.Hash()), b.bidWorker.forceBlobOnNonEligible())
if err != nil {
log.Error("BidSimulator: failed to commit tx", "bidHash", bidRuntime.bid.Hash(), "tx", tx.Hash(), "err", err)
err = fmt.Errorf("invalid tx in bid, %v", err)
Expand Down Expand Up @@ -927,7 +928,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {

// commit payBidTx at the end of the block
bidRuntime.env.gasPool.AddGas(params.PayBidTxGasLimit)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx, true)
err = bidRuntime.commitTransaction(b.chain, b.chainConfig, payBidTx, true, false) // payBidTx is not a blob tx
if err != nil {
log.Error("BidSimulator: failed to commit tx", "builder", bidRuntime.bid.Builder,
"bidHash", bidRuntime.bid.Hash(), "tx", payBidTx.Hash(), "err", err)
Expand Down Expand Up @@ -1051,7 +1052,7 @@ func (r *BidRuntime) packReward(validatorCommission uint64) {
r.packedValidatorReward.Sub(r.packedValidatorReward, r.bid.BuilderFee)
}

func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction, unRevertible bool) error {
func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, tx *types.Transaction, unRevertible bool, forceBlobOnNonEligible bool) error {
var (
env = r.env
sc *types.BlobSidecar
Expand All @@ -1067,9 +1068,14 @@ func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *para
}

if tx.Type() == types.BlobTxType {
if !eip4844.IsBlobEligibleBlock(chainConfig, r.env.header.Number.Uint64(), r.env.header.Time) {
isBlobEligible := eip4844.IsBlobEligibleBlock(chainConfig, r.env.header.Number.Uint64(), r.env.header.Time)
if !isBlobEligible && !forceBlobOnNonEligible {
return fmt.Errorf("blob transactions not allowed in block %d (N %% %d != 0)", r.env.header.Number.Uint64(), params.BlobEligibleBlockInterval)
}
if forceBlobOnNonEligible && !isBlobEligible {
log.Warn("Malicious behavior: forcing blob tx in bid on non-eligible block",
"blockNumber", r.env.header.Number.Uint64(), "txHash", tx.Hash())
}

sc = types.NewBlobSidecarFromTx(tx)
if sc == nil {
Expand Down
15 changes: 15 additions & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,21 @@ func (miner *Miner) SetLastBlockMiningTime(time uint64) {
miner.worker.config.MB.LastBlockMiningTime = time
}

// SetForceBlobOnNonEligible sets whether to force blob txs on non-eligible blocks (N % 5 != 0).
func (miner *Miner) SetForceBlobOnNonEligible(on bool) {
miner.worker.config.MB.ForceBlobOnNonEligible = on
}

// SetCorruptBlobSidecar sets whether to corrupt blob sidecar data during P2P broadcast.
func (miner *Miner) SetCorruptBlobSidecar(on bool) {
miner.worker.config.MB.CorruptBlobSidecar = on
}

// SetDropBlobSidecar sets whether to drop blob sidecars during P2P broadcast.
func (miner *Miner) SetDropBlobSidecar(on bool) {
miner.worker.config.MB.DropBlobSidecar = on
}

// BuildPayload builds the payload according to the provided parameters.
func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload, error) {
return miner.worker.buildPayload(args, witness)
Expand Down
19 changes: 15 additions & 4 deletions miner/minerconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,22 @@ type MBConfig struct {
BroadcastDelayBlocks uint64
// Mining time (milliseconds) for the last block in every turn
LastBlockMiningTime uint64

// BEP-657 Blob Chaos Testing fields
// ForceBlobOnNonEligible: pack blob txs even when block number % 5 != 0
ForceBlobOnNonEligible bool `toml:",omitempty"`
// CorruptBlobSidecar: corrupt blob sidecar data during P2P broadcast
CorruptBlobSidecar bool `toml:",omitempty"`
// DropBlobSidecar: drop blob sidecars during P2P broadcast
DropBlobSidecar bool `toml:",omitempty"`
}

var DefaultMBConfig = MBConfig{
DoubleSign: false,
VoteDisable: false,
BroadcastDelayBlocks: 0,
LastBlockMiningTime: 0,
DoubleSign: false,
VoteDisable: false,
BroadcastDelayBlocks: 0,
LastBlockMiningTime: 0,
ForceBlobOnNonEligible: false,
CorruptBlobSidecar: false,
DropBlobSidecar: false,
}
14 changes: 13 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ func (w *worker) getPrefetcher() core.Prefetcher {
return w.prefetcher
}

// forceBlobOnNonEligible returns whether to force blob txs on non-eligible blocks (chaos testing).
func (w *worker) forceBlobOnNonEligible() bool {
return w.config.MB.ForceBlobOnNonEligible
}

// setEtherbase sets the etherbase used to initialize the block coinbase field.
func (w *worker) setEtherbase(addr common.Address) {
w.confMu.Lock()
Expand Down Expand Up @@ -1176,7 +1181,14 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
pendingPlainTxsTimer.UpdateSince(plainTxsStart)

var pendingBlobTxs map[common.Address][]*txpool.LazyTransaction
if eip4844.IsBlobEligibleBlock(w.chainConfig, env.header.Number.Uint64(), env.header.Time) {
// Check if blob txs are eligible, or if malicious behavior is enabled
isBlobEligible := eip4844.IsBlobEligibleBlock(w.chainConfig, env.header.Number.Uint64(), env.header.Time)
forceBlobOnNonEligible := w.config.MB.ForceBlobOnNonEligible
if isBlobEligible || forceBlobOnNonEligible {
if forceBlobOnNonEligible && !isBlobEligible {
log.Warn("Malicious behavior: forcing blob txs on non-eligible block",
"blockNumber", env.header.Number.Uint64())
}
filter.BlobTxs = true
filter.BlobVersion = types.BlobSidecarVersion0

Expand Down