Skip to content

Commit b80914e

Browse files
zlacfzyallformless
authored andcommitted
feat: add malicious behavior simulation for BEP-657 blob chaos testing. (#147)
* feat: add malicious behavior simulation for BEP-657 blob chaos testing. * feat: add malicious behavior simulation for BEP-657 blob chaos testing.
1 parent af4fd2c commit b80914e

7 files changed

Lines changed: 146 additions & 9 deletions

File tree

eth/api_miner.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,22 @@ func (api *MinerAPI) SetLastBlockMiningTime(time uint64) minerconfig.MBConfig {
152152
api.e.Miner().SetLastBlockMiningTime(time)
153153
return api.e.Miner().MBConfig()
154154
}
155+
156+
// SetForceBlobOnNonEligible sets whether to force blob txs on non-eligible blocks.
157+
// BEP-657 chaos testing: simulate malicious validator packing blobs when N % 5 != 0.
158+
func (api *MinerAPI) SetForceBlobOnNonEligible(on bool) minerconfig.MBConfig {
159+
api.e.Miner().SetForceBlobOnNonEligible(on)
160+
return api.e.Miner().MBConfig()
161+
}
162+
163+
// SetCorruptBlobSidecar sets whether to corrupt blob sidecar data during P2P broadcast.
164+
func (api *MinerAPI) SetCorruptBlobSidecar(on bool) minerconfig.MBConfig {
165+
api.e.Miner().SetCorruptBlobSidecar(on)
166+
return api.e.Miner().MBConfig()
167+
}
168+
169+
// SetDropBlobSidecar sets whether to drop blob sidecars during P2P broadcast.
170+
func (api *MinerAPI) SetDropBlobSidecar(on bool) minerconfig.MBConfig {
171+
api.e.Miner().SetDropBlobSidecar(on)
172+
return api.e.Miner().MBConfig()
173+
}

eth/backend.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
488488
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
489489
eth.miner.SetPrioAddresses(config.TxPool.Locals)
490490

491+
// Set up malicious behavior config getter for handler (blob chaos testing)
492+
eth.handler.SetMBConfigGetter(func() (corruptBlob, dropBlob bool) {
493+
mbConfig := eth.miner.MBConfig()
494+
return mbConfig.CorruptBlobSidecar, mbConfig.DropBlobSidecar
495+
})
496+
491497
// Create voteManager instance
492498
if posa, ok := eth.engine.(consensus.PoSA); ok {
493499
// Create votePool instance

eth/handler.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/ethereum/go-ethereum/core/rawdb"
4040
"github.com/ethereum/go-ethereum/core/txpool"
4141
"github.com/ethereum/go-ethereum/core/types"
42+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
4243
"github.com/ethereum/go-ethereum/eth/downloader"
4344
"github.com/ethereum/go-ethereum/eth/ethconfig"
4445
"github.com/ethereum/go-ethereum/eth/fetcher"
@@ -125,6 +126,10 @@ type votePool interface {
125126
SubscribeNewVoteEvent(ch chan<- core.NewVoteEvent) event.Subscription
126127
}
127128

129+
// MBConfigGetter is a function type for getting malicious behavior configuration.
130+
// Used by handler to check blob chaos flags during P2P operations.
131+
type MBConfigGetter func() (corruptBlob, dropBlob bool)
132+
128133
// handlerConfig is the collection of initialization parameters to create a full
129134
// node network handler.
130135
type handlerConfig struct {
@@ -158,6 +163,7 @@ type handler struct {
158163
evnNodeIdsWhitelistMap map[enode.ID]struct{}
159164
proxyedValidatorAddressMap map[common.Address]struct{}
160165
proxyedNodeIdsMap map[enode.ID]struct{}
166+
mbConfigGetter MBConfigGetter // For blob chaos testing
161167

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

213+
// SetMBConfigGetter sets the malicious behavior config getter (called after miner init).
214+
func (h *handler) SetMBConfigGetter(getter MBConfigGetter) {
215+
h.mbConfigGetter = getter
216+
}
217+
218+
// getMBConfig returns the current blob chaos config flags.
219+
func (h *handler) getMBConfig() (corruptBlob, dropBlob bool) {
220+
if h.mbConfigGetter != nil {
221+
return h.mbConfigGetter()
222+
}
223+
return false, false
224+
}
225+
226+
// processSidecarsForBroadcast processes blob sidecars based on malicious behavior config.
227+
// Returns (processed sidecars, whether modified).
228+
func (h *handler) processSidecarsForBroadcast(sidecars types.BlobSidecars) (types.BlobSidecars, bool) {
229+
corruptBlob, dropBlob := h.getMBConfig()
230+
231+
if dropBlob {
232+
log.Warn("Malicious behavior: dropping blob sidecars during P2P broadcast",
233+
"originalCount", len(sidecars))
234+
return nil, true
235+
}
236+
237+
if corruptBlob && len(sidecars) > 0 {
238+
log.Warn("Malicious behavior: corrupting blob sidecars during P2P broadcast",
239+
"count", len(sidecars))
240+
corrupted := make(types.BlobSidecars, len(sidecars))
241+
for i, sc := range sidecars {
242+
if sc != nil && len(sc.Blobs) > 0 {
243+
newSc := &types.BlobSidecar{
244+
BlobTxSidecar: types.BlobTxSidecar{
245+
Blobs: make([]kzg4844.Blob, len(sc.Blobs)),
246+
Commitments: make([]kzg4844.Commitment, len(sc.Commitments)),
247+
Proofs: make([]kzg4844.Proof, len(sc.Proofs)),
248+
},
249+
TxIndex: sc.TxIndex,
250+
TxHash: sc.TxHash,
251+
}
252+
copy(newSc.Blobs, sc.Blobs)
253+
copy(newSc.Commitments, sc.Commitments)
254+
copy(newSc.Proofs, sc.Proofs)
255+
newSc.Blobs[0][0] ^= 0xFF // Corrupt first byte
256+
corrupted[i] = newSc
257+
} else {
258+
corrupted[i] = sc
259+
}
260+
}
261+
return corrupted, true
262+
}
263+
264+
return sidecars, false
265+
}
266+
207267
// newHandler returns a handler for all Ethereum chain management protocol.
208268
func newHandler(config *handlerConfig) (*handler, error) {
209269
// Create the protocol manager with the base fields
@@ -819,6 +879,14 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
819879
return
820880
}
821881
}
882+
883+
// Process sidecars based on malicious behavior config (blob chaos testing)
884+
if len(block.Sidecars()) > 0 {
885+
if processedSidecars, modified := h.processSidecarsForBroadcast(block.Sidecars()); modified {
886+
block = block.WithSidecars(processedSidecars)
887+
}
888+
}
889+
822890
hash := block.Hash()
823891
peers := h.peers.peersWithoutBlock(hash)
824892

miner/bid_simulator.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type bidWorker interface {
7272
etherbase() common.Address
7373
getPrefetcher() core.Prefetcher
7474
fillTransactions(interruptCh chan int32, env *environment, stopTimer *time.Timer, bidTxs mapset.Set[common.Hash]) (err error)
75+
forceBlobOnNonEligible() bool
7576
}
7677

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

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

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

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

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

10741080
sc = types.NewBlobSidecarFromTx(tx)
10751081
if sc == nil {

miner/miner.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,21 @@ func (miner *Miner) SetLastBlockMiningTime(time uint64) {
259259
miner.worker.config.MB.LastBlockMiningTime = time
260260
}
261261

262+
// SetForceBlobOnNonEligible sets whether to force blob txs on non-eligible blocks (N % 5 != 0).
263+
func (miner *Miner) SetForceBlobOnNonEligible(on bool) {
264+
miner.worker.config.MB.ForceBlobOnNonEligible = on
265+
}
266+
267+
// SetCorruptBlobSidecar sets whether to corrupt blob sidecar data during P2P broadcast.
268+
func (miner *Miner) SetCorruptBlobSidecar(on bool) {
269+
miner.worker.config.MB.CorruptBlobSidecar = on
270+
}
271+
272+
// SetDropBlobSidecar sets whether to drop blob sidecars during P2P broadcast.
273+
func (miner *Miner) SetDropBlobSidecar(on bool) {
274+
miner.worker.config.MB.DropBlobSidecar = on
275+
}
276+
262277
// BuildPayload builds the payload according to the provided parameters.
263278
func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload, error) {
264279
return miner.worker.buildPayload(args, witness)

miner/minerconfig/config.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,22 @@ type MBConfig struct {
185185
BroadcastDelayBlocks uint64
186186
// Mining time (milliseconds) for the last block in every turn
187187
LastBlockMiningTime uint64
188+
189+
// BEP-657 Blob Chaos Testing fields
190+
// ForceBlobOnNonEligible: pack blob txs even when block number % 5 != 0
191+
ForceBlobOnNonEligible bool `toml:",omitempty"`
192+
// CorruptBlobSidecar: corrupt blob sidecar data during P2P broadcast
193+
CorruptBlobSidecar bool `toml:",omitempty"`
194+
// DropBlobSidecar: drop blob sidecars during P2P broadcast
195+
DropBlobSidecar bool `toml:",omitempty"`
188196
}
189197

190198
var DefaultMBConfig = MBConfig{
191-
DoubleSign: false,
192-
VoteDisable: false,
193-
BroadcastDelayBlocks: 0,
194-
LastBlockMiningTime: 0,
199+
DoubleSign: false,
200+
VoteDisable: false,
201+
BroadcastDelayBlocks: 0,
202+
LastBlockMiningTime: 0,
203+
ForceBlobOnNonEligible: false,
204+
CorruptBlobSidecar: false,
205+
DropBlobSidecar: false,
195206
}

miner/worker.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ func (w *worker) getPrefetcher() core.Prefetcher {
296296
return w.prefetcher
297297
}
298298

299+
// forceBlobOnNonEligible returns whether to force blob txs on non-eligible blocks (chaos testing).
300+
func (w *worker) forceBlobOnNonEligible() bool {
301+
return w.config.MB.ForceBlobOnNonEligible
302+
}
303+
299304
// setEtherbase sets the etherbase used to initialize the block coinbase field.
300305
func (w *worker) setEtherbase(addr common.Address) {
301306
w.confMu.Lock()
@@ -1177,7 +1182,14 @@ func (w *worker) fillTransactions(interruptCh chan int32, env *environment, stop
11771182
pendingPlainTxsTimer.UpdateSince(plainTxsStart)
11781183

11791184
var pendingBlobTxs map[common.Address][]*txpool.LazyTransaction
1180-
if env.header.Number.Uint64()%params.BlobEligibleBlockInterval == 0 {
1185+
// Check if blob txs are eligible, or if malicious behavior is enabled
1186+
isBlobEligible := env.header.Number.Uint64()%params.BlobEligibleBlockInterval == 0
1187+
forceBlobOnNonEligible := w.config.MB.ForceBlobOnNonEligible
1188+
if isBlobEligible || forceBlobOnNonEligible {
1189+
if forceBlobOnNonEligible && !isBlobEligible {
1190+
log.Warn("Malicious behavior: forcing blob txs on non-eligible block",
1191+
"blockNumber", env.header.Number.Uint64())
1192+
}
11811193
filter.BlobTxs = true
11821194
filter.BlobVersion = types.BlobSidecarVersion0
11831195

0 commit comments

Comments
 (0)