From 59d394967213fcd7a63099f1a37459cf532a970b Mon Sep 17 00:00:00 2001 From: kant Date: Mon, 23 Mar 2026 10:33:52 -0700 Subject: [PATCH 1/3] disable bid options slashing in oracle service Comment out bid options unmarshaling, position constraint checking, and shutterised bid option logic in the oracle updater. Bundle atomicity slashing is preserved. --- oracle/pkg/updater/updater.go | 114 +++++++++++++++++----------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/oracle/pkg/updater/updater.go b/oracle/pkg/updater/updater.go index 33070d2be..63be90931 100644 --- a/oracle/pkg/updater/updater.go +++ b/oracle/pkg/updater/updater.go @@ -21,7 +21,7 @@ import ( "github.com/primev/mev-commit/x/contracts/txmonitor" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/errgroup" - "google.golang.org/protobuf/proto" + // "google.golang.org/protobuf/proto" ) type SettlementType string @@ -309,17 +309,17 @@ func (u *Updater) handleOpenedCommitment( revertableTxnsMap[txn] = true } - opts := new(bidderapiv1.BidOptions) - if update.BidOptions != nil { - if err := proto.Unmarshal(update.BidOptions, opts); err != nil { - u.logger.Error( - "failed to unmarshal bid options", - "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - "error", err, - ) - return err - } - } + // opts := new(bidderapiv1.BidOptions) + // if update.BidOptions != nil { + // if err := proto.Unmarshal(update.BidOptions, opts); err != nil { + // u.logger.Error( + // "failed to unmarshal bid options", + // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + // "error", err, + // ) + // return err + // } + // } // Ensure Bundle is atomic and present in the block for i := 0; i < len(commitmentTxnHashes); i++ { @@ -339,18 +339,18 @@ func (u *Updater) handleOpenedCommitment( "revertible", revertableTxnsMap[commitmentTxnHashes[i]], ) - for _, opt := range opts.Options { - if sOpt := opt.GetShutterisedBidOption(); sOpt != nil { - u.logger.Info( - "shutterised bid option present, skipping slash", - "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - "txnHash", update.TxnHash, - "blockNumber", update.BlockNumber, - "shutter option", sOpt, - ) - return nil - } - } + // for _, opt := range opts.Options { + // if sOpt := opt.GetShutterisedBidOption(); sOpt != nil { + // u.logger.Info( + // "shutterised bid option present, skipping slash", + // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + // "txnHash", update.TxnHash, + // "blockNumber", update.BlockNumber, + // "shutter option", sOpt, + // ) + // return nil + // } + // } // The committer did not include the transactions in the block // correctly, so this is a slash to be processed @@ -362,41 +362,41 @@ func (u *Updater) handleOpenedCommitment( ) } - for idx, opt := range opts.Options { - if opt.GetPositionConstraint() != nil { - if checkPositionConstraintSatisfied(opt.GetPositionConstraint(), txnDetails, txns) { - u.logger.Debug( - "positional constraint satisfied", - "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - "txnHash", update.TxnHash, - "blockNumber", update.BlockNumber, - "constraint", opt.GetPositionConstraint(), - ) - // Remove the satisfied constraint - opts.Options = append(opts.Options[:idx], opts.Options[idx+1:]...) - break - } - } - } + // for idx, opt := range opts.Options { + // if opt.GetPositionConstraint() != nil { + // if checkPositionConstraintSatisfied(opt.GetPositionConstraint(), txnDetails, txns) { + // u.logger.Debug( + // "positional constraint satisfied", + // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + // "txnHash", update.TxnHash, + // "blockNumber", update.BlockNumber, + // "constraint", opt.GetPositionConstraint(), + // ) + // // Remove the satisfied constraint + // opts.Options = append(opts.Options[:idx], opts.Options[idx+1:]...) + // break + // } + // } + // } } - if len(opts.Options) > 0 { - u.logger.Info( - "not all positional constraints satisfied", - "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - "txnHash", update.TxnHash, - "blockNumber", update.BlockNumber, - "totalPositionalConstraintsLeft", len(opts.Options), - ) - // The committer did not include the transactions in the block - // correctly, so this is a slash to be processed - return u.settle( - ctx, - update, - SettlementTypeSlash, - residualPercentage, - ) - } + // if len(opts.Options) > 0 { + // u.logger.Info( + // "not all positional constraints satisfied", + // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + // "txnHash", update.TxnHash, + // "blockNumber", update.BlockNumber, + // "totalPositionalConstraintsLeft", len(opts.Options), + // ) + // // The committer did not include the transactions in the block + // // correctly, so this is a slash to be processed + // return u.settle( + // ctx, + // update, + // SettlementTypeSlash, + // residualPercentage, + // ) + // } return u.settle( ctx, From ba478d597c44866d0ea2013f4bac98be66e375b0 Mon Sep 17 00:00:00 2001 From: kant Date: Mon, 23 Mar 2026 10:39:50 -0700 Subject: [PATCH 2/3] feat: add bid-options-slash-enabled flag to oracle (default false) Add a --bid-options-slash-enabled CLI flag (env: MEV_ORACLE_BID_OPTIONS_SLASH_ENABLED) that gates bid options slashing in the oracle updater. When disabled (default), the oracle skips position constraint checking, shutterised bid option handling, and unsatisfied constraint slashing. Bundle atomicity slashing is always active. --- oracle/cmd/main.go | 9 ++ oracle/pkg/node/node.go | 2 + oracle/pkg/updater/updater.go | 160 +++++++++++++++-------------- oracle/pkg/updater/updater_test.go | 7 ++ 4 files changed, 103 insertions(+), 75 deletions(-) diff --git a/oracle/cmd/main.go b/oracle/cmd/main.go index 6acfed2ae..019cdae3a 100644 --- a/oracle/cmd/main.go +++ b/oracle/cmd/main.go @@ -256,6 +256,13 @@ var ( "https://bloxroute.regulated.blxrbdn.com", ), }) + + optionBidOptionsSlashEnabled = altsrc.NewBoolFlag(&cli.BoolFlag{ + Name: "bid-options-slash-enabled", + Usage: "Enable slashing based on bid options (position constraints, shutterised bids)", + EnvVars: []string{"MEV_ORACLE_BID_OPTIONS_SLASH_ENABLED"}, + Value: false, + }) ) func main() { @@ -288,6 +295,7 @@ func main() { optionGasTipCap, optionGasFeeCap, optionRelayUrls, + optionBidOptionsSlashEnabled, } app := &cli.App{ Name: "mev-oracle", @@ -408,6 +416,7 @@ func launchOracleWithConfig(c *cli.Context) error { DefaultGasTipCap: gasTipCap, DefaultGasFeeCap: gasFeeCap, RelayUrls: c.StringSlice(optionRelayUrls.Name), + BidOptionsSlashEnabled: c.Bool(optionBidOptionsSlashEnabled.Name), }) if err != nil { return fmt.Errorf("failed starting node: %w", err) diff --git a/oracle/pkg/node/node.go b/oracle/pkg/node/node.go index b60531391..812b85c70 100644 --- a/oracle/pkg/node/node.go +++ b/oracle/pkg/node/node.go @@ -63,6 +63,7 @@ type Options struct { DefaultGasLimit uint64 DefaultGasTipCap *big.Int DefaultGasFeeCap *big.Int + BidOptionsSlashEnabled bool } type Node struct { @@ -269,6 +270,7 @@ func NewNode(opts *Options) (*Node, error) { evtMgr, oracleTransactorSession, txmonitor.NewEVMHelperWithLogger(rawClient, nd.logger, contracts), + opts.BidOptionsSlashEnabled, ) if err != nil { nd.logger.Error("failed to instantiate updater", "error", err) diff --git a/oracle/pkg/updater/updater.go b/oracle/pkg/updater/updater.go index 63be90931..f40099e0e 100644 --- a/oracle/pkg/updater/updater.go +++ b/oracle/pkg/updater/updater.go @@ -21,7 +21,7 @@ import ( "github.com/primev/mev-commit/x/contracts/txmonitor" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/errgroup" - // "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/proto" ) type SettlementType string @@ -84,15 +84,16 @@ type EVMClient interface { } type Updater struct { - logger *slog.Logger - l1Client EVMClient - winnerRegister WinnerRegister - oracle Oracle - evtMgr events.EventManager - l1BlockCache *lru.Cache[uint64, map[string]TxMetadata] - openedCmts chan *preconf.PreconfmanagerOpenedCommitmentStored - metrics *metrics - receiptBatcher txmonitor.BatchReceiptGetter + logger *slog.Logger + l1Client EVMClient + winnerRegister WinnerRegister + oracle Oracle + evtMgr events.EventManager + l1BlockCache *lru.Cache[uint64, map[string]TxMetadata] + openedCmts chan *preconf.PreconfmanagerOpenedCommitmentStored + metrics *metrics + receiptBatcher txmonitor.BatchReceiptGetter + bidOptionsSlashEnabled bool } func NewUpdater( @@ -102,21 +103,23 @@ func NewUpdater( evtMgr events.EventManager, oracle Oracle, receiptBatcher txmonitor.BatchReceiptGetter, + bidOptionsSlashEnabled bool, ) (*Updater, error) { l1BlockCache, err := lru.New[uint64, map[string]TxMetadata](1024) if err != nil { return nil, fmt.Errorf("failed to create L1 block cache: %w", err) } return &Updater{ - logger: logger, - l1Client: l1Client, - l1BlockCache: l1BlockCache, - winnerRegister: winnerRegister, - evtMgr: evtMgr, - oracle: oracle, - receiptBatcher: receiptBatcher, - metrics: newMetrics(), - openedCmts: make(chan *preconf.PreconfmanagerOpenedCommitmentStored), + logger: logger, + l1Client: l1Client, + l1BlockCache: l1BlockCache, + winnerRegister: winnerRegister, + evtMgr: evtMgr, + oracle: oracle, + receiptBatcher: receiptBatcher, + metrics: newMetrics(), + openedCmts: make(chan *preconf.PreconfmanagerOpenedCommitmentStored), + bidOptionsSlashEnabled: bidOptionsSlashEnabled, }, nil } @@ -309,17 +312,20 @@ func (u *Updater) handleOpenedCommitment( revertableTxnsMap[txn] = true } - // opts := new(bidderapiv1.BidOptions) - // if update.BidOptions != nil { - // if err := proto.Unmarshal(update.BidOptions, opts); err != nil { - // u.logger.Error( - // "failed to unmarshal bid options", - // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - // "error", err, - // ) - // return err - // } - // } + var opts *bidderapiv1.BidOptions + if u.bidOptionsSlashEnabled { + opts = new(bidderapiv1.BidOptions) + if update.BidOptions != nil { + if err := proto.Unmarshal(update.BidOptions, opts); err != nil { + u.logger.Error( + "failed to unmarshal bid options", + "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + "error", err, + ) + return err + } + } + } // Ensure Bundle is atomic and present in the block for i := 0; i < len(commitmentTxnHashes); i++ { @@ -339,18 +345,20 @@ func (u *Updater) handleOpenedCommitment( "revertible", revertableTxnsMap[commitmentTxnHashes[i]], ) - // for _, opt := range opts.Options { - // if sOpt := opt.GetShutterisedBidOption(); sOpt != nil { - // u.logger.Info( - // "shutterised bid option present, skipping slash", - // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - // "txnHash", update.TxnHash, - // "blockNumber", update.BlockNumber, - // "shutter option", sOpt, - // ) - // return nil - // } - // } + if u.bidOptionsSlashEnabled { + for _, opt := range opts.Options { + if sOpt := opt.GetShutterisedBidOption(); sOpt != nil { + u.logger.Info( + "shutterised bid option present, skipping slash", + "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + "txnHash", update.TxnHash, + "blockNumber", update.BlockNumber, + "shutter option", sOpt, + ) + return nil + } + } + } // The committer did not include the transactions in the block // correctly, so this is a slash to be processed @@ -362,41 +370,43 @@ func (u *Updater) handleOpenedCommitment( ) } - // for idx, opt := range opts.Options { - // if opt.GetPositionConstraint() != nil { - // if checkPositionConstraintSatisfied(opt.GetPositionConstraint(), txnDetails, txns) { - // u.logger.Debug( - // "positional constraint satisfied", - // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - // "txnHash", update.TxnHash, - // "blockNumber", update.BlockNumber, - // "constraint", opt.GetPositionConstraint(), - // ) - // // Remove the satisfied constraint - // opts.Options = append(opts.Options[:idx], opts.Options[idx+1:]...) - // break - // } - // } - // } + if u.bidOptionsSlashEnabled { + for idx, opt := range opts.Options { + if opt.GetPositionConstraint() != nil { + if checkPositionConstraintSatisfied(opt.GetPositionConstraint(), txnDetails, txns) { + u.logger.Debug( + "positional constraint satisfied", + "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + "txnHash", update.TxnHash, + "blockNumber", update.BlockNumber, + "constraint", opt.GetPositionConstraint(), + ) + // Remove the satisfied constraint + opts.Options = append(opts.Options[:idx], opts.Options[idx+1:]...) + break + } + } + } + } } - // if len(opts.Options) > 0 { - // u.logger.Info( - // "not all positional constraints satisfied", - // "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), - // "txnHash", update.TxnHash, - // "blockNumber", update.BlockNumber, - // "totalPositionalConstraintsLeft", len(opts.Options), - // ) - // // The committer did not include the transactions in the block - // // correctly, so this is a slash to be processed - // return u.settle( - // ctx, - // update, - // SettlementTypeSlash, - // residualPercentage, - // ) - // } + if u.bidOptionsSlashEnabled && len(opts.Options) > 0 { + u.logger.Info( + "not all positional constraints satisfied", + "commitmentIdx", common.Bytes2Hex(update.CommitmentIndex[:]), + "txnHash", update.TxnHash, + "blockNumber", update.BlockNumber, + "totalPositionalConstraintsLeft", len(opts.Options), + ) + // The committer did not include the transactions in the block + // correctly, so this is a slash to be processed + return u.settle( + ctx, + update, + SettlementTypeSlash, + residualPercentage, + ) + } return u.settle( ctx, diff --git a/oracle/pkg/updater/updater_test.go b/oracle/pkg/updater/updater_test.go index e161b35f9..355feabff 100644 --- a/oracle/pkg/updater/updater_test.go +++ b/oracle/pkg/updater/updater_test.go @@ -214,6 +214,7 @@ func TestUpdater(t *testing.T) { evtMgr, oracle, &testBatcher{}, + false, ) if err != nil { t.Fatal(err) @@ -423,6 +424,7 @@ func TestUpdaterRevertedTxns(t *testing.T) { evtMgr, oracle, testBatcher, + false, ) if err != nil { t.Fatal(err) @@ -632,6 +634,7 @@ func TestUpdaterRevertedTxnsWithRevertingHashes(t *testing.T) { evtMgr, oracle, testBatcher, + false, ) if err != nil { t.Fatal(err) @@ -809,6 +812,7 @@ func TestUpdaterBundlesFailure(t *testing.T) { evtMgr, oracle, &testBatcher{}, + false, ) if err != nil { t.Fatal(err) @@ -994,6 +998,7 @@ func TestUpdaterIgnoreCommitments(t *testing.T) { evtMgr, oracle, &testBatcher{}, + false, ) if err != nil { t.Fatal(err) @@ -1086,6 +1091,7 @@ func TestComputeResidualAfterDecay(t *testing.T) { nil, nil, nil, + false, ) if err != nil { // The current NewUpdater only returns error on cache creation failure, unlikely here. @@ -1629,6 +1635,7 @@ func TestBidOptions(t *testing.T) { evtMgr, oracle, &testBatcher{}, + true, ) if err != nil { t.Fatal(err) From 253acf5198e01d19f8d3f65f770bbc84184fbdf8 Mon Sep 17 00:00:00 2001 From: kant Date: Mon, 23 Mar 2026 10:41:32 -0700 Subject: [PATCH 3/3] test: cover both enabled and disabled bid options slash in TestBidOptions Split TestBidOptions into subtests for enabled/disabled flag. When disabled, all commitments settle as reward with no bid options slashing. --- oracle/pkg/updater/updater_test.go | 208 ++++++++++++++++------------- 1 file changed, 115 insertions(+), 93 deletions(-) diff --git a/oracle/pkg/updater/updater_test.go b/oracle/pkg/updater/updater_test.go index 355feabff..c524cf2c2 100644 --- a/oracle/pkg/updater/updater_test.go +++ b/oracle/pkg/updater/updater_test.go @@ -1580,18 +1580,6 @@ func TestBidOptions(t *testing.T) { commitments = append(commitments, commitment) } - register := &testWinnerRegister{ - winners: []testWinner{ - { - blockNum: 5, - winner: updater.Winner{ - Winner: builderAddr.Bytes(), - }, - }, - }, - settlements: make(chan testSettlement, 1), - } - body := &types.Body{Transactions: txns, Uncles: nil} l1Client := &testEVMClient{ @@ -1624,94 +1612,128 @@ func TestBidOptions(t *testing.T) { &pcABI, ) - oracle := &testOracle{ - commitments: make(chan processedCommitment, 1), - } + for _, tc := range []struct { + name string + bidOptionsSlashEnabled bool + }{ + {name: "enabled", bidOptionsSlashEnabled: true}, + {name: "disabled", bidOptionsSlashEnabled: false}, + } { + t.Run(tc.name, func(t *testing.T) { + register := &testWinnerRegister{ + winners: []testWinner{ + { + blockNum: 5, + winner: updater.Winner{ + Winner: builderAddr.Bytes(), + }, + }, + }, + settlements: make(chan testSettlement, 1), + } - updtr, err := updater.NewUpdater( - slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})), - l1Client, - register, - evtMgr, - oracle, - &testBatcher{}, - true, - ) - if err != nil { - t.Fatal(err) - } + oracle := &testOracle{ + commitments: make(chan processedCommitment, 1), + } - ctx, cancel := context.WithCancel(context.Background()) - done := updtr.Start(ctx) + updtr, err := updater.NewUpdater( + slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})), + l1Client, + register, + evtMgr, + oracle, + &testBatcher{}, + tc.bidOptionsSlashEnabled, + ) + if err != nil { + t.Fatal(err) + } - for idx, c := range commitments { - if err := publishOpenedCommitment(evtMgr, &pcABI, c); err != nil { - t.Fatal(err) - } + ctx, cancel := context.WithCancel(context.Background()) + done := updtr.Start(ctx) - if c.Committer.Cmp(otherBuilderAddr) == 0 { - continue - } + for idx, c := range commitments { + if err := publishOpenedCommitment(evtMgr, &pcABI, c); err != nil { + t.Fatal(err) + } - select { - case <-time.After(5 * time.Second): - t.Fatal("timeout") - case commitment := <-oracle.commitments: - if !bytes.Equal(commitment.commitmentIdx[:], c.CommitmentIndex[:]) { - t.Fatal("wrong commitment index") - } - if commitment.blockNum.Cmp(big.NewInt(5)) != 0 { - t.Fatal("wrong block number") - } - if commitment.builder != c.Committer { - t.Fatal("wrong builder") - } - if idx%2 == 0 && commitment.isSlash { - t.Fatal("wrong isSlash") - } - if (idx%2 == 1 && idx < 10) && !commitment.isSlash { - t.Fatal("wrong isSlash") - } - if commitment.residualDecay.Cmp(big.NewInt(50*updater.PRECISION)) != 0 { - t.Fatal("wrong residual decay") - } - } + if c.Committer.Cmp(otherBuilderAddr) == 0 { + continue + } - select { - case <-time.After(5 * time.Second): - t.Fatal("timeout") - case settlement := <-register.settlements: - if !bytes.Equal(settlement.commitmentIdx, c.CommitmentIndex[:]) { - t.Fatal("wrong commitment index") - } - if settlement.txHash != c.TxnHash { - t.Fatal("wrong txn hash") - } - if settlement.blockNum != 5 { - t.Fatal("wrong block number") - } - if !bytes.Equal(settlement.builder, c.Committer.Bytes()) { - t.Fatal("wrong builder") - } - if settlement.amount.Uint64() != 10 { - t.Fatal("wrong amount") - } - if idx%2 == 0 && settlement.settlementType != updater.SettlementTypeReward { - t.Fatal("wrong settlement type") - } - if (idx%2 == 1 && idx < 10) && settlement.settlementType != updater.SettlementTypeSlash { - t.Fatal("wrong settlement type") - } - if settlement.decayPercentage != 50*updater.PRECISION { - t.Fatal("wrong decay percentage") + select { + case <-time.After(5 * time.Second): + t.Fatal("timeout") + case commitment := <-oracle.commitments: + if !bytes.Equal(commitment.commitmentIdx[:], c.CommitmentIndex[:]) { + t.Fatal("wrong commitment index") + } + if commitment.blockNum.Cmp(big.NewInt(5)) != 0 { + t.Fatal("wrong block number") + } + if commitment.builder != c.Committer { + t.Fatal("wrong builder") + } + if tc.bidOptionsSlashEnabled { + if idx%2 == 0 && commitment.isSlash { + t.Fatal("wrong isSlash") + } + if (idx%2 == 1 && idx < 10) && !commitment.isSlash { + t.Fatal("wrong isSlash") + } + } else { + if commitment.isSlash { + t.Fatalf("expected no slash when bid options disabled, idx=%d", idx) + } + } + if commitment.residualDecay.Cmp(big.NewInt(50*updater.PRECISION)) != 0 { + t.Fatal("wrong residual decay") + } + } + + select { + case <-time.After(5 * time.Second): + t.Fatal("timeout") + case settlement := <-register.settlements: + if !bytes.Equal(settlement.commitmentIdx, c.CommitmentIndex[:]) { + t.Fatal("wrong commitment index") + } + if settlement.txHash != c.TxnHash { + t.Fatal("wrong txn hash") + } + if settlement.blockNum != 5 { + t.Fatal("wrong block number") + } + if !bytes.Equal(settlement.builder, c.Committer.Bytes()) { + t.Fatal("wrong builder") + } + if settlement.amount.Uint64() != 10 { + t.Fatal("wrong amount") + } + if tc.bidOptionsSlashEnabled { + if idx%2 == 0 && settlement.settlementType != updater.SettlementTypeReward { + t.Fatal("wrong settlement type") + } + if (idx%2 == 1 && idx < 10) && settlement.settlementType != updater.SettlementTypeSlash { + t.Fatal("wrong settlement type") + } + } else { + if settlement.settlementType != updater.SettlementTypeReward { + t.Fatalf("expected reward when bid options disabled, got %s, idx=%d", settlement.settlementType, idx) + } + } + if settlement.decayPercentage != 50*updater.PRECISION { + t.Fatal("wrong decay percentage") + } + } } - } - } - cancel() - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("timeout") + cancel() + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } + }) } }