Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,40 @@ run-follower-compare-sepolia:
.PHONY: clean-run-follower-compare-sepolia
clean-run-follower-compare-sepolia: clean-follower run-follower-compare-sepolia

.PHONY: run-follower-compare-mainnet
run-follower-compare-mainnet: build-replay-env
Comment thread
svlachakis marked this conversation as resolved.
Outdated
@echo "Starting Nitro sequencer follower (Arbitrum One with Nethermind, external-only mode)..."
CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries \
PR_IGNORE_CALLSTACK=false \
PR_NETH_RPC_CLIENT_URL=http://localhost:20545 \
PR_EXECUTION_MODE=external \
target/bin/nitro \
--persistent.global-config /tmp/sequencer_follower_mainnet \
--parent-chain.connection.url=http://38.154.254.162:8545 \
--parent-chain.blob-client.beacon-url=http://38.154.254.162:4000 \
--chain.id=42161 \
--chain.name=arb1 \
--init.bootstrap-from-execution=true \
--init.execution-client-url=http://localhost:20545 \
--init.start-block=22207817 \
--validation.wasm.allowed-wasm-module-roots=0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39 \
Comment thread
hudem1 marked this conversation as resolved.
Outdated
--execution.forwarding-target=null \
--http.addr=0.0.0.0 \
--http.port=8747

.PHONY: clean-run-follower-compare-mainnet
clean-run-follower-compare-mainnet: clean-follower-mainnet run-follower-compare-mainnet

.PHONY: clean-follower
clean-follower:
@echo "Cleaning sequencer follower directory..."
@rm -rf /tmp/sequencer_follower

.PHONY: clean-follower-mainnet
clean-follower-mainnet:
@echo "Cleaning sequencer follower (Mainnet) directory..."
@rm -rf /tmp/sequencer_follower_mainnet

.PHONY: run-sequencer
run-sequencer: clean-sequencer
@echo "Starting Nitro sequencer..."
Expand Down
42 changes: 42 additions & 0 deletions cmd/conf/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type InitConfig struct {
ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"`
ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"`
ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"`
BootstrapFromExecution bool `koanf:"bootstrap-from-execution"`
ExecutionClientUrl string `koanf:"execution-client-url"`
Comment thread
svlachakis marked this conversation as resolved.
Outdated
StartBlock uint64 `koanf:"start-block"`
Comment thread
svlachakis marked this conversation as resolved.
ValidateBootstrap bool `koanf:"validate-bootstrap"`
}

var InitConfigDefault = InitConfig{
Expand Down Expand Up @@ -73,6 +77,11 @@ var InitConfigDefault = InitConfig{
ReorgToMessageBatch: -1,
ReorgToBlockBatch: -1,
ValidateGenesisAssertion: true,

BootstrapFromExecution: false,
ExecutionClientUrl: "",
StartBlock: 0,
ValidateBootstrap: false,
}

func InitConfigAddOptions(prefix string, f *pflag.FlagSet) {
Expand Down Expand Up @@ -108,6 +117,11 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) {
"\"false\"- do not rebuild on startup",
)
f.Bool(prefix+".validate-genesis-assertion", InitConfigDefault.ValidateGenesisAssertion, "tests genesis assertion posted on parent chain against the genesis block created on init")

f.Bool(prefix+".bootstrap-from-execution", InitConfigDefault.BootstrapFromExecution, "bootstrap chain database from execution client instead of downloading snapshot")
f.String(prefix+".execution-client-url", InitConfigDefault.ExecutionClientUrl, "execution client RPC URL for bootstrapping (required when bootstrap-from-execution is true)")
f.Uint64(prefix+".start-block", InitConfigDefault.StartBlock, "block number to start from when bootstrapping from execution client (required when bootstrap-from-execution is true)")
f.Bool(prefix+".validate-bootstrap", InitConfigDefault.ValidateBootstrap, "if true: validate bootstrap state root against L1 assertion")
}

