Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 38 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -315,15 +315,15 @@ run-follower-compare-local:
CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries \
PR_EXIT_AFTER_GENESIS=false \
PR_IGNORE_CALLSTACK=false \
PR_NETH_RPC_CLIENT_URL=http://localhost:20545 \
PR_NETH_WS_CLIENT_URL=ws://localhost:28551 \
PR_OVERRIDE_FORWARDER_URL=ws://localhost:8548 \
PR_EXECUTION_MODE=compare \
target/bin/nitro \
--persistent.global-config /tmp/sequencer_follower \
--ipc.path /tmp/dev-test/geth.ipc \
--conf.file ../arbitrum-nitro-testnode/data/config/sequencer_follower_config_native.json \
--node.seq-coordinator.my-url ws://follower:8548 \
--execution.nethermind-url http://localhost:20545 \
--execution.nethermind-ws-url ws://localhost:28551 \
--execution.execution-mode compare \
--http.port 7547 \
--ws.port 7548

Expand All @@ -340,14 +340,14 @@ run-follower-compare-sepolia:
CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries \
PR_EXIT_AFTER_GENESIS=false \
PR_IGNORE_CALLSTACK=false \
PR_NETH_RPC_CLIENT_URL=http://localhost:20545 \
PR_NETH_WS_CLIENT_URL=ws://localhost:28551 \
PR_EXECUTION_MODE=compare \
target/bin/nitro \
--persistent.global-config /tmp/sequencer_follower \
--parent-chain.connection.url=http://209.127.228.66/rpc/$$SEPOLIA_RPC_API_KEY \
--parent-chain.blob-client.beacon-url=http://209.127.228.66/consensus/$$SEPOLIA_RPC_API_KEY \
--chain.id=421614 \
--execution.nethermind-url http://localhost:20545 \
--execution.nethermind-ws-url ws://localhost:28551 \
--execution.execution-mode compare \
--execution.forwarding-target null \
--execution.enable-prefetch-block=false \
--http.addr=0.0.0.0 \
Expand All @@ -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:
@echo "Starting Nitro sequencer follower (Arbitrum One with Nethermind, external-only mode)..."
CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries \
PR_IGNORE_CALLSTACK=false \
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.start-block=22207817 \
--execution.nethermind-url=http://localhost:20545 \
--execution.nethermind-ws-url ws://localhost:28551 \
--execution.execution-mode=external \
--validation.wasm.allowed-wasm-module-roots=0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39 \
--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 Expand Up @@ -388,14 +417,14 @@ run-sequencer-nethermind: clean-sequencer-nethermind
@echo "Ensure Nethermind is running at http://localhost:20545"
CGO_LDFLAGS=-Wl,-no_warn_duplicate_libraries \
PR_EXIT_AFTER_GENESIS=false PR_IGNORE_CALLSTACK=false \
PR_EXECUTION_MODE=compare \
PR_NETH_RPC_CLIENT_URL=http://localhost:20545 \
PR_NETH_WS_CLIENT_URL=ws://localhost:28551 \
target/bin/nitro \
--persistent.global-config /tmp/sequencer_neth \
--ipc.path /tmp/dev-test/geth.ipc \
--conf.file ../arbitrum-nitro-testnode/data/config/sequencer_config_native.json \
--node.feed.output.enable --node.feed.output.port 9642 \
--execution.nethermind-url http://localhost:20545 \
--execution.nethermind-ws-url ws://localhost:28551 \
--execution.execution-mode compare \
--http.api net,web3,eth,txpool,debug,timeboost,auctioneer \
--http.port 8547 --ws.port 8548

Expand Down
36 changes: 36 additions & 0 deletions cmd/conf/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ 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"`
StartBlock uint64 `koanf:"start-block"`
Comment thread
svlachakis marked this conversation as resolved.
}

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

BootstrapFromExecution: false,
StartBlock: 0,
}

func InitConfigAddOptions(prefix string, f *pflag.FlagSet) {
Expand Down Expand Up @@ -108,6 +113,9 @@ 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.Uint64(prefix+".start-block", InitConfigDefault.StartBlock, "block number to start from when bootstrapping from execution client (required when bootstrap-from-execution is true)")
}

func (c *InitConfig) Validate() error {
Expand Down Expand Up @@ -136,6 +144,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 {
// StartBlock == 0 is valid for chains with genesis at block 0, but bootstrap is typically
// used for starting at a later block. If starting from genesis (block 0), standard init
// methods should be used instead of bootstrap.
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) (*types.Block, error) {
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.Execution.NethermindUrl,
"startBlock", config.Init.StartBlock)

// Validate parameters
if config.Execution.NethermindUrl == "" {
return nil, nil, errors.New("--execution.nethermind-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.Execution.NethermindUrl,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think config at execution level should be more general - so ExternalUrl instead of NethermindUrl

config.Init.StartBlock)
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
16 changes: 10 additions & 6 deletions cmd/nitro/nitro.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,14 +434,14 @@ func mainImpl() int {
log.Info("enabling custom tracer", "name", traceConfig.TracerName)
}

execMode := nethexec.GetExecutionModeFromEnv()
execMode := nethexec.GetExecutionMode(&nodeConfig.Execution)

var initDigester nethexec.InitMessageDigester
switch execMode {
case nethexec.ModeInternalOnly:
initDigester = nethexec.NewFakeRemoteExecutionRpcClient()
case nethexec.ModeDualCompare, nethexec.ModeExternalOnly:
rpcClient, newClientErr := nethexec.NewNethRpcClient()
rpcClient, newClientErr := nethexec.NewNethRpcClient(nodeConfig.Execution.NethermindUrl, nodeConfig.Execution.NethermindWsUrl)
if newClientErr != nil {
log.Crit("failed to create real RPC client", "err", newClientErr)
return 1
Expand Down Expand Up @@ -564,19 +564,23 @@ func mainImpl() int {
case nethexec.ModeInternalOnly:
execNode = gethNode
case nethexec.ModeExternalOnly:
execNode, err = nethexec.NewNethermindExecutionClient()
execNode, err = nethexec.NewNethermindExecutionClient(
nodeConfig.Execution.NethermindUrl,
nodeConfig.Execution.NethermindWsUrl,
)
if err != nil {
log.Error("failed to create nethermind execution client", "err", err)
return 1
}
log.Info("Created nethermind execution client")
case nethexec.ModeDualCompare:
nmExec, err := nethexec.NewNethermindExecutionClient()
nmExec, err := nethexec.NewNethermindExecutionClient(
nodeConfig.Execution.NethermindUrl,
nodeConfig.Execution.NethermindWsUrl,
)
if err != nil {
log.Error("failed to create nethermind execution client", "err", err)
return 1
}
log.Info("Created nethermind execution client")
execNode = nethexec.NewCompareExecutionClient(gethNode, nmExec, fatalErrChan)
log.Info("Created compare execution client")
}
Expand Down
Loading