diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index 6b66fe302b..990fd2e2be 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -3,7 +3,6 @@ package arbcompress -const LEVEL_FAST = 0 const LEVEL_WELL = 11 const WINDOW_SIZE = 22 // BROTLI_DEFAULT_WINDOW @@ -11,6 +10,6 @@ func compressedBufferSizeFor(length int) int { return length + (length>>10)*8 + 64 // actual limit is: length + (length >> 14) * 4 + 6 } -func CompressFast(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_FAST) +func CompressLevel(input []byte, level int) ([]byte, error) { + return compressLevel(input, level) } diff --git a/arbcompress/compress_test.go b/arbcompress/compress_test.go index fffcda8923..21629d9663 100644 --- a/arbcompress/compress_test.go +++ b/arbcompress/compress_test.go @@ -27,7 +27,7 @@ func testCompressDecompress(t *testing.T, data []byte) { } testDecompress(t, compressedWell, data) - compressedFast, err := CompressFast(data) + compressedFast, err := CompressLevel(data, 0) if err != nil { t.Fatal(err) } diff --git a/arbitrator/prover/test-cases/go/main.go b/arbitrator/prover/test-cases/go/main.go index a5a1028fb0..6efcf2c2db 100644 --- a/arbitrator/prover/test-cases/go/main.go +++ b/arbitrator/prover/test-cases/go/main.go @@ -42,7 +42,7 @@ func MerkleSample(data [][]byte, toproove int) (bool, error) { } func testCompression(data []byte) { - compressed, err := arbcompress.CompressFast(data) + compressed, err := arbcompress.CompressLevel(data, 0) if err != nil { panic(err) } diff --git a/arbnode/execution/tx_pre_checker.go b/arbnode/execution/tx_pre_checker.go index 968a1f266b..7d9e26d16c 100644 --- a/arbnode/execution/tx_pre_checker.go +++ b/arbnode/execution/tx_pre_checker.go @@ -188,7 +188,11 @@ func PreCheckTx(bc *core.BlockChain, chainConfig *params.ChainConfig, header *ty if config.Strictness >= TxPreCheckerStrictnessFullValidation && tx.Nonce() > stateNonce { return MakeNonceError(sender, tx.Nonce(), stateNonce) } - dataCost, _ := arbos.L1PricingState().GetPosterInfo(tx, l1pricing.BatchPosterAddress) + brotliCompressionLevel, err := arbos.BrotliCompressionLevel() + if err != nil { + return fmt.Errorf("failed to get brotli compression level: %w", err) + } + dataCost, _ := arbos.L1PricingState().GetPosterInfo(tx, l1pricing.BatchPosterAddress, brotliCompressionLevel) dataGas := arbmath.BigDiv(dataCost, header.BaseFee) if tx.Gas() < intrinsic+dataGas.Uint64() { return core.ErrIntrinsicGas diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 48e29e1219..a47f4e790b 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/addressSet" "github.com/offchainlabs/nitro/arbos/addressTable" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -35,23 +36,24 @@ import ( // persisted beyond the end of the test.) type ArbosState struct { - arbosVersion uint64 // version of the ArbOS storage format and semantics - upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade - upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade - networkFeeAccount storage.StorageBackedAddress - l1PricingState *l1pricing.L1PricingState - l2PricingState *l2pricing.L2PricingState - retryableState *retryables.RetryableState - addressTable *addressTable.AddressTable - chainOwners *addressSet.AddressSet - sendMerkle *merkleAccumulator.MerkleAccumulator - blockhashes *blockhash.Blockhashes - chainId storage.StorageBackedBigInt - chainConfig storage.StorageBackedBytes - genesisBlockNum storage.StorageBackedUint64 - infraFeeAccount storage.StorageBackedAddress - backingStorage *storage.Storage - Burner burn.Burner + arbosVersion uint64 // version of the ArbOS storage format and semantics + upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade + upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade + networkFeeAccount storage.StorageBackedAddress + l1PricingState *l1pricing.L1PricingState + l2PricingState *l2pricing.L2PricingState + retryableState *retryables.RetryableState + addressTable *addressTable.AddressTable + chainOwners *addressSet.AddressSet + sendMerkle *merkleAccumulator.MerkleAccumulator + blockhashes *blockhash.Blockhashes + chainId storage.StorageBackedBigInt + chainConfig storage.StorageBackedBytes + genesisBlockNum storage.StorageBackedUint64 + infraFeeAccount storage.StorageBackedAddress + brotliCompressionLevel storage.StorageBackedUint64 // brotli compression level used for pricing + backingStorage *storage.Storage + Burner burn.Burner } var ErrUninitializedArbOS = errors.New("ArbOS uninitialized") @@ -82,6 +84,7 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error) backingStorage.OpenStorageBackedBytes(chainConfigSubspace), backingStorage.OpenStorageBackedUint64(uint64(genesisBlockNumOffset)), backingStorage.OpenStorageBackedAddress(uint64(infraFeeAccountOffset)), + backingStorage.OpenStorageBackedUint64(uint64(brotliCompressionLevelOffset)), backingStorage, burner, }, nil @@ -139,6 +142,7 @@ const ( chainIdOffset genesisBlockNumOffset infraFeeAccountOffset + brotliCompressionLevelOffset ) type SubspaceID []byte @@ -215,6 +219,7 @@ func InitializeArbosState(stateDB vm.StateDB, burner burn.Burner, chainConfig *p chainConfigStorage := sto.OpenStorageBackedBytes(chainConfigSubspace) _ = chainConfigStorage.Set(initMessage.SerializedChainConfig) _ = sto.SetUint64ByUint64(uint64(genesisBlockNumOffset), chainConfig.ArbitrumChainParams.GenesisBlockNum) + _ = sto.SetUint64ByUint64(uint64(brotliCompressionLevelOffset), 0) // default brotliCompressionLevel for fast compression is 0 initialRewardsRecipient := l1pricing.BatchPosterAddress if desiredArbosVersion >= 2 { @@ -318,6 +323,17 @@ func (state *ArbosState) UpgradeArbosVersion( if !firstTime { ensure(state.chainOwners.ClearList()) } + case 11: + if !chainConfig.DebugMode() { + // This upgrade isn't finalized so we only want to support it for testing + return fmt.Errorf( + "the chain is upgrading to unsupported ArbOS version %v, %w", + state.arbosVersion+1, + ErrFatalNodeOutOfDate, + ) + } + // Update Brotli compression level for fast compression from 0 to 1 + ensure(state.SetBrotliCompressionLevel(1)) default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", @@ -379,6 +395,17 @@ func (state *ArbosState) SetFormatVersion(val uint64) { state.Restrict(state.backingStorage.SetUint64ByUint64(uint64(versionOffset), val)) } +func (state *ArbosState) BrotliCompressionLevel() (uint64, error) { + return state.brotliCompressionLevel.Get() +} + +func (state *ArbosState) SetBrotliCompressionLevel(val uint64) error { + if val <= arbcompress.LEVEL_WELL { + return state.brotliCompressionLevel.Set(val) + } + return errors.New("invalid brotli compression level") +} + func (state *ArbosState) RetryableState() *retryables.RetryableState { return state.retryableState } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 87ecac9e77..6f87864b61 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -271,7 +271,11 @@ func ProduceBlockAdvanced( if basefee.Sign() > 0 { dataGas = math.MaxUint64 - posterCost, _ := state.L1PricingState().GetPosterInfo(tx, poster) + brotliCompressionLevel, err := state.BrotliCompressionLevel() + if err != nil { + return nil, nil, fmt.Errorf("failed to get brotli compression level: %w", err) + } + posterCost, _ := state.L1PricingState().GetPosterInfo(tx, poster, brotliCompressionLevel) posterCostInL2Gas := arbmath.BigDiv(posterCost, basefee) if posterCostInL2Gas.IsUint64() { diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 34d6021f00..e506f76907 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -489,7 +489,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending( return nil } -func (ps *L1PricingState) getPosterUnitsWithoutCache(tx *types.Transaction, posterAddr common.Address) uint64 { +func (ps *L1PricingState) getPosterUnitsWithoutCache(tx *types.Transaction, posterAddr common.Address, brotliCompressionLevel uint64) uint64 { if posterAddr != BatchPosterAddress { return 0 @@ -500,7 +500,7 @@ func (ps *L1PricingState) getPosterUnitsWithoutCache(tx *types.Transaction, post return 0 } - l1Bytes, err := byteCountAfterBrotli0(txBytes) + l1Bytes, err := byteCountAfterBrotliLevel(txBytes, int(brotliCompressionLevel)) if err != nil { panic(fmt.Sprintf("failed to compress tx: %v", err)) } @@ -508,13 +508,13 @@ func (ps *L1PricingState) getPosterUnitsWithoutCache(tx *types.Transaction, post } // GetPosterInfo returns the poster cost and the calldata units for a transaction -func (ps *L1PricingState) GetPosterInfo(tx *types.Transaction, poster common.Address) (*big.Int, uint64) { +func (ps *L1PricingState) GetPosterInfo(tx *types.Transaction, poster common.Address, brotliCompressionLevel uint64) (*big.Int, uint64) { if poster != BatchPosterAddress { return common.Big0, 0 } units := atomic.LoadUint64(&tx.CalldataUnits) if units == 0 { - units = ps.getPosterUnitsWithoutCache(tx, poster) + units = ps.getPosterUnitsWithoutCache(tx, poster, brotliCompressionLevel) atomic.StoreUint64(&tx.CalldataUnits, units) } @@ -570,23 +570,23 @@ func makeFakeTxForMessage(message *core.Message) *types.Transaction { }) } -func (ps *L1PricingState) PosterDataCost(message *core.Message, poster common.Address) (*big.Int, uint64) { +func (ps *L1PricingState) PosterDataCost(message *core.Message, poster common.Address, brotliCompressionLevel uint64) (*big.Int, uint64) { tx := message.Tx if tx != nil { - return ps.GetPosterInfo(tx, poster) + return ps.GetPosterInfo(tx, poster, brotliCompressionLevel) } // Otherwise, we don't have an underlying transaction, so we're likely in gas estimation. // We'll instead make a fake tx from the message info we do have, and then pad our cost a bit to be safe. tx = makeFakeTxForMessage(message) - units := ps.getPosterUnitsWithoutCache(tx, poster) + units := ps.getPosterUnitsWithoutCache(tx, poster, brotliCompressionLevel) units = arbmath.UintMulByBips(units+estimationPaddingUnits, arbmath.OneInBips+estimationPaddingBasisPoints) pricePerUnit, _ := ps.PricePerUnit() return am.BigMulByUint(pricePerUnit, units), units } -func byteCountAfterBrotli0(input []byte) (uint64, error) { - compressed, err := arbcompress.CompressFast(input) +func byteCountAfterBrotliLevel(input []byte, level int) (uint64, error) { + compressed, err := arbcompress.CompressLevel(input, level) if err != nil { return 0, err } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 0d44ac548e..3572042a09 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -410,7 +410,11 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err // Since tips go to the network, and not to the poster, we use the basefee. // Note, this only determines the amount of gas bought, not the price per gas. - posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster) + brotliCompressionLevel, err := p.state.BrotliCompressionLevel() + if err != nil { + return common.Address{}, fmt.Errorf("failed to get brotli compression level: %w", err) + } + posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster, brotliCompressionLevel) if calldataUnits > 0 { p.state.Restrict(p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits)) } diff --git a/contracts b/contracts index edebd7b616..ae11e7a864 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit edebd7b61630c5d9988b572d6099417a6857d7a7 +Subproject commit ae11e7a86488e28c706e062ae501bfb7157197c1 diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index f13f8ce6c0..d795345839 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -469,7 +469,11 @@ func (n NodeInterface) GasEstimateL1Component( // Compute the fee paid for L1 in L2 terms // See in GasChargingHook that this does not induce truncation error // - feeForL1, _ := pricing.PosterDataCost(msg, l1pricing.BatchPosterAddress) + brotliCompressionLevel, err := c.State.BrotliCompressionLevel() + if err != nil { + return 0, nil, nil, fmt.Errorf("failed to get brotli compression level: %w", err) + } + feeForL1, _ := pricing.PosterDataCost(msg, l1pricing.BatchPosterAddress, brotliCompressionLevel) feeForL1 = arbmath.BigMulByBips(feeForL1, arbos.GasEstimationL1PricePadding) gasForL1 := arbmath.BigDiv(feeForL1, baseFee).Uint64() return gasForL1, baseFee, l1BaseFeeEstimate, nil @@ -507,7 +511,11 @@ func (n NodeInterface) GasEstimateComponents( if err != nil { return 0, 0, nil, nil, err } - feeForL1, _ := pricing.PosterDataCost(msg, l1pricing.BatchPosterAddress) + brotliCompressionLevel, err := c.State.BrotliCompressionLevel() + if err != nil { + return 0, 0, nil, nil, fmt.Errorf("failed to get brotli compression level: %w", err) + } + feeForL1, _ := pricing.PosterDataCost(msg, l1pricing.BatchPosterAddress, brotliCompressionLevel) baseFee, err := c.State.L2PricingState().BaseFeeWei() if err != nil { diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index ee81c1c3e6..ec375699b7 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -135,7 +135,12 @@ func init() { return } - posterCost, _ := state.L1PricingState().PosterDataCost(msg, l1pricing.BatchPosterAddress) + brotliCompressionLevel, err := state.BrotliCompressionLevel() + if err != nil { + log.Error("failed to get brotli compression level", "err", err) + return + } + posterCost, _ := state.L1PricingState().PosterDataCost(msg, l1pricing.BatchPosterAddress, brotliCompressionLevel) posterCostInL2Gas := arbos.GetPosterGas(state, header.BaseFee, msg.TxRunMode, posterCost) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 1abf1d0d09..166768940b 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -142,6 +142,10 @@ func (con ArbOwner) SetAmortizedCostCapBips(c ctx, evm mech, cap uint64) error { return c.State.L1PricingState().SetAmortizedCostCapBips(cap) } +func (con ArbOwner) SetBrotliCompressionLevel(c ctx, evm mech, level uint64) error { + return c.State.SetBrotliCompressionLevel(level) +} + func (con ArbOwner) ReleaseL1PricerSurplusFunds(c ctx, evm mech, maxWeiToRelease huge) (huge, error) { balance := evm.StateDB.GetBalance(l1pricing.L1PricerFundsPoolAddress) l1p := c.State.L1PricingState() diff --git a/precompiles/ArbOwnerPublic.go b/precompiles/ArbOwnerPublic.go index fb4326b1ce..4064f41bef 100644 --- a/precompiles/ArbOwnerPublic.go +++ b/precompiles/ArbOwnerPublic.go @@ -47,3 +47,8 @@ func (con ArbOwnerPublic) GetInfraFeeAccount(c ctx, evm mech) (addr, error) { } return c.State.InfraFeeAccount() } + +// GetBrotliCompressionLevel gets the current brotli compression level used for fast compression +func (con ArbOwnerPublic) GetBrotliCompressionLevel(c ctx, evm mech) (uint64, error) { + return c.State.BrotliCompressionLevel() +} diff --git a/precompiles/precompile.go b/precompiles/precompile.go index a91756e639..77102a95b9 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -554,6 +554,7 @@ func Precompiles() map[addr]ArbosPrecompile { ArbOwnerPublic := insert(MakePrecompile(templates.ArbOwnerPublicMetaData, &ArbOwnerPublic{Address: hex("6b")})) ArbOwnerPublic.methodsByName["GetInfraFeeAccount"].arbosVersion = 5 ArbOwnerPublic.methodsByName["RectifyChainOwner"].arbosVersion = 11 + ArbOwnerPublic.methodsByName["GetBrotliCompressionLevel"].arbosVersion = 12 ArbRetryableImpl := &ArbRetryableTx{Address: types.ArbRetryableTxAddress} ArbRetryable := insert(MakePrecompile(templates.ArbRetryableTxMetaData, ArbRetryableImpl)) @@ -589,6 +590,7 @@ func Precompiles() map[addr]ArbosPrecompile { ArbOwner.methodsByName["SetInfraFeeAccount"].arbosVersion = 5 ArbOwner.methodsByName["ReleaseL1PricerSurplusFunds"].arbosVersion = 10 ArbOwner.methodsByName["SetChainConfig"].arbosVersion = 11 + ArbOwner.methodsByName["SetBrotliCompressionLevel"].arbosVersion = 12 insert(ownerOnly(ArbOwnerImpl.Address, ArbOwner, emitOwnerActs)) insert(debugOnly(MakePrecompile(templates.ArbDebugMetaData, &ArbDebug{Address: hex("ff")}))) diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index bdd998357e..ea7edc2ee8 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -303,7 +303,7 @@ func TestSequencerPriceAdjustsFrom25Gwei(t *testing.T) { func compressedTxSize(t *testing.T, tx *types.Transaction) uint64 { txBin, err := tx.MarshalBinary() Require(t, err) - compressed, err := arbcompress.CompressFast(txBin) + compressed, err := arbcompress.CompressLevel(txBin, 0) Require(t, err) return uint64(len(compressed)) } diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index a8209499df..b14215fbf0 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -174,7 +174,7 @@ func FuzzStateTransition(f *testing.F) { binary.BigEndian.PutUint64(seqBatch[32:40], uint64(len(delayedMessages))) if compressSeqMsg { seqBatch = append(seqBatch, arbstate.BrotliMessageHeaderByte) - seqMsgCompressed, err := arbcompress.CompressFast(seqMsg) + seqMsgCompressed, err := arbcompress.CompressLevel(seqMsg, 0) if err != nil { panic(fmt.Sprintf("failed to compress sequencer message: %v", err)) }