Skip to content

Commit

Permalink
support fork updates and read initial fork from l1 (#707)
Browse files Browse the repository at this point in the history
* support fork updates and read initial fork from l1

* ci(kurtosis): remove sequencer-initial-fork-id

---------

Co-authored-by: Max Revitt <[email protected]>
  • Loading branch information
hexoscott and revitteth authored Jul 4, 2024
1 parent 7fa1b89 commit ff361bd
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 55 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci_zkevm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
tests:
strategy:
matrix:
os: [ ubuntu-20.04, macos-14 ] # list of os: https://github.com/actions/virtual-environments
os: [ ubuntu-20.04, macos-13 ] # list of os: https://github.com/actions/virtual-environments
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ matrix.os == 'macos-14' && 40 || 30 }}

Expand Down Expand Up @@ -96,6 +96,7 @@ jobs:
/usr/local/bin/yq -i '.args.data_availability_mode = "rollup"' cdk-erigon-sequencer-params.yml
/usr/local/bin/yq -i '.args.cdk_erigon_node_image = "cdk-erigon:local"' cdk-erigon-sequencer-params.yml
/usr/local/bin/yq -i '.args.zkevm_bridge_service_image = "hermeznetwork/zkevm-bridge-service:v0.5.0-RC8"' cdk-erigon-sequencer-params.yml
sed -i '/zkevm\.sequencer-initial-fork-id/d' ./templates/cdk-erigon/config-sequencer.yaml
- name: Deploy Kurtosis CDK package
working-directory: ./kurtosis-cdk
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ Sequencer specific config:
- `zkevm.executor-urls`: A csv list of the executor URLs. These will be used in a round robbin fashion by the sequencer
- `zkevm.executor-strict`: Defaulted to true, but can be set to false when running the sequencer without verifications (use with extreme caution)
- `zkevm.witness-full`: Defaulted to true. Controls whether the full or partial witness is used with the executor.
- `zkevm.sequencer-initial-fork-id`: The fork id to start the network with.

