diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..6096bc4963 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +export L1_FORK_PUBLIC_NETWORK=true +export L1_RPC='https://eth-mainnet.g.alchemy.com/v2/' +export IMPL_SALT='tokamak network' +export L2_NATIVE_TOKEN=0x2be5e8c109e2197D077D13A82dAead6a9b3433C5 diff --git a/Makefile b/Makefile index 7526c9be66..e2e1a6eb2b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ COMPOSEFLAGS=-d ITESTS_L2_HOST=http://localhost:9545 BEDROCK_TAGS_REMOTE?=origin -OP_STACK_GO_BUILDER?=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +OP_STACK_GO_BUILDER?=onthertech/titan-op-stack-go:latest # Requires at least Python v3.9; specify a minor version below if needed PYTHON?=python3 @@ -105,7 +105,7 @@ nuke: clean devnet-clean .PHONY: nuke pre-devnet: - @if [ "${FORK_PUBLIC_NETWORK}" != "true" ] && ! [ -x "$(command -v geth)" ]; then \ + @if ! [ -x "$(command -v geth)" ]; then \ make install-geth; \ fi @if [ ! -e op-program/bin ]; then \ diff --git a/bedrock-devnet/devnet/__init__.py b/bedrock-devnet/devnet/__init__.py index 050ee20529..c3895af8c6 100644 --- a/bedrock-devnet/devnet/__init__.py +++ b/bedrock-devnet/devnet/__init__.py @@ -10,11 +10,12 @@ import http.client import gzip import glob +import ssl +import urllib from multiprocessing import Process, Queue import concurrent.futures from collections import namedtuple -import devnet.log_setup pjoin = os.path.join @@ -25,9 +26,9 @@ parser.add_argument('--fork-public-network', help='Fork the public network', type=bool, - default=os.environ.get('FORK_PUBLIC_NETWORK').lower() == 'true' if os.environ.get('FORK_PUBLIC_NETWORK') else False) + default=os.environ.get('L1_FORK_PUBLIC_NETWORK').lower() == 'true' if os.environ.get('L1_FORK_PUBLIC_NETWORK') else False) parser.add_argument('--l1-rpc-url', help='Public L1 RPC URL', type=str, default=os.environ.get('L1_RPC')) -parser.add_argument('--from-block-number', help='From block number', type=int, default=os.environ.get('FROM_BLOCK_NUMBER')) +parser.add_argument('--block-number', help='From block number', type=int, default=os.environ.get('BLOCK_NUMBER')) parser.add_argument('--l2-native-token', help='L2 native token', type=str, default=os.environ.get('L2_NATIVE_TOKEN')) parser.add_argument('--admin-key', help='The admin private key for upgrade contracts', type=str, default=os.environ.get('DEVNET_ADMIN_PRIVATE_KEY')) parser.add_argument('--l2-image', help='Using local l2', type=str, default=os.environ.get('L2_IMAGE') if os.environ.get('L2_IMAGE') is not None else 'onthertech/titan-op-geth:nightly') @@ -77,6 +78,15 @@ def main(): sdk_dir = pjoin(monorepo_dir, 'packages', 'tokamak', 'sdk') bedrock_devnet_dir = pjoin(monorepo_dir, 'bedrock-devnet') + if args.fork_public_network: + if args.block_number is not None: + block_number = str(args.block_number) + else: + response = json.loads(eth_new_head(args.l1_rpc_url)) + block_number = str(int(response['result'], 16)) + else: + block_number = "0" + paths = Bunch( mono_repo_dir=monorepo_dir, devnet_dir=devnet_dir, @@ -96,10 +106,10 @@ def main(): addresses_json_path=pjoin(devnet_dir, 'addresses.json'), sdk_addresses_json_path=pjoin(devnet_dir, 'sdk-addresses.json'), rollup_config_path=pjoin(devnet_dir, 'rollup.json'), - fork_public_network = args.fork_public_network, - l1_rpc_url = args.l1_rpc_url, - l2_native_token = args.l2_native_token, - from_block_number = args.from_block_number, + fork_public_network=args.fork_public_network, + l1_rpc_url=args.l1_rpc_url, + block_number=block_number, + l2_native_token=args.l2_native_token, bedrock_devnet_path=bedrock_devnet_dir, admin_key=args.admin_key ) @@ -123,12 +133,16 @@ def main(): else: log.info(f'Building docker images for git commit {git_commit} ({git_date})') run_command(['docker', 'compose', 'build', '--progress', 'plain', - '--build-arg', f'GIT_COMMIT={git_commit}', '--build-arg', f'GIT_DATE={git_date}'], - cwd=paths.ops_bedrock_dir, env={ + '--build-arg', f'GIT_COMMIT={git_commit}', '--build-arg', f'GIT_DATE={git_date}'], + cwd=paths.ops_bedrock_dir, env={ 'PWD': paths.ops_bedrock_dir, - 'L2_IMAGE': args.l2_image, 'DOCKER_BUILDKIT': '1', # (should be available by default in later versions, but explicitly enable it anyway) - 'COMPOSE_DOCKER_CLI_BUILD': '1' # use the docker cache + 'COMPOSE_DOCKER_CLI_BUILD': '1', # use the docker cache + 'L2_IMAGE': args.l2_image, + 'L1_DOCKER_FILE': 'Dockerfile.l1.fork' if paths.fork_public_network else 'Dockerfile.l1', + 'L1_RPC': paths.l1_rpc_url if paths.fork_public_network else '', + 'BLOCK_NUMBER': paths.block_number, + 'L1_FORK_PUBLIC_NETWORK': str(paths.fork_public_network) }) log.info('Devnet starting') @@ -143,24 +157,20 @@ def deploy_contracts(paths): account = response['result'][0 if paths.admin_key is None else 1] log.info(f'Deploying with {account}') - # Proxy exists on the fork public network(used by anvil) - # We don't need to deploy the proxy contract - # https://book.getfoundry.sh/tutorials/create2-tutorial - if not paths.fork_public_network: - # send some ether to the create2 deployer account - cmd = [ - 'cast', 'send', - '--rpc-url', 'http://127.0.0.1:8545', - '--value', '1ether', '0x3fAB184622Dc19b6109349B94811493BF2a45362' - ] - cmd.extend(['--from', account, '--unlocked']) if paths.admin_key is None else cmd.extend(['--password', '1234']) - run_command(cmd, env={}, cwd=paths.contracts_bedrock_dir) - - # deploy the create2 deployer - run_command([ - 'cast', 'publish', '--rpc-url', 'http://127.0.0.1:8545', - '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222' - ], env={}, cwd=paths.contracts_bedrock_dir) + # send some ether to the create2 deployer account + cmd = [ + 'cast', 'send', + '--rpc-url', 'http://127.0.0.1:8545', + '--value', '1ether', '0x3fAB184622Dc19b6109349B94811493BF2a45362' + ] + cmd.extend(['--from', account, '--unlocked']) if paths.admin_key is None else cmd.extend(['--password', '1234']) + run_command(cmd, env={}, cwd=paths.contracts_bedrock_dir) + + # deploy the create2 deployer + run_command([ + 'cast', 'publish', '--rpc-url', 'http://127.0.0.1:8545', + '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222' + ], env={}, cwd=paths.contracts_bedrock_dir) fqn = 'scripts/Deploy.s.sol:Deploy' cmd = [ @@ -239,22 +249,14 @@ def devnet_l1_genesis(paths): log.info('Generating L1 genesis state') init_devnet_l1_deploy_config(paths) - if paths.fork_public_network: - log.info('Start to fork the public network. Wait to warm up the fork public network.') - geth = subprocess.Popen([ - 'anvil', '--fork-url', paths.l1_rpc_url, '--fork-block-number', str(paths.from_block_number), - '--chain-id', '1337' - ]) - time.sleep(30) - else: - if paths.admin_key is not None and not os.path.exists(pjoin(paths.bedrock_devnet_path, 'data')) : - init_admin_geth(paths) + if paths.admin_key is not None and not os.path.exists(pjoin(paths.bedrock_devnet_path, 'data')) : + init_admin_geth(paths) - geth = subprocess.Popen([ - 'geth', '--dev', '--dev.period', '2', '--http', '--http.api', 'eth,debug', - '--verbosity', '4', '--gcmode', 'archive', '--dev.gaslimit', '30000000', - '--rpc.allow-unprotected-txs' - ], cwd=pjoin(paths.mono_repo_dir, 'bedrock-devnet')) + geth = subprocess.Popen([ + 'geth', '--dev', '--dev.period', '2', '--http', '--http.api', 'eth,debug', + '--verbosity', '4', '--gcmode', 'archive', '--dev.gaslimit', '30000000', + '--rpc.allow-unprotected-txs' + ], cwd=pjoin(paths.mono_repo_dir, 'bedrock-devnet')) try: forge = ChildProcess(deploy_contracts, paths) @@ -264,13 +266,9 @@ def devnet_l1_genesis(paths): if err: raise Exception(f"Exception occurred in child process: {err}") - if paths.fork_public_network: - res = anvil_dumpState('127.0.0.1:8545') - allocs = convert_anvil_dump(res) - else: - res = debug_dumpBlock('127.0.0.1:8545') - response = json.loads(res) - allocs = response['result'] + res = debug_dumpBlock('127.0.0.1:8545') + response = json.loads(res) + allocs = response['result'] write_json(paths.allocs_path, allocs) finally: geth.terminate() @@ -303,6 +301,8 @@ def devnet_deploy(paths, args): log.info('Starting L1.') run_command(['docker', 'compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={ 'PWD': paths.ops_bedrock_dir, + 'L1_RPC': paths.l1_rpc_url if paths.fork_public_network else '', + 'BLOCK_NUMBER': paths.block_number, }) wait_up(8545) wait_for_rpc_server('127.0.0.1:8545') @@ -340,12 +340,18 @@ def devnet_deploy(paths, args): run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={ 'PWD': paths.ops_bedrock_dir, 'L2OO_ADDRESS': l2_output_oracle, - 'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address + 'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address, + 'L1_RPC': paths.l1_rpc_url if paths.fork_public_network else '', + 'BLOCK_NUMBER': paths.block_number, + 'WAITING_L1_PORT': '9999' if paths.fork_public_network else '8545', + 'L1_FORK_PUBLIC_NETWORK': str(paths.fork_public_network) }) log.info('Bringing up `artifact-server`') run_command(['docker', 'compose', 'up', '-d', 'artifact-server'], cwd=paths.ops_bedrock_dir, env={ - 'PWD': paths.ops_bedrock_dir + 'PWD': paths.ops_bedrock_dir, + 'L1_RPC': paths.l1_rpc_url if paths.fork_public_network else '', + 'BLOCK_NUMBER': paths.block_number, }) shutil.rmtree(pjoin(paths.bedrock_devnet_path, 'data'), ignore_errors=True) @@ -364,6 +370,29 @@ def eth_accounts(url): conn.close() return data +def eth_new_head(url): + parsed_url = urllib.parse.urlparse(url.strip()) + hostname = parsed_url.hostname + path = parsed_url.path if parsed_url.path else '/' + # Create a context that does not verify SSL certificates + context = ssl._create_unverified_context() + conn = http.client.HTTPSConnection(hostname, context=context) + headers = {'Content-type': 'application/json'} + body = '{"id":2, "jsonrpc":"2.0", "method": "eth_blockNumber", "params":[]}' + try: + conn.request('POST', path, body, headers) + response = conn.getresponse() + data = response.read().decode() + + except Exception as e: + log.error(f"Failed to fetch the latest block: {e}") + data = None + finally: + conn.close() + + return data + + def debug_dumpBlock(url): log.info(f'Fetch debug_dumpBlock {url}') conn = http.client.HTTPConnection(url) @@ -503,7 +532,7 @@ def validate_fork_public_network(args): fork_public_network = args.fork_public_network l1_rpc_url = args.l1_rpc_url l2_native_token = args.l2_native_token - from_block_number = args.from_block_number + block_number = args.block_number # If fork the public network, validate the required params related to if fork_public_network: if not l1_rpc_url: @@ -512,43 +541,8 @@ def validate_fork_public_network(args): if not l2_native_token: raise Exception("Please provide the L2_NATIVE_TOKEN for the forked network.") + log.info(f'Fork from RPC URL: {l1_rpc_url}, block number: {block_number}, l2 native token: {l2_native_token}') - if not from_block_number: - raise Exception("Please provide the FROM_BLOCK_NUMBER for the forked network.") - - if from_block_number <= 0: - raise Exception("Please provide the FROM_BLOCK_NUMBER is bigger than zero.") - log.info(f'Fork from RPC URL: {l1_rpc_url}, from block number: {from_block_number}, l2 native token: {l2_native_token}') - - -def anvil_dumpState(url): - log.info(f'Fetch debug_dumpBlock {url}') - conn = http.client.HTTPConnection(url) - headers = {'Content-type': 'application/json'} - body = '{"id":3, "jsonrpc":"2.0", "method": "anvil_dumpState", "params":[]}' - conn.request('POST', '/', body, headers) - data = conn.getresponse().read() - # Anvil returns a JSON-RPC response with a hex-encoded "result" field - result = json.loads(data.decode('utf-8'))['result'] - result_bytes = bytes.fromhex(result[2:]) - uncompressed = gzip.decompress(result_bytes).decode() - return json.loads(uncompressed) - -def convert_anvil_dump(dump): - accounts = dump['accounts'] - - for account in accounts.values(): - bal = account['balance'] - account['balance'] = str(int(bal, 16)) - - if 'storage' in account: - storage = account['storage'] - storage_keys = list(storage.keys()) - for key in storage_keys: - value = storage[key] - del storage[key] - storage[pad_hex(key)] = pad_hex(value) - return dump def pad_hex(input): return '0x' + input.replace('0x', '').zfill(64) diff --git a/op-batcher/Dockerfile b/op-batcher/Dockerfile index 6732ecc656..17516000ba 100644 --- a/op-batcher/Dockerfile +++ b/op-batcher/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-challenger/Dockerfile b/op-challenger/Dockerfile index ad57e2b52b..2d27026f7c 100644 --- a/op-challenger/Dockerfile +++ b/op-challenger/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-heartbeat/Dockerfile b/op-heartbeat/Dockerfile index 2e3996cde6..4db7308707 100644 --- a/op-heartbeat/Dockerfile +++ b/op-heartbeat/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-node/Dockerfile b/op-node/Dockerfile index 0cd7f16d9c..44d86b2a5a 100644 --- a/op-node/Dockerfile +++ b/op-node/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index f5aa45d2d0..4981dd0e99 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -265,6 +265,13 @@ var ( EnvVars: prefixEnvVars("OVERRIDE_CANYON"), Hidden: false, } + IsForkPublicNetworkFlag = &cli.BoolFlag{ + Name: "l1.fork-public-network", + Usage: "Determine to use the fork public network or not", + EnvVars: prefixEnvVars("L1_FORK_PUBLIC_NETWORK"), + Required: false, + Hidden: true, + } ) var requiredFlags = []cli.Flag{ @@ -311,6 +318,7 @@ var optionalFlags = []cli.Flag{ RollupLoadProtocolVersions, CanyonOverrideFlag, L1RethDBPath, + IsForkPublicNetworkFlag, } // Flags contains the list of configuration options available to the binary. diff --git a/op-node/node/config.go b/op-node/node/config.go index cb970d5ebf..531d2fa882 100644 --- a/op-node/node/config.go +++ b/op-node/node/config.go @@ -63,6 +63,9 @@ type Config struct { // [OPTIONAL] The reth DB path to read receipts from RethDBPath string + + // [OPTIONAL] The flag determines whatever the network is the fork public network + IsForkPublicNetwork bool } type RPCConfig struct { diff --git a/op-node/node/node.go b/op-node/node/node.go index c2bcedb0b8..35c22ba906 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -159,6 +159,9 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { // Set the RethDB path in the EthClientConfig, if there is one configured. rpcCfg.EthClientConfig.RethDBPath = cfg.RethDBPath + // Set the flag in the EthClientConfig, if the network is the fork public network + rpcCfg.EthClientConfig.IsForkPublicNetwork = cfg.IsForkPublicNetwork + n.l1Source, err = sources.NewL1Client( client.NewInstrumentedRPC(l1Node, n.metrics), n.log, n.metrics.L1SourceCache, rpcCfg) if err != nil { diff --git a/op-node/service.go b/op-node/service.go index 64c564cc51..95dcd8fdfb 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -101,10 +101,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { Moniker: ctx.String(flags.HeartbeatMonikerFlag.Name), URL: ctx.String(flags.HeartbeatURLFlag.Name), }, - ConfigPersistence: configPersistence, - Sync: *syncConfig, - RollupHalt: haltOption, - RethDBPath: ctx.String(flags.L1RethDBPath.Name), + ConfigPersistence: configPersistence, + Sync: *syncConfig, + RollupHalt: haltOption, + RethDBPath: ctx.String(flags.L1RethDBPath.Name), + IsForkPublicNetwork: ctx.Bool(flags.IsForkPublicNetworkFlag.Name), } if err := cfg.LoadPersisted(log); err != nil { diff --git a/op-program/Dockerfile b/op-program/Dockerfile index 91688b5160..a119910714 100644 --- a/op-program/Dockerfile +++ b/op-program/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-proposer/Dockerfile b/op-proposer/Dockerfile index 8eb4b7cc0e..fc192c2fbe 100644 --- a/op-proposer/Dockerfile +++ b/op-proposer/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go diff --git a/op-service/sources/eth_client.go b/op-service/sources/eth_client.go index 7a54a5838f..1ce11ebfd2 100644 --- a/op-service/sources/eth_client.go +++ b/op-service/sources/eth_client.go @@ -65,6 +65,9 @@ type EthClientConfig struct { // [OPTIONAL] The reth DB path to fetch receipts from RethDBPath string + + // [OPTIONAL] The flag determines whatever the network is the fork public network + IsForkPublicNetwork bool } func (c *EthClientConfig) Check() error { @@ -138,6 +141,9 @@ type EthClient struct { // [OPTIONAL] The reth DB path to fetch receipts from rethDbPath string + + // [OPTIONAL] The flag determines whatever the network is the fork public network + isForkPublicNetwork bool } func (s *EthClient) PickReceiptsMethod(txCount uint64) ReceiptsFetchingMethod { @@ -186,6 +192,7 @@ func NewEthClient(client client.RPC, log log.Logger, metrics caching.Metrics, co lastMethodsReset: time.Now(), methodResetDuration: config.MethodResetDuration, rethDbPath: config.RethDBPath, + isForkPublicNetwork: config.IsForkPublicNetwork, }, nil } @@ -364,7 +371,7 @@ func (s *EthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (e job = v } else { txHashes := eth.TransactionsToHashes(txs) - job = NewReceiptsFetchingJob(s, s.client, s.maxBatchSize, eth.ToBlockID(info), info.ReceiptHash(), txHashes, s.rethDbPath) + job = NewReceiptsFetchingJob(s, s.client, s.maxBatchSize, eth.ToBlockID(info), info.ReceiptHash(), txHashes, s.rethDbPath, s.isForkPublicNetwork) s.receiptsCache.Add(blockHash, job) } receipts, err := job.Fetch(ctx) diff --git a/op-service/sources/receipts.go b/op-service/sources/receipts.go index f3f5205ee4..83302fdc65 100644 --- a/op-service/sources/receipts.go +++ b/op-service/sources/receipts.go @@ -397,18 +397,21 @@ type receiptsFetchingJob struct { rethDbPath string result types.Receipts + + isForkPublicNetwork bool } func NewReceiptsFetchingJob(requester ReceiptsRequester, client rpcClient, maxBatchSize int, block eth.BlockID, - receiptHash common.Hash, txHashes []common.Hash, rethDb string) *receiptsFetchingJob { + receiptHash common.Hash, txHashes []common.Hash, rethDb string, isForkPublicNetwork bool) *receiptsFetchingJob { return &receiptsFetchingJob{ - requester: requester, - client: client, - maxBatchSize: maxBatchSize, - block: block, - receiptHash: receiptHash, - txHashes: txHashes, - rethDbPath: rethDb, + requester: requester, + client: client, + maxBatchSize: maxBatchSize, + block: block, + receiptHash: receiptHash, + txHashes: txHashes, + rethDbPath: rethDb, + isForkPublicNetwork: isForkPublicNetwork, } } @@ -444,7 +447,10 @@ func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error { if err != nil { // errors if results are not available yet, should never happen. return err } - if err := validateReceipts(job.block, job.receiptHash, job.txHashes, result); err != nil { + // In the case we use the fork public network, anvil has the issue with the receipt when we fetch by the block hash + // So, in this case, we will ignore the error when validating the receipts we get from the l1 node by the block hash + // ref: https://github.com/foundry-rs/foundry/issues/3840 + if err := validateReceipts(job.block, job.receiptHash, job.txHashes, result); err != nil && !job.isForkPublicNetwork { job.fetcher.Reset() // if results are fetched but invalid, try restart all the fetching to try and get valid data. return err } @@ -502,7 +508,10 @@ func (job *receiptsFetchingJob) runAltMethod(ctx context.Context, m ReceiptsFetc job.requester.OnReceiptsMethodErr(m, err) return err } else { - if err := validateReceipts(job.block, job.receiptHash, job.txHashes, result); err != nil { + // In the case we use the fork public network, anvil has the issue with the receipt when we fetch by the block hash + // So, in this case, we will ignore the error when validating the receipts we get from the l1 node by the block hash + // ref: https://github.com/foundry-rs/foundry/issues/3840 + if err := validateReceipts(job.block, job.receiptHash, job.txHashes, result); err != nil && !job.isForkPublicNetwork { return err } job.result = result diff --git a/op-wheel/Dockerfile b/op-wheel/Dockerfile index 30f7a8a371..b495783c9f 100644 --- a/op-wheel/Dockerfile +++ b/op-wheel/Dockerfile @@ -1,4 +1,4 @@ -ARG OP_STACK_GO_BUILDER=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest +ARG OP_STACK_GO_BUILDER=onthertech/titan-op-stack-go:latest FROM $OP_STACK_GO_BUILDER as builder # See "make golang-docker" and /ops/docker/op-stack-go FROM alpine:3.18 diff --git a/ops-bedrock/Dockerfile.l1.fork b/ops-bedrock/Dockerfile.l1.fork new file mode 100644 index 0000000000..bc5c07eea9 --- /dev/null +++ b/ops-bedrock/Dockerfile.l1.fork @@ -0,0 +1,9 @@ +FROM ghcr.io/foundry-rs/foundry:nightly-293fad73670b7b59ca901c7f2105bf7a29165a90 + +RUN apk add --no-cache jq +RUN apk add --update python3 py3-pip + +COPY entrypoint-fork-l1.sh /entrypoint.sh +COPY fork-public-network.py /main.py + +ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] diff --git a/ops-bedrock/docker-compose.yml b/ops-bedrock/docker-compose.yml index fe00645981..1632960515 100644 --- a/ops-bedrock/docker-compose.yml +++ b/ops-bedrock/docker-compose.yml @@ -18,23 +18,26 @@ services: args: GIT_COMMIT: "dev" GIT_DATE: "0" - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:devnet + image: onthertech/titan-op-stack-go:${IMAGE_TAG:-latest} entrypoint: ["echo", "build complete"] l1: build: context: . - dockerfile: Dockerfile.l1 + dockerfile: ${L1_DOCKER_FILE:-Dockerfile.l1} ports: - "8545:8545" - "8546:8546" - "7060:6060" + - "9999:9999" volumes: - "l1_data:/db" - "${PWD}/../.devnet/genesis-l1.json:/genesis.json" - "${PWD}/test-jwt-secret.txt:/config/test-jwt-secret.txt" environment: GETH_MINER_RECOMMIT: 100ms + BLOCK_NUMBER: ${BLOCK_NUMBER} + L1_RPC: ${L1_RPC} l2: build: @@ -45,6 +48,7 @@ services: ports: - "9545:8545" - "8060:6060" + - "8551:8551" volumes: - "l2_data:/db" - "${PWD}/../.devnet/genesis-l2.json:/genesis.json" @@ -65,11 +69,13 @@ services: context: ../ dockerfile: ./op-node/Dockerfile args: - OP_STACK_GO_BUILDER: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:devnet - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:devnet + OP_STACK_GO_BUILDER: onthertech/titan-op-stack-go:${IMAGE_TAG:-latest} + image: onthertech/titan-op-node:${IMAGE_TAG:-latest} command: > + /bin/sh -c "while ! nc -z l1 ${WAITING_L1_PORT:-8545}; do sleep 1; done; echo 'Port is now available' && op-node - --l1=ws://l1:8546 + --l1=http://l1:8545 + --l1.fork-public-network=${L1_FORK_PUBLIC_NETWORK:-false} --l2=http://l2:8551 --l2.jwt-secret=/config/test-jwt-secret.txt --sequencer.enabled @@ -91,6 +97,7 @@ services: --metrics.port=7300 --pprof.enabled --rpc.enable-admin + --l1.trustrpc" ports: - "7545:8545" - "9003:9003" @@ -102,6 +109,9 @@ services: - "${PWD}/test-jwt-secret.txt:/config/test-jwt-secret.txt" - "${PWD}/../.devnet/rollup.json:/rollup.json" - op_log:/op_log + environment: + L1_FORK_PUBLIC_NETWORK: "${L1_FORK_PUBLIC_NETWORK}" + WAITING_L1_PORT: "${WAITING_L1_PORT}" op-proposer: depends_on: @@ -113,12 +123,14 @@ services: context: ../ dockerfile: ./op-proposer/Dockerfile args: - OP_STACK_GO_BUILDER: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:devnet - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:devnet + OP_STACK_GO_BUILDER: onthertech/titan-op-stack-go:${IMAGE_TAG:-latest} + image: onthertech/titan-op-proposer:${IMAGE_TAG:-latest} ports: - "6062:6060" - "7302:7300" - "6546:8545" + command: > + /bin/sh -c "while ! nc -z op-node 8545 ; do sleep 1; done; echo 'op-node is now available' && op-proposer" environment: OP_PROPOSER_L1_ETH_RPC: http://l1:8545 OP_PROPOSER_ROLLUP_RPC: http://op-node:8545 @@ -142,12 +154,14 @@ services: context: ../ dockerfile: ./op-batcher/Dockerfile args: - OP_STACK_GO_BUILDER: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:devnet - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:devnet + OP_STACK_GO_BUILDER: onthertech/titan-op-stack-go:${IMAGE_TAG:-latest} + image: onthertech/titan-op-batcher:${IMAGE_TAG:-latest} ports: - "6061:6060" - "7301:7300" - "6545:8545" + command: > + /bin/sh -c "while ! nc -z op-node 8545 ; do sleep 1; done; echo 'op-node is now available' && op-batcher" environment: OP_BATCHER_L1_ETH_RPC: http://l1:8545 OP_BATCHER_L2_ETH_RPC: http://l2:8545 diff --git a/ops-bedrock/entrypoint-fork-l1.sh b/ops-bedrock/entrypoint-fork-l1.sh new file mode 100644 index 0000000000..b8d9edc6c7 --- /dev/null +++ b/ops-bedrock/entrypoint-fork-l1.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -exu + +GENESIS_FILE_PATH="${GENESIS_FILE_PATH:-/genesis.json}" +CHAIN_ID=$(jq -r .config.chainId "$GENESIS_FILE_PATH") +GAS_LIMIT=$(jq -r .gasLimit "$GENESIS_FILE_PATH") +GAS_LIMIT_VALUE=$(echo "GAS_LIMIT" | awk '{ printf("%d", $0) }') + +RPC_PORT="${RPC_PORT:-8545}" +L1_RPC="${L1_RPC}" +BLOCK_NUMBER="${BLOCK_NUMBER}" + +exec anvil \ + --fork-url "$L1_RPC" \ + --fork-block-number "$BLOCK_NUMBER" \ + --host "0.0.0.0" \ + --port "$RPC_PORT" \ + --base-fee "1" \ + --block-time "12" \ + --gas-limit "$GAS_LIMIT_VALUE" \ + --chain-id "$CHAIN_ID" \ + "$@" & + +LONG_LIVED_PID=$! + +# Wait for the long-lived service to be ready +while ! nc -z localhost "$RPC_PORT"; do + sleep 1 +done +echo "Long-lived service is up and running." + +python3 main.py --l1-rpc http://localhost:${RPC_PORT} & + +wait $LONG_LIVED_PID diff --git a/ops-bedrock/fork-public-network.py b/ops-bedrock/fork-public-network.py new file mode 100644 index 0000000000..f37fe065f7 --- /dev/null +++ b/ops-bedrock/fork-public-network.py @@ -0,0 +1,136 @@ +import argparse +import http.client +import json +import urllib +from http.server import HTTPServer, BaseHTTPRequestHandler + +parser = argparse.ArgumentParser(description="Your script description") +parser.add_argument("--l1-rpc", type=str, required=True, help="L1 RPC") + +args = parser.parse_args() + +class Handler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write(b"OK") + +def health_check_l1(server_class=HTTPServer, handler_class=Handler, port=9999): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + httpd.serve_forever() + + +def read_data(filename): + # Open the file and read its content + with open(filename, 'r') as f: + data = json.load(f) + return data + +def make_req(url, payload): + try: + parsed_url = urllib.parse.urlparse(url) + conn = http.client.HTTPConnection(parsed_url.hostname, parsed_url.port) + + headers = { + 'Content-Type': 'application/json' + } + + # Send the request + conn.request("POST", parsed_url.path, payload, headers) + + # Get the response + response = conn.getresponse() + + # Check for successful response + if response.status == 200: + # Close the connection + conn.close() + return True + else: + print(f"Error: {response.status}") + # Close the connection + conn.close() + return False + except: + return False + + +def main(): + l1_rpc = args.l1_rpc + + print(f"Connected to RPC node: {l1_rpc}") + + data_path = "./genesis.json" + data = read_data(data_path) + + for addr, acc in data["alloc"].items(): + balance = acc.get("balance") + code = acc.get("code") + nonce = acc.get("nonce") + storage = acc.get("storage") + if balance is not None: + payload = json.dumps({ + "jsonrpc": "2.0", + "id": 1, + "method": "anvil_setBalance", + "params": [ + addr, + balance + ] + }) + is_success = make_req(l1_rpc, payload) + if not is_success: + print(f"failed to set balance, addr: {addr}, balance: {balance}") + + if code is not None: + payload = json.dumps({ + "jsonrpc": "2.0", + "id": 1, + "method": "anvil_setCode", + "params": [ + addr, + code + ] + }) + is_success = make_req(l1_rpc, payload) + if not is_success: + print(f"failed to set code, addr: {addr}, code: {code}") + + if nonce is not None: + payload = json.dumps({ + "jsonrpc": "2.0", + "id": 1, + "method": "anvil_setNonce", + "params": [ + addr, + nonce + ] + }) + is_success = make_req(l1_rpc, payload) + if not is_success: + print(f"failed to set nonce, addr: {addr}, nonce: {nonce}") + + if storage is not None: + for slot, val in storage.items(): + payload = json.dumps({ + "jsonrpc": "2.0", + "id": 1, + "method": "anvil_setStorageAt", + "params": [ + addr, + slot, + val + ] + }) + is_success = make_req(l1_rpc, payload) + if not is_success: + print(f"failed to set storage, addr: {addr}, slot: {slot}, val: {val}") + + health_check_l1() + + + + +if __name__ == "__main__": + main() diff --git a/packages/tokamak/contracts-bedrock/deploy-config/devnetL1-template.json b/packages/tokamak/contracts-bedrock/deploy-config/devnetL1-template.json index 86a6ba07c5..364e4da6c8 100644 --- a/packages/tokamak/contracts-bedrock/deploy-config/devnetL1-template.json +++ b/packages/tokamak/contracts-bedrock/deploy-config/devnetL1-template.json @@ -13,7 +13,7 @@ "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "cliqueSignerAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "l1UseClique": true, - "l1StartingBlockTag": "earliest", + "l1StartingBlockTag": "latest", "l2OutputOracleSubmissionInterval": 10, "l2OutputOracleStartingTimestamp": 0, "l2OutputOracleStartingBlockNumber": 0, diff --git a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol index a24f20c99d..6f3a005d1e 100644 --- a/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/tokamak/contracts-bedrock/scripts/Deploy.s.sol @@ -144,8 +144,16 @@ contract Deploy is Deployer { /// @notice Deploy the Safe function deployL2NativeToken() public onlyDevnet broadcast { + string memory path = string.concat(vm.projectRoot(), "/deploy-config/", deploymentContext, ".json"); + address setupAddr_ = vm.envOr("L2_NATIVE_TOKEN", address(0)); + // If L2NativeToken is already existing on the network, we don't deploy the new contract. + if (setupAddr_ != address(0)) { + cfg.setNativeTokenAddress(setupAddr_, path); + console.log("Native token deployed at", setupAddr_); + return; + } address addr_ = getL2NativeToken(); - cfg.setNativeTokenAddress(addr_, string.concat(vm.projectRoot(), "/deploy-config/", deploymentContext, ".json")); + cfg.setNativeTokenAddress(addr_, path); console.log("Native token deployed at", addr_); save("L2NativeToken", addr_); }