Skip to content

Commit 9eb5f88

Browse files
mdehoogdanyalproutyukaitu-cbangel-ding-cb
authored
Fjord: Add FastLZ compression into L1CostFunc (ethereum-optimism#9618)
* Add FastLZ for better L1Cost estimation Co-authored-by: Michael de Hoog <michael.dehoog@coinbase.com> Co-authored-by: Danyal Prout <danyal.prout@coinbase.com> Co-authored-by: Yukai Tu <yukai.tu@coinbase.com> Co-authored-by: angel-ding-cb <angel.ding@coinbase.com> * 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 <danyal.prout@coinbase.com> Co-authored-by: Yukai Tu <yukai.tu@coinbase.com> Co-authored-by: angel-ding-cb <angel.ding@coinbase.com> Co-authored-by: Danyal Prout <me@dany.al>
1 parent ae86d57 commit 9eb5f88

26 files changed

+1429
-39
lines changed

.circleci/config.yml

+6
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,12 @@ workflows:
17581758
on_changes: cannon,packages/contracts-bedrock/src/cannon
17591759
uses_artifacts: true
17601760
requires: ["go-mod-download", "pnpm-monorepo"]
1761+
- fuzz-golang:
1762+
name: op-e2e-fuzz
1763+
package_name: op-e2e
1764+
on_changes: op-e2e,packages/contracts-bedrock/src
1765+
uses_artifacts: true
1766+
requires: ["go-mod-download", "pnpm-monorepo"]
17611767
- go-test:
17621768
name: op-heartbeat-tests
17631769
module: op-heartbeat

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ require (
227227
rsc.io/tmplfunc v0.0.3 // indirect
228228
)
229229

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

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

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/
175175
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
176176
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
177177
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
178-
github.com/ethereum-optimism/op-geth v1.101315.1-rc.2 h1:uUrcs8fGrdDnVELB66GcMZRvwIeJow64DOtF+VFdAzY=
179-
github.com/ethereum-optimism/op-geth v1.101315.1-rc.2/go.mod h1:VXVFzx1mr/JyJac5M4k5W/+0cqHZMkqKsIVDsOyj2rs=
178+
github.com/ethereum-optimism/op-geth v1.101315.1-rc.3 h1:BvmzUehVSo7uuqtApy/h/A5uRDAuU2tJQLgHCWTxAUQ=
179+
github.com/ethereum-optimism/op-geth v1.101315.1-rc.3/go.mod h1:VXVFzx1mr/JyJac5M4k5W/+0cqHZMkqKsIVDsOyj2rs=
180180
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240510200259-4be7024d2ba7 h1:e7oXWZwODAMM2TLo9beGDXaX2cCw7uM7qAqamYBHV40=
181181
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240510200259-4be7024d2ba7/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0=
182182
github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY=

op-bindings/bindings/gaspriceoracle.go

+85-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

op-chain-ops/cmd/ecotone-scalar/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func main() {
5959
scalar = uint(decoded.BaseFeeScalar)
6060
blobScalar = uint(decoded.BlobBaseFeeScalar)
6161
} else {
62-
encoded = eth.EncodeScalar(eth.EcostoneScalars{
62+
encoded = eth.EncodeScalar(eth.EcotoneScalars{
6363
BlobBaseFeeScalar: uint32(blobScalar),
6464
BaseFeeScalar: uint32(scalar),
6565
})

op-chain-ops/genesis/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ func (d *DeployConfig) FeeScalar() [32]byte {
493493
if d.GasPriceOracleScalar != 0 {
494494
return common.BigToHash(big.NewInt(int64(d.GasPriceOracleScalar)))
495495
}
496-
return eth.EncodeScalar(eth.EcostoneScalars{
496+
return eth.EncodeScalar(eth.EcotoneScalars{
497497
BlobBaseFeeScalar: d.GasPriceOracleBlobBaseFeeScalar,
498498
BaseFeeScalar: d.GasPriceOracleBaseFeeScalar,
499499
})

op-e2e/Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ clean:
6464
rm -r ../.devnet
6565
rm -r ../op-program/bin
6666
.PHONY: clean
67+
68+
fuzz:
69+
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFjordCostFunction ./
70+
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFastLzGethSolidity ./
71+
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFastLzCgo ./
72+

op-e2e/actions/fjord_fork_test.go

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package actions
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"math/big"
7+
"testing"
8+
9+
"github.com/ethereum-optimism/optimism/op-service/predeploys"
10+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
11+
"github.com/ethereum/go-ethereum/common"
12+
"github.com/ethereum/go-ethereum/common/hexutil"
13+
"github.com/ethereum/go-ethereum/core/types"
14+
"github.com/ethereum/go-ethereum/crypto"
15+
"github.com/ethereum/go-ethereum/log"
16+
"github.com/stretchr/testify/require"
17+
18+
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
19+
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
20+
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
21+
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
22+
"github.com/ethereum-optimism/optimism/op-service/testlog"
23+
)
24+
25+
var (
26+
fjordGasPriceOracleCodeHash = common.HexToHash("0xa88fa50a2745b15e6794247614b5298483070661adacb8d32d716434ed24c6b2")
27+
// https://basescan.org/tx/0x8debb2fe54200183fb8baa3c6dbd8e6ec2e4f7a4add87416cd60336b8326d16a
28+
txHex = "02f875822105819b8405709fb884057d460082e97f94273ca93a52b817294830ed7572aa591ccfa647fd80881249c58b0021fb3fc080a05bb08ccfd68f83392e446dac64d88a2d28e7072c06502dfabc4a77e77b5c7913a05878d53dd4ebba4f6367e572d524dffcabeec3abb1d8725ee3ac5dc32e1852e3"
29+
)
30+
31+
func TestFjordNetworkUpgradeTransactions(gt *testing.T) {
32+
t := NewDefaultTesting(gt)
33+
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
34+
genesisBlock := hexutil.Uint64(0)
35+
fjordOffset := hexutil.Uint64(2)
36+
37+
dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default
38+
39+
// Activate all forks at genesis, and schedule Fjord the block after
40+
dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
41+
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
42+
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
43+
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock
44+
dp.DeployConfig.L2GenesisFjordTimeOffset = &fjordOffset
45+
require.NoError(t, dp.DeployConfig.Check(), "must have valid config")
46+
47+
sd := e2eutils.Setup(t, dp, defaultAlloc)
48+
log := testlog.Logger(t, log.LvlDebug)
49+
_, _, _, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
50+
ethCl := engine.EthClient()
51+
52+
// start op-nodes
53+
sequencer.ActL2PipelineFull(t)
54+
verifier.ActL2PipelineFull(t)
55+
56+
// Get gas price from oracle
57+
gasPriceOracle, err := bindings.NewGasPriceOracleCaller(predeploys.GasPriceOracleAddr, ethCl)
58+
require.NoError(t, err)
59+
60+
// Get current implementations addresses (by slot) for L1Block + GasPriceOracle
61+
initialGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, nil)
62+
require.NoError(t, err)
63+
64+
sequencer.ActBuildL2ToFjord(t)
65+
66+
// get latest block
67+
latestBlock, err := ethCl.BlockByNumber(context.Background(), nil)
68+
require.NoError(t, err)
69+
require.Equal(t, sequencer.L2Unsafe().Number, latestBlock.Number().Uint64())
70+
71+
transactions := latestBlock.Transactions()
72+
// L1Block: 1 set-L1-info + 1 deploys + 1 upgradeTo + 1 enable fjord on GPO
73+
// See [derive.FjordNetworkUpgradeTransactions]
74+
require.Equal(t, 4, len(transactions))
75+
76+
// All transactions are successful
77+
for i := 1; i < 4; i++ {
78+
txn := transactions[i]
79+
receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash())
80+
require.NoError(t, err)
81+
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
82+
require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data")
83+
}
84+
85+
expectedGasPriceOracleAddress := crypto.CreateAddress(derive.GasPriceOracleFjordDeployerAddress, 0)
86+
87+
// Gas Price Oracle Proxy is updated
88+
updatedGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, latestBlock.Number())
89+
require.NoError(t, err)
90+
require.Equal(t, expectedGasPriceOracleAddress, common.BytesToAddress(updatedGasPriceOracleAddress))
91+
require.NotEqualf(t, initialGasPriceOracleAddress, updatedGasPriceOracleAddress, "Gas Price Oracle Proxy address should have changed")
92+
verifyCodeHashMatches(t, ethCl, expectedGasPriceOracleAddress, fjordGasPriceOracleCodeHash)
93+
94+
// Check that Fjord was activated
95+
isFjord, err := gasPriceOracle.IsFjord(nil)
96+
require.NoError(t, err)
97+
require.True(t, isFjord)
98+
99+
// Check GetL1GasUsed is updated
100+
txData, err := hex.DecodeString(txHex)
101+
require.NoError(t, err)
102+
103+
gpoL1GasUsed, err := gasPriceOracle.GetL1GasUsed(&bind.CallOpts{}, txData)
104+
require.NoError(t, err)
105+
require.Equal(gt, uint64(1_888), gpoL1GasUsed.Uint64())
106+
107+
// Check that GetL1Fee takes into account fast LZ
108+
gpoFee, err := gasPriceOracle.GetL1Fee(&bind.CallOpts{}, txData)
109+
require.NoError(t, err)
110+
111+
gethFee := fjordL1Cost(t, gasPriceOracle, types.RollupCostData{
112+
FastLzSize: uint64(types.FlzCompressLen(txData) + 68),
113+
})
114+
require.Equal(t, gethFee.Uint64(), gpoFee.Uint64())
115+
116+
// Check that L1FeeUpperBound works
117+
upperBound, err := gasPriceOracle.GetL1FeeUpperBound(&bind.CallOpts{}, big.NewInt(int64(len(txData))))
118+
require.NoError(t, err)
119+
120+
txLen := len(txData) + 68
121+
flzUpperBound := uint64(txLen + txLen/255 + 16)
122+
123+
upperBoundCost := fjordL1Cost(t, gasPriceOracle, types.RollupCostData{FastLzSize: flzUpperBound})
124+
require.Equal(t, upperBoundCost.Uint64(), upperBound.Uint64())
125+
}
126+
127+
func fjordL1Cost(t require.TestingT, gasPriceOracle *bindings.GasPriceOracleCaller, rollupCostData types.RollupCostData) *big.Int {
128+
baseFeeScalar, err := gasPriceOracle.BaseFeeScalar(nil)
129+
require.NoError(t, err)
130+
l1BaseFee, err := gasPriceOracle.L1BaseFee(nil)
131+
require.NoError(t, err)
132+
blobBaseFeeScalar, err := gasPriceOracle.BlobBaseFeeScalar(nil)
133+
require.NoError(t, err)
134+
blobBaseFee, err := gasPriceOracle.BlobBaseFee(nil)
135+
require.NoError(t, err)
136+
137+
costFunc := types.NewL1CostFuncFjord(
138+
l1BaseFee,
139+
blobBaseFee,
140+
new(big.Int).SetUint64(uint64(baseFeeScalar)),
141+
new(big.Int).SetUint64(uint64(blobBaseFeeScalar)))
142+
143+
fee, _ := costFunc(rollupCostData)
144+
return fee
145+
}

op-e2e/actions/l2_sequencer.go

+7
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,10 @@ func (s *L2Sequencer) ActBuildL2ToEcotone(t Testing) {
175175
s.ActL2EndBlock(t)
176176
}
177177
}
178+
func (s *L2Sequencer) ActBuildL2ToFjord(t Testing) {
179+
require.NotNil(t, s.rollupCfg.FjordTime, "cannot activate FjordTime when it is not scheduled")
180+
for s.L2Unsafe().Time < *s.rollupCfg.FjordTime {
181+
s.ActL2StartBlock(t)
182+
s.ActL2EndBlock(t)
183+
}
184+
}

op-e2e/external.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (eec *ExternalEthClient) Close() error {
6565
return nil
6666
}
6767

68-
func (er *ExternalRunner) Run(t *testing.T) *ExternalEthClient {
68+
func (er *ExternalRunner) Run(t testing.TB) *ExternalEthClient {
6969
if er.BinPath == "" {
7070
t.Error("no external bin path set")
7171
}

op-e2e/external/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type TestParms struct {
5454
SkipTests map[string]string `json:"skip_tests"`
5555
}
5656

57-
func (tp TestParms) SkipIfNecessary(t *testing.T) {
57+
func (tp TestParms) SkipIfNecessary(t testing.TB) {
5858
if len(tp.SkipTests) == 0 {
5959
return
6060
}

0 commit comments

Comments
 (0)