From ea83cb8fff8dad63f7f15091b778bed8ceff0304 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 7 May 2022 17:26:18 -0400 Subject: [PATCH 01/61] Initial impl, fails some tests --- arbnode/api.go | 8 +- arbnode/sequencer.go | 7 - arbos/arbosState/initialization_test.go | 13 +- arbos/arbosState/initialize.go | 11 +- arbos/block_processor.go | 2 +- arbos/internal_tx.go | 3 +- arbos/l1pricing/l1pricing.go | 440 ++++++++++++-------- arbos/l1pricing/l1pricing_test.go | 31 +- arbos/tx_processor.go | 2 +- contracts/src/precompiles/ArbAggregator.sol | 15 - contracts/src/precompiles/ArbOwner.sol | 3 - nodeInterface/NodeInterface.go | 2 +- nodeInterface/virtual-contracts.go | 6 +- precompiles/ArbAggregator.go | 86 ++-- precompiles/ArbAggregator_test.go | 17 +- precompiles/ArbGasInfo.go | 46 +- precompiles/ArbOwner.go | 11 +- system_tests/fees_test.go | 87 ---- util/arbmath/math.go | 8 + 19 files changed, 352 insertions(+), 446 deletions(-) diff --git a/arbnode/api.go b/arbnode/api.go index 0681c1787d..27f862f822 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -98,7 +98,7 @@ func (api *ArbDebugAPI) PricingModel(ctx context.Context, start, end rpc.BlockNu if err != nil { return history, err } - l1BaseFeeUpdateTime, err := state.L1PricingState().LastL1BaseFeeUpdateTime() + l1BaseFeeUpdateTime, err := state.L1PricingState().LastUpdateTime() if err != nil { return history, err } @@ -115,8 +115,8 @@ func (api *ArbDebugAPI) PricingModel(ctx context.Context, start, end rpc.BlockNu rateEstimate, _ := l2Pricing.RateEstimate() gasPool, _ := l2Pricing.GasPool() - l1BaseFeeEstimate, _ := l1Pricing.L1BaseFeeEstimateWei() - l1BaseFeeUpdateTime, err := l1Pricing.LastL1BaseFeeUpdateTime() + l1BaseFeeEstimate, _ := l1Pricing.PricePerUnit() + l1BaseFeeUpdateTime, err := l1Pricing.LastUpdateTime() if err != nil { return history, err } @@ -135,7 +135,7 @@ func (api *ArbDebugAPI) PricingModel(ctx context.Context, start, end rpc.BlockNu gasPoolTarget, _ := l2Pricing.GasPoolTarget() gasPoolWeight, _ := l2Pricing.GasPoolWeight() maxPerBlockGasLimit, _ := l2Pricing.MaxPerBlockGasLimit() - l1BaseFeeEstimateInertia, err := l1Pricing.L1BaseFeeEstimateInertia() + l1BaseFeeEstimateInertia, err := l1Pricing.Inertia() if err != nil { return history, err } diff --git a/arbnode/sequencer.go b/arbnode/sequencer.go index 8de7da70bd..3ed47ef3b9 100644 --- a/arbnode/sequencer.go +++ b/arbnode/sequencer.go @@ -85,13 +85,6 @@ func (s *Sequencer) PublishTransaction(ctx context.Context, tx *types.Transactio } func (s *Sequencer) preTxFilter(state *arbosState.ArbosState, tx *types.Transaction, sender common.Address) error { - agg, err := state.L1PricingState().ReimbursableAggregatorForSender(sender) - if err != nil { - return err - } - if agg == nil || *agg != l1pricing.SequencerAddress { - return errors.New("transaction sender's preferred aggregator is not the sequencer") - } return nil } diff --git a/arbos/arbosState/initialization_test.go b/arbos/arbosState/initialization_test.go index 675c87e972..29ea89c7c9 100644 --- a/arbos/arbosState/initialization_test.go +++ b/arbos/arbosState/initialization_test.go @@ -168,20 +168,15 @@ func checkAccounts(db *state.StateDB, arbState *ArbosState, accts []statetransfe t.Fatal(err) } } - if acct.AggregatorInfo != nil { - fc, err := l1p.AggregatorFeeCollector(addr) + sequencer, err := l1p.Sequencer() + Require(t, err) + if acct.AggregatorInfo != nil && acct.Addr == sequencer { + fc, err := l1p.PaySequencerFeesTo() Require(t, err) if fc != acct.AggregatorInfo.FeeCollector { t.Fatal() } } - if acct.AggregatorToPay != nil { - aggregator, err := l1p.ReimbursableAggregatorForSender(addr) - Require(t, err) - if aggregator == nil || *aggregator != *acct.AggregatorToPay { - Fail(t) - } - } } _ = l1p } diff --git a/arbos/arbosState/initialize.go b/arbos/arbosState/initialize.go index 87f5a837c0..88a16af064 100644 --- a/arbos/arbosState/initialize.go +++ b/arbos/arbosState/initialize.go @@ -154,15 +154,14 @@ func initializeRetryables(rs *retryables.RetryableState, initData statetransfer. func initializeArbosAccount(statedb *state.StateDB, arbosState *ArbosState, account statetransfer.AccountInitializationInfo) error { l1pState := arbosState.L1PricingState() if account.AggregatorInfo != nil { - err := l1pState.SetAggregatorFeeCollector(account.Addr, account.AggregatorInfo.FeeCollector) + sequencer, err := l1pState.Sequencer() if err != nil { return err } - } - if account.AggregatorToPay != nil { - err := l1pState.SetUserSpecifiedAggregator(account.Addr, account.AggregatorToPay) - if err != nil { - return err + if account.Addr == sequencer { + if err := l1pState.SetPaySequencerFeesTo(account.AggregatorInfo.FeeCollector); err != nil { + return err + } } } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 6b215dc16a..be824ca726 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -209,7 +209,7 @@ func ProduceBlockAdvanced( if gasPrice.Sign() > 0 { dataGas = math.MaxUint64 - state.L1PricingState().AddPosterInfo(tx, sender, poster) + state.L1PricingState().AddPosterInfo(tx, poster) posterCostInL2Gas := arbmath.BigDiv(tx.PosterCost, gasPrice) if posterCostInL2Gas.IsUint64() { diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 452196ad84..e1c15a1f3d 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -51,7 +51,6 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos if err != nil { panic(err) } - l1BaseFee, _ := inputs[0].(*big.Int) // current block's l2BaseFee, _ := inputs[1].(*big.Int) // the last L2 block's base fee (which is the result of the calculation 2 blocks ago) l1BlockNumber, _ := inputs[2].(uint64) // current block's timePassed, _ := inputs[3].(uint64) // since last block @@ -84,7 +83,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, state.FormatVersion(), false) - state.L1PricingState().UpdatePricingModel(l1BaseFee, currentTime) + state.L1PricingState().UpdateTime(currentTime) state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) } diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 9d3776f2fd..81a930b1eb 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -5,242 +5,361 @@ package l1pricing import ( "errors" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" "math/big" "github.com/offchainlabs/nitro/arbcompress" - "github.com/offchainlabs/nitro/arbos/addressSet" "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" ) type L1PricingState struct { - storage *storage.Storage - defaultAggregator storage.StorageBackedAddress - l1BaseFeeEstimate storage.StorageBackedBigInt - l1BaseFeeEstimateInertia storage.StorageBackedUint64 - lastL1BaseFeeUpdateTime storage.StorageBackedUint64 - userSpecifiedAggregators *storage.Storage - refuseDefaultAggregator *storage.Storage - aggregatorFeeCollectors *storage.Storage - aggregatorCompressionRatios *storage.Storage + storage *storage.Storage + // parameters + sequencer storage.StorageBackedAddress + paySequencerFeesTo storage.StorageBackedAddress + payRewardsTo storage.StorageBackedAddress + equilibrationTime storage.StorageBackedUint64 + inertia storage.StorageBackedUint64 + perUnitReward storage.StorageBackedUint64 + // variables + currentTime storage.StorageBackedUint64 + lastUpdateTime storage.StorageBackedUint64 + availableFunds storage.StorageBackedUint64 + fundsDueToSequencer storage.StorageBackedUint64 + fundsDueForRewards storage.StorageBackedUint64 + collectedSinceUpdate storage.StorageBackedUint64 + unitsSinceUpdate storage.StorageBackedUint64 + pricePerUnit storage.StorageBackedBigInt } var ( - SequencerAddress = common.HexToAddress("0xA4B000000000000000000073657175656e636572") + SequencerAddress = common.HexToAddress("0xA4B000000000000000000073657175656e636572") + L1PricerFundsPoolAddress = common.HexToAddress("0xA4B0000000000000000000000000000000000f6") - userSpecifiedAggregatorKey = []byte{0} - refuseDefaultAggregatorKey = []byte{1} - aggregatorFeeCollectorKey = []byte{2} - aggregatorCompressionRatioKey = []byte{3} + ErrInvalidTime = errors.New("invalid timestamp") ) const ( - defaultAggregatorAddressOffset uint64 = 0 - l1BaseFeeEstimateOffset uint64 = 1 - l1BaseFeeEstimateInertiaOffset uint64 = 2 - lastL1BaseFeeUpdateTimeOffset uint64 = 3 + sequencerOffset uint64 = iota + paySequencerFeesToOffset + payRewardsToOffset + equilibrationTimeOffset + inertiaOffset + perUnitRewardOffset + currentTimeOffset + lastUpdateTimeOffset + availableFundsOffset + fundsDueToSequencerOffset + fundsDueForRewards + collectedSinceOffset + unitsSinceOffset + pricePerUnitOffset ) -const InitialL1BaseFeeEstimate = 50 * params.GWei -const InitialL1BaseFeeEstimateInertia = 24 +const ( + InitialEquilibrationTime = 10000000 + InitialInertia = 10 + InitialPerUnitReward = 10 + InitialPricePerUnitGwei = 50 +) func InitializeL1PricingState(sto *storage.Storage) error { - err := sto.SetByUint64(defaultAggregatorAddressOffset, common.BytesToHash(SequencerAddress.Bytes())) - if err != nil { + if err := sto.SetByUint64(sequencerOffset, util.AddressToHash(SequencerAddress)); err != nil { + return err + } + if err := sto.SetByUint64(paySequencerFeesToOffset, util.AddressToHash(SequencerAddress)); err != nil { return err } - if err := sto.SetUint64ByUint64(l1BaseFeeEstimateInertiaOffset, InitialL1BaseFeeEstimateInertia); err != nil { + if err := sto.SetByUint64(payRewardsToOffset, util.AddressToHash(SequencerAddress)); err != nil { return err } - if err := sto.SetUint64ByUint64(l1BaseFeeEstimateOffset, InitialL1BaseFeeEstimate); err != nil { + if err := sto.SetUint64ByUint64(equilibrationTimeOffset, InitialEquilibrationTime); err != nil { return err } - return sto.SetUint64ByUint64(lastL1BaseFeeUpdateTimeOffset, 0) + if err := sto.SetUint64ByUint64(inertiaOffset, InitialInertia); err != nil { + return err + } + if err := sto.SetUint64ByUint64(perUnitRewardOffset, InitialPerUnitReward); err != nil { + return err + } + pricePerUnit := sto.OpenStorageBackedBigInt(pricePerUnitOffset) + return pricePerUnit.Set(big.NewInt(InitialPricePerUnitGwei * 1000000000)) } func OpenL1PricingState(sto *storage.Storage) *L1PricingState { return &L1PricingState{ sto, - sto.OpenStorageBackedAddress(defaultAggregatorAddressOffset), - sto.OpenStorageBackedBigInt(l1BaseFeeEstimateOffset), - sto.OpenStorageBackedUint64(l1BaseFeeEstimateInertiaOffset), - sto.OpenStorageBackedUint64(lastL1BaseFeeUpdateTimeOffset), - sto.OpenSubStorage(userSpecifiedAggregatorKey), - sto.OpenSubStorage(refuseDefaultAggregatorKey), - sto.OpenSubStorage(aggregatorFeeCollectorKey), - sto.OpenSubStorage(aggregatorCompressionRatioKey), + sto.OpenStorageBackedAddress(sequencerOffset), + sto.OpenStorageBackedAddress(paySequencerFeesToOffset), + sto.OpenStorageBackedAddress(payRewardsToOffset), + sto.OpenStorageBackedUint64(equilibrationTimeOffset), + sto.OpenStorageBackedUint64(inertiaOffset), + sto.OpenStorageBackedUint64(perUnitRewardOffset), + sto.OpenStorageBackedUint64(currentTimeOffset), + sto.OpenStorageBackedUint64(lastUpdateTimeOffset), + sto.OpenStorageBackedUint64(availableFundsOffset), + sto.OpenStorageBackedUint64(fundsDueToSequencerOffset), + sto.OpenStorageBackedUint64(fundsDueForRewards), + sto.OpenStorageBackedUint64(collectedSinceOffset), + sto.OpenStorageBackedUint64(unitsSinceOffset), + sto.OpenStorageBackedBigInt(pricePerUnitOffset), } } -func (ps *L1PricingState) DefaultAggregator() (common.Address, error) { - return ps.defaultAggregator.Get() +func (ps *L1PricingState) Sequencer() (common.Address, error) { + return ps.sequencer.Get() } -func (ps *L1PricingState) SetDefaultAggregator(val common.Address) error { - return ps.defaultAggregator.Set(val) +func (ps *L1PricingState) SetSequencer(seq common.Address) error { + return ps.sequencer.Set(seq) } -func (ps *L1PricingState) L1BaseFeeEstimateWei() (*big.Int, error) { - return ps.l1BaseFeeEstimate.Get() +func (ps *L1PricingState) PaySequencerFeesTo() (common.Address, error) { + return ps.paySequencerFeesTo.Get() } -func (ps *L1PricingState) SetL1BaseFeeEstimateWei(val *big.Int) error { - return ps.l1BaseFeeEstimate.Set(val) +func (ps *L1PricingState) SetPaySequencerFeesTo(addr common.Address) error { + return ps.paySequencerFeesTo.Set(addr) } -func (ps *L1PricingState) LastL1BaseFeeUpdateTime() (uint64, error) { - return ps.lastL1BaseFeeUpdateTime.Get() +func (ps *L1PricingState) PayRewardsTo() (common.Address, error) { + return ps.payRewardsTo.Get() } -func (ps *L1PricingState) SetLastL1BaseFeeUpdateTime(t uint64) error { - return ps.lastL1BaseFeeUpdateTime.Set(t) +func (ps *L1PricingState) EquilibrationTime() (uint64, error) { + return ps.equilibrationTime.Get() } -// Update the pricing model with info from the start of a block -func (ps *L1PricingState) UpdatePricingModel(baseFeeSample *big.Int, currentTime uint64) { +func (ps *L1PricingState) Inertia() (uint64, error) { + return ps.inertia.Get() +} - if baseFeeSample.Sign() == 0 { - // The sequencer's normal messages do not include the l1 basefee, so ignore them - return - } +func (ps *L1PricingState) SetInertia(inertia uint64) error { + return ps.inertia.Set(inertia) +} - // update the l1 basefee estimate, which is the weighted average of the past and present - // basefee' = weighted average of the historical rate and the current, discounting time passed - // basefee' = (memory * basefee + sqrt(passed) * sample) / (memory + sqrt(passed)) - // - baseFee, _ := ps.L1BaseFeeEstimateWei() - inertia, _ := ps.L1BaseFeeEstimateInertia() - lastTime, _ := ps.LastL1BaseFeeUpdateTime() - if currentTime <= lastTime { - return - } - passedSqrt := arbmath.ApproxSquareRoot(currentTime - lastTime) - newBaseFee := arbmath.BigDivByUint( - arbmath.BigAdd(arbmath.BigMulByUint(baseFee, inertia), arbmath.BigMulByUint(baseFeeSample, passedSqrt)), - inertia+passedSqrt, - ) +func (ps *L1PricingState) PerUnitReward() (uint64, error) { + return ps.perUnitReward.Get() +} - _ = ps.SetL1BaseFeeEstimateWei(newBaseFee) - _ = ps.SetLastL1BaseFeeUpdateTime(currentTime) +func (ps *L1PricingState) CurrentTime() (uint64, error) { + return ps.currentTime.Get() } -// Get how slowly ArbOS updates its estimate of the L1 basefee -func (ps *L1PricingState) L1BaseFeeEstimateInertia() (uint64, error) { - return ps.l1BaseFeeEstimateInertia.Get() +func (ps *L1PricingState) SetCurrentTime(t uint64) error { + return ps.currentTime.Set(t) } -// Set how slowly ArbOS updates its estimate of the L1 basefee -func (ps *L1PricingState) SetL1BaseFeeEstimateInertia(inertia uint64) error { - return ps.l1BaseFeeEstimateInertia.Set(inertia) +func (ps *L1PricingState) LastUpdateTime() (uint64, error) { + return ps.lastUpdateTime.Get() } -func (ps *L1PricingState) userSpecifiedAggregatorsForAddress(sender common.Address) *addressSet.AddressSet { - return addressSet.OpenAddressSet(ps.userSpecifiedAggregators.OpenSubStorage(sender.Bytes())) +func (ps *L1PricingState) SetLastUpdateTime(t uint64) error { + return ps.lastUpdateTime.Set(t) } -// Get sender's user-specified aggregator, or nil if there is none. This does NOT fall back to the default aggregator -// if there is no user-specified aggregator. If that is what you want, call ReimbursableAggregatorForSender instead. -func (ps *L1PricingState) UserSpecifiedAggregator(sender common.Address) (*common.Address, error) { - return ps.userSpecifiedAggregatorsForAddress(sender).GetAnyMember() +func (ps *L1PricingState) AvailableFunds() (uint64, error) { + return ps.availableFunds.Get() } -func (ps *L1PricingState) SetUserSpecifiedAggregator(sender common.Address, maybeAggregator *common.Address) error { - paSet := ps.userSpecifiedAggregatorsForAddress(sender) - if err := paSet.Clear(); err != nil { - return err - } - if maybeAggregator == nil { - return nil - } - return paSet.Add(*maybeAggregator) +func (ps *L1PricingState) SetAvailableFunds(amt uint64) error { + return ps.availableFunds.Set(amt) } -func (ps *L1PricingState) RefusesDefaultAggregator(addr common.Address) (bool, error) { - val, err := ps.refuseDefaultAggregator.Get(common.BytesToHash(addr.Bytes())) - if err != nil { - return false, err - } - return val != (common.Hash{}), nil +func (ps *L1PricingState) FundsDueToSequencer() (uint64, error) { + return ps.fundsDueToSequencer.Get() } -func (ps *L1PricingState) SetRefusesDefaultAggregator(addr common.Address, refuses bool) error { - val := uint64(0) - if refuses { - val = 1 - } - return ps.refuseDefaultAggregator.Set(common.BytesToHash(addr.Bytes()), common.BigToHash(arbmath.UintToBig(val))) +func (ps *L1PricingState) SetFundsDueToSequencer(amt uint64) error { + return ps.fundsDueToSequencer.Set(amt) } -// Get the aggregator who is eligible to be reimbursed for L1 costs of txs from sender, or nil if there is none. -func (ps *L1PricingState) ReimbursableAggregatorForSender(sender common.Address) (*common.Address, error) { - fromTable, err := ps.UserSpecifiedAggregator(sender) +func (ps *L1PricingState) FundsDueForRewards() (uint64, error) { + return ps.fundsDueForRewards.Get() +} + +func (ps *L1PricingState) SetFundsDueForRewards(amt uint64) error { + return ps.fundsDueForRewards.Set(amt) +} + +func (ps *L1PricingState) CollectedSinceUpdate() (uint64, error) { + return ps.collectedSinceUpdate.Get() +} + +func (ps *L1PricingState) SetCollectedSinceUpdate(amt uint64) error { + return ps.collectedSinceUpdate.Set(amt) +} + +func (ps *L1PricingState) UnitsSinceUpdate() (uint64, error) { + return ps.unitsSinceUpdate.Get() +} + +func (ps *L1PricingState) SetUnitsSinceUpdate(units uint64) error { + return ps.unitsSinceUpdate.Set(units) +} + +func (ps *L1PricingState) PricePerUnit() (*big.Int, error) { + return ps.pricePerUnit.Get() +} + +func (ps *L1PricingState) SetPricePerUnit(price *big.Int) error { + return ps.pricePerUnit.Set(price) +} + +// Update the pricing model with info from the start of a block +func (ps *L1PricingState) UpdateTime(currentTime uint64) { + _ = ps.SetCurrentTime(currentTime) +} + +// Update the pricing model based on a payment by the sequencer +func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent uint64) error { + // compute previous shortfall + fundsDueToSequencer, err := ps.FundsDueToSequencer() + if err != nil { + return err + } + fundsDueForRewards, err := ps.FundsDueForRewards() if err != nil { - return nil, err + return err } - if fromTable != nil { - return fromTable, nil + availableFunds, err := ps.AvailableFunds() + if err != nil { + return err } + oldShortfall := int64(fundsDueToSequencer) + int64(fundsDueForRewards) - int64(availableFunds) - refuses, err := ps.RefusesDefaultAggregator(sender) - if err != nil || refuses { - return nil, err + // compute allocation fraction + lastUpdateTime, err := ps.LastUpdateTime() + if err != nil { + return err + } + if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { + return ErrInvalidTime } - aggregator, err := ps.DefaultAggregator() + allocFractionNum := updateTime - lastUpdateTime + allocFractionDenom := currentTime - lastUpdateTime + + // allocate units to this update + unitsSinceUpdate, err := ps.UnitsSinceUpdate() if err != nil { - return nil, err + return err } - if aggregator == (common.Address{}) { - return nil, nil + unitsAllocated := unitsSinceUpdate * allocFractionNum / allocFractionDenom + unitsSinceUpdate -= unitsAllocated + if err := ps.SetUnitsSinceUpdate(unitsSinceUpdate); err != nil { + return err } - return &aggregator, nil -} + // allocate funds to this update + collectedSinceUpdate, err := ps.CollectedSinceUpdate() + if err != nil { + return err + } + fundsToMove := collectedSinceUpdate * allocFractionNum / allocFractionDenom + collectedSinceUpdate -= fundsToMove + if err := ps.SetCollectedSinceUpdate(collectedSinceUpdate); err != nil { + return err + } + availableFunds += fundsToMove -func (ps *L1PricingState) SetAggregatorFeeCollector(aggregator common.Address, addr common.Address) error { - return ps.aggregatorFeeCollectors.Set(common.BytesToHash(aggregator.Bytes()), common.BytesToHash(addr.Bytes())) -} + // update amounts due + perUnitReward, err := ps.PerUnitReward() + if err != nil { + return err + } + fundsDueToSequencer += weiSpent + if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { + return err + } + fundsDueForRewards += perUnitReward * allocFractionNum / allocFractionDenom + if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { + return err + } -func (ps *L1PricingState) AggregatorFeeCollector(aggregator common.Address) (common.Address, error) { - raw, err := ps.aggregatorFeeCollectors.Get(common.BytesToHash(aggregator.Bytes())) - if raw == (common.Hash{}) { - return aggregator, err - } else { - return common.BytesToAddress(raw.Bytes()), err + // settle up, by paying out available funds + payRewardsTo, err := ps.PayRewardsTo() + if err != nil { + return err + } + paymentForRewards := arbmath.MinUint(availableFunds, fundsDueForRewards) + if paymentForRewards > 0 { + availableFunds -= paymentForRewards + if err := ps.SetAvailableFunds(availableFunds); err != nil { + return err + } + fundsDueForRewards -= paymentForRewards + if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { + return err + } + core.Transfer(stateDb, L1PricerFundsPoolAddress, payRewardsTo, arbmath.UintToBig(paymentForRewards)) + } + sequencerPaymentAddr, err := ps.Sequencer() + if err != nil { + return err + } + paymentForSequencer := arbmath.MinUint(availableFunds, fundsDueToSequencer) + if paymentForSequencer > 0 { + availableFunds -= paymentForSequencer + if err := ps.SetAvailableFunds(availableFunds); err != nil { + return err + } + fundsDueToSequencer -= paymentForSequencer + if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { + return err + } + core.Transfer(stateDb, L1PricerFundsPoolAddress, sequencerPaymentAddr, arbmath.UintToBig(paymentForSequencer)) } -} -func (ps *L1PricingState) AggregatorCompressionRatio(aggregator common.Address) (arbmath.Bips, error) { - raw, err := ps.aggregatorCompressionRatios.Get(common.BytesToHash(aggregator.Bytes())) - if raw == (common.Hash{}) { - return arbmath.OneInBips, err - } else { - return arbmath.BigToBips(raw.Big()), err + // update time + if err := ps.SetLastUpdateTime(updateTime); err != nil { + return err } -} -func (ps *L1PricingState) SetAggregatorCompressionRatio(aggregator common.Address, ratio arbmath.Bips) error { - if ratio > arbmath.PercentToBips(200) { - return errors.New("compression ratio out of bounds") + // adjust the price + if unitsAllocated > 0 { + shortfall := int64(fundsDueToSequencer) + int64(fundsDueForRewards) - int64(availableFunds) + inertia, err := ps.Inertia() + if err != nil { + return err + } + equilTime, err := ps.EquilibrationTime() + if err != nil { + return err + } + fdenom := unitsAllocated + equilTime/inertia + price, err := ps.PricePerUnit() + if err != nil { + return err + } + newPrice := new(big.Int).Sub(price, big.NewInt((shortfall-oldShortfall)/int64(fdenom)+shortfall/int64(equilTime*fdenom))) + if newPrice.Sign() >= 0 { + price = newPrice + } else { + price = big.NewInt(0) + } + if err := ps.SetPricePerUnit(price); err != nil { + return err + } } - return ps.aggregatorCompressionRatios.Set(util.AddressToHash(aggregator), util.UintToHash(uint64(ratio))) + return nil } -func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, sender, poster common.Address) { - +func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, poster common.Address) { tx.PosterCost = big.NewInt(0) tx.PosterIsReimbursable = false - aggregator, perr := ps.ReimbursableAggregatorForSender(sender) + sequencer, perr := ps.Sequencer() txBytes, merr := tx.MarshalBinary() txType := tx.Type() - if !util.TxTypeHasPosterCosts(txType) || perr != nil || merr != nil || aggregator == nil || poster != *aggregator { + if !util.TxTypeHasPosterCosts(txType) || perr != nil || merr != nil || poster != sequencer { return } @@ -251,40 +370,21 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, sender, poster co } // Approximate the l1 fee charged for posting this tx's calldata - l1GasPrice, _ := ps.L1BaseFeeEstimateWei() - l1BytePrice := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) - l1Fee := arbmath.BigMulByUint(l1BytePrice, uint64(l1Bytes)) - - // Adjust the price paid by the aggregator's reported improvements due to batching - ratio, _ := ps.AggregatorCompressionRatio(poster) - adjustedL1Fee := arbmath.BigMulByBips(l1Fee, ratio) - + pricePerUnit, _ := ps.PricePerUnit() + tx.PosterCost = arbmath.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028) tx.PosterIsReimbursable = true - tx.PosterCost = adjustedL1Fee } const TxFixedCost = 140 // assumed maximum size in bytes of a typical RLP-encoded tx, not including its calldata -func (ps *L1PricingState) PosterDataCost(message core.Message, sender, poster common.Address) (*big.Int, bool) { - +func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, bool) { if tx := message.UnderlyingTransaction(); tx != nil { if tx.PosterCost == nil { - ps.AddPosterInfo(tx, sender, poster) + ps.AddPosterInfo(tx, poster) } return tx.PosterCost, tx.PosterIsReimbursable } - if message.RunMode() == types.MessageGasEstimationMode { - // assume for the purposes of gas estimation that the poster will be the user's preferred aggregator - aggregator, _ := ps.ReimbursableAggregatorForSender(sender) - if aggregator != nil { - poster = *aggregator - } else { - // assume the user will use the delayed inbox since there's no reimbursable party - return big.NewInt(0), false - } - } - byteCount, err := byteCountAfterBrotli0(message.Data()) if err != nil { log.Error("failed to compress tx", "err", err) @@ -293,13 +393,9 @@ func (ps *L1PricingState) PosterDataCost(message core.Message, sender, poster co // Approximate the l1 fee charged for posting this tx's calldata l1Bytes := byteCount + TxFixedCost - l1GasPrice, _ := ps.L1BaseFeeEstimateWei() - l1BytePrice := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) - l1Fee := arbmath.BigMulByUint(l1BytePrice, uint64(l1Bytes)) + pricePerUnit, _ := ps.PricePerUnit() - // Adjust the price paid by the aggregator's reported improvements due to batching - ratio, _ := ps.AggregatorCompressionRatio(poster) - return arbmath.BigMulByBips(l1Fee, ratio), true + return arbmath.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028), true } func byteCountAfterBrotli0(input []byte) (uint64, error) { diff --git a/arbos/l1pricing/l1pricing_test.go b/arbos/l1pricing/l1pricing_test.go index d93720228a..9237f9b718 100644 --- a/arbos/l1pricing/l1pricing_test.go +++ b/arbos/l1pricing/l1pricing_test.go @@ -8,13 +8,11 @@ import ( "math/big" "testing" - "github.com/offchainlabs/nitro/arbos/burn" - "github.com/offchainlabs/nitro/arbos/storage" - "github.com/offchainlabs/nitro/util/arbmath" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/storage" ) func TestTxFixedCost(t *testing.T) { @@ -57,33 +55,16 @@ func TestL1PriceUpdate(t *testing.T) { Require(t, err) ps := OpenL1PricingState(sto) - tyme, err := ps.LastL1BaseFeeUpdateTime() + tyme, err := ps.LastUpdateTime() Require(t, err) if tyme != 0 { Fail(t) } - priceEstimate, err := ps.L1BaseFeeEstimateWei() - Require(t, err) - if priceEstimate.Cmp(big.NewInt(InitialL1BaseFeeEstimate)) != 0 { - Fail(t) - } - - newPrice := big.NewInt(20 * params.GWei) - ps.UpdatePricingModel(newPrice, 2) - priceEstimate, err = ps.L1BaseFeeEstimateWei() + initialPriceEstimate := big.NewInt(InitialPricePerUnitGwei * 1000000000) + priceEstimate, err := ps.PricePerUnit() Require(t, err) - - if priceEstimate.Cmp(newPrice) <= 0 || priceEstimate.Cmp(big.NewInt(InitialL1BaseFeeEstimate)) >= 0 { + if priceEstimate.Cmp(initialPriceEstimate) != 0 { Fail(t) } - - ps.UpdatePricingModel(newPrice, uint64(1)<<63) - priceEstimate, err = ps.L1BaseFeeEstimateWei() - Require(t, err) - - priceLimit := arbmath.BigAdd(newPrice, big.NewInt(300)) - if arbmath.BigGreaterThan(priceEstimate, priceLimit) || arbmath.BigLessThan(priceEstimate, newPrice) { - Fail(t, priceEstimate) - } } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 5a79fc77f8..41fe9c35a8 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -254,7 +254,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er var gasNeededToStartEVM uint64 gasPrice := p.evm.Context.BaseFee coinbase := p.evm.Context.Coinbase - posterCost, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, p.msg.From(), coinbase) + posterCost, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, coinbase) if p.msg.RunMode() == types.MessageGasEstimationMode { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. diff --git a/contracts/src/precompiles/ArbAggregator.sol b/contracts/src/precompiles/ArbAggregator.sol index 53958a6be2..707378090f 100644 --- a/contracts/src/precompiles/ArbAggregator.sol +++ b/contracts/src/precompiles/ArbAggregator.sol @@ -13,10 +13,6 @@ interface ArbAggregator { /// isDefault is true if addr is set to prefer the default aggregator function getPreferredAggregator(address addr) external view returns (address, bool); - /// @notice Set the caller's preferred aggregator. - /// @param prefAgg If prefAgg is zero, this sets the caller to prefer the default aggregator - function setPreferredAggregator(address prefAgg) external; - /// @notice Get default aggregator. function getDefaultAggregator() external view returns (address); @@ -25,17 +21,6 @@ interface ArbAggregator { /// @param newDefault New default aggregator function setDefaultAggregator(address newDefault) external; - /// @notice Get the aggregator's compression ratio - /// @param aggregator The aggregator to fetch the compression ratio for - /// @return The compression ratio, measured in basis points - function getCompressionRatio(address aggregator) external view returns (uint64); - - /// @notice Set the aggregator's compression ratio - /// This reverts unless called by the aggregator, its fee collector, or a chain owner - /// @param aggregator The aggregator to set the compression ratio for - /// @param ratio The compression ratio, measured in basis points - function setCompressionRatio(address aggregator, uint64 ratio) external; - /// @notice Get the address where fees to aggregator are sent. /// @param aggregator The aggregator to get the fee collector for /// @return The fee collectors address. This will often but not always be the same as the aggregator's address. diff --git a/contracts/src/precompiles/ArbOwner.sol b/contracts/src/precompiles/ArbOwner.sol index 375f352cb3..a0cdecc445 100644 --- a/contracts/src/precompiles/ArbOwner.sol +++ b/contracts/src/precompiles/ArbOwner.sol @@ -24,9 +24,6 @@ interface ArbOwner { /// @notice Retrieves the list of chain owners function getAllChainOwners() external view returns (address[] memory); - /// @notice Set the L1 basefee estimate directly, bypassing the autoregression - function setL1BaseFeeEstimate(uint256 priceInWei) external; - /// @notice Set how slowly ArbOS updates its estimate of the L1 basefee function setL1BaseFeeEstimateInertia(uint64 inertia) external; diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index d09e40afb3..d18d9d5fd1 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -123,7 +123,7 @@ func (n NodeInterface) EstimateRetryableTicket( pRetryTo = &to } - l1BaseFee, _ := c.State.L1PricingState().L1BaseFeeEstimateWei() + l1BaseFee, _ := c.State.L1PricingState().PricePerUnit() maxSubmissionFee := retryables.RetryableSubmissionFee(len(data), l1BaseFee) submitTx := &types.ArbitrumSubmitRetryableTx{ diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index 10e24f96b8..2c8051b9f4 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -125,12 +125,12 @@ func init() { log.Error("failed to open ArbOS state", "err", err) return } - poster, _ := state.L1PricingState().ReimbursableAggregatorForSender(msg.From()) - if poster == nil || header.BaseFee.Sign() == 0 { + poster, _ := state.L1PricingState().Sequencer() + if header.BaseFee.Sign() == 0 { // if gas is free or there's no reimbursable poster, the user won't pay for L1 data costs return } - posterCost, _ := state.L1PricingState().PosterDataCost(msg, msg.From(), *poster) + posterCost, _ := state.L1PricingState().PosterDataCost(msg, poster) posterCostInL2Gas := arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, header.BaseFee)) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } diff --git a/precompiles/ArbAggregator.go b/precompiles/ArbAggregator.go index f13fd84e19..a367a40192 100644 --- a/precompiles/ArbAggregator.go +++ b/precompiles/ArbAggregator.go @@ -5,12 +5,10 @@ package precompiles import ( "errors" - "math/big" - "github.com/ethereum/go-ethereum/common" + "math/big" "github.com/offchainlabs/nitro/arbos/arbosState" - "github.com/offchainlabs/nitro/util/arbmath" ) // Provides aggregators and their users methods for configuring how they participate in L1 aggregation. @@ -22,85 +20,58 @@ type ArbAggregator struct { // Gets an account's preferred aggregator func (con ArbAggregator) GetPreferredAggregator(c ctx, evm mech, address addr) (prefAgg addr, isDefault bool, err error) { - l1p := c.State.L1PricingState() - maybePrefAgg, err := l1p.UserSpecifiedAggregator(address) - if err != nil { - return common.Address{}, false, err - } - if maybePrefAgg != nil { - return *maybePrefAgg, false, nil - } - maybeReimbursableAgg, err := l1p.ReimbursableAggregatorForSender(address) - if err != nil || maybeReimbursableAgg == nil { - return common.Address{}, false, err - } - return *maybeReimbursableAgg, true, nil -} - -// Sets the caller's preferred aggregator to that provided -func (con ArbAggregator) SetPreferredAggregator(c ctx, evm mech, prefAgg addr) error { - var maybePrefAgg *common.Address - if prefAgg != (common.Address{}) { - maybePrefAgg = &prefAgg - } - return c.State.L1PricingState().SetUserSpecifiedAggregator(c.caller, maybePrefAgg) + sequencer, err := c.State.L1PricingState().Sequencer() + return sequencer, true, err } // Gets the chain's default aggregator func (con ArbAggregator) GetDefaultAggregator(c ctx, evm mech) (addr, error) { - return c.State.L1PricingState().DefaultAggregator() + return c.State.L1PricingState().Sequencer() } // Sets the chain's default aggregator (caller must be the current default aggregator, its fee collector, or an owner) func (con ArbAggregator) SetDefaultAggregator(c ctx, evm mech, newDefault addr) error { l1State := c.State.L1PricingState() - defaultAgg, err := l1State.DefaultAggregator() - if err != nil { - return err - } - allowed, err := accountIsAggregatorOrCollectorOrOwner(c.caller, defaultAgg, c.State) + allowed, err := accountIsSequencerOrCollectorOrOwner(c.caller, c.State) if err != nil { return err } if !allowed { return errors.New("Only the current default (or its fee collector / chain owner) may change the default") } - return l1State.SetDefaultAggregator(newDefault) -} - -// Get the aggregator's compression ratio, measured in basis points -func (con ArbAggregator) GetCompressionRatio(c ctx, evm mech, aggregator addr) (uint64, error) { - ratio, err := c.State.L1PricingState().AggregatorCompressionRatio(aggregator) - return uint64(ratio), err + return l1State.SetSequencer(newDefault) } -// Set the aggregator's compression ratio, measured in basis points -func (con ArbAggregator) SetCompressionRatio(c ctx, evm mech, aggregator addr, newRatio uint64) error { - allowed, err := accountIsAggregatorOrCollectorOrOwner(c.caller, aggregator, c.State) +// Gets an aggregator's fee collector +func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, aggregator addr) (addr, error) { + l1p := c.State.L1PricingState() + sequencer, err := l1p.Sequencer() if err != nil { - return err + return common.Address{}, err } - if !allowed { - return errors.New("Only an aggregator (or its fee collector / chain owner) may change its compression ratio") + if aggregator != sequencer { + return common.Address{}, nil } - return c.State.L1PricingState().SetAggregatorCompressionRatio(aggregator, arbmath.Bips(newRatio)) -} - -// Gets an aggregator's fee collector -func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, aggregator addr) (addr, error) { - return c.State.L1PricingState().AggregatorFeeCollector(aggregator) + return c.State.L1PricingState().PaySequencerFeesTo() } // Sets an aggregator's fee collector (caller must be the aggregator, its fee collector, or an owner) func (con ArbAggregator) SetFeeCollector(c ctx, evm mech, aggregator addr, newFeeCollector addr) error { - allowed, err := accountIsAggregatorOrCollectorOrOwner(c.caller, aggregator, c.State) + sequencer, err := c.State.L1PricingState().Sequencer() + if err != nil { + return err + } + if sequencer != aggregator { + return errors.New("Cannot set fee collector for non-sequencer address") + } + allowed, err := accountIsSequencerOrCollectorOrOwner(c.caller, c.State) if err != nil { return err } if !allowed { return errors.New("Only an aggregator (or its fee collector / chain owner) may change its fee collector") } - return c.State.L1PricingState().SetAggregatorFeeCollector(aggregator, newFeeCollector) + return c.State.L1PricingState().SetPaySequencerFeesTo(newFeeCollector) } // Gets an aggregator's current fixed fee to submit a tx @@ -115,12 +86,13 @@ func (con ArbAggregator) SetTxBaseFee(c ctx, evm mech, aggregator addr, feeInL1G return nil } -func accountIsAggregatorOrCollectorOrOwner(account, aggregator addr, state *arbosState.ArbosState) (bool, error) { - if account == aggregator { - return true, nil - } +func accountIsSequencerOrCollectorOrOwner(account addr, state *arbosState.ArbosState) (bool, error) { l1State := state.L1PricingState() - collector, err := l1State.AggregatorFeeCollector(aggregator) + sequencer, err := l1State.Sequencer() + if account == sequencer || err != nil { + return true, err + } + collector, err := l1State.PaySequencerFeesTo() if account == collector || err != nil { return true, err } diff --git a/precompiles/ArbAggregator_test.go b/precompiles/ArbAggregator_test.go index 156131a0cb..344d829422 100644 --- a/precompiles/ArbAggregator_test.go +++ b/precompiles/ArbAggregator_test.go @@ -43,10 +43,8 @@ func TestPreferredAggregator(t *testing.T) { userAddr := common.BytesToAddress(crypto.Keccak256([]byte{0})[:20]) defaultAggAddr := common.BytesToAddress(crypto.Keccak256([]byte{1})[:20]) - prefAggAddr := common.BytesToAddress(crypto.Keccak256([]byte{2})[:20]) callerCtx := testContext(common.Address{}, evm) - userCtx := testContext(userAddr, evm) // initial preferred aggregator should be the default of zero address res, isDefault, err := ArbAggregator{}.GetPreferredAggregator(callerCtx, evm, userAddr) @@ -71,26 +69,13 @@ func TestPreferredAggregator(t *testing.T) { if res != defaultAggAddr { Fail(t) } - - // set preferred aggregator - Require(t, agg.SetPreferredAggregator(userCtx, evm, prefAggAddr)) - - // preferred aggregator should now be prefAggAddr - res, isDefault, err = agg.GetPreferredAggregator(callerCtx, evm, userAddr) - Require(t, err) - if isDefault { - Fail(t) - } - if res != prefAggAddr { - Fail(t) - } } func TestFeeCollector(t *testing.T) { evm := newMockEVMForTesting() agg := ArbAggregator{} - aggAddr := common.BytesToAddress(crypto.Keccak256([]byte{0})[:20]) + aggAddr := l1pricing.SequencerAddress collectorAddr := common.BytesToAddress(crypto.Keccak256([]byte{1})[:20]) impostorAddr := common.BytesToAddress(crypto.Keccak256([]byte{2})[:20]) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 7c49c3442a..461ec2fb4b 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -6,8 +6,6 @@ package precompiles import ( "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/storage" @@ -28,22 +26,17 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator( evm mech, aggregator addr, ) (huge, huge, huge, huge, huge, huge, error) { - l1GasPrice, err := c.State.L1PricingState().L1BaseFeeEstimateWei() + l1GasPrice, err := c.State.L1PricingState().PricePerUnit() if err != nil { return nil, nil, nil, nil, nil, nil, err } l2GasPrice := evm.Context.BaseFee - ratio, err := c.State.L1PricingState().AggregatorCompressionRatio(aggregator) - if err != nil { - return nil, nil, nil, nil, nil, nil, err - } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) - perL1CalldataUnit := arbmath.BigDivByUint(arbmath.BigMulByBips(weiForL1Calldata, ratio), 16) // the cost of a simple tx without calldata - perL2Tx := arbmath.BigMulByUint(perL1CalldataUnit, 16*l1pricing.TxFixedCost) + perL2Tx := arbmath.BigMulByUint(weiForL1Calldata, l1pricing.TxFixedCost) // nitro's compute-centric l2 gas pricing has no special compute component that rises independently perArbGasBase := l2GasPrice @@ -52,37 +45,29 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator( weiForL2Storage := arbmath.BigMul(l2GasPrice, storageArbGas) - return perL2Tx, perL1CalldataUnit, weiForL2Storage, perArbGasBase, perArbGasCongestion, perArbGasTotal, nil + return perL2Tx, weiForL1Calldata, weiForL2Storage, perArbGasBase, perArbGasCongestion, perArbGasTotal, nil } // Get prices in wei when using the caller's preferred aggregator func (con ArbGasInfo) GetPricesInWei(c ctx, evm mech) (huge, huge, huge, huge, huge, huge, error) { - maybeAggregator, err := c.State.L1PricingState().ReimbursableAggregatorForSender(c.caller) + maybeAggregator, err := c.State.L1PricingState().Sequencer() if err != nil { return nil, nil, nil, nil, nil, nil, err } - if maybeAggregator == nil { - return con.GetPricesInWeiWithAggregator(c, evm, common.Address{}) - } - return con.GetPricesInWeiWithAggregator(c, evm, *maybeAggregator) + return con.GetPricesInWeiWithAggregator(c, evm, maybeAggregator) } // Get prices in ArbGas when using the provided aggregator func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregator addr) (huge, huge, huge, error) { - l1GasPrice, err := c.State.L1PricingState().L1BaseFeeEstimateWei() + l1GasPrice, err := c.State.L1PricingState().PricePerUnit() if err != nil { return nil, nil, nil, err } l2GasPrice := evm.Context.BaseFee - ratio, err := c.State.L1PricingState().AggregatorCompressionRatio(aggregator) - if err != nil { - return nil, nil, nil, err - } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) - compressedCharge := arbmath.BigMulByBips(weiForL1Calldata, ratio) - gasForL1Calldata := arbmath.BigDiv(compressedCharge, l2GasPrice) + gasForL1Calldata := arbmath.BigDiv(weiForL1Calldata, l2GasPrice) perL2Tx := big.NewInt(l1pricing.TxFixedCost) return perL2Tx, gasForL1Calldata, storageArbGas, nil @@ -90,14 +75,11 @@ func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregato // Get prices in ArbGas when using the caller's preferred aggregator func (con ArbGasInfo) GetPricesInArbGas(c ctx, evm mech) (huge, huge, huge, error) { - maybeAggregator, err := c.State.L1PricingState().ReimbursableAggregatorForSender(c.caller) + maybeAggregator, err := c.State.L1PricingState().Sequencer() if err != nil { return nil, nil, nil, err } - if maybeAggregator == nil { - return con.GetPricesInArbGasWithAggregator(c, evm, common.Address{}) - } - return con.GetPricesInArbGasWithAggregator(c, evm, *maybeAggregator) + return con.GetPricesInArbGasWithAggregator(c, evm, maybeAggregator) } // Get the rollup's speed limit, pool size, and tx gas limit @@ -143,17 +125,19 @@ func (con ArbGasInfo) GetRateEstimateInertia(c ctx, evm mech) (uint64, error) { // Get the current estimate of the L1 basefee func (con ArbGasInfo) GetL1BaseFeeEstimate(c ctx, evm mech) (huge, error) { - return c.State.L1PricingState().L1BaseFeeEstimateWei() + reward, err := c.State.L1PricingState().PerUnitReward() + return arbmath.UintToBig(reward), err } // Get how slowly ArbOS updates its estimate of the L1 basefee func (con ArbGasInfo) GetL1BaseFeeEstimateInertia(c ctx, evm mech) (uint64, error) { - return c.State.L1PricingState().L1BaseFeeEstimateInertia() + return c.State.L1PricingState().Inertia() } -// Deprecated -- Same as getL1BaseFeeEstimate() +// Get the current estimate of the L1 basefee func (con ArbGasInfo) GetL1GasPriceEstimate(c ctx, evm mech) (huge, error) { - return con.GetL1BaseFeeEstimate(c, evm) + reward, err := c.State.L1PricingState().PerUnitReward() + return arbmath.UintToBig(reward), err } // Get the fee paid to the aggregator for posting this tx diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index e9ec187a38..a1d96fdcc2 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -20,6 +20,10 @@ type ArbOwner struct { OwnerActsGasCost func(bytes4, addr, []byte) (uint64, error) } +var ( + ErrOutOfBounds = errors.New("value out of bounds") +) + // Add account as a chain owner func (con ArbOwner) AddChainOwner(c ctx, evm mech, newOwner addr) error { return c.State.ChainOwners().Add(newOwner) @@ -44,14 +48,9 @@ func (con ArbOwner) GetAllChainOwners(c ctx, evm mech) ([]common.Address, error) return c.State.ChainOwners().AllMembers() } -// Sets the L1 basefee estimate directly, bypassing the autoregression -func (con ArbOwner) SetL1BaseFeeEstimate(c ctx, evm mech, priceInWei huge) error { - return c.State.L1PricingState().SetL1BaseFeeEstimateWei(priceInWei) -} - // Set how slowly ArbOS updates its estimate of the L1 basefee func (con ArbOwner) SetL1BaseFeeEstimateInertia(c ctx, evm mech, inertia uint64) error { - return c.State.L1PricingState().SetL1BaseFeeEstimateInertia(inertia) + return c.State.L1PricingState().SetInertia(inertia) } // Sets the L2 gas price directly, bypassing the pool calculus diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index e292ecf989..7ec74dd925 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -13,97 +13,10 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" - "github.com/offchainlabs/nitro/util/colors" - "github.com/offchainlabs/nitro/util/testhelpers" ) -func TestTips(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - l2info, _, l2client, l1info, _, l1client, stack := CreateTestNodeOnL1(t, ctx, true) - defer stack.Close() - - auth := l2info.GetDefaultTransactOpts("Owner", ctx) - callOpts := l2info.GetDefaultCallOpts("Owner", ctx) - aggregator := testhelpers.RandomAddress() - - // get the network fee account - arbOwnerPublic, err := precompilesgen.NewArbOwnerPublic(common.HexToAddress("0x6b"), l2client) - Require(t, err, "could not deploy ArbOwner contract") - networkFeeAccount, err := arbOwnerPublic.GetNetworkFeeAccount(callOpts) - Require(t, err, "could not get the network fee account") - - // set a preferred aggregator who won't be the one to post the tx - arbAggregator, err := precompilesgen.NewArbAggregator(common.HexToAddress("0x6d"), l2client) - Require(t, err, "could not deploy ArbAggregator contract") - tx, err := arbAggregator.SetPreferredAggregator(&auth, aggregator) - Require(t, err, "could not set L2 gas price") - _, err = EnsureTxSucceeded(ctx, l2client, tx) - Require(t, err) - - basefee := GetBaseFee(t, l2client, ctx) - auth.GasFeeCap = arbmath.BigMulByUfrac(basefee, 5, 4) // add room for a 20% tip - auth.GasTipCap = arbmath.BigMulByUfrac(basefee, 1, 4) // add a 20% tip - - networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount) - - // use L1 to post a message since the sequencer won't do it - nosend := auth - nosend.NoSend = true - tx, err = arbAggregator.SetPreferredAggregator(&nosend, aggregator) - Require(t, err) - receipt := SendSignedTxViaL1(t, ctx, l1info, l1client, l2client, tx) - if receipt.Status != types.ReceiptStatusSuccessful { - Fail(t, "failed to prefer the sequencer") - } - - networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount) - colors.PrintMint("network: ", networkFeeAccount, networkBefore, networkAfter) - colors.PrintBlue("pricing: ", l2info.GasPrice, auth.GasFeeCap, auth.GasTipCap) - colors.PrintBlue("payment: ", tx.GasPrice(), tx.GasFeeCap(), tx.GasTipCap()) - - if !arbmath.BigEquals(tx.GasPrice(), auth.GasFeeCap) { - Fail(t, "user did not pay the tip") - } - - tip := arbmath.BigMulByUint(arbmath.BigSub(tx.GasPrice(), basefee), receipt.GasUsed) - full := arbmath.BigMulByUint(basefee, receipt.GasUsed) // was gasprice before upgrade - networkRevenue := arbmath.BigSub(networkAfter, networkBefore) - colors.PrintMint("price: ", tip, full, networkRevenue) - colors.PrintRed("used: ", receipt.GasUsed, basefee) - - if !arbmath.BigEquals(full, networkRevenue) { - Fail(t, "the network didn't receive the funds") - } -} - -// Test that the sequencer won't subvert a user's aggregation preferences -func TestSequencerWontPostWhenNotPreferred(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - l2info, _, client := CreateTestL2(t, ctx) - auth := l2info.GetDefaultTransactOpts("Owner", ctx) - - // prefer a 3rd party aggregator - arbAggregator, err := precompilesgen.NewArbAggregator(common.HexToAddress("0x6d"), client) - Require(t, err, "could not deploy ArbAggregator contract") - tx, err := arbAggregator.SetPreferredAggregator(&auth, testhelpers.RandomAddress()) - Require(t, err, "could not set L2 gas price") - _, err = EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - // get the network fee account - _, err = arbAggregator.SetPreferredAggregator(&auth, testhelpers.RandomAddress()) - colors.PrintBlue("expecting error: ", err) - if err == nil { - Fail(t, "the sequencer should have rejected this tx") - } -} - func TestSequencerFeePaid(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/util/arbmath/math.go b/util/arbmath/math.go index 6d1a561af8..9bfbcbe308 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -28,6 +28,14 @@ func MinInt(value, ceiling int64) int64 { return value } +// the minimum of two ints +func MinUint(value, ceiling uint64) uint64 { + if value > ceiling { + return ceiling + } + return value +} + // the maximum of two ints func MaxInt(value, floor int64) int64 { if value < floor { From a73e4150bb9f7494e48be627c478172ed6fa7d9a Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 9 May 2022 08:46:13 -0400 Subject: [PATCH 02/61] Fix bug --- precompiles/ArbGasInfo.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 461ec2fb4b..7f59e77144 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -125,8 +125,7 @@ func (con ArbGasInfo) GetRateEstimateInertia(c ctx, evm mech) (uint64, error) { // Get the current estimate of the L1 basefee func (con ArbGasInfo) GetL1BaseFeeEstimate(c ctx, evm mech) (huge, error) { - reward, err := c.State.L1PricingState().PerUnitReward() - return arbmath.UintToBig(reward), err + return c.State.L1PricingState().PricePerUnit() } // Get how slowly ArbOS updates its estimate of the L1 basefee @@ -136,8 +135,7 @@ func (con ArbGasInfo) GetL1BaseFeeEstimateInertia(c ctx, evm mech) (uint64, erro // Get the current estimate of the L1 basefee func (con ArbGasInfo) GetL1GasPriceEstimate(c ctx, evm mech) (huge, error) { - reward, err := c.State.L1PricingState().PerUnitReward() - return arbmath.UintToBig(reward), err + return c.State.L1PricingState().PricePerUnit() } // Get the fee paid to the aggregator for posting this tx From b47a90784af23502735a047f648af67753e2a13a Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 9 May 2022 11:19:02 -0400 Subject: [PATCH 03/61] Track collected funds in special account --- arbos/block_processor.go | 6 +- arbos/l1pricing/l1pricing.go | 111 +++++++++++++++++------------------ 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index be824ca726..e24afae3f8 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -6,6 +6,7 @@ package arbos import ( "encoding/binary" "fmt" + "github.com/offchainlabs/nitro/arbos/l1pricing" "math" "math/big" "strconv" @@ -45,7 +46,10 @@ func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState coinbase := common.Address{} if l1info != nil { timestamp = l1info.l1Timestamp - coinbase = l1info.poster + sequencer, _ := state.L1PricingState().Sequencer() + if l1info.poster == sequencer { + coinbase = l1pricing.L1PricerFundsPoolAddress + } } if prevHeader != nil { lastBlockHash = prevHeader.Hash() diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 81a930b1eb..7033fabb45 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -30,14 +30,14 @@ type L1PricingState struct { inertia storage.StorageBackedUint64 perUnitReward storage.StorageBackedUint64 // variables - currentTime storage.StorageBackedUint64 - lastUpdateTime storage.StorageBackedUint64 - availableFunds storage.StorageBackedUint64 - fundsDueToSequencer storage.StorageBackedUint64 - fundsDueForRewards storage.StorageBackedUint64 - collectedSinceUpdate storage.StorageBackedUint64 - unitsSinceUpdate storage.StorageBackedUint64 - pricePerUnit storage.StorageBackedBigInt + currentTime storage.StorageBackedUint64 + lastUpdateTime storage.StorageBackedUint64 + availableFunds storage.StorageBackedBigInt + fundsDueToSequencer storage.StorageBackedBigInt + fundsDueForRewards storage.StorageBackedBigInt + // funds collected since update are recorded as the balance in account L1PricerFundsPoolAddress + unitsSinceUpdate storage.StorageBackedUint64 + pricePerUnit storage.StorageBackedBigInt } var ( @@ -59,7 +59,6 @@ const ( availableFundsOffset fundsDueToSequencerOffset fundsDueForRewards - collectedSinceOffset unitsSinceOffset pricePerUnitOffset ) @@ -105,10 +104,9 @@ func OpenL1PricingState(sto *storage.Storage) *L1PricingState { sto.OpenStorageBackedUint64(perUnitRewardOffset), sto.OpenStorageBackedUint64(currentTimeOffset), sto.OpenStorageBackedUint64(lastUpdateTimeOffset), - sto.OpenStorageBackedUint64(availableFundsOffset), - sto.OpenStorageBackedUint64(fundsDueToSequencerOffset), - sto.OpenStorageBackedUint64(fundsDueForRewards), - sto.OpenStorageBackedUint64(collectedSinceOffset), + sto.OpenStorageBackedBigInt(availableFundsOffset), + sto.OpenStorageBackedBigInt(fundsDueToSequencerOffset), + sto.OpenStorageBackedBigInt(fundsDueForRewards), sto.OpenStorageBackedUint64(unitsSinceOffset), sto.OpenStorageBackedBigInt(pricePerUnitOffset), } @@ -166,38 +164,30 @@ func (ps *L1PricingState) SetLastUpdateTime(t uint64) error { return ps.lastUpdateTime.Set(t) } -func (ps *L1PricingState) AvailableFunds() (uint64, error) { +func (ps *L1PricingState) AvailableFunds() (*big.Int, error) { return ps.availableFunds.Get() } -func (ps *L1PricingState) SetAvailableFunds(amt uint64) error { +func (ps *L1PricingState) SetAvailableFunds(amt *big.Int) error { return ps.availableFunds.Set(amt) } -func (ps *L1PricingState) FundsDueToSequencer() (uint64, error) { +func (ps *L1PricingState) FundsDueToSequencer() (*big.Int, error) { return ps.fundsDueToSequencer.Get() } -func (ps *L1PricingState) SetFundsDueToSequencer(amt uint64) error { +func (ps *L1PricingState) SetFundsDueToSequencer(amt *big.Int) error { return ps.fundsDueToSequencer.Set(amt) } -func (ps *L1PricingState) FundsDueForRewards() (uint64, error) { +func (ps *L1PricingState) FundsDueForRewards() (*big.Int, error) { return ps.fundsDueForRewards.Get() } -func (ps *L1PricingState) SetFundsDueForRewards(amt uint64) error { +func (ps *L1PricingState) SetFundsDueForRewards(amt *big.Int) error { return ps.fundsDueForRewards.Set(amt) } -func (ps *L1PricingState) CollectedSinceUpdate() (uint64, error) { - return ps.collectedSinceUpdate.Get() -} - -func (ps *L1PricingState) SetCollectedSinceUpdate(amt uint64) error { - return ps.collectedSinceUpdate.Set(amt) -} - func (ps *L1PricingState) UnitsSinceUpdate() (uint64, error) { return ps.unitsSinceUpdate.Get() } @@ -220,7 +210,7 @@ func (ps *L1PricingState) UpdateTime(currentTime uint64) { } // Update the pricing model based on a payment by the sequencer -func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent uint64) error { +func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { // compute previous shortfall fundsDueToSequencer, err := ps.FundsDueToSequencer() if err != nil { @@ -234,7 +224,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err != nil { return err } - oldShortfall := int64(fundsDueToSequencer) + int64(fundsDueForRewards) - int64(availableFunds) + oldShortfall := new(big.Int).Sub(new(big.Int).Add(fundsDueToSequencer, fundsDueForRewards), availableFunds) // compute allocation fraction lastUpdateTime, err := ps.LastUpdateTime() @@ -244,42 +234,37 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { return ErrInvalidTime } - allocFractionNum := updateTime - lastUpdateTime - allocFractionDenom := currentTime - lastUpdateTime + allocFractionNum := big.NewInt(int64(updateTime - lastUpdateTime)) + allocFractionDenom := big.NewInt(int64(currentTime - lastUpdateTime)) // allocate units to this update unitsSinceUpdate, err := ps.UnitsSinceUpdate() if err != nil { return err } - unitsAllocated := unitsSinceUpdate * allocFractionNum / allocFractionDenom + unitsAllocated := unitsSinceUpdate * (updateTime - lastUpdateTime) / (currentTime - lastUpdateTime) unitsSinceUpdate -= unitsAllocated if err := ps.SetUnitsSinceUpdate(unitsSinceUpdate); err != nil { return err } // allocate funds to this update - collectedSinceUpdate, err := ps.CollectedSinceUpdate() - if err != nil { - return err - } - fundsToMove := collectedSinceUpdate * allocFractionNum / allocFractionDenom - collectedSinceUpdate -= fundsToMove - if err := ps.SetCollectedSinceUpdate(collectedSinceUpdate); err != nil { - return err - } - availableFunds += fundsToMove + collectedSinceUpdate := stateDb.GetBalance(L1PricerFundsPoolAddress) + fundsToMove := new(big.Int).Div(new(big.Int).Mul(collectedSinceUpdate, allocFractionNum), allocFractionDenom) + stateDb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) + availableFunds = new(big.Int).Add(availableFunds, fundsToMove) // update amounts due perUnitReward, err := ps.PerUnitReward() if err != nil { return err } - fundsDueToSequencer += weiSpent + fundsDueToSequencer = new(big.Int).Add(fundsDueToSequencer, weiSpent) if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - fundsDueForRewards += perUnitReward * allocFractionNum / allocFractionDenom + newRewards := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(perUnitReward)), allocFractionNum), allocFractionDenom) + fundsDueForRewards = new(big.Int).Add(fundsDueForRewards, newRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } @@ -289,33 +274,39 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err != nil { return err } - paymentForRewards := arbmath.MinUint(availableFunds, fundsDueForRewards) - if paymentForRewards > 0 { - availableFunds -= paymentForRewards + paymentForRewards := availableFunds + if fundsDueForRewards.Cmp(paymentForRewards) < 0 { + paymentForRewards = fundsDueForRewards + } + if paymentForRewards.Sign() > 0 { + availableFunds = new(big.Int).Sub(availableFunds, paymentForRewards) if err := ps.SetAvailableFunds(availableFunds); err != nil { return err } - fundsDueForRewards -= paymentForRewards + fundsDueForRewards = new(big.Int).Sub(fundsDueForRewards, paymentForRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } - core.Transfer(stateDb, L1PricerFundsPoolAddress, payRewardsTo, arbmath.UintToBig(paymentForRewards)) + core.Transfer(stateDb, L1PricerFundsPoolAddress, payRewardsTo, paymentForRewards) } sequencerPaymentAddr, err := ps.Sequencer() if err != nil { return err } - paymentForSequencer := arbmath.MinUint(availableFunds, fundsDueToSequencer) - if paymentForSequencer > 0 { - availableFunds -= paymentForSequencer + paymentForSequencer := availableFunds + if fundsDueToSequencer.Cmp(paymentForSequencer) < 0 { + paymentForSequencer = fundsDueToSequencer + } + if paymentForSequencer.Sign() > 0 { + availableFunds = new(big.Int).Sub(availableFunds, paymentForSequencer) if err := ps.SetAvailableFunds(availableFunds); err != nil { return err } - fundsDueToSequencer -= paymentForSequencer + fundsDueToSequencer = new(big.Int).Sub(fundsDueToSequencer, paymentForSequencer) if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - core.Transfer(stateDb, L1PricerFundsPoolAddress, sequencerPaymentAddr, arbmath.UintToBig(paymentForSequencer)) + core.Transfer(stateDb, L1PricerFundsPoolAddress, sequencerPaymentAddr, paymentForSequencer) } // update time @@ -325,7 +316,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT // adjust the price if unitsAllocated > 0 { - shortfall := int64(fundsDueToSequencer) + int64(fundsDueForRewards) - int64(availableFunds) + shortfall := new(big.Int).Sub(new(big.Int).Add(fundsDueToSequencer, fundsDueForRewards), availableFunds) inertia, err := ps.Inertia() if err != nil { return err @@ -334,12 +325,18 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err != nil { return err } - fdenom := unitsAllocated + equilTime/inertia + fdenom := big.NewInt(int64(unitsAllocated + equilTime/inertia)) price, err := ps.PricePerUnit() if err != nil { return err } - newPrice := new(big.Int).Sub(price, big.NewInt((shortfall-oldShortfall)/int64(fdenom)+shortfall/int64(equilTime*fdenom))) + newPrice := new(big.Int).Sub( + price, + new(big.Int).Add( + new(big.Int).Div(new(big.Int).Sub(shortfall, oldShortfall), fdenom), + new(big.Int).Div(shortfall, new(big.Int).Mul(big.NewInt(int64(equilTime)), fdenom)), + ), + ) if newPrice.Sign() >= 0 { price = newPrice } else { From d4ba8405099e3a5106ba776dd6f083b0bb84633b Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 9 May 2022 12:29:38 -0400 Subject: [PATCH 04/61] Fix test --- system_tests/seqcompensation_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index 56b70a9cb1..7af09e14bd 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -13,7 +13,7 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" ) -// Sequencer address gets something for posting batches +// L1 Pricer pool address gets something when the sequencer posts batches func TestSequencerCompensation(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -55,7 +55,7 @@ func TestSequencerCompensation(t *testing.T) { if initialSeqBalance.Sign() != 0 { Fail(t, "Unexpected initial sequencer balance:", initialSeqBalance) } - finalSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.SequencerAddress, nil) + finalSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.L1PricerFundsPoolAddress, nil) Require(t, err) if finalSeqBalance.Sign() <= 0 { Fail(t, "Unexpected final sequencer balance:", finalSeqBalance) From 84da1a364a747f8c37d0783345791e7230cb60b5 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 16 May 2022 08:16:38 -0400 Subject: [PATCH 05/61] Merge integration --- arbnode/api.go | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/arbnode/api.go b/arbnode/api.go index a0389fe7bf..322d8e85b1 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -164,7 +164,6 @@ type PricingModelHistory struct { GasBacklog []uint64 `json:"gasBacklog"` GasUsed []uint64 `json:"gasUsed"` L1BaseFeeEstimate []*big.Int `json:"l1BaseFeeEstimate"` - L1BaseFeeUpdateTime []uint64 `json:"l1BaseFeeUpdateTime"` MinBaseFee *big.Int `json:"minBaseFee"` SpeedLimit uint64 `json:"speedLimit"` MaxPerBlockGasLimit uint64 `json:"maxPerBlockGasLimit"` @@ -183,25 +182,12 @@ func (api *ArbDebugAPI) PricingModel(ctx context.Context, start, end rpc.BlockNu } history := PricingModelHistory{ - First: uint64(start), - Timestamp: make([]uint64, blocks), - BaseFee: make([]*big.Int, blocks), - GasBacklog: make([]uint64, blocks), - GasUsed: make([]uint64, blocks), - L1BaseFeeEstimate: make([]*big.Int, blocks), - L1BaseFeeUpdateTime: make([]uint64, blocks+1), - } - - if start > core.NitroGenesisBlock { - state, _, err := stateAndHeader(api.blockchain, uint64(start)-1) - if err != nil { - return history, err - } - l1BaseFeeUpdateTime, err := state.L1PricingState().LastL1BaseFeeUpdateTime() - if err != nil { - return history, err - } - history.L1BaseFeeUpdateTime[0] = l1BaseFeeUpdateTime + First: uint64(start), + Timestamp: make([]uint64, blocks), + BaseFee: make([]*big.Int, blocks), + GasBacklog: make([]uint64, blocks), + GasUsed: make([]uint64, blocks), + L1BaseFeeEstimate: make([]*big.Int, blocks), } for i := uint64(0); i < uint64(blocks); i++ { @@ -221,21 +207,16 @@ func (api *ArbDebugAPI) PricingModel(ctx context.Context, start, end rpc.BlockNu } gasBacklog, _ := l2Pricing.GasBacklog() - l1BaseFeeEstimate, _ := l1Pricing.L1BaseFeeEstimateWei() - l1BaseFeeUpdateTime, err := l1Pricing.LastL1BaseFeeUpdateTime() - if err != nil { - return history, err - } + l1BaseFeeEstimate, _ := l1Pricing.PricePerUnit() history.GasBacklog[i] = gasBacklog history.GasUsed[i] = header.GasUsed history.L1BaseFeeEstimate[i] = l1BaseFeeEstimate - history.L1BaseFeeUpdateTime[i+1] = l1BaseFeeUpdateTime if i == uint64(blocks)-1 { speedLimit, _ := l2Pricing.SpeedLimitPerSecond() maxPerBlockGasLimit, _ := l2Pricing.MaxPerBlockGasLimit() - l1BaseFeeEstimateInertia, err := l1Pricing.L1BaseFeeEstimateInertia() + l1BaseFeeEstimateInertia, err := l1Pricing.Inertia() minBaseFee, _ := l2Pricing.MinBaseFeeWei() pricingInertia, _ := l2Pricing.PricingInertia() backlogTolerance, _ := l2Pricing.BacklogTolerance() From cbd177d2c45150f314548077f1a5ea54214d5b5d Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 16 May 2022 09:11:27 -0400 Subject: [PATCH 06/61] Disable obsolete tests --- system_tests/fees_test.go | 12 +++++------- system_tests/seqcompensation_test.go | 8 +++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 7ec74dd925..5fb17f1c38 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -49,9 +49,6 @@ func TestSequencerFeePaid(t *testing.T) { gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1 - if !arbmath.BigEquals(seqRevenue, arbmath.BigMulByUint(tx.GasPrice(), receipt.GasUsedForL1)) { - Fail(t, "sequencer didn't receive expected payment") - } if !arbmath.BigEquals(networkRevenue, arbmath.BigMulByUint(tx.GasPrice(), gasUsedForL2)) { Fail(t, "network didn't receive expected payment") } @@ -63,8 +60,9 @@ func TestSequencerFeePaid(t *testing.T) { compressed, err := arbcompress.CompressFast(txBin) Require(t, err) - if uint64(len(compressed)) != paidBytes { - t.Fatal("unexpected number of bytes paid for") - } - + _ = paidBytes + _ = compressed + // if uint64(len(compressed)) != paidBytes { + // t.Fatal("unexpected number of bytes paid for") + //} } diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index 7af09e14bd..e59e9a80e0 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -57,8 +57,10 @@ func TestSequencerCompensation(t *testing.T) { } finalSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.L1PricerFundsPoolAddress, nil) Require(t, err) - if finalSeqBalance.Sign() <= 0 { - Fail(t, "Unexpected final sequencer balance:", finalSeqBalance) - } + // Comment out this check, because new scheme doesn't reimburse sequencer immediately + // if finalSeqBalance.Sign() <= 0 { + // Fail(t, "Unexpected final sequencer balance:", finalSeqBalance) + //} + _ = finalSeqBalance } From ea25cdc8c49a27e8298a34c0c584cf9c2d14ebb1 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 16 May 2022 12:38:45 -0400 Subject: [PATCH 07/61] Add ArbitrumBatchPostingReportTx type --- arbos/incomingmessage.go | 30 ++++++++++++++++++++++++++++++ go-ethereum | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index 83c486b5f5..f0c1d4973a 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -29,6 +29,7 @@ const ( L1MessageType_BatchForGasEstimation = 10 // probably won't use this in practice L1MessageType_Initialize = 11 L1MessageType_EthDeposit = 12 + L1MessageType_BatchPostingReport = 13 L1MessageType_Invalid = 0xFF ) @@ -220,6 +221,12 @@ func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int) (types.Trans case L1MessageType_RollupEvent: log.Debug("ignoring rollup event message") return types.Transactions{}, nil + case L1MessageType_BatchPostingReport: + tx, err := parseBatchPostingReportMessage(bytes.NewReader(msg.L2msg), chainId) + if err != nil { + return nil, err + } + return types.Transactions{tx}, nil case L1MessageType_Invalid: // intentionally invalid message return nil, errors.New("invalid message") @@ -495,3 +502,26 @@ func parseSubmitRetryableMessage(rd io.Reader, header *L1IncomingMessageHeader, } return types.NewTx(tx), err } + +func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Transaction, error) { + batchPosterAddr, err := util.AddressFrom256FromReader(rd) + if err != nil { + return nil, err + } + batchNum, err := util.HashFromReader(rd) + if err != nil { + return nil, err + } + + l1BaseFee, err := util.HashFromReader(rd) + if err != nil { + return nil, err + } + tx := &types.ArbitrumBatchPostingReportTx{ + ChainId: chainId, + BatchPosterAddr: batchPosterAddr, + BatchNum: batchNum.Big(), + L1BaseFee: l1BaseFee.Big(), + } + return types.NewTx(tx), nil +} diff --git a/go-ethereum b/go-ethereum index 8399804607..605e3da491 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 839980460785d90edd4015becaa154e2b2dfb3a7 +Subproject commit 605e3da49185b180991ae94f4564f7096b78564b From 9884f3b57e87b39885df9da05070e91b776f8927 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 16 May 2022 16:31:07 -0400 Subject: [PATCH 08/61] Update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 605e3da491..90212f3b98 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 605e3da49185b180991ae94f4564f7096b78564b +Subproject commit 90212f3b989a8af1c3826ee135534125a2271266 From bcb6dabd0ab55d43764eff69251a349d21d67cf1 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 28 May 2022 08:51:58 -0400 Subject: [PATCH 09/61] Restore geth pin to geth master --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 90212f3b98..d56aba49b8 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 90212f3b989a8af1c3826ee135534125a2271266 +Subproject commit d56aba49b86a299e3e0305640e5c046917eb9d74 From d86a7e3b0fc57f758ca6c13b064977f88360cfda Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 28 May 2022 08:58:19 -0400 Subject: [PATCH 10/61] Add ArbitrumInternalTx type for batch posting report --- arbos/incomingmessage.go | 14 ++++++++------ arbos/internal_tx.go | 3 ++- arbos/util/util.go | 3 +++ contracts/src/precompiles/ArbosActs.sol | 6 ++++++ precompiles/ArbosActs.go | 4 ++++ 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index f0c1d4973a..de1ace1477 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -517,11 +517,13 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Tran if err != nil { return nil, err } - tx := &types.ArbitrumBatchPostingReportTx{ - ChainId: chainId, - BatchPosterAddr: batchPosterAddr, - BatchNum: batchNum.Big(), - L1BaseFee: l1BaseFee.Big(), + data, err := util.PackInternalTxDataBatchPostingReport(batchPosterAddr, batchNum, l1BaseFee) + if err != nil { + return nil, err } - return types.NewTx(tx), nil + return types.NewTx(&types.ArbitrumInternalTx{ + ChainId: chainId, + SubType: arbInternalTxBatchPostReport, + Data: data, + }), nil } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index e1c15a1f3d..0ff852bf95 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -17,7 +17,8 @@ import ( // Types of ArbitrumInternalTx, distinguished by the first data byte const ( // Contains 8 bytes indicating the big endian L1 block number to set - arbInternalTxStartBlock uint8 = 0 + arbInternalTxStartBlock uint8 = 0 + arbInternalTxBatchPostReport uint8 = 1 ) func InternalTxStartBlock( diff --git a/arbos/util/util.go b/arbos/util/util.go index 55c10b0b93..75b48b5d8c 100644 --- a/arbos/util/util.go +++ b/arbos/util/util.go @@ -24,6 +24,8 @@ var ParseL2ToL1TransactionLog func(interface{}, *types.Log) error var ParseL2ToL1TxLog func(interface{}, *types.Log) error var PackInternalTxDataStartBlock func(...interface{}) ([]byte, error) var UnpackInternalTxDataStartBlock func([]byte) ([]interface{}, error) +var PackInternalTxDataBatchPostingReport func(...interface{}) ([]byte, error) +var UnpackInternalTxDataBatchPostingReport func([]byte) ([]interface{}, error) var PackArbRetryableTxRedeem func(...interface{}) ([]byte, error) func init() { @@ -88,6 +90,7 @@ func init() { acts := precompilesgen.ArbosActsABI PackInternalTxDataStartBlock, UnpackInternalTxDataStartBlock = callParser(acts, "startBlock") + PackInternalTxDataBatchPostingReport, UnpackInternalTxDataBatchPostingReport = callParser(acts, "batchPostingReport") PackArbRetryableTxRedeem, _ = callParser(precompilesgen.ArbRetryableTxABI, "redeem") } diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index c4258fab88..4b3969f7e7 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -37,5 +37,11 @@ interface ArbosActs { uint64 timePassed ) external; + function batchPostingReport( + address batchPosterAddress, + uint256 batchNumber, + uint256 l1BaseFeeWei + ) external; + error CallerNotArbOS(); } diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index 9bc84d94d6..f517c2331b 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -14,3 +14,7 @@ type ArbosActs struct { func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock huge, l1BlockNumber, timeLastBlock uint64) error { return con.CallerNotArbOSError() } + +func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchPosterAddress addr, batchNumber, l1BaseFeeWei huge) error { + return con.CallerNotArbOSError() +} From c35f27344a3544d7233967d2b23ad9e8f72439bc Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 28 May 2022 14:11:38 -0400 Subject: [PATCH 11/61] Add method to ArbosActs --- arbos/incomingmessage.go | 6 +- arbos/internal_tx.go | 103 ++++++++++++++++-------- arbos/tx_processor.go | 4 +- contracts/src/precompiles/ArbosActs.sol | 1 + precompiles/ArbosActs.go | 2 +- 5 files changed, 80 insertions(+), 36 deletions(-) diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index de1ace1477..20b58f5454 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -504,6 +504,10 @@ func parseSubmitRetryableMessage(rd io.Reader, header *L1IncomingMessageHeader, } func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Transaction, error) { + batchTimestamp, err := util.HashFromReader(rd) + if err != nil { + return nil, err + } batchPosterAddr, err := util.AddressFrom256FromReader(rd) if err != nil { return nil, err @@ -517,7 +521,7 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Tran if err != nil { return nil, err } - data, err := util.PackInternalTxDataBatchPostingReport(batchPosterAddr, batchNum, l1BaseFee) + data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, l1BaseFee) if err != nil { return nil, err } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 0ff852bf95..de5b974034 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -5,6 +5,8 @@ package arbos import ( "fmt" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "math/big" "github.com/ethereum/go-ethereum/common" @@ -47,44 +49,79 @@ func InternalTxStartBlock( } } -func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM) { - inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) - if err != nil { - panic(err) - } - l2BaseFee, _ := inputs[1].(*big.Int) // the last L2 block's base fee (which is the result of the calculation 2 blocks ago) - l1BlockNumber, _ := inputs[2].(uint64) // current block's - timePassed, _ := inputs[3].(uint64) // since last block - - nextL1BlockNumber, err := state.Blockhashes().NextBlockNumber() - state.Restrict(err) - - if state.FormatVersion() >= 3 { - // The `l2BaseFee` in the tx data is indeed the last block's base fee, - // however, for the purposes of this function, we need the previous computed base fee. - // Since the computed base fee takes one block to apply, the last block's base fee - // is actually two calculations ago. Instead, as of ArbOS format version 3, - // we use the current state's base fee, which is the result of the last calculation. - l2BaseFee, err = state.L2PricingState().BaseFeeWei() +type BatchFetcher func(uint64) ([]byte, error) + +func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcher) { + switch tx.SubType { + case arbInternalTxStartBlock: + inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) + if err != nil { + panic(err) + } + l2BaseFee, _ := inputs[1].(*big.Int) // the last L2 block's base fee (which is the result of the calculation 2 blocks ago) + l1BlockNumber, _ := inputs[2].(uint64) // current block's + timePassed, _ := inputs[3].(uint64) // since last block + + nextL1BlockNumber, err := state.Blockhashes().NextBlockNumber() state.Restrict(err) - } - if l1BlockNumber >= nextL1BlockNumber { - var prevHash common.Hash - if evm.Context.BlockNumber.Sign() > 0 { - prevHash = evm.Context.GetHash(evm.Context.BlockNumber.Uint64() - 1) + if state.FormatVersion() >= 3 { + // The `l2BaseFee` in the tx data is indeed the last block's base fee, + // however, for the purposes of this function, we need the previous computed base fee. + // Since the computed base fee takes one block to apply, the last block's base fee + // is actually two calculations ago. Instead, as of ArbOS format version 3, + // we use the current state's base fee, which is the result of the last calculation. + l2BaseFee, err = state.L2PricingState().BaseFeeWei() + state.Restrict(err) } - state.Restrict(state.Blockhashes().RecordNewL1Block(l1BlockNumber, prevHash)) - } - currentTime := evm.Context.Time.Uint64() + if l1BlockNumber >= nextL1BlockNumber { + var prevHash common.Hash + if evm.Context.BlockNumber.Sign() > 0 { + prevHash = evm.Context.GetHash(evm.Context.BlockNumber.Uint64() - 1) + } + state.Restrict(state.Blockhashes().RecordNewL1Block(l1BlockNumber, prevHash)) + } + + currentTime := evm.Context.Time.Uint64() + + // Try to reap 2 retryables + _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) + _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) - // Try to reap 2 retryables - _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) - _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) + state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, state.FormatVersion(), false) + state.L1PricingState().UpdateTime(currentTime) - state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, state.FormatVersion(), false) - state.L1PricingState().UpdateTime(currentTime) + state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) + case arbInternalTxBatchPostReport: + if state.FormatVersion() <= 3 { + // this is a no-op in old versions + return + } + inputs, err := util.UnpackInternalTxDataBatchPostingReport(tx.Data) + if err != nil { + panic(err) + } + batchTimestamp, _ := inputs[0].(*big.Int) + // ignore input[1], batchPosterAddress, which exists because we might need it in the future + batchNumberBig, _ := inputs[2].(*big.Int) + batchNumber := batchNumberBig.Uint64() + l1BaseFeeWei, _ := inputs[3].(*big.Int) - state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) + batchData, err := fetcher(batchNumber) + if err != nil { + panic(err) + } + dataGas := params.TxDataNonZeroGasEIP2028 * uint64(len(batchData)) + for _, b := range batchData { + if b == 0 { + dataGas -= params.TxDataNonZeroGasEIP2028 - params.TxDataZeroGas + } + } + weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(dataGas)) + err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) + if err != nil { + log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) + } + } } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index cd5ae602c3..c7471d60bd 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -41,6 +41,7 @@ type TxProcessor struct { TopTxType *byte // set once in StartTxHook evm *vm.EVM CurrentRetryable *common.Hash + batchFetcher BatchFetcher } func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { @@ -55,6 +56,7 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { TopTxType: nil, evm: evm, CurrentRetryable: nil, + // TODO: initialize batchFetcher } } @@ -119,7 +121,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r if p.msg.From() != arbosAddress { return false, 0, errors.New("internal tx not from arbAddress"), nil } - ApplyInternalTxUpdate(tx, p.state, evm) + ApplyInternalTxUpdate(tx, p.state, evm, p.batchFetcher) return true, 0, nil, nil case *types.ArbitrumSubmitRetryableTx: defer (startTracer())() diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index 4b3969f7e7..06127f8bfd 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -38,6 +38,7 @@ interface ArbosActs { ) external; function batchPostingReport( + uint256 batchTimestamp, address batchPosterAddress, uint256 batchNumber, uint256 l1BaseFeeWei diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index f517c2331b..db44a6d313 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -15,6 +15,6 @@ func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock h return con.CallerNotArbOSError() } -func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchPosterAddress addr, batchNumber, l1BaseFeeWei huge) error { +func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber, l1BaseFeeWei huge) error { return con.CallerNotArbOSError() } From 7f375ebc2a2c4f9ed428e9f19dd086d14172a333 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 28 May 2022 14:49:10 -0400 Subject: [PATCH 12/61] Add explanatory comment --- arbos/incomingmessage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index 20b58f5454..619550bbb2 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -529,5 +529,6 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Tran ChainId: chainId, SubType: arbInternalTxBatchPostReport, Data: data, + // don't need to fill in the other fields, since they exist only to ensure uniqueness, and batchNum is already unique }), nil } From b11c5d2d49ce11e90f3c1006507f5f9d9da18f25 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sun, 29 May 2022 07:44:59 -0400 Subject: [PATCH 13/61] Refactor Arbitrum internal tx --- arbos/block_processor.go | 3 +-- arbos/internal_tx.go | 9 ++++----- contracts/src/precompiles/ArbosActs.sol | 1 + precompiles/ArbosActs.go | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index bd3de6e143..3722b982e2 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -190,9 +190,8 @@ func ProduceBlockAdvanced( } else { tx = txes[0] txes = txes[1:] - switch tx := tx.GetInner().(type) { + switch tx.GetInner().(type) { case *types.ArbitrumInternalTx: - tx.TxIndex = uint64(len(receipts)) default: hooks = sequencingHooks // the sequencer has the ability to drop this tx isUserTx = true diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index de5b974034..ef63b822b3 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -37,15 +37,14 @@ func InternalTxStartBlock( if l1BaseFee == nil { l1BaseFee = big.NewInt(0) } - data, err := util.PackInternalTxDataStartBlock(l1BaseFee, lastHeader.BaseFee, l1BlockNum, timePassed) + data, err := util.PackInternalTxDataStartBlock(l1BaseFee, lastHeader.BaseFee, l1BlockNum, l2BlockNum, timePassed) if err != nil { panic(fmt.Sprintf("Failed to pack internal tx %v", err)) } return &types.ArbitrumInternalTx{ - ChainId: chainId, - SubType: arbInternalTxStartBlock, - Data: data, - L2BlockNumber: l2BlockNum, + ChainId: chainId, + SubType: arbInternalTxStartBlock, + Data: data, } } diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index 06127f8bfd..0608940a93 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -34,6 +34,7 @@ interface ArbosActs { uint256 l1BaseFee, uint256 l2BaseFeeLastBlock, uint64 l1BlockNumber, + uint64 l2BlockNumber, uint64 timePassed ) external; diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index db44a6d313..d5805264f2 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -11,7 +11,7 @@ type ArbosActs struct { CallerNotArbOSError func() error } -func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock huge, l1BlockNumber, timeLastBlock uint64) error { +func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock huge, l1BlockNumber, l2BlockNumber, timeLastBlock uint64) error { return con.CallerNotArbOSError() } From c9dbeb1207ca995d2d2b88e8daa855fac5122224 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sun, 29 May 2022 07:51:25 -0400 Subject: [PATCH 14/61] Update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index d56aba49b8..d3f41784e6 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit d56aba49b86a299e3e0305640e5c046917eb9d74 +Subproject commit d3f41784e61042cc0b79b8c8be342e6d56cd5edd From 3867415cf29c335ed206201b409819a6923bb83f Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sun, 29 May 2022 08:24:49 -0400 Subject: [PATCH 15/61] Renaming --- arbos/internal_tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index ef63b822b3..0fc8a414f2 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -48,7 +48,7 @@ func InternalTxStartBlock( } } -type BatchFetcher func(uint64) ([]byte, error) +type BatchFetcher func(batchNum uint64) (batchData []byte, err error) func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcher) { switch tx.SubType { From cde372bb4f16f533967a22903e5874c559ca9cc3 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sun, 29 May 2022 09:04:20 -0400 Subject: [PATCH 16/61] Add data hash field to batch posting report tx --- arbos/internal_tx.go | 7 ++++--- contracts/src/precompiles/ArbosActs.sol | 1 + precompiles/ArbosActs.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 0fc8a414f2..fff6411e0f 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -48,7 +48,7 @@ func InternalTxStartBlock( } } -type BatchFetcher func(batchNum uint64) (batchData []byte, err error) +type BatchFetcher func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcher) { switch tx.SubType { @@ -105,9 +105,10 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos // ignore input[1], batchPosterAddress, which exists because we might need it in the future batchNumberBig, _ := inputs[2].(*big.Int) batchNumber := batchNumberBig.Uint64() - l1BaseFeeWei, _ := inputs[3].(*big.Int) + batchDataHash, _ := inputs[3].(common.Hash) + l1BaseFeeWei, _ := inputs[4].(*big.Int) - batchData, err := fetcher(batchNumber) + batchData, err := fetcher(batchNumber, batchDataHash) if err != nil { panic(err) } diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index 0608940a93..b8fedc23c8 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -42,6 +42,7 @@ interface ArbosActs { uint256 batchTimestamp, address batchPosterAddress, uint256 batchNumber, + bytes32 batchDataHash, uint256 l1BaseFeeWei ) external; diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index d5805264f2..a3b0439036 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -15,6 +15,6 @@ func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock h return con.CallerNotArbOSError() } -func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber, l1BaseFeeWei huge) error { +func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber huge, batchDataHash hash, l1BaseFeeWei huge) error { return con.CallerNotArbOSError() } From 279a3ccb025bd73161da1491b0e6d62de4af8211 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 30 May 2022 09:05:59 -0400 Subject: [PATCH 17/61] Set up batchFetcher in tx processor --- arbnode/api.go | 5 ++++- arbnode/node.go | 12 +++++++++++ arbos/internal_tx.go | 4 ++-- arbos/tx_processor.go | 5 +++-- cmd/replay/main.go | 4 ++++ validator/block_validator.go | 10 ++++++--- validator/challenge_manager.go | 7 +++++-- validator/stateless_block_validator.go | 28 +++++++++++++++++++++----- 8 files changed, 60 insertions(+), 15 deletions(-) diff --git a/arbnode/api.go b/arbnode/api.go index 322d8e85b1..4e951ba8de 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -40,7 +40,10 @@ func (a *BlockValidatorAPI) RevalidateBlock(ctx context.Context, blockNum rpc.Bl } moduleRoot = moduleRoots[0] } - return a.val.ValidateBlock(ctx, header, moduleRoot) + batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { + panic("not yet implemented") // BUGBUG + } + return a.val.ValidateBlock(ctx, header, moduleRoot, batchFetcher) } func (a *BlockValidatorAPI) LatestValidatedBlock(ctx context.Context) (uint64, error) { diff --git a/arbnode/node.go b/arbnode/node.go index 25ce2c99ae..e643dc61a3 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -671,6 +671,18 @@ func createNodeImpl( return nil, err } + arbos.BatchFetcher = func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) { + bnum := new(big.Int).SetUint64(batchNum) + batches, err := sequencerInbox.LookupBatchesInRange(context.Background(), bnum, bnum) + if err != nil { + return nil, err + } + if len(batches) <= 1 { + return nil, errors.New("expected sequencer batch not found") + } + return batches[0].GetData(ctx, l1client) + } + nitroMachineConfig := validator.DefaultNitroMachineConfig if config.Wasm.RootPath != "" { nitroMachineConfig.RootPath = config.Wasm.RootPath diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index fff6411e0f..e578df2816 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -48,9 +48,9 @@ func InternalTxStartBlock( } } -type BatchFetcher func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) +type BatchFetcherFunc func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) -func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcher) { +func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcherFunc) { switch tx.SubType { case arbInternalTxStartBlock: inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index c7471d60bd..82f9ae23a9 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -28,6 +28,8 @@ import ( var arbosAddress = types.ArbosAddress +var BatchFetcher BatchFetcherFunc + // A TxProcessor is created and freed for every L2 transaction. // It tracks state for ArbOS, allowing it infuence in Geth's tx processing. // Public fields are accessible in precompiles. @@ -41,7 +43,6 @@ type TxProcessor struct { TopTxType *byte // set once in StartTxHook evm *vm.EVM CurrentRetryable *common.Hash - batchFetcher BatchFetcher } func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { @@ -121,7 +122,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r if p.msg.From() != arbosAddress { return false, 0, errors.New("internal tx not from arbAddress"), nil } - ApplyInternalTxUpdate(tx, p.state, evm, p.batchFetcher) + ApplyInternalTxUpdate(tx, p.state, evm, BatchFetcher) return true, 0, nil, nil case *types.ArbitrumSubmitRetryableTx: defer (startTracer())() diff --git a/cmd/replay/main.go b/cmd/replay/main.go index d078aaaeae..6ffdd1966a 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -152,6 +152,10 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) + arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { + return wavmio.ResolvePreImage(batchHash), nil + } + chainContext := WavmChainContext{} newBlock, _ = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig) diff --git a/validator/block_validator.go b/validator/block_validator.go index 8c98c43f66..bb25f24597 100644 --- a/validator/block_validator.go +++ b/validator/block_validator.go @@ -7,6 +7,7 @@ import ( "context" "encoding/binary" "fmt" + "github.com/offchainlabs/nitro/arbos" "os" "path/filepath" "runtime" @@ -204,8 +205,8 @@ func (v *BlockValidator) readLastBlockValidatedDbInfo() error { return nil } -func (v *BlockValidator) prepareBlock(header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus) { - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, v.config.StorePreimages) +func (v *BlockValidator) prepareBlock(header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus, batchFetcher arbos.BatchFetcherFunc) { + preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, v.config.StorePreimages, batchFetcher) if err != nil { log.Error("failed to set up validation", "err", err, "header", header, "prevHeader", prevHeader) return @@ -255,7 +256,10 @@ func (v *BlockValidator) NewBlock(block *types.Block, prevHeader *types.Header, if v.nextValidationEntryBlock <= blockNum { v.nextValidationEntryBlock = blockNum + 1 } - v.LaunchUntrackedThread(func() { v.prepareBlock(block.Header(), prevHeader, msg, status) }) + batchFetcher := func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { + return v.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) + } + v.LaunchUntrackedThread(func() { v.prepareBlock(block.Header(), prevHeader, msg, status, batchFetcher) }) } var launchTime = time.Now().Format("2006_01_02__15_04") diff --git a/validator/challenge_manager.go b/validator/challenge_manager.go index 0e8fb68e72..6712174142 100644 --- a/validator/challenge_manager.go +++ b/validator/challenge_manager.go @@ -416,9 +416,12 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if err != nil { return err } + batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { + return m.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) + } if tooFar { // Just record the part of block creation before the message is read - _, preimages, err := RecordBlockCreation(m.blockchain, blockHeader, nil) + _, preimages, err := RecordBlockCreation(m.blockchain, blockHeader, nil, batchFetcher) if err != nil { return err } @@ -440,7 +443,7 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if nextHeader == nil { return fmt.Errorf("next block header %v after challenge point unknown", blockNum+1) } - preimages, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(m.blockchain, nextHeader, blockHeader, message, false) + preimages, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(m.blockchain, nextHeader, blockHeader, message, false, batchFetcher) if err != nil { return err } diff --git a/validator/stateless_block_validator.go b/validator/stateless_block_validator.go index fcee6ffaae..18b1ab1617 100644 --- a/validator/stateless_block_validator.go +++ b/validator/stateless_block_validator.go @@ -4,8 +4,10 @@ package validator import ( + "bytes" "context" "fmt" + "github.com/ethereum/go-ethereum/crypto" "github.com/offchainlabs/nitro/arbutil" "github.com/ethereum/go-ethereum/arbitrum" @@ -213,7 +215,7 @@ func NewStatelessBlockValidator( } // If msg is nil, this will record block creation up to the point where message would be accessed (for a "too far" proof) -func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, msg *arbstate.MessageWithMetadata) (common.Hash, map[common.Hash][]byte, error) { +func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, msg *arbstate.MessageWithMetadata, batchFetcher arbos.BatchFetcherFunc) (common.Hash, map[common.Hash][]byte, error) { recordingdb, chaincontext, recordingKV, err := arbitrum.PrepareRecording(blockchain, prevHeader) if err != nil { return common.Hash{}, nil, err @@ -237,6 +239,19 @@ func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, } } + oldBlockPreimages := make(map[[32]byte][]byte) + arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { + batchData, err := batchFetcher(batchSeqNum, batchHash) + if err != nil { + return nil, err + } + if !bytes.Equal(crypto.Keccak256(batchData), batchHash[:]) { + return nil, errors.New("batch data mismatch") + } + oldBlockPreimages[batchHash] = batchData + return batchData, nil + } + var blockHash common.Hash if msg != nil { block, _ := arbos.ProduceBlock( @@ -251,11 +266,14 @@ func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, } preimages, err := arbitrum.PreimagesFromRecording(chaincontext, recordingKV) + for h, d := range oldBlockPreimages { + preimages[h] = d + } return blockHash, preimages, err } -func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool) (preimages map[common.Hash][]byte, hasDelayedMessage bool, delayedMsgNr uint64, err error) { +func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool, batchFetcher arbos.BatchFetcherFunc) (preimages map[common.Hash][]byte, hasDelayedMessage bool, delayedMsgNr uint64, err error) { var prevHash common.Hash if prevHeader != nil { prevHash = prevHeader.Hash() @@ -267,7 +285,7 @@ func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *typ if prevHeader != nil && producePreimages { var blockhash common.Hash - blockhash, preimages, err = RecordBlockCreation(blockchain, prevHeader, &msg) + blockhash, preimages, err = RecordBlockCreation(blockchain, prevHeader, &msg, batchFetcher) if err != nil { return } @@ -392,7 +410,7 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid return mach.GetGlobalState(), delayedMsg, nil } -func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash) (bool, error) { +func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash, batchFetcher arbos.BatchFetcherFunc) (bool, error) { if header == nil { return false, errors.New("header not found") } @@ -406,7 +424,7 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ if err != nil { return false, err } - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, false) + preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, false, batchFetcher) if err != nil { return false, fmt.Errorf("failed to get block data to validate: %w", err) } From dd20dbac8ba5374aa4357c1d5e20e632500372ed Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 31 May 2022 14:25:59 -0500 Subject: [PATCH 18/61] Implement new batch fetching scheme for automatic L1 gas pricing --- arbnode/api.go | 5 +- arbnode/inbox_reader.go | 2 +- arbnode/node.go | 22 ++--- arbnode/transaction_streamer.go | 21 ++++- arbos/block_processor.go | 24 ++++- arbos/incomingmessage.go | 23 +++-- arbos/incomingmessage_test.go | 2 +- arbos/internal_tx.go | 26 ++---- arbos/tx_processor.go | 5 +- arbstate/geth_test.go | 2 +- cmd/replay/main.go | 12 +-- contracts/src/precompiles/ArbosActs.sol | 4 +- precompiles/ArbosActs.go | 2 +- system_tests/replay_fuzz/replay_fuzz.go | 12 ++- system_tests/retryable_test.go | 2 +- validator/block_validator.go | 18 ++-- validator/challenge_manager.go | 23 +++-- validator/stateless_block_validator.go | 112 +++++++++++++----------- 18 files changed, 181 insertions(+), 136 deletions(-) diff --git a/arbnode/api.go b/arbnode/api.go index 4e951ba8de..322d8e85b1 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -40,10 +40,7 @@ func (a *BlockValidatorAPI) RevalidateBlock(ctx context.Context, blockNum rpc.Bl } moduleRoot = moduleRoots[0] } - batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { - panic("not yet implemented") // BUGBUG - } - return a.val.ValidateBlock(ctx, header, moduleRoot, batchFetcher) + return a.val.ValidateBlock(ctx, header, moduleRoot) } func (a *BlockValidatorAPI) LatestValidatedBlock(ctx context.Context) (uint64, error) { diff --git a/arbnode/inbox_reader.go b/arbnode/inbox_reader.go index 27700ec173..5ece1fea48 100644 --- a/arbnode/inbox_reader.go +++ b/arbnode/inbox_reader.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/offchainlabs/nitro/util/headerreader" "math/big" "strings" "sync" @@ -18,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/stopwaiter" ) diff --git a/arbnode/node.go b/arbnode/node.go index e643dc61a3..29f33cb320 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -7,10 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/offchainlabs/nitro/cmd/genericconf" - "golang.org/x/term" "math/big" "os" "path/filepath" @@ -18,6 +14,11 @@ import ( "syscall" "time" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/offchainlabs/nitro/cmd/genericconf" + "golang.org/x/term" + "github.com/offchainlabs/nitro/util/headerreader" "github.com/ethereum/go-ethereum/rpc" @@ -670,18 +671,7 @@ func createNodeImpl( if err != nil { return nil, err } - - arbos.BatchFetcher = func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) { - bnum := new(big.Int).SetUint64(batchNum) - batches, err := sequencerInbox.LookupBatchesInRange(context.Background(), bnum, bnum) - if err != nil { - return nil, err - } - if len(batches) <= 1 { - return nil, errors.New("expected sequencer batch not found") - } - return batches[0].GetData(ctx, l1client) - } + txStreamer.SetInboxReader(inboxReader) nitroMachineConfig := validator.DefaultNitroMachineConfig if config.Wasm.RootPath != "" { diff --git a/arbnode/transaction_streamer.go b/arbnode/transaction_streamer.go index 36dfdb6fbe..2d808138a5 100644 --- a/arbnode/transaction_streamer.go +++ b/arbnode/transaction_streamer.go @@ -54,6 +54,7 @@ type TransactionStreamer struct { coordinator *SeqCoordinator broadcastServer *broadcaster.Broadcaster validator *validator.BlockValidator + inboxReader *InboxReader } func NewTransactionStreamer(db ethdb.Database, bc *core.BlockChain, broadcastServer *broadcaster.Broadcaster) (*TransactionStreamer, error) { @@ -96,6 +97,16 @@ func (s *TransactionStreamer) SetSeqCoordinator(coordinator *SeqCoordinator) { s.coordinator = coordinator } +func (s *TransactionStreamer) SetInboxReader(inboxReader *InboxReader) { + if s.Started() { + panic("trying to set inbox reader after start") + } + if s.inboxReader != nil { + panic("trying to set inbox reader when already set") + } + s.inboxReader = inboxReader +} + func (s *TransactionStreamer) cleanupInconsistentState() error { // If it doesn't exist yet, set the message count to 0 hasMessageCount, err := s.db.Has(messageCountKey) @@ -696,6 +707,10 @@ func (s *TransactionStreamer) createBlocks(ctx context.Context) error { } }() + batchFetcher := func(batchNum uint64) ([]byte, error) { + return s.inboxReader.GetSequencerMessageBytes(ctx, batchNum) + } + for pos < msgCount { statedb, err = s.bc.StateAt(lastBlockHeader.Root) @@ -720,14 +735,18 @@ func (s *TransactionStreamer) createBlocks(ctx context.Context) error { return err } - block, receipts := arbos.ProduceBlock( + block, receipts, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, lastBlockHeader, statedb, s.bc, s.bc.Config(), + batchFetcher, ) + if err != nil { + return err + } // ProduceBlock advances one message pos++ diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 3722b982e2..5d8204d0e9 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -6,11 +6,12 @@ package arbos import ( "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbos/l1pricing" "math" "math/big" "strconv" + "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/util" @@ -99,6 +100,8 @@ func noopSequencingHooks() *SequencingHooks { } } +type FallibleBatchFetcher func(batchNum uint64) ([]byte, error) + func ProduceBlock( message *L1IncomingMessage, delayedMessagesRead uint64, @@ -106,17 +109,30 @@ func ProduceBlock( statedb *state.StateDB, chainContext core.ChainContext, chainConfig *params.ChainConfig, -) (*types.Block, types.Receipts) { - txes, err := message.ParseL2Transactions(chainConfig.ChainID) + batchFetcher FallibleBatchFetcher, +) (*types.Block, types.Receipts, error) { + var batchFetchErr error + txes, err := message.ParseL2Transactions(chainConfig.ChainID, func(batchNum uint64) []byte { + data, err := batchFetcher(batchNum) + if err != nil { + batchFetchErr = err + return nil + } + return data + }) + if batchFetchErr != nil { + return nil, nil, batchFetchErr + } if err != nil { log.Warn("error parsing incoming message", "err", err) txes = types.Transactions{} } hooks := noopSequencingHooks() - return ProduceBlockAdvanced( + block, receipts := ProduceBlockAdvanced( message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, ) + return block, receipts, nil } // A bit more flexible than ProduceBlock for use in the sequencer. diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index 619550bbb2..ad627f9938 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/util" ) @@ -170,7 +171,9 @@ func ParseIncomingL1Message(rd io.Reader) (*L1IncomingMessage, error) { }, nil } -func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int) (types.Transactions, error) { +type InfallibleBatchFetcher func(batchNum uint64) []byte + +func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int, batchFetcher InfallibleBatchFetcher) (types.Transactions, error) { if len(msg.L2msg) > MaxL2MessageSize { // ignore the message if l2msg is too large return nil, errors.New("message too large") @@ -222,7 +225,7 @@ func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int) (types.Trans log.Debug("ignoring rollup event message") return types.Transactions{}, nil case L1MessageType_BatchPostingReport: - tx, err := parseBatchPostingReportMessage(bytes.NewReader(msg.L2msg), chainId) + tx, err := parseBatchPostingReportMessage(bytes.NewReader(msg.L2msg), chainId, batchFetcher) if err != nil { return nil, err } @@ -503,7 +506,7 @@ func parseSubmitRetryableMessage(rd io.Reader, header *L1IncomingMessageHeader, return types.NewTx(tx), err } -func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Transaction, error) { +func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, batchFetcher InfallibleBatchFetcher) (*types.Transaction, error) { batchTimestamp, err := util.HashFromReader(rd) if err != nil { return nil, err @@ -512,16 +515,26 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Tran if err != nil { return nil, err } - batchNum, err := util.HashFromReader(rd) + batchNumHash, err := util.HashFromReader(rd) if err != nil { return nil, err } + batchNum := batchNumHash.Big().Uint64() l1BaseFee, err := util.HashFromReader(rd) if err != nil { return nil, err } - data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, l1BaseFee) + batchData := batchFetcher(batchNum) + var batchDataGas uint64 + for _, b := range batchData { + if b == 0 { + batchDataGas += params.TxDataZeroGas + } else { + batchDataGas += params.TxDataNonZeroGasEIP2028 + } + } + data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, batchDataGas, l1BaseFee.Big()) if err != nil { return nil, err } diff --git a/arbos/incomingmessage_test.go b/arbos/incomingmessage_test.go index f5ac4b2dd6..4aa3c86c41 100644 --- a/arbos/incomingmessage_test.go +++ b/arbos/incomingmessage_test.go @@ -34,7 +34,7 @@ func TestSerializeAndParseL1Message(t *testing.T) { if err != nil { t.Error(err) } - txes, err := newMsg.ParseL2Transactions(chainId) + txes, err := newMsg.ParseL2Transactions(chainId, nil) if err != nil { t.Error(err) } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index e578df2816..149469034f 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -5,10 +5,10 @@ package arbos import ( "fmt" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "math/big" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -48,9 +48,7 @@ func InternalTxStartBlock( } } -type BatchFetcherFunc func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) - -func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcherFunc) { +func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM) { switch tx.SubType { case arbInternalTxStartBlock: inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) @@ -102,23 +100,11 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos panic(err) } batchTimestamp, _ := inputs[0].(*big.Int) - // ignore input[1], batchPosterAddress, which exists because we might need it in the future - batchNumberBig, _ := inputs[2].(*big.Int) - batchNumber := batchNumberBig.Uint64() - batchDataHash, _ := inputs[3].(common.Hash) + // ignore input[1], batchPosterAddress, and input[2], batchNumber, which exist because we might need them in the future + batchDataGas, _ := inputs[3].(uint64) l1BaseFeeWei, _ := inputs[4].(*big.Int) - batchData, err := fetcher(batchNumber, batchDataHash) - if err != nil { - panic(err) - } - dataGas := params.TxDataNonZeroGasEIP2028 * uint64(len(batchData)) - for _, b := range batchData { - if b == 0 { - dataGas -= params.TxDataNonZeroGasEIP2028 - params.TxDataZeroGas - } - } - weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(dataGas)) + weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(batchDataGas)) err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) if err != nil { log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 82f9ae23a9..cd5ae602c3 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -28,8 +28,6 @@ import ( var arbosAddress = types.ArbosAddress -var BatchFetcher BatchFetcherFunc - // A TxProcessor is created and freed for every L2 transaction. // It tracks state for ArbOS, allowing it infuence in Geth's tx processing. // Public fields are accessible in precompiles. @@ -57,7 +55,6 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { TopTxType: nil, evm: evm, CurrentRetryable: nil, - // TODO: initialize batchFetcher } } @@ -122,7 +119,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r if p.msg.From() != arbosAddress { return false, 0, errors.New("internal tx not from arbAddress"), nil } - ApplyInternalTxUpdate(tx, p.state, evm, BatchFetcher) + ApplyInternalTxUpdate(tx, p.state, evm) return true, 0, nil, nil case *types.ArbitrumSubmitRetryableTx: defer (startTracer())() diff --git a/arbstate/geth_test.go b/arbstate/geth_test.go index 33b11d5235..74b71b7e06 100644 --- a/arbstate/geth_test.go +++ b/arbstate/geth_test.go @@ -116,7 +116,7 @@ func RunMessagesThroughAPI(t *testing.T, msgs [][]byte, statedb *state.StateDB) if err != nil { t.Error(err) } - txes, err := msg.ParseL2Transactions(chainId) + txes, err := msg.ParseL2Transactions(chainId, nil) if err != nil { t.Error(err) } diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 6ffdd1966a..251f0b5402 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -152,12 +152,14 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) - arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - return wavmio.ResolvePreImage(batchHash), nil - } - chainContext := WavmChainContext{} - newBlock, _ = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig) + batchFetcher := func(batchNum uint64) ([]byte, error) { + return wavmio.ReadInboxMessage(batchNum), nil + } + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher) + if err != nil { + panic(err) + } } else { // Initialize ArbOS with this init message and create the genesis block. diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index b8fedc23c8..46bf402a74 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -41,8 +41,8 @@ interface ArbosActs { function batchPostingReport( uint256 batchTimestamp, address batchPosterAddress, - uint256 batchNumber, - bytes32 batchDataHash, + uint64 batchNumber, + uint64 batchDataGas, uint256 l1BaseFeeWei ) external; diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index a3b0439036..1edb485add 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -15,6 +15,6 @@ func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock h return con.CallerNotArbOSError() } -func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber huge, batchDataHash hash, l1BaseFeeWei huge) error { +func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber uint64, batchDataGas uint64, l1BaseFeeWei huge) error { return con.CallerNotArbOSError() } diff --git a/system_tests/replay_fuzz/replay_fuzz.go b/system_tests/replay_fuzz/replay_fuzz.go index f296e082de..55cc3bf261 100644 --- a/system_tests/replay_fuzz/replay_fuzz.go +++ b/system_tests/replay_fuzz/replay_fuzz.go @@ -29,6 +29,7 @@ func BuildBlock( chainContext core.ChainContext, chainConfig *params.ChainConfig, inbox arbstate.InboxBackend, + seqBatch []byte, ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { @@ -45,10 +46,13 @@ func BuildBlock( delayedMessagesRead = inboxMultiplexer.DelayedMessagesRead() l1Message := message.Message - block, _ := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, + batchFetcher := func(uint64) ([]byte, error) { + return seqBatch, nil + } + block, _, err := arbos.ProduceBlock( + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher, ) - return block, nil + return block, err } // A simple mock inbox multiplexer backend @@ -142,7 +146,7 @@ func Fuzz(input []byte) int { positionWithinMessage: 0, delayedMessages: delayedMessages, } - _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox) + _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index f15180cf03..f5d430368b 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -53,7 +53,7 @@ func retryableSetup(t *testing.T) ( if len(messages) != 1 { Fail(t, "expected 1 message from retryable submission, found", len(messages)) } - txs, err := messages[0].Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID) + txs, err := messages[0].Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID, nil) Require(t, err) if len(txs) != 1 { Fail(t, "expected 1 tx from retryable submission, found", len(txs)) diff --git a/validator/block_validator.go b/validator/block_validator.go index bb25f24597..594ea1a7be 100644 --- a/validator/block_validator.go +++ b/validator/block_validator.go @@ -7,7 +7,6 @@ import ( "context" "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbos" "os" "path/filepath" "runtime" @@ -205,13 +204,13 @@ func (v *BlockValidator) readLastBlockValidatedDbInfo() error { return nil } -func (v *BlockValidator) prepareBlock(header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus, batchFetcher arbos.BatchFetcherFunc) { - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, v.config.StorePreimages, batchFetcher) +func (v *BlockValidator) prepareBlock(ctx context.Context, header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus) { + preimages, readBatchInfo, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(ctx, v.blockchain, v.inboxReader, header, prevHeader, msg, v.config.StorePreimages) if err != nil { log.Error("failed to set up validation", "err", err, "header", header, "prevHeader", prevHeader) return } - validationEntry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages) + validationEntry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages, readBatchInfo) if err != nil { log.Error("failed to create validation entry", "err", err, "header", header, "prevHeader", prevHeader) return @@ -256,10 +255,7 @@ func (v *BlockValidator) NewBlock(block *types.Block, prevHeader *types.Header, if v.nextValidationEntryBlock <= blockNum { v.nextValidationEntryBlock = blockNum + 1 } - batchFetcher := func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - return v.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) - } - v.LaunchUntrackedThread(func() { v.prepareBlock(block.Header(), prevHeader, msg, status, batchFetcher) }) + v.LaunchUntrackedThread(func() { v.prepareBlock(context.Background(), block.Header(), prevHeader, msg, status) }) } var launchTime = time.Now().Format("2006_01_02__15_04") @@ -402,10 +398,14 @@ func (v *BlockValidator) validate(ctx context.Context, validationStatus *validat default: } }() + entry.BatchInfo = append(entry.BatchInfo, BatchInfo{ + Number: entry.StartPosition.BatchNumber, + Data: seqMsg, + }) log.Info("starting validation for block", "blockNr", entry.BlockNumber) for _, moduleRoot := range validationStatus.ModuleRoots { before := time.Now() - gsEnd, delayedMsg, err := v.executeBlock(ctx, entry, seqMsg, moduleRoot) + gsEnd, delayedMsg, err := v.executeBlock(ctx, entry, moduleRoot) duration := time.Since(before) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { diff --git a/validator/challenge_manager.go b/validator/challenge_manager.go index 6712174142..2b66cb2a29 100644 --- a/validator/challenge_manager.go +++ b/validator/challenge_manager.go @@ -7,9 +7,10 @@ import ( "context" "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbstate" "math/big" + "github.com/offchainlabs/nitro/arbstate" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -416,15 +417,14 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if err != nil { return err } - batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { - return m.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) - } + var batchInfo []BatchInfo if tooFar { // Just record the part of block creation before the message is read - _, preimages, err := RecordBlockCreation(m.blockchain, blockHeader, nil, batchFetcher) + _, preimages, readBatchInfo, err := RecordBlockCreation(ctx, m.blockchain, m.inboxReader, blockHeader, nil) if err != nil { return err } + batchInfo = readBatchInfo err = SetMachinePreimageResolver(ctx, machine, preimages, nil, m.blockchain, m.das) if err != nil { return err @@ -443,7 +443,7 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if nextHeader == nil { return fmt.Errorf("next block header %v after challenge point unknown", blockNum+1) } - preimages, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(m.blockchain, nextHeader, blockHeader, message, false, batchFetcher) + preimages, readBatchInfo, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(ctx, m.blockchain, m.inboxReader, nextHeader, blockHeader, message, false) if err != nil { return err } @@ -451,7 +451,12 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if err != nil { return err } - err = SetMachinePreimageResolver(ctx, machine, preimages, batchBytes, m.blockchain, m.das) + readBatchInfo = append(readBatchInfo, BatchInfo{ + Number: startGlobalState.Batch, + Data: batchBytes, + }) + batchInfo = readBatchInfo + err = SetMachinePreimageResolver(ctx, machine, preimages, batchInfo, m.blockchain, m.das) if err != nil { return err } @@ -465,7 +470,9 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in return err } } - err = machine.AddSequencerInboxMessage(startGlobalState.Batch, batchBytes) + } + for _, batch := range batchInfo { + err = machine.AddSequencerInboxMessage(batch.Number, batch.Data) if err != nil { return err } diff --git a/validator/stateless_block_validator.go b/validator/stateless_block_validator.go index 18b1ab1617..af9f276221 100644 --- a/validator/stateless_block_validator.go +++ b/validator/stateless_block_validator.go @@ -4,10 +4,9 @@ package validator import ( - "bytes" "context" "fmt" - "github.com/ethereum/go-ethereum/crypto" + "github.com/offchainlabs/nitro/arbutil" "github.com/ethereum/go-ethereum/arbitrum" @@ -138,6 +137,7 @@ type validationEntry struct { StartPosition GlobalStatePosition EndPosition GlobalStatePosition Preimages map[common.Hash][]byte + BatchInfo []BatchInfo } func (v *validationEntry) start() GoGlobalState { @@ -166,6 +166,7 @@ func newValidationEntry( hasDelayed bool, delayedMsgNr uint64, preimages map[common.Hash][]byte, + batchInfo []BatchInfo, ) (*validationEntry, error) { extraInfo, err := types.DeserializeHeaderExtraInformation(header) if err != nil { @@ -185,6 +186,7 @@ func newValidationEntry( HasDelayedMsg: hasDelayed, DelayedMsgNr: delayedMsgNr, Preimages: preimages, + BatchInfo: batchInfo, }, nil } @@ -214,11 +216,16 @@ func NewStatelessBlockValidator( return validator, nil } +type BatchInfo struct { + Number uint64 + Data []byte +} + // If msg is nil, this will record block creation up to the point where message would be accessed (for a "too far" proof) -func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, msg *arbstate.MessageWithMetadata, batchFetcher arbos.BatchFetcherFunc) (common.Hash, map[common.Hash][]byte, error) { +func RecordBlockCreation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, prevHeader *types.Header, msg *arbstate.MessageWithMetadata) (common.Hash, map[common.Hash][]byte, []BatchInfo, error) { recordingdb, chaincontext, recordingKV, err := arbitrum.PrepareRecording(blockchain, prevHeader) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, nil, err } chainConfig := blockchain.Config() @@ -228,52 +235,51 @@ func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, if prevHeader != nil { initialArbosState, err := arbosState.OpenSystemArbosState(recordingdb, nil, true) if err != nil { - return common.Hash{}, nil, fmt.Errorf("error opening initial ArbOS state: %w", err) + return common.Hash{}, nil, nil, fmt.Errorf("error opening initial ArbOS state: %w", err) } chainId, err := initialArbosState.ChainId() if err != nil { - return common.Hash{}, nil, fmt.Errorf("error getting chain ID from initial ArbOS state: %w", err) + return common.Hash{}, nil, nil, fmt.Errorf("error getting chain ID from initial ArbOS state: %w", err) } if chainId.Cmp(chainConfig.ChainID) != 0 { - return common.Hash{}, nil, fmt.Errorf("unexpected chain ID %v in ArbOS state, expected %v", chainId, chainConfig.ChainID) + return common.Hash{}, nil, nil, fmt.Errorf("unexpected chain ID %v in ArbOS state, expected %v", chainId, chainConfig.ChainID) } } - oldBlockPreimages := make(map[[32]byte][]byte) - arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - batchData, err := batchFetcher(batchSeqNum, batchHash) - if err != nil { - return nil, err - } - if !bytes.Equal(crypto.Keccak256(batchData), batchHash[:]) { - return nil, errors.New("batch data mismatch") - } - oldBlockPreimages[batchHash] = batchData - return batchData, nil - } - var blockHash common.Hash + var readBatchInfo []BatchInfo if msg != nil { - block, _ := arbos.ProduceBlock( + batchFetcher := func(batchNum uint64) ([]byte, error) { + data, err := inboxReader.GetSequencerMessageBytes(ctx, batchNum) + if err != nil { + return nil, err + } + readBatchInfo = append(readBatchInfo, BatchInfo{ + Number: batchNum, + Data: data, + }) + return data, nil + } + block, _, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, prevHeader, recordingdb, chaincontext, chainConfig, + batchFetcher, ) + if err != nil { + return common.Hash{}, nil, nil, err + } blockHash = block.Hash() } preimages, err := arbitrum.PreimagesFromRecording(chaincontext, recordingKV) - for h, d := range oldBlockPreimages { - preimages[h] = d - } - - return blockHash, preimages, err + return blockHash, preimages, readBatchInfo, err } -func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool, batchFetcher arbos.BatchFetcherFunc) (preimages map[common.Hash][]byte, hasDelayedMessage bool, delayedMsgNr uint64, err error) { +func BlockDataForValidation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool) (preimages map[common.Hash][]byte, readBatchInfo []BatchInfo, hasDelayedMessage bool, delayedMsgNr uint64, err error) { var prevHash common.Hash if prevHeader != nil { prevHash = prevHeader.Hash() @@ -285,7 +291,7 @@ func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *typ if prevHeader != nil && producePreimages { var blockhash common.Hash - blockhash, preimages, err = RecordBlockCreation(blockchain, prevHeader, &msg, batchFetcher) + blockhash, preimages, readBatchInfo, err = RecordBlockCreation(ctx, blockchain, inboxReader, prevHeader, &msg) if err != nil { return } @@ -305,23 +311,25 @@ func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *typ return } -func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, preimages map[common.Hash][]byte, seqMsg []byte, bc *core.BlockChain, das arbstate.SimpleDASReader) error { +func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, preimages map[common.Hash][]byte, batchInfo []BatchInfo, bc *core.BlockChain, das arbstate.SimpleDASReader) error { recordNewPreimages := true if preimages == nil { preimages = make(map[common.Hash][]byte) recordNewPreimages = false } - if arbstate.IsDASMessageHeaderByte(seqMsg[40]) { - if das == nil { - log.Error("No DAS configured, but sequencer message found with DAS header") - if bc.Config().ArbitrumChainParams.DataAvailabilityCommittee { - return errors.New("processing data availability chain without DAS configured") - } - } else { - _, err := arbstate.RecoverPayloadFromDasBatch(ctx, seqMsg, das, preimages) - if err != nil { - return err + for _, batch := range batchInfo { + if arbstate.IsDASMessageHeaderByte(batch.Data[40]) { + if das == nil { + log.Error("No DAS configured, but sequencer message found with DAS header") + if bc.Config().ArbitrumChainParams.DataAvailabilityCommittee { + return errors.New("processing data availability chain without DAS configured") + } + } else { + _, err := arbstate.RecoverPayloadFromDasBatch(ctx, batch.Data, das, preimages) + if err != nil { + return err + } } } } @@ -354,7 +362,7 @@ func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, pr }) } -func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *validationEntry, seqMsg []byte, moduleRoot common.Hash) (GoGlobalState, []byte, error) { +func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *validationEntry, moduleRoot common.Hash) (GoGlobalState, []byte, error) { start := entry.StartPosition gsStart := entry.start() @@ -363,7 +371,7 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid return GoGlobalState{}, nil, fmt.Errorf("unabled to get WASM machine: %w", err) } mach := basemachine.Clone() - err = SetMachinePreimageResolver(ctx, mach, entry.Preimages, seqMsg, v.blockchain, v.daService) + err = SetMachinePreimageResolver(ctx, mach, entry.Preimages, entry.BatchInfo, v.blockchain, v.daService) if err != nil { return GoGlobalState{}, nil, err } @@ -372,10 +380,12 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid log.Error("error while setting global state for proving", "err", err, "gsStart", gsStart) return GoGlobalState{}, nil, errors.New("error while setting global state for proving") } - err = mach.AddSequencerInboxMessage(start.BatchNumber, seqMsg) - if err != nil { - log.Error("error while trying to add sequencer msg for proving", "err", err, "seq", start.BatchNumber, "blockNr", entry.BlockNumber) - return GoGlobalState{}, nil, errors.New("error while trying to add sequencer msg for proving") + for _, batch := range entry.BatchInfo { + err = mach.AddSequencerInboxMessage(batch.Number, batch.Data) + if err != nil { + log.Error("error while trying to add sequencer msg for proving", "err", err, "seq", start.BatchNumber, "blockNr", entry.BlockNumber) + return GoGlobalState{}, nil, errors.New("error while trying to add sequencer msg for proving") + } } var delayedMsg []byte if entry.HasDelayedMsg { @@ -410,7 +420,7 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid return mach.GetGlobalState(), delayedMsg, nil } -func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash, batchFetcher arbos.BatchFetcherFunc) (bool, error) { +func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash) (bool, error) { if header == nil { return false, errors.New("header not found") } @@ -424,7 +434,7 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ if err != nil { return false, err } - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, false, batchFetcher) + preimages, readBatchInfo, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(ctx, v.blockchain, v.inboxReader, header, prevHeader, msg, false) if err != nil { return false, fmt.Errorf("failed to get block data to validate: %w", err) } @@ -443,7 +453,7 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ return false, fmt.Errorf("failed calculating position for validation: %w", err) } - entry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages) + entry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages, readBatchInfo) if err != nil { return false, fmt.Errorf("failed to create validation entry %w", err) } @@ -454,8 +464,12 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ if err != nil { return false, err } + entry.BatchInfo = append(entry.BatchInfo, BatchInfo{ + Number: startPos.BatchNumber, + Data: seqMsg, + }) - gsEnd, _, err := v.executeBlock(ctx, entry, seqMsg, moduleRoot) + gsEnd, _, err := v.executeBlock(ctx, entry, moduleRoot) if err != nil { return false, err } From 7cb27c00f4d1319f1bea7a3797fb3610ca56b649 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 31 May 2022 15:25:46 -0500 Subject: [PATCH 19/61] Rotate CI docker cache key --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dc08d3861d..db9ce1044c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,8 +35,8 @@ jobs: uses: actions/cache@v2 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-a-${{ github.sha }} - restore-keys: ${{ runner.os }}-buildx-a- + key: ${{ runner.os }}-buildx-b-${{ github.sha }} + restore-keys: ${{ runner.os }}-buildx-b- - name: Build nitro-node docker uses: docker/build-push-action@v2 From 4e00b40a1973b8227ca767b6e0170835387083e9 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Wed, 1 Jun 2022 00:07:57 -0500 Subject: [PATCH 20/61] touch up math --- arbos/l1pricing/l1pricing.go | 60 ++++++++++++++++++------------------ arbos/storage/storage.go | 4 +++ util/arbmath/math.go | 2 +- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 7033fabb45..b156d8a826 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -5,12 +5,13 @@ package l1pricing import ( "errors" + "math/big" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" - "math/big" "github.com/offchainlabs/nitro/arbcompress" - "github.com/offchainlabs/nitro/util/arbmath" + am "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -90,7 +91,7 @@ func InitializeL1PricingState(sto *storage.Storage) error { return err } pricePerUnit := sto.OpenStorageBackedBigInt(pricePerUnitOffset) - return pricePerUnit.Set(big.NewInt(InitialPricePerUnitGwei * 1000000000)) + return pricePerUnit.SetByUint(InitialPricePerUnitGwei * 1000000000) } func OpenL1PricingState(sto *storage.Storage) *L1PricingState { @@ -210,7 +211,7 @@ func (ps *L1PricingState) UpdateTime(currentTime uint64) { } // Update the pricing model based on a payment by the sequencer -func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { +func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { // compute previous shortfall fundsDueToSequencer, err := ps.FundsDueToSequencer() if err != nil { @@ -224,7 +225,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err != nil { return err } - oldShortfall := new(big.Int).Sub(new(big.Int).Add(fundsDueToSequencer, fundsDueForRewards), availableFunds) + oldShortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), availableFunds) // compute allocation fraction lastUpdateTime, err := ps.LastUpdateTime() @@ -234,8 +235,8 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { return ErrInvalidTime } - allocFractionNum := big.NewInt(int64(updateTime - lastUpdateTime)) - allocFractionDenom := big.NewInt(int64(currentTime - lastUpdateTime)) + allocFractionNum := am.UintToBig(updateTime - lastUpdateTime) + allocFractionDenom := am.UintToBig(currentTime - lastUpdateTime) // allocate units to this update unitsSinceUpdate, err := ps.UnitsSinceUpdate() @@ -249,22 +250,22 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT } // allocate funds to this update - collectedSinceUpdate := stateDb.GetBalance(L1PricerFundsPoolAddress) - fundsToMove := new(big.Int).Div(new(big.Int).Mul(collectedSinceUpdate, allocFractionNum), allocFractionDenom) - stateDb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) - availableFunds = new(big.Int).Add(availableFunds, fundsToMove) + collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) + fundsToMove := am.BigDiv(am.BigMul(collectedSinceUpdate, allocFractionNum), allocFractionDenom) + statedb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) + availableFunds = am.BigAdd(availableFunds, fundsToMove) // update amounts due perUnitReward, err := ps.PerUnitReward() if err != nil { return err } - fundsDueToSequencer = new(big.Int).Add(fundsDueToSequencer, weiSpent) + fundsDueToSequencer = am.BigAdd(fundsDueToSequencer, weiSpent) if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - newRewards := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(perUnitReward)), allocFractionNum), allocFractionDenom) - fundsDueForRewards = new(big.Int).Add(fundsDueForRewards, newRewards) + newRewards := am.BigDiv(am.BigMulByUint(allocFractionNum, perUnitReward), allocFractionDenom) + fundsDueForRewards = am.BigAdd(fundsDueForRewards, newRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } @@ -275,26 +276,26 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT return err } paymentForRewards := availableFunds - if fundsDueForRewards.Cmp(paymentForRewards) < 0 { + if am.BigLessThan(fundsDueForRewards, paymentForRewards) { paymentForRewards = fundsDueForRewards } if paymentForRewards.Sign() > 0 { - availableFunds = new(big.Int).Sub(availableFunds, paymentForRewards) + availableFunds = am.BigSub(availableFunds, paymentForRewards) if err := ps.SetAvailableFunds(availableFunds); err != nil { return err } - fundsDueForRewards = new(big.Int).Sub(fundsDueForRewards, paymentForRewards) + fundsDueForRewards = am.BigSub(fundsDueForRewards, paymentForRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } - core.Transfer(stateDb, L1PricerFundsPoolAddress, payRewardsTo, paymentForRewards) + core.Transfer(statedb, L1PricerFundsPoolAddress, payRewardsTo, paymentForRewards) } sequencerPaymentAddr, err := ps.Sequencer() if err != nil { return err } paymentForSequencer := availableFunds - if fundsDueToSequencer.Cmp(paymentForSequencer) < 0 { + if am.BigLessThan(fundsDueToSequencer, paymentForSequencer) { paymentForSequencer = fundsDueToSequencer } if paymentForSequencer.Sign() > 0 { @@ -306,7 +307,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - core.Transfer(stateDb, L1PricerFundsPoolAddress, sequencerPaymentAddr, paymentForSequencer) + core.Transfer(statedb, L1PricerFundsPoolAddress, sequencerPaymentAddr, paymentForSequencer) } // update time @@ -316,7 +317,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT // adjust the price if unitsAllocated > 0 { - shortfall := new(big.Int).Sub(new(big.Int).Add(fundsDueToSequencer, fundsDueForRewards), availableFunds) + shortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), availableFunds) inertia, err := ps.Inertia() if err != nil { return err @@ -325,18 +326,17 @@ func (ps *L1PricingState) UpdateForSequencerSpending(stateDb vm.StateDB, updateT if err != nil { return err } - fdenom := big.NewInt(int64(unitsAllocated + equilTime/inertia)) price, err := ps.PricePerUnit() if err != nil { return err } - newPrice := new(big.Int).Sub( - price, - new(big.Int).Add( - new(big.Int).Div(new(big.Int).Sub(shortfall, oldShortfall), fdenom), - new(big.Int).Div(shortfall, new(big.Int).Mul(big.NewInt(int64(equilTime)), fdenom)), - ), + + priceChange := am.BigDivByUint( + am.BigAdd(am.BigSub(shortfall, oldShortfall), am.BigDivByUint(shortfall, equilTime)), + unitsAllocated+equilTime/inertia, ) + + newPrice := am.BigSub(price, priceChange) if newPrice.Sign() >= 0 { price = newPrice } else { @@ -368,7 +368,7 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, poster common.Add // Approximate the l1 fee charged for posting this tx's calldata pricePerUnit, _ := ps.PricePerUnit() - tx.PosterCost = arbmath.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028) + tx.PosterCost = am.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028) tx.PosterIsReimbursable = true } @@ -392,7 +392,7 @@ func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Add l1Bytes := byteCount + TxFixedCost pricePerUnit, _ := ps.PricePerUnit() - return arbmath.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028), true + return am.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028), true } func byteCountAfterBrotli0(input []byte) (uint64, error) { diff --git a/arbos/storage/storage.go b/arbos/storage/storage.go index 6dfd1c4d83..df9f726a5b 100644 --- a/arbos/storage/storage.go +++ b/arbos/storage/storage.go @@ -450,6 +450,10 @@ func (sbbi *StorageBackedBigInt) Set(val *big.Int) error { return sbbi.StorageSlot.Set(common.BigToHash(val)) } +func (sbbi *StorageBackedBigInt) SetByUint(val uint64) error { + return sbbi.StorageSlot.Set(util.UintToHash(val)) +} + type StorageBackedAddress struct { StorageSlot } diff --git a/util/arbmath/math.go b/util/arbmath/math.go index 3f3c152624..fb0f3e1d54 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -28,7 +28,7 @@ func MinInt(value, ceiling int64) int64 { return value } -// the minimum of two ints +// the minimum of two uints func MinUint(value, ceiling uint64) uint64 { if value > ceiling { return ceiling From 5fac5ded8fd69469900ae1c5068936e15a8ffadf Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Wed, 1 Jun 2022 00:14:55 -0500 Subject: [PATCH 21/61] time deltas --- arbos/l1pricing/l1pricing.go | 10 +++++----- util/arbmath/math.go | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index b156d8a826..d4dc96acb9 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -235,8 +235,8 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { return ErrInvalidTime } - allocFractionNum := am.UintToBig(updateTime - lastUpdateTime) - allocFractionDenom := am.UintToBig(currentTime - lastUpdateTime) + updateTimeDelta := updateTime - lastUpdateTime + timeDelta := currentTime - lastUpdateTime // allocate units to this update unitsSinceUpdate, err := ps.UnitsSinceUpdate() @@ -251,7 +251,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT // allocate funds to this update collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) - fundsToMove := am.BigDiv(am.BigMul(collectedSinceUpdate, allocFractionNum), allocFractionDenom) + fundsToMove := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, updateTimeDelta), timeDelta) statedb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) availableFunds = am.BigAdd(availableFunds, fundsToMove) @@ -264,8 +264,8 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - newRewards := am.BigDiv(am.BigMulByUint(allocFractionNum, perUnitReward), allocFractionDenom) - fundsDueForRewards = am.BigAdd(fundsDueForRewards, newRewards) + newRewards := am.SaturatingUMul(updateTimeDelta, perUnitReward) / timeDelta + fundsDueForRewards = am.BigAddByUint(fundsDueForRewards, newRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } diff --git a/util/arbmath/math.go b/util/arbmath/math.go index fb0f3e1d54..ffe01fcf8d 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -118,6 +118,11 @@ func BigDiv(dividend *big.Int, divisor *big.Int) *big.Int { return new(big.Int).Div(dividend, divisor) } +// add a uint to a huge +func BigAddByUint(augend *big.Int, addend uint64) *big.Int { + return new(big.Int).Add(augend, UintToBig(addend)) +} + // multiply a huge by a rational func BigMulByFrac(value *big.Int, numerator, denominator int64) *big.Int { value = new(big.Int).Set(value) From 86222fa577727e6e27b664009e2557d4aeb48a5e Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Wed, 1 Jun 2022 00:17:12 -0500 Subject: [PATCH 22/61] update CI cache key --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dc08d3861d..db9ce1044c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,8 +35,8 @@ jobs: uses: actions/cache@v2 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-a-${{ github.sha }} - restore-keys: ${{ runner.os }}-buildx-a- + key: ${{ runner.os }}-buildx-b-${{ github.sha }} + restore-keys: ${{ runner.os }}-buildx-b- - name: Build nitro-node docker uses: docker/build-push-action@v2 From 01b594c0e01cec5efb307a314f0fbd9b5ca0d314 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 2 Jun 2022 07:55:33 -0400 Subject: [PATCH 23/61] Remove unneeded version check --- arbos/internal_tx.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 149469034f..26a5c54480 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -91,10 +91,6 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) case arbInternalTxBatchPostReport: - if state.FormatVersion() <= 3 { - // this is a no-op in old versions - return - } inputs, err := util.UnpackInternalTxDataBatchPostingReport(tx.Data) if err != nil { panic(err) From d0f071bdbcb71ed378b321ec6f52acf4a3e3fcfd Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 2 Jun 2022 14:25:36 -0400 Subject: [PATCH 24/61] Address some code review comments --- arbos/internal_tx.go | 2 +- arbos/l1pricing/l1pricing.go | 67 ++++++++++++------------------- arbos/l1pricing/l1pricing_test.go | 2 +- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 26a5c54480..609cbf6026 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -101,7 +101,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos l1BaseFeeWei, _ := inputs[4].(*big.Int) weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(batchDataGas)) - err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) + err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, evm, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) if err != nil { log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) } diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index d4dc96acb9..2ed1774515 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -32,18 +32,17 @@ type L1PricingState struct { perUnitReward storage.StorageBackedUint64 // variables currentTime storage.StorageBackedUint64 - lastUpdateTime storage.StorageBackedUint64 - availableFunds storage.StorageBackedBigInt + lastUpdateTime storage.StorageBackedUint64 // timestamp of the last update from L1 that we processed fundsDueToSequencer storage.StorageBackedBigInt fundsDueForRewards storage.StorageBackedBigInt // funds collected since update are recorded as the balance in account L1PricerFundsPoolAddress - unitsSinceUpdate storage.StorageBackedUint64 - pricePerUnit storage.StorageBackedBigInt + unitsSinceUpdate storage.StorageBackedUint64 // calldata units collected for since last update + pricePerUnit storage.StorageBackedBigInt // current price per calldata unit } var ( SequencerAddress = common.HexToAddress("0xA4B000000000000000000073657175656e636572") - L1PricerFundsPoolAddress = common.HexToAddress("0xA4B0000000000000000000000000000000000f6") + L1PricerFundsPoolAddress = common.HexToAddress("0xA4B00000000000000000000000000000000000f6") ErrInvalidTime = errors.New("invalid timestamp") ) @@ -57,7 +56,6 @@ const ( perUnitRewardOffset currentTimeOffset lastUpdateTimeOffset - availableFundsOffset fundsDueToSequencerOffset fundsDueForRewards unitsSinceOffset @@ -65,10 +63,10 @@ const ( ) const ( - InitialEquilibrationTime = 10000000 + InitialEquilibrationTime = 60 * 60 // one hour InitialInertia = 10 InitialPerUnitReward = 10 - InitialPricePerUnitGwei = 50 + InitialPricePerUnitWei = 50 * params.GWei ) func InitializeL1PricingState(sto *storage.Storage) error { @@ -91,7 +89,7 @@ func InitializeL1PricingState(sto *storage.Storage) error { return err } pricePerUnit := sto.OpenStorageBackedBigInt(pricePerUnitOffset) - return pricePerUnit.SetByUint(InitialPricePerUnitGwei * 1000000000) + return pricePerUnit.SetByUint(InitialPricePerUnitWei) } func OpenL1PricingState(sto *storage.Storage) *L1PricingState { @@ -105,7 +103,6 @@ func OpenL1PricingState(sto *storage.Storage) *L1PricingState { sto.OpenStorageBackedUint64(perUnitRewardOffset), sto.OpenStorageBackedUint64(currentTimeOffset), sto.OpenStorageBackedUint64(lastUpdateTimeOffset), - sto.OpenStorageBackedBigInt(availableFundsOffset), sto.OpenStorageBackedBigInt(fundsDueToSequencerOffset), sto.OpenStorageBackedBigInt(fundsDueForRewards), sto.OpenStorageBackedUint64(unitsSinceOffset), @@ -165,14 +162,6 @@ func (ps *L1PricingState) SetLastUpdateTime(t uint64) error { return ps.lastUpdateTime.Set(t) } -func (ps *L1PricingState) AvailableFunds() (*big.Int, error) { - return ps.availableFunds.Get() -} - -func (ps *L1PricingState) SetAvailableFunds(amt *big.Int) error { - return ps.availableFunds.Set(amt) -} - func (ps *L1PricingState) FundsDueToSequencer() (*big.Int, error) { return ps.fundsDueToSequencer.Get() } @@ -211,7 +200,7 @@ func (ps *L1PricingState) UpdateTime(currentTime uint64) { } // Update the pricing model based on a payment by the sequencer -func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { +func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm.EVM, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { // compute previous shortfall fundsDueToSequencer, err := ps.FundsDueToSequencer() if err != nil { @@ -221,17 +210,16 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT if err != nil { return err } - availableFunds, err := ps.AvailableFunds() - if err != nil { - return err - } - oldShortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), availableFunds) + oldShortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) - // compute allocation fraction + // compute allocation fraction -- will allocate updateTimeDelta/timeDelta fraction of units and funds to this update lastUpdateTime, err := ps.LastUpdateTime() if err != nil { return err } + if lastUpdateTime == 0 && currentTime > 0 { // it's the first update, so there isn't a last update time + lastUpdateTime = currentTime - 1 + } if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { return ErrInvalidTime } @@ -243,7 +231,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT if err != nil { return err } - unitsAllocated := unitsSinceUpdate * (updateTime - lastUpdateTime) / (currentTime - lastUpdateTime) + unitsAllocated := unitsSinceUpdate * updateTimeDelta / timeDelta unitsSinceUpdate -= unitsAllocated if err := ps.SetUnitsSinceUpdate(unitsSinceUpdate); err != nil { return err @@ -253,7 +241,6 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) fundsToMove := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, updateTimeDelta), timeDelta) statedb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) - availableFunds = am.BigAdd(availableFunds, fundsToMove) // update amounts due perUnitReward, err := ps.PerUnitReward() @@ -275,39 +262,37 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT if err != nil { return err } - paymentForRewards := availableFunds + paymentForRewards := statedb.GetBalance(L1PricerFundsPoolAddress) if am.BigLessThan(fundsDueForRewards, paymentForRewards) { paymentForRewards = fundsDueForRewards } if paymentForRewards.Sign() > 0 { - availableFunds = am.BigSub(availableFunds, paymentForRewards) - if err := ps.SetAvailableFunds(availableFunds); err != nil { - return err - } fundsDueForRewards = am.BigSub(fundsDueForRewards, paymentForRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } - core.Transfer(statedb, L1PricerFundsPoolAddress, payRewardsTo, paymentForRewards) + err := util.TransferBalance(&L1PricerFundsPoolAddress, &payRewardsTo, paymentForRewards, evm, util.TracingBeforeEVM) + if err != nil { + return err + } } - sequencerPaymentAddr, err := ps.Sequencer() + sequencerPaymentAddr, err := ps.PaySequencerFeesTo() if err != nil { return err } - paymentForSequencer := availableFunds + paymentForSequencer := statedb.GetBalance(L1PricerFundsPoolAddress) if am.BigLessThan(fundsDueToSequencer, paymentForSequencer) { paymentForSequencer = fundsDueToSequencer } if paymentForSequencer.Sign() > 0 { - availableFunds = new(big.Int).Sub(availableFunds, paymentForSequencer) - if err := ps.SetAvailableFunds(availableFunds); err != nil { - return err - } fundsDueToSequencer = new(big.Int).Sub(fundsDueToSequencer, paymentForSequencer) if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { return err } - core.Transfer(statedb, L1PricerFundsPoolAddress, sequencerPaymentAddr, paymentForSequencer) + err := util.TransferBalance(&L1PricerFundsPoolAddress, &sequencerPaymentAddr, paymentForSequencer, evm, util.TracingBeforeEVM) + if err != nil { + return err + } } // update time @@ -317,7 +302,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, updateT // adjust the price if unitsAllocated > 0 { - shortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), availableFunds) + shortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) inertia, err := ps.Inertia() if err != nil { return err diff --git a/arbos/l1pricing/l1pricing_test.go b/arbos/l1pricing/l1pricing_test.go index 9237f9b718..0a14c690cf 100644 --- a/arbos/l1pricing/l1pricing_test.go +++ b/arbos/l1pricing/l1pricing_test.go @@ -61,7 +61,7 @@ func TestL1PriceUpdate(t *testing.T) { Fail(t) } - initialPriceEstimate := big.NewInt(InitialPricePerUnitGwei * 1000000000) + initialPriceEstimate := big.NewInt(InitialPricePerUnitWei) priceEstimate, err := ps.PricePerUnit() Require(t, err) if priceEstimate.Cmp(initialPriceEstimate) != 0 { From 3042af3414452d24aae3f2a4039787361cd9582c Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Fri, 3 Jun 2022 07:07:33 -0400 Subject: [PATCH 25/61] Fix bug --- arbos/l1pricing/l1pricing.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 2ed1774515..dec2037f94 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -353,8 +353,11 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, poster common.Add // Approximate the l1 fee charged for posting this tx's calldata pricePerUnit, _ := ps.PricePerUnit() - tx.PosterCost = am.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028) + numUnits := l1Bytes * params.TxDataNonZeroGasEIP2028 + tx.PosterCost = am.BigMulByUint(pricePerUnit, numUnits) tx.PosterIsReimbursable = true + unitsSinceUpdate, _ := ps.UnitsSinceUpdate() + _ = ps.SetUnitsSinceUpdate(unitsSinceUpdate + numUnits) } const TxFixedCost = 140 // assumed maximum size in bytes of a typical RLP-encoded tx, not including its calldata From 81e86cf2df5702d92b1f88d85b69432196056a51 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 4 Jun 2022 08:44:09 -0400 Subject: [PATCH 26/61] Refactor and rename --- arbnode/sequencer.go | 2 +- arbos/arbosState/initialization_test.go | 9 +- arbos/arbosState/initialize.go | 8 +- arbos/block_processor.go | 4 +- arbos/internal_tx.go | 5 +- arbos/l1pricing/batchPoster.go | 189 ++++++++++++++++++++ arbos/l1pricing/batchPoster_test.go | 113 ++++++++++++ arbos/l1pricing/l1pricing.go | 140 ++++++++------- arbstate/inbox.go | 2 +- contracts/src/precompiles/ArbAggregator.sol | 47 +++-- nodeInterface/virtual-contracts.go | 6 +- precompiles/ArbAggregator.go | 124 ++++++++----- precompiles/ArbAggregator_test.go | 53 ++---- precompiles/ArbGasInfo.go | 12 +- system_tests/fees_test.go | 4 +- system_tests/seqcompensation_test.go | 2 +- 16 files changed, 527 insertions(+), 193 deletions(-) create mode 100644 arbos/l1pricing/batchPoster.go create mode 100644 arbos/l1pricing/batchPoster_test.go diff --git a/arbnode/sequencer.go b/arbnode/sequencer.go index 0a0a35ec6f..dd42231e7d 100644 --- a/arbnode/sequencer.go +++ b/arbnode/sequencer.go @@ -210,7 +210,7 @@ func (s *Sequencer) sequenceTransactions(ctx context.Context) { header := &arbos.L1IncomingMessageHeader{ Kind: arbos.L1MessageType_L2Message, - Poster: l1pricing.SequencerAddress, + Poster: l1pricing.BatchPosterAddress, BlockNumber: l1Block, Timestamp: uint64(timestamp), RequestId: nil, diff --git a/arbos/arbosState/initialization_test.go b/arbos/arbosState/initialization_test.go index 29ea89c7c9..a6982f8ce4 100644 --- a/arbos/arbosState/initialization_test.go +++ b/arbos/arbosState/initialization_test.go @@ -142,6 +142,7 @@ func checkRetryables(arbState *ArbosState, expected []statetransfer.Initializati func checkAccounts(db *state.StateDB, arbState *ArbosState, accts []statetransfer.AccountInitializationInfo, t *testing.T) { l1p := arbState.L1PricingState() + posterTable := l1p.BatchPosterTable() for _, acct := range accts { addr := acct.Addr if db.GetNonce(addr) != acct.Nonce { @@ -168,10 +169,12 @@ func checkAccounts(db *state.StateDB, arbState *ArbosState, accts []statetransfe t.Fatal(err) } } - sequencer, err := l1p.Sequencer() + isPoster, err := posterTable.ContainsPoster(addr) Require(t, err) - if acct.AggregatorInfo != nil && acct.Addr == sequencer { - fc, err := l1p.PaySequencerFeesTo() + if acct.AggregatorInfo != nil && isPoster { + posterInfo, err := posterTable.OpenPoster(addr) + Require(t, err) + fc, err := posterInfo.PayTo() Require(t, err) if fc != acct.AggregatorInfo.FeeCollector { t.Fatal() diff --git a/arbos/arbosState/initialize.go b/arbos/arbosState/initialize.go index 88a16af064..46f940d69d 100644 --- a/arbos/arbosState/initialize.go +++ b/arbos/arbosState/initialize.go @@ -153,13 +153,15 @@ func initializeRetryables(rs *retryables.RetryableState, initData statetransfer. func initializeArbosAccount(statedb *state.StateDB, arbosState *ArbosState, account statetransfer.AccountInitializationInfo) error { l1pState := arbosState.L1PricingState() + posterTable := l1pState.BatchPosterTable() if account.AggregatorInfo != nil { - sequencer, err := l1pState.Sequencer() + isPoster, err := posterTable.ContainsPoster(account.Addr) if err != nil { return err } - if account.Addr == sequencer { - if err := l1pState.SetPaySequencerFeesTo(account.AggregatorInfo.FeeCollector); err != nil { + if !isPoster { + _, err = posterTable.AddPoster(account.Addr, account.AggregatorInfo.FeeCollector) + if err != nil { return err } } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 5d8204d0e9..6ab4a98379 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -48,8 +48,8 @@ func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState coinbase := common.Address{} if l1info != nil { timestamp = l1info.l1Timestamp - sequencer, _ := state.L1PricingState().Sequencer() - if l1info.poster == sequencer { + isBatchPoster, _ := state.L1PricingState().BatchPosterTable().ContainsPoster(l1info.poster) + if isBatchPoster { coinbase = l1pricing.L1PricerFundsPoolAddress } } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 609cbf6026..64e4238f14 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -96,12 +96,13 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos panic(err) } batchTimestamp, _ := inputs[0].(*big.Int) - // ignore input[1], batchPosterAddress, and input[2], batchNumber, which exist because we might need them in the future + batchPosterAddress, _ := inputs[1].(common.Address) + // ignore input[2], batchNumber, which exist because we might need them in the future batchDataGas, _ := inputs[3].(uint64) l1BaseFeeWei, _ := inputs[4].(*big.Int) weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(batchDataGas)) - err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, evm, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) + err = state.L1PricingState().UpdateForBatchPosterSpending(evm.StateDB, evm, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), batchPosterAddress, weiSpent) if err != nil { log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) } diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go new file mode 100644 index 0000000000..2ed95eb104 --- /dev/null +++ b/arbos/l1pricing/batchPoster.go @@ -0,0 +1,189 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package l1pricing + +import ( + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/storage" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/util/arbmath" + "math/big" +) + +var ( + ErrNotExist = errors.New("batch poster does not exist in table") + ErrAlreadyExists = errors.New("tried to add a batch poster that already exists") +) + +// layout of storage in the table +type BatchPostersTable struct { + storage *storage.Storage + numPosters storage.StorageBackedUint64 +} + +type BatchPosterState struct { + existsIfNonzero storage.StorageBackedUint64 + fundsDue storage.StorageBackedBigInt + payTo storage.StorageBackedAddress +} + +func InitializeBatchPostersTable(storage *storage.Storage) error { + // no initialization needed at present + return nil +} + +func OpenBatchPostersTable(storage *storage.Storage) *BatchPostersTable { + return &BatchPostersTable{ + storage: storage, + numPosters: storage.OpenStorageBackedUint64(0), + } +} + +func (bpt *BatchPostersTable) OpenPoster(poster common.Address) (*BatchPosterState, error) { + bpState := bpt.internalOpen(poster) + existsIfNonzero, err := bpState.existsIfNonzero.Get() + if err != nil { + return nil, err + } + if existsIfNonzero == 0 { + return nil, ErrNotExist + } + return bpState, nil +} + +func (bpt *BatchPostersTable) internalOpen(poster common.Address) *BatchPosterState { + bpStorage := bpt.storage.OpenSubStorage(poster.Bytes()) + return &BatchPosterState{ + existsIfNonzero: bpStorage.OpenStorageBackedUint64(0), + fundsDue: bpStorage.OpenStorageBackedBigInt(1), + payTo: bpStorage.OpenStorageBackedAddress(2), + } +} + +func (bpt *BatchPostersTable) ContainsPoster(poster common.Address) (bool, error) { + _, err := bpt.OpenPoster(poster) + if errors.Is(err, ErrNotExist) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (bpt *BatchPostersTable) AddPoster(posterAddress common.Address, payTo common.Address) (*BatchPosterState, error) { + bpState := bpt.internalOpen(posterAddress) + alreadyExists, err := bpState.existsIfNonzero.Get() + if err != nil { + return nil, err + } + if alreadyExists != 0 { + return nil, ErrAlreadyExists + } + if err := bpState.fundsDue.Set(big.NewInt(0)); err != nil { + return nil, err + } + if err := bpState.payTo.Set(payTo); err != nil { + return nil, err + } + if err := bpState.existsIfNonzero.Set(1); err != nil { + return nil, err + } + + numPosters, err := bpt.numPosters.Get() + if err != nil { + return nil, err + } + if err := bpt.storage.SetByUint64(numPosters+1, util.AddressToHash(posterAddress)); err != nil { + return nil, err + } + if err := bpt.numPosters.Set(numPosters + 1); err != nil { + return nil, err + } + + return bpState, nil +} + +func (bpt *BatchPostersTable) AllPosters() ([]common.Address, error) { + numPosters, err := bpt.numPosters.Get() + if err != nil { + return nil, err + } + ret := []common.Address{} + for i := uint64(0); i < numPosters; i++ { + posterAddrAsHash, err := bpt.storage.GetByUint64(i + 1) + if err != nil { + return nil, err + } + ret = append(ret, common.BytesToAddress(posterAddrAsHash.Bytes())) + } + return ret, nil +} + +func (bpt *BatchPostersTable) TotalFundsDue() (*big.Int, error) { + allPosters, err := bpt.AllPosters() + if err != nil { + return nil, err + } + ret := big.NewInt(0) + for _, posterAddr := range allPosters { + poster, err := bpt.OpenPoster(posterAddr) + if err != nil { + return nil, err + } + fundsDue, err := poster.FundsDue() + if err != nil { + return nil, err + } + ret = arbmath.BigAdd(ret, fundsDue) + } + return ret, nil +} + +func (bps *BatchPosterState) FundsDue() (*big.Int, error) { + return bps.fundsDue.Get() +} + +func (bps *BatchPosterState) SetFundsDue(val *big.Int) error { + return bps.fundsDue.Set(val) +} + +func (bps *BatchPosterState) PayTo() (common.Address, error) { + return bps.payTo.Get() +} + +func (bps *BatchPosterState) SetPayTo(addr common.Address) error { + return bps.payTo.Set(addr) +} + +type FundsDueItem struct { + dueTo common.Address + balance *big.Int +} + +func (bpt *BatchPostersTable) GetFundsDueList() ([]FundsDueItem, error) { + ret := []FundsDueItem{} + allPosters, err := bpt.AllPosters() + if err != nil { + return nil, err + } + for _, posterAddr := range allPosters { + poster, err := bpt.OpenPoster(posterAddr) + if err != nil { + return nil, err + } + due, err := poster.FundsDue() + if err != nil { + return nil, err + } + if due.Sign() > 0 { + ret = append(ret, FundsDueItem{ + dueTo: posterAddr, + balance: due, + }) + } + } + return ret, nil +} diff --git a/arbos/l1pricing/batchPoster_test.go b/arbos/l1pricing/batchPoster_test.go new file mode 100644 index 0000000000..7cea720fa9 --- /dev/null +++ b/arbos/l1pricing/batchPoster_test.go @@ -0,0 +1,113 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package l1pricing + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/storage" + "math/big" + "testing" +) + +func TestBatchPosterTable(t *testing.T) { + sto := storage.NewMemoryBacked(burn.NewSystemBurner(nil, false)) + err := InitializeBatchPostersTable(sto) + Require(t, err) + + bpTable := OpenBatchPostersTable(sto) + + addr1 := common.Address{1, 2, 3} + pay1 := common.Address{4, 5, 6, 7} + addr2 := common.Address{2, 4, 6} + pay2 := common.Address{8, 10, 12, 14} + + // test creation and counting of bps + allPosters, err := bpTable.AllPosters() + Require(t, err) + if len(allPosters) != 0 { + t.Fatal() + } + exists, err := bpTable.ContainsPoster(addr1) + Require(t, err) + if exists { + t.Fatal() + } + + bp1, err := bpTable.AddPoster(addr1, pay1) + Require(t, err) + getPay1, err := bp1.PayTo() + Require(t, err) + if getPay1 != pay1 { + t.Fatal() + } + getDue1, err := bp1.FundsDue() + Require(t, err) + if getDue1.Sign() != 0 { + t.Fatal() + } + exists, err = bpTable.ContainsPoster(addr1) + Require(t, err) + if !exists { + t.Fatal() + } + + bp2, err := bpTable.AddPoster(addr2, pay2) + Require(t, err) + _ = bp2 + getPay2, err := bp2.PayTo() + Require(t, err) + if getPay2 != pay2 { + t.Fatal() + } + getDue2, err := bp2.FundsDue() + Require(t, err) + if getDue2.Sign() != 0 { + t.Fatal() + } + exists, err = bpTable.ContainsPoster(addr2) + Require(t, err) + if !exists { + t.Fatal() + } + + allPosters, err = bpTable.AllPosters() + Require(t, err) + if len(allPosters) != 2 { + t.Fatal() + } + + // test get/set of BP fields + bp1, err = bpTable.OpenPoster(addr1) + Require(t, err) + err = bp1.SetPayTo(addr2) + Require(t, err) + getPay1, err = bp1.PayTo() + Require(t, err) + if getPay1 != addr2 { + t.Fatal() + } + err = bp1.SetFundsDue(big.NewInt(13)) + Require(t, err) + getDue1, err = bp1.FundsDue() + Require(t, err) + if getDue1.Uint64() != 13 { + t.Fatal() + } + + // test adding up the fundsDue + err = bp2.SetFundsDue(big.NewInt(42)) + Require(t, err) + getDue2, err = bp2.FundsDue() + Require(t, err) + if getDue2.Uint64() != 42 { + t.Fatal() + } + + totalDue, err := bpTable.TotalFundsDue() + Require(t, err) + if totalDue.Uint64() != 13+42 { + t.Fatal() + } +} diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index dec2037f94..141501ab95 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -23,40 +23,38 @@ import ( type L1PricingState struct { storage *storage.Storage + // parameters - sequencer storage.StorageBackedAddress - paySequencerFeesTo storage.StorageBackedAddress - payRewardsTo storage.StorageBackedAddress - equilibrationTime storage.StorageBackedUint64 - inertia storage.StorageBackedUint64 - perUnitReward storage.StorageBackedUint64 + batchPosterTable *BatchPostersTable + payRewardsTo storage.StorageBackedAddress + equilibrationTime storage.StorageBackedUint64 + inertia storage.StorageBackedUint64 + perUnitReward storage.StorageBackedUint64 // variables - currentTime storage.StorageBackedUint64 - lastUpdateTime storage.StorageBackedUint64 // timestamp of the last update from L1 that we processed - fundsDueToSequencer storage.StorageBackedBigInt - fundsDueForRewards storage.StorageBackedBigInt + currentTime storage.StorageBackedUint64 + lastUpdateTime storage.StorageBackedUint64 // timestamp of the last update from L1 that we processed + fundsDueForRewards storage.StorageBackedBigInt // funds collected since update are recorded as the balance in account L1PricerFundsPoolAddress unitsSinceUpdate storage.StorageBackedUint64 // calldata units collected for since last update pricePerUnit storage.StorageBackedBigInt // current price per calldata unit } var ( - SequencerAddress = common.HexToAddress("0xA4B000000000000000000073657175656e636572") + BatchPosterTableKey = []byte{0} + BatchPosterAddress = common.HexToAddress("0xA4B000000000000000000073657175656e636572") + BatchPosterPayToAddress = BatchPosterAddress L1PricerFundsPoolAddress = common.HexToAddress("0xA4B00000000000000000000000000000000000f6") ErrInvalidTime = errors.New("invalid timestamp") ) const ( - sequencerOffset uint64 = iota - paySequencerFeesToOffset - payRewardsToOffset + payRewardsToOffset uint64 = iota equilibrationTimeOffset inertiaOffset perUnitRewardOffset currentTimeOffset lastUpdateTimeOffset - fundsDueToSequencerOffset fundsDueForRewards unitsSinceOffset pricePerUnitOffset @@ -70,13 +68,15 @@ const ( ) func InitializeL1PricingState(sto *storage.Storage) error { - if err := sto.SetByUint64(sequencerOffset, util.AddressToHash(SequencerAddress)); err != nil { + bptStorage := sto.OpenSubStorage(BatchPosterTableKey) + if err := InitializeBatchPostersTable(bptStorage); err != nil { return err } - if err := sto.SetByUint64(paySequencerFeesToOffset, util.AddressToHash(SequencerAddress)); err != nil { + bpTable := OpenBatchPostersTable(bptStorage) + if _, err := bpTable.AddPoster(BatchPosterAddress, BatchPosterPayToAddress); err != nil { return err } - if err := sto.SetByUint64(payRewardsToOffset, util.AddressToHash(SequencerAddress)); err != nil { + if err := sto.SetByUint64(payRewardsToOffset, util.AddressToHash(BatchPosterAddress)); err != nil { return err } if err := sto.SetUint64ByUint64(equilibrationTimeOffset, InitialEquilibrationTime); err != nil { @@ -95,35 +95,21 @@ func InitializeL1PricingState(sto *storage.Storage) error { func OpenL1PricingState(sto *storage.Storage) *L1PricingState { return &L1PricingState{ sto, - sto.OpenStorageBackedAddress(sequencerOffset), - sto.OpenStorageBackedAddress(paySequencerFeesToOffset), + OpenBatchPostersTable(sto.OpenSubStorage(BatchPosterTableKey)), sto.OpenStorageBackedAddress(payRewardsToOffset), sto.OpenStorageBackedUint64(equilibrationTimeOffset), sto.OpenStorageBackedUint64(inertiaOffset), sto.OpenStorageBackedUint64(perUnitRewardOffset), sto.OpenStorageBackedUint64(currentTimeOffset), sto.OpenStorageBackedUint64(lastUpdateTimeOffset), - sto.OpenStorageBackedBigInt(fundsDueToSequencerOffset), sto.OpenStorageBackedBigInt(fundsDueForRewards), sto.OpenStorageBackedUint64(unitsSinceOffset), sto.OpenStorageBackedBigInt(pricePerUnitOffset), } } -func (ps *L1PricingState) Sequencer() (common.Address, error) { - return ps.sequencer.Get() -} - -func (ps *L1PricingState) SetSequencer(seq common.Address) error { - return ps.sequencer.Set(seq) -} - -func (ps *L1PricingState) PaySequencerFeesTo() (common.Address, error) { - return ps.paySequencerFeesTo.Get() -} - -func (ps *L1PricingState) SetPaySequencerFeesTo(addr common.Address) error { - return ps.paySequencerFeesTo.Set(addr) +func (ps *L1PricingState) BatchPosterTable() *BatchPostersTable { + return ps.batchPosterTable } func (ps *L1PricingState) PayRewardsTo() (common.Address, error) { @@ -162,14 +148,6 @@ func (ps *L1PricingState) SetLastUpdateTime(t uint64) error { return ps.lastUpdateTime.Set(t) } -func (ps *L1PricingState) FundsDueToSequencer() (*big.Int, error) { - return ps.fundsDueToSequencer.Get() -} - -func (ps *L1PricingState) SetFundsDueToSequencer(amt *big.Int) error { - return ps.fundsDueToSequencer.Set(amt) -} - func (ps *L1PricingState) FundsDueForRewards() (*big.Int, error) { return ps.fundsDueForRewards.Get() } @@ -199,10 +177,16 @@ func (ps *L1PricingState) UpdateTime(currentTime uint64) { _ = ps.SetCurrentTime(currentTime) } -// Update the pricing model based on a payment by the sequencer -func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm.EVM, updateTime uint64, currentTime uint64, weiSpent *big.Int) error { +// Update the pricing model based on a payment by a batch poster +func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm *vm.EVM, updateTime uint64, currentTime uint64, batchPoster common.Address, weiSpent *big.Int) error { + batchPosterTable := ps.BatchPosterTable() + posterState, err := batchPosterTable.OpenPoster(batchPoster) + if err != nil { + return err + } + // compute previous shortfall - fundsDueToSequencer, err := ps.FundsDueToSequencer() + totalFundsDue, err := batchPosterTable.TotalFundsDue() if err != nil { return err } @@ -210,7 +194,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm if err != nil { return err } - oldShortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) + oldShortfall := am.BigSub(am.BigAdd(totalFundsDue, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) // compute allocation fraction -- will allocate updateTimeDelta/timeDelta fraction of units and funds to this update lastUpdateTime, err := ps.LastUpdateTime() @@ -247,8 +231,12 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm if err != nil { return err } - fundsDueToSequencer = am.BigAdd(fundsDueToSequencer, weiSpent) - if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { + fundsDueToPoster, err := posterState.FundsDue() + if err != nil { + return err + } + fundsDueToPoster = am.BigAdd(fundsDueToPoster, weiSpent) + if err := posterState.SetFundsDue(fundsDueToPoster); err != nil { return err } newRewards := am.SaturatingUMul(updateTimeDelta, perUnitReward) / timeDelta @@ -257,7 +245,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm return err } - // settle up, by paying out available funds + // settle up our rewards owed, as much as possible payRewardsTo, err := ps.PayRewardsTo() if err != nil { return err @@ -276,23 +264,46 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm return err } } - sequencerPaymentAddr, err := ps.PaySequencerFeesTo() + + // settle up our batch poster payments owed, as much as possible + allPosterAddrs, err := batchPosterTable.AllPosters() if err != nil { return err } - paymentForSequencer := statedb.GetBalance(L1PricerFundsPoolAddress) - if am.BigLessThan(fundsDueToSequencer, paymentForSequencer) { - paymentForSequencer = fundsDueToSequencer - } - if paymentForSequencer.Sign() > 0 { - fundsDueToSequencer = new(big.Int).Sub(fundsDueToSequencer, paymentForSequencer) - if err := ps.SetFundsDueToSequencer(fundsDueToSequencer); err != nil { + remainingFundsDueToPosters := big.NewInt(0) + availableFunds := statedb.GetBalance(L1PricerFundsPoolAddress) + for _, posterAddr := range allPosterAddrs { + poster, err := batchPosterTable.OpenPoster(posterAddr) + if err != nil { return err } - err := util.TransferBalance(&L1PricerFundsPoolAddress, &sequencerPaymentAddr, paymentForSequencer, evm, util.TracingBeforeEVM) + balanceDueToPoster, err := poster.FundsDue() if err != nil { return err } + balanceToTransfer := balanceDueToPoster + if am.BigLessThan(availableFunds, balanceToTransfer) { + balanceToTransfer = availableFunds + } + if balanceToTransfer.Sign() > 0 { + addrToPay, err := poster.PayTo() + if err != nil { + return err + } + err = util.TransferBalance(&L1PricerFundsPoolAddress, &addrToPay, balanceToTransfer, evm, util.TracingBeforeEVM) + if err != nil { + return err + } + availableFunds = am.BigSub(availableFunds, balanceToTransfer) + balanceDueToPoster = am.BigSub(balanceDueToPoster, balanceToTransfer) + err = poster.SetFundsDue(balanceDueToPoster) + if err != nil { + return err + } + } + if balanceDueToPoster.Sign() > 0 { + remainingFundsDueToPosters = am.BigAdd(remainingFundsDueToPosters, balanceDueToPoster) + } } // update time @@ -302,7 +313,7 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm // adjust the price if unitsAllocated > 0 { - shortfall := am.BigSub(am.BigAdd(fundsDueToSequencer, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) + shortfall := am.BigSub(am.BigAdd(remainingFundsDueToPosters, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) inertia, err := ps.Inertia() if err != nil { return err @@ -334,14 +345,17 @@ func (ps *L1PricingState) UpdateForSequencerSpending(statedb vm.StateDB, evm *vm return nil } -func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, poster common.Address) { +func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common.Address) { tx.PosterCost = big.NewInt(0) tx.PosterIsReimbursable = false - sequencer, perr := ps.Sequencer() + contains, err := ps.batchPosterTable.ContainsPoster(posterAddr) + if err != nil || !contains { + return + } txBytes, merr := tx.MarshalBinary() txType := tx.Type() - if !util.TxTypeHasPosterCosts(txType) || perr != nil || merr != nil || poster != sequencer { + if !util.TxTypeHasPosterCosts(txType) || merr != nil { return } diff --git a/arbstate/inbox.go b/arbstate/inbox.go index 9644cabdd6..a485ddfcdd 100644 --- a/arbstate/inbox.go +++ b/arbstate/inbox.go @@ -368,7 +368,7 @@ func (r *inboxMultiplexer) getNextMsg() (*MessageWithMetadata, error) { Message: &arbos.L1IncomingMessage{ Header: &arbos.L1IncomingMessageHeader{ Kind: arbos.L1MessageType_L2Message, - Poster: l1pricing.SequencerAddress, + Poster: l1pricing.BatchPosterAddress, BlockNumber: blockNumber, Timestamp: timestamp, RequestId: nil, diff --git a/contracts/src/precompiles/ArbAggregator.sol b/contracts/src/precompiles/ArbAggregator.sol index 707378090f..e29ecf3f80 100644 --- a/contracts/src/precompiles/ArbAggregator.sol +++ b/contracts/src/precompiles/ArbAggregator.sol @@ -7,30 +7,41 @@ pragma solidity >=0.4.21 <0.9.0; /// @title Provides aggregators and their users methods for configuring how they participate in L1 aggregation. /// @notice Precompiled contract that exists in every Arbitrum chain at 0x000000000000000000000000000000000000006d interface ArbAggregator { - /// @notice Get the preferred aggregator for an address. - /// @param addr The address to fetch aggregator for - /// @return (preferredAggregatorAddress, isDefault) - /// isDefault is true if addr is set to prefer the default aggregator + /// @notice Deprecated, customization of preferred aggregator is no longer supported + /// @notice Get the address of an arbitrarily chosen batch poster. + /// @param addr ignored + /// @return (batchPosterAddress, true) function getPreferredAggregator(address addr) external view returns (address, bool); + /// @notice Deprecated, there is no longer a single preferred aggregator, use getBatchPosters instead /// @notice Get default aggregator. function getDefaultAggregator() external view returns (address); - /// @notice Set the preferred aggregator. - /// This reverts unless called by the aggregator, its fee collector, or a chain owner - /// @param newDefault New default aggregator - function setDefaultAggregator(address newDefault) external; - - /// @notice Get the address where fees to aggregator are sent. - /// @param aggregator The aggregator to get the fee collector for - /// @return The fee collectors address. This will often but not always be the same as the aggregator's address. - function getFeeCollector(address aggregator) external view returns (address); - - /// @notice Set the address where fees to aggregator are sent. - /// This reverts unless called by the aggregator, its fee collector, or a chain owner - /// @param aggregator The aggregator to set the fee collector for + /// @notice Get a list of all current batch posters + /// @return Batch poster addresses + function getBatchPosters() external view returns (address[] memory); + + /// @notice Deprecated, use addBatchPoster instead + /// @notice Adds newBatchPoster as a batch poster, if it is not already a batch poster + /// This reverts unless called by a chain owner + /// @param newBatchPoster New batch poster + function setDefaultAggregator(address newBatchPoster) external; + + /// @notice Adds newBatchPoster as a batch poster + /// This reverts unless called by a chain owner + /// @param newBatchPoster New batch poster + function addBatchPoster(address newBatchPoster) external; + + /// @notice Get the address where fees to batchPoster are sent. + /// @param batchPoster The batch poster to get the fee collector for + /// @return The fee collectors address. This will sometimes but not always be the same as the batch poster's address. + function getFeeCollector(address batchPoster) external view returns (address); + + /// @notice Set the address where fees to batchPoster are sent. + /// This reverts unless called by the batch poster, its fee collector, or a chain owner + /// @param batchPoster The batch poster to set the fee collector for /// @param newFeeCollector The new fee collector to set - function setFeeCollector(address aggregator, address newFeeCollector) external; + function setFeeCollector(address batchPoster, address newFeeCollector) external; /// @notice Deprecated, always returns zero /// @notice Get the tx base fee (in approximate L1 gas) for aggregator diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index f0d83e520d..7c5843b330 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -125,7 +125,11 @@ func init() { log.Error("failed to open ArbOS state", "err", err) return } - poster, _ := state.L1PricingState().Sequencer() + var poster common.Address + allPosters, _ := state.L1PricingState().BatchPosterTable().AllPosters() + if len(allPosters) > 0 { + poster = allPosters[0] + } if header.BaseFee.Sign() == 0 { // if gas is free or there's no reimbursable poster, the user won't pay for L1 data costs return diff --git a/precompiles/ArbAggregator.go b/precompiles/ArbAggregator.go index a367a40192..c87b3b0378 100644 --- a/precompiles/ArbAggregator.go +++ b/precompiles/ArbAggregator.go @@ -7,8 +7,6 @@ import ( "errors" "github.com/ethereum/go-ethereum/common" "math/big" - - "github.com/offchainlabs/nitro/arbos/arbosState" ) // Provides aggregators and their users methods for configuring how they participate in L1 aggregation. @@ -18,60 +16,111 @@ type ArbAggregator struct { Address addr // 0x6d } -// Gets an account's preferred aggregator +var ErrNotOwner = errors.New("must be called by chain owner") + +// [Deprecated] func (con ArbAggregator) GetPreferredAggregator(c ctx, evm mech, address addr) (prefAgg addr, isDefault bool, err error) { - sequencer, err := c.State.L1PricingState().Sequencer() - return sequencer, true, err + posters, err := c.State.L1PricingState().BatchPosterTable().AllPosters() + if err != nil { + return common.Address{}, false, err + } + if len(posters) == 0 { + return common.Address{}, false, errors.New("no batch posters exist") + } + return posters[0], true, err } -// Gets the chain's default aggregator +// [Deprecated] func (con ArbAggregator) GetDefaultAggregator(c ctx, evm mech) (addr, error) { - return c.State.L1PricingState().Sequencer() + posters, err := c.State.L1PricingState().BatchPosterTable().AllPosters() + if err != nil { + return common.Address{}, err + } + if len(posters) == 0 { + return common.Address{}, errors.New("no batch posters exist") + } + return posters[0], err +} + +// Get the addresses of all current batch posters +func (con ArbAggregator) GetBatchPosters(c ctx, evm mech) ([]addr, error) { + return c.State.L1PricingState().BatchPosterTable().AllPosters() } -// Sets the chain's default aggregator (caller must be the current default aggregator, its fee collector, or an owner) +// [Deprecated] func (con ArbAggregator) SetDefaultAggregator(c ctx, evm mech, newDefault addr) error { - l1State := c.State.L1PricingState() - allowed, err := accountIsSequencerOrCollectorOrOwner(c.caller, c.State) + isOwner, err := c.State.ChainOwners().IsMember(c.caller) + if err != nil { + return err + } + if !isOwner { + return ErrNotOwner + } + batchPosterTable := c.State.L1PricingState().BatchPosterTable() + isBatchPoster, err := batchPosterTable.ContainsPoster(newDefault) if err != nil { return err } - if !allowed { - return errors.New("Only the current default (or its fee collector / chain owner) may change the default") + if !isBatchPoster { + _, err = batchPosterTable.AddPoster(newDefault, newDefault) + if err != nil { + return err + } } - return l1State.SetSequencer(newDefault) + return nil } -// Gets an aggregator's fee collector -func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, aggregator addr) (addr, error) { - l1p := c.State.L1PricingState() - sequencer, err := l1p.Sequencer() +func (con ArbAggregator) AddBatchPoster(c ctx, evm mech, newBatchPoster addr) error { + isOwner, err := c.State.ChainOwners().IsMember(c.caller) if err != nil { - return common.Address{}, err + return err + } + if !isOwner { + return ErrNotOwner + } + batchPosterTable := c.State.L1PricingState().BatchPosterTable() + isBatchPoster, err := batchPosterTable.ContainsPoster(newBatchPoster) + if err != nil { + return err } - if aggregator != sequencer { - return common.Address{}, nil + if !isBatchPoster { + _, err = batchPosterTable.AddPoster(newBatchPoster, newBatchPoster) + if err != nil { + return err + } } - return c.State.L1PricingState().PaySequencerFeesTo() + return nil } -// Sets an aggregator's fee collector (caller must be the aggregator, its fee collector, or an owner) -func (con ArbAggregator) SetFeeCollector(c ctx, evm mech, aggregator addr, newFeeCollector addr) error { - sequencer, err := c.State.L1PricingState().Sequencer() +// Gets a batch poster's fee collector +func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, batchPoster addr) (addr, error) { + posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster) if err != nil { - return err + return addr{}, err } - if sequencer != aggregator { - return errors.New("Cannot set fee collector for non-sequencer address") + return posterInfo.PayTo() +} + +// Sets a batch poster's fee collector (caller must be the batch poster, its fee collector, or an owner) +func (con ArbAggregator) SetFeeCollector(c ctx, evm mech, batchPoster addr, newFeeCollector addr) error { + posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster) + if err != nil { + return err } - allowed, err := accountIsSequencerOrCollectorOrOwner(c.caller, c.State) + oldFeeCollector, err := posterInfo.PayTo() if err != nil { return err } - if !allowed { - return errors.New("Only an aggregator (or its fee collector / chain owner) may change its fee collector") + if c.caller != batchPoster && c.caller != oldFeeCollector { + isOwner, err := c.State.ChainOwners().IsMember(c.caller) + if err != nil { + return err + } + if !isOwner { + return errors.New("Only a batch poster (or its fee collector / chain owner) may change its fee collector") + } } - return c.State.L1PricingState().SetPaySequencerFeesTo(newFeeCollector) + return posterInfo.SetPayTo(newFeeCollector) } // Gets an aggregator's current fixed fee to submit a tx @@ -85,16 +134,3 @@ func (con ArbAggregator) SetTxBaseFee(c ctx, evm mech, aggregator addr, feeInL1G // This is deprecated and is now a no-op. return nil } - -func accountIsSequencerOrCollectorOrOwner(account addr, state *arbosState.ArbosState) (bool, error) { - l1State := state.L1PricingState() - sequencer, err := l1State.Sequencer() - if account == sequencer || err != nil { - return true, err - } - collector, err := l1State.PaySequencerFeesTo() - if account == collector || err != nil { - return true, err - } - return state.ChainOwners().IsMember(account) -} diff --git a/precompiles/ArbAggregator_test.go b/precompiles/ArbAggregator_test.go index 344d829422..ce1cebde5d 100644 --- a/precompiles/ArbAggregator_test.go +++ b/precompiles/ArbAggregator_test.go @@ -12,61 +12,30 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" ) -func TestDefaultAggregator(t *testing.T) { +func TestArbAggregatorBatchPosters(t *testing.T) { evm := newMockEVMForTesting() context := testContext(common.Address{}, evm) addr := common.BytesToAddress(crypto.Keccak256([]byte{})[:20]) - // initial default aggregator should be zero address - def, err := ArbAggregator{}.GetDefaultAggregator(context, evm) + // initially should have one batch poster + bps, err := ArbAggregator{}.GetBatchPosters(context, evm) Require(t, err) - if def != (l1pricing.SequencerAddress) { + if len(bps) != 1 { Fail(t) } - // set default aggregator to addr + // add addr as a batch poster Require(t, ArbDebug{}.BecomeChainOwner(context, evm)) - Require(t, ArbAggregator{}.SetDefaultAggregator(context, evm, addr)) + Require(t, ArbAggregator{}.AddBatchPoster(context, evm, addr)) - // default aggregator should now be addr - res, err := ArbAggregator{}.GetDefaultAggregator(context, evm) + // there should now be two batch posters, and addr should be one of them + bps, err = ArbAggregator{}.GetBatchPosters(context, evm) Require(t, err) - if res != addr { + if len(bps) != 2 { Fail(t) } -} - -func TestPreferredAggregator(t *testing.T) { - evm := newMockEVMForTesting() - agg := ArbAggregator{} - - userAddr := common.BytesToAddress(crypto.Keccak256([]byte{0})[:20]) - defaultAggAddr := common.BytesToAddress(crypto.Keccak256([]byte{1})[:20]) - - callerCtx := testContext(common.Address{}, evm) - - // initial preferred aggregator should be the default of zero address - res, isDefault, err := ArbAggregator{}.GetPreferredAggregator(callerCtx, evm, userAddr) - Require(t, err) - if !isDefault { - Fail(t) - } - if res != (l1pricing.SequencerAddress) { - Fail(t) - } - - // set default aggregator - Require(t, ArbDebug{}.BecomeChainOwner(callerCtx, evm)) - Require(t, agg.SetDefaultAggregator(callerCtx, evm, defaultAggAddr)) - - // preferred aggregator should be the new default address - res, isDefault, err = agg.GetPreferredAggregator(callerCtx, evm, userAddr) - Require(t, err) - if !isDefault { - Fail(t) - } - if res != defaultAggAddr { + if bps[0] != addr && bps[1] != addr { Fail(t) } } @@ -75,7 +44,7 @@ func TestFeeCollector(t *testing.T) { evm := newMockEVMForTesting() agg := ArbAggregator{} - aggAddr := l1pricing.SequencerAddress + aggAddr := l1pricing.BatchPosterAddress collectorAddr := common.BytesToAddress(crypto.Keccak256([]byte{1})[:20]) impostorAddr := common.BytesToAddress(crypto.Keccak256([]byte{2})[:20]) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index d49e30cc45..b94837cf2c 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -50,11 +50,7 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator( // Get prices in wei when using the caller's preferred aggregator func (con ArbGasInfo) GetPricesInWei(c ctx, evm mech) (huge, huge, huge, huge, huge, huge, error) { - maybeAggregator, err := c.State.L1PricingState().Sequencer() - if err != nil { - return nil, nil, nil, nil, nil, nil, err - } - return con.GetPricesInWeiWithAggregator(c, evm, maybeAggregator) + return con.GetPricesInWeiWithAggregator(c, evm, addr{}) } // Get prices in ArbGas when using the provided aggregator @@ -75,11 +71,7 @@ func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregato // Get prices in ArbGas when using the caller's preferred aggregator func (con ArbGasInfo) GetPricesInArbGas(c ctx, evm mech) (huge, huge, huge, error) { - maybeAggregator, err := c.State.L1PricingState().Sequencer() - if err != nil { - return nil, nil, nil, err - } - return con.GetPricesInArbGasWithAggregator(c, evm, maybeAggregator) + return con.GetPricesInArbGasWithAggregator(c, evm, addr{}) } // Get the rollup's speed limit, pool size, and tx gas limit diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 5fb17f1c38..051f6c4965 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -36,13 +36,13 @@ func TestSequencerFeePaid(t *testing.T) { l1Estimate, err := arbGasInfo.GetL1GasPriceEstimate(callOpts) Require(t, err) networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount) - seqBefore := GetBalance(t, ctx, l2client, l1pricing.SequencerAddress) + seqBefore := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) l2info.GasPrice = GetBaseFee(t, l2client, ctx) tx, receipt := TransferBalance(t, "Faucet", "Faucet", big.NewInt(0), l2info, l2client, ctx) networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount) - seqAfter := GetBalance(t, ctx, l2client, l1pricing.SequencerAddress) + seqAfter := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) networkRevenue := arbmath.BigSub(networkAfter, networkBefore) seqRevenue := arbmath.BigSub(seqAfter, seqBefore) diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index e59e9a80e0..0f36bb9986 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -50,7 +50,7 @@ func TestSequencerCompensation(t *testing.T) { Fail(t, "Unexpected balance:", l2balance) } - initialSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.SequencerAddress, big.NewInt(0)) + initialSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.BatchPosterAddress, big.NewInt(0)) Require(t, err) if initialSeqBalance.Sign() != 0 { Fail(t, "Unexpected initial sequencer balance:", initialSeqBalance) From 655c0c1cafdd11d98e57e0e3a8acfba30a2dc526 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 4 Jun 2022 09:27:28 -0400 Subject: [PATCH 27/61] Use AddressSet in BatchPosterTable --- arbos/l1pricing/batchPoster.go | 77 +++++++++++----------------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index 2ed95eb104..7dbe50bf5c 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -6,100 +6,83 @@ package l1pricing import ( "errors" "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/addressSet" "github.com/offchainlabs/nitro/arbos/storage" - "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/util/arbmath" "math/big" ) var ( + PosterAddrsKey = []byte{0} + PosterInfoKey = []byte{1} + ErrNotExist = errors.New("batch poster does not exist in table") ErrAlreadyExists = errors.New("tried to add a batch poster that already exists") ) // layout of storage in the table type BatchPostersTable struct { - storage *storage.Storage - numPosters storage.StorageBackedUint64 + posterAddrs *addressSet.AddressSet + posterInfo *storage.Storage } type BatchPosterState struct { - existsIfNonzero storage.StorageBackedUint64 - fundsDue storage.StorageBackedBigInt - payTo storage.StorageBackedAddress + fundsDue storage.StorageBackedBigInt + payTo storage.StorageBackedAddress } func InitializeBatchPostersTable(storage *storage.Storage) error { - // no initialization needed at present - return nil + // no initialization needed for posterInfo + return addressSet.Initialize(storage.OpenSubStorage(PosterAddrsKey)) } func OpenBatchPostersTable(storage *storage.Storage) *BatchPostersTable { return &BatchPostersTable{ - storage: storage, - numPosters: storage.OpenStorageBackedUint64(0), + posterAddrs: addressSet.OpenAddressSet(storage.OpenSubStorage(PosterAddrsKey)), + posterInfo: storage.OpenSubStorage(PosterInfoKey), } } func (bpt *BatchPostersTable) OpenPoster(poster common.Address) (*BatchPosterState, error) { - bpState := bpt.internalOpen(poster) - existsIfNonzero, err := bpState.existsIfNonzero.Get() + isBatchPoster, err := bpt.posterAddrs.IsMember(poster) if err != nil { return nil, err } - if existsIfNonzero == 0 { + if !isBatchPoster { return nil, ErrNotExist } - return bpState, nil + return bpt.internalOpen(poster), nil } func (bpt *BatchPostersTable) internalOpen(poster common.Address) *BatchPosterState { - bpStorage := bpt.storage.OpenSubStorage(poster.Bytes()) + bpStorage := bpt.posterInfo.OpenSubStorage(poster.Bytes()) return &BatchPosterState{ - existsIfNonzero: bpStorage.OpenStorageBackedUint64(0), - fundsDue: bpStorage.OpenStorageBackedBigInt(1), - payTo: bpStorage.OpenStorageBackedAddress(2), + fundsDue: bpStorage.OpenStorageBackedBigInt(0), + payTo: bpStorage.OpenStorageBackedAddress(1), } } func (bpt *BatchPostersTable) ContainsPoster(poster common.Address) (bool, error) { - _, err := bpt.OpenPoster(poster) - if errors.Is(err, ErrNotExist) { - return false, nil - } - if err != nil { - return false, err - } - return true, nil + return bpt.posterAddrs.IsMember(poster) } func (bpt *BatchPostersTable) AddPoster(posterAddress common.Address, payTo common.Address) (*BatchPosterState, error) { - bpState := bpt.internalOpen(posterAddress) - alreadyExists, err := bpState.existsIfNonzero.Get() + isBatchPoster, err := bpt.posterAddrs.IsMember(posterAddress) if err != nil { return nil, err } - if alreadyExists != 0 { + if isBatchPoster { return nil, ErrAlreadyExists } + bpState := bpt.internalOpen(posterAddress) if err := bpState.fundsDue.Set(big.NewInt(0)); err != nil { return nil, err } if err := bpState.payTo.Set(payTo); err != nil { return nil, err } - if err := bpState.existsIfNonzero.Set(1); err != nil { - return nil, err - } - numPosters, err := bpt.numPosters.Get() - if err != nil { - return nil, err - } - if err := bpt.storage.SetByUint64(numPosters+1, util.AddressToHash(posterAddress)); err != nil { - return nil, err - } - if err := bpt.numPosters.Set(numPosters + 1); err != nil { + if err := bpt.posterAddrs.Add(posterAddress); err != nil { return nil, err } @@ -107,19 +90,7 @@ func (bpt *BatchPostersTable) AddPoster(posterAddress common.Address, payTo comm } func (bpt *BatchPostersTable) AllPosters() ([]common.Address, error) { - numPosters, err := bpt.numPosters.Get() - if err != nil { - return nil, err - } - ret := []common.Address{} - for i := uint64(0); i < numPosters; i++ { - posterAddrAsHash, err := bpt.storage.GetByUint64(i + 1) - if err != nil { - return nil, err - } - ret = append(ret, common.BytesToAddress(posterAddrAsHash.Bytes())) - } - return ret, nil + return bpt.posterAddrs.AllMembers() } func (bpt *BatchPostersTable) TotalFundsDue() (*big.Int, error) { From ffde9e81990086fd9ee1dbd9c1d887b5a53dc814 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 4 Jun 2022 10:06:45 -0400 Subject: [PATCH 28/61] Add ArbOwner methods to control params --- arbos/l1pricing/l1pricing.go | 12 ++++++++++++ contracts/src/precompiles/ArbOwner.sol | 12 ++++++++++++ precompiles/ArbOwner.go | 16 ++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 141501ab95..988a8a313e 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -116,10 +116,18 @@ func (ps *L1PricingState) PayRewardsTo() (common.Address, error) { return ps.payRewardsTo.Get() } +func (ps *L1PricingState) SetPayRewardsTo(addr common.Address) error { + return ps.payRewardsTo.Set(addr) +} + func (ps *L1PricingState) EquilibrationTime() (uint64, error) { return ps.equilibrationTime.Get() } +func (ps *L1PricingState) SetEquilibrationTime(equilTime uint64) error { + return ps.equilibrationTime.Set(equilTime) +} + func (ps *L1PricingState) Inertia() (uint64, error) { return ps.inertia.Get() } @@ -132,6 +140,10 @@ func (ps *L1PricingState) PerUnitReward() (uint64, error) { return ps.perUnitReward.Get() } +func (ps *L1PricingState) SetPerUnitReward(weiPerSecond uint64) error { + return ps.perUnitReward.Set(weiPerSecond) +} + func (ps *L1PricingState) CurrentTime() (uint64, error) { return ps.currentTime.Get() } diff --git a/contracts/src/precompiles/ArbOwner.sol b/contracts/src/precompiles/ArbOwner.sol index d0283c19a4..6502f2e62f 100644 --- a/contracts/src/precompiles/ArbOwner.sol +++ b/contracts/src/precompiles/ArbOwner.sol @@ -66,6 +66,18 @@ interface ArbOwner { /// @notice Upgrades ArbOS to the requested version at the requested timestamp function scheduleArbOSUpgrade(uint64 newVersion, uint64 timestamp) external; + /// @notice Sets equilibration time parameter for L1 price adjustment algorithm + function setL1PricingEquilibrationTime(uint64 equilibrationTime) external; + + /// @notice Sets inertia parameter for L1 price adjustment algorithm + function setL1PricingInertia(uint64 inertia) external; + + /// @notice Sets reward recipient address for L1 price adjustment algorithm + function setL1PricingRewardRecipient(address recipient) external; + + /// @notice Sets reward amount for L1 price adjustment algorithm, in wei per second + function setL1PricingRewardRate(uint64 weiPerSecond) external; + // Emitted when a successful call is made to this precompile event OwnerActs(bytes4 indexed method, address indexed owner, bytes data); } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 37d987546d..6eb3364dc1 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -117,3 +117,19 @@ func (con ArbOwner) SetNetworkFeeAccount(c ctx, evm mech, newNetworkFeeAccount a func (con ArbOwner) ScheduleArbOSUpgrade(c ctx, evm mech, newVersion uint64, timestamp uint64) error { return c.State.ScheduleArbOSUpgrade(newVersion, timestamp) } + +func (con ArbOwner) SetL1PricingEquilibrationTime(c ctx, evm mech, equilibrationTime uint64) error { + return c.State.L1PricingState().SetEquilibrationTime(equilibrationTime) +} + +func (con ArbOwner) SetL1PricingInertia(c ctx, evm mech, inertia uint64) error { + return c.State.L1PricingState().SetInertia(inertia) +} + +func (con ArbOwner) SetL1PricingRewardRecipient(c ctx, evm mech, recipient addr) error { + return c.State.L1PricingState().SetPayRewardsTo(recipient) +} + +func (con ArbOwner) SetL1PricingRewardRate(c ctx, evm mech, weiPerSecond uint64) error { + return c.State.L1PricingState().SetPerUnitReward(weiPerSecond) +} From 62f40ab9bd64bd347950ef131abb38eb2666ecff Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 4 Jun 2022 14:21:31 -0400 Subject: [PATCH 29/61] Add test and fix bugs --- arbos/l1pricing/l1pricing.go | 54 +++++------ arbos/l1pricing_test.go | 119 +++++++++++++++++++++++++ contracts/src/precompiles/ArbOwner.sol | 4 +- precompiles/ArbOwner.go | 4 +- util/arbmath/math.go | 10 +++ 5 files changed, 155 insertions(+), 36 deletions(-) create mode 100644 arbos/l1pricing_test.go diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 988a8a313e..ae8492a28e 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -140,8 +140,8 @@ func (ps *L1PricingState) PerUnitReward() (uint64, error) { return ps.perUnitReward.Get() } -func (ps *L1PricingState) SetPerUnitReward(weiPerSecond uint64) error { - return ps.perUnitReward.Set(weiPerSecond) +func (ps *L1PricingState) SetPerUnitReward(weiPerUnit uint64) error { + return ps.perUnitReward.Set(weiPerUnit) } func (ps *L1PricingState) CurrentTime() (uint64, error) { @@ -196,6 +196,14 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } + dueToPoster, err := posterState.FundsDue() + if err != nil { + return err + } + err = posterState.SetFundsDue(am.BigAdd(dueToPoster, weiSpent)) + if err != nil { + return err + } // compute previous shortfall totalFundsDue, err := batchPosterTable.TotalFundsDue() @@ -214,7 +222,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * return err } if lastUpdateTime == 0 && currentTime > 0 { // it's the first update, so there isn't a last update time - lastUpdateTime = currentTime - 1 + lastUpdateTime = updateTime - 1 } if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { return ErrInvalidTime @@ -235,47 +243,30 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * // allocate funds to this update collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) - fundsToMove := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, updateTimeDelta), timeDelta) - statedb.SubBalance(L1PricerFundsPoolAddress, fundsToMove) + availableFunds := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, updateTimeDelta), timeDelta) - // update amounts due + // pay rewards, as much as possible perUnitReward, err := ps.PerUnitReward() if err != nil { return err } - fundsDueToPoster, err := posterState.FundsDue() - if err != nil { - return err - } - fundsDueToPoster = am.BigAdd(fundsDueToPoster, weiSpent) - if err := posterState.SetFundsDue(fundsDueToPoster); err != nil { - return err + paymentForRewards := am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) + if am.BigLessThan(availableFunds, paymentForRewards) { + unitsAllocated = am.SaturatingCastToUint(am.BigDivByUint(availableFunds, perUnitReward)) + paymentForRewards = am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) } - newRewards := am.SaturatingUMul(updateTimeDelta, perUnitReward) / timeDelta - fundsDueForRewards = am.BigAddByUint(fundsDueForRewards, newRewards) - if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { + if err := ps.SetFundsDueForRewards(am.BigSub(fundsDueForRewards, paymentForRewards)); err != nil { return err } - - // settle up our rewards owed, as much as possible payRewardsTo, err := ps.PayRewardsTo() if err != nil { return err } - paymentForRewards := statedb.GetBalance(L1PricerFundsPoolAddress) - if am.BigLessThan(fundsDueForRewards, paymentForRewards) { - paymentForRewards = fundsDueForRewards - } - if paymentForRewards.Sign() > 0 { - fundsDueForRewards = am.BigSub(fundsDueForRewards, paymentForRewards) - if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { - return err - } - err := util.TransferBalance(&L1PricerFundsPoolAddress, &payRewardsTo, paymentForRewards, evm, util.TracingBeforeEVM) - if err != nil { - return err - } + err = util.TransferBalance(&L1PricerFundsPoolAddress, &payRewardsTo, paymentForRewards, evm, util.TracingBeforeEVM) + if err != nil { + return err } + availableFunds = am.BigSub(availableFunds, paymentForRewards) // settle up our batch poster payments owed, as much as possible allPosterAddrs, err := batchPosterTable.AllPosters() @@ -283,7 +274,6 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * return err } remainingFundsDueToPosters := big.NewInt(0) - availableFunds := statedb.GetBalance(L1PricerFundsPoolAddress) for _, posterAddr := range allPosterAddrs { poster, err := batchPosterTable.OpenPoster(posterAddr) if err != nil { diff --git a/arbos/l1pricing_test.go b/arbos/l1pricing_test.go new file mode 100644 index 0000000000..442d04d0e9 --- /dev/null +++ b/arbos/l1pricing_test.go @@ -0,0 +1,119 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbos + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/util/arbmath" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/burn" +) + +const ( + unitReward_test = 10 + unitsPerSecond_test = 78 + fundsCollectedPerSecond_test = 7800 + fundsSpent_test = 3000 +) + +func TestL1PricingFundsDue(t *testing.T) { + evm := newMockEVMForTesting() + burner := burn.NewSystemBurner(nil, false) + arbosSt, err := arbosState.OpenArbosState(evm.StateDB, burner) + Require(t, err) + + l1p := arbosSt.L1PricingState() + err = l1p.SetPerUnitReward(unitReward_test) + Require(t, err) + rewardAddress := common.Address{137} + err = l1p.SetPayRewardsTo(rewardAddress) + Require(t, err) + + posterTable := l1p.BatchPosterTable() + + // check initial funds state + rewardsDue, err := l1p.FundsDueForRewards() + Require(t, err) + if rewardsDue.Sign() != 0 { + t.Fatal() + } + if evm.StateDB.GetBalance(rewardAddress).Sign() != 0 { + t.Fatal() + } + posterAddrs, err := posterTable.AllPosters() + Require(t, err) + if len(posterAddrs) != 1 { + t.Fatal() + } + firstPoster := posterAddrs[0] + firstPayTo := common.Address{1, 2} + poster, err := posterTable.OpenPoster(firstPoster) + Require(t, err) + due, err := poster.FundsDue() + Require(t, err) + if due.Sign() != 0 { + t.Fatal() + } + err = poster.SetPayTo(firstPayTo) + Require(t, err) + + // add another poster + secondPoster := common.Address{3, 4, 5} + secondPayTo := common.Address{6, 7} + _, err = posterTable.AddPoster(secondPoster, secondPayTo) + Require(t, err) + + // create some fake collection + balanceAdded := big.NewInt(fundsCollectedPerSecond_test * 3) + unitsAdded := uint64(unitsPerSecond_test * 3) + evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, balanceAdded) + err = l1p.SetUnitsSinceUpdate(unitsAdded) + Require(t, err) + + // submit a fake spending update, then check that balances are correct + err = l1p.UpdateForBatchPosterSpending(evm.StateDB, evm, 1, 3, firstPoster, big.NewInt(fundsSpent_test)) + Require(t, err) + rewardRecipientBalance := evm.StateDB.GetBalance(rewardAddress) + expectedUnitsCollected := unitsPerSecond_test + if !arbmath.BigEquals(rewardRecipientBalance, big.NewInt(int64(unitReward_test*expectedUnitsCollected))) { + t.Fatal(rewardRecipientBalance, unitReward_test*expectedUnitsCollected) + } + unitsRemaining, err := l1p.UnitsSinceUpdate() + Require(t, err) + if unitsRemaining != (3*unitsPerSecond_test)-uint64(expectedUnitsCollected) { + t.Fatal(unitsRemaining, (3*unitsPerSecond_test)-uint64(expectedUnitsCollected)) + } + remainingFunds := arbmath.BigSub(balanceAdded, rewardRecipientBalance) + maxCollectable := big.NewInt(fundsSpent_test) + if arbmath.BigLessThan(remainingFunds, maxCollectable) { + maxCollectable = remainingFunds + } + fundsReceived := evm.StateDB.GetBalance(firstPayTo) + if !arbmath.BigEquals(fundsReceived, maxCollectable) { + t.Fatal(fundsReceived, maxCollectable) + } + fundsStillHeld := evm.StateDB.GetBalance(l1pricing.L1PricerFundsPoolAddress) + if !arbmath.BigEquals(fundsStillHeld, arbmath.BigSub(remainingFunds, maxCollectable)) { + t.Fatal() + } +} + +func newMockEVMForTesting() *vm.EVM { + chainConfig := params.ArbitrumDevTestChainConfig() + _, statedb := arbosState.NewArbosMemoryBackedArbOSState() + context := vm.BlockContext{ + BlockNumber: big.NewInt(0), + GasLimit: ^uint64(0), + Time: big.NewInt(0), + } + evm := vm.NewEVM(context, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + evm.ProcessingHook = &TxProcessor{} + return evm +} diff --git a/contracts/src/precompiles/ArbOwner.sol b/contracts/src/precompiles/ArbOwner.sol index 6502f2e62f..eaf8df4fc9 100644 --- a/contracts/src/precompiles/ArbOwner.sol +++ b/contracts/src/precompiles/ArbOwner.sol @@ -75,8 +75,8 @@ interface ArbOwner { /// @notice Sets reward recipient address for L1 price adjustment algorithm function setL1PricingRewardRecipient(address recipient) external; - /// @notice Sets reward amount for L1 price adjustment algorithm, in wei per second - function setL1PricingRewardRate(uint64 weiPerSecond) external; + /// @notice Sets reward amount for L1 price adjustment algorithm, in wei per unit + function setL1PricingRewardRate(uint64 weiPerUnit) external; // Emitted when a successful call is made to this precompile event OwnerActs(bytes4 indexed method, address indexed owner, bytes data); diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 6eb3364dc1..baaeedeed5 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -130,6 +130,6 @@ func (con ArbOwner) SetL1PricingRewardRecipient(c ctx, evm mech, recipient addr) return c.State.L1PricingState().SetPayRewardsTo(recipient) } -func (con ArbOwner) SetL1PricingRewardRate(c ctx, evm mech, weiPerSecond uint64) error { - return c.State.L1PricingState().SetPerUnitReward(weiPerSecond) +func (con ArbOwner) SetL1PricingRewardRate(c ctx, evm mech, weiPerUnit uint64) error { + return c.State.L1PricingState().SetPerUnitReward(weiPerUnit) } diff --git a/util/arbmath/math.go b/util/arbmath/math.go index ffe01fcf8d..336c334a31 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -246,6 +246,16 @@ func SaturatingUCast(value int64) uint64 { return uint64(value) } +func SaturatingCastToUint(value *big.Int) uint64 { + if value.Sign() < 0 { + return 0 + } + if !value.IsUint64() { + return math.MaxUint64 + } + return value.Uint64() +} + // the number of eth-words needed to store n bytes func WordsForBytes(nbytes uint64) uint64 { return (nbytes + 31) / 32 From af563be6325ae71b0c2908e1460e4f6e84224422 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sat, 4 Jun 2022 18:00:02 -0400 Subject: [PATCH 30/61] Send report message from L1 --- contracts/src/bridge/IInbox.sol | 6 +++++ contracts/src/bridge/Inbox.sol | 30 +++++++++++++++++++++++- contracts/src/bridge/SequencerInbox.sol | 11 +++++++++ contracts/src/libraries/MessageTypes.sol | 1 + contracts/src/mocks/InboxStub.sol | 8 +++++++ contracts/src/rollup/BridgeCreator.sol | 9 +++++-- 6 files changed, 62 insertions(+), 3 deletions(-) diff --git a/contracts/src/bridge/IInbox.sol b/contracts/src/bridge/IInbox.sol index eef60dc3dd..9e416222b9 100644 --- a/contracts/src/bridge/IInbox.sol +++ b/contracts/src/bridge/IInbox.sol @@ -73,6 +73,12 @@ interface IInbox is IMessageProvider { bytes calldata data ) external payable returns (uint256); + function submitBatchSpendingReportTransaction( + address batchPosterAddr, + bytes32 dataHash, + uint256 batchNumber + ) external returns (uint256); + /// @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error function createRetryableTicket( address to, diff --git a/contracts/src/bridge/Inbox.sol b/contracts/src/bridge/Inbox.sol index acb66c9ac7..2f7f88b4c0 100644 --- a/contracts/src/bridge/Inbox.sol +++ b/contracts/src/bridge/Inbox.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.4; import "./IInbox.sol"; +import "./ISequencerInbox.sol"; import "./IBridge.sol"; import "./Messages.sol"; @@ -15,6 +16,7 @@ import { L1MessageType_L2FundedByL1, L1MessageType_submitRetryableTx, L1MessageType_ethDeposit, + L1MessageType_batchPostingReport, L2MessageType_unsignedEOATx, L2MessageType_unsignedContractTx } from "../libraries/MessageTypes.sol"; @@ -31,6 +33,7 @@ import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; */ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { IBridge public override bridge; + ISequencerInbox public sequencerInbox; modifier onlyOwner() { // whoevever owns the Bridge, also owns the Inbox. this is usually the rollup contract @@ -49,9 +52,14 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { _unpause(); } - function initialize(IBridge _bridge) external initializer onlyDelegated { + function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox) + external + initializer + onlyDelegated + { if (address(bridge) != address(0)) revert AlreadyInit(); bridge = _bridge; + sequencerInbox = _sequencerInbox; __Pausable_init(); } @@ -190,6 +198,26 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { ); } + function submitBatchSpendingReportTransaction( + address batchPosterAddr, + bytes32 dataHash, + uint256 batchNumber + ) external virtual override whenNotPaused returns (uint256) { + require(ISequencerInbox(msg.sender) == sequencerInbox, "unauthorized"); + return + _deliverMessage( + L1MessageType_batchPostingReport, + batchPosterAddr, + abi.encodePacked( + block.timestamp, + batchPosterAddr, + dataHash, + batchNumber, + block.basefee + ) + ); + } + /** * @notice Get the L1 fee for submitting a retryable * @dev This fee can be paid by funds already in the L2 aliased address or by the current message value diff --git a/contracts/src/bridge/SequencerInbox.sol b/contracts/src/bridge/SequencerInbox.sol index 53dbc41dd8..ddf0a73251 100644 --- a/contracts/src/bridge/SequencerInbox.sol +++ b/contracts/src/bridge/SequencerInbox.sol @@ -5,10 +5,12 @@ pragma solidity ^0.8.0; import "./IBridge.sol"; +import "./IInbox.sol"; import "./ISequencerInbox.sol"; import "../rollup/IRollupLogic.sol"; import "./Messages.sol"; +import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; import {GasRefundEnabled, IGasRefunder} from "../libraries/IGasRefunder.sol"; import "../libraries/DelegateCallAware.sol"; import {MAX_DATA_SIZE} from "../libraries/Constants.sol"; @@ -25,6 +27,7 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox uint256 public totalDelayedMessagesRead; IBridge public delayedBridge; + IInbox public delayedInbox; /// @dev The size of the batch header uint256 public constant HEADER_LENGTH = 40; @@ -46,12 +49,14 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox function initialize( IBridge delayedBridge_, + IInbox delayedInbox_, address rollup_, ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_ ) external onlyDelegated { if (delayedBridge != IBridge(address(0))) revert AlreadyInit(); if (delayedBridge_ == IBridge(address(0))) revert HadZeroInit(); delayedBridge = delayedBridge_; + delayedInbox = delayedInbox_; rollup = rollup_; maxTimeVariation = maxTimeVariation_; } @@ -150,6 +155,7 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox dataHash, afterDelayedMessagesRead ); + _reportBatchSpending(dataHash, sequenceNumber); emit SequencerBatchDelivered( inboxAccs.length - 1, beforeAcc, @@ -178,6 +184,7 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox dataHash, afterDelayedMessagesRead ); + _reportBatchSpending(dataHash, sequenceNumber); emit SequencerBatchDelivered( sequenceNumber, beforeAcc, @@ -190,6 +197,10 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox emit SequencerBatchData(sequenceNumber, data); } + function _reportBatchSpending(bytes32 dataHash, uint256 sequenceNumber) internal { + delayedInbox.submitBatchSpendingReportTransaction(msg.sender, dataHash, sequenceNumber); + } + function dasKeysetHashFromBatchData(bytes memory data) internal pure returns (bytes32) { if (data.length < 33 || data[0] & 0x80 == 0) { return bytes32(0); diff --git a/contracts/src/libraries/MessageTypes.sol b/contracts/src/libraries/MessageTypes.sol index 770a8135ea..093cb332a2 100644 --- a/contracts/src/libraries/MessageTypes.sol +++ b/contracts/src/libraries/MessageTypes.sol @@ -8,6 +8,7 @@ uint8 constant L2_MSG = 3; uint8 constant L1MessageType_L2FundedByL1 = 7; uint8 constant L1MessageType_submitRetryableTx = 9; uint8 constant L1MessageType_ethDeposit = 12; +uint8 constant L1MessageType_batchPostingReport = 13; uint8 constant L2MessageType_unsignedEOATx = 0; uint8 constant L2MessageType_unsignedContractTx = 1; diff --git a/contracts/src/mocks/InboxStub.sol b/contracts/src/mocks/InboxStub.sol index c9677855c9..de32569523 100644 --- a/contracts/src/mocks/InboxStub.sol +++ b/contracts/src/mocks/InboxStub.sol @@ -99,6 +99,14 @@ contract InboxStub is IInbox { revert("NOT_IMPLEMENTED"); } + function submitBatchSpendingReportTransaction( + address, + bytes32, + uint256 + ) external pure override returns (uint256) { + revert("NOT_IMPLEMENTED"); + } + function createRetryableTicket( address, uint256, diff --git a/contracts/src/rollup/BridgeCreator.sol b/contracts/src/rollup/BridgeCreator.sol index e684320280..50c34d947e 100644 --- a/contracts/src/rollup/BridgeCreator.sol +++ b/contracts/src/rollup/BridgeCreator.sol @@ -101,8 +101,13 @@ contract BridgeCreator is Ownable { } frame.delayedBridge.initialize(); - frame.sequencerInbox.initialize(IBridge(frame.delayedBridge), rollup, maxTimeVariation); - frame.inbox.initialize(IBridge(frame.delayedBridge)); + frame.sequencerInbox.initialize( + IBridge(frame.delayedBridge), + IInbox(frame.inbox), + rollup, + maxTimeVariation + ); + frame.inbox.initialize(IBridge(frame.delayedBridge), ISequencerInbox(frame.sequencerInbox)); frame.rollupEventBridge.initialize(address(frame.delayedBridge), rollup); frame.outbox.initialize(rollup, IBridge(frame.delayedBridge)); From 7b45d030bdb30dc3504d6d8391f727fbe7b1dcd3 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 6 Jun 2022 07:42:35 -0400 Subject: [PATCH 31/61] Update submodule pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index d3f41784e6..471db8b266 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit d3f41784e61042cc0b79b8c8be342e6d56cd5edd +Subproject commit 471db8b2667a7d2c5f7517c13d52237db93b0d48 From eb0aacb95c0d267f3a3a7d58123842b10c3d44ca Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Mon, 6 Jun 2022 07:42:46 -0400 Subject: [PATCH 32/61] Debugging WIP --- arbos/block_processor.go | 5 ++++- arbos/internal_tx.go | 8 +++++--- arbos/l1pricing/l1pricing.go | 20 ++++++++++++++------ arbos/storage/storage.go | 3 ++- arbos/tx_processor.go | 7 ++++++- contracts/src/bridge/SequencerInbox.sol | 8 ++++++-- nodeInterface/virtual-contracts.go | 2 +- 7 files changed, 38 insertions(+), 15 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 6ab4a98379..8d68be3fad 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -206,8 +206,11 @@ func ProduceBlockAdvanced( } else { tx = txes[0] txes = txes[1:] - switch tx.GetInner().(type) { + inner := tx.GetInner() + switch innerTx := inner.(type) { case *types.ArbitrumInternalTx: + innerTx.L2BlockNumber = arbmath.BigAddByUint(lastBlockHeader.Number, 1) + innerTx.TxIndex = uint64(len(receipts)) default: hooks = sequencingHooks // the sequencer has the ability to drop this tx isUserTx = true diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 64e4238f14..fc4b531f57 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -5,6 +5,7 @@ package arbos import ( "fmt" + "github.com/offchainlabs/nitro/util/arbmath" "math/big" "github.com/ethereum/go-ethereum/log" @@ -42,9 +43,10 @@ func InternalTxStartBlock( panic(fmt.Sprintf("Failed to pack internal tx %v", err)) } return &types.ArbitrumInternalTx{ - ChainId: chainId, - SubType: arbInternalTxStartBlock, - Data: data, + ChainId: chainId, + L2BlockNumber: arbmath.BigAddByUint(lastHeader.Number, 1), + SubType: arbInternalTxStartBlock, + Data: data, } } diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index ae8492a28e..906585940c 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -176,6 +176,14 @@ func (ps *L1PricingState) SetUnitsSinceUpdate(units uint64) error { return ps.unitsSinceUpdate.Set(units) } +func (ps *L1PricingState) AddToUnitsSinceUpdate(units uint64) error { + oldUnits, err := ps.unitsSinceUpdate.Get() + if err != nil { + return err + } + return ps.unitsSinceUpdate.Set(oldUnits + units) +} + func (ps *L1PricingState) PricePerUnit() (*big.Int, error) { return ps.pricePerUnit.Get() } @@ -371,32 +379,32 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common pricePerUnit, _ := ps.PricePerUnit() numUnits := l1Bytes * params.TxDataNonZeroGasEIP2028 tx.PosterCost = am.BigMulByUint(pricePerUnit, numUnits) + tx.CalldataUnits = numUnits tx.PosterIsReimbursable = true - unitsSinceUpdate, _ := ps.UnitsSinceUpdate() - _ = ps.SetUnitsSinceUpdate(unitsSinceUpdate + numUnits) } const TxFixedCost = 140 // assumed maximum size in bytes of a typical RLP-encoded tx, not including its calldata -func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, bool) { +func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, uint64, bool) { if tx := message.UnderlyingTransaction(); tx != nil { if tx.PosterCost == nil { ps.AddPosterInfo(tx, poster) } - return tx.PosterCost, tx.PosterIsReimbursable + return tx.PosterCost, tx.CalldataUnits, tx.PosterIsReimbursable } byteCount, err := byteCountAfterBrotli0(message.Data()) if err != nil { log.Error("failed to compress tx", "err", err) - return big.NewInt(0), false + return big.NewInt(0), 0, false } // Approximate the l1 fee charged for posting this tx's calldata l1Bytes := byteCount + TxFixedCost pricePerUnit, _ := ps.PricePerUnit() - return am.BigMulByUint(pricePerUnit, l1Bytes*params.TxDataNonZeroGasEIP2028), true + units := l1Bytes * params.TxDataNonZeroGasEIP2028 + return am.BigMulByUint(pricePerUnit, units), units, true } func byteCountAfterBrotli0(input []byte) (uint64, error) { diff --git a/arbos/storage/storage.go b/arbos/storage/storage.go index df9f726a5b..edbe46feb6 100644 --- a/arbos/storage/storage.go +++ b/arbos/storage/storage.go @@ -295,7 +295,8 @@ func (ss *StorageSlot) Get() (common.Hash, error) { func (ss *StorageSlot) Set(value common.Hash) error { if ss.burner.ReadOnly() { log.Error("Read-only burner attempted to mutate state", "value", value) - return vm.ErrWriteProtection + panic("Read-only burner attempted to mutate state") + // return vm.ErrWriteProtection } err := ss.burner.Burn(writeCost(value)) if err != nil { diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index cd5ae602c3..1da4283339 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -254,7 +254,12 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er var gasNeededToStartEVM uint64 gasPrice := p.evm.Context.BaseFee coinbase := p.evm.Context.Coinbase - posterCost, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, coinbase) + posterCost, calldataUnits, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, coinbase) + + _ = calldataUnits + // if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { + // return nil, err + //} if p.msg.RunMode() == types.MessageGasEstimationMode { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. diff --git a/contracts/src/bridge/SequencerInbox.sol b/contracts/src/bridge/SequencerInbox.sol index ddf0a73251..b272bedced 100644 --- a/contracts/src/bridge/SequencerInbox.sol +++ b/contracts/src/bridge/SequencerInbox.sol @@ -155,7 +155,9 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox dataHash, afterDelayedMessagesRead ); - _reportBatchSpending(dataHash, sequenceNumber); + if (data.length > 0) { + _reportBatchSpending(dataHash, sequenceNumber); + } emit SequencerBatchDelivered( inboxAccs.length - 1, beforeAcc, @@ -184,7 +186,9 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox dataHash, afterDelayedMessagesRead ); - _reportBatchSpending(dataHash, sequenceNumber); + if (data.length > 0) { + _reportBatchSpending(dataHash, sequenceNumber); + } emit SequencerBatchDelivered( sequenceNumber, beforeAcc, diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index 7c5843b330..1d57105283 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -134,7 +134,7 @@ func init() { // if gas is free or there's no reimbursable poster, the user won't pay for L1 data costs return } - posterCost, _ := state.L1PricingState().PosterDataCost(msg, poster) + posterCost, _, _ := state.L1PricingState().PosterDataCost(msg, poster) posterCostInL2Gas := arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, header.BaseFee)) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } From a0dc7840e476aa0ed218e66f70c37a643b728658 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 9 Jun 2022 11:49:29 -0500 Subject: [PATCH 33/61] Don't post a batch if the only contents are a batch posting report --- arbnode/batch_poster.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 09f8007611..691087f631 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -7,10 +7,12 @@ import ( "bytes" "context" "fmt" - "github.com/offchainlabs/nitro/util/headerreader" "math/big" "time" + "github.com/offchainlabs/nitro/arbos" + "github.com/offchainlabs/nitro/util/headerreader" + "github.com/andybalholm/brotli" "github.com/pkg/errors" flag "github.com/spf13/pflag" @@ -341,24 +343,30 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context, timeSinceBatc return nil, err } forcePostBatch := timeSinceBatchPosted >= b.config.MaxBatchPostInterval + haveUsefulMessage := false for b.building.msgCount < msgCount { msg, err := b.streamer.GetMessage(b.building.msgCount) if err != nil { log.Error("error getting message from streamer", "error", err) break } + if msg.Message.Header.Kind != arbos.L1MessageType_BatchPostingReport { + haveUsefulMessage = true + } success, err := b.building.segments.AddMessage(&msg) if err != nil { log.Error("error adding message to batch", "error", err) break } if !success { - forcePostBatch = true // this batch is full + // this batch is full + forcePostBatch = true + haveUsefulMessage = true break } b.building.msgCount++ } - if !forcePostBatch { + if !forcePostBatch || !haveUsefulMessage { // the batch isn't full yet and we've posted a batch recently // don't post anything for now return nil, nil From 97d67e3f1c4de3ef5f3e6078ab5e13262bf41375 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 9 Jun 2022 12:36:24 -0500 Subject: [PATCH 34/61] Fix batch posting receipt preimage resolution when preimages disabled --- validator/challenge_manager.go | 2 +- validator/stateless_block_validator.go | 39 ++++++++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/validator/challenge_manager.go b/validator/challenge_manager.go index e585ea38c8..02fa735b2f 100644 --- a/validator/challenge_manager.go +++ b/validator/challenge_manager.go @@ -420,7 +420,7 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in var batchInfo []BatchInfo if tooFar { // Just record the part of block creation before the message is read - _, preimages, readBatchInfo, err := RecordBlockCreation(ctx, m.blockchain, m.inboxReader, blockHeader, nil) + _, preimages, readBatchInfo, err := RecordBlockCreation(ctx, m.blockchain, m.inboxReader, blockHeader, nil, true) if err != nil { return err } diff --git a/validator/stateless_block_validator.go b/validator/stateless_block_validator.go index a18965c032..08a8a59546 100644 --- a/validator/stateless_block_validator.go +++ b/validator/stateless_block_validator.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -222,10 +223,26 @@ type BatchInfo struct { } // If msg is nil, this will record block creation up to the point where message would be accessed (for a "too far" proof) -func RecordBlockCreation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, prevHeader *types.Header, msg *arbstate.MessageWithMetadata) (common.Hash, map[common.Hash][]byte, []BatchInfo, error) { - recordingdb, chaincontext, recordingKV, err := arbitrum.PrepareRecording(blockchain, prevHeader) - if err != nil { - return common.Hash{}, nil, nil, err +func RecordBlockCreation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, prevHeader *types.Header, msg *arbstate.MessageWithMetadata, producePreimages bool) (common.Hash, map[common.Hash][]byte, []BatchInfo, error) { + var recordingdb *state.StateDB + var chaincontext core.ChainContext + var recordingKV *arbitrum.RecordingKV + var err error + if producePreimages { + recordingdb, chaincontext, recordingKV, err = arbitrum.PrepareRecording(blockchain, prevHeader) + if err != nil { + return common.Hash{}, nil, nil, err + } + } else { + var prevRoot common.Hash + if prevHeader != nil { + prevRoot = prevHeader.Root + } + recordingdb, err = blockchain.StateAt(prevRoot) + if err != nil { + return common.Hash{}, nil, nil, err + } + chaincontext = blockchain } chainConfig := blockchain.Config() @@ -275,7 +292,13 @@ func RecordBlockCreation(ctx context.Context, blockchain *core.BlockChain, inbox blockHash = block.Hash() } - preimages, err := arbitrum.PreimagesFromRecording(chaincontext, recordingKV) + var preimages map[common.Hash][]byte + if recordingKV != nil { + preimages, err = arbitrum.PreimagesFromRecording(chaincontext, recordingKV) + if err != nil { + return common.Hash{}, nil, nil, err + } + } return blockHash, preimages, readBatchInfo, err } @@ -289,9 +312,9 @@ func BlockDataForValidation(ctx context.Context, blockchain *core.BlockChain, in return } - if prevHeader != nil && producePreimages { + if prevHeader != nil { var blockhash common.Hash - blockhash, preimages, readBatchInfo, err = RecordBlockCreation(ctx, blockchain, inboxReader, prevHeader, &msg) + blockhash, preimages, readBatchInfo, err = RecordBlockCreation(ctx, blockchain, inboxReader, prevHeader, &msg, producePreimages) if err != nil { return } @@ -319,7 +342,7 @@ func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, pr } for _, batch := range batchInfo { - if arbstate.IsDASMessageHeaderByte(batch.Data[40]) { + if len(batch.Data) >= 41 && arbstate.IsDASMessageHeaderByte(batch.Data[40]) { if das == nil { log.Error("No DAS configured, but sequencer message found with DAS header") if bc.Config().ArbitrumChainParams.DataAvailabilityCommittee { From 79b672dfa1bad16e778666ccdab8618461969019 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 9 Jun 2022 13:08:33 -0500 Subject: [PATCH 35/61] Wait for only the last useful block in block validator test --- system_tests/block_validator_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 1ea14ae286..b535f58d3a 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -92,11 +92,25 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, expensiveTx bo Fail(t, "Unexpected balance:", l2balance) } - lastBlockHeader, err := l2clientB.HeaderByNumber(ctx, nil) + lastBlock, err := l2clientB.BlockByNumber(ctx, nil) Require(t, err) + for { + usefulBlock := false + for _, tx := range lastBlock.Transactions() { + if tx.Type() != types.ArbitrumInternalTxType { + usefulBlock = true + break + } + } + if usefulBlock { + break + } + lastBlock, err = l2clientB.BlockByHash(ctx, lastBlock.ParentHash()) + Require(t, err) + } testDeadLine, _ := t.Deadline() nodeA.StopAndWait() - if !nodeB.BlockValidator.WaitForBlock(lastBlockHeader.Number.Uint64(), time.Until(testDeadLine)-time.Second*10) { + if !nodeB.BlockValidator.WaitForBlock(lastBlock.NumberU64(), time.Until(testDeadLine)-time.Second*10) { Fail(t, "did not validate all blocks") } nodeB.StopAndWait() From 0975aefbf726320f20384958b3a13e31a9251ecb Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Thu, 9 Jun 2022 13:23:54 -0500 Subject: [PATCH 36/61] use 4 byte method selectors instead of internaltx subtypes --- arbos/block_processor.go | 2 ++ arbos/incomingmessage.go | 1 - arbos/internal_tx.go | 17 +++++------------ arbos/util/util.go | 5 ++++- go-ethereum | 2 +- precompiles/precompile.go | 34 ++++++++++++++++++++++++++-------- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 8d68be3fad..2db29fcfaf 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -31,6 +31,8 @@ import ( // set by the precompile module, to avoid a package dependence cycle var ArbRetryableTxAddress common.Address var ArbSysAddress common.Address +var InternalTxStartBlockMethodID [4]byte +var InternalTxBatchPostingReportMethodID [4]byte var RedeemScheduledEventID common.Hash var L2ToL1TransactionEventID common.Hash var L2ToL1TxEventID common.Hash diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index ad627f9938..b2ba70bf66 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -540,7 +540,6 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, batchFetcher } return types.NewTx(&types.ArbitrumInternalTx{ ChainId: chainId, - SubType: arbInternalTxBatchPostReport, Data: data, // don't need to fill in the other fields, since they exist only to ensure uniqueness, and batchNum is already unique }), nil diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index fc4b531f57..8123b50613 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -5,9 +5,10 @@ package arbos import ( "fmt" - "github.com/offchainlabs/nitro/util/arbmath" "math/big" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/common" @@ -17,13 +18,6 @@ import ( "github.com/offchainlabs/nitro/arbos/util" ) -// Types of ArbitrumInternalTx, distinguished by the first data byte -const ( - // Contains 8 bytes indicating the big endian L1 block number to set - arbInternalTxStartBlock uint8 = 0 - arbInternalTxBatchPostReport uint8 = 1 -) - func InternalTxStartBlock( chainId, l1BaseFee *big.Int, @@ -45,14 +39,13 @@ func InternalTxStartBlock( return &types.ArbitrumInternalTx{ ChainId: chainId, L2BlockNumber: arbmath.BigAddByUint(lastHeader.Number, 1), - SubType: arbInternalTxStartBlock, Data: data, } } func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM) { - switch tx.SubType { - case arbInternalTxStartBlock: + switch *(*[4]byte)(tx.Data[:4]) { + case InternalTxStartBlockMethodID: inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) if err != nil { panic(err) @@ -92,7 +85,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos state.L1PricingState().UpdateTime(currentTime) state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) - case arbInternalTxBatchPostReport: + case InternalTxBatchPostingReportMethodID: inputs, err := util.UnpackInternalTxDataBatchPostingReport(tx.Data) if err != nil { panic(err) diff --git a/arbos/util/util.go b/arbos/util/util.go index 75b48b5d8c..a1e22f19f1 100644 --- a/arbos/util/util.go +++ b/arbos/util/util.go @@ -15,10 +15,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util/arbmath" ) var AddressAliasOffset *big.Int var InverseAddressAliasOffset *big.Int +var InternalTxStartBlockMethodID [4]byte +var InternalTxBatchPostReportMethodID [4]byte var ParseRedeemScheduledLog func(interface{}, *types.Log) error var ParseL2ToL1TransactionLog func(interface{}, *types.Log) error var ParseL2ToL1TxLog func(interface{}, *types.Log) error @@ -34,7 +37,7 @@ func init() { panic("Error initializing AddressAliasOffset") } AddressAliasOffset = offset - InverseAddressAliasOffset = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), AddressAliasOffset) + InverseAddressAliasOffset = arbmath.BigSub(new(big.Int).Lsh(big.NewInt(1), 160), AddressAliasOffset) // Create a mechanism for parsing event logs logParser := func(source string, name string) func(interface{}, *types.Log) error { diff --git a/go-ethereum b/go-ethereum index 471db8b266..1809c5ce04 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 471db8b2667a7d2c5f7517c13d52237db93b0d48 +Subproject commit 1809c5ce04b420ea707381e49dc71d85ef4b634f diff --git a/precompiles/precompile.go b/precompiles/precompile.go index fdb0e61e56..6dbc970f68 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -58,12 +58,14 @@ const ( ) type Precompile struct { - methods map[[4]byte]PrecompileMethod - events map[string]PrecompileEvent - errors map[string]PrecompileError - implementer reflect.Value - address common.Address - arbosVersion uint64 + methods map[[4]byte]PrecompileMethod + methodsByName map[string]PrecompileMethod + events map[string]PrecompileEvent + errors map[string]PrecompileError + name string + implementer reflect.Value + address common.Address + arbosVersion uint64 } type PrecompileMethod struct { @@ -154,6 +156,7 @@ func MakePrecompile(metadata *bind.MetaData, implementer interface{}) (addr, Pre } methods := make(map[[4]byte]PrecompileMethod) + methodsByName := make(map[string]PrecompileMethod) events := make(map[string]PrecompileEvent) errors := make(map[string]PrecompileError) @@ -218,13 +221,15 @@ func MakePrecompile(metadata *bind.MetaData, implementer interface{}) (addr, Pre ) } - methods[id] = PrecompileMethod{ + method := PrecompileMethod{ name, method, purity, handler, 0, } + methods[id] = method + methodsByName[name] = method } // provide the implementer mechanisms to emit logs for the solidity events @@ -487,8 +492,10 @@ func MakePrecompile(metadata *bind.MetaData, implementer interface{}) (addr, Pre return address, Precompile{ methods, + methodsByName, events, errors, + contract, reflect.ValueOf(implementer), address, 0, @@ -518,7 +525,6 @@ func Precompiles() map[addr]ArbosPrecompile { insert(MakePrecompile(templates.ArbGasInfoMetaData, &ArbGasInfo{Address: hex("6c")})) insert(MakePrecompile(templates.ArbAggregatorMetaData, &ArbAggregator{Address: hex("6d")})) insert(MakePrecompile(templates.ArbStatisticsMetaData, &ArbStatistics{Address: hex("6f")})) - insert(MakePrecompile(templates.ArbosActsMetaData, &ArbosActs{Address: types.ArbosAddress})) eventCtx := func(gasLimit uint64, err error) *Context { if err != nil { @@ -559,6 +565,10 @@ func Precompiles() map[addr]ArbosPrecompile { insert(ownerOnly(ArbOwnerImpl.Address, ArbOwner, emitOwnerActs)) insert(debugOnly(MakePrecompile(templates.ArbDebugMetaData, &ArbDebug{Address: hex("ff")}))) + ArbosActs := insert(MakePrecompile(templates.ArbosActsMetaData, &ArbosActs{Address: types.ArbosAddress})) + arbos.InternalTxStartBlockMethodID = ArbosActs.GetMethodID("StartBlock") + arbos.InternalTxBatchPostingReportMethodID = ArbosActs.GetMethodID("BatchPostingReport") + return contracts } @@ -567,6 +577,14 @@ func (p Precompile) SwapImpl(impl interface{}) Precompile { return p } +func (p Precompile) GetMethodID(name string) bytes4 { + method, ok := p.methodsByName[name] + if !ok { + panic(fmt.Sprintf("Precompile %v does not have a method with the name %v", p.name, name)) + } + return *(*bytes4)(method.template.ID) +} + // call a precompile in typed form, deserializing its inputs and serializing its outputs func (p Precompile) Call( input []byte, From 0de60c12a1b966481f9c6fb2973de0e4fc93523a Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Thu, 9 Jun 2022 13:35:08 -0500 Subject: [PATCH 37/61] misc math tweak --- arbos/internal_tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 8123b50613..ce0b4314ca 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -96,7 +96,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos batchDataGas, _ := inputs[3].(uint64) l1BaseFeeWei, _ := inputs[4].(*big.Int) - weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(batchDataGas)) + weiSpent := arbmath.BigMulByUint(l1BaseFeeWei, batchDataGas) err = state.L1PricingState().UpdateForBatchPosterSpending(evm.StateDB, evm, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), batchPosterAddress, weiSpent) if err != nil { log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) From 48b70be639c1ab2449802060036830025940b8b6 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 9 Jun 2022 14:47:36 -0400 Subject: [PATCH 38/61] Restore erroneously commented-out code --- arbos/tx_processor.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 1da4283339..2c66faab67 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -256,10 +256,9 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er coinbase := p.evm.Context.Coinbase posterCost, calldataUnits, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, coinbase) - _ = calldataUnits - // if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - // return nil, err - //} + if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { + return nil, err + } if p.msg.RunMode() == types.MessageGasEstimationMode { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. From 9e485bdda715dc56f2e90a771cecc4032df447cb Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Fri, 10 Jun 2022 05:17:40 -0500 Subject: [PATCH 39/61] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 1809c5ce04..f7a56f4a62 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 1809c5ce04b420ea707381e49dc71d85ef4b634f +Subproject commit f7a56f4a628be29584293353b927a65b7fc8bbfb From c1866dbf0e24b1b57c0faeb9222b750913b04c14 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Fri, 10 Jun 2022 07:33:20 -0500 Subject: [PATCH 40/61] address review comments --- arbos/util/util.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/arbos/util/util.go b/arbos/util/util.go index a1e22f19f1..ca97fa0c6b 100644 --- a/arbos/util/util.go +++ b/arbos/util/util.go @@ -20,8 +20,6 @@ import ( var AddressAliasOffset *big.Int var InverseAddressAliasOffset *big.Int -var InternalTxStartBlockMethodID [4]byte -var InternalTxBatchPostReportMethodID [4]byte var ParseRedeemScheduledLog func(interface{}, *types.Log) error var ParseL2ToL1TransactionLog func(interface{}, *types.Log) error var ParseL2ToL1TxLog func(interface{}, *types.Log) error From ffb27e7a7158d4c99e35cd7cdbf43293074c9e25 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Fri, 10 Jun 2022 09:44:32 -0500 Subject: [PATCH 41/61] fix tests --- arbos/block_processor.go | 7 +------ arbos/internal_tx.go | 5 ++--- go-ethereum | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 2db29fcfaf..b9dad5ec82 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -208,12 +208,7 @@ func ProduceBlockAdvanced( } else { tx = txes[0] txes = txes[1:] - inner := tx.GetInner() - switch innerTx := inner.(type) { - case *types.ArbitrumInternalTx: - innerTx.L2BlockNumber = arbmath.BigAddByUint(lastBlockHeader.Number, 1) - innerTx.TxIndex = uint64(len(receipts)) - default: + if tx.Type() == types.ArbitrumInternalTxType { hooks = sequencingHooks // the sequencer has the ability to drop this tx isUserTx = true } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index ce0b4314ca..7445e761cc 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -37,9 +37,8 @@ func InternalTxStartBlock( panic(fmt.Sprintf("Failed to pack internal tx %v", err)) } return &types.ArbitrumInternalTx{ - ChainId: chainId, - L2BlockNumber: arbmath.BigAddByUint(lastHeader.Number, 1), - Data: data, + ChainId: chainId, + Data: data, } } diff --git a/go-ethereum b/go-ethereum index f7a56f4a62..8096aa9f82 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit f7a56f4a628be29584293353b927a65b7fc8bbfb +Subproject commit 8096aa9f821207e93cf9994858cadeb767e6dc46 From 36cfb203c96af947cb52704e37a13fb254a37144 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Sun, 12 Jun 2022 19:38:16 -0500 Subject: [PATCH 42/61] Improve test and fix bug --- arbos/l1pricing/l1pricing.go | 32 ++++++----- arbos/l1pricing_test.go | 100 ++++++++++++++++++++++++++--------- 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 906585940c..df738cee24 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -204,14 +204,6 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - dueToPoster, err := posterState.FundsDue() - if err != nil { - return err - } - err = posterState.SetFundsDue(am.BigAdd(dueToPoster, weiSpent)) - if err != nil { - return err - } // compute previous shortfall totalFundsDue, err := batchPosterTable.TotalFundsDue() @@ -249,19 +241,31 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * return err } + dueToPoster, err := posterState.FundsDue() + if err != nil { + return err + } + err = posterState.SetFundsDue(am.BigAdd(dueToPoster, weiSpent)) + if err != nil { + return err + } + perUnitReward, err := ps.PerUnitReward() + if err != nil { + return err + } + fundsDueForRewards = am.BigAdd(fundsDueForRewards, am.BigMulByUint(am.UintToBig(unitsAllocated), perUnitReward)) + if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { + return err + } + // allocate funds to this update collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) availableFunds := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, updateTimeDelta), timeDelta) // pay rewards, as much as possible - perUnitReward, err := ps.PerUnitReward() - if err != nil { - return err - } paymentForRewards := am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) if am.BigLessThan(availableFunds, paymentForRewards) { - unitsAllocated = am.SaturatingCastToUint(am.BigDivByUint(availableFunds, perUnitReward)) - paymentForRewards = am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) + paymentForRewards = availableFunds } if err := ps.SetFundsDueForRewards(am.BigSub(fundsDueForRewards, paymentForRewards)); err != nil { return err diff --git a/arbos/l1pricing_test.go b/arbos/l1pricing_test.go index 442d04d0e9..bd6b7b5e45 100644 --- a/arbos/l1pricing_test.go +++ b/arbos/l1pricing_test.go @@ -16,21 +16,79 @@ import ( "github.com/offchainlabs/nitro/arbos/burn" ) -const ( - unitReward_test = 10 - unitsPerSecond_test = 78 - fundsCollectedPerSecond_test = 7800 - fundsSpent_test = 3000 -) +type l1PricingTest struct { + unitReward uint64 + unitsPerSecond uint64 + fundsCollectedPerSecond uint64 + fundsSpent uint64 +} + +type l1TestExpectedResults struct { + rewardRecipientBalance *big.Int + unitsRemaining uint64 + fundsReceived *big.Int + fundsStillHeld *big.Int +} + +func TestL1Pricing(t *testing.T) { + inputs := []*l1PricingTest{ + { + unitReward: 10, + unitsPerSecond: 78, + fundsCollectedPerSecond: 7800, + fundsSpent: 3000, + }, + { + unitReward: 10, + unitsPerSecond: 78, + fundsCollectedPerSecond: 1313, + fundsSpent: 3000, + }, + { + unitReward: 10, + unitsPerSecond: 78, + fundsCollectedPerSecond: 31, + fundsSpent: 3000, + }, + } + for _, input := range inputs { + expectedResult := expectedResultsForL1Test(input) + _testL1PricingFundsDue(t, input, expectedResult) + } +} -func TestL1PricingFundsDue(t *testing.T) { +func expectedResultsForL1Test(input *l1PricingTest) *l1TestExpectedResults { + ret := &l1TestExpectedResults{} + availableFunds := arbmath.UintToBig(input.fundsCollectedPerSecond) + fundsWantedForRewards := big.NewInt(int64(input.unitReward * input.unitsPerSecond)) + unitsAllocated := arbmath.UintToBig(input.unitsPerSecond) + if arbmath.BigLessThan(availableFunds, fundsWantedForRewards) { + ret.rewardRecipientBalance = availableFunds + } else { + ret.rewardRecipientBalance = fundsWantedForRewards + } + availableFunds = arbmath.BigSub(availableFunds, ret.rewardRecipientBalance) + ret.unitsRemaining = (3 * input.unitsPerSecond) - unitsAllocated.Uint64() + + maxCollectable := big.NewInt(int64(input.fundsSpent)) + if arbmath.BigLessThan(availableFunds, maxCollectable) { + maxCollectable = availableFunds + } + ret.fundsReceived = maxCollectable + availableFunds = arbmath.BigSub(availableFunds, maxCollectable) + ret.fundsStillHeld = arbmath.BigAdd(arbmath.UintToBig(2*input.fundsCollectedPerSecond), availableFunds) + + return ret +} + +func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedResults *l1TestExpectedResults) { evm := newMockEVMForTesting() burner := burn.NewSystemBurner(nil, false) arbosSt, err := arbosState.OpenArbosState(evm.StateDB, burner) Require(t, err) l1p := arbosSt.L1PricingState() - err = l1p.SetPerUnitReward(unitReward_test) + err = l1p.SetPerUnitReward(testParams.unitReward) Require(t, err) rewardAddress := common.Address{137} err = l1p.SetPayRewardsTo(rewardAddress) @@ -71,36 +129,30 @@ func TestL1PricingFundsDue(t *testing.T) { Require(t, err) // create some fake collection - balanceAdded := big.NewInt(fundsCollectedPerSecond_test * 3) - unitsAdded := uint64(unitsPerSecond_test * 3) + balanceAdded := big.NewInt(int64(testParams.fundsCollectedPerSecond * 3)) + unitsAdded := uint64(testParams.unitsPerSecond * 3) evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, balanceAdded) err = l1p.SetUnitsSinceUpdate(unitsAdded) Require(t, err) // submit a fake spending update, then check that balances are correct - err = l1p.UpdateForBatchPosterSpending(evm.StateDB, evm, 1, 3, firstPoster, big.NewInt(fundsSpent_test)) + err = l1p.UpdateForBatchPosterSpending(evm.StateDB, evm, 1, 3, firstPoster, new(big.Int).SetUint64(testParams.fundsSpent)) Require(t, err) rewardRecipientBalance := evm.StateDB.GetBalance(rewardAddress) - expectedUnitsCollected := unitsPerSecond_test - if !arbmath.BigEquals(rewardRecipientBalance, big.NewInt(int64(unitReward_test*expectedUnitsCollected))) { - t.Fatal(rewardRecipientBalance, unitReward_test*expectedUnitsCollected) + if !arbmath.BigEquals(rewardRecipientBalance, expectedResults.rewardRecipientBalance) { + t.Fatal(rewardRecipientBalance, expectedResults.rewardRecipientBalance) } unitsRemaining, err := l1p.UnitsSinceUpdate() Require(t, err) - if unitsRemaining != (3*unitsPerSecond_test)-uint64(expectedUnitsCollected) { - t.Fatal(unitsRemaining, (3*unitsPerSecond_test)-uint64(expectedUnitsCollected)) - } - remainingFunds := arbmath.BigSub(balanceAdded, rewardRecipientBalance) - maxCollectable := big.NewInt(fundsSpent_test) - if arbmath.BigLessThan(remainingFunds, maxCollectable) { - maxCollectable = remainingFunds + if unitsRemaining != expectedResults.unitsRemaining { + t.Fatal(unitsRemaining, expectedResults.unitsRemaining) } fundsReceived := evm.StateDB.GetBalance(firstPayTo) - if !arbmath.BigEquals(fundsReceived, maxCollectable) { - t.Fatal(fundsReceived, maxCollectable) + if !arbmath.BigEquals(fundsReceived, expectedResults.fundsReceived) { + t.Fatal(fundsReceived, expectedResults.fundsReceived) } fundsStillHeld := evm.StateDB.GetBalance(l1pricing.L1PricerFundsPoolAddress) - if !arbmath.BigEquals(fundsStillHeld, arbmath.BigSub(remainingFunds, maxCollectable)) { + if !arbmath.BigEquals(fundsStillHeld, expectedResults.fundsStillHeld) { t.Fatal() } } From 93a29e11ae3beeb7a34b1d310144044118494a15 Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Mon, 13 Jun 2022 11:57:03 -0500 Subject: [PATCH 43/61] end-to-end test & TestSequencerFeePaid + fix --- arbos/incomingmessage.go | 4 +- system_tests/fees_test.go | 123 ++++++++++++++++++++++++++++++++++---- util/arbmath/math.go | 5 ++ 3 files changed, 118 insertions(+), 14 deletions(-) diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index b2ba70bf66..f7ff779e8d 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -534,7 +534,9 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, batchFetcher batchDataGas += params.TxDataNonZeroGasEIP2028 } } - data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, batchDataGas, l1BaseFee.Big()) + data, err := util.PackInternalTxDataBatchPostingReport( + batchTimestamp.Big(), batchPosterAddr, batchNum, batchDataGas, l1BaseFee.Big(), + ) if err != nil { return nil, err } diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 051f6c4965..cd1542941e 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -7,14 +7,19 @@ import ( "context" "math/big" "testing" + "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/colors" ) func TestSequencerFeePaid(t *testing.T) { @@ -33,36 +38,128 @@ func TestSequencerFeePaid(t *testing.T) { networkFeeAccount, err := arbOwnerPublic.GetNetworkFeeAccount(callOpts) Require(t, err, "could not get the network fee account") - l1Estimate, err := arbGasInfo.GetL1GasPriceEstimate(callOpts) + l1Estimate, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts) Require(t, err) networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount) - seqBefore := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) l2info.GasPrice = GetBaseFee(t, l2client, ctx) tx, receipt := TransferBalance(t, "Faucet", "Faucet", big.NewInt(0), l2info, l2client, ctx) + txSize := compressedTxSize(t, tx) networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount) - seqAfter := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) + l1Charge := arbmath.BigMulByUint(l2info.GasPrice, receipt.GasUsedForL1) networkRevenue := arbmath.BigSub(networkAfter, networkBefore) - seqRevenue := arbmath.BigSub(seqAfter, seqBefore) - gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1 - if !arbmath.BigEquals(networkRevenue, arbmath.BigMulByUint(tx.GasPrice(), gasUsedForL2)) { Fail(t, "network didn't receive expected payment") } - paidBytes := arbmath.BigDiv(seqRevenue, l1Estimate).Uint64() / params.TxDataNonZeroGasEIP2028 + l1GasBought := arbmath.BigDiv(l1Charge, l1Estimate).Uint64() + l1GasActual := txSize * params.TxDataNonZeroGasEIP2028 + + colors.PrintBlue("bytes ", l1GasBought/params.TxDataNonZeroGasEIP2028, txSize) + + if l1GasBought != l1GasActual { + Fail(t, "the sequencer's future revenue does not match its costs", l1GasBought, l1GasActual) + } +} + +func TestSequencerPriceAdjusts(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + chainConfig := params.ArbitrumDevTestChainConfig() + conf := arbnode.ConfigDefaultL1Test() + conf.DelayedSequencer.FinalizeDistance = 1 + + l2info, node, l2client, _, _, l1client, stack := CreateTestNodeOnL1WithConfig(t, ctx, true, conf, chainConfig) + defer stack.Close() + + arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), l2client) + Require(t, err) + lastEstimate, err := arbGasInfo.GetL1BaseFeeEstimate(&bind.CallOpts{Context: ctx}) + Require(t, err) + lastBatchCount, err := node.InboxTracker.GetBatchCount() + Require(t, err) + l1Header, err := l1client.HeaderByNumber(ctx, nil) + Require(t, err) + + sequencerBalanceBefore := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) + timesPriceAdjusted := 0 + + colors.PrintBlue("Initial values") + colors.PrintBlue(" L1 base fee ", l1Header.BaseFee) + colors.PrintBlue(" L1 estimate ", lastEstimate) + + for i := 0; i < 128; i++ { + tx, receipt := TransferBalance(t, "Owner", "Owner", common.Big0, l2info, l2client, ctx) + header, err := l2client.HeaderByHash(ctx, receipt.BlockHash) + Require(t, err) + + units := compressedTxSize(t, tx) * params.TxDataNonZeroGasEIP2028 + currEstimate := arbmath.BigDivByUint(arbmath.BigMulByUint(header.BaseFee, receipt.GasUsedForL1), units) + + if !arbmath.BigEquals(lastEstimate, currEstimate) { + l1Header, err = l1client.HeaderByNumber(ctx, nil) + Require(t, err) + + callOpts := &bind.CallOpts{Context: ctx, BlockNumber: receipt.BlockNumber} + trueEstimate, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts) + Require(t, err) + + colors.PrintGrey("ArbOS updated its L1 estimate") + colors.PrintGrey(" L1 base fee ", l1Header.BaseFee) + colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", currEstimate, " = ", trueEstimate) + + oldDiff := arbmath.BigAbs(arbmath.BigSub(lastEstimate, l1Header.BaseFee)) + newDiff := arbmath.BigAbs(arbmath.BigSub(currEstimate, l1Header.BaseFee)) + + if arbmath.BigGreaterThan(newDiff, oldDiff) { + Fail(t, "L1 gas price estimate should tend toward the basefee") + } + if !arbmath.BigEquals(trueEstimate, currEstimate) { + Fail(t, "New L1 estimate does not match receipt") + } + lastEstimate = trueEstimate + timesPriceAdjusted++ + } + + if i%16 == 0 { + // see that the inbox advances + + for j := 16; j > 0; j-- { + newBatchCount, err := node.InboxTracker.GetBatchCount() + Require(t, err) + if newBatchCount > lastBatchCount { + colors.PrintGrey("posted new batch ", newBatchCount) + lastBatchCount = newBatchCount + break + } + if j == 1 { + Fail(t, "batch count didn't update in time") + } + time.Sleep(time.Millisecond * 100) + } + } + } + + sequencerBalanceAfter := GetBalance(t, ctx, l2client, l1pricing.BatchPosterAddress) + colors.PrintMint("sequencer balance ", sequencerBalanceBefore, " ➤ ", sequencerBalanceAfter) + colors.PrintMint("price changes ", timesPriceAdjusted) + + if timesPriceAdjusted == 0 { + Fail(t, "L1 gas price estimate never adjusted") + } + if !arbmath.BigGreaterThan(sequencerBalanceAfter, sequencerBalanceBefore) { + Fail(t, "sequencer didn't get paid") + } +} +func compressedTxSize(t *testing.T, tx *types.Transaction) uint64 { txBin, err := tx.MarshalBinary() Require(t, err) compressed, err := arbcompress.CompressFast(txBin) Require(t, err) - - _ = paidBytes - _ = compressed - // if uint64(len(compressed)) != paidBytes { - // t.Fatal("unexpected number of bytes paid for") - //} + return uint64(len(compressed)) } diff --git a/util/arbmath/math.go b/util/arbmath/math.go index 336c334a31..e54c3aa64f 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -118,6 +118,11 @@ func BigDiv(dividend *big.Int, divisor *big.Int) *big.Int { return new(big.Int).Div(dividend, divisor) } +// absolute value of a huge +func BigAbs(value *big.Int) *big.Int { + return new(big.Int).Abs(value) +} + // add a uint to a huge func BigAddByUint(augend *big.Int, addend uint64) *big.Int { return new(big.Int).Add(augend, UintToBig(addend)) From ebbd284ca62dc6490698ba5c5beb16c123113fcf Mon Sep 17 00:00:00 2001 From: Rachel Franks Date: Mon, 13 Jun 2022 13:50:56 -0500 Subject: [PATCH 44/61] automatically add posters & fix SetPricePerUnit bug --- arbos/l1pricing/batchPoster.go | 6 +++--- arbos/l1pricing/l1pricing.go | 2 +- system_tests/fees_test.go | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index 7dbe50bf5c..d5af8f58e2 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -5,18 +5,18 @@ package l1pricing import ( "errors" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbos/addressSet" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/util/arbmath" - "math/big" ) var ( PosterAddrsKey = []byte{0} PosterInfoKey = []byte{1} - ErrNotExist = errors.New("batch poster does not exist in table") ErrAlreadyExists = errors.New("tried to add a batch poster that already exists") ) @@ -49,7 +49,7 @@ func (bpt *BatchPostersTable) OpenPoster(poster common.Address) (*BatchPosterSta return nil, err } if !isBatchPoster { - return nil, ErrNotExist + return bpt.AddPoster(poster, poster) } return bpt.internalOpen(poster), nil } diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index df738cee24..f69e929874 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -352,7 +352,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * } else { price = big.NewInt(0) } - if err := ps.SetPricePerUnit(price); err != nil { + if err := ps.SetPricePerUnit(newPrice); err != nil { return err } } diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index cd1542941e..2e8b5cde6a 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -93,7 +93,7 @@ func TestSequencerPriceAdjusts(t *testing.T) { colors.PrintBlue(" L1 estimate ", lastEstimate) for i := 0; i < 128; i++ { - tx, receipt := TransferBalance(t, "Owner", "Owner", common.Big0, l2info, l2client, ctx) + tx, receipt := TransferBalance(t, "Owner", "Owner", common.Big1, l2info, l2client, ctx) header, err := l2client.HeaderByHash(ctx, receipt.BlockHash) Require(t, err) @@ -121,6 +121,9 @@ func TestSequencerPriceAdjusts(t *testing.T) { if !arbmath.BigEquals(trueEstimate, currEstimate) { Fail(t, "New L1 estimate does not match receipt") } + if arbmath.BigEquals(trueEstimate, common.Big0) { + Fail(t, "Estimate is zero", i) + } lastEstimate = trueEstimate timesPriceAdjusted++ } From 874ec66353d01fcdcf3692b226813fc3994d27d3 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Tue, 14 Jun 2022 07:51:06 -0400 Subject: [PATCH 45/61] Correct regression in this branch --- arbos/l1pricing/l1pricing.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index f69e929874..fc3658dfac 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -347,10 +347,8 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * ) newPrice := am.BigSub(price, priceChange) - if newPrice.Sign() >= 0 { - price = newPrice - } else { - price = big.NewInt(0) + if newPrice.Sign() < 0 { + newPrice = big.NewInt(0) } if err := ps.SetPricePerUnit(newPrice); err != nil { return err From d1f9d12c4f1c34d64e1f8f27f65298a2d73358ab Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Tue, 14 Jun 2022 18:06:04 -0400 Subject: [PATCH 46/61] Debugging and fixes WIP --- arbos/addressSet/addressSet.go | 14 ++-- arbos/incomingmessage.go | 6 +- arbos/internal_tx.go | 1 - arbos/l1pricing/batchPoster.go | 4 +- arbos/l1pricing/l1pricing.go | 98 +++++++++++++------------- arbos/l1pricing/l1pricing_test.go | 3 +- contracts/src/precompiles/ArbOwner.sol | 4 +- precompiles/ArbGasInfo.go | 6 +- precompiles/ArbOwner.go | 4 +- system_tests/fees_test.go | 2 +- 10 files changed, 76 insertions(+), 66 deletions(-) diff --git a/arbos/addressSet/addressSet.go b/arbos/addressSet/addressSet.go index d47f08fc5e..b6894d48ed 100644 --- a/arbos/addressSet/addressSet.go +++ b/arbos/addressSet/addressSet.go @@ -44,9 +44,9 @@ func (aset *AddressSet) GetAnyMember() (*common.Address, error) { if err != nil || size == 0 { return nil, err } - addrAsHash, err := aset.backingStorage.GetByUint64(1) - addr := common.BytesToAddress(addrAsHash.Bytes()) - return &addr, err + sba := aset.backingStorage.OpenStorageBackedAddressOrNil(1) + addr, err := sba.Get() + return addr, err } func (aset *AddressSet) Clear() error { @@ -72,11 +72,11 @@ func (aset *AddressSet) AllMembers() ([]common.Address, error) { } ret := make([]common.Address, size) for i := range ret { - bytes, err := aset.backingStorage.GetByUint64(uint64(i + 1)) + sba := aset.backingStorage.OpenStorageBackedAddress(uint64(i + 1)) + ret[i], err = sba.Get() if err != nil { return nil, err } - ret[i] = common.BytesToAddress(bytes.Bytes()) } return ret, nil } @@ -90,13 +90,15 @@ func (aset *AddressSet) Add(addr common.Address) error { if err != nil { return err } + sba := aset.backingStorage.OpenStorageBackedAddress(1 + size) slot := util.UintToHash(1 + size) addrAsHash := common.BytesToHash(addr.Bytes()) err = aset.byAddress.Set(addrAsHash, slot) if err != nil { return err } - err = aset.backingStorage.Set(slot, addrAsHash) + sba = aset.backingStorage.OpenStorageBackedAddress(1 + size) + err = sba.Set(addr) if err != nil { return err } diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index f7ff779e8d..0edd5035ef 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -511,7 +511,11 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, batchFetcher if err != nil { return nil, err } - batchPosterAddr, err := util.AddressFrom256FromReader(rd) + batchPosterAddr, err := util.AddressFromReader(rd) + if err != nil { + return nil, err + } + _, err = util.HashFromReader(rd) // unused: data hash if err != nil { return nil, err } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 7445e761cc..3cb340c0e3 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -81,7 +81,6 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, state.FormatVersion(), false) - state.L1PricingState().UpdateTime(currentTime) state.UpgradeArbosVersionIfNecessary(currentTime, evm.ChainConfig()) case InternalTxBatchPostingReportMethodID: diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index d5af8f58e2..73b6a16b29 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -75,7 +75,7 @@ func (bpt *BatchPostersTable) AddPoster(posterAddress common.Address, payTo comm return nil, ErrAlreadyExists } bpState := bpt.internalOpen(posterAddress) - if err := bpState.fundsDue.Set(big.NewInt(0)); err != nil { + if err := bpState.fundsDue.Set(common.Big0); err != nil { return nil, err } if err := bpState.payTo.Set(payTo); err != nil { @@ -98,7 +98,7 @@ func (bpt *BatchPostersTable) TotalFundsDue() (*big.Int, error) { if err != nil { return nil, err } - ret := big.NewInt(0) + ret := common.Big0 for _, posterAddr := range allPosters { poster, err := bpt.OpenPoster(posterAddr) if err != nil { diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index fc3658dfac..733a9aeb53 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -25,13 +25,12 @@ type L1PricingState struct { storage *storage.Storage // parameters - batchPosterTable *BatchPostersTable - payRewardsTo storage.StorageBackedAddress - equilibrationTime storage.StorageBackedUint64 - inertia storage.StorageBackedUint64 - perUnitReward storage.StorageBackedUint64 + batchPosterTable *BatchPostersTable + payRewardsTo storage.StorageBackedAddress + equilibrationUnits storage.StorageBackedBigInt + inertia storage.StorageBackedUint64 + perUnitReward storage.StorageBackedUint64 // variables - currentTime storage.StorageBackedUint64 lastUpdateTime storage.StorageBackedUint64 // timestamp of the last update from L1 that we processed fundsDueForRewards storage.StorageBackedBigInt // funds collected since update are recorded as the balance in account L1PricerFundsPoolAddress @@ -50,21 +49,20 @@ var ( const ( payRewardsToOffset uint64 = iota - equilibrationTimeOffset + equilibrationUnitsOffset inertiaOffset perUnitRewardOffset - currentTimeOffset lastUpdateTimeOffset - fundsDueForRewards + fundsDueForRewardsOffset unitsSinceOffset pricePerUnitOffset ) const ( - InitialEquilibrationTime = 60 * 60 // one hour - InitialInertia = 10 - InitialPerUnitReward = 10 - InitialPricePerUnitWei = 50 * params.GWei + InitialEquilibrationUnits uint64 = 60 * params.TxDataNonZeroGasEIP2028 * 100000 // one minute at 100000 bytes / sec + InitialInertia = 10 + InitialPerUnitReward = 10 + InitialPricePerUnitWei = 50 * params.GWei ) func InitializeL1PricingState(sto *storage.Storage) error { @@ -79,12 +77,17 @@ func InitializeL1PricingState(sto *storage.Storage) error { if err := sto.SetByUint64(payRewardsToOffset, util.AddressToHash(BatchPosterAddress)); err != nil { return err } - if err := sto.SetUint64ByUint64(equilibrationTimeOffset, InitialEquilibrationTime); err != nil { + equilibrationUnits := sto.OpenStorageBackedBigInt(equilibrationUnitsOffset) + if err := equilibrationUnits.Set(am.UintToBig(InitialEquilibrationUnits)); err != nil { return err } if err := sto.SetUint64ByUint64(inertiaOffset, InitialInertia); err != nil { return err } + fundsDueForRewards := sto.OpenStorageBackedBigInt(fundsDueForRewardsOffset) + if err := fundsDueForRewards.Set(common.Big0); err != nil { + return err + } if err := sto.SetUint64ByUint64(perUnitRewardOffset, InitialPerUnitReward); err != nil { return err } @@ -97,12 +100,11 @@ func OpenL1PricingState(sto *storage.Storage) *L1PricingState { sto, OpenBatchPostersTable(sto.OpenSubStorage(BatchPosterTableKey)), sto.OpenStorageBackedAddress(payRewardsToOffset), - sto.OpenStorageBackedUint64(equilibrationTimeOffset), + sto.OpenStorageBackedBigInt(equilibrationUnitsOffset), sto.OpenStorageBackedUint64(inertiaOffset), sto.OpenStorageBackedUint64(perUnitRewardOffset), - sto.OpenStorageBackedUint64(currentTimeOffset), sto.OpenStorageBackedUint64(lastUpdateTimeOffset), - sto.OpenStorageBackedBigInt(fundsDueForRewards), + sto.OpenStorageBackedBigInt(fundsDueForRewardsOffset), sto.OpenStorageBackedUint64(unitsSinceOffset), sto.OpenStorageBackedBigInt(pricePerUnitOffset), } @@ -120,12 +122,12 @@ func (ps *L1PricingState) SetPayRewardsTo(addr common.Address) error { return ps.payRewardsTo.Set(addr) } -func (ps *L1PricingState) EquilibrationTime() (uint64, error) { - return ps.equilibrationTime.Get() +func (ps *L1PricingState) EquilibrationUnits() (*big.Int, error) { + return ps.equilibrationUnits.Get() } -func (ps *L1PricingState) SetEquilibrationTime(equilTime uint64) error { - return ps.equilibrationTime.Set(equilTime) +func (ps *L1PricingState) SetEquilibrationUnits(equilUnits *big.Int) error { + return ps.equilibrationUnits.Set(equilUnits) } func (ps *L1PricingState) Inertia() (uint64, error) { @@ -144,14 +146,6 @@ func (ps *L1PricingState) SetPerUnitReward(weiPerUnit uint64) error { return ps.perUnitReward.Set(weiPerUnit) } -func (ps *L1PricingState) CurrentTime() (uint64, error) { - return ps.currentTime.Get() -} - -func (ps *L1PricingState) SetCurrentTime(t uint64) error { - return ps.currentTime.Set(t) -} - func (ps *L1PricingState) LastUpdateTime() (uint64, error) { return ps.lastUpdateTime.Get() } @@ -192,11 +186,6 @@ func (ps *L1PricingState) SetPricePerUnit(price *big.Int) error { return ps.pricePerUnit.Set(price) } -// Update the pricing model with info from the start of a block -func (ps *L1PricingState) UpdateTime(currentTime uint64) { - _ = ps.SetCurrentTime(currentTime) -} - // Update the pricing model based on a payment by a batch poster func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm *vm.EVM, updateTime uint64, currentTime uint64, batchPoster common.Address, weiSpent *big.Int) error { batchPosterTable := ps.BatchPosterTable() @@ -214,7 +203,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - oldShortfall := am.BigSub(am.BigAdd(totalFundsDue, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) + oldShortfall := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) // compute allocation fraction -- will allocate updateTimeDelta/timeDelta fraction of units and funds to this update lastUpdateTime, err := ps.LastUpdateTime() @@ -267,7 +256,8 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if am.BigLessThan(availableFunds, paymentForRewards) { paymentForRewards = availableFunds } - if err := ps.SetFundsDueForRewards(am.BigSub(fundsDueForRewards, paymentForRewards)); err != nil { + fundsDueForRewards = am.BigSub(fundsDueForRewards, paymentForRewards) + if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { return err } payRewardsTo, err := ps.PayRewardsTo() @@ -285,7 +275,6 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - remainingFundsDueToPosters := big.NewInt(0) for _, posterAddr := range allPosterAddrs { poster, err := batchPosterTable.OpenPoster(posterAddr) if err != nil { @@ -315,9 +304,6 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * return err } } - if balanceDueToPoster.Sign() > 0 { - remainingFundsDueToPosters = am.BigAdd(remainingFundsDueToPosters, balanceDueToPoster) - } } // update time @@ -327,28 +313,42 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * // adjust the price if unitsAllocated > 0 { - shortfall := am.BigSub(am.BigAdd(remainingFundsDueToPosters, fundsDueForRewards), statedb.GetBalance(L1PricerFundsPoolAddress)) + totalFundsDue, err = batchPosterTable.TotalFundsDue() + if err != nil { + return err + } + fundsDueForRewards, err = ps.FundsDueForRewards() + if err != nil { + return err + } + shortfall := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + inertia, err := ps.Inertia() if err != nil { return err } - equilTime, err := ps.EquilibrationTime() + equilUnits, err := ps.EquilibrationUnits() if err != nil { return err } + inertiaUnits := am.BigDivByUint(equilUnits, inertia) price, err := ps.PricePerUnit() if err != nil { return err } - priceChange := am.BigDivByUint( - am.BigAdd(am.BigSub(shortfall, oldShortfall), am.BigDivByUint(shortfall, equilTime)), - unitsAllocated+equilTime/inertia, + allocPlusInert := am.BigAddByUint(inertiaUnits, unitsAllocated) + priceChange := am.BigDiv( + am.BigSub( + am.BigMul(shortfall, am.BigSub(equilUnits, common.Big1)), + am.BigMul(oldShortfall, equilUnits), + ), + am.BigMul(equilUnits, allocPlusInert), ) - newPrice := am.BigSub(price, priceChange) + newPrice := am.BigAdd(price, priceChange) if newPrice.Sign() < 0 { - newPrice = big.NewInt(0) + newPrice = common.Big0 } if err := ps.SetPricePerUnit(newPrice); err != nil { return err @@ -358,7 +358,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * } func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common.Address) { - tx.PosterCost = big.NewInt(0) + tx.PosterCost = common.Big0 tx.PosterIsReimbursable = false contains, err := ps.batchPosterTable.ContainsPoster(posterAddr) @@ -398,7 +398,7 @@ func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Add byteCount, err := byteCountAfterBrotli0(message.Data()) if err != nil { log.Error("failed to compress tx", "err", err) - return big.NewInt(0), 0, false + return common.Big0, 0, false } // Approximate the l1 fee charged for posting this tx's calldata diff --git a/arbos/l1pricing/l1pricing_test.go b/arbos/l1pricing/l1pricing_test.go index 0a14c690cf..7c83373987 100644 --- a/arbos/l1pricing/l1pricing_test.go +++ b/arbos/l1pricing/l1pricing_test.go @@ -4,6 +4,7 @@ package l1pricing import ( + am "github.com/offchainlabs/nitro/util/arbmath" "math" "math/big" "testing" @@ -61,7 +62,7 @@ func TestL1PriceUpdate(t *testing.T) { Fail(t) } - initialPriceEstimate := big.NewInt(InitialPricePerUnitWei) + initialPriceEstimate := am.UintToBig(InitialPricePerUnitWei) priceEstimate, err := ps.PricePerUnit() Require(t, err) if priceEstimate.Cmp(initialPriceEstimate) != 0 { diff --git a/contracts/src/precompiles/ArbOwner.sol b/contracts/src/precompiles/ArbOwner.sol index eaf8df4fc9..1e6e2b3a86 100644 --- a/contracts/src/precompiles/ArbOwner.sol +++ b/contracts/src/precompiles/ArbOwner.sol @@ -66,8 +66,8 @@ interface ArbOwner { /// @notice Upgrades ArbOS to the requested version at the requested timestamp function scheduleArbOSUpgrade(uint64 newVersion, uint64 timestamp) external; - /// @notice Sets equilibration time parameter for L1 price adjustment algorithm - function setL1PricingEquilibrationTime(uint64 equilibrationTime) external; + /// @notice Sets equilibration units parameter for L1 price adjustment algorithm + function setL1PricingEquilibrationUnits(uint256 equilibrationUnits) external; /// @notice Sets inertia parameter for L1 price adjustment algorithm function setL1PricingInertia(uint64 inertia) external; diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index b94837cf2c..bb0dc7c3cb 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -127,7 +127,11 @@ func (con ArbGasInfo) GetL1BaseFeeEstimateInertia(c ctx, evm mech) (uint64, erro // Get the current estimate of the L1 basefee func (con ArbGasInfo) GetL1GasPriceEstimate(c ctx, evm mech) (huge, error) { - return c.State.L1PricingState().PricePerUnit() + ppu, err := c.State.L1PricingState().PricePerUnit() + if err != nil { + return nil, err + } + return arbmath.BigMulByUint(ppu, params.TxDataNonZeroGasEIP2028), nil } // Get the fee paid to the aggregator for posting this tx diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index baaeedeed5..56d1cda5c7 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -118,8 +118,8 @@ func (con ArbOwner) ScheduleArbOSUpgrade(c ctx, evm mech, newVersion uint64, tim return c.State.ScheduleArbOSUpgrade(newVersion, timestamp) } -func (con ArbOwner) SetL1PricingEquilibrationTime(c ctx, evm mech, equilibrationTime uint64) error { - return c.State.L1PricingState().SetEquilibrationTime(equilibrationTime) +func (con ArbOwner) SetL1PricingEquilibrationUnits(c ctx, evm mech, equilibrationUnits huge) error { + return c.State.L1PricingState().SetEquilibrationUnits(equilibrationUnits) } func (con ArbOwner) SetL1PricingInertia(c ctx, evm mech, inertia uint64) error { diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 2e8b5cde6a..8ba6a21b5b 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -113,7 +113,7 @@ func TestSequencerPriceAdjusts(t *testing.T) { colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", currEstimate, " = ", trueEstimate) oldDiff := arbmath.BigAbs(arbmath.BigSub(lastEstimate, l1Header.BaseFee)) - newDiff := arbmath.BigAbs(arbmath.BigSub(currEstimate, l1Header.BaseFee)) + newDiff := arbmath.BigAbs(arbmath.BigSub(trueEstimate, l1Header.BaseFee)) if arbmath.BigGreaterThan(newDiff, oldDiff) { Fail(t, "L1 gas price estimate should tend toward the basefee") From 0a1905ade240a64d790776143fb510ace286a76a Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Wed, 15 Jun 2022 07:50:41 -0400 Subject: [PATCH 47/61] Merge integration --- arbos/tx_processor.go | 15 +++++++++------ nodeInterface/virtual-contracts.go | 7 +------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 0ccad332c6..8655894549 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -242,18 +242,17 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r return false, 0, nil, nil } -func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { +func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, error) { // Because a user pays a 1-dimensional gas price, we must re-express poster L1 calldata costs // as if the user was buying an equivalent amount of L2 compute gas. This hook determines what // that cost looks like, ensuring the user can pay and saving the result for later reference. var gasNeededToStartEVM uint64 gasPrice := p.evm.Context.BaseFee - coinbase := p.evm.Context.Coinbase - posterCost, calldataUnits, reimburse := p.state.L1PricingState().PosterDataCost(p.msg, p.msg.From(), coinbase) + posterCost, calldataUnits, _ := p.state.L1PricingState().PosterDataCost(p.msg, p.msg.From()) if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return err + return nil, err } if p.msg.RunMode() == types.MessageGasEstimationMode { @@ -284,7 +283,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { if *gasRemaining < gasNeededToStartEVM { // the user couldn't pay for call data, so give up - return core.ErrIntrinsicGas + return nil, core.ErrIntrinsicGas } *gasRemaining -= gasNeededToStartEVM @@ -297,7 +296,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { *gasRemaining = gasAvailable } } - return nil + return nil, nil // TODO: verify that first value (tip recipient) should always be nil } func (p *TxProcessor) NonrefundableGas() uint64 { @@ -461,3 +460,7 @@ func (p *TxProcessor) L1BlockHash(blockCtx vm.BlockContext, l1BlockNumber uint64 func (p *TxProcessor) FillReceiptInfo(receipt *types.Receipt) { receipt.GasUsedForL1 = p.posterGas } + +func (p *TxProcessor) DropTip() bool { + return true // TODO: verify that this is correct (always dropping tips) +} diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index 1c4a21fcf3..730b62a0c9 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -125,17 +125,12 @@ func init() { log.Error("failed to open ArbOS state", "err", err) return } - var poster common.Address - allPosters, _ := state.L1PricingState().BatchPosterTable().AllPosters() - if len(allPosters) > 0 { - poster = allPosters[0] - } if header.BaseFee.Sign() == 0 { // if gas is free or there's no reimbursable poster, the user won't pay for L1 data costs return } - posterCost := state.L1PricingState().PosterDataCost(msg, msg.From(), *poster) + posterCost, _, _ := state.L1PricingState().PosterDataCost(msg, msg.From()) posterCostInL2Gas := arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, header.BaseFee)) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } From 5743c5d350c791fedd7e6083bb55c113c405f156 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Wed, 15 Jun 2022 14:20:58 -0400 Subject: [PATCH 48/61] Fix test --- system_tests/fees_test.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 8ba6a21b5b..a410e298c8 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -98,33 +98,35 @@ func TestSequencerPriceAdjusts(t *testing.T) { Require(t, err) units := compressedTxSize(t, tx) * params.TxDataNonZeroGasEIP2028 - currEstimate := arbmath.BigDivByUint(arbmath.BigMulByUint(header.BaseFee, receipt.GasUsedForL1), units) + estimatedL1FeePerUnit := arbmath.BigDivByUint(arbmath.BigMulByUint(header.BaseFee, receipt.GasUsedForL1), units) - if !arbmath.BigEquals(lastEstimate, currEstimate) { + if !arbmath.BigEquals(lastEstimate, estimatedL1FeePerUnit) { l1Header, err = l1client.HeaderByNumber(ctx, nil) Require(t, err) callOpts := &bind.CallOpts{Context: ctx, BlockNumber: receipt.BlockNumber} - trueEstimate, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts) + actualL1FeePerUnit, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts) Require(t, err) colors.PrintGrey("ArbOS updated its L1 estimate") colors.PrintGrey(" L1 base fee ", l1Header.BaseFee) - colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", currEstimate, " = ", trueEstimate) + colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", estimatedL1FeePerUnit, " = ", actualL1FeePerUnit) oldDiff := arbmath.BigAbs(arbmath.BigSub(lastEstimate, l1Header.BaseFee)) - newDiff := arbmath.BigAbs(arbmath.BigSub(trueEstimate, l1Header.BaseFee)) + newDiff := arbmath.BigAbs(arbmath.BigSub(actualL1FeePerUnit, l1Header.BaseFee)) if arbmath.BigGreaterThan(newDiff, oldDiff) { Fail(t, "L1 gas price estimate should tend toward the basefee") } - if !arbmath.BigEquals(trueEstimate, currEstimate) { - Fail(t, "New L1 estimate does not match receipt") + diff := arbmath.BigAbs(arbmath.BigSub(actualL1FeePerUnit, estimatedL1FeePerUnit)) + maxDiffToAllow := arbmath.BigDivByUint(actualL1FeePerUnit, 100) + if arbmath.BigLessThan(maxDiffToAllow, diff) { // verify that estimates is within 1% of actual + Fail(t, "New L1 estimate differs too much from receipt") } - if arbmath.BigEquals(trueEstimate, common.Big0) { + if arbmath.BigEquals(actualL1FeePerUnit, common.Big0) { Fail(t, "Estimate is zero", i) } - lastEstimate = trueEstimate + lastEstimate = actualL1FeePerUnit timesPriceAdjusted++ } From c81e911b72b427342a50e2ed3ba5c1c8988cad2a Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 16 Jun 2022 12:29:02 -0400 Subject: [PATCH 49/61] Use coinbase to determine reimbursability --- arbos/block_processor.go | 7 +------ arbos/l1pricing/l1pricing.go | 7 +++++-- arbos/tx_processor.go | 21 +++++++++++++++++---- nodeInterface/virtual-contracts.go | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index b9dad5ec82..91838e602f 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -10,8 +10,6 @@ import ( "math/big" "strconv" - "github.com/offchainlabs/nitro/arbos/l1pricing" - "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/util" @@ -50,10 +48,7 @@ func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState coinbase := common.Address{} if l1info != nil { timestamp = l1info.l1Timestamp - isBatchPoster, _ := state.L1PricingState().BatchPosterTable().ContainsPoster(l1info.poster) - if isBatchPoster { - coinbase = l1pricing.L1PricerFundsPoolAddress - } + coinbase = l1info.poster } if prevHeader != nil { lastBlockHash = prevHeader.Hash() diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 733a9aeb53..31c2204a78 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -361,8 +361,7 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common tx.PosterCost = common.Big0 tx.PosterIsReimbursable = false - contains, err := ps.batchPosterTable.ContainsPoster(posterAddr) - if err != nil || !contains { + if posterAddr != BatchPosterAddress { return } txBytes, merr := tx.MarshalBinary() @@ -395,6 +394,10 @@ func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Add return tx.PosterCost, tx.CalldataUnits, tx.PosterIsReimbursable } + if poster != BatchPosterAddress { + return common.Big0, 0, false + } + byteCount, err := byteCountAfterBrotli0(message.Data()) if err != nil { log.Error("failed to compress tx", "err", err) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 8655894549..931e6870c1 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -5,6 +5,7 @@ package arbos import ( "errors" + "github.com/offchainlabs/nitro/arbos/l1pricing" "math" "math/big" "time" @@ -250,16 +251,21 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er var gasNeededToStartEVM uint64 gasPrice := p.evm.Context.BaseFee - posterCost, calldataUnits, _ := p.state.L1PricingState().PosterDataCost(p.msg, p.msg.From()) - if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return nil, err - } + var posterCost *big.Int + var calldataUnits uint64 if p.msg.RunMode() == types.MessageGasEstimationMode { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. // This will help the user pad the total they'll pay in case the price rises a bit. // Note, reducing the poster cost will increase share the network fee gets, not reduce the total. + posterCost, calldataUnits, _ = p.state.L1PricingState().PosterDataCost(p.msg, l1pricing.BatchPosterAddress) + if calldataUnits > 0 { + if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { + return nil, err + } + } + minGasPrice, _ := p.state.L2PricingState().MinBaseFeeWei() adjustedPrice := arbmath.BigMulByFrac(gasPrice, 7, 8) // assume congestion @@ -270,6 +276,13 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er // Pad the L1 cost by 10% in case the L1 gas price rises posterCost = arbmath.BigMulByFrac(posterCost, 110, 100) + } else { + posterCost, calldataUnits, _ = p.state.L1PricingState().PosterDataCost(p.msg, p.evm.Context.Coinbase) + if calldataUnits > 0 { + if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { + return nil, err + } + } } if gasPrice.Sign() > 0 { posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice) // the cost as if it were an amount of gas diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index 730b62a0c9..f8cbbd08bd 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -130,7 +130,7 @@ func init() { return } - posterCost, _, _ := state.L1PricingState().PosterDataCost(msg, msg.From()) + posterCost, _, _ := state.L1PricingState().PosterDataCost(msg, header.Coinbase) posterCostInL2Gas := arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, header.BaseFee)) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } From 51a2d8d57a84bbdad95ef7c78d034454d359c612 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 16 Jun 2022 16:38:35 -0400 Subject: [PATCH 50/61] Remove spurious _reportBatchSpending --- contracts/src/bridge/SequencerInbox.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/src/bridge/SequencerInbox.sol b/contracts/src/bridge/SequencerInbox.sol index fedd681203..7128a7c379 100644 --- a/contracts/src/bridge/SequencerInbox.sol +++ b/contracts/src/bridge/SequencerInbox.sol @@ -186,9 +186,6 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox dataHash, afterDelayedMessagesRead ); - if (data.length > 0) { - _reportBatchSpending(dataHash, sequenceNumber); - } emit SequencerBatchDelivered( sequenceNumber, beforeAcc, From 0ab910c3ffe029837abca3eb67bb7aa521dad0b1 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Thu, 16 Jun 2022 16:38:51 -0400 Subject: [PATCH 51/61] Refactor --- arbos/tx_processor.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 931e6870c1..61654fe3cc 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -251,21 +251,24 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er var gasNeededToStartEVM uint64 gasPrice := p.evm.Context.BaseFee - var posterCost *big.Int - var calldataUnits uint64 + var poster common.Address + if p.msg.RunMode() == types.MessageGasEstimationMode { + poster = l1pricing.BatchPosterAddress + } else { + poster = p.evm.Context.Coinbase + } + posterCost, calldataUnits, _ := p.state.L1PricingState().PosterDataCost(p.msg, poster) + if calldataUnits > 0 { + if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { + return nil, err + } + } if p.msg.RunMode() == types.MessageGasEstimationMode { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. // This will help the user pad the total they'll pay in case the price rises a bit. // Note, reducing the poster cost will increase share the network fee gets, not reduce the total. - posterCost, calldataUnits, _ = p.state.L1PricingState().PosterDataCost(p.msg, l1pricing.BatchPosterAddress) - if calldataUnits > 0 { - if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return nil, err - } - } - minGasPrice, _ := p.state.L2PricingState().MinBaseFeeWei() adjustedPrice := arbmath.BigMulByFrac(gasPrice, 7, 8) // assume congestion @@ -276,14 +279,8 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er // Pad the L1 cost by 10% in case the L1 gas price rises posterCost = arbmath.BigMulByFrac(posterCost, 110, 100) - } else { - posterCost, calldataUnits, _ = p.state.L1PricingState().PosterDataCost(p.msg, p.evm.Context.Coinbase) - if calldataUnits > 0 { - if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return nil, err - } - } } + if gasPrice.Sign() > 0 { posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice) // the cost as if it were an amount of gas if !posterCostInL2Gas.IsUint64() { From db445097947dad7cfc088c0af96b3bc86aa36180 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 17 Jun 2022 07:12:09 -0500 Subject: [PATCH 52/61] Fix merge --- arbos/l1pricing/l1pricing.go | 16 +++++++--------- arbos/tx_processor.go | 13 +++++++------ nodeInterface/virtual-contracts.go | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 31c2204a78..3c11001a86 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -264,7 +264,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - err = util.TransferBalance(&L1PricerFundsPoolAddress, &payRewardsTo, paymentForRewards, evm, util.TracingBeforeEVM) + err = util.TransferBalance(&L1PricerFundsPoolAddress, &payRewardsTo, paymentForRewards, evm, util.TracingBeforeEVM, "batchPosterReward") if err != nil { return err } @@ -293,7 +293,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - err = util.TransferBalance(&L1PricerFundsPoolAddress, &addrToPay, balanceToTransfer, evm, util.TracingBeforeEVM) + err = util.TransferBalance(&L1PricerFundsPoolAddress, &addrToPay, balanceToTransfer, evm, util.TracingBeforeEVM, "batchPosterRefund") if err != nil { return err } @@ -359,7 +359,6 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common.Address) { tx.PosterCost = common.Big0 - tx.PosterIsReimbursable = false if posterAddr != BatchPosterAddress { return @@ -381,27 +380,26 @@ func (ps *L1PricingState) AddPosterInfo(tx *types.Transaction, posterAddr common numUnits := l1Bytes * params.TxDataNonZeroGasEIP2028 tx.PosterCost = am.BigMulByUint(pricePerUnit, numUnits) tx.CalldataUnits = numUnits - tx.PosterIsReimbursable = true } const TxFixedCost = 140 // assumed maximum size in bytes of a typical RLP-encoded tx, not including its calldata -func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, uint64, bool) { +func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, uint64) { if tx := message.UnderlyingTransaction(); tx != nil { if tx.PosterCost == nil { ps.AddPosterInfo(tx, poster) } - return tx.PosterCost, tx.CalldataUnits, tx.PosterIsReimbursable + return tx.PosterCost, tx.CalldataUnits } if poster != BatchPosterAddress { - return common.Big0, 0, false + return common.Big0, 0 } byteCount, err := byteCountAfterBrotli0(message.Data()) if err != nil { log.Error("failed to compress tx", "err", err) - return common.Big0, 0, false + return common.Big0, 0 } // Approximate the l1 fee charged for posting this tx's calldata @@ -409,7 +407,7 @@ func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Add pricePerUnit, _ := ps.PricePerUnit() units := l1Bytes * params.TxDataNonZeroGasEIP2028 - return am.BigMulByUint(pricePerUnit, units), units, true + return am.BigMulByUint(pricePerUnit, units), units } func byteCountAfterBrotli0(input []byte) (uint64, error) { diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 23f8948341..4decf7208b 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -5,11 +5,12 @@ package arbos import ( "errors" - "github.com/offchainlabs/nitro/arbos/l1pricing" "math" "math/big" "time" + "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" @@ -246,7 +247,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r return false, 0, nil, nil } -func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, error) { +func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { // Because a user pays a 1-dimensional gas price, we must re-express poster L1 calldata costs // as if the user was buying an equivalent amount of L2 compute gas. This hook determines what // that cost looks like, ensuring the user can pay and saving the result for later reference. @@ -260,10 +261,10 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er } else { poster = p.evm.Context.Coinbase } - posterCost, calldataUnits, _ := p.state.L1PricingState().PosterDataCost(p.msg, poster) + posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster) if calldataUnits > 0 { if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return nil, err + return err } } @@ -296,7 +297,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er if *gasRemaining < gasNeededToStartEVM { // the user couldn't pay for call data, so give up - return nil, core.ErrIntrinsicGas + return core.ErrIntrinsicGas } *gasRemaining -= gasNeededToStartEVM @@ -309,7 +310,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (*common.Address, er *gasRemaining = gasAvailable } } - return nil, nil // TODO: verify that first value (tip recipient) should always be nil + return nil } func (p *TxProcessor) NonrefundableGas() uint64 { diff --git a/nodeInterface/virtual-contracts.go b/nodeInterface/virtual-contracts.go index f8cbbd08bd..d899153d7e 100644 --- a/nodeInterface/virtual-contracts.go +++ b/nodeInterface/virtual-contracts.go @@ -130,7 +130,7 @@ func init() { return } - posterCost, _, _ := state.L1PricingState().PosterDataCost(msg, header.Coinbase) + posterCost, _ := state.L1PricingState().PosterDataCost(msg, header.Coinbase) posterCostInL2Gas := arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, header.BaseFee)) *gascap = arbmath.SaturatingUAdd(*gascap, posterCostInL2Gas) } From d76dd0851afeba117eff2940426d70bf9237ee05 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 17 Jun 2022 10:28:12 -0500 Subject: [PATCH 53/61] Remove unused DropTip method (was used before merge) --- arbos/tx_processor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 4decf7208b..99da947620 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -476,7 +476,3 @@ func (p *TxProcessor) L1BlockHash(blockCtx vm.BlockContext, l1BlockNumber uint64 func (p *TxProcessor) FillReceiptInfo(receipt *types.Receipt) { receipt.GasUsedForL1 = p.posterGas } - -func (p *TxProcessor) DropTip() bool { - return true // TODO: verify that this is correct (always dropping tips) -} From 291bcbd704c739b7a1339f73643a325175ea2f47 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 17 Jun 2022 10:51:58 -0500 Subject: [PATCH 54/61] Fix sequencer inbox force include test --- contracts/test/contract/sequencerInboxForceInclude.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/test/contract/sequencerInboxForceInclude.spec.ts b/contracts/test/contract/sequencerInboxForceInclude.spec.ts index cc74f294dd..2de836f007 100644 --- a/contracts/test/contract/sequencerInboxForceInclude.spec.ts +++ b/contracts/test/contract/sequencerInboxForceInclude.spec.ts @@ -257,6 +257,7 @@ describe('SequencerInboxForceInclude', async () => { await sequencerInbox.initialize( bridgeProxy.address, + inbox.address, await dummyRollup.getAddress(), { delayBlocks: maxDelayBlocks, @@ -265,7 +266,7 @@ describe('SequencerInboxForceInclude', async () => { futureSeconds: 3000, }, ) - await inbox.initialize(bridgeProxy.address) + await inbox.initialize(bridgeProxy.address, sequencerInbox.address) await bridge.setInbox(inbox.address, true) From 1e6f13892b9e8757bad92424972ec915998c6bbf Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Fri, 17 Jun 2022 13:59:09 -0400 Subject: [PATCH 55/61] Commit for merge --- arbos/l1pricing/batchPoster.go | 58 +++++++++++++++++++--------------- arbos/l1pricing/l1pricing.go | 8 ++--- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index 73b6a16b29..f764a3cf22 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -13,6 +13,8 @@ import ( "github.com/offchainlabs/nitro/util/arbmath" ) +const totalFundsDueOffset = 0 + var ( PosterAddrsKey = []byte{0} PosterInfoKey = []byte{1} @@ -22,24 +24,30 @@ var ( // layout of storage in the table type BatchPostersTable struct { - posterAddrs *addressSet.AddressSet - posterInfo *storage.Storage + posterAddrs *addressSet.AddressSet + posterInfo *storage.Storage + totalFundsDue storage.StorageBackedBigInt } type BatchPosterState struct { - fundsDue storage.StorageBackedBigInt - payTo storage.StorageBackedAddress + fundsDue storage.StorageBackedBigInt + payTo storage.StorageBackedAddress + postersTable *BatchPostersTable } func InitializeBatchPostersTable(storage *storage.Storage) error { - // no initialization needed for posterInfo + totalFundsDue := storage.OpenStorageBackedBigInt(totalFundsDueOffset) + if err := totalFundsDue.Set(common.Big0); err != nil { + return err + } return addressSet.Initialize(storage.OpenSubStorage(PosterAddrsKey)) } func OpenBatchPostersTable(storage *storage.Storage) *BatchPostersTable { return &BatchPostersTable{ - posterAddrs: addressSet.OpenAddressSet(storage.OpenSubStorage(PosterAddrsKey)), - posterInfo: storage.OpenSubStorage(PosterInfoKey), + posterAddrs: addressSet.OpenAddressSet(storage.OpenSubStorage(PosterAddrsKey)), + posterInfo: storage.OpenSubStorage(PosterInfoKey), + totalFundsDue: storage.OpenStorageBackedBigInt(totalFundsDueOffset), } } @@ -57,8 +65,9 @@ func (bpt *BatchPostersTable) OpenPoster(poster common.Address) (*BatchPosterSta func (bpt *BatchPostersTable) internalOpen(poster common.Address) *BatchPosterState { bpStorage := bpt.posterInfo.OpenSubStorage(poster.Bytes()) return &BatchPosterState{ - fundsDue: bpStorage.OpenStorageBackedBigInt(0), - payTo: bpStorage.OpenStorageBackedAddress(1), + fundsDue: bpStorage.OpenStorageBackedBigInt(0), + payTo: bpStorage.OpenStorageBackedAddress(1), + postersTable: bpt, } } @@ -94,23 +103,7 @@ func (bpt *BatchPostersTable) AllPosters() ([]common.Address, error) { } func (bpt *BatchPostersTable) TotalFundsDue() (*big.Int, error) { - allPosters, err := bpt.AllPosters() - if err != nil { - return nil, err - } - ret := common.Big0 - for _, posterAddr := range allPosters { - poster, err := bpt.OpenPoster(posterAddr) - if err != nil { - return nil, err - } - fundsDue, err := poster.FundsDue() - if err != nil { - return nil, err - } - ret = arbmath.BigAdd(ret, fundsDue) - } - return ret, nil + return bpt.totalFundsDue.Get() } func (bps *BatchPosterState) FundsDue() (*big.Int, error) { @@ -118,6 +111,19 @@ func (bps *BatchPosterState) FundsDue() (*big.Int, error) { } func (bps *BatchPosterState) SetFundsDue(val *big.Int) error { + fundsDue := bps.fundsDue + totalFundsDue := bps.postersTable.totalFundsDue + prev, err := fundsDue.Get() + if err != nil { + return err + } + prevTotal, err := totalFundsDue.Get() + if err != nil { + return err + } + if err := totalFundsDue.Set(arbmath.BigSub(arbmath.BigAdd(prevTotal, val), prev)); err != nil { + return err + } return bps.fundsDue.Set(val) } diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 31c2204a78..d70b2eeb5e 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -203,7 +203,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - oldShortfall := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + oldSurplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) // compute allocation fraction -- will allocate updateTimeDelta/timeDelta fraction of units and funds to this update lastUpdateTime, err := ps.LastUpdateTime() @@ -321,7 +321,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if err != nil { return err } - shortfall := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + surplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) inertia, err := ps.Inertia() if err != nil { @@ -340,8 +340,8 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * allocPlusInert := am.BigAddByUint(inertiaUnits, unitsAllocated) priceChange := am.BigDiv( am.BigSub( - am.BigMul(shortfall, am.BigSub(equilUnits, common.Big1)), - am.BigMul(oldShortfall, equilUnits), + am.BigMul(surplus, am.BigSub(equilUnits, common.Big1)), + am.BigMul(oldSurplus, equilUnits), ), am.BigMul(equilUnits, allocPlusInert), ) From 502b79c162a0aca6fff8866ca353a21287ebbae6 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Fri, 17 Jun 2022 14:16:26 -0400 Subject: [PATCH 56/61] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 84bfb285f8..4a00cd8cc4 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 84bfb285f83d383416e85e4ffd8cdd3e409f6dd3 +Subproject commit 4a00cd8cc4b2b0025c8ba9e98259714ed69e49cb From 6332df5b49fe90038a804bcf986f623912961cbc Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Fri, 17 Jun 2022 15:35:27 -0400 Subject: [PATCH 57/61] Don't create poster based on untrusted call --- arbos/arbosState/initialization_test.go | 2 +- arbos/l1pricing/batchPoster.go | 8 ++++++-- arbos/l1pricing/batchPoster_test.go | 2 +- arbos/l1pricing/l1pricing.go | 4 ++-- arbos/l1pricing_test.go | 2 +- precompiles/ArbAggregator.go | 4 ++-- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/arbos/arbosState/initialization_test.go b/arbos/arbosState/initialization_test.go index a6982f8ce4..0d4804b736 100644 --- a/arbos/arbosState/initialization_test.go +++ b/arbos/arbosState/initialization_test.go @@ -172,7 +172,7 @@ func checkAccounts(db *state.StateDB, arbState *ArbosState, accts []statetransfe isPoster, err := posterTable.ContainsPoster(addr) Require(t, err) if acct.AggregatorInfo != nil && isPoster { - posterInfo, err := posterTable.OpenPoster(addr) + posterInfo, err := posterTable.OpenPoster(addr, false) Require(t, err) fc, err := posterInfo.PayTo() Require(t, err) diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index f764a3cf22..f13703c16f 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -20,6 +20,7 @@ var ( PosterInfoKey = []byte{1} ErrAlreadyExists = errors.New("tried to add a batch poster that already exists") + ErrNotExist = errors.New("tried to open a batch poster that does not exist") ) // layout of storage in the table @@ -51,12 +52,15 @@ func OpenBatchPostersTable(storage *storage.Storage) *BatchPostersTable { } } -func (bpt *BatchPostersTable) OpenPoster(poster common.Address) (*BatchPosterState, error) { +func (bpt *BatchPostersTable) OpenPoster(poster common.Address, createIfNotExist bool) (*BatchPosterState, error) { isBatchPoster, err := bpt.posterAddrs.IsMember(poster) if err != nil { return nil, err } if !isBatchPoster { + if !createIfNotExist { + return nil, ErrNotExist + } return bpt.AddPoster(poster, poster) } return bpt.internalOpen(poster), nil @@ -147,7 +151,7 @@ func (bpt *BatchPostersTable) GetFundsDueList() ([]FundsDueItem, error) { return nil, err } for _, posterAddr := range allPosters { - poster, err := bpt.OpenPoster(posterAddr) + poster, err := bpt.OpenPoster(posterAddr, false) if err != nil { return nil, err } diff --git a/arbos/l1pricing/batchPoster_test.go b/arbos/l1pricing/batchPoster_test.go index 7cea720fa9..9cff4f3817 100644 --- a/arbos/l1pricing/batchPoster_test.go +++ b/arbos/l1pricing/batchPoster_test.go @@ -79,7 +79,7 @@ func TestBatchPosterTable(t *testing.T) { } // test get/set of BP fields - bp1, err = bpTable.OpenPoster(addr1) + bp1, err = bpTable.OpenPoster(addr1, false) Require(t, err) err = bp1.SetPayTo(addr2) Require(t, err) diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 4ff5c1dec0..9187796558 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -189,7 +189,7 @@ func (ps *L1PricingState) SetPricePerUnit(price *big.Int) error { // Update the pricing model based on a payment by a batch poster func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm *vm.EVM, updateTime uint64, currentTime uint64, batchPoster common.Address, weiSpent *big.Int) error { batchPosterTable := ps.BatchPosterTable() - posterState, err := batchPosterTable.OpenPoster(batchPoster) + posterState, err := batchPosterTable.OpenPoster(batchPoster, true) if err != nil { return err } @@ -276,7 +276,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * return err } for _, posterAddr := range allPosterAddrs { - poster, err := batchPosterTable.OpenPoster(posterAddr) + poster, err := batchPosterTable.OpenPoster(posterAddr, false) if err != nil { return err } diff --git a/arbos/l1pricing_test.go b/arbos/l1pricing_test.go index bd6b7b5e45..e70bf7680b 100644 --- a/arbos/l1pricing_test.go +++ b/arbos/l1pricing_test.go @@ -112,7 +112,7 @@ func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedRes } firstPoster := posterAddrs[0] firstPayTo := common.Address{1, 2} - poster, err := posterTable.OpenPoster(firstPoster) + poster, err := posterTable.OpenPoster(firstPoster, true) Require(t, err) due, err := poster.FundsDue() Require(t, err) diff --git a/precompiles/ArbAggregator.go b/precompiles/ArbAggregator.go index c87b3b0378..27c589bb00 100644 --- a/precompiles/ArbAggregator.go +++ b/precompiles/ArbAggregator.go @@ -94,7 +94,7 @@ func (con ArbAggregator) AddBatchPoster(c ctx, evm mech, newBatchPoster addr) er // Gets a batch poster's fee collector func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, batchPoster addr) (addr, error) { - posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster) + posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster, false) if err != nil { return addr{}, err } @@ -103,7 +103,7 @@ func (con ArbAggregator) GetFeeCollector(c ctx, evm mech, batchPoster addr) (add // Sets a batch poster's fee collector (caller must be the batch poster, its fee collector, or an owner) func (con ArbAggregator) SetFeeCollector(c ctx, evm mech, batchPoster addr, newFeeCollector addr) error { - posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster) + posterInfo, err := c.State.L1PricingState().BatchPosterTable().OpenPoster(batchPoster, false) if err != nil { return err } From 82a1de9e3c7e2e5a70ce1d40e7bcb4dfd6bff856 Mon Sep 17 00:00:00 2001 From: Ed Felten Date: Fri, 17 Jun 2022 16:09:51 -0400 Subject: [PATCH 58/61] Fixes from code review --- arbos/addressSet/addressSet.go | 5 ++- arbos/addressSet/addressSet_test.go | 3 +- arbos/arbosState/initialize.go | 9 ++++- arbos/l1pricing/batchPoster.go | 7 ++-- arbos/l1pricing/batchPoster_test.go | 5 ++- arbos/l1pricing/l1pricing.go | 5 ++- arbos/l1pricing_test.go | 3 +- arbos/storage/storage.go | 3 +- contracts/src/precompiles/ArbAggregator.sol | 6 --- precompiles/ArbAggregator.go | 45 ++------------------- precompiles/ArbOwner.go | 2 +- precompiles/ArbOwnerPublic.go | 2 +- system_tests/seqcompensation_test.go | 8 ---- 13 files changed, 32 insertions(+), 71 deletions(-) diff --git a/arbos/addressSet/addressSet.go b/arbos/addressSet/addressSet.go index b6894d48ed..3903e34944 100644 --- a/arbos/addressSet/addressSet.go +++ b/arbos/addressSet/addressSet.go @@ -65,11 +65,14 @@ func (aset *AddressSet) Clear() error { return aset.size.Clear() } -func (aset *AddressSet) AllMembers() ([]common.Address, error) { +func (aset *AddressSet) AllMembers(maxNumToReturn uint64) ([]common.Address, error) { size, err := aset.size.Get() if err != nil { return nil, err } + if size > maxNumToReturn { + size = maxNumToReturn + } ret := make([]common.Address, size) for i := range ret { sba := aset.backingStorage.OpenStorageBackedAddress(uint64(i + 1)) diff --git a/arbos/addressSet/addressSet_test.go b/arbos/addressSet/addressSet_test.go index 7a70184a27..8485f85cc6 100644 --- a/arbos/addressSet/addressSet_test.go +++ b/arbos/addressSet/addressSet_test.go @@ -4,6 +4,7 @@ package addressSet import ( + "github.com/ethereum/go-ethereum/common/math" "testing" "github.com/ethereum/go-ethereum/common" @@ -92,7 +93,7 @@ func TestAddressSet(t *testing.T) { } Require(t, aset.Add(addr1)) - all, err := aset.AllMembers() + all, err := aset.AllMembers(math.MaxUint64) Require(t, err) if len(all) != 2 { Fail(t) diff --git a/arbos/arbosState/initialize.go b/arbos/arbosState/initialize.go index de3b08b305..be957f2c71 100644 --- a/arbos/arbosState/initialize.go +++ b/arbos/arbosState/initialize.go @@ -160,8 +160,13 @@ func initializeArbosAccount(statedb *state.StateDB, arbosState *ArbosState, acco if err != nil { return err } - if !isPoster { - _, err = posterTable.AddPoster(account.Addr, account.AggregatorInfo.FeeCollector) + if isPoster { + // poster is already authorized, just set its fee collector + poster, err := posterTable.OpenPoster(account.Addr, false) + if err != nil { + return err + } + err = poster.SetPayTo(account.AggregatorInfo.FeeCollector) if err != nil { return err } diff --git a/arbos/l1pricing/batchPoster.go b/arbos/l1pricing/batchPoster.go index f13703c16f..6d332706ea 100644 --- a/arbos/l1pricing/batchPoster.go +++ b/arbos/l1pricing/batchPoster.go @@ -5,6 +5,7 @@ package l1pricing import ( "errors" + "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -102,8 +103,8 @@ func (bpt *BatchPostersTable) AddPoster(posterAddress common.Address, payTo comm return bpState, nil } -func (bpt *BatchPostersTable) AllPosters() ([]common.Address, error) { - return bpt.posterAddrs.AllMembers() +func (bpt *BatchPostersTable) AllPosters(maxNumToGet uint64) ([]common.Address, error) { + return bpt.posterAddrs.AllMembers(maxNumToGet) } func (bpt *BatchPostersTable) TotalFundsDue() (*big.Int, error) { @@ -146,7 +147,7 @@ type FundsDueItem struct { func (bpt *BatchPostersTable) GetFundsDueList() ([]FundsDueItem, error) { ret := []FundsDueItem{} - allPosters, err := bpt.AllPosters() + allPosters, err := bpt.AllPosters(math.MaxUint64) if err != nil { return nil, err } diff --git a/arbos/l1pricing/batchPoster_test.go b/arbos/l1pricing/batchPoster_test.go index 9cff4f3817..5b47534b23 100644 --- a/arbos/l1pricing/batchPoster_test.go +++ b/arbos/l1pricing/batchPoster_test.go @@ -5,6 +5,7 @@ package l1pricing import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/storage" "math/big" @@ -24,7 +25,7 @@ func TestBatchPosterTable(t *testing.T) { pay2 := common.Address{8, 10, 12, 14} // test creation and counting of bps - allPosters, err := bpTable.AllPosters() + allPosters, err := bpTable.AllPosters(math.MaxUint64) Require(t, err) if len(allPosters) != 0 { t.Fatal() @@ -72,7 +73,7 @@ func TestBatchPosterTable(t *testing.T) { t.Fatal() } - allPosters, err = bpTable.AllPosters() + allPosters, err = bpTable.AllPosters(math.MaxUint64) Require(t, err) if len(allPosters) != 2 { t.Fatal() diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 9187796558..b413266071 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -5,6 +5,7 @@ package l1pricing import ( "errors" + "github.com/ethereum/go-ethereum/common/math" "math/big" "github.com/ethereum/go-ethereum/core/vm" @@ -213,7 +214,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * if lastUpdateTime == 0 && currentTime > 0 { // it's the first update, so there isn't a last update time lastUpdateTime = updateTime - 1 } - if updateTime > currentTime || updateTime < lastUpdateTime || currentTime == lastUpdateTime { + if updateTime >= currentTime || updateTime < lastUpdateTime { return ErrInvalidTime } updateTimeDelta := updateTime - lastUpdateTime @@ -271,7 +272,7 @@ func (ps *L1PricingState) UpdateForBatchPosterSpending(statedb vm.StateDB, evm * availableFunds = am.BigSub(availableFunds, paymentForRewards) // settle up our batch poster payments owed, as much as possible - allPosterAddrs, err := batchPosterTable.AllPosters() + allPosterAddrs, err := batchPosterTable.AllPosters(math.MaxUint64) if err != nil { return err } diff --git a/arbos/l1pricing_test.go b/arbos/l1pricing_test.go index e70bf7680b..fb0f1779c6 100644 --- a/arbos/l1pricing_test.go +++ b/arbos/l1pricing_test.go @@ -5,6 +5,7 @@ package arbos import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/vm" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l1pricing" @@ -105,7 +106,7 @@ func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedRes if evm.StateDB.GetBalance(rewardAddress).Sign() != 0 { t.Fatal() } - posterAddrs, err := posterTable.AllPosters() + posterAddrs, err := posterTable.AllPosters(math.MaxUint64) Require(t, err) if len(posterAddrs) != 1 { t.Fatal() diff --git a/arbos/storage/storage.go b/arbos/storage/storage.go index edbe46feb6..df9f726a5b 100644 --- a/arbos/storage/storage.go +++ b/arbos/storage/storage.go @@ -295,8 +295,7 @@ func (ss *StorageSlot) Get() (common.Hash, error) { func (ss *StorageSlot) Set(value common.Hash) error { if ss.burner.ReadOnly() { log.Error("Read-only burner attempted to mutate state", "value", value) - panic("Read-only burner attempted to mutate state") - // return vm.ErrWriteProtection + return vm.ErrWriteProtection } err := ss.burner.Burn(writeCost(value)) if err != nil { diff --git a/contracts/src/precompiles/ArbAggregator.sol b/contracts/src/precompiles/ArbAggregator.sol index e29ecf3f80..4c01f00b6e 100644 --- a/contracts/src/precompiles/ArbAggregator.sol +++ b/contracts/src/precompiles/ArbAggregator.sol @@ -21,12 +21,6 @@ interface ArbAggregator { /// @return Batch poster addresses function getBatchPosters() external view returns (address[] memory); - /// @notice Deprecated, use addBatchPoster instead - /// @notice Adds newBatchPoster as a batch poster, if it is not already a batch poster - /// This reverts unless called by a chain owner - /// @param newBatchPoster New batch poster - function setDefaultAggregator(address newBatchPoster) external; - /// @notice Adds newBatchPoster as a batch poster /// This reverts unless called by a chain owner /// @param newBatchPoster New batch poster diff --git a/precompiles/ArbAggregator.go b/precompiles/ArbAggregator.go index 27c589bb00..224a104bc7 100644 --- a/precompiles/ArbAggregator.go +++ b/precompiles/ArbAggregator.go @@ -5,7 +5,7 @@ package precompiles import ( "errors" - "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/l1pricing" "math/big" ) @@ -20,54 +20,17 @@ var ErrNotOwner = errors.New("must be called by chain owner") // [Deprecated] func (con ArbAggregator) GetPreferredAggregator(c ctx, evm mech, address addr) (prefAgg addr, isDefault bool, err error) { - posters, err := c.State.L1PricingState().BatchPosterTable().AllPosters() - if err != nil { - return common.Address{}, false, err - } - if len(posters) == 0 { - return common.Address{}, false, errors.New("no batch posters exist") - } - return posters[0], true, err + return l1pricing.BatchPosterAddress, true, err } // [Deprecated] func (con ArbAggregator) GetDefaultAggregator(c ctx, evm mech) (addr, error) { - posters, err := c.State.L1PricingState().BatchPosterTable().AllPosters() - if err != nil { - return common.Address{}, err - } - if len(posters) == 0 { - return common.Address{}, errors.New("no batch posters exist") - } - return posters[0], err + return l1pricing.BatchPosterAddress, nil } // Get the addresses of all current batch posters func (con ArbAggregator) GetBatchPosters(c ctx, evm mech) ([]addr, error) { - return c.State.L1PricingState().BatchPosterTable().AllPosters() -} - -// [Deprecated] -func (con ArbAggregator) SetDefaultAggregator(c ctx, evm mech, newDefault addr) error { - isOwner, err := c.State.ChainOwners().IsMember(c.caller) - if err != nil { - return err - } - if !isOwner { - return ErrNotOwner - } - batchPosterTable := c.State.L1PricingState().BatchPosterTable() - isBatchPoster, err := batchPosterTable.ContainsPoster(newDefault) - if err != nil { - return err - } - if !isBatchPoster { - _, err = batchPosterTable.AddPoster(newDefault, newDefault) - if err != nil { - return err - } - } - return nil + return c.State.L1PricingState().BatchPosterTable().AllPosters(65536) } func (con ArbAggregator) AddBatchPoster(c ctx, evm mech, newBatchPoster addr) error { diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index e81d784722..230130520a 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -44,7 +44,7 @@ func (con ArbOwner) IsChainOwner(c ctx, evm mech, addr addr) (bool, error) { // Retrieves the list of chain owners func (con ArbOwner) GetAllChainOwners(c ctx, evm mech) ([]common.Address, error) { - return c.State.ChainOwners().AllMembers() + return c.State.ChainOwners().AllMembers(65536) } // Set how slowly ArbOS updates its estimate of the L1 basefee diff --git a/precompiles/ArbOwnerPublic.go b/precompiles/ArbOwnerPublic.go index 6d1a4fa62a..a11e07c4de 100644 --- a/precompiles/ArbOwnerPublic.go +++ b/precompiles/ArbOwnerPublic.go @@ -16,7 +16,7 @@ type ArbOwnerPublic struct { // Retrieves the list of chain owners func (con ArbOwnerPublic) GetAllChainOwners(c ctx, evm mech) ([]common.Address, error) { - return c.State.ChainOwners().AllMembers() + return c.State.ChainOwners().AllMembers(65536) } // See if the user is a chain owner diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index 0f36bb9986..51833c1ef7 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -55,12 +55,4 @@ func TestSequencerCompensation(t *testing.T) { if initialSeqBalance.Sign() != 0 { Fail(t, "Unexpected initial sequencer balance:", initialSeqBalance) } - finalSeqBalance, err := l2clientB.BalanceAt(ctx, l1pricing.L1PricerFundsPoolAddress, nil) - Require(t, err) - // Comment out this check, because new scheme doesn't reimburse sequencer immediately - // if finalSeqBalance.Sign() <= 0 { - // Fail(t, "Unexpected final sequencer balance:", finalSeqBalance) - //} - _ = finalSeqBalance - } From 209ee9b0e140185de01474b38aa1761f935d760d Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Wed, 15 Jun 2022 20:39:58 -0500 Subject: [PATCH 59/61] Backport sequencer inbox reader test flakiness fix --- system_tests/common_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 1ec7c14076..c2a360052d 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -212,6 +212,8 @@ func CreateTestNodeOnL1WithConfig(t *testing.T, ctx context.Context, isSequencer if !isSequencer { nodeConfig.BatchPoster.Enable = false + nodeConfig.Sequencer.Enable = false + nodeConfig.DelayedSequencer.Enable = false } node, err := arbnode.CreateNode(ctx, l2stack, l2chainDb, nodeConfig, l2blockchain, l1client, addresses, sequencerTxOptsPtr, nil) From 1cb2b9e8ea752dd35716233cce65e1b3bd1c2f23 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 17 Jun 2022 16:58:27 -0500 Subject: [PATCH 60/61] Charge L1 gas for eth_calls --- arbos/tx_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 9f22ca71e3..6cb98fe804 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -256,7 +256,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { gasPrice := p.evm.Context.BaseFee var poster common.Address - if p.msg.RunMode() == types.MessageGasEstimationMode { + if p.msg.RunMode() != types.MessageCommitMode { poster = l1pricing.BatchPosterAddress } else { poster = p.evm.Context.Coinbase From d54f53e8fd9bf681eb0077e344ba56abb231ff74 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 17 Jun 2022 18:27:21 -0500 Subject: [PATCH 61/61] Fix one step proof tests with inbox changes --- contracts/test/prover/one-step-proof.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/test/prover/one-step-proof.ts b/contracts/test/prover/one-step-proof.ts index 91cf3065d7..c87011c59f 100644 --- a/contracts/test/prover/one-step-proof.ts +++ b/contracts/test/prover/one-step-proof.ts @@ -15,7 +15,8 @@ async function sendTestMessages() { const path = msgRoot + "msg" + String(msgNum) + ".bin"; const buf = fs.readFileSync(path); await inbox.sendL2MessageFromOrigin(buf, gasOpts); - await seqInbox.addSequencerL2BatchFromOrigin(msgNum, buf, 0, ethers.constants.AddressZero, gasOpts); + // Don't use the FromOrigin variant as the stub will fail to create a batch posting report + await seqInbox.addSequencerL2Batch(msgNum, buf, 0, ethers.constants.AddressZero, gasOpts); } }