Skip to content

Commit

Permalink
Fjord: Add FastLZ compression into L1CostFunc (#9618)
Browse files Browse the repository at this point in the history
* Add FastLZ for better L1Cost estimation

Co-authored-by: Michael de Hoog <[email protected]>
Co-authored-by: Danyal Prout <[email protected]>
Co-authored-by: Yukai Tu <[email protected]>
Co-authored-by: angel-ding-cb <[email protected]>

* fix all the tests

* fix: upate GPO network transactions to match spec

* Update GPO contracts

* update to 1d model / add tests

* update allocs and test framework to support new fjord contracts

* add fuzz testing

* increase minimum estimation to 100 / update circleci for e2e fuzz tests

* use linear regression for l1 gas used

* Add differential fastlz fuzzing between solady/cgo fastlz/geth fastlz

* Review feedback

* Bump geth

* fix: ensure we don't gc the data during fastlz compression

* Replace common.Hex2Bytes with common.FromHex

---------

Co-authored-by: Danyal Prout <[email protected]>
Co-authored-by: Yukai Tu <[email protected]>
Co-authored-by: angel-ding-cb <[email protected]>
Co-authored-by: Danyal Prout <[email protected]>
  • Loading branch information
5 people authored May 16, 2024
1 parent ae86d57 commit 9eb5f88
Show file tree
Hide file tree
Showing 26 changed files with 1,429 additions and 39 deletions.
6 changes: 6 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,12 @@ workflows:
on_changes: cannon,packages/contracts-bedrock/src/cannon
uses_artifacts: true
requires: ["go-mod-download", "pnpm-monorepo"]
- fuzz-golang:
name: op-e2e-fuzz
package_name: op-e2e
on_changes: op-e2e,packages/contracts-bedrock/src
uses_artifacts: true
requires: ["go-mod-download", "pnpm-monorepo"]
- go-test:
name: op-heartbeat-tests
module: op-heartbeat
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101315.1-rc.2
replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101315.1-rc.3

//replace github.com/ethereum/go-ethereum v1.13.9 => ../op-geth

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101315.1-rc.2 h1:uUrcs8fGrdDnVELB66GcMZRvwIeJow64DOtF+VFdAzY=
github.com/ethereum-optimism/op-geth v1.101315.1-rc.2/go.mod h1:VXVFzx1mr/JyJac5M4k5W/+0cqHZMkqKsIVDsOyj2rs=
github.com/ethereum-optimism/op-geth v1.101315.1-rc.3 h1:BvmzUehVSo7uuqtApy/h/A5uRDAuU2tJQLgHCWTxAUQ=
github.com/ethereum-optimism/op-geth v1.101315.1-rc.3/go.mod h1:VXVFzx1mr/JyJac5M4k5W/+0cqHZMkqKsIVDsOyj2rs=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240510200259-4be7024d2ba7 h1:e7oXWZwODAMM2TLo9beGDXaX2cCw7uM7qAqamYBHV40=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240510200259-4be7024d2ba7/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0=
github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY=
Expand Down
87 changes: 85 additions & 2 deletions op-bindings/bindings/gaspriceoracle.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion op-chain-ops/cmd/ecotone-scalar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func main() {
scalar = uint(decoded.BaseFeeScalar)
blobScalar = uint(decoded.BlobBaseFeeScalar)
} else {
encoded = eth.EncodeScalar(eth.EcostoneScalars{
encoded = eth.EncodeScalar(eth.EcotoneScalars{
BlobBaseFeeScalar: uint32(blobScalar),
BaseFeeScalar: uint32(scalar),
})
Expand Down
2 changes: 1 addition & 1 deletion op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ func (d *DeployConfig) FeeScalar() [32]byte {
if d.GasPriceOracleScalar != 0 {
return common.BigToHash(big.NewInt(int64(d.GasPriceOracleScalar)))
}
return eth.EncodeScalar(eth.EcostoneScalars{
return eth.EncodeScalar(eth.EcotoneScalars{
BlobBaseFeeScalar: d.GasPriceOracleBlobBaseFeeScalar,
BaseFeeScalar: d.GasPriceOracleBaseFeeScalar,
})
Expand Down
6 changes: 6 additions & 0 deletions op-e2e/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ clean:
rm -r ../.devnet
rm -r ../op-program/bin
.PHONY: clean

fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFjordCostFunction ./
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFastLzGethSolidity ./
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFastLzCgo ./

145 changes: 145 additions & 0 deletions op-e2e/actions/fjord_fork_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package actions

import (
"context"
"encoding/hex"
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)

var (
fjordGasPriceOracleCodeHash = common.HexToHash("0xa88fa50a2745b15e6794247614b5298483070661adacb8d32d716434ed24c6b2")
// https://basescan.org/tx/0x8debb2fe54200183fb8baa3c6dbd8e6ec2e4f7a4add87416cd60336b8326d16a
txHex = "02f875822105819b8405709fb884057d460082e97f94273ca93a52b817294830ed7572aa591ccfa647fd80881249c58b0021fb3fc080a05bb08ccfd68f83392e446dac64d88a2d28e7072c06502dfabc4a77e77b5c7913a05878d53dd4ebba4f6367e572d524dffcabeec3abb1d8725ee3ac5dc32e1852e3"
)

func TestFjordNetworkUpgradeTransactions(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
genesisBlock := hexutil.Uint64(0)
fjordOffset := hexutil.Uint64(2)

dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default

// Activate all forks at genesis, and schedule Fjord the block after
dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisFjordTimeOffset = &fjordOffset
require.NoError(t, dp.DeployConfig.Check(), "must have valid config")

sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
_, _, _, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
ethCl := engine.EthClient()

// start op-nodes
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)

// Get gas price from oracle
gasPriceOracle, err := bindings.NewGasPriceOracleCaller(predeploys.GasPriceOracleAddr, ethCl)
require.NoError(t, err)

// Get current implementations addresses (by slot) for L1Block + GasPriceOracle
initialGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, nil)
require.NoError(t, err)

sequencer.ActBuildL2ToFjord(t)

// get latest block
latestBlock, err := ethCl.BlockByNumber(context.Background(), nil)
require.NoError(t, err)
require.Equal(t, sequencer.L2Unsafe().Number, latestBlock.Number().Uint64())

transactions := latestBlock.Transactions()
// L1Block: 1 set-L1-info + 1 deploys + 1 upgradeTo + 1 enable fjord on GPO
// See [derive.FjordNetworkUpgradeTransactions]
require.Equal(t, 4, len(transactions))

// All transactions are successful
for i := 1; i < 4; i++ {
txn := transactions[i]
receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data")
}

expectedGasPriceOracleAddress := crypto.CreateAddress(derive.GasPriceOracleFjordDeployerAddress, 0)

// Gas Price Oracle Proxy is updated
updatedGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, latestBlock.Number())
require.NoError(t, err)
require.Equal(t, expectedGasPriceOracleAddress, common.BytesToAddress(updatedGasPriceOracleAddress))
require.NotEqualf(t, initialGasPriceOracleAddress, updatedGasPriceOracleAddress, "Gas Price Oracle Proxy address should have changed")
verifyCodeHashMatches(t, ethCl, expectedGasPriceOracleAddress, fjordGasPriceOracleCodeHash)

// Check that Fjord was activated
isFjord, err := gasPriceOracle.IsFjord(nil)
require.NoError(t, err)
require.True(t, isFjord)

// Check GetL1GasUsed is updated
txData, err := hex.DecodeString(txHex)
require.NoError(t, err)

gpoL1GasUsed, err := gasPriceOracle.GetL1GasUsed(&bind.CallOpts{}, txData)
require.NoError(t, err)
require.Equal(gt, uint64(1_888), gpoL1GasUsed.Uint64())

// Check that GetL1Fee takes into account fast LZ
gpoFee, err := gasPriceOracle.GetL1Fee(&bind.CallOpts{}, txData)
require.NoError(t, err)

gethFee := fjordL1Cost(t, gasPriceOracle, types.RollupCostData{
FastLzSize: uint64(types.FlzCompressLen(txData) + 68),
})
require.Equal(t, gethFee.Uint64(), gpoFee.Uint64())

// Check that L1FeeUpperBound works
upperBound, err := gasPriceOracle.GetL1FeeUpperBound(&bind.CallOpts{}, big.NewInt(int64(len(txData))))
require.NoError(t, err)

txLen := len(txData) + 68
flzUpperBound := uint64(txLen + txLen/255 + 16)

upperBoundCost := fjordL1Cost(t, gasPriceOracle, types.RollupCostData{FastLzSize: flzUpperBound})
require.Equal(t, upperBoundCost.Uint64(), upperBound.Uint64())
}

func fjordL1Cost(t require.TestingT, gasPriceOracle *bindings.GasPriceOracleCaller, rollupCostData types.RollupCostData) *big.Int {
baseFeeScalar, err := gasPriceOracle.BaseFeeScalar(nil)
require.NoError(t, err)
l1BaseFee, err := gasPriceOracle.L1BaseFee(nil)
require.NoError(t, err)
blobBaseFeeScalar, err := gasPriceOracle.BlobBaseFeeScalar(nil)
require.NoError(t, err)
blobBaseFee, err := gasPriceOracle.BlobBaseFee(nil)
require.NoError(t, err)

costFunc := types.NewL1CostFuncFjord(
l1BaseFee,
blobBaseFee,
new(big.Int).SetUint64(uint64(baseFeeScalar)),
new(big.Int).SetUint64(uint64(blobBaseFeeScalar)))

fee, _ := costFunc(rollupCostData)
return fee
}
7 changes: 7 additions & 0 deletions op-e2e/actions/l2_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,10 @@ func (s *L2Sequencer) ActBuildL2ToEcotone(t Testing) {
s.ActL2EndBlock(t)
}
}
func (s *L2Sequencer) ActBuildL2ToFjord(t Testing) {
require.NotNil(t, s.rollupCfg.FjordTime, "cannot activate FjordTime when it is not scheduled")
for s.L2Unsafe().Time < *s.rollupCfg.FjordTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
}
}
2 changes: 1 addition & 1 deletion op-e2e/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (eec *ExternalEthClient) Close() error {
return nil
}

func (er *ExternalRunner) Run(t *testing.T) *ExternalEthClient {
func (er *ExternalRunner) Run(t testing.TB) *ExternalEthClient {
if er.BinPath == "" {
t.Error("no external bin path set")
}
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/external/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type TestParms struct {
SkipTests map[string]string `json:"skip_tests"`
}

func (tp TestParms) SkipIfNecessary(t *testing.T) {
func (tp TestParms) SkipIfNecessary(t testing.TB) {
if len(tp.SkipTests) == 0 {
return
}
Expand Down
Loading

0 comments on commit 9eb5f88

Please sign in to comment.