@@ -210,6 +210,33 @@ func estimateTxCompressionRatio(data []byte, blockNumber uint64, blockTime uint6
210210 return ratio , nil
211211}
212212
213+ // calculateTxCompressedSize calculates the size of `data` after compression using da-codec.
214+ // We constrain compressed_size so that it cannot exceed the original size:
215+ //
216+ // compressed_size(tx) = min(size(zstd(rlp(tx))), size(rlp(tx)))
217+ //
218+ // This provides an upper bound on the rollup fee for a given transaction, regardless
219+ // what compression algorithm the sequencer/prover uses.
220+ func calculateTxCompressedSize (data []byte , blockNumber uint64 , blockTime uint64 , config * params.ChainConfig ) (* big.Int , error ) {
221+ // Compressed size of empty data is 0.
222+ // In practice, the rlp-encoded transaction is always non-empty.
223+ if len (data ) == 0 {
224+ return common .Big0 , nil
225+ }
226+
227+ // Compress data using da-codec
228+ compressed , err := encoding .CompressScrollBatchBytes (data , blockNumber , blockTime , config )
229+ if err != nil {
230+ log .Error ("Transaction compression failed" , "error" , err , "data size" , len (data ), "data" , common .Bytes2Hex (data ), "blockNumber" , blockNumber , "blockTime" , blockTime , "galileoTime" , config .GalileoTime )
231+ return nil , fmt .Errorf ("transaction compression failed: %w" , err )
232+ }
233+
234+ if len (compressed ) < len (data ) {
235+ return new (big.Int ).SetUint64 (uint64 (len (compressed ))), nil
236+ }
237+ return new (big.Int ).SetUint64 (uint64 (len (data ))), nil
238+ }
239+
213240// calculatePenalty computes the penalty multiplier based on compression ratio
214241// penalty(tx) = compression_ratio(tx) >= penalty_threshold ? 1 * PRECISION : penalty_factor
215242func calculatePenalty (compressionRatio , penaltyThreshold , penaltyFactor * big.Int ) * big.Int {
@@ -290,6 +317,48 @@ func calculateEncodedL1DataFeeFeynman(
290317 return l1DataFee
291318}
292319
320+ // calculateEncodedL1DataFeeGalileo computes the rollup fee for an RLP-encoded tx, post Galileo
321+ //
322+ // Post Galileo rollup fee formula:
323+ // rollupFee(tx) = feePerByte * compressedSize(tx) * (1 + penalty(tx)) / PRECISION
324+ //
325+ // Where:
326+ // feePerByte = (execScalar * l1BaseFee + blobScalar * l1BlobBaseFee)
327+ // compressedSize(tx) = min(len(zstd(rlp(tx))), len(rlp(tx)))
328+ // penalty(tx) = compressedSize(tx) / penaltyFactor
329+ func calculateEncodedL1DataFeeGalileo (
330+ l1BaseFee * big.Int ,
331+ l1BlobBaseFee * big.Int ,
332+ execScalar * big.Int ,
333+ blobScalar * big.Int ,
334+ penaltyFactor * big.Int ,
335+ compressedSize * big.Int ,
336+ ) * big.Int {
337+ // Sanitize penalty factor.
338+ if penaltyFactor .Cmp (common .Big0 ) == 0 {
339+ penaltyFactor = common .Big1
340+ }
341+
342+ // feePerByte = (execScalar * l1BaseFee) + (blobScalar * l1BlobBaseFee)
343+ execGas := new (big.Int ).Mul (execScalar , l1BaseFee )
344+ blobGas := new (big.Int ).Mul (blobScalar , l1BlobBaseFee )
345+ feePerByte := new (big.Int ).Add (execGas , blobGas )
346+
347+ // baseTerm = feePerByte * compressedSize
348+ baseTerm := new (big.Int ).Mul (feePerByte , compressedSize )
349+
350+ // penaltyTerm = (baseTerm * compressedSize) / penaltyFactor
351+ // Note: We divide by penaltyFactor after multiplication to preserve precision.
352+ penaltyTerm := new (big.Int ).Mul (baseTerm , compressedSize )
353+ penaltyTerm .Div (penaltyTerm , penaltyFactor )
354+
355+ // rollupFee = (baseTerm + penaltyTerm) / PRECISION
356+ rollupFee := new (big.Int ).Add (baseTerm , penaltyTerm )
357+ rollupFee .Div (rollupFee , rcfg .Precision ) // execScalar and blobScalar are scaled by PRECISION
358+
359+ return rollupFee
360+ }
361+
293362// calculateL1GasUsed computes the L1 gas used based on the calldata and
294363// constant sized overhead. The overhead can be decreased as the cost of the
295364// batch submission goes down via contract optimizations. This will not overflow
@@ -341,7 +410,7 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.Cha
341410 l1DataFee = calculateEncodedL1DataFee (raw , gpoState .overhead , gpoState .l1BaseFee , gpoState .scalar )
342411 } else if ! config .IsFeynman (blockTime ) {
343412 l1DataFee = calculateEncodedL1DataFeeCurie (raw , gpoState .l1BaseFee , gpoState .l1BlobBaseFee , gpoState .commitScalar , gpoState .blobScalar )
344- } else {
413+ } else if ! config . IsGalileo ( blockTime ) {
345414 // Calculate compression ratio for Feynman
346415 // Note: We compute the transaction ratio on tx.data, not on the full encoded transaction.
347416 compressionRatio , err := estimateTxCompressionRatio (tx .Data (), blockNumber .Uint64 (), blockTime , config )
@@ -360,6 +429,21 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.Cha
360429 gpoState .penaltyFactor ,
361430 compressionRatio ,
362431 )
432+ } else {
433+ // Note: In Galileo, we take the compressed size of the full RLP-encoded transaction.
434+ compressedSize , err := calculateTxCompressedSize (raw , blockNumber .Uint64 (), blockTime , config )
435+ if err != nil {
436+ return nil , fmt .Errorf ("failed to calculate compressed size: tx hash=%s: %w" , tx .Hash ().Hex (), err )
437+ }
438+
439+ l1DataFee = calculateEncodedL1DataFeeGalileo (
440+ gpoState .l1BaseFee ,
441+ gpoState .l1BlobBaseFee ,
442+ gpoState .commitScalar , // now represents execScalar
443+ gpoState .blobScalar ,
444+ gpoState .penaltyFactor , // in Galileo, penaltyFactor is repurposed as a coefficient of the blob utilization penalty
445+ compressedSize ,
446+ )
363447 }
364448
365449 // ensure l1DataFee fits into uint64 for circuit compatibility
0 commit comments