@@ -43,48 +43,14 @@ type retryPolicy struct {
4343 MinBackoff time.Duration
4444 MaxBackoff time.Duration
4545 MaxBlobBytes uint64
46- // MaxItems caps the number of items packed into a single Submit
47- // call. DA clients that fan out per-item Uploads (fiber) benefit
48- // linearly from larger batches — settlement throughput scales
49- // with concurrency until per-Upload latency dominates. Default
50- // 1 preserves legacy single-item-per-Submit semantics for
51- // backends that flatten a batch into one blob (JSON-RPC blob
52- // client). The fiber path overrides this from config.
53- MaxItems int
5446}
5547
56- // defaultBatchItems is the conservative default for non-fiber backends
57- // that historically expected one item per Submit call. The fiber path
58- // raises this via config because it can fan out per-item Uploads.
59- const defaultBatchItems = 1
60-
61- // fiberDefaultBatchItems is the upper bound on items packed into a
62- // single fiber Submit. Each item gets its own concurrent Upload, so
63- // this caps the per-batch goroutine fan-out.
64- //
65- // HACK(fiber-throughput): hardcoded at 16. The right value depends on
66- // memory budget × per-item Upload size × Fibre validator-side
67- // throughput, none of which the submitter can know at compile time.
68- // Should be a config knob (FiberDAConfig.UploadConcurrency was
69- // scaffolded for exactly this earlier — wire it through here when the
70- // concurrent-uploads change graduates from prototype). 16 is a
71- // pragmatic measurement default that gives meaningful concurrency
72- // without overwhelming celestia-node's per-FSP rate.
73- const fiberDefaultBatchItems = 16
74-
7548func defaultRetryPolicy (maxAttempts int , maxDuration time.Duration ) retryPolicy {
7649 return retryPolicy {
77- MaxAttempts : maxAttempts ,
78- MinBackoff : initialBackoff ,
79- MaxBackoff : maxDuration ,
80- // TODO(throughput-cleanup): same value is used by
81- // executing/executor.go::RetrieveBatch as the raw-tx budget
82- // (with a 2% reservation) and again here as the marshaled
83- // blob ceiling. They are semantically different limits;
84- // the duplication is what made packed-block-larger-than-cap
85- // failures non-obvious. See common/consts.go.
50+ MaxAttempts : maxAttempts ,
51+ MinBackoff : initialBackoff ,
52+ MaxBackoff : maxDuration ,
8653 MaxBlobBytes : common .DefaultMaxBlobSize ,
87- MaxItems : defaultBatchItems ,
8854 }
8955}
9056
@@ -606,19 +572,12 @@ func submitToDA[T any](
606572 }
607573
608574 pol := defaultRetryPolicy (s .config .DA .MaxSubmitAttempts , s .config .DA .BlockTime .Duration )
609- // Fiber's DA client fans out per-item Uploads concurrently, so
610- // packing more items per Submit lifts settlement throughput. For
611- // non-fiber backends the default of 1 preserves the legacy
612- // flatten-one-blob behavior.
613- if s .config .DA .IsFiberEnabled () {
614- pol .MaxItems = fiberDefaultBatchItems
615- }
616575
617576 rs := retryState {Attempt : 0 , Backoff : 0 }
618577
619578 // Limit this submission to a single size-capped batch
620579 if len (marshaled ) > 0 {
621- batchItems , batchMarshaled , err := limitBatchBySize (items , marshaled , pol .MaxBlobBytes , pol . MaxItems )
580+ batchItems , batchMarshaled , err := limitBatchBySize (items , marshaled , pol .MaxBlobBytes )
622581 if err != nil {
623582 s .logger .Error ().
624583 Str ("itemType" , itemType ).
@@ -729,42 +688,27 @@ func submitToDA[T any](
729688 return fmt .Errorf ("failed to submit all %s(s) to DA layer after %d attempts" , itemType , rs .Attempt )
730689}
731690
732- // limitBatchBySize returns a prefix of items whose per-item marshaled size
733- // fits within maxItemBytes. The total batch size is bounded by item count
734- // (maxItems), not by total bytes — DA clients that can fan out per-item
735- // Uploads (e.g. the fiber DA client) settle each item in its own
736- // concurrent Upload call, so packing more items per batch lifts the
737- // effective settlement throughput. DA clients that flatten a batch into
738- // a single blob still get one item per call when maxItems == 1.
739- //
740- // If the first item exceeds maxItemBytes, returns ErrOversizedItem
741- // (unrecoverable). If no items fit at all (empty inputs), returns a
742- // distinct error so the caller can distinguish "nothing to send".
743- //
744- // TODO(throughput-cleanup): see common/consts.go — maxItemBytes is the
745- // per-item chain ceiling, separate from the raw-tx budget driving
746- // FilterTxs. Once that split lands, the duplicate-cap-everywhere
747- // problem these fixes work around goes away.
748- func limitBatchBySize [T any ](items []T , marshaled [][]byte , maxItemBytes uint64 , maxItems int ) ([]T , [][]byte , error ) {
749- if maxItems <= 0 {
750- maxItems = 1
751- }
691+ // limitBatchBySize returns a prefix of items whose total marshaled size does not exceed maxBytes.
692+ // If the first item exceeds maxBytes, it returns ErrOversizedItem which is unrecoverable.
693+ func limitBatchBySize [T any ](items []T , marshaled [][]byte , maxBytes uint64 ) ([]T , [][]byte , error ) {
694+ total := uint64 (0 )
752695 count := 0
753696 for i := range items {
754- if count >= maxItems {
755- break
756- }
757697 sz := uint64 (len (marshaled [i ]))
758- if sz > maxItemBytes {
698+ if sz > maxBytes {
759699 if i == 0 {
760- return nil , nil , fmt .Errorf ("%w: item size %d exceeds max %d" , common .ErrOversizedItem , sz , maxItemBytes )
700+ return nil , nil , fmt .Errorf ("%w: item size %d exceeds max %d" , common .ErrOversizedItem , sz , maxBytes )
761701 }
762702 break
763703 }
704+ if total + sz > maxBytes {
705+ break
706+ }
707+ total += sz
764708 count ++
765709 }
766710 if count == 0 {
767- return nil , nil , fmt .Errorf ("no items fit within %d bytes" , maxItemBytes )
711+ return nil , nil , fmt .Errorf ("no items fit within %d bytes" , maxBytes )
768712 }
769713 return items [:count ], marshaled [:count ], nil
770714}
0 commit comments