func (c *InitConfig) Validate() error {
Expand Down Expand Up @@ -136,6 +150,34 @@ func (c *InitConfig) Validate() error {
if c.RebuildLocalWasm != "auto" && c.RebuildLocalWasm != "force" && c.RebuildLocalWasm != "false" {
return fmt.Errorf("invalid value of rebuild-local-wasm, want: auto or force or false, got: %s", c.RebuildLocalWasm)
}

if c.BootstrapFromExecution {
if c.ExecutionClientUrl == "" {
return fmt.Errorf("execution-client-url is required when bootstrap-from-execution is enabled")
}
if c.StartBlock == 0 {
return fmt.Errorf("start-block must be greater than 0 when bootstrap-from-execution is enabled")
Comment thread
hudem1 marked this conversation as resolved.
}
// Check for conflicting init methods
conflictingMethods := []bool{
c.Url != "",
c.Latest != "",
c.DevInit,
c.Empty,
c.ImportFile != "",
c.GenesisJsonFile != "",
}
conflictCount := 0
for _, conflict := range conflictingMethods {
if conflict {
conflictCount++
}
}
if conflictCount > 0 {
return fmt.Errorf("bootstrap-from-execution cannot be used with other init methods (url, latest, dev-init, empty, import-file, genesis-json-file)")
}
}

return nil
}

Expand Down
151 changes: 151 additions & 0 deletions cmd/nitro/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err
return file, err
}

func createGenesisFromExecution(ctx context.Context, nethermindURL string, blockNum uint64, chainConfig *params.ChainConfig) (*types.Block, error) {
Comment thread
svlachakis marked this conversation as resolved.
Outdated
log.Info("Retrieving block from execution client", "url", nethermindURL, "block", blockNum)

client, err := ethclient.Dial(nethermindURL)
if err != nil {
return nil, fmt.Errorf("failed to connect to execution client: %w", err)
}
defer client.Close()

block, err := client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNum))
if err != nil {
return nil, fmt.Errorf("failed to get block %d: %w", blockNum, err)
}

log.Info("Retrieved block from execution client",
"number", block.NumberU64(),
"hash", block.Hash().Hex(),
"stateRoot", block.Root().Hex())

return block, nil
}

func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string, checksum []byte) (string, error) {
grabclient := grab.NewClient()
printTicker := time.NewTicker(time.Second)
Expand Down Expand Up @@ -667,6 +689,135 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo
}
}

if config.Init.BootstrapFromExecution {
log.Info("Bootstrap mode: creating genesis from execution client",
"url", config.Init.ExecutionClientUrl,
"startBlock", config.Init.StartBlock)

// Validate parameters
if config.Init.ExecutionClientUrl == "" {
return nil, nil, errors.New("--init.execution-client-url required when --init.bootstrap-from-execution=true")
}
if config.Init.StartBlock == 0 {
Comment thread
svlachakis marked this conversation as resolved.
return nil, nil, errors.New("--init.start-block required when --init.bootstrap-from-execution=true")
}

// Check database is empty
if err := checkEmptyDatabaseDir(stack.InstanceDir(), config.Init.Force); err != nil {
return nil, nil, err
}

// Get chain config
chainConfig, err := chaininfo.GetChainConfig(chainId, config.Chain.Name,
0, config.Chain.InfoFiles, config.Chain.InfoJson)
if err != nil {
return nil, nil, fmt.Errorf("failed to get chain config: %w", err)
}

// Verify target block exists in Nethermind
targetBlock, err := createGenesisFromExecution(ctx, config.Init.ExecutionClientUrl,
config.Init.StartBlock, chainConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to get block from execution: %w", err)
}

// Open databases
chainData, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata",
config.Execution.Caching.DatabaseCache, config.Persistent.Handles,
config.Persistent.Ancient, "l2chaindata/", false,
persistentConfig.Pebble.ExtraOptions("l2chaindata"))
if err != nil {
return nil, nil, err
}

wasmDb, err := stack.OpenDatabaseWithExtraOptions("wasm",
config.Execution.Caching.DatabaseCache, config.Persistent.Handles,
"wasm/", false, persistentConfig.Pebble.ExtraOptions("wasm"))
if err != nil {
return nil, nil, err
}