Resource Utilisation config:
- `zkevm.smt-regenerate-in-memory`: As documented above, allows SMT regeneration in memory if machine has enough RAM, for a speedup in initial sync.
Expand Down
5 changes: 0 additions & 5 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,11 +461,6 @@ var (
Usage: "Regenerate the SMT in memory (requires a lot of RAM for most chains)",
Value: false,
}
SequencerInitialForkId = cli.Uint64Flag{
Name: "zkevm.sequencer-initial-fork-id",
Usage: "The initial fork id to launch the sequencer with",
Value: 8,
}
SequencerBlockSealTime = cli.StringFlag{
Name: "zkevm.sequencer-block-seal-time",
Usage: "Block seal time. Defaults to 6s",
Expand Down
15 changes: 11 additions & 4 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,8 +778,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
var l1Topics [][]libcommon.Hash
var l1Contracts []libcommon.Address
if isSequencer {
l1Topics = [][]libcommon.Hash{{contracts.InitialSequenceBatchesTopic}}
l1Contracts = []libcommon.Address{cfg.AddressZkevm}
l1Topics = [][]libcommon.Hash{{
contracts.InitialSequenceBatchesTopic,
contracts.AddNewRollupTypeTopic,
contracts.CreateNewRollupTopic,
contracts.UpdateRollupTopic,
}}
l1Contracts = []libcommon.Address{cfg.AddressZkevm, cfg.AddressRollup}
} else {
l1Topics = seqAndVerifTopics
l1Contracts = seqAndVerifL1Contracts
Expand Down Expand Up @@ -863,8 +868,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {

l1BlockSyncer := syncer.NewL1Syncer(
ethermanClients,
[]libcommon.Address{cfg.AddressZkevm},
[][]libcommon.Hash{{contracts.SequenceBatchesTopic}},
[]libcommon.Address{cfg.AddressZkevm, cfg.AddressRollup},
[][]libcommon.Hash{{
contracts.SequenceBatchesTopic,
}},
cfg.L1BlockRange,
cfg.L1QueryDelay,
cfg.L1HighestBlockType,
Expand Down
1 change: 0 additions & 1 deletion eth/ethconfig/config_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ type Zk struct {
L1FirstBlock uint64
RpcRateLimits int
DatastreamVersion int
SequencerInitialForkId uint64
SequencerBlockSealTime time.Duration
SequencerBatchSealTime time.Duration
SequencerNonEmptyBatchSealTime time.Duration
Expand Down
1 change: 0 additions & 1 deletion turbo/cli/default_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ var DefaultFlags = []cli.Flag{
&utils.RebuildTreeAfterFlag,
&utils.IncrementTreeAlways,
&utils.SmtRegenerateInMemory,
&utils.SequencerInitialForkId,
&utils.SequencerBlockSealTime,
&utils.SequencerBatchSealTime,
&utils.SequencerNonEmptyBatchSealTime,
Expand Down
2 changes: 0 additions & 2 deletions turbo/cli/flags_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ func ApplyFlagsForZkConfig(ctx *cli.Context, cfg *ethconfig.Config) {
RebuildTreeAfter: ctx.Uint64(utils.RebuildTreeAfterFlag.Name),
IncrementTreeAlways: ctx.Bool(utils.IncrementTreeAlways.Name),
SmtRegenerateInMemory: ctx.Bool(utils.SmtRegenerateInMemory.Name),
SequencerInitialForkId: ctx.Uint64(utils.SequencerInitialForkId.Name),
SequencerBlockSealTime: sequencerBlockSealTime,
SequencerBatchSealTime: sequencerBatchSealTime,
SequencerNonEmptyBatchSealTime: sequencerNonEmptyBatchSealTime,
Expand Down Expand Up @@ -162,7 +161,6 @@ func ApplyFlagsForZkConfig(ctx *cli.Context, cfg *ethconfig.Config) {
checkFlag(utils.L2RpcUrlFlag.Name, cfg.L2RpcUrl)
checkFlag(utils.L2DataStreamerUrlFlag.Name, cfg.L2DataStreamerUrl)
} else {
checkFlag(utils.SequencerInitialForkId.Name, cfg.SequencerInitialForkId)
checkFlag(utils.ExecutorUrls.Name, cfg.ExecutorUrls)
checkFlag(utils.ExecutorStrictMode.Name, cfg.ExecutorStrictMode)
checkFlag(utils.DataStreamHost.Name, cfg.DataStreamHost)
Expand Down
3 changes: 3 additions & 0 deletions zk/contracts/l1_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ var (
UpdateL1InfoTreeTopic = common.HexToHash("0xda61aa7823fcd807e37b95aabcbe17f03a6f3efd514176444dae191d27fd66b3")
InitialSequenceBatchesTopic = common.HexToHash("0x060116213bcbf54ca19fd649dc84b59ab2bbd200ab199770e4d923e222a28e7f")
SequenceBatchesTopic = common.HexToHash("0x3e54d0825ed78523037d00a81759237eb436ce774bd546993ee67a1b67b6e766")
AddNewRollupTypeTopic = common.HexToHash("0xa2970448b3bd66ba7e524e7b2a5b9cf94fa29e32488fb942afdfe70dd4b77b52")
CreateNewRollupTopic = common.HexToHash("0x194c983456df6701c6a50830b90fe80e72b823411d0d524970c9590dc277a641")
UpdateRollupTopic = common.HexToHash("0xf585e04c05d396901170247783d3e5f0ee9c1df23072985b50af089f5e48b19d")
)
73 changes: 73 additions & 0 deletions zk/hermez_db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const L1_INFO_LEAVES = "l1_info_leaves" // l1 inf
const L1_INFO_ROOTS = "l1_info_roots" // root hash -> l1 info tree index
const INVALID_BATCHES = "invalid_batches" // batch number -> true
const LOCAL_EXIT_ROOTS = "local_exit_roots" // l2 block number -> local exit root
const ROllUP_TYPES_FORKS = "rollup_types_forks" // rollup type id -> fork id
const FORK_HISTORY = "fork_history" // index -> fork id + last verified batch

type HermezDb struct {
tx kv.RwTx
Expand Down Expand Up @@ -102,6 +104,8 @@ func CreateHermezBuckets(tx kv.RwTx) error {
L1_INFO_ROOTS,
INVALID_BATCHES,
LOCAL_EXIT_ROOTS,
ROllUP_TYPES_FORKS,
FORK_HISTORY,
}
for _, t := range tables {
if err := tx.CreateBucket(t); err != nil {
Expand Down Expand Up @@ -1506,3 +1510,72 @@ func (db *HermezDbReader) GetLocalExitRootForBatchNo(batchNo uint64) (common.Has
}
return common.BytesToHash(v), nil
}

func (db *HermezDb) WriteRollupType(rollupType, forkId uint64) error {
return db.tx.Put(ROllUP_TYPES_FORKS, Uint64ToBytes(rollupType), Uint64ToBytes(forkId))
}

func (db *HermezDbReader) GetForkFromRollupType(rollupType uint64) (uint64, error) {
v, err := db.tx.GetOne(ROllUP_TYPES_FORKS, Uint64ToBytes(rollupType))
if err != nil {
return 0, err
}
return BytesToUint64(v), nil
}

func (db *HermezDb) WriteNewForkHistory(forkId, lastVerifiedBatch uint64) error {
cursor, err := db.tx.Cursor(FORK_HISTORY)
if err != nil {
return err
}
defer cursor.Close()
lastIndex, _, err := cursor.Last()
if err != nil {
return err
}
nextIndex := BytesToUint64(lastIndex) + 1
k := Uint64ToBytes(nextIndex)
forkBytes := Uint64ToBytes(forkId)
batchBytes := Uint64ToBytes(lastVerifiedBatch)
v := append(forkBytes, batchBytes...)
return db.tx.Put(FORK_HISTORY, k, v)
}

func (db *HermezDbReader) GetLatestForkHistory() (uint64, uint64, error) {
cursor, err := db.tx.Cursor(FORK_HISTORY)
if err != nil {
return 0, 0, err
}
defer cursor.Close()
_, v, err := cursor.Last()
if err != nil {
return 0, 0, err
}
if len(v) == 0 {
return 0, 0, nil
}
forkId := BytesToUint64(v[:8])
lastVerifiedBatch := BytesToUint64(v[8:])

return forkId, lastVerifiedBatch, nil
}

func (db *HermezDbReader) GetAllForkHistory() ([]uint64, []uint64, error) {
var forks, batches []uint64
cursor, err := db.tx.Cursor(FORK_HISTORY)
if err != nil {
return nil, nil, err
}
defer cursor.Close()
for k, v, err := cursor.First(); k != nil; k, v, err = cursor.Next() {
if err != nil {
return nil, nil, err
}
forkId := BytesToUint64(v[:8])
lastVerifiedBatch := BytesToUint64(v[8:])
forks = append(forks, forkId)
batches = append(batches, lastVerifiedBatch)
}

return forks, batches, nil
}
48 changes: 45 additions & 3 deletions zk/stages/stage_l1_sequencer_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ledgerwatch/erigon/zk/hermez_db"
"github.com/ledgerwatch/erigon/zk/types"
"github.com/ledgerwatch/log/v3"
"math/big"
)

type L1SequencerSyncCfg struct {
Expand Down Expand Up @@ -90,9 +91,50 @@ Loop:
if err := HandleInitialSequenceBatches(cfg.syncer, hermezDb, l, header); err != nil {
return err
}
// we only ever handle a single injected batch as a sequencer currently so we can just
// exit early here
break Loop
case contracts.AddNewRollupTypeTopic:
rollupType := l.Topics[1].Big().Uint64()
forkIdBytes := l.Data[64:96] // 3rd positioned item in the log data
forkId := new(big.Int).SetBytes(forkIdBytes).Uint64()
if err := hermezDb.WriteRollupType(rollupType, forkId); err != nil {
return err
}
case contracts.CreateNewRollupTopic:
rollupId := l.Topics[1].Big().Uint64()
if rollupId != cfg.zkCfg.L1RollupId {
return fmt.Errorf("received CreateNewRollupTopic for unknown rollup id: %v", rollupId)
}
rollupTypeBytes := l.Data[0:32]
rollupType := new(big.Int).SetBytes(rollupTypeBytes).Uint64()
fork, err := hermezDb.GetForkFromRollupType(rollupType)
if err != nil {
return err
}
if fork == 0 {
log.Error("received CreateNewRollupTopic for unknown rollup type", "rollupType", rollupType)
}
if err := hermezDb.WriteNewForkHistory(fork, 0); err != nil {
return err
}
case contracts.UpdateRollupTopic:
rollupId := l.Topics[1].Big().Uint64()
if rollupId != cfg.zkCfg.L1RollupId {
log.Warn("received UpdateRollupTopic for unknown rollup id", "rollupId", rollupId)
continue
}
newRollupBytes := l.Data[0:32]
newRollup := new(big.Int).SetBytes(newRollupBytes).Uint64()
fork, err := hermezDb.GetForkFromRollupType(newRollup)
if err != nil {
return err
}
if fork == 0 {
return fmt.Errorf("received UpdateRollupTopic for unknown rollup type: %v", newRollup)
}
latestVerifiedBytes := l.Data[32:64]
latestVerified := new(big.Int).SetBytes(latestVerifiedBytes).Uint64()
if err := hermezDb.WriteNewForkHistory(fork, latestVerified); err != nil {
return err
}
default:
log.Warn("received unexpected topic from l1 sequencer sync stage", "topic", l.Topics[0])
}
Expand Down
5 changes: 3 additions & 2 deletions zk/stages/stage_sequence_execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func SpawnSequencingStage(

sdb := newStageDb(tx)

l1Recovery := cfg.zk.L1SyncStartBlock > 0

executionAt, err := s.ExecutionAt(tx)
if err != nil {
return err
Expand All @@ -61,7 +63,7 @@ func SpawnSequencingStage(
return err
}

forkId, err := prepareForkId(cfg, lastBatch, executionAt, sdb.hermezDb)
forkId, err := prepareForkId(lastBatch, executionAt, sdb.hermezDb)
if err != nil {
return err
}
Expand Down Expand Up @@ -123,7 +125,6 @@ func SpawnSequencingStage(
nonEmptyBatchTimer := time.NewTicker(cfg.zk.SequencerNonEmptyBatchSealTime)
defer nonEmptyBatchTimer.Stop()

l1Recovery := cfg.zk.L1SyncStartBlock > 0
hasAnyTransactionsInThisBatch := false
thisBatch := lastBatch + 1
batchCounters := vm.NewBatchCounterCollector(sdb.smt.GetDepth(), uint16(forkId), cfg.zk.VirtualCountersSmtReduction, cfg.zk.ShouldCountersBeUnlimited(l1Recovery))
Expand Down
2 changes: 1 addition & 1 deletion zk/stages/stage_sequence_execute_injected_batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func handleInjectedBatch(
parentBlock *types.Block,
forkId uint64,
) (*types.Transaction, *types.Receipt, uint8, error) {
decodedBlocks, err := zktx.DecodeBatchL2Blocks(injected.Transaction, cfg.zk.SequencerInitialForkId)
decodedBlocks, err := zktx.DecodeBatchL2Blocks(injected.Transaction, forkId)
if err != nil {
return nil, nil, 0, err
}
Expand Down
61 changes: 41 additions & 20 deletions zk/stages/stage_sequence_execute_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (

"math/big"

"errors"

"github.com/0xPolygonHermez/zkevm-data-streamer/datastreamer"
"github.com/ledgerwatch/erigon/chain"
"github.com/ledgerwatch/erigon/common/math"
Expand All @@ -38,6 +36,7 @@ import (
zktypes "github.com/ledgerwatch/erigon/zk/types"
"github.com/ledgerwatch/erigon/zk/utils"
"github.com/ledgerwatch/log/v3"
"fmt"
)

const (
Expand Down Expand Up @@ -185,30 +184,52 @@ type nextBatchL1Data struct {
LimitTimestamp uint64
}

func prepareForkId(cfg SequenceBlockCfg, lastBatch, executionAt uint64, hermezDb *hermez_db.HermezDb) (uint64, error) {
var forkId uint64 = 0
type forkDb interface {
GetAllForkHistory() ([]uint64, []uint64, error)
GetLatestForkHistory() (uint64, uint64, error)
GetForkId(batch uint64) (uint64, error)
WriteForkIdBlockOnce(forkId, block uint64) error
WriteForkId(batch, forkId uint64) error
}

func prepareForkId(lastBatch, executionAt uint64, hermezDb forkDb) (uint64, error) {
var err error
var latest uint64

if executionAt == 0 {
// capture the initial sequencer fork id for the first batch
forkId = cfg.zk.SequencerInitialForkId
if err := hermezDb.WriteForkId(1, forkId); err != nil {
return forkId, err
}
if err := hermezDb.WriteForkIdBlockOnce(uint64(forkId), 1); err != nil {
return forkId, err
}
} else {
forkId, err = hermezDb.GetForkId(lastBatch)
if err != nil {
return forkId, err
// get all history and find the fork appropriate for the batch we're processing now
allForks, allBatches, err := hermezDb.GetAllForkHistory()
if err != nil {
return 0, err
}

nextBatch := lastBatch + 1

// iterate over the batch boundaries and find the latest fork that applies
for idx, batch := range allBatches {
if nextBatch > batch {
latest = allForks[idx]
}
if forkId == 0 {
return forkId, errors.New("the network cannot have a 0 fork id")
}

if latest == 0 {
return 0, fmt.Errorf("could not find a suitable fork for batch %v, cannot start sequencer, check contract configuration", lastBatch+1)
}

// now we need to check the last batch to see if we need to update the fork id
lastBatchFork, err := hermezDb.GetForkId(lastBatch)
if err != nil {
return 0, err
}

// write the fork height once for the next block at the point of fork upgrade
if lastBatchFork < latest {
log.Info("Upgrading fork id", "from", lastBatchFork, "to", latest, "batch", nextBatch)
if err := hermezDb.WriteForkIdBlockOnce(latest, executionAt+1); err != nil {
return latest, err
}
}

return forkId, nil
return latest, nil
}

func prepareHeader(tx kv.RwTx, previousBlockNumber, deltaTimestamp, forcedTimestamp, forkId uint64, coinbase common.Address) (*types.Header, *types.Block, error) {
Expand Down
Loading

0 comments on commit ff361bd

Please sign in to comment.