@@ -22,10 +22,49 @@ const (
2222 popTx = 2
2323)
2424
25+ // defaultProfitPercentMinimum is to ensure committed transactions, bundles, sbundles don't fall below this threshold
26+ // when profit is enforced
27+ const defaultProfitPercentMinimum = 70
28+
29+ var (
30+ defaultProfitThreshold = big .NewInt (defaultProfitPercentMinimum )
31+ defaultAlgorithmConfig = algorithmConfig {
32+ EnforceProfit : false ,
33+ ExpectedProfit : common .Big0 ,
34+ ProfitThresholdPercent : defaultProfitThreshold ,
35+ }
36+ )
37+
2538var emptyCodeHash = common .HexToHash ("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" )
2639
2740var errInterrupt = errors .New ("miner worker interrupted" )
2841
42+ // lowProfitError is returned when an order is not committed due to low profit or low effective gas price
43+ type lowProfitError struct {
44+ ExpectedProfit * big.Int
45+ ActualProfit * big.Int
46+
47+ ExpectedEffectiveGasPrice * big.Int
48+ ActualEffectiveGasPrice * big.Int
49+ }
50+
51+ func (e * lowProfitError ) Error () string {
52+ return fmt .Sprintf (
53+ "low profit: expected %v, actual %v, expected effective gas price %v, actual effective gas price %v" ,
54+ e .ExpectedProfit , e .ActualProfit , e .ExpectedEffectiveGasPrice , e .ActualEffectiveGasPrice ,
55+ )
56+ }
57+
58+ type algorithmConfig struct {
59+ // EnforceProfit is true if we want to enforce a minimum profit threshold
60+ // for committing a transaction based on ProfitThresholdPercent
61+ EnforceProfit bool
62+ // ExpectedProfit should be set on a per transaction basis when profit is enforced
63+ ExpectedProfit * big.Int
64+ // ProfitThresholdPercent is the minimum profit threshold for committing a transaction
65+ ProfitThresholdPercent * big.Int
66+ }
67+
2968type chainData struct {
3069 chainConfig * params.ChainConfig
3170 chain * core.BlockChain
@@ -156,49 +195,51 @@ func (envDiff *environmentDiff) commitTx(tx *types.Transaction, chData chainData
156195
157196 receipt , newState , err := applyTransactionWithBlacklist (signer , chData .chainConfig , chData .chain , coinbase ,
158197 envDiff .gasPool , envDiff .state , header , tx , & header .GasUsed , * chData .chain .GetVMConfig (), chData .blacklist )
198+
159199 envDiff .state = newState
160200 if err != nil {
161201 switch {
162202 case errors .Is (err , core .ErrGasLimitReached ):
163203 // Pop the current out-of-gas transaction without shifting in the next from the account
164204 from , _ := types .Sender (signer , tx )
165205 log .Trace ("Gas limit exceeded for current block" , "sender" , from )
166- return nil , popTx , err
206+ return receipt , popTx , err
167207
168208 case errors .Is (err , core .ErrNonceTooLow ):
169209 // New head notification data race between the transaction pool and miner, shift
170210 from , _ := types .Sender (signer , tx )
171211 log .Trace ("Skipping transaction with low nonce" , "sender" , from , "nonce" , tx .Nonce ())
172- return nil , shiftTx , err
212+ return receipt , shiftTx , err
173213
174214 case errors .Is (err , core .ErrNonceTooHigh ):
175215 // Reorg notification data race between the transaction pool and miner, skip account =
176216 from , _ := types .Sender (signer , tx )
177217 log .Trace ("Skipping account with hight nonce" , "sender" , from , "nonce" , tx .Nonce ())
178- return nil , popTx , err
218+ return receipt , popTx , err
179219
180220 case errors .Is (err , core .ErrTxTypeNotSupported ):
181221 // Pop the unsupported transaction without shifting in the next from the account
182222 from , _ := types .Sender (signer , tx )
183223 log .Trace ("Skipping unsupported transaction type" , "sender" , from , "type" , tx .Type ())
184- return nil , popTx , err
224+ return receipt , popTx , err
185225
186226 default :
187227 // Strange error, discard the transaction and get the next in line (note, the
188228 // nonce-too-high clause will prevent us from executing in vain).
189229 log .Trace ("Transaction failed, account skipped" , "hash" , tx .Hash (), "err" , err )
190- return nil , shiftTx , err
230+ return receipt , shiftTx , err
191231 }
192232 }
193233
194234 envDiff .newProfit = envDiff .newProfit .Add (envDiff .newProfit , gasPrice .Mul (gasPrice , big .NewInt (int64 (receipt .GasUsed ))))
195235 envDiff .newTxs = append (envDiff .newTxs , tx )
196236 envDiff .newReceipts = append (envDiff .newReceipts , receipt )
237+
197238 return receipt , shiftTx , nil
198239}
199240
200241// Commit Bundle to env diff
201- func (envDiff * environmentDiff ) commitBundle (bundle * types.SimulatedBundle , chData chainData , interrupt * int32 ) error {
242+ func (envDiff * environmentDiff ) commitBundle (bundle * types.SimulatedBundle , chData chainData , interrupt * int32 , algoConf algorithmConfig ) error {
202243 coinbase := envDiff .baseEnvironment .coinbase
203244 tmpEnvDiff := envDiff .copy ()
204245
@@ -208,7 +249,7 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
208249 var gasUsed uint64
209250
210251 for _ , tx := range bundle .OriginalBundle .Txs {
211- if tmpEnvDiff .header .BaseFee != nil && tx .Type () == 2 {
252+ if tmpEnvDiff .header .BaseFee != nil && tx .Type () == types . DynamicFeeTxType {
212253 // Sanity check for extremely large numbers
213254 if tx .GasFeeCap ().BitLen () > 256 {
214255 return core .ErrFeeCapVeryHigh
@@ -264,12 +305,34 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
264305 bundleSimEffGP := new (big.Int ).Set (bundle .MevGasPrice )
265306
266307 // allow >-1% divergence
267- bundleActualEffGP . Mul (bundleActualEffGP , big . NewInt ( 100 ))
268- bundleSimEffGP . Mul (bundleSimEffGP , big .NewInt (99 ))
308+ actualEGP := new (big. Int ). Mul (bundleActualEffGP , common . Big100 ) // bundle actual effective gas price * 100
309+ simulatedEGP := new (big. Int ). Mul (bundleSimEffGP , big .NewInt (99 )) // bundle simulated effective gas price * 99
269310
270- if bundleSimEffGP .Cmp (bundleActualEffGP ) == 1 {
311+ if simulatedEGP .Cmp (actualEGP ) > 0 {
271312 log .Trace ("Bundle underpays after inclusion" , "bundle" , bundle .OriginalBundle .Hash )
272- return errors .New ("bundle underpays" )
313+ return & lowProfitError {
314+ ExpectedEffectiveGasPrice : bundleSimEffGP ,
315+ ActualEffectiveGasPrice : bundleActualEffGP ,
316+ }
317+ }
318+
319+ if algoConf .EnforceProfit {
320+ // if profit is enforced between simulation and actual commit, only allow ProfitThresholdPercent divergence
321+ simulatedBundleProfit := new (big.Int ).Set (bundle .TotalEth )
322+ actualBundleProfit := new (big.Int ).Mul (bundleActualEffGP , big .NewInt (int64 (gasUsed )))
323+
324+ // We want to make simulated profit smaller to allow for some leeway in cases where the actual profit is
325+ // lower due to transaction ordering
326+ simulatedProfitMultiple := new (big.Int ).Mul (simulatedBundleProfit , algoConf .ProfitThresholdPercent )
327+ actualProfitMultiple := new (big.Int ).Mul (actualBundleProfit , common .Big100 )
328+
329+ if simulatedProfitMultiple .Cmp (actualProfitMultiple ) > 0 {
330+ log .Trace ("Lower bundle profit found after inclusion" , "bundle" , bundle .OriginalBundle .Hash )
331+ return & lowProfitError {
332+ ExpectedProfit : simulatedBundleProfit ,
333+ ActualProfit : actualBundleProfit ,
334+ }
335+ }
273336 }
274337
275338 * envDiff = * tmpEnvDiff
@@ -395,7 +458,7 @@ func (envDiff *environmentDiff) commitPayoutTx(amount *big.Int, sender, receiver
395458 return receipt , nil
396459}
397460
398- func (envDiff * environmentDiff ) commitSBundle (b * types.SimSBundle , chData chainData , interrupt * int32 , key * ecdsa.PrivateKey ) error {
461+ func (envDiff * environmentDiff ) commitSBundle (b * types.SimSBundle , chData chainData , interrupt * int32 , key * ecdsa.PrivateKey , algoConf algorithmConfig ) error {
399462 if key == nil {
400463 return errors .New ("no private key provided" )
401464 }
@@ -423,11 +486,33 @@ func (envDiff *environmentDiff) commitSBundle(b *types.SimSBundle, chData chainD
423486 simEGP := new (big.Int ).Set (b .MevGasPrice )
424487
425488 // allow > 1% difference
426- gotEGP = gotEGP .Mul (gotEGP , big .NewInt (101 ))
427- simEGP = simEGP .Mul (simEGP , common .Big100 )
489+ actualEGP := new (big. Int ) .Mul (gotEGP , big .NewInt (101 ))
490+ simulatedEGP := new (big. Int ) .Mul (simEGP , common .Big100 )
428491
429- if gotEGP .Cmp (simEGP ) < 0 {
430- return fmt .Errorf ("incorrect EGP: got %d, expected %d" , gotEGP , simEGP )
492+ if simulatedEGP .Cmp (actualEGP ) > 0 {
493+ return & lowProfitError {
494+ ExpectedEffectiveGasPrice : simEGP ,
495+ ActualEffectiveGasPrice : gotEGP ,
496+ }
497+ }
498+
499+ if algoConf .EnforceProfit {
500+ // if profit is enforced between simulation and actual commit, only allow >-1% divergence
501+ simulatedSbundleProfit := new (big.Int ).Set (b .Profit )
502+ actualSbundleProfit := new (big.Int ).Set (coinbaseDelta )
503+
504+ // We want to make simulated profit smaller to allow for some leeway in cases where the actual profit is
505+ // lower due to transaction ordering
506+ simulatedProfitMultiple := new (big.Int ).Mul (simulatedSbundleProfit , algoConf .ProfitThresholdPercent )
507+ actualProfitMultiple := new (big.Int ).Mul (actualSbundleProfit , common .Big100 )
508+
509+ if simulatedProfitMultiple .Cmp (actualProfitMultiple ) > 0 {
510+ log .Trace ("Lower sbundle profit found after inclusion" , "sbundle" , b .Bundle .Hash ())
511+ return & lowProfitError {
512+ ExpectedProfit : simulatedSbundleProfit ,
513+ ActualProfit : actualSbundleProfit ,
514+ }
515+ }
431516 }
432517
433518 * envDiff = * tmpEnvDiff
0 commit comments