+
diff --git flashbots/builder/miner/worker.go chainbound/bolt/miner/worker.go
+index 09d46ed99f9f600550d979c31b582201ab4eef0a..c845edbddbcf5e1ea57a5485b0a0225db30a738a 100644
+--- flashbots/builder/miner/worker.go
++++ chainbound/bolt/miner/worker.go
+@@ -25,6 +25,7 @@ "sync"
+ "sync/atomic"
+ "time"
+
++ "github.com/chainbound/shardmap"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
+@@ -644,7 +645,7 @@ plainTxs := newTransactionsByPriceAndNonce(w.current.signer, txs, nil, nil, w.current.header.BaseFee) // Mixed bag of everrything, yolo
+ blobTxs := newTransactionsByPriceAndNonce(w.current.signer, nil, nil, nil, w.current.header.BaseFee) // Empty bag, don't bother optimising
+
+ tcount := w.current.tcount
+- w.commitTransactions(w.current, plainTxs, blobTxs, nil)
++ w.commitTransactions(w.current, plainTxs, blobTxs, nil, nil)
+
+ // Only update the snapshot if any new transactions were added
+ // to the pending block
+@@ -1017,14 +1018,30 @@
+ return nil
+ }
+
+-func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
++// commitTransactions applies sorted transactions to the current environment, updating the state
++// and creating the resulting block
++//
++// Assumptions:
++// - there are no nonce-conflicting transactions between `plainTxs`, `blobTxs` and the constraints
++// - all transaction are correctly signed
++func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, constraints types.HashToConstraintDecoded, interrupt *atomic.Int32) error {
+ gasLimit := env.header.GasLimit
+ if env.gasPool == nil {
+ env.gasPool = new(core.GasPool).AddGas(gasLimit)
+ }
+ var coalescedLogs []*types.Log
+
++ // Here we initialize and track the constraints left to be executed along
++ // with their gas requirements
++ constraintsOrderedByIndex,
++ constraintsWithoutIndex,
++ constraintsTotalGasLeft,
++ constraintsTotalBlobGasLeft := types.ParseConstraintsDecoded(constraints)
++
+ for {
++ // `env.tcount` starts from 0 so it's correct to use it as the current index
++ currentTxIndex := uint64(env.tcount)
++
+ // Check interruption signal and abort building if it's fired.
+ if interrupt != nil {
+ if signal := interrupt.Load(); signal != commitInterruptNone {
+@@ -1036,102 +1053,166 @@ if env.gasPool.Gas() < params.TxGas {
+ log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
+ break
+ }
++
++ blobGasLeft := uint64(params.MaxBlobGasPerBlock - env.blobs*params.BlobTxBlobGasPerBlob)
++
+ // If we don't have enough blob space for any further blob transactions,
+ // skip that list altogether
+- if !blobTxs.Empty() && env.blobs*params.BlobTxBlobGasPerBlob >= params.MaxBlobGasPerBlock {
++ if !blobTxs.Empty() && blobGasLeft <= 0 {
+ log.Trace("Not enough blob space for further blob transactions")
+ blobTxs.Clear()
+ // Fall though to pick up any plain txs
+ }
+ // Retrieve the next transaction and abort if all done.
+ var (
+- ltx *txpool.LazyTransaction
+- txs *transactionsByPriceAndNonce
+- pltx *txpool.LazyTransaction
+- ptip *uint256.Int
+- bltx *txpool.LazyTransaction
+- btip *uint256.Int
++ lazyTx *txpool.LazyTransaction
++ txs *transactionsByPriceAndNonce
++ plainLazyTx *txpool.LazyTransaction
++ plainTxTip *uint256.Int
++ blobLazyTx *txpool.LazyTransaction
++ blobTxTip *uint256.Int
+ )
+
+- pTxWithMinerFee := plainTxs.Peek()
+- if pTxWithMinerFee != nil {
+- pltx = pTxWithMinerFee.Tx()
+- ptip = pTxWithMinerFee.fees
++ if pTxWithMinerFee := plainTxs.Peek(); pTxWithMinerFee != nil {
++ plainLazyTx = pTxWithMinerFee.Tx()
++ plainTxTip = pTxWithMinerFee.fees
+ }
+
+- bTxWithMinerFee := blobTxs.Peek()
+- if bTxWithMinerFee != nil {
+- bltx = bTxWithMinerFee.Tx()
+- btip = bTxWithMinerFee.fees
++ if bTxWithMinerFee := blobTxs.Peek(); bTxWithMinerFee != nil {
++ blobLazyTx = bTxWithMinerFee.Tx()
++ blobTxTip = bTxWithMinerFee.fees
+ }
+
+ switch {
+- case pltx == nil:
+- txs, ltx = blobTxs, bltx
+- case bltx == nil:
+- txs, ltx = plainTxs, pltx
++ case plainLazyTx == nil:
++ txs, lazyTx = blobTxs, blobLazyTx
++ case blobLazyTx == nil:
++ txs, lazyTx = plainTxs, plainLazyTx
+ default:
+- if ptip.Lt(btip) {
+- txs, ltx = blobTxs, bltx
++ if plainTxTip.Lt(blobTxTip) {
++ txs, lazyTx = blobTxs, blobLazyTx
+ } else {
+- txs, ltx = plainTxs, pltx
++ txs, lazyTx = plainTxs, plainLazyTx
+ }
+ }
+
+- if ltx == nil {
+- break
++ type candidateTx struct {
++ tx *types.Transaction
++ isConstraint bool
++ }
++ // candidate is the transaction we should execute in this cycle of the loop
++ var candidate struct {
++ tx *types.Transaction
++ isConstraint bool
+ }
+
+- // 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)
+- txs.Pop()
+- continue
++ var constraintTx *types.ConstraintDecoded
++ if len(constraintsOrderedByIndex) > 0 {
++ constraintTx = constraintsOrderedByIndex[0]
+ }
+- if left := uint64(params.MaxBlobGasPerBlock - env.blobs*params.BlobTxBlobGasPerBlob); left < ltx.BlobGas {
+- log.Trace("Not enough blob gas left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas)
+- txs.Pop()
+- continue
++
++ isSomePoolTxLeft := lazyTx != nil
++
++ isThereConstraintWithThisIndex := constraintTx != nil && constraintTx.Index != nil && *constraintTx.Index == currentTxIndex
++ if isThereConstraintWithThisIndex {
++ // we retrieve the candidate constraint by shifting it from the list
++ candidate = candidateTx{tx: common.Shift(&constraintsOrderedByIndex).Tx, isConstraint: true}
++ } else {
++ if isSomePoolTxLeft {
++ // Check if there enough gas left for this tx
++ if constraintsTotalGasLeft+lazyTx.Gas > env.gasPool.Gas() || constraintsTotalBlobGasLeft+lazyTx.BlobGas > blobGasLeft {
++ // Skip this tx and try to fit one with less gas.
++ // Drop all consecutive transactions from the same sender because of `nonce-too-high` clause.
++ log.Debug("Could not find transactions gas with the remaining constraints, account skipped", "hash", lazyTx.Hash)
++ txs.Pop()
++ // Edge case:
++ //
++ // Assumption: suppose sender A sends tx T_1 with nonce 1, and T_2 with nonce 2, and T_2 is a constraint.
++ //
++ //
++ // When running the block building algorithm I first have to make sure to reserve enough gas for the constraints.
++ // This implies that when a pooled tx comes I have to check if there is enough gas for it while taking into account
++ // the rest of the remaining constraint gas to allocate.
++ // Suppose there is no gas for the pooled tx T_1, then I have to drop it and consequently drop every tx from the same
++ // sender with higher nonce due to "nonce-too-high" issues, including T_2.
++ // But then, I have dropped a constraint which means my bid is invalid.
++ //
++ // FIXME: for the PoC we're not handling this
++
++ // Repeat the loop to try to find another pool transaction
++ continue
++ }
++ // We can safely consider the pool tx as the candidate,
++ // since by assumption it is not nonce-conflicting
++ tx := lazyTx.Resolve()
++ if tx == nil {
++ log.Trace("Ignoring evicted transaction", "hash", candidate.tx.Hash())
++ txs.Pop()
++ continue
++ }
++ candidate = candidateTx{tx: tx, isConstraint: false}
++ } else {
++ // No more pool tx left, we can add the unindexed ones if available
++ if len(constraintsWithoutIndex) == 0 {
++ // To recap, this means:
++ // 1. there are no more pool tx left
++ // 2. there are no more constraints without an index
++ // 3. the remaining indexes inside `constraintsOrderedByIndex`, if any, cannot be satisfied
++ // As such, we can safely exist
++ break
++ }
++ candidate = candidateTx{tx: common.Shift(&constraintsWithoutIndex).Tx, isConstraint: true}
++ }
+ }
+- // Transaction seems to fit, pull it up from the pool
+- tx := ltx.Resolve()
+- if tx == nil {
+- log.Trace("Ignoring evicted transaction", "hash", ltx.Hash)
+- txs.Pop()
+- continue
+- }
+- // Error may be ignored here. The error has already been checked
+- // during transaction acceptance is the transaction pool.
+- from, _ := types.Sender(env.signer, tx)
++
++ // Error may be ignored here, see assumption
++ from, _ := types.Sender(env.signer, candidate.tx)
+
+ // Check whether the tx is replay protected. If we're not in the EIP155 hf
+ // phase, start ignoring the sender until we do.
+- if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
+- log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash, "eip155", w.chainConfig.EIP155Block)
++ if candidate.tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
++ log.Trace("Ignoring replay protected transaction", "hash", candidate.tx.Hash(), "eip155", w.chainConfig.EIP155Block)
+ txs.Pop()
+ continue
+ }
+ // Start executing the transaction
+- env.state.SetTxContext(tx.Hash(), env.tcount)
++ env.state.SetTxContext(candidate.tx.Hash(), env.tcount)
+
+- logs, err := w.commitTransaction(env, tx)
++ logs, err := w.commitTransaction(env, candidate.tx)
+ switch {
+ case errors.Is(err, core.ErrNonceTooLow):
+ // New head notification data race between the transaction pool and miner, shift
+- log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
+- txs.Shift()
++ log.Trace("Skipping transaction with low nonce", "hash", candidate.tx.Hash(), "sender", from, "nonce", candidate.tx.Nonce())
++ if candidate.isConstraint {
++ log.Warn(fmt.Sprintf("Skipping constraint with low nonce, hash %s, sender %s, nonce %d", candidate.tx.Hash(), from, candidate.tx.Nonce()))
++ } else {
++ txs.Shift()
++ }
+
+ case errors.Is(err, nil):
+ // Everything ok, collect the logs and shift in the next transaction from the same account
+ coalescedLogs = append(coalescedLogs, logs...)
+ env.tcount++
+- txs.Shift()
++ if candidate.isConstraint {
++ // Update the amount of gas left for the constraints
++ constraintsTotalGasLeft -= candidate.tx.Gas()
++ constraintsTotalBlobGasLeft -= candidate.tx.BlobGas()
++
++ constraintTip, _ := candidate.tx.EffectiveGasTip(env.header.BaseFee)
++ log.Info(fmt.Sprintf("Executed constraint %s at index %d with effective gas tip %d", candidate.tx.Hash().String(), currentTxIndex, constraintTip))
++ } else {
++ txs.Shift()
++ }
+
+ default:
+ // Transaction is regarded as invalid, drop all consecutive transactions from
+ // the same sender because of `nonce-too-high` clause.
+- log.Debug("Transaction failed, account skipped", "hash", ltx.Hash, "err", err)
+- txs.Pop()
++ log.Debug("Transaction failed, account skipped", "hash", candidate.tx.Hash(), "err", err)
++ if candidate.isConstraint {
++ log.Warn("Constraint failed, account skipped", "hash", candidate.tx.Hash(), "err", err)
++ } else {
++ txs.Pop()
++ }
+ }
+ }
+ if !w.isRunning() && len(coalescedLogs) > 0 {
+@@ -1154,16 +1235,18 @@ }
+
+ // generateParams wraps various of settings for generating sealing task.
+ type generateParams struct {
+- timestamp uint64 // The timestamp for sealing task
+- forceTime bool // Flag whether the given timestamp is immutable or not
+- parentHash common.Hash // Parent block hash, empty means the latest chain head
+- coinbase common.Address // The fee recipient address for including transaction
+- gasLimit uint64 // The validator's requested gas limit target
+- random common.Hash // The randomness generated by beacon chain, empty before the merge
+- withdrawals types.Withdrawals // List of withdrawals to include in block.
+- beaconRoot *common.Hash // The beacon root (cancun field).
+- noTxs bool // Flag whether an empty block without any transaction is expected
+- onBlock BlockHookFn // Callback to call for each produced block
++ timestamp uint64 // The timestamp for sealing task
++ forceTime bool // Flag whether the given timestamp is immutable or not
++ parentHash common.Hash // Parent block hash, empty means the latest chain head
++ coinbase common.Address // The fee recipient address for including transaction
++ gasLimit uint64 // The validator's requested gas limit target
++ random common.Hash // The randomness generated by beacon chain, empty before the merge
++ withdrawals types.Withdrawals // List of withdrawals to include in block.
++ beaconRoot *common.Hash // The beacon root (cancun field).
++ noTxs bool // Flag whether an empty block without any transaction is expected
++ onBlock BlockHookFn // Callback to call for each produced block
++ slot uint64 // The slot in which the block is being produced
++ constraintsCache *shardmap.FIFOMap[uint64, types.HashToConstraintDecoded] // The preconfirmations to include in the block
+ }
+
+ func doPrepareHeader(genParams *generateParams, chain *core.BlockChain, config *Config, chainConfig *params.ChainConfig, extra []byte, engine consensus.Engine) (*types.Header, *types.Header, error) {
+@@ -1266,7 +1349,7 @@ }
+ return env, nil
+ }
+
+-func (w *worker) fillTransactionsSelectAlgo(interrupt *atomic.Int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, map[common.Hash]struct{}, error) {
++func (w *worker) fillTransactionsSelectAlgo(interrupt *atomic.Int32, env *environment, constraints types.HashToConstraintDecoded) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, map[common.Hash]struct{}, error) {
+ var (
+ blockBundles []types.SimulatedBundle
+ allBundles []types.SimulatedBundle
+@@ -1274,21 +1357,35 @@ usedSbundles []types.UsedSBundle
+ mempoolTxHashes map[common.Hash]struct{}
+ err error
+ )
+- switch w.flashbots.algoType {
+- case ALGO_GREEDY, ALGO_GREEDY_BUCKETS, ALGO_GREEDY_MULTISNAP, ALGO_GREEDY_BUCKETS_MULTISNAP:
++
++ // switch w.flashbots.algoType {
++ //
++ // case ALGO_GREEDY, ALGO_GREEDY_BUCKETS, ALGO_GREEDY_MULTISNAP, ALGO_GREEDY_BUCKETS_MULTISNAP:
++ //
++ // blockBundles, allBundles, usedSbundles, mempoolTxHashes, err = w.fillTransactionsAlgoWorker(interrupt, env)
++ // blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env, constraints)
++ // case ALGO_MEV_GETH:
++ // blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env, constraints)
++ // default:
++ // blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env, constraints)
++ // }
++
++ // // FIXME: (BOLT) the greedy algorithms do not support the constraints interface at the moment.
++ // // As such for this PoC we will be always using the MEV GETH algorithm regardless of the worker configuration.
++ if len(constraints) > 0 {
++ blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env, constraints)
++ } else {
+ blockBundles, allBundles, usedSbundles, mempoolTxHashes, err = w.fillTransactionsAlgoWorker(interrupt, env)
+- case ALGO_MEV_GETH:
+- blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env)
+- default:
+- blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env)
+ }
++
+ return blockBundles, allBundles, usedSbundles, mempoolTxHashes, err
+ }
+
+ // fillTransactions retrieves the pending transactions from the txpool and fills them
+ // into the given sealing block. The transaction selection and ordering strategy can
+ // be customized with the plugin in the future.
+-func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, map[common.Hash]struct{}, error) {
++func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment, constraints types.HashToConstraintDecoded) ([]types.SimulatedBundle, []types.SimulatedBundle, map[common.Hash]struct{}, error) {
++ log.Info(fmt.Sprintf("Filling transactions with %d constraints:", len(constraints)))
+ w.mu.RLock()
+ tip := w.tip
+ w.mu.RUnlock()
+@@ -1304,6 +1401,12 @@ mempoolTxHashes[tx.Hash] = struct{}{}
+ }
+ }
+
++ // NOTE: as done with builder txs, we need to fill mempoolTxHashes with the constraints hashes
++ // in order to pass block validation
++ for hash := range constraints {
++ mempoolTxHashes[hash] = struct{}{}
++ }
++
+ if env.header.BaseFee != nil {
+ filter.BaseFee = uint256.MustFromBig(env.header.BaseFee)
+ }
+@@ -1316,6 +1419,45 @@
+ filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true
+ pendingBlobTxs := w.eth.TxPool().Pending(filter)
+
++ // Drop all transactions that conflict with the constraints (sender, nonce)
++ signerAndNonceOfConstraints := make(map[common.Address]uint64)
++
++ for _, constraint := range constraints {
++ from, err := types.Sender(env.signer, constraint.Tx)
++ log.Info(fmt.Sprintf("Inside fillTransactions, constraint %s from %s", constraint.Tx.Hash().String(), from.String()))
++ if err != nil {
++ // NOTE: is this the right behaviour? If this happens the builder is not able to
++ // produce a valid bid
++ log.Error("Failed to recover sender from constraint. Skipping constraint", "err", err)
++ continue
++ }
++
++ signerAndNonceOfConstraints[from] = constraint.Tx.Nonce()
++ }
++ for sender, lazyTxs := range pendingPlainTxs {
++ common.Filter(&lazyTxs, func(lazyTx *txpool.LazyTransaction) bool {
++ if nonce, ok := signerAndNonceOfConstraints[sender]; ok {
++ if lazyTx.Tx.Nonce() == nonce {
++ return false
++ }
++ }
++
++ return true
++ })
++ }
++
++ for sender, lazyTxs := range pendingBlobTxs {
++ common.Filter(&lazyTxs, func(lazyTx *txpool.LazyTransaction) bool {
++ if nonce, ok := signerAndNonceOfConstraints[sender]; ok {
++ if lazyTx.Tx.Nonce() == nonce {
++ return false
++ }
++ }
++
++ return true
++ })
++ }
++
+ // Split the pending transactions into locals and remotes.
+ localPlainTxs, remotePlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs
+ localBlobTxs, remoteBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs
+@@ -1333,48 +1475,49 @@ }
+
+ var blockBundles []types.SimulatedBundle
+ var allBundles []types.SimulatedBundle
+- if w.flashbots.isFlashbots {
+- bundles, ccBundleCh := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
+- bundles = append(bundles, <-ccBundleCh...)
+-
+- var (
+- bundleTxs []*types.Transaction
+- resultingBundle simulatedBundle
+- mergedBundles []types.SimulatedBundle
+- numBundles int
+- err error
+- )
+- // Sets allBundles in outer scope
+- bundleTxs, resultingBundle, mergedBundles, numBundles, allBundles, err = w.generateFlashbotsBundle(env, bundles, pending)
+- if err != nil {
+- log.Error("Failed to generate flashbots bundle", "err", err)
+- return nil, nil, nil, err
+- }
+- log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(resultingBundle.TotalEth), "gasUsed", resultingBundle.TotalGasUsed, "bundleScore", resultingBundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
+- if len(bundleTxs) == 0 {
+- return nil, nil, nil, errors.New("no bundles to apply")
+- }
+- if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
+- return nil, nil, nil, err
+- }
+- blockBundles = mergedBundles
+- env.profit.Add(env.profit, resultingBundle.EthSentToCoinbase)
+- }
++ // if w.flashbots.isFlashbots {
++ // bundles, ccBundleCh := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
++ // bundles = append(bundles, <-ccBundleCh...)
++ //
++ // var (
++ // bundleTxs []*types.Transaction
++ // resultingBundle simulatedBundle
++ // mergedBundles []types.SimulatedBundle
++ // numBundles int
++ // err error
++ // )
++ // // Sets allBundles in outer scope
++ // bundleTxs, resultingBundle, mergedBundles, numBundles, allBundles, err = w.generateFlashbotsBundle(env, bundles, pending)
++ // if err != nil {
++ // log.Error("Failed to generate flashbots bundle", "err", err)
++ // return nil, nil, nil, err
++ // }
++ // log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(resultingBundle.TotalEth), "gasUsed", resultingBundle.TotalGasUsed, "bundleScore", resultingBundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
++ // if len(bundleTxs) == 0 {
++ // log.Info("No bundles to apply")
++ // return nil, nil, nil, errors.New("no bundles to apply")
++ // }
++ // if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
++ // return nil, nil, nil, err
++ // }
++ // blockBundles = mergedBundles
++ // env.profit.Add(env.profit, resultingBundle.EthSentToCoinbase)
++ // }
+
+ // Fill the block with all available pending transactions.
+- if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 {
++ if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 || len(constraints) > 0 {
+ plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, nil, nil, env.header.BaseFee)
+ blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, nil, nil, env.header.BaseFee)
+
+- if err := w.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil {
++ if err := w.commitTransactions(env, plainTxs, blobTxs, constraints, interrupt); err != nil {
+ return nil, nil, nil, err
+ }
+ }
+- if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 {
++ if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 || len(constraints) > 0 {
+ plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, nil, nil, env.header.BaseFee)
+ blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, nil, nil, env.header.BaseFee)
+
+- if err := w.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil {
++ if err := w.commitTransactions(env, plainTxs, blobTxs, constraints, interrupt); err != nil {
+ return nil, nil, nil, err
+ }
+ }
+@@ -1400,6 +1543,7 @@ }
+ // Split the pending transactions into locals and remotes
+ // Fill the block with all available pending transactions.
+ pending := w.eth.TxPool().Pending(filter)
++
+ mempoolTxHashes := make(map[common.Hash]struct{}, len(pending))
+ for _, txs := range pending {
+ for _, tx := range txs {
+@@ -1587,11 +1731,25 @@ }
+
+ orderCloseTime := time.Now()
+
+- blockBundles, allBundles, usedSbundles, mempoolTxHashes, err := w.fillTransactionsSelectAlgo(nil, work)
++ var constraints types.HashToConstraintDecoded
++
++ if params.constraintsCache != nil {
++ constraints, _ = params.constraintsCache.Get(params.slot)
++ log.Info(fmt.Sprintf("[BOLT]: found %d constraints for slot %d ", len(constraints), params.slot))
++ }
++
++ blockBundles, allBundles, usedSbundles, mempoolTxHashes, err := w.fillTransactionsSelectAlgo(nil, work, constraints)
+ if err != nil {
+ return &newPayloadResult{err: err}
+ }
+
++ // NOTE: as done with builder txs, we need to fill mempoolTxHashes with the constraints hashes
++ // in order to pass block validation. Otherwise the constraints will be rejected as unknown
++ // because they not part of the mempool and not part of the known bundles
++ for hash := range constraints {
++ mempoolTxHashes[hash] = struct{}{}
++ }
++
+ // We mark transactions created by the builder as mempool transactions so code validating bundles will not fail
+ // for transactions created by the builder such as mev share refunds.
+ for _, tx := range work.txs {
+@@ -1645,6 +1803,8 @@
+ return block, blockProfit, nil
+ }
+
++// checkProposerPayment checks that the last transaction in the block is targeting the
++// validator coinbase and returns the block profit equal to the value of the last transaction.
+ func (w *worker) checkProposerPayment(work *environment, validatorCoinbase common.Address) (*big.Int, error) {
+ if len(work.txs) == 0 {
+ return nil, errors.New("no proposer payment tx")
+@@ -1694,7 +1854,7 @@ return
+ }
+
+ // Fill pending transactions from the txpool
+- _, _, _, _, err = w.fillTransactionsSelectAlgo(interrupt, work)
++ _, _, _, _, err = w.fillTransactionsSelectAlgo(interrupt, work, nil)
+ switch {
+ case err == nil:
+ // The entire block is filled, decrease resubmit interval in case
+@@ -2198,6 +2358,8 @@ w.mu.Lock()
+ sender := w.coinbase
+ w.mu.Unlock()
+ builderBalance := env.state.GetBalance(sender).ToBig()
++
++ log.Info(fmt.Sprintf("[BOLT]: builderBalance %v, reserve.builderBalance %v", builderBalance, reserve.builderBalance))
+
+ availableFunds := new(big.Int).Sub(builderBalance, reserve.builderBalance)
+ if availableFunds.Sign() <= 0 {
+
+
diff --git flashbots/builder/miner/worker_test.go chainbound/bolt/miner/worker_test.go
+index d65ad578de31558b667c7934cb7581751853fa8f..745a476183e62a79286c3dfb5cc90566092244d4 100644
+--- flashbots/builder/miner/worker_test.go
++++ chainbound/bolt/miner/worker_test.go
+@@ -24,6 +24,7 @@ "sync/atomic"
+ "testing"
+ "time"
+
++ "github.com/chainbound/shardmap"
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+@@ -77,6 +78,9 @@ // Test transactions
+ pendingTxs []*types.Transaction
+ newTxs []*types.Transaction
+
++ // Test testConstraintsCache
++ testConstraintsCache = new(shardmap.FIFOMap[uint64, types.HashToConstraintDecoded])
++
+ testConfig = &Config{
+ Recommit: time.Second,
+ GasCeil: params.GenesisGasLimit,
+@@ -84,6 +88,8 @@ }
+
+ defaultGenesisAlloc = types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}
+ )
++
++const pendingTxsLen = 50
+
+ func init() {
+ testTxPoolConfig = legacypool.DefaultConfig
+@@ -98,15 +104,32 @@ Epoch: 30000,
+ }
+
+ signer := types.LatestSigner(params.TestChainConfig)
+- tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
+- ChainID: params.TestChainConfig.ChainID,
+- Nonce: 0,
+- To: &testUserAddress,
+- Value: big.NewInt(1000),
+- Gas: params.TxGas,
+- GasPrice: big.NewInt(params.InitialBaseFee),
+- })
+- pendingTxs = append(pendingTxs, tx1)
++ for i := 0; i < pendingTxsLen; i++ {
++ tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
++ ChainID: params.TestChainConfig.ChainID,
++ Nonce: uint64(i),
++ To: &testUserAddress,
++ Value: big.NewInt(1000),
++ Gas: params.TxGas,
++ GasPrice: big.NewInt(params.InitialBaseFee),
++ })
++
++ // Add some constraints every 3 txs, and every 6 add an index
++ if i%3 == 0 {
++ idx := new(uint64)
++ if i%2 == 0 {
++ *idx = uint64(i)
++ } else {
++ idx = nil
++ }
++ constraints := make(map[common.Hash]*types.ConstraintDecoded)
++ constraints[tx1.Hash()] = &types.ConstraintDecoded{Index: idx, Tx: tx1}
++ // FIXME: slot 0 is probably not correct for these tests
++ testConstraintsCache.Put(0, constraints)
++ }
++
++ pendingTxs = append(pendingTxs, tx1)
++ }
+
+ tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{
+ Nonce: 1,
+@@ -130,7 +153,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, alloc types.GenesisAlloc, n int, gasLimit uint64) *testWorkerBackend {
+ if alloc == nil {
+ alloc = defaultGenesisAlloc
+ }
+- var gspec = &core.Genesis{
++ gspec := &core.Genesis{
+ Config: chainConfig,
+ GasLimit: gasLimit,
+ Alloc: alloc,
+@@ -251,10 +274,10 @@
+ w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), nil, 0)
+ defer w.close()
+
+- taskCh := make(chan struct{}, 2)
++ taskCh := make(chan struct{}, pendingTxsLen*2)
+ checkEqual := func(t *testing.T, task *task) {
+ // The work should contain 1 tx
+- receiptLen, balance := 1, uint256.NewInt(1000)
++ receiptLen, balance := pendingTxsLen, uint256.NewInt(50_000)
+ if len(task.receipts) != receiptLen {
+ t.Fatalf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen)
+ }
+@@ -378,12 +401,12 @@ }
+
+ func TestGetSealingWorkEthash(t *testing.T) {
+ t.Parallel()
+- testGetSealingWork(t, ethashChainConfig, ethash.NewFaker())
++ testGetSealingWork(t, ethashChainConfig, ethash.NewFaker(), nil)
+ }
+
+ func TestGetSealingWorkClique(t *testing.T) {
+ t.Parallel()
+- testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()))
++ testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), nil)
+ }
+
+ func TestGetSealingWorkPostMerge(t *testing.T) {
+@@ -391,10 +414,25 @@ t.Parallel()
+ local := new(params.ChainConfig)
+ *local = *ethashChainConfig
+ local.TerminalTotalDifficulty = big.NewInt(0)
+- testGetSealingWork(t, local, ethash.NewFaker())
++ testGetSealingWork(t, local, ethash.NewFaker(), nil)
+ }
+
+-func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
++// TestGetSealingWorkWithConstraints tests the getSealingWork function with constraints.
++// This is the main test for the modified block building algorithm. Unfortunately
++// is not easy to make an end to end test where the constraints are pulled from the relay.
++//
++// A suggestion is to walk through the executing code with a debugger to further inspect the algorithm.
++//
++// However, if you want to check that functionality see `builder_test.go`
++func TestGetSealingWorkWithConstraints(t *testing.T) {
++ // t.Parallel()
++ local := new(params.ChainConfig)
++ *local = *ethashChainConfig
++ local.TerminalTotalDifficulty = big.NewInt(0)
++ testGetSealingWork(t, local, ethash.NewFaker(), testConstraintsCache)
++}
++
++func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, constraintsCache *shardmap.FIFOMap[uint64, types.HashToConstraintDecoded]) {
+ defer engine.Close()
+ w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), nil, 0)
+ defer w.close()
+@@ -486,15 +524,16 @@
+ // This API should work even when the automatic sealing is not enabled
+ for _, c := range cases {
+ r := w.getSealingBlock(&generateParams{
+- parentHash: c.parent,
+- timestamp: timestamp,
+- coinbase: c.coinbase,
+- random: c.random,
+- withdrawals: nil,
+- beaconRoot: nil,
+- noTxs: false,
+- forceTime: true,
+- onBlock: nil,
++ parentHash: c.parent,
++ timestamp: timestamp,
++ coinbase: c.coinbase,
++ random: c.random,
++ withdrawals: nil,
++ beaconRoot: nil,
++ noTxs: false,
++ forceTime: true,
++ onBlock: nil,
++ constraintsCache: constraintsCache,
+ })
+ if c.expectErr {
+ if r.err == nil {
+