@@ -78,6 +78,17 @@ type TransactionPool interface {
7878 // SubmitTxNoWait adds the transaction into the transaction pool and returns immediately.
7979 SubmitTxNoWait (ctx context.Context , tx []byte , meta * TransactionMeta ) error
8080
81+ // SubmitProposedBatch adds the given (possibly new) transaction batch into the current
82+ // proposal queue.
83+ SubmitProposedBatch (batch [][]byte )
84+
85+ // PromoteProposedBatch promotes the specified transactions that are already in the transaction
86+ // pool into the current proposal queue.
87+ PromoteProposedBatch (batch []hash.Hash )
88+
89+ // ClearProposedBatch clears the proposal queue.
90+ ClearProposedBatch ()
91+
8192 // RemoveTxBatch removes a transaction batch from the transaction pool.
8293 RemoveTxBatch (txs []hash.Hash )
8394
@@ -183,11 +194,13 @@ type txPool struct {
183194 schedulerTicker * time.Ticker
184195 schedulerNotifier * pubsub.Broker
185196
197+ proposedTxsLock sync.Mutex
198+ proposedTxs map [hash.Hash ]* transaction.CheckedTransaction
199+
186200 blockInfoLock sync.Mutex
187201 blockInfo * BlockInfo
188202 lastRecheckRound uint64
189203
190- epoCh * channels.RingChannel
191204 republishCh * channels.RingChannel
192205
193206 // roundWeightLimits is guarded by schedulerLock.
@@ -237,7 +250,7 @@ func (t *txPool) submitTx(ctx context.Context, rawTx []byte, meta *TransactionMe
237250 // Skip recently seen transactions.
238251 txHash := hash .NewFromBytes (rawTx )
239252 if _ , seen := t .seenCache .Peek (txHash ); seen && ! meta .Recheck {
240- t .logger .Debug ("ignoring already seen transaction" , "tx " , rawTx )
253+ t .logger .Debug ("ignoring already seen transaction" , "tx_hash " , txHash )
241254 return fmt .Errorf ("duplicate transaction" )
242255 }
243256
@@ -251,11 +264,13 @@ func (t *txPool) submitTx(ctx context.Context, rawTx []byte, meta *TransactionMe
251264 // Queue transaction for checks.
252265 t .logger .Debug ("queuing transaction for check" ,
253266 "tx" , rawTx ,
267+ "tx_hash" , txHash ,
254268 "recheck" , meta .Recheck ,
255269 )
256270 if err := t .checkTxQueue .Add (tx ); err != nil {
257271 t .logger .Warn ("unable to queue transaction" ,
258272 "tx" , rawTx ,
273+ "tx_hash" , txHash ,
259274 "err" , err ,
260275 )
261276 return err
@@ -269,6 +284,47 @@ func (t *txPool) submitTx(ctx context.Context, rawTx []byte, meta *TransactionMe
269284 return nil
270285}
271286
287+ func (t * txPool ) SubmitProposedBatch (batch [][]byte ) {
288+ // Also ingest into the regular pool (may fail).
289+ for _ , rawTx := range batch {
290+ _ = t .SubmitTxNoWait (context .Background (), rawTx , & TransactionMeta {Local : false })
291+ }
292+
293+ t .proposedTxsLock .Lock ()
294+ defer t .proposedTxsLock .Unlock ()
295+
296+ for _ , rawTx := range batch {
297+ tx := transaction .RawCheckedTransaction (rawTx )
298+ t .proposedTxs [tx .Hash ()] = tx
299+ }
300+ }
301+
302+ func (t * txPool ) PromoteProposedBatch (batch []hash.Hash ) {
303+ txs , missingTxs := t .GetKnownBatch (batch )
304+ if len (missingTxs ) > 0 {
305+ t .logger .Debug ("promoted proposed batch contains missing transactions" ,
306+ "missing_tx_count" , len (missingTxs ),
307+ )
308+ }
309+
310+ t .proposedTxsLock .Lock ()
311+ defer t .proposedTxsLock .Unlock ()
312+
313+ for _ , tx := range txs {
314+ if tx == nil {
315+ continue
316+ }
317+ t .proposedTxs [tx .Hash ()] = tx
318+ }
319+ }
320+
321+ func (t * txPool ) ClearProposedBatch () {
322+ t .proposedTxsLock .Lock ()
323+ defer t .proposedTxsLock .Unlock ()
324+
325+ t .proposedTxs = make (map [hash.Hash ]* transaction.CheckedTransaction )
326+ }
327+
272328func (t * txPool ) RemoveTxBatch (txs []hash.Hash ) {
273329 t .schedulerLock .Lock ()
274330 defer t .schedulerLock .Unlock ()
@@ -303,7 +359,6 @@ func (t *txPool) GetPrioritizedBatch(offset *hash.Hash, limit uint32) []*transac
303359
304360func (t * txPool ) GetKnownBatch (batch []hash.Hash ) ([]* transaction.CheckedTransaction , map [hash.Hash ]int ) {
305361 t .schedulerLock .Lock ()
306- defer t .schedulerLock .Unlock ()
307362
308363 if t .schedulerQueue == nil {
309364 result := make ([]* transaction.CheckedTransaction , 0 , len (batch ))
@@ -312,9 +367,28 @@ func (t *txPool) GetKnownBatch(batch []hash.Hash) ([]*transaction.CheckedTransac
312367 result = append (result , nil )
313368 missing [txHash ] = index
314369 }
370+ t .schedulerLock .Unlock ()
315371 return result , missing
316372 }
317- return t .schedulerQueue .GetKnownBatch (batch )
373+
374+ txs , missingTxs := t .schedulerQueue .GetKnownBatch (batch )
375+ t .schedulerLock .Unlock ()
376+
377+ // Also check the proposed transactions set.
378+ t .proposedTxsLock .Lock ()
379+ defer t .proposedTxsLock .Unlock ()
380+
381+ for txHash , index := range missingTxs {
382+ tx , exists := t .proposedTxs [txHash ]
383+ if ! exists {
384+ continue
385+ }
386+
387+ delete (missingTxs , txHash )
388+ txs [index ] = tx
389+ }
390+
391+ return txs , missingTxs
318392}
319393
320394func (t * txPool ) ProcessBlock (bi * BlockInfo ) error {
@@ -327,7 +401,6 @@ func (t *txPool) ProcessBlock(bi *BlockInfo) error {
327401 return fmt .Errorf ("failed to update scheduler: %w" , err )
328402 }
329403
330- t .epoCh .In () <- struct {}{}
331404 // Force recheck on epoch transitions.
332405 t .recheckTxCh .In () <- struct {}{}
333406 }
@@ -409,12 +482,13 @@ func (t *txPool) WakeupScheduler() {
409482
410483func (t * txPool ) Clear () {
411484 t .schedulerLock .Lock ()
412- defer t .schedulerLock .Unlock ()
413-
414485 if t .schedulerQueue != nil {
415486 t .schedulerQueue .Clear ()
416487 }
488+ t .schedulerLock .Unlock ()
489+
417490 t .seenCache .Clear ()
491+ t .ClearProposedBatch ()
418492
419493 pendingScheduleSize .With (t .getMetricLabels ()).Set (0 )
420494}
@@ -527,6 +601,7 @@ func (t *txPool) checkTxBatch(ctx context.Context, rr host.RichRuntime) {
527601 if ! res .IsSuccess () {
528602 t .logger .Debug ("check tx failed" ,
529603 "tx" , batch [i ].Tx ,
604+ "tx_hash" , batch [i ].TxHash ,
530605 "result" , res ,
531606 "recheck" , batch [i ].Meta .Recheck ,
532607 )
@@ -829,6 +904,10 @@ func New(
829904 return nil , fmt .Errorf ("error creating seen cache: %w" , err )
830905 }
831906
907+ // The transaction check queue should be 10% larger than the transaction pool to allow for some
908+ // buffer in case the schedule queue is full and is being rechecked.
909+ maxCheckTxQueueSize := (110 * cfg .MaxPoolSize ) / 100
910+
832911 return & txPool {
833912 logger : logging .GetLogger ("runtime/txpool" ),
834913 stopCh : make (chan struct {}),
@@ -839,13 +918,13 @@ func New(
839918 host : host ,
840919 txPublisher : txPublisher ,
841920 seenCache : seenCache ,
842- checkTxQueue : newCheckTxQueue (cfg . MaxPoolSize , cfg .MaxCheckTxBatchSize ),
921+ checkTxQueue : newCheckTxQueue (maxCheckTxQueueSize , cfg .MaxCheckTxBatchSize ),
843922 checkTxCh : channels .NewRingChannel (1 ),
844923 checkTxNotifier : pubsub .NewBroker (false ),
845924 recheckTxCh : channels .NewRingChannel (1 ),
846925 schedulerTicker : time .NewTicker (1 * time .Hour ),
847926 schedulerNotifier : pubsub .NewBroker (false ),
848- epoCh : channels . NewRingChannel ( 1 ),
927+ proposedTxs : make ( map [hash. Hash ] * transaction. CheckedTransaction ),
849928 republishCh : channels .NewRingChannel (1 ),
850929 roundWeightLimits : make (map [transaction.Weight ]uint64 ),
851930 }, nil
0 commit comments