if err := validateOrUpgradeWasmStoreSchemaVersion(wasmDb); err != nil {
return nil, nil, err
}

chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb)

_, err = rawdb.ParseStateScheme(cacheConfig.StateScheme, chainDb)
if err != nil {
return nil, nil, err
}

// Create minimal ArbOS initialization at block 0
initData := &statetransfer.ArbosInitializationInfo{
NextBlockNumber: 0,
Accounts: []statetransfer.AccountInitializationInfo{},
}
initDataReader := statetransfer.NewMemoryInitDataReader(initData)

// Serialize chain config for ArbOS
serializedChainConfig, err := json.Marshal(chainConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to serialize chain config: %w", err)
}

parsedInitMessage := &arbostypes.ParsedInitMessage{
ChainId: chainConfig.ChainID,
InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee,
ChainConfig: chainConfig,
SerializedChainConfig: serializedChainConfig,
}

log.Info("Initializing ArbOS at genesis")

// Use Arbitrum's initialization to create genesis with ArbOS state
l2BlockChain, err := gethexec.WriteOrTestBlockChain(
chainDb,
cacheConfig,
initDataReader,
chainConfig,
nil,
tracer,
parsedInitMessage,
&config.Execution.TxIndexer,
100000,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to initialize blockchain: %w", err)
}

log.Info("ArbOS genesis created",
"number", l2BlockChain.Genesis().NumberU64(),
"hash", l2BlockChain.Genesis().Hash().Hex())

// Now store the bootstrap block from Nethermind and update head
log.Info("Storing bootstrap block from Nethermind", "block", config.Init.StartBlock)

rawdb.WriteHeader(chainDb, targetBlock.Header())
Comment thread
damian-orzechowski marked this conversation as resolved.
rawdb.WriteBody(chainDb, targetBlock.Hash(), config.Init.StartBlock, targetBlock.Body())
rawdb.WriteReceipts(chainDb, targetBlock.Hash(), config.Init.StartBlock, types.Receipts{})
rawdb.WriteCanonicalHash(chainDb, targetBlock.Hash(), config.Init.StartBlock)
rawdb.WriteHeaderNumber(chainDb, targetBlock.Hash(), config.Init.StartBlock)

// Update head pointers
rawdb.WriteHeadHeaderHash(chainDb, targetBlock.Hash())
rawdb.WriteHeadBlockHash(chainDb, targetBlock.Hash())
rawdb.WriteHeadFastBlockHash(chainDb, targetBlock.Hash())

// Skip wasm rebuilding
err = gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingPositionKey,
gethexec.RebuildingDone)
if err != nil {
return nil, nil, fmt.Errorf("failed to set wasm rebuilding done: %w", err)
}

log.Info("Bootstrap complete - Nitro will sync from bootstrap block",
"genesisBlock", 0,
"headBlock", config.Init.StartBlock)

return chainDb, l2BlockChain, nil
}

// Check if database was misplaced in parent dir
const errorFmt = "database was not found in %s, but it was found in %s (have you placed the database in the wrong directory?)"
parentDir := filepath.Dir(stack.InstanceDir())
Expand Down
7 changes: 7 additions & 0 deletions execution/nethexec/nethrpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ func (c *nethRpcClient) Reorg(ctx context.Context, count arbutil.MessageIndex, n
return result, nil
}

func (c *nethRpcClient) Initialize(ctx context.Context, parsedInitMessage *arbostypes.ParsedInitMessage) error {
log.Info("Initialize called on Nethermind client - skipping as bootstrap already completed")
// When bootstrapping from execution client, initialization is already done
// We don't need to initialize Nethermind since it's already synced
return nil
}

func (c *nethRpcClient) SequenceDelayedMessage(ctx context.Context, message *arbostypes.L1IncomingMessage, delayedSeqNum uint64) error {
log.Debug("Making JSON-RPC call to SequenceDelayedMessage", "url", c.url, "delayedSeqNum", delayedSeqNum)
params := seqDelayedParams{DelayedSeqNum: delayedSeqNum, Message: message}
Expand Down