Commit 0a338da9 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into bindings.bridging.base.contracts

parents c118ba2f 6bbb5974
---
'@eth-optimism/chain-mon': minor
'@eth-optimism/core-utils': patch
---
Added a new service wallet-mon to identify unexpected transfers from key accounts
...@@ -74,11 +74,11 @@ nuke: clean devnet-clean ...@@ -74,11 +74,11 @@ nuke: clean devnet-clean
.PHONY: nuke .PHONY: nuke
devnet-up: devnet-up:
@bash ./ops-bedrock/devnet-up.sh PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=.
.PHONY: devnet-up .PHONY: devnet-up
devnet-up-deploy: devnet-up-deploy:
PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. --deploy
.PHONY: devnet-up-deploy .PHONY: devnet-up-deploy
devnet-down: devnet-down:
......
...@@ -4,74 +4,142 @@ import os ...@@ -4,74 +4,142 @@ import os
import subprocess import subprocess
import json import json
import socket import socket
import calendar
import datetime
import time import time
import shutil import shutil
import devnet.log_setup import devnet.log_setup
from devnet.genesis import GENESIS_TMPL from devnet.genesis import GENESIS_TMPL
pjoin = os.path.join
parser = argparse.ArgumentParser(description='Bedrock devnet launcher') parser = argparse.ArgumentParser(description='Bedrock devnet launcher')
parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd()) parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd())
parser.add_argument('--deploy', help='Whether the contracts should be predeployed or deployed', type=bool, action=argparse.BooleanOptionalAction)
log = logging.getLogger() log = logging.getLogger()
class Bunch:
def __init__(self, **kwds):
self.__dict__.update(kwds)
def main(): def main():
args = parser.parse_args() args = parser.parse_args()
pjoin = os.path.join
monorepo_dir = os.path.abspath(args.monorepo_dir) monorepo_dir = os.path.abspath(args.monorepo_dir)
devnet_dir = pjoin(monorepo_dir, '.devnet') devnet_dir = pjoin(monorepo_dir, '.devnet')
ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock')
contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock') contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock')
deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1') deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1')
op_node_dir = pjoin(args.monorepo_dir, 'op-node') op_node_dir = pjoin(args.monorepo_dir, 'op-node')
genesis_l1_path = pjoin(devnet_dir, 'genesis-l1.json') ops_bedrock_dir=pjoin(monorepo_dir, 'ops-bedrock')
genesis_l2_path = pjoin(devnet_dir, 'genesis-l2.json')
addresses_json_path = pjoin(devnet_dir, 'addresses.json') paths = Bunch(
sdk_addresses_json_path = pjoin(devnet_dir, 'sdk-addresses.json') mono_repo_dir=monorepo_dir,
rollup_config_path = pjoin(devnet_dir, 'rollup.json') devnet_dir=devnet_dir,
contracts_bedrock_dir=contracts_bedrock_dir,
deployment_dir=deployment_dir,
deploy_config_dir=pjoin(contracts_bedrock_dir, 'deploy-config'),
op_node_dir=op_node_dir,
ops_bedrock_dir=ops_bedrock_dir,
genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'),
genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.json'),
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')
)
os.makedirs(devnet_dir, exist_ok=True) os.makedirs(devnet_dir, exist_ok=True)
if os.path.exists(genesis_l1_path): if args.deploy:
log.info('L2 genesis already generated.') log.info('Devnet with upcoming smart contract deployments')
devnet_deploy(paths)
else:
log.info('Devnet with smart contracts pre-deployed')
devnet_prestate(paths)
# Bring up the devnet where the L1 contracts are in the genesis state
def devnet_prestate(paths):
date = datetime.datetime.utcnow()
utc_time = hex(calendar.timegm(date.utctimetuple()))
done_file = pjoin(paths.devnet_dir, 'done')
if os.path.exists(done_file):
log.info('Genesis files already exist')
else:
log.info('Creating genesis files')
deploy_config_path = pjoin(paths.deploy_config_dir, 'devnetL1.json')
# read the json file
deploy_config = read_json(deploy_config_path)
deploy_config['l1GenesisBlockTimestamp'] = utc_time
temp_deploy_config = pjoin(paths.devnet_dir, 'deploy-config.json')
write_json(temp_deploy_config, deploy_config)
outfile_l1 = paths.genesis_l1_path
outfile_l2 = paths.genesis_l2_path
outfile_rollup = paths.rollup_config_path
run_command(['go', 'run', 'cmd/main.go', 'genesis', 'devnet', '--deploy-config', temp_deploy_config, '--outfile.l1', outfile_l1, '--outfile.l2', outfile_l2, '--outfile.rollup', outfile_rollup], cwd=paths.op_node_dir)
write_json(done_file, {})
log.info('Bringing up L1.')
run_command(['docker-compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir
})
wait_up(8545)
log.info('Bringing up L2.')
run_command(['docker-compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir
})
wait_up(9545)
log.info('Bringing up the services.')
run_command(['docker-compose', 'up', '-d', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': '0x6900000000000000000000000000000000000000'
})
# Bring up the devnet where the contracts are deployed to L1
def devnet_deploy(paths):
if os.path.exists(paths.genesis_l1_path):
log.info('L1 genesis already generated.')
else: else:
log.info('Generating L1 genesis.') log.info('Generating L1 genesis.')
write_json(genesis_l1_path, GENESIS_TMPL) write_json(paths.genesis_l1_path, GENESIS_TMPL)
log.info('Starting L1.') log.info('Starting L1.')
run_command(['docker-compose', 'up', '-d', 'l1'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(8545) wait_up(8545)
log.info('Generating network config.') log.info('Generating network config.')
devnet_cfg_orig = pjoin(contracts_bedrock_dir, 'deploy-config', 'devnetL1.json') devnet_cfg_orig = pjoin(paths.contracts_bedrock_dir, 'deploy-config', 'devnetL1.json')
devnet_cfg_backup = pjoin(devnet_dir, 'devnetL1.json.bak') devnet_cfg_backup = pjoin(paths.devnet_dir, 'devnetL1.json.bak')
shutil.copy(devnet_cfg_orig, devnet_cfg_backup) shutil.copy(devnet_cfg_orig, devnet_cfg_backup)
deploy_config = read_json(devnet_cfg_orig) deploy_config = read_json(devnet_cfg_orig)
deploy_config['l1GenesisBlockTimestamp'] = GENESIS_TMPL['timestamp'] deploy_config['l1GenesisBlockTimestamp'] = GENESIS_TMPL['timestamp']
deploy_config['l1StartingBlockTag'] = 'earliest' deploy_config['l1StartingBlockTag'] = 'earliest'
write_json(devnet_cfg_orig, deploy_config) write_json(devnet_cfg_orig, deploy_config)
if os.path.exists(addresses_json_path): if os.path.exists(paths.addresses_json_path):
log.info('Contracts already deployed.') log.info('Contracts already deployed.')
addresses = read_json(addresses_json_path) addresses = read_json(paths.addresses_json_path)
else: else:
log.info('Deploying contracts.') log.info('Deploying contracts.')
run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy', '--tags', 'l1'], env={ run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy', '--tags', 'l1'], env={
'CHAIN_ID': '900', 'CHAIN_ID': '900',
'L1_RPC': 'http://localhost:8545', 'L1_RPC': 'http://localhost:8545',
'PRIVATE_KEY_DEPLOYER': 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' 'PRIVATE_KEY_DEPLOYER': 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
}, cwd=contracts_bedrock_dir) }, cwd=paths.contracts_bedrock_dir)
contracts = os.listdir(deployment_dir) contracts = os.listdir(paths.deployment_dir)
addresses = {} addresses = {}
for c in contracts: for c in contracts:
if not c.endswith('.json'): if not c.endswith('.json'):
continue continue
data = read_json(pjoin(deployment_dir, c)) data = read_json(pjoin(paths.deployment_dir, c))
addresses[c.replace('.json', '')] = data['address'] addresses[c.replace('.json', '')] = data['address']
sdk_addresses = {} sdk_addresses = {}
sdk_addresses.update({ sdk_addresses.update({
...@@ -84,10 +152,10 @@ def main(): ...@@ -84,10 +152,10 @@ def main():
sdk_addresses['L1StandardBridge'] = addresses['Proxy__OVM_L1StandardBridge'] sdk_addresses['L1StandardBridge'] = addresses['Proxy__OVM_L1StandardBridge']
sdk_addresses['OptimismPortal'] = addresses['OptimismPortalProxy'] sdk_addresses['OptimismPortal'] = addresses['OptimismPortalProxy']
sdk_addresses['L2OutputOracle'] = addresses['L2OutputOracleProxy'] sdk_addresses['L2OutputOracle'] = addresses['L2OutputOracleProxy']
write_json(addresses_json_path, addresses) write_json(paths.addresses_json_path, addresses)
write_json(sdk_addresses_json_path, sdk_addresses) write_json(paths.sdk_addresses_json_path, sdk_addresses)
if os.path.exists(genesis_l2_path): if os.path.exists(paths.genesis_l2_path):
log.info('L2 genesis and rollup configs already generated.') log.info('L2 genesis and rollup configs already generated.')
else: else:
log.info('Generating L2 genesis and rollup configs.') log.info('Generating L2 genesis and rollup configs.')
...@@ -95,25 +163,25 @@ def main(): ...@@ -95,25 +163,25 @@ def main():
'go', 'run', 'cmd/main.go', 'genesis', 'l2', 'go', 'run', 'cmd/main.go', 'genesis', 'l2',
'--l1-rpc', 'http://localhost:8545', '--l1-rpc', 'http://localhost:8545',
'--deploy-config', devnet_cfg_orig, '--deploy-config', devnet_cfg_orig,
'--deployment-dir', deployment_dir, '--deployment-dir', paths.deployment_dir,
'--outfile.l2', pjoin(devnet_dir, 'genesis-l2.json'), '--outfile.l2', pjoin(paths.devnet_dir, 'genesis-l2.json'),
'--outfile.rollup', pjoin(devnet_dir, 'rollup.json') '--outfile.rollup', pjoin(paths.devnet_dir, 'rollup.json')
], cwd=op_node_dir) ], cwd=paths.op_node_dir)
rollup_config = read_json(rollup_config_path) rollup_config = read_json(paths.rollup_config_path)
if os.path.exists(devnet_cfg_backup): if os.path.exists(devnet_cfg_backup):
shutil.move(devnet_cfg_backup, devnet_cfg_orig) shutil.move(devnet_cfg_backup, devnet_cfg_orig)
log.info('Bringing up L2.') log.info('Bringing up L2.')
run_command(['docker-compose', 'up', '-d', 'l2'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(9545) wait_up(9545)
log.info('Bringing up everything else.') log.info('Bringing up everything else.')
run_command(['docker-compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir, 'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': addresses['L2OutputOracleProxy'], 'L2OO_ADDRESS': addresses['L2OutputOracleProxy'],
'SEQUENCER_BATCH_INBOX_ADDRESS': rollup_config['batch_inbox_address'] 'SEQUENCER_BATCH_INBOX_ADDRESS': rollup_config['batch_inbox_address']
}) })
......
...@@ -19,7 +19,7 @@ require ( ...@@ -19,7 +19,7 @@ require (
github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ds-leveldb v0.5.0 github.com/ipfs/go-ds-leveldb v0.5.0
github.com/libp2p/go-libp2p v0.25.1 github.com/libp2p/go-libp2p v0.25.1
github.com/libp2p/go-libp2p-pubsub v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.9.3
github.com/libp2p/go-libp2p-testing v0.12.0 github.com/libp2p/go-libp2p-testing v0.12.0
github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-isatty v0.0.17
github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-base32 v0.1.0
...@@ -62,7 +62,6 @@ require ( ...@@ -62,7 +62,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect github.com/elastic/gosigar v0.14.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
...@@ -90,7 +89,6 @@ require ( ...@@ -90,7 +89,6 @@ require (
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
github.com/ipfs/go-cid v0.3.2 // indirect github.com/ipfs/go-cid v0.3.2 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
......
...@@ -135,8 +135,6 @@ github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZi ...@@ -135,8 +135,6 @@ github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZi
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
...@@ -332,9 +330,6 @@ github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1 ...@@ -332,9 +330,6 @@ github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1
github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo=
github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q=
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
...@@ -409,8 +404,8 @@ github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJ ...@@ -409,8 +404,8 @@ github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJ
github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g=
github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw=
github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI=
github.com/libp2p/go-libp2p-pubsub v0.9.0 h1:mcLb4WzwhUG4OKb0rp1/bYMd/DYhvMyzJheQH3LMd1s= github.com/libp2p/go-libp2p-pubsub v0.9.3 h1:ihcz9oIBMaCK9kcx+yHWm3mLAFBMAUsM4ux42aikDxo=
github.com/libp2p/go-libp2p-pubsub v0.9.0/go.mod h1:OEsj0Cc/BpkqikXRTrVspWU/Hx7bMZwHP+6vNMd+c7I= github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-mplex v0.7.0 h1:BDhFZdlk5tbr0oyFq/xv/NPGfjbnrsDam1EvutpBDbY= github.com/libp2p/go-mplex v0.7.0 h1:BDhFZdlk5tbr0oyFq/xv/NPGfjbnrsDam1EvutpBDbY=
...@@ -723,7 +718,6 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i ...@@ -723,7 +718,6 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2,15 +2,20 @@ package bindings ...@@ -2,15 +2,20 @@ package bindings
import ( import (
"fmt" "fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// layouts respresents the set of storage layouts. It is populated in an init function.
var layouts = make(map[string]*solc.StorageLayout) var layouts = make(map[string]*solc.StorageLayout)
// deployedBytecodes represents the set of deployed bytecodes. It is populated
// in an init function.
var deployedBytecodes = make(map[string]string) var deployedBytecodes = make(map[string]string)
// GetStorageLayout returns the storage layout of a contract by name.
func GetStorageLayout(name string) (*solc.StorageLayout, error) { func GetStorageLayout(name string) (*solc.StorageLayout, error) {
layout := layouts[name] layout := layouts[name]
if layout == nil { if layout == nil {
...@@ -19,11 +24,36 @@ func GetStorageLayout(name string) (*solc.StorageLayout, error) { ...@@ -19,11 +24,36 @@ func GetStorageLayout(name string) (*solc.StorageLayout, error) {
return layout, nil return layout, nil
} }
// GetDeployedBytecode returns the deployed bytecode of a contract by name.
func GetDeployedBytecode(name string) ([]byte, error) { func GetDeployedBytecode(name string) ([]byte, error) {
bc := deployedBytecodes[name] bc := deployedBytecodes[name]
if bc == "" { if bc == "" {
return nil, fmt.Errorf("%s: deployed bytecode not found", name) return nil, fmt.Errorf("%s: deployed bytecode not found", name)
} }
if !isHex(bc) {
return nil, fmt.Errorf("%s: invalid deployed bytecode", name)
}
return common.FromHex(bc), nil return common.FromHex(bc), nil
} }
// isHexCharacter returns bool of c being a valid hexadecimal.
func isHexCharacter(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
}
// isHex validates whether each byte is valid hexadecimal string.
func isHex(str string) bool {
if len(str)%2 != 0 {
return false
}
str = strings.TrimPrefix(str, "0x")
for _, c := range []byte(str) {
if !isHexCharacter(c) {
return false
}
}
return true
}
...@@ -5,7 +5,6 @@ import "github.com/ethereum/go-ethereum/common" ...@@ -5,7 +5,6 @@ import "github.com/ethereum/go-ethereum/common"
const ( const (
L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016" L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016"
DeployerWhitelist = "0x4200000000000000000000000000000000000002" DeployerWhitelist = "0x4200000000000000000000000000000000000002"
LegacyERC20ETH = "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"
WETH9 = "0x4200000000000000000000000000000000000006" WETH9 = "0x4200000000000000000000000000000000000006"
L2CrossDomainMessenger = "0x4200000000000000000000000000000000000007" L2CrossDomainMessenger = "0x4200000000000000000000000000000000000007"
L2StandardBridge = "0x4200000000000000000000000000000000000010" L2StandardBridge = "0x4200000000000000000000000000000000000010"
...@@ -26,7 +25,6 @@ const ( ...@@ -26,7 +25,6 @@ const (
var ( var (
L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser) L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser)
DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist) DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist)
LegacyERC20ETHAddr = common.HexToAddress(LegacyERC20ETH)
WETH9Addr = common.HexToAddress(WETH9) WETH9Addr = common.HexToAddress(WETH9)
L2CrossDomainMessengerAddr = common.HexToAddress(L2CrossDomainMessenger) L2CrossDomainMessengerAddr = common.HexToAddress(L2CrossDomainMessenger)
L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge) L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge)
...@@ -46,10 +44,20 @@ var ( ...@@ -46,10 +44,20 @@ var (
Predeploys = make(map[string]*common.Address) Predeploys = make(map[string]*common.Address)
) )
// IsProxied returns true for predeploys that will sit behind a proxy contract
func IsProxied(predeployAddr common.Address) bool {
switch predeployAddr {
case WETH9Addr:
case GovernanceTokenAddr:
default:
return true
}
return false
}
func init() { func init() {
Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr
Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr
Predeploys["LegacyERC20ETH"] = &LegacyERC20ETHAddr
Predeploys["WETH9"] = &WETH9Addr Predeploys["WETH9"] = &WETH9Addr
Predeploys["L2CrossDomainMessenger"] = &L2CrossDomainMessengerAddr Predeploys["L2CrossDomainMessenger"] = &L2CrossDomainMessengerAddr
Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr
......
...@@ -52,7 +52,7 @@ func Main(cliCtx *cli.Context) error { ...@@ -52,7 +52,7 @@ func Main(cliCtx *cli.Context) error {
return err return err
} }
p2pConfig, err := p2pcli.NewConfig(cliCtx, config.BlockTime) p2pConfig, err := p2pcli.NewConfig(cliCtx, config)
if err != nil { if err != nil {
return fmt.Errorf("failed to load p2p config: %w", err) return fmt.Errorf("failed to load p2p config: %w", err)
} }
......
...@@ -113,6 +113,8 @@ type DeployConfig struct { ...@@ -113,6 +113,8 @@ type DeployConfig struct {
GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"` GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"`
// The initial value of the gas scalar // The initial value of the gas scalar
GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"` GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"`
// Whether or not include governance token predeploy
EnableGovernance bool `json:"enableGovernance"`
// The ERC20 symbol of the GovernanceToken // The ERC20 symbol of the GovernanceToken
GovernanceTokenSymbol string `json:"governanceTokenSymbol"` GovernanceTokenSymbol string `json:"governanceTokenSymbol"`
// The ERC20 name of the GovernanceToken // The ERC20 name of the GovernanceToken
...@@ -240,6 +242,7 @@ func (d *DeployConfig) Check() error { ...@@ -240,6 +242,7 @@ func (d *DeployConfig) Check() error {
if d.L2GenesisBlockBaseFeePerGas == nil { if d.L2GenesisBlockBaseFeePerGas == nil {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig)
} }
if d.EnableGovernance {
if d.GovernanceTokenName == "" { if d.GovernanceTokenName == "" {
return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig) return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig)
} }
...@@ -249,6 +252,7 @@ func (d *DeployConfig) Check() error { ...@@ -249,6 +252,7 @@ func (d *DeployConfig) Check() error {
if d.GovernanceTokenOwner == (common.Address{}) { if d.GovernanceTokenOwner == (common.Address{}) {
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig)
} }
}
return nil return nil
} }
...@@ -492,11 +496,13 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage ...@@ -492,11 +496,13 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
"symbol": "WETH", "symbol": "WETH",
"decimals": 18, "decimals": 18,
} }
if config.EnableGovernance {
storage["GovernanceToken"] = state.StorageValues{ storage["GovernanceToken"] = state.StorageValues{
"_name": config.GovernanceTokenName, "_name": config.GovernanceTokenName,
"_symbol": config.GovernanceTokenSymbol, "_symbol": config.GovernanceTokenSymbol,
"_owner": config.GovernanceTokenOwner, "_owner": config.GovernanceTokenOwner,
} }
}
storage["ProxyAdmin"] = state.StorageValues{ storage["ProxyAdmin"] = state.StorageValues{
"_owner": config.ProxyAdminOwner, "_owner": config.ProxyAdminOwner,
} }
......
package genesis package genesis
import ( import (
"github.com/ethereum-optimism/optimism/op-chain-ops/state" "fmt"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
) )
// BuildL2DeveloperGenesis will build the developer Optimism Genesis // BuildL2DeveloperGenesis will build the L2 genesis block.
// Block. Suitable for devnets. func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock) genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
db := state.NewMemoryStateDB(genspec) db := state.NewMemoryStateDB(genspec)
if config.FundDevAccounts { if config.FundDevAccounts {
FundDevAccounts(db) FundDevAccounts(db)
}
SetPrecompileBalances(db) SetPrecompileBalances(db)
}
storage, err := NewL2StorageConfig(config, l1StartBlock) storage, err := NewL2StorageConfig(config, l1StartBlock)
if err != nil { if err != nil {
...@@ -32,17 +35,43 @@ func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (* ...@@ -32,17 +35,43 @@ func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (*
return nil, err return nil, err
} }
if err := SetL2Proxies(db); err != nil { // Set up the proxies
err = setProxies(db, predeploys.ProxyAdminAddr, bigL2PredeployNamespace, 2048)
if err != nil {
return nil, err return nil, err
} }
if err := SetImplementations(db, storage, immutable); err != nil { // Set up the implementations
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return nil, err return nil, err
} }
for name, predeploy := range predeploys.Predeploys {
if err := SetDevOnlyL2Implementations(db, storage, immutable); err != nil { addr := *predeploy
if addr == predeploys.GovernanceTokenAddr && !config.EnableGovernance {
// there is no governance token configured, so skip the governance token predeploy
log.Warn("Governance is not enabled, skipping governance token predeploy.")
continue
}
codeAddr := addr
if predeploys.IsProxied(addr) {
codeAddr, err = AddressToCodeNamespace(addr)
if err != nil {
return nil, fmt.Errorf("error converting to code namespace: %w", err)
}
db.CreateAccount(codeAddr)
db.SetState(addr, ImplementationSlot, codeAddr.Hash())
} else {
db.DeleteState(addr, AdminSlot)
}
if err := setupPredeploy(db, deployResults, storage, name, addr, codeAddr); err != nil {
return nil, err return nil, err
} }
code := db.GetCode(codeAddr)
if len(code) == 0 {
return nil, fmt.Errorf("code not set for %s", name)
}
}
return db.Genesis(), nil return db.Genesis(), nil
} }
...@@ -8,17 +8,16 @@ import ( ...@@ -8,17 +8,16 @@ import (
"os" "os"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/stretchr/testify/require"
) )
var writeFile bool var writeFile bool
...@@ -29,10 +28,8 @@ func init() { ...@@ -29,10 +28,8 @@ func init() {
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
func TestBuildL2DeveloperGenesis(t *testing.T) { // Tests the BuildL2MainnetGenesis factory with the provided config.
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json") func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesis {
require.Nil(t, err)
backend := backends.NewSimulatedBackend( backend := backends.NewSimulatedBackend(
core.GenesisAlloc{ core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
...@@ -42,55 +39,65 @@ func TestBuildL2DeveloperGenesis(t *testing.T) { ...@@ -42,55 +39,65 @@ func TestBuildL2DeveloperGenesis(t *testing.T) {
block, err := backend.BlockByNumber(context.Background(), common.Big0) block, err := backend.BlockByNumber(context.Background(), common.Big0)
require.NoError(t, err) require.NoError(t, err)
gen, err := genesis.BuildL2DeveloperGenesis(config, block) gen, err := genesis.BuildL2Genesis(config, block)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, gen) require.NotNil(t, gen)
depB, err := bindings.GetDeployedBytecode("Proxy") proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err) require.NoError(t, err)
for name, address := range predeploys.Predeploys { for name, predeploy := range predeploys.Predeploys {
addr := *address addr := *predeploy
account, ok := gen.Alloc[addr] account, ok := gen.Alloc[addr]
require.Equal(t, ok, true) require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0) require.Greater(t, len(account.Code), 0)
if name == "GovernanceToken" || name == "LegacyERC20ETH" || name == "ProxyAdmin" || name == "WETH9" {
continue
}
adminSlot, ok := account.Storage[genesis.AdminSlot] adminSlot, ok := account.Storage[genesis.AdminSlot]
require.Equal(t, ok, true) isProxy := predeploys.IsProxied(addr) ||
require.Equal(t, adminSlot, predeploys.ProxyAdminAddr.Hash()) (!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr)
require.Equal(t, account.Code, depB) if isProxy {
require.Equal(t, true, ok, name)
require.Equal(t, predeploys.ProxyAdminAddr.Hash(), adminSlot)
require.Equal(t, proxyBytecode, account.Code)
} else {
require.Equal(t, false, ok, name)
require.NotEqual(t, proxyBytecode, account.Code, name)
}
} }
require.Equal(t, 2343, len(gen.Alloc))
if writeFile { if writeFile {
file, _ := json.MarshalIndent(gen, "", " ") file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644) _ = os.WriteFile("genesis.json", file, 0644)
} }
return gen
} }
func TestBuildL2DeveloperGenesisDevAccountsFunding(t *testing.T) { func TestBuildL2DeveloperGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json") config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err) require.Nil(t, err)
config.FundDevAccounts = false config.EnableGovernance = false
config.FundDevAccounts = true
err = config.InitDeveloperDeployedAddresses() err = config.InitDeveloperDeployedAddresses()
require.NoError(t, err) require.NoError(t, err)
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2342, len(gen.Alloc))
}
backend := backends.NewSimulatedBackend( func TestBuildL2MainnetGenesis(t *testing.T) {
core.GenesisAlloc{ config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, require.Nil(t, err)
}, config.EnableGovernance = true
15000000, config.FundDevAccounts = false
) gen := testBuildL2Genesis(t, config)
block, err := backend.BlockByNumber(context.Background(), common.Big0) require.Equal(t, 2064, len(gen.Alloc))
require.NoError(t, err) }
gen, err := genesis.BuildL2DeveloperGenesis(config, block) func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
require.NoError(t, err) config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Equal(t, 2321, len(gen.Alloc)) require.Nil(t, err)
config.EnableGovernance = false
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2064, len(gen.Alloc))
} }
...@@ -2,12 +2,10 @@ package genesis ...@@ -2,12 +2,10 @@ package genesis
import ( import (
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables" "github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state" "github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -15,19 +13,6 @@ import ( ...@@ -15,19 +13,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// UntouchableCodeHashes contains code hashes of all the contracts
// that should not be touched by the migration process.
type ChainHashMap map[uint64]common.Hash
var (
// UntouchablePredeploys are addresses in the predeploy namespace
// that should not be touched by the migration process.
UntouchablePredeploys = map[common.Address]bool{
predeploys.GovernanceTokenAddr: true,
predeploys.WETH9Addr: true,
}
)
// FundDevAccounts will fund each of the development accounts. // FundDevAccounts will fund each of the development accounts.
func FundDevAccounts(db vm.StateDB) { func FundDevAccounts(db vm.StateDB) {
for _, account := range DevAccounts { for _, account := range DevAccounts {
...@@ -36,14 +21,6 @@ func FundDevAccounts(db vm.StateDB) { ...@@ -36,14 +21,6 @@ func FundDevAccounts(db vm.StateDB) {
} }
} }
// SetL2Proxies will set each of the proxies in the state. It requires
// a Proxy and ProxyAdmin deployment present so that the Proxy bytecode
// can be set in state and the ProxyAdmin can be set as the admin of the
// Proxy.
func SetL2Proxies(db vm.StateDB) error {
return setProxies(db, predeploys.ProxyAdminAddr, bigL2PredeployNamespace, 2048)
}
// SetL1Proxies will set each of the proxies in the state. It requires // SetL1Proxies will set each of the proxies in the state. It requires
// a Proxy and ProxyAdmin deployment present so that the Proxy bytecode // a Proxy and ProxyAdmin deployment present so that the Proxy bytecode
// can be set in state and the ProxyAdmin can be set as the admin of the // can be set in state and the ProxyAdmin can be set as the admin of the
...@@ -65,11 +42,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int ...@@ -65,11 +42,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
bigAddr := new(big.Int).Or(namespace, new(big.Int).SetUint64(i)) bigAddr := new(big.Int).Or(namespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr) addr := common.BigToAddress(bigAddr)
if UntouchablePredeploys[addr] {
log.Info("Skipping setting proxy", "address", addr)
continue
}
if !db.Exist(addr) { if !db.Exist(addr) {
db.CreateAccount(addr) db.CreateAccount(addr)
} }
...@@ -82,87 +54,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int ...@@ -82,87 +54,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
return nil return nil
} }
func SetLegacyETH(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
return setupPredeploy(db, deployResults, storage, "LegacyERC20ETH", predeploys.LegacyERC20ETHAddr, predeploys.LegacyERC20ETHAddr)
}
// SetImplementations will set the implementations of the contracts in the state
// and configure the proxies to point to the implementations. It also sets
// the appropriate storage values for each contract at the proxy address.
func SetImplementations(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
for name, address := range predeploys.Predeploys {
if UntouchablePredeploys[*address] {
continue
}
if *address == predeploys.LegacyERC20ETHAddr {
continue
}
codeAddr, err := AddressToCodeNamespace(*address)
if err != nil {
return fmt.Errorf("error converting to code namespace: %w", err)
}
if !db.Exist(codeAddr) {
db.CreateAccount(codeAddr)
}
db.SetState(*address, ImplementationSlot, codeAddr.Hash())
if err := setupPredeploy(db, deployResults, storage, name, *address, codeAddr); err != nil {
return err
}
code := db.GetCode(codeAddr)
if len(code) == 0 {
return fmt.Errorf("code not set for %s", name)
}
}
return nil
}
func SetDevOnlyL2Implementations(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
for name, address := range predeploys.Predeploys {
if !UntouchablePredeploys[*address] {
continue
}
db.CreateAccount(*address)
if err := setupPredeploy(db, deployResults, storage, name, *address, *address); err != nil {
return err
}
code := db.GetCode(*address)
if len(code) == 0 {
return fmt.Errorf("code not set for %s", name)
}
}
db.CreateAccount(predeploys.LegacyERC20ETHAddr)
if err := setupPredeploy(db, deployResults, storage, "LegacyERC20ETH", predeploys.LegacyERC20ETHAddr, predeploys.LegacyERC20ETHAddr); err != nil {
return fmt.Errorf("error setting up legacy eth: %w", err)
}
return nil
}
// SetPrecompileBalances will set a single wei at each precompile address. // SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper. This should only // This is an optimization to make calling them cheaper. This should only
// be used for devnets. // be used for devnets.
......
...@@ -35,5 +35,10 @@ ...@@ -35,5 +35,10 @@
"l1CrossDomainMessengerProxy": "0xff000000000000000000000000000000000000dd", "l1CrossDomainMessengerProxy": "0xff000000000000000000000000000000000000dd",
"deploymentWaitConfirmations": 1, "deploymentWaitConfirmations": 1,
"fundDevAccounts": true "fundDevAccounts": true,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333"
} }
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
"proxyAdminOwner": "0x0000000000000000000000000000000000000222", "proxyAdminOwner": "0x0000000000000000000000000000000000000222",
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333", "governanceTokenOwner": "0x0000000000000000000000000000000000000333",
......
...@@ -226,6 +226,15 @@ func (db *MemoryStateDB) SetState(addr common.Address, key, value common.Hash) { ...@@ -226,6 +226,15 @@ func (db *MemoryStateDB) SetState(addr common.Address, key, value common.Hash) {
db.genesis.Alloc[addr] = account db.genesis.Alloc[addr] = account
} }
func (db *MemoryStateDB) DeleteState(addr common.Address, key common.Hash) {
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
delete(account.Storage, key)
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) Suicide(common.Address) bool { func (db *MemoryStateDB) Suicide(common.Address) bool {
panic("Suicide unimplemented") panic("Suicide unimplemented")
} }
......
...@@ -185,7 +185,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -185,7 +185,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(deployConf, l1Block) l2Genesis, err := genesis.BuildL2Genesis(deployConf, l1Block)
require.NoError(t, err, "failed to create l2 genesis") require.NoError(t, err, "failed to create l2 genesis")
if alloc.PrefundTestUsers { if alloc.PrefundTestUsers {
for _, addr := range deployParams.Addresses.All() { for _, addr := range deployParams.Addresses.All() {
......
...@@ -53,7 +53,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e ...@@ -53,7 +53,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
require.Nil(t, err) require.Nil(t, err)
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block) l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block)
require.Nil(t, err) require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock() l2GenesisBlock := l2Genesis.ToBlock()
......
...@@ -359,7 +359,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -359,7 +359,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
} }
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block) l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -813,10 +813,10 @@ func TestSystemDenseTopology(t *testing.T) { ...@@ -813,10 +813,10 @@ func TestSystemDenseTopology(t *testing.T) {
// Set peer scoring for each node, but without banning // Set peer scoring for each node, but without banning
for _, node := range cfg.Nodes { for _, node := range cfg.Nodes {
params, err := p2p.GetPeerScoreParams("light", 2) params, err := p2p.GetScoringParams("light", &node.Rollup)
require.NoError(t, err) require.NoError(t, err)
node.P2P = &p2p.Config{ node.P2P = &p2p.Config{
PeerScoring: &params, ScoringParams: params,
BanningEnabled: false, BanningEnabled: false,
} }
} }
......
...@@ -61,7 +61,7 @@ var Subcommands = cli.Commands{ ...@@ -61,7 +61,7 @@ var Subcommands = cli.Commands{
} }
l1StartBlock := l1Genesis.ToBlock() l1StartBlock := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(config, l1StartBlock) l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
if err != nil { if err != nil {
return err return err
} }
...@@ -144,7 +144,7 @@ var Subcommands = cli.Commands{ ...@@ -144,7 +144,7 @@ var Subcommands = cli.Commands{
} }
// Build the developer L2 genesis block // Build the developer L2 genesis block
l2Genesis, err := genesis.BuildL2DeveloperGenesis(config, l1StartBlock) l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
if err != nil { if err != nil {
return fmt.Errorf("error creating l2 developer genesis: %w", err) return fmt.Errorf("error creating l2 developer genesis: %w", err)
} }
......
package flags package flags
import ( import (
"fmt"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -25,14 +26,17 @@ var ( ...@@ -25,14 +26,17 @@ var (
Required: false, Required: false,
EnvVar: p2pEnv("NO_DISCOVERY"), EnvVar: p2pEnv("NO_DISCOVERY"),
} }
Scoring = cli.StringFlag{
Name: "p2p.scoring",
Usage: "Sets the peer scoring strategy for the P2P stack. Can be one of: none or light.",
Required: false,
EnvVar: p2pEnv("PEER_SCORING"),
}
PeerScoring = cli.StringFlag{ PeerScoring = cli.StringFlag{
Name: "p2p.scoring.peers", Name: "p2p.scoring.peers",
Usage: "Sets the peer scoring strategy for the P2P stack. " + Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name),
"Can be one of: none or light." +
"Custom scoring strategies can be defined in the config file.",
Required: false, Required: false,
Value: "none", Hidden: true,
EnvVar: p2pEnv("PEER_SCORING"),
} }
PeerScoreBands = cli.StringFlag{ PeerScoreBands = cli.StringFlag{
Name: "p2p.score.bands", Name: "p2p.score.bands",
...@@ -66,12 +70,9 @@ var ( ...@@ -66,12 +70,9 @@ var (
TopicScoring = cli.StringFlag{ TopicScoring = cli.StringFlag{
Name: "p2p.scoring.topics", Name: "p2p.scoring.topics",
Usage: "Sets the topic scoring strategy. " + Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name),
"Can be one of: none or light." +
"Custom scoring strategies can be defined in the config file.",
Required: false, Required: false,
Value: "none", Hidden: true,
EnvVar: p2pEnv("TOPIC_SCORING"),
} }
P2PPrivPath = cli.StringFlag{ P2PPrivPath = cli.StringFlag{
Name: "p2p.priv.path", Name: "p2p.priv.path",
...@@ -303,6 +304,7 @@ var p2pFlags = []cli.Flag{ ...@@ -303,6 +304,7 @@ var p2pFlags = []cli.Flag{
NoDiscovery, NoDiscovery,
P2PPrivPath, P2PPrivPath,
P2PPrivRaw, P2PPrivRaw,
Scoring,
PeerScoring, PeerScoring,
PeerScoreBands, PeerScoreBands,
Banning, Banning,
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/ethereum-optimism/optimism/op-node/rollup"
ds "github.com/ipfs/go-datastore" ds "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/sync" "github.com/ipfs/go-datastore/sync"
leveldb "github.com/ipfs/go-ds-leveldb" leveldb "github.com/ipfs/go-ds-leveldb"
...@@ -24,7 +25,7 @@ import ( ...@@ -24,7 +25,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
) )
func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) { func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error) {
conf := &p2p.Config{} conf := &p2p.Config{}
if ctx.GlobalBool(flags.DisableP2P.Name) { if ctx.GlobalBool(flags.DisableP2P.Name) {
...@@ -54,7 +55,7 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) { ...@@ -54,7 +55,7 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) {
return nil, fmt.Errorf("failed to load p2p gossip options: %w", err) return nil, fmt.Errorf("failed to load p2p gossip options: %w", err)
} }
if err := loadPeerScoringParams(conf, ctx, blockTime); err != nil { if err := loadScoringParams(conf, ctx, rollupCfg); err != nil {
return nil, fmt.Errorf("failed to load p2p peer scoring options: %w", err) return nil, fmt.Errorf("failed to load p2p peer scoring options: %w", err)
} }
...@@ -62,10 +63,6 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) { ...@@ -62,10 +63,6 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) {
return nil, fmt.Errorf("failed to load banning option: %w", err) return nil, fmt.Errorf("failed to load banning option: %w", err)
} }
if err := loadTopicScoringParams(conf, ctx, blockTime); err != nil {
return nil, fmt.Errorf("failed to load p2p topic scoring options: %w", err)
}
conf.EnableReqRespSync = ctx.GlobalBool(flags.SyncReqRespFlag.Name) conf.EnableReqRespSync = ctx.GlobalBool(flags.SyncReqRespFlag.Name)
return conf, nil return conf, nil
...@@ -84,37 +81,22 @@ func validatePort(p uint) (uint16, error) { ...@@ -84,37 +81,22 @@ func validatePort(p uint) (uint16, error) {
return uint16(p), nil return uint16(p), nil
} }
// loadTopicScoringParams loads the topic scoring options from the CLI context. // loadScoringParams loads the peer scoring options from the CLI context.
// func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Config) error {
// If the topic scoring options are not set, then the default topic scoring. scoringLevel := ctx.GlobalString(flags.Scoring.Name)
func loadTopicScoringParams(conf *p2p.Config, ctx *cli.Context, blockTime uint64) error { // Check old names for backwards compatibility
scoringLevel := ctx.GlobalString(flags.TopicScoring.Name) if scoringLevel == "" {
if scoringLevel != "" { scoringLevel = ctx.GlobalString(flags.PeerScoring.Name)
// Set default block topic scoring parameters
// See prysm: https://github.com/prysmaticlabs/prysm/blob/develop/beacon-chain/p2p/gossip_scoring_params.go
// And research from lighthouse: https://gist.github.com/blacktemplar/5c1862cb3f0e32a1a7fb0b25e79e6e2c
// And docs: https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#topic-parameter-calculation-and-decay
topicScoreParams, err := p2p.GetTopicScoreParams(scoringLevel, blockTime)
if err != nil {
return err
} }
conf.TopicScoring = &topicScoreParams if scoringLevel == "" {
scoringLevel = ctx.GlobalString(flags.TopicScoring.Name)
} }
return nil
}
// loadPeerScoringParams loads the scoring options from the CLI context.
//
// If the scoring level is not set, no scoring is enabled.
func loadPeerScoringParams(conf *p2p.Config, ctx *cli.Context, blockTime uint64) error {
scoringLevel := ctx.GlobalString(flags.PeerScoring.Name)
if scoringLevel != "" { if scoringLevel != "" {
peerScoreParams, err := p2p.GetPeerScoreParams(scoringLevel, blockTime) params, err := p2p.GetScoringParams(scoringLevel, rollupCfg)
if err != nil { if err != nil {
return err return err
} }
conf.PeerScoring = &peerScoreParams conf.ScoringParams = params
} }
return nil return nil
......
...@@ -51,6 +51,11 @@ type SetupP2P interface { ...@@ -51,6 +51,11 @@ type SetupP2P interface {
ReqRespSyncEnabled() bool ReqRespSyncEnabled() bool
} }
// ScoringParams defines the various types of peer scoring parameters.
type ScoringParams struct {
PeerScoring pubsub.PeerScoreParams
}
// Config sets up a p2p host and discv5 service from configuration. // Config sets up a p2p host and discv5 service from configuration.
// This implements SetupP2P. // This implements SetupP2P.
type Config struct { type Config struct {
...@@ -62,9 +67,7 @@ type Config struct { ...@@ -62,9 +67,7 @@ type Config struct {
// Enable P2P-based alt-syncing method (req-resp protocol, not gossip) // Enable P2P-based alt-syncing method (req-resp protocol, not gossip)
AltSync bool AltSync bool
// Pubsub Scoring Parameters ScoringParams *ScoringParams
PeerScoring *pubsub.PeerScoreParams
TopicScoring *pubsub.TopicScoreParams
// Whether to ban peers based on their [PeerScoring] score. Should be negative. // Whether to ban peers based on their [PeerScoring] score. Should be negative.
BanningEnabled bool BanningEnabled bool
...@@ -135,7 +138,10 @@ func (conf *Config) Disabled() bool { ...@@ -135,7 +138,10 @@ func (conf *Config) Disabled() bool {
} }
func (conf *Config) PeerScoringParams() *pubsub.PeerScoreParams { func (conf *Config) PeerScoringParams() *pubsub.PeerScoreParams {
return conf.PeerScoring if conf.ScoringParams == nil {
return nil
}
return &conf.ScoringParams.PeerScoring
} }
func (conf *Config) BanPeers() bool { func (conf *Config) BanPeers() bool {
...@@ -150,10 +156,6 @@ func (conf *Config) BanDuration() time.Duration { ...@@ -150,10 +156,6 @@ func (conf *Config) BanDuration() time.Duration {
return conf.BanningDuration return conf.BanningDuration
} }
func (conf *Config) TopicScoringParams() *pubsub.TopicScoreParams {
return conf.TopicScoring
}
func (conf *Config) ReqRespSyncEnabled() bool { func (conf *Config) ReqRespSyncEnabled() bool {
return conf.EnableReqRespSync return conf.EnableReqRespSync
} }
......
...@@ -52,7 +52,6 @@ var MessageDomainValidSnappy = [4]byte{1, 0, 0, 0} ...@@ -52,7 +52,6 @@ var MessageDomainValidSnappy = [4]byte{1, 0, 0, 0}
type GossipSetupConfigurables interface { type GossipSetupConfigurables interface {
PeerScoringParams() *pubsub.PeerScoreParams PeerScoringParams() *pubsub.PeerScoreParams
TopicScoringParams() *pubsub.TopicScoreParams
// ConfigureGossip creates configuration options to apply to the GossipSub setup // ConfigureGossip creates configuration options to apply to the GossipSub setup
ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option
} }
...@@ -423,7 +422,7 @@ func (p *publisher) Close() error { ...@@ -423,7 +422,7 @@ func (p *publisher) Close() error {
return p.blocksTopic.Close() return p.blocksTopic.Close()
} }
func JoinGossip(p2pCtx context.Context, self peer.ID, topicScoreParams *pubsub.TopicScoreParams, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, error) { func JoinGossip(p2pCtx context.Context, self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, error) {
val := guardGossipValidator(log, logValidationResult(self, "validated block", log, BuildBlocksValidator(log, cfg, runCfg))) val := guardGossipValidator(log, logValidationResult(self, "validated block", log, BuildBlocksValidator(log, cfg, runCfg)))
blocksTopicName := blocksTopicV1(cfg) blocksTopicName := blocksTopicV1(cfg)
err := ps.RegisterTopicValidator(blocksTopicName, err := ps.RegisterTopicValidator(blocksTopicName,
...@@ -443,12 +442,6 @@ func JoinGossip(p2pCtx context.Context, self peer.ID, topicScoreParams *pubsub.T ...@@ -443,12 +442,6 @@ func JoinGossip(p2pCtx context.Context, self peer.ID, topicScoreParams *pubsub.T
} }
go LogTopicEvents(p2pCtx, log.New("topic", "blocks"), blocksTopicEvents) go LogTopicEvents(p2pCtx, log.New("topic", "blocks"), blocksTopicEvents)
if topicScoreParams != nil {
if err = blocksTopic.SetScoreParams(topicScoreParams); err != nil {
return nil, fmt.Errorf("failed to set topic score params: %w", err)
}
}
subscription, err := blocksTopic.Subscribe() subscription, err := blocksTopic.Subscribe()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to subscribe to blocks gossip topic: %w", err) return nil, fmt.Errorf("failed to subscribe to blocks gossip topic: %w", err)
......
...@@ -133,7 +133,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l ...@@ -133,7 +133,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l
if err != nil { if err != nil {
return fmt.Errorf("failed to start gossipsub router: %w", err) return fmt.Errorf("failed to start gossipsub router: %w", err)
} }
n.gsOut, err = JoinGossip(resourcesCtx, n.host.ID(), setup.TopicScoringParams(), n.gs, log, rollupCfg, runCfg, gossipIn) n.gsOut, err = JoinGossip(resourcesCtx, n.host.ID(), n.gs, log, rollupCfg, runCfg, gossipIn)
if err != nil { if err != nil {
return fmt.Errorf("failed to join blocks gossip topic: %w", err) return fmt.Errorf("failed to join blocks gossip topic: %w", err)
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"math" "math"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/rollup"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
) )
...@@ -12,6 +13,15 @@ import ( ...@@ -12,6 +13,15 @@ import (
// DecayToZero is the decay factor for a peer's score to zero. // DecayToZero is the decay factor for a peer's score to zero.
const DecayToZero = 0.01 const DecayToZero = 0.01
// MeshWeight is the weight of the mesh delivery topic.
const MeshWeight = -0.7
// MaxInMeshScore is the maximum score for being in the mesh.
const MaxInMeshScore = 10
// DecayEpoch is the number of epochs to decay the score over.
const DecayEpoch = time.Duration(5)
// ScoreDecay returns the decay factor for a given duration. // ScoreDecay returns the decay factor for a given duration.
func ScoreDecay(duration time.Duration, slot time.Duration) float64 { func ScoreDecay(duration time.Duration, slot time.Duration) float64 {
numOfTimes := duration / slot numOfTimes := duration / slot
...@@ -22,8 +32,8 @@ func ScoreDecay(duration time.Duration, slot time.Duration) float64 { ...@@ -22,8 +32,8 @@ func ScoreDecay(duration time.Duration, slot time.Duration) float64 {
// See [PeerScoreParams] for detailed documentation. // See [PeerScoreParams] for detailed documentation.
// //
// [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams // [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams
var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams { var LightPeerScoreParams = func(cfg *rollup.Config) pubsub.PeerScoreParams {
slot := time.Duration(blockTime) * time.Second slot := time.Duration(cfg.BlockTime) * time.Second
if slot == 0 { if slot == 0 {
slot = 2 * time.Second slot = 2 * time.Second
} }
...@@ -32,8 +42,29 @@ var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams { ...@@ -32,8 +42,29 @@ var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams {
epoch := 6 * slot epoch := 6 * slot
tenEpochs := 10 * epoch tenEpochs := 10 * epoch
oneHundredEpochs := 100 * epoch oneHundredEpochs := 100 * epoch
invalidDecayPeriod := 50 * epoch
return pubsub.PeerScoreParams{ return pubsub.PeerScoreParams{
Topics: make(map[string]*pubsub.TopicScoreParams), Topics: map[string]*pubsub.TopicScoreParams{
blocksTopicV1(cfg): {
TopicWeight: 0.8,
TimeInMeshWeight: MaxInMeshScore / inMeshCap(slot),
TimeInMeshQuantum: slot,
TimeInMeshCap: inMeshCap(slot),
FirstMessageDeliveriesWeight: 1,
FirstMessageDeliveriesDecay: ScoreDecay(20*epoch, slot),
FirstMessageDeliveriesCap: 23,
MeshMessageDeliveriesWeight: MeshWeight,
MeshMessageDeliveriesDecay: ScoreDecay(DecayEpoch*epoch, slot),
MeshMessageDeliveriesCap: float64(uint64(epoch/slot) * uint64(DecayEpoch)),
MeshMessageDeliveriesThreshold: float64(uint64(epoch/slot) * uint64(DecayEpoch) / 10),
MeshMessageDeliveriesWindow: 2 * time.Second,
MeshMessageDeliveriesActivation: 4 * epoch,
MeshFailurePenaltyWeight: MeshWeight,
MeshFailurePenaltyDecay: ScoreDecay(DecayEpoch*epoch, slot),
InvalidMessageDeliveriesWeight: -140.4475,
InvalidMessageDeliveriesDecay: ScoreDecay(invalidDecayPeriod, slot),
},
},
TopicScoreCap: 34, TopicScoreCap: 34,
AppSpecificScore: func(p peer.ID) float64 { AppSpecificScore: func(p peer.ID) float64 {
return 0 return 0
...@@ -51,65 +82,22 @@ var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams { ...@@ -51,65 +82,22 @@ var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams {
} }
} }
// DisabledPeerScoreParams is an instantiation of [pubsub.PeerScoreParams] where all scoring is disabled. // the cap for `inMesh` time scoring.
// See [PeerScoreParams] for detailed documentation. func inMeshCap(slot time.Duration) float64 {
// return float64((3600 * time.Second) / slot)
// [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams
var DisabledPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams {
slot := time.Duration(blockTime) * time.Second
if slot == 0 {
slot = 2 * time.Second
}
// We initialize an "epoch" as 6 blocks suggesting 6 blocks,
// each taking ~ 2 seconds, is 12 seconds
epoch := 6 * slot
tenEpochs := 10 * epoch
oneHundredEpochs := 100 * epoch
return pubsub.PeerScoreParams{
Topics: make(map[string]*pubsub.TopicScoreParams),
// 0 represent no cap
TopicScoreCap: 0,
AppSpecificScore: func(p peer.ID) float64 {
return 0
},
AppSpecificWeight: 1,
// ignore colocation scoring
IPColocationFactorWeight: 0,
IPColocationFactorWhitelist: nil,
// 0 disables the behaviour penalty
BehaviourPenaltyWeight: 0,
BehaviourPenaltyDecay: ScoreDecay(tenEpochs, slot),
DecayInterval: slot,
DecayToZero: DecayToZero,
RetainScore: oneHundredEpochs,
}
}
// PeerScoreParamsByName is a map of name to function that returns a [pubsub.PeerScoreParams] based on the provided [rollup.Config].
var PeerScoreParamsByName = map[string](func(blockTime uint64) pubsub.PeerScoreParams){
"light": LightPeerScoreParams,
"none": DisabledPeerScoreParams,
} }
// AvailablePeerScoreParams returns a list of available peer score params. func GetScoringParams(name string, cfg *rollup.Config) (*ScoringParams, error) {
// These can be used as an input to [GetPeerScoreParams] which returns the switch name {
// corresponding [pubsub.PeerScoreParams]. case "light":
func AvailablePeerScoreParams() []string { return &ScoringParams{
var params []string PeerScoring: LightPeerScoreParams(cfg),
for name := range PeerScoreParamsByName { }, nil
params = append(params, name) case "none":
return nil, nil
default:
return nil, fmt.Errorf("unknown p2p scoring level: %v", name)
} }
return params
}
// GetPeerScoreParams returns the [pubsub.PeerScoreParams] for the given name.
func GetPeerScoreParams(name string, blockTime uint64) (pubsub.PeerScoreParams, error) {
params, ok := PeerScoreParamsByName[name]
if !ok {
return pubsub.PeerScoreParams{}, fmt.Errorf("invalid params %s", name)
}
return params(blockTime), nil
} }
// NewPeerScoreThresholds returns a default [pubsub.PeerScoreThresholds]. // NewPeerScoreThresholds returns a default [pubsub.PeerScoreThresholds].
......
...@@ -2,10 +2,10 @@ package p2p ...@@ -2,10 +2,10 @@ package p2p
import ( import (
"math" "math"
"sort"
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
...@@ -25,14 +25,6 @@ func (testSuite *PeerParamsTestSuite) TestPeerScoreConstants() { ...@@ -25,14 +25,6 @@ func (testSuite *PeerParamsTestSuite) TestPeerScoreConstants() {
testSuite.Equal(0.01, DecayToZero) testSuite.Equal(0.01, DecayToZero)
} }
// TestAvailablePeerScoreParams validates the available peer score parameters.
func (testSuite *PeerParamsTestSuite) TestAvailablePeerScoreParams() {
available := AvailablePeerScoreParams()
sort.Strings(available)
expected := []string{"light", "none"}
testSuite.Equal(expected, available)
}
// TestNewPeerScoreThresholds validates the peer score thresholds. // TestNewPeerScoreThresholds validates the peer score thresholds.
// //
// This is tested to ensure that the thresholds are not modified and missed in review. // This is tested to ensure that the thresholds are not modified and missed in review.
...@@ -50,57 +42,17 @@ func (testSuite *PeerParamsTestSuite) TestNewPeerScoreThresholds() { ...@@ -50,57 +42,17 @@ func (testSuite *PeerParamsTestSuite) TestNewPeerScoreThresholds() {
} }
// TestGetPeerScoreParams validates the peer score parameters. // TestGetPeerScoreParams validates the peer score parameters.
func (testSuite *PeerParamsTestSuite) TestGetPeerScoreParams() { func (testSuite *PeerParamsTestSuite) TestGetPeerScoreParams_None() {
params, err := GetPeerScoreParams("light", 1) params, err := GetScoringParams("none", &chaincfg.Goerli)
testSuite.NoError(err) testSuite.NoError(err)
expected := LightPeerScoreParams(1) testSuite.Nil(params)
testSuite.Equal(expected.DecayInterval, params.DecayInterval)
testSuite.Equal(time.Duration(1)*time.Second, params.DecayInterval)
params, err = GetPeerScoreParams("none", 1)
testSuite.NoError(err)
expected = DisabledPeerScoreParams(1)
testSuite.Equal(expected.DecayInterval, params.DecayInterval)
testSuite.Equal(time.Duration(1)*time.Second, params.DecayInterval)
_, err = GetPeerScoreParams("invalid", 1)
testSuite.Error(err)
} }
// TestLightPeerScoreParams validates the light peer score params. // TestLightPeerScoreParams validates the light peer score params.
func (testSuite *PeerParamsTestSuite) TestLightPeerScoreParams() { func (testSuite *PeerParamsTestSuite) TestGetPeerScoreParams_Light() {
blockTime := uint64(1) cfg := chaincfg.Goerli
slot := time.Duration(blockTime) * time.Second cfg.BlockTime = 1
epoch := 6 * slot slot := time.Duration(cfg.BlockTime) * time.Second
oneHundredEpochs := 100 * epoch
// calculate the behavior penalty decay
duration := 10 * epoch
decay := math.Pow(DecayToZero, 1/float64(duration/slot))
testSuite.Equal(0.9261187281287935, decay)
// Test the params
params, err := GetPeerScoreParams("light", blockTime)
testSuite.NoError(err)
testSuite.Equal(params.Topics, make(map[string]*pubsub.TopicScoreParams))
testSuite.Equal(params.TopicScoreCap, float64(34))
// testSuite.Equal(params.AppSpecificScore("alice"), float(0))
testSuite.Equal(params.AppSpecificWeight, float64(1))
testSuite.Equal(params.IPColocationFactorWeight, float64(-35))
testSuite.Equal(params.IPColocationFactorThreshold, int(10))
testSuite.Nil(params.IPColocationFactorWhitelist)
testSuite.Equal(params.BehaviourPenaltyWeight, float64(-16))
testSuite.Equal(params.BehaviourPenaltyThreshold, float64(6))
testSuite.Equal(params.BehaviourPenaltyDecay, decay)
testSuite.Equal(params.DecayInterval, slot)
testSuite.Equal(params.DecayToZero, DecayToZero)
testSuite.Equal(params.RetainScore, oneHundredEpochs)
}
// TestDisabledPeerScoreParams validates the disabled peer score params.
func (testSuite *PeerParamsTestSuite) TestDisabledPeerScoreParams() {
blockTime := uint64(1)
slot := time.Duration(blockTime) * time.Second
epoch := 6 * slot epoch := 6 * slot
oneHundredEpochs := 100 * epoch oneHundredEpochs := 100 * epoch
...@@ -110,27 +62,33 @@ func (testSuite *PeerParamsTestSuite) TestDisabledPeerScoreParams() { ...@@ -110,27 +62,33 @@ func (testSuite *PeerParamsTestSuite) TestDisabledPeerScoreParams() {
testSuite.Equal(0.9261187281287935, decay) testSuite.Equal(0.9261187281287935, decay)
// Test the params // Test the params
params, err := GetPeerScoreParams("none", blockTime) scoringParams, err := GetScoringParams("light", &cfg)
peerParams := scoringParams.PeerScoring
testSuite.NoError(err) testSuite.NoError(err)
testSuite.Equal(params.Topics, make(map[string]*pubsub.TopicScoreParams)) // Topics should contain options for block topic
testSuite.Equal(params.TopicScoreCap, float64(0)) testSuite.Len(peerParams.Topics, 1)
testSuite.Equal(params.AppSpecificWeight, float64(1)) topicParams, ok := peerParams.Topics[blocksTopicV1(&cfg)]
testSuite.Equal(params.IPColocationFactorWeight, float64(0)) testSuite.True(ok, "should have block topic params")
testSuite.Nil(params.IPColocationFactorWhitelist) testSuite.NotZero(topicParams.TimeInMeshQuantum)
testSuite.Equal(params.BehaviourPenaltyWeight, float64(0)) testSuite.Equal(peerParams.TopicScoreCap, float64(34))
testSuite.Equal(params.BehaviourPenaltyDecay, decay) testSuite.Equal(peerParams.AppSpecificWeight, float64(1))
testSuite.Equal(params.DecayInterval, slot) testSuite.Equal(peerParams.IPColocationFactorWeight, float64(-35))
testSuite.Equal(params.DecayToZero, DecayToZero) testSuite.Equal(peerParams.IPColocationFactorThreshold, 10)
testSuite.Equal(params.RetainScore, oneHundredEpochs) testSuite.Nil(peerParams.IPColocationFactorWhitelist)
testSuite.Equal(peerParams.BehaviourPenaltyWeight, float64(-16))
testSuite.Equal(peerParams.BehaviourPenaltyThreshold, float64(6))
testSuite.Equal(peerParams.BehaviourPenaltyDecay, decay)
testSuite.Equal(peerParams.DecayInterval, slot)
testSuite.Equal(peerParams.DecayToZero, DecayToZero)
testSuite.Equal(peerParams.RetainScore, oneHundredEpochs)
} }
// TestParamsZeroBlockTime validates peer score params use default slot for 0 block time. // TestParamsZeroBlockTime validates peer score params use default slot for 0 block time.
func (testSuite *PeerParamsTestSuite) TestParamsZeroBlockTime() { func (testSuite *PeerParamsTestSuite) TestParamsZeroBlockTime() {
cfg := chaincfg.Goerli
cfg.BlockTime = 0
slot := 2 * time.Second slot := 2 * time.Second
params, err := GetPeerScoreParams("none", uint64(0)) params, err := GetScoringParams("light", &cfg)
testSuite.NoError(err)
testSuite.Equal(params.DecayInterval, slot)
params, err = GetPeerScoreParams("light", uint64(0))
testSuite.NoError(err) testSuite.NoError(err)
testSuite.Equal(params.DecayInterval, slot) testSuite.Equal(params.PeerScoring.DecayInterval, slot)
} }
...@@ -102,7 +102,8 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts [] ...@@ -102,7 +102,8 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
&rollup.Config{L2ChainID: big.NewInt(123)}, &rollup.Config{L2ChainID: big.NewInt(123)},
extPeerStore, testSuite.mockMetricer, logger) extPeerStore, testSuite.mockMetricer, logger)
opts = append(opts, ConfigurePeerScoring(&Config{ opts = append(opts, ConfigurePeerScoring(&Config{
PeerScoring: &pubsub.PeerScoreParams{ ScoringParams: &ScoringParams{
PeerScoring: pubsub.PeerScoreParams{
AppSpecificScore: func(p peer.ID) float64 { AppSpecificScore: func(p peer.ID) float64 {
if p == hosts[0].ID() { if p == hosts[0].ID() {
return -1000 return -1000
...@@ -114,6 +115,7 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts [] ...@@ -114,6 +115,7 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
DecayInterval: time.Second, DecayInterval: time.Second,
DecayToZero: 0.01, DecayToZero: 0.01,
}, },
},
}, scorer, logger)...) }, scorer, logger)...)
ps, err := pubsub.NewGossipSubWithRouter(ctx, h, rt, opts...) ps, err := pubsub.NewGossipSubWithRouter(ctx, h, rt, opts...)
if err != nil { if err != nil {
......
...@@ -85,10 +85,6 @@ func (p *Prepared) BanDuration() time.Duration { ...@@ -85,10 +85,6 @@ func (p *Prepared) BanDuration() time.Duration {
return 1 * time.Hour return 1 * time.Hour
} }
func (p *Prepared) TopicScoringParams() *pubsub.TopicScoreParams {
return nil
}
func (p *Prepared) Disabled() bool { func (p *Prepared) Disabled() bool {
return false return false
} }
......
...@@ -100,6 +100,12 @@ type peerRequest struct { ...@@ -100,6 +100,12 @@ type peerRequest struct {
complete *atomic.Bool complete *atomic.Bool
} }
type inFlightCheck struct {
num uint64
result chan bool
}
type SyncClientMetrics interface { type SyncClientMetrics interface {
ClientPayloadByNumberEvent(num uint64, resultCode byte, duration time.Duration) ClientPayloadByNumberEvent(num uint64, resultCode byte, duration time.Duration)
PayloadsQuarantineSize(n int) PayloadsQuarantineSize(n int)
...@@ -200,6 +206,7 @@ type SyncClient struct { ...@@ -200,6 +206,7 @@ type SyncClient struct {
requests chan rangeRequest requests chan rangeRequest
peerRequests chan peerRequest peerRequests chan peerRequest
inFlightChecks chan inFlightCheck
results chan syncResult results chan syncResult
...@@ -235,6 +242,7 @@ func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rc ...@@ -235,6 +242,7 @@ func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rc
requests: make(chan rangeRequest), // blocking requests: make(chan rangeRequest), // blocking
peerRequests: make(chan peerRequest, 128), peerRequests: make(chan peerRequest, 128),
results: make(chan syncResult, 128), results: make(chan syncResult, 128),
inFlightChecks: make(chan inFlightCheck, 128),
globalRL: rate.NewLimiter(globalServerBlocksRateLimit, globalServerBlocksBurst), globalRL: rate.NewLimiter(globalServerBlocksRateLimit, globalServerBlocksBurst),
resCtx: ctx, resCtx: ctx,
resCancel: cancel, resCancel: cancel,
...@@ -328,6 +336,14 @@ func (s *SyncClient) mainLoop() { ...@@ -328,6 +336,14 @@ func (s *SyncClient) mainLoop() {
ctx, cancel := context.WithTimeout(s.resCtx, maxResultProcessing) ctx, cancel := context.WithTimeout(s.resCtx, maxResultProcessing)
s.onResult(ctx, res) s.onResult(ctx, res)
cancel() cancel()
case check := <-s.inFlightChecks:
s.log.Info("Checking in flight", "num", check.num)
complete, ok := s.inFlight[check.num]
if !ok {
check.result <- false
} else {
check.result <- !complete.Load()
}
case <-s.resCtx.Done(): case <-s.resCtx.Done():
s.log.Info("stopped P2P req-resp L2 block sync client") s.log.Info("stopped P2P req-resp L2 block sync client")
return return
...@@ -335,6 +351,21 @@ func (s *SyncClient) mainLoop() { ...@@ -335,6 +351,21 @@ func (s *SyncClient) mainLoop() {
} }
} }
func (s *SyncClient) isInFlight(ctx context.Context, num uint64) (bool, error) {
check := inFlightCheck{num: num, result: make(chan bool, 1)}
select {
case s.inFlightChecks <- check:
case <-ctx.Done():
return false, errors.New("context cancelled when publishing in flight check")
}
select {
case res := <-check.result:
return res, nil
case <-ctx.Done():
return false, errors.New("context cancelled while waiting for in flight check response")
}
}
// onRangeRequest is exclusively called by the main loop, and has thus direct access to the request bookkeeping state. // onRangeRequest is exclusively called by the main loop, and has thus direct access to the request bookkeeping state.
// This function transforms requested block ranges into work for each peer. // This function transforms requested block ranges into work for each peer.
func (s *SyncClient) onRangeRequest(ctx context.Context, req rangeRequest) { func (s *SyncClient) onRangeRequest(ctx context.Context, req rangeRequest) {
......
...@@ -164,9 +164,13 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -164,9 +164,13 @@ func TestMultiPeerSync(t *testing.T) {
cfg, payloads := setupSyncTestData(100) cfg, payloads := setupSyncTestData(100)
// Buffered channel of all blocks requested from any client.
requested := make(chan uint64, 100)
setupPeer := func(ctx context.Context, h host.Host) (*SyncClient, chan *eth.ExecutionPayload) { setupPeer := func(ctx context.Context, h host.Host) (*SyncClient, chan *eth.ExecutionPayload) {
// Serving payloads: just load them from the map, if they exist // Serving payloads: just load them from the map, if they exist
servePayload := mockPayloadFn(func(n uint64) (*eth.ExecutionPayload, error) { servePayload := mockPayloadFn(func(n uint64) (*eth.ExecutionPayload, error) {
requested <- n
p, ok := payloads.getPayload(n) p, ok := payloads.getPayload(n)
if !ok { if !ok {
return nil, ethereum.NotFound return nil, ethereum.NotFound
...@@ -241,6 +245,20 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -241,6 +245,20 @@ func TestMultiPeerSync(t *testing.T) {
require.True(t, ok, "expecting known payload") require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload") require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
} }
// Wait for the request for block 25 to be made
ctx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
defer cancelFunc()
requestMade := false
for requestMade != true {
select {
case blockNum := <-requested:
if blockNum == 25 {
requestMade = true
}
case <-ctx.Done():
t.Fatal("Did not request block 25 in a reasonable time")
}
}
// the request for 25 should fail. See: // the request for 25 should fail. See:
// server: WARN peer requested unknown block by number num=25 // server: WARN peer requested unknown block by number num=25
// client: WARN failed p2p sync request num=25 err="peer failed to serve request with code 1" // client: WARN failed p2p sync request num=25 err="peer failed to serve request with code 1"
...@@ -251,7 +269,14 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -251,7 +269,14 @@ func TestMultiPeerSync(t *testing.T) {
// But the re-request checks the status in the main loop, and it may thus look like it's still in-flight, // But the re-request checks the status in the main loop, and it may thus look like it's still in-flight,
// and thus not run the new request. // and thus not run the new request.
// Wait till the failed request is recognized as marked as done, so the re-request actually runs. // Wait till the failed request is recognized as marked as done, so the re-request actually runs.
for !clB.inFlight[25].Load() { ctx, cancelFunc = context.WithTimeout(context.Background(), 30*time.Second)
defer cancelFunc()
for {
isInFlight, err := clB.isInFlight(ctx, 25)
require.NoError(t, err)
if !isInFlight {
break
}
time.Sleep(time.Second) time.Sleep(time.Second)
} }
// And request a range again, 25 is there now, and 21-24 should follow quickly (some may already have been fetched and wait in quarantine) // And request a range again, 25 is there now, and 21-24 should follow quickly (some may already have been fetched and wait in quarantine)
......
package p2p
import (
"fmt"
"time"
pubsub "github.com/libp2p/go-libp2p-pubsub"
)
// MeshWeight is the weight of the mesh delivery topic.
const MeshWeight = -0.7
// MaxInMeshScore is the maximum score for being in the mesh.
const MaxInMeshScore = 10
// DecayEpoch is the number of epochs to decay the score over.
const DecayEpoch = time.Duration(5)
// LightTopicScoreParams is a default instantiation of [pubsub.TopicScoreParams].
// See [TopicScoreParams] for detailed documentation.
//
// [TopicScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#TopicScoreParams
var LightTopicScoreParams = func(blockTime uint64) pubsub.TopicScoreParams {
slot := time.Duration(blockTime) * time.Second
if slot == 0 {
slot = 2 * time.Second
}
epoch := 6 * slot
invalidDecayPeriod := 50 * epoch
return pubsub.TopicScoreParams{
TopicWeight: 0.8,
TimeInMeshWeight: MaxInMeshScore / inMeshCap(slot),
TimeInMeshQuantum: slot,
TimeInMeshCap: inMeshCap(slot),
FirstMessageDeliveriesWeight: 1,
FirstMessageDeliveriesDecay: ScoreDecay(20*epoch, slot),
FirstMessageDeliveriesCap: 23,
MeshMessageDeliveriesWeight: MeshWeight,
MeshMessageDeliveriesDecay: ScoreDecay(DecayEpoch*epoch, slot),
MeshMessageDeliveriesCap: float64(uint64(epoch/slot) * uint64(DecayEpoch)),
MeshMessageDeliveriesThreshold: float64(uint64(epoch/slot) * uint64(DecayEpoch) / 10),
MeshMessageDeliveriesWindow: 2 * time.Second,
MeshMessageDeliveriesActivation: 4 * epoch,
MeshFailurePenaltyWeight: MeshWeight,
MeshFailurePenaltyDecay: ScoreDecay(DecayEpoch*epoch, slot),
InvalidMessageDeliveriesWeight: -140.4475,
InvalidMessageDeliveriesDecay: ScoreDecay(invalidDecayPeriod, slot),
}
}
// the cap for `inMesh` time scoring.
func inMeshCap(slot time.Duration) float64 {
return float64((3600 * time.Second) / slot)
}
// DisabledTopicScoreParams is an instantiation of [pubsub.TopicScoreParams] where all scoring is disabled.
// See [TopicScoreParams] for detailed documentation.
//
// [TopicScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#TopicScoreParams
var DisabledTopicScoreParams = func(blockTime uint64) pubsub.TopicScoreParams {
slot := time.Duration(blockTime) * time.Second
if slot == 0 {
slot = 2 * time.Second
}
epoch := 6 * slot
invalidDecayPeriod := 50 * epoch
return pubsub.TopicScoreParams{
TopicWeight: 0, // disabled
TimeInMeshWeight: 0, // disabled
TimeInMeshQuantum: slot,
TimeInMeshCap: inMeshCap(slot),
FirstMessageDeliveriesWeight: 0, // disabled
FirstMessageDeliveriesDecay: ScoreDecay(20*epoch, slot),
FirstMessageDeliveriesCap: 23,
MeshMessageDeliveriesWeight: 0, // disabled
MeshMessageDeliveriesDecay: ScoreDecay(DecayEpoch*epoch, slot),
MeshMessageDeliveriesCap: float64(uint64(epoch/slot) * uint64(DecayEpoch)),
MeshMessageDeliveriesThreshold: float64(uint64(epoch/slot) * uint64(DecayEpoch) / 10),
MeshMessageDeliveriesWindow: 2 * time.Second,
MeshMessageDeliveriesActivation: 4 * epoch,
MeshFailurePenaltyWeight: 0, // disabled
MeshFailurePenaltyDecay: ScoreDecay(DecayEpoch*epoch, slot),
InvalidMessageDeliveriesWeight: 0, // disabled
InvalidMessageDeliveriesDecay: ScoreDecay(invalidDecayPeriod, slot),
}
}
// TopicScoreParamsByName is a map of name to [pubsub.TopicScoreParams].
var TopicScoreParamsByName = map[string](func(blockTime uint64) pubsub.TopicScoreParams){
"light": LightTopicScoreParams,
"none": DisabledTopicScoreParams,
}
// AvailableTopicScoreParams returns a list of available topic score params.
// These can be used as an input to [GetTopicScoreParams] which returns the
// corresponding [pubsub.TopicScoreParams].
func AvailableTopicScoreParams() []string {
var params []string
for name := range TopicScoreParamsByName {
params = append(params, name)
}
return params
}
// GetTopicScoreParams returns the [pubsub.TopicScoreParams] for the given name.
func GetTopicScoreParams(name string, blockTime uint64) (pubsub.TopicScoreParams, error) {
params, ok := TopicScoreParamsByName[name]
if !ok {
return pubsub.TopicScoreParams{}, fmt.Errorf("invalid topic params %s", name)
}
return params(blockTime), nil
}
...@@ -142,7 +142,7 @@ func (cfg *Config) CheckL1ChainID(ctx context.Context, client L1Client) error { ...@@ -142,7 +142,7 @@ func (cfg *Config) CheckL1ChainID(ctx context.Context, client L1Client) error {
return err return err
} }
if cfg.L1ChainID.Cmp(id) != 0 { if cfg.L1ChainID.Cmp(id) != 0 {
return fmt.Errorf("incorrect L1 RPC chain id %d, expected %d", cfg.L1ChainID, id) return fmt.Errorf("incorrect L1 RPC chain id %d, expected %d", id, cfg.L1ChainID)
} }
return nil return nil
} }
...@@ -154,7 +154,7 @@ func (cfg *Config) CheckL1GenesisBlockHash(ctx context.Context, client L1Client) ...@@ -154,7 +154,7 @@ func (cfg *Config) CheckL1GenesisBlockHash(ctx context.Context, client L1Client)
return err return err
} }
if l1GenesisBlockRef.Hash != cfg.Genesis.L1.Hash { if l1GenesisBlockRef.Hash != cfg.Genesis.L1.Hash {
return fmt.Errorf("incorrect L1 genesis block hash %d, expected %d", cfg.Genesis.L1.Hash, l1GenesisBlockRef.Hash) return fmt.Errorf("incorrect L1 genesis block hash %s, expected %s", l1GenesisBlockRef.Hash, cfg.Genesis.L1.Hash)
} }
return nil return nil
} }
...@@ -171,7 +171,7 @@ func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error { ...@@ -171,7 +171,7 @@ func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error {
return err return err
} }
if cfg.L2ChainID.Cmp(id) != 0 { if cfg.L2ChainID.Cmp(id) != 0 {
return fmt.Errorf("incorrect L2 RPC chain id, expected from config %d, obtained from client %d", cfg.L2ChainID, id) return fmt.Errorf("incorrect L2 RPC chain id %d, expected %d", id, cfg.L2ChainID)
} }
return nil return nil
} }
...@@ -183,7 +183,7 @@ func (cfg *Config) CheckL2GenesisBlockHash(ctx context.Context, client L2Client) ...@@ -183,7 +183,7 @@ func (cfg *Config) CheckL2GenesisBlockHash(ctx context.Context, client L2Client)
return err return err
} }
if l2GenesisBlockRef.Hash != cfg.Genesis.L2.Hash { if l2GenesisBlockRef.Hash != cfg.Genesis.L2.Hash {
return fmt.Errorf("incorrect L2 genesis block hash %d, expected %d", cfg.Genesis.L2.Hash, l2GenesisBlockRef.Hash) return fmt.Errorf("incorrect L2 genesis block hash %s, expected %s", l2GenesisBlockRef.Hash, cfg.Genesis.L2.Hash)
} }
return nil return nil
} }
......
...@@ -43,7 +43,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -43,7 +43,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
return nil, fmt.Errorf("failed to load p2p signer: %w", err) return nil, fmt.Errorf("failed to load p2p signer: %w", err)
} }
p2pConfig, err := p2pcli.NewConfig(ctx, rollupConfig.BlockTime) p2pConfig, err := p2pcli.NewConfig(ctx, rollupConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load p2p config: %w", err) return nil, fmt.Errorf("failed to load p2p config: %w", err)
} }
......
...@@ -266,15 +266,14 @@ type blockHashParameter struct { ...@@ -266,15 +266,14 @@ type blockHashParameter struct {
func unusableMethod(err error) bool { func unusableMethod(err error) bool {
if rpcErr, ok := err.(rpc.Error); ok { if rpcErr, ok := err.(rpc.Error); ok {
code := rpcErr.ErrorCode() code := rpcErr.ErrorCode()
// method not found, or invalid params // invalid request, method not found, or invalid params
if code == -32601 || code == -32602 { if code == -32600 || code == -32601 || code == -32602 {
return true return true
} }
} else {
errText := strings.ToLower(err.Error())
if strings.Contains(errText, "unknown method") || strings.Contains(errText, "invalid param") || strings.Contains(errText, "is not available") {
return true
} }
} errText := strings.ToLower(err.Error())
return false return strings.Contains(errText, "unsupported method") || // alchemy -32600 message
strings.Contains(errText, "unknown method") ||
strings.Contains(errText, "invalid param") ||
strings.Contains(errText, "is not available")
} }
#!/usr/bin/env bash
# This script starts a local devnet using Docker Compose. We have to use
# this more complicated Bash script rather than Compose's native orchestration
# tooling because we need to start each service in a specific order, and specify
# their configuration along the way. The order is:
#
# 1. Start L1.
# 2. Compile contracts.
# 3. Deploy the contracts to L1 if necessary.
# 4. Start L2, inserting the compiled contract artifacts into the genesis.
# 5. Get the genesis hashes and timestamps from L1/L2.
# 6. Generate the rollup driver's config using the genesis hashes and the
# timestamps recovered in step 4 as well as the address of the OptimismPortal
# contract deployed in step 3.
# 7. Start the rollup driver.
# 8. Start the L2 output submitter.
#
# The timestamps are critically important here, since the rollup driver will fill in
# empty blocks if the tip of L1 lags behind the current timestamp. This can lead to
# a perceived infinite loop. To get around this, we set the timestamp to the current
# time in this script.
#
# This script is safe to run multiple times. It stores state in `.devnet`, and
# contracts-bedrock/deployments/devnetL1.
#
# Don't run this script directly. Run it using the makefile, e.g. `make devnet-up`.
# To clean up your devnet, run `make devnet-clean`.
set -eu
L1_URL="http://localhost:8545"
L2_URL="http://localhost:9545"
OP_NODE="$PWD/op-node"
CONTRACTS_BEDROCK="$PWD/packages/contracts-bedrock"
NETWORK=devnetL1
DEVNET="$PWD/.devnet"
# Helper method that waits for a given URL to be up. Can't use
# cURL's built-in retry logic because connection reset errors
# are ignored unless you're using a very recent version of cURL
function wait_up {
echo -n "Waiting for $1 to come up..."
i=0
until curl -s -f -o /dev/null "$1"
do
echo -n .
sleep 0.25
((i=i+1))
if [ "$i" -eq 300 ]; then
echo " Timeout!" >&2
exit 1
fi
done
echo "Done!"
}
mkdir -p ./.devnet
# Regenerate the L1 genesis file if necessary. The existence of the genesis
# file is used to determine if we need to recreate the devnet's state folder.
if [ ! -f "$DEVNET/done" ]; then
echo "Regenerating genesis files"
TIMESTAMP=$(date +%s | xargs printf '0x%x')
cat "$CONTRACTS_BEDROCK/deploy-config/devnetL1.json" | jq -r ".l1GenesisBlockTimestamp = \"$TIMESTAMP\"" > /tmp/bedrock-devnet-deploy-config.json
(
cd "$OP_NODE"
go run cmd/main.go genesis devnet \
--deploy-config /tmp/bedrock-devnet-deploy-config.json \
--outfile.l1 $DEVNET/genesis-l1.json \
--outfile.l2 $DEVNET/genesis-l2.json \
--outfile.rollup $DEVNET/rollup.json
touch "$DEVNET/done"
)
fi
# Bring up L1.
(
cd ops-bedrock
echo "Bringing up L1..."
DOCKER_BUILDKIT=1 docker-compose build --progress plain
docker-compose up -d l1
wait_up $L1_URL
)
# Bring up L2.
(
cd ops-bedrock
echo "Bringing up L2..."
docker-compose up -d l2
wait_up $L2_URL
)
L2OO_ADDRESS="0x6900000000000000000000000000000000000000"
# Bring up everything else.
(
cd ops-bedrock
echo "Bringing up devnet..."
L2OO_ADDRESS="$L2OO_ADDRESS" \
docker-compose up -d op-proposer op-batcher
echo "Bringing up stateviz webserver..."
docker-compose up -d stateviz
)
echo "Devnet ready."
...@@ -89,3 +89,7 @@ ENTRYPOINT ["npm", "run", "start:drippie-mon"] ...@@ -89,3 +89,7 @@ ENTRYPOINT ["npm", "run", "start:drippie-mon"]
FROM base as wd-mon FROM base as wd-mon
WORKDIR /opt/optimism/packages/chain-mon WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["yarn", "run", "start:wd-mon"] ENTRYPOINT ["yarn", "run", "start:wd-mon"]
FROM base as wallet-mon
WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["yarn", "run", "start:wallet-mon"]
FROM debian:bullseye-20220822-slim as foundry-build
SHELL ["/bin/bash", "-c"]
WORKDIR /opt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y curl build-essential git && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
chmod +x ./rustup.sh && \
./rustup.sh -y
WORKDIR /opt/foundry
# Only diff from upstream docker image is this clone instead
# of COPY. We select a specific commit to use.
RUN git clone https://github.com/foundry-rs/foundry.git . \
&& git checkout da2392e58bb8a7fefeba46b40c4df1afad8ccd22
RUN source $HOME/.profile && \
cargo build --release && \
strip /opt/foundry/target/release/forge && \
strip /opt/foundry/target/release/cast && \
strip /opt/foundry/target/release/anvil
FROM ethereum/client-go:alltools-v1.10.25 as geth
FROM ghcr.io/crytic/echidna/echidna:v2.0.4 as echidna-test
FROM python:3.8.13-slim-bullseye
ENV GOPATH=/go
ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH
ENV DEBIAN_FRONTEND=noninteractive
COPY --from=foundry-build /opt/foundry/target/release/forge /usr/local/bin/forge
COPY --from=foundry-build /opt/foundry/target/release/cast /usr/local/bin/cast
COPY --from=foundry-build /opt/foundry/target/release/anvil /usr/local/bin/anvil
COPY --from=geth /usr/local/bin/abigen /usr/local/bin/abigen
COPY --from=echidna-test /usr/local/bin/echidna-test /usr/local/bin/echidna-test
COPY check-changed.sh /usr/local/bin/check-changed
RUN apt-get update && \
apt-get install -y bash curl openssh-client git build-essential ca-certificates jq musl gnupg coreutils && \
curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh && \
curl -sL https://go.dev/dl/go1.19.linux-amd64.tar.gz -o go1.19.linux-amd64.tar.gz && \
tar -C /usr/local/ -xzvf go1.19.linux-amd64.tar.gz && \
ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt && \
bash nodesource_setup.sh && \
apt-get install -y nodejs && \
npm i -g npm@8.11.0 \
npm i -g yarn && \
npm i -g depcheck && \
pip install slither-analyzer==0.9.3 && \
go install gotest.tools/gotestsum@latest && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.48.0 && \
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash && \
chmod +x /usr/local/bin/check-changed
RUN echo "downloading solidity compilers" && \
curl -o solc-linux-amd64-v0.5.17+commit.d19bba13 -sL https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.5.17+commit.d19bba13 && \
curl -o solc-linux-amd64-v0.8.9+commit.e5eed63a -sL https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.8.9+commit.e5eed63a && \
curl -o solc-linux-amd64-v0.8.10+commit.fc410830 -sL https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.8.10+commit.fc410830 && \
curl -o solc-linux-amd64-v0.8.12+commit.f00d7308 -sL https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.8.12+commit.f00d7308 && \
echo "verifying checksums" && \
(echo "c35ce7a4d3ffa5747c178b1e24c8541b2e5d8a82c1db3719eb4433a1f19e16f3 solc-linux-amd64-v0.5.17+commit.d19bba13" | sha256sum --check --status - || exit 1) && \
(echo "f851f11fad37496baabaf8d6cb5c057ca0d9754fddb7a351ab580d7fd728cb94 solc-linux-amd64-v0.8.9+commit.e5eed63a" | sha256sum --check --status - || exit 1) && \
(echo "c7effacf28b9d64495f81b75228fbf4266ac0ec87e8f1adc489ddd8a4dd06d89 solc-linux-amd64-v0.8.10+commit.fc410830" | sha256sum --check --status - || exit 1) && \
(echo "556c3ec44faf8ff6b67933fa8a8a403abe82c978d6e581dbfec4bd07360bfbf3 solc-linux-amd64-v0.8.12+commit.f00d7308" | sha256sum --check --status - || exit 1) && \
echo "caching compilers" && \
mkdir -p ~/.cache/hardhat-nodejs/compilers/linux-amd64 && \
cp solc-linux-amd64-v0.5.17+commit.d19bba13 ~/.cache/hardhat-nodejs/compilers/linux-amd64/ && \
cp solc-linux-amd64-v0.8.9+commit.e5eed63a ~/.cache/hardhat-nodejs/compilers/linux-amd64/ && \
cp solc-linux-amd64-v0.8.10+commit.fc410830 ~/.cache/hardhat-nodejs/compilers/linux-amd64/ && \
cp solc-linux-amd64-v0.8.12+commit.f00d7308 ~/.cache/hardhat-nodejs/compilers/linux-amd64/ && \
mkdir -p ~/.svm/0.5.17 && \
cp solc-linux-amd64-v0.5.17+commit.d19bba13 ~/.svm/0.5.17/solc-0.5.17 && \
mkdir -p ~/.svm/0.8.9 && \
cp solc-linux-amd64-v0.8.9+commit.e5eed63a ~/.svm/0.8.9/solc-0.8.9 && \
mkdir -p ~/.svm/0.8.10 && \
cp solc-linux-amd64-v0.8.10+commit.fc410830 ~/.svm/0.8.10/solc-0.8.10 && \
mkdir -p ~/.svm/0.8.12 && \
cp solc-linux-amd64-v0.8.12+commit.f00d7308 ~/.svm/0.8.12/solc-0.8.12 && \
rm solc-linux-amd64-v0.5.17+commit.d19bba13 && \
rm solc-linux-amd64-v0.8.9+commit.e5eed63a && \
rm solc-linux-amd64-v0.8.10+commit.fc410830 && \
rm solc-linux-amd64-v0.8.12+commit.f00d7308
RUN echo "downloading and verifying Codecov uploader" && \
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import && \
curl -Os "https://uploader.codecov.io/latest/linux/codecov" && \
curl -Os "https://uploader.codecov.io/latest/linux/codecov.SHA256SUM" && \
curl -Os "https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig" && \
gpgv codecov.SHA256SUM.sig codecov.SHA256SUM && \
shasum -a 256 -c codecov.SHA256SUM || sha256sum -c codecov.SHA256SUM && \
cp codecov /usr/local/bin/codecov && \
chmod +x /usr/local/bin/codecov && \
rm codecov
RUN echo "downloading mockery tool" && \
mkdir -p mockery-tmp-dir && \
curl -o mockery-tmp-dir/mockery.tar.gz -sL https://github.com/vektra/mockery/releases/download/v2.28.1/mockery_2.28.1_Linux_x86_64.tar.gz && \
tar -xzvf mockery-tmp-dir/mockery.tar.gz -C mockery-tmp-dir && \
cp mockery-tmp-dir/mockery /usr/local/bin/mockery && \
chmod +x /usr/local/bin/mockery && \
rm -rf mockery-tmp-dir
#!/usr/bin/env bash
set -euo pipefail
DOCKER_REPO=$1
GIT_TAG=$2
GIT_SHA=$3
IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(fault-detector|proxyd|indexer|op-[a-z0-9\-]*)' || true)
if [ -z "$IMAGE_NAME" ]; then
echo "image name could not be parsed from git tag '$GIT_TAG'"
exit 1
fi
IMAGE_TAG=$(echo "$GIT_TAG" | grep -Eow 'v.*' || true)
if [ -z "$IMAGE_TAG" ]; then
echo "image tag could not be parsed from git tag '$GIT_TAG'"
exit 1
fi
SOURCE_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$GIT_SHA"
TARGET_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$IMAGE_TAG"
TARGET_IMAGE_TAG_LATEST="$DOCKER_REPO/$IMAGE_NAME:latest"
echo "Checking if docker images exist for '$IMAGE_NAME'"
echo ""
tags=$(gcloud container images list-tags "$DOCKER_REPO/$IMAGE_NAME" --limit 1 --format json)
if [ "$tags" = "[]" ]; then
echo "No existing docker images were found for '$IMAGE_NAME'. The code tagged with '$GIT_TAG' may not have an associated dockerfile or docker build job."
echo "If this service has a dockerfile, add a docker-publish job for it in the circleci config."
echo ""
echo "Exiting"
exit 0
fi
echo "Tagging $SOURCE_IMAGE_TAG with '$IMAGE_TAG'"
gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG"
# Do not tag with latest if the release is a release candidate.
if [[ "$IMAGE_TAG" == *"rc"* ]]; then
echo "Not tagging with 'latest' because the release is a release candidate."
exit 0
fi
echo "Tagging $SOURCE_IMAGE_TAG with 'latest'"
gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG_LATEST"
const os = require('os')
// this script unbundles the published packages output
// from changesets action to a key-value pair to be used
// with our publishing CI workflow
data = process.argv[2]
data = JSON.parse(data)
for (const i of data) {
const name = i.name.replace("@eth-optimism/", "")
const version = i.version
process.stdout.write(`::set-output name=${name}::${version}` + os.EOL)
}
#!/usr/bin/env python3
import json
import subprocess
import os
GETH_VERSION='v1.11.2'
def main():
for project in ('.', 'indexer'):
print(f'Updating {project}...')
update_mod(project)
def update_mod(project):
print('Replacing...')
subprocess.run([
'go',
'mod',
'edit',
'-replace',
f'github.com/ethereum/go-ethereum@{GETH_VERSION}=github.com/ethereum-optimism/op-geth@optimism'
], cwd=os.path.join(project), check=True)
print('Tidying...')
subprocess.run([
'go',
'mod',
'tidy'
], cwd=os.path.join(project), check=True)
if __name__ == '__main__':
main()
...@@ -8,6 +8,17 @@ BALANCE_MON__RPC= ...@@ -8,6 +8,17 @@ BALANCE_MON__RPC=
# JSON array in the format [{ "address": <address>, "nickname": <nickname> }, ... ] # JSON array in the format [{ "address": <address>, "nickname": <nickname> }, ... ]
BALANCE_MON__ACCOUNTS= BALANCE_MON__ACCOUNTS=
###############################################################################
# ↓ wallet-mon ↓ #
###############################################################################
# RPC pointing to network to monitor
WALLET_MON__RPC=
# The block number to start monitoring from
# Defaults to the first bedrock block if unset.
WALLET_MON__START_BLOCK_NUMBER=
############################################################################### ###############################################################################
# ↓ drippie-mon ↓ # # ↓ drippie-mon ↓ #
############################################################################### ###############################################################################
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
], ],
"scripts": { "scripts": {
"start:balance-mon": "ts-node ./src/balance-mon/service.ts", "start:balance-mon": "ts-node ./src/balance-mon/service.ts",
"start:wallet-mon": "ts-node ./src/wallet-mon/service.ts",
"start:drippie-mon": "ts-node ./src/drippie-mon/service.ts", "start:drippie-mon": "ts-node ./src/drippie-mon/service.ts",
"start:wd-mon": "ts-node ./src/wd-mon/service.ts", "start:wd-mon": "ts-node ./src/wd-mon/service.ts",
"test:coverage": "echo 'No tests defined.'", "test:coverage": "echo 'No tests defined.'",
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.1", "@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/contracts-periphery": "1.0.8", "@eth-optimism/contracts-periphery": "1.0.8",
"@eth-optimism/contracts-bedrock": "0.14.0",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.1.0", "@eth-optimism/sdk": "2.1.0",
"ethers": "^5.7.0", "ethers": "^5.7.0",
......
export * from './balance-mon/service' export * from './balance-mon/service'
export * from './drippie-mon/service' export * from './drippie-mon/service'
export * from './wd-mon/service' export * from './wd-mon/service'
export * from './wallet-mon/service'
import {
BaseServiceV2,
StandardOptions,
Gauge,
Counter,
validators,
waitForProvider,
} from '@eth-optimism/common-ts'
import { getChainId, compareAddrs } from '@eth-optimism/core-utils'
import { Provider } from '@ethersproject/abstract-provider'
import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json'
import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
import l2OutputOracleArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L2OutputOracleProxy.json'
import { version } from '../../package.json'
const networks = {
1: {
name: 'mainnet',
l1StartingBlockTag: mainnetConfig.l1StartingBlockTag,
accounts: [
{
label: 'Proposer',
wallet: mainnetConfig.l2OutputOracleProposer,
target: l2OutputOracleArtifactsMainnet.address,
},
{
label: 'Batcher',
wallet: mainnetConfig.batchSenderAddress,
target: mainnetConfig.batchInboxAddress,
},
],
},
10: {
name: 'goerli',
l1StartingBlockTag: goerliConfig.l1StartingBlockTag,
accounts: [
{
label: 'Proposer',
wallet: goerliConfig.l2OutputOracleProposer,
target: l2OutputOracleArtifactsGoerli.address,
},
{
label: 'Batcher',
wallet: goerliConfig.batchSenderAddress,
target: goerliConfig.batchInboxAddress,
},
],
},
}
type WalletMonOptions = {
rpc: Provider
startBlockNumber: number
}
type WalletMonMetrics = {
validatedCalls: Counter
unexpectedCalls: Counter
unexpectedRpcErrors: Counter
}
type WalletMonState = {
chainId: number
highestUncheckedBlockNumber: number
}
export class WalletMonService extends BaseServiceV2<
WalletMonOptions,
WalletMonMetrics,
WalletMonState
> {
constructor(options?: Partial<WalletMonOptions & StandardOptions>) {
super({
version,
name: 'wallet-mon',
loop: true,
options: {
loopIntervalMs: 1000,
...options,
},
optionsSpec: {
rpc: {
validator: validators.provider,
desc: 'Provider for network to monitor balances on',
},
startBlockNumber: {
validator: validators.num,
default: -1,
desc: 'L1 block number to start checking from',
public: true,
},
},
metricsSpec: {
validatedCalls: {
type: Gauge,
desc: 'Transactions from the account checked',
labels: ['wallet', 'target', 'nickname'],
},
unexpectedCalls: {
type: Counter,
desc: 'Number of unexpected wallets',
labels: ['wallet', 'target', 'nickname'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
labels: ['section', 'name'],
},
},
})
}
protected async init(): Promise<void> {
// Connect to L1.
await waitForProvider(this.options.rpc, {
logger: this.logger,
name: 'L1',
})
this.state.chainId = await getChainId(this.options.rpc)
const l1StartingBlockTag = networks[this.state.chainId].l1StartingBlockTag
if (this.options.startBlockNumber === -1) {
const block = await this.options.rpc.getBlock(l1StartingBlockTag)
this.state.highestUncheckedBlockNumber = block.number
} else {
this.state.highestUncheckedBlockNumber = this.options.startBlockNumber
}
}
protected async main(): Promise<void> {
if (
(await this.options.rpc.getBlockNumber()) <
this.state.highestUncheckedBlockNumber
) {
this.logger.info('Waiting for new blocks')
return
}
const network = networks[this.state.chainId]
const accounts = network.accounts
const block = await this.options.rpc.getBlock(
this.state.highestUncheckedBlockNumber
)
this.logger.info('Checking block', {
number: block.number,
})
const transactions = []
for (const txHash of block.transactions) {
const t = await this.options.rpc.getTransaction(txHash)
transactions.push(t)
}
for (const transaction of transactions) {
for (const account of accounts) {
if (compareAddrs(account.wallet, transaction.from)) {
if (compareAddrs(account.target, transaction.to)) {
this.metrics.validatedCalls.inc({
nickname: account.label,
wallet: account.address,
target: account.target,
})
this.logger.info('validated call', {
nickname: account.label,
wallet: account.address,
target: account.target,
})
} else {
this.metrics.unexpectedCalls.inc({
nickname: account.label,
wallet: account.address,
target: transaction.to,
})
this.logger.error('Unexpected call detected', {
nickname: account.label,
address: account.address,
target: transaction.to,
})
}
}
}
}
this.logger.info('Checked block', {
number: this.state.highestUncheckedBlockNumber,
})
this.state.highestUncheckedBlockNumber++
}
}
if (require.main === module) {
const service = new WalletMonService()
service.run()
}
This diff is collapsed.
...@@ -254,15 +254,12 @@ ...@@ -254,15 +254,12 @@
======================= =======================
| Name | Type | Slot | Offset | Bytes | Contract | | Name | Type | Slot | Offset | Bytes | Contract |
|--------------|-------------------------------------------------|------|--------|-------|-------------------------------------------------------------| |-----------------|--------------------------------------------|------|--------|-------|-------------------------------------------------------------|
| _owner | address | 0 | 0 | 20 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | _initialized | uint8 | 0 | 0 | 1 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| gameImpls | mapping(enum GameType => contract IDisputeGame) | 1 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | _initializing | bool | 0 | 1 | 1 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGames | mapping(Hash => contract IDisputeGame) | 2 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory | | __gap | uint256[50] | 1 | 0 | 1600 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| _owner | address | 51 | 0 | 20 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
======================= | __gap | uint256[49] | 52 | 0 | 1568 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
➡ contracts/dispute/BondManager.sol:BondManager | gameImpls | mapping(GameType => contract IDisputeGame) | 101 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
======================= | disputeGames | mapping(Hash => contract IDisputeGame) | 102 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGameList | contract IDisputeGame[] | 103 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| Name | Type | Slot | Offset | Bytes | Contract |
|-------|---------------------------------------------|------|--------|-------|-----------------------------------------------|
| bonds | mapping(bytes32 => struct BondManager.Bond) | 0 | 0 | 32 | contracts/dispute/BondManager.sol:BondManager |
...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
} }
/** /**
* @custom:semver 1.6.0 * @custom:semver 1.7.0
* *
* @param _l2Oracle Address of the L2OutputOracle contract. * @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals. * @param _guardian Address that can pause deposits and withdrawals.
...@@ -152,7 +152,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -152,7 +152,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
address _guardian, address _guardian,
bool _paused, bool _paused,
SystemConfig _config SystemConfig _config
) Semver(1, 6, 0) { ) Semver(1, 7, 0) {
L2_ORACLE = _l2Oracle; L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian; GUARDIAN = _guardian;
SYSTEM_CONFIG = _config; SYSTEM_CONFIG = _config;
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { OptimismPortal } from "../L1/OptimismPortal.sol";
/**
* @title PortalSender
* @notice The PortalSender is a simple intermediate contract that will transfer the balance of the
* L1StandardBridge to the OptimismPortal during the Bedrock migration.
*/
contract PortalSender {
/**
* @notice Address of the OptimismPortal contract.
*/
OptimismPortal public immutable PORTAL;
/**
* @param _portal Address of the OptimismPortal contract.
*/
constructor(OptimismPortal _portal) {
PORTAL = _portal;
}
/**
* @notice Sends balance of this contract to the OptimismPortal.
*/
function donate() public {
PORTAL.donateETH{ value: address(this).balance }();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IBondManager } from "./interfaces/IBondManager.sol";
/**
* @title BondManager
* @notice The Bond Manager serves as an escrow for permissionless output proposal bonds.
*/
contract BondManager is IBondManager {
/**
* @notice The Bond Type
*/
struct Bond {
address owner;
bytes32 id;
uint128 expiration;
uint128 amount;
}
/**
* @notice The permissioned dispute game factory.
* @dev Used to verify the status of bonds.
*/
IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY;
/**
* @notice Amount of gas used to transfer ether when splitting the bond.
* This is a reasonable amount of gas for a transfer, even to a smart contract.
* The number of participants is bound of by the block gas limit.
*/
uint256 private constant TRANSFER_GAS = 30_000;
/**
* @notice Mapping from bondId to bond.
*/
mapping(bytes32 => Bond) public bonds;
/**
* @notice Instantiates the bond maanger with the registered dispute game factory.
* @param _disputeGameFactory is the dispute game factory.
*/
constructor(IDisputeGameFactory _disputeGameFactory) {
DISPUTE_GAME_FACTORY = _disputeGameFactory;
}
/**
* @inheritdoc IBondManager
*/
function post(
bytes32 _bondId,
address _bondOwner,
uint128 _minClaimHold
) external payable {
require(bonds[_bondId].owner == address(0), "BondManager: BondId already posted.");
require(_bondOwner != address(0), "BondManager: Owner cannot be the zero address.");
require(msg.value > 0, "BondManager: Value must be non-zero.");
uint128 expiration = uint128(_minClaimHold + block.timestamp);
bonds[_bondId] = Bond({
owner: _bondOwner,
id: _bondId,
expiration: expiration,
amount: uint128(msg.value)
});
emit BondPosted(_bondId, _bondOwner, expiration, msg.value);
}
/**
* @inheritdoc IBondManager
*/
function seize(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameType.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
/**
* @inheritdoc IBondManager
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameType.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
uint256 len = _claimRecipients.length;
uint256 proportionalAmount = b.amount / len;
// Send the proportional amount to each recipient. Do not revert if a send fails as that
// will prevent other recipients from receiving their share.
for (uint256 i; i < len; i++) {
SafeCall.send({
_target: payable(_claimRecipients[i]),
_gas: TRANSFER_GAS,
_value: proportionalAmount
});
}
}
/**
* @inheritdoc IBondManager
*/
function reclaim(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner == msg.sender, "BondManager: Unauthorized claimant.");
require(b.expiration <= block.timestamp, "BondManager: Bond isn't claimable yet.");
delete bonds[_bondId];
emit BondReclaimed(_bondId, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
}
...@@ -4,17 +4,20 @@ pragma solidity ^0.8.15; ...@@ -4,17 +4,20 @@ pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol"; import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol"; import "../libraries/DisputeErrors.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol"; import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol"; import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol"; import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IVersioned } from "./interfaces/IVersioned.sol";
/** /**
* @title DisputeGameFactory * @title DisputeGameFactory
* @notice A factory contract for creating `IDisputeGame` contracts. * @notice A factory contract for creating `IDisputeGame` contracts.
*/ */
contract DisputeGameFactory is Ownable, IDisputeGameFactory { contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersioned {
/** /**
* @dev Allows for the creation of clone proxies with immutable arguments. * @dev Allows for the creation of clone proxies with immutable arguments.
*/ */
...@@ -32,12 +35,41 @@ contract DisputeGameFactory is Ownable, IDisputeGameFactory { ...@@ -32,12 +35,41 @@ contract DisputeGameFactory is Ownable, IDisputeGameFactory {
*/ */
mapping(Hash => IDisputeGame) internal disputeGames; mapping(Hash => IDisputeGame) internal disputeGames;
/**
* @notice An append-only array of disputeGames that have been created.
* @dev This accessor is used by offchain game solvers to efficiently
* track dispute games
*/
IDisputeGame[] public disputeGameList;
/** /**
* @notice Constructs a new DisputeGameFactory contract. * @notice Constructs a new DisputeGameFactory contract.
*/
constructor() OwnableUpgradeable() {
initialize(address(0));
}
/**
* @notice Initializes the contract.
* @param _owner The owner of the contract. * @param _owner The owner of the contract.
*/ */
constructor(address _owner) Ownable() { function initialize(address _owner) public initializer {
transferOwnership(_owner); __Ownable_init();
_transferOwnership(_owner);
}
/**
* @inheritdoc IVersioned
*/
function version() external pure returns (string memory) {
return "0.0.1";
}
/**
* @inheritdoc IDisputeGameFactory
*/
function gameCount() external view returns (uint256 _gameCount) {
_gameCount = disputeGameList.length;
} }
/** /**
...@@ -81,6 +113,7 @@ contract DisputeGameFactory is Ownable, IDisputeGameFactory { ...@@ -81,6 +113,7 @@ contract DisputeGameFactory is Ownable, IDisputeGameFactory {
// Store the dispute game in the mapping & emit the `DisputeGameCreated` event. // Store the dispute game in the mapping & emit the `DisputeGameCreated` event.
disputeGames[uuid] = proxy; disputeGames[uuid] = proxy;
disputeGameList.push(proxy);
emit DisputeGameCreated(address(proxy), gameType, rootClaim); emit DisputeGameCreated(address(proxy), gameType, rootClaim);
} }
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IVersioned } from "./interfaces/IVersioned.sol";
import { IFaultDisputeGame } from "./interfaces/IFaultDisputeGame.sol";
import { IInitializable } from "./interfaces/IInitializable.sol";
import { IBondManager } from "./interfaces/IBondManager.sol";
import { Clone } from "../libraries/Clone.sol";
import { LibHashing } from "./lib/LibHashing.sol";
import { LibPosition } from "./lib/LibPosition.sol";
import { LibClock } from "./lib/LibClock.sol";
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
/**
* @title FaultDisputeGame
* @notice An implementation of the `IFaultDisputeGame` interface.
*/
contract FaultDisputeGame is IFaultDisputeGame, Clone {
////////////////////////////////////////////////////////////////
// State Vars //
////////////////////////////////////////////////////////////////
/**
* @notice The current Semver of the FaultDisputeGame implementation.
*/
string internal constant VERSION = "0.0.1";
/**
* @notice The max depth of the game.
*/
uint256 internal constant MAX_GAME_DEPTH = 63;
/**
* @notice The duration of the game.
* @dev TODO: Account for resolution buffer. (?)
*/
Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/**
* @notice The root claim's position is always at depth 0; index 0.
*/
Position internal constant ROOT_POSITION = Position.wrap(0);
/**
* @notice The starting timestamp of the game
*/
Timestamp public gameStart;
/**
* @inheritdoc IDisputeGame
*/
GameStatus public status;
/**
* @inheritdoc IDisputeGame
*/
IBondManager public bondManager;
/**
* @notice An append-only array of all claims made during the dispute game.
*/
ClaimData[] public claimData;
/**
* @notice An internal mapping to allow for constant-time lookups of existing claims.
*/
mapping(ClaimHash => bool) internal claims;
////////////////////////////////////////////////////////////////
// External Logic //
////////////////////////////////////////////////////////////////
/**
* @inheritdoc IFaultDisputeGame
*/
function attack(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, true);
}
/**
* @inheritdoc IFaultDisputeGame
*/
function defend(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, false);
}
/**
* @inheritdoc IFaultDisputeGame
*/
function step(
uint256 _prestateIndex,
uint256 _parentIndex,
bytes calldata _stateData,
bytes calldata _proof
) external {
// TODO - Call the VM to perform the execution step.
}
////////////////////////////////////////////////////////////////
// Internal Logic //
////////////////////////////////////////////////////////////////
/**
* @notice Internal move function, used by both `attack` and `defend`.
* @param _challengeIndex The index of the claim being moved against.
* @param _pivot The claim at the next logical position in the game.
* @param _isAttack Whether or not the move is an attack or defense.
*/
function _move(
uint256 _challengeIndex,
Claim _pivot,
bool _isAttack
) internal {
// Moves cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) {
revert GameNotInProgress();
}
// The only move that can be made against a root claim is an attack. This is because the
// root claim commits to the entire state; Therefore, the only valid defense is to do
// nothing if it is agreed with.
if (_challengeIndex == 0 && !_isAttack) {
revert CannotDefendRootClaim();
}
// Get the parent. If it does not exist, the call will revert with OOB.
ClaimData memory parent = claimData[_challengeIndex];
// Set the parent claim as countered.
claimData[_challengeIndex].countered = true;
// Compute the position that the claim commits to. Because the parent's position is already
// known, we can compute the next position by moving left or right depending on whether
// or not the move is an attack or defense.
Position nextPosition = _isAttack
? LibPosition.attack(parent.position)
: LibPosition.defend(parent.position);
// At the leaf nodes of the game, the only option is to run a step to prove or disprove
// the above claim. At this depth, the parent claim commits to the state after a single
// instruction step.
if (LibPosition.depth(nextPosition) >= MAX_GAME_DEPTH) {
revert GameDepthExceeded();
}
// Fetch the grandparent clock, if it exists.
// The grandparent clock should always exist unless the parent is the root claim.
Clock grandparentClock;
if (parent.parentIndex != type(uint32).max) {
grandparentClock = claimData[parent.parentIndex].clock;
}
// Compute the duration of the next clock. This is done by adding the duration of the
// grandparent claim to the difference between the current block timestamp and the
// parent's clock timestamp.
Duration nextDuration = Duration.wrap(
uint64(
// First, fetch the duration of the grandparent claim.
Duration.unwrap(LibClock.duration(grandparentClock)) +
// Second, add the difference between the current block timestamp and the
// parent's clock timestamp.
block.timestamp -
Timestamp.unwrap(LibClock.timestamp(parent.clock))
)
);
// Enforce the clock time rules. If the new clock duration is greater than half of the game
// duration, then the move is invalid and cannot be made.
if (Duration.unwrap(nextDuration) > Duration.unwrap(GAME_DURATION) >> 1) {
revert ClockTimeExceeded();
}
// Construct the next clock with the new duration and the current block timestamp.
Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp)));
// Do not allow for a duplicate claim to be made.
ClaimHash claimHash = LibHashing.hashClaimPos(_pivot, nextPosition);
if (claims[claimHash]) {
revert ClaimAlreadyExists();
}
claims[claimHash] = true;
// Create the new claim.
claimData.push(
ClaimData({
parentIndex: uint32(_challengeIndex),
claim: _pivot,
position: nextPosition,
clock: nextClock,
countered: false
})
);
// Emit the appropriate event for the attack or defense.
emit Move(_challengeIndex, _pivot, msg.sender);
}
////////////////////////////////////////////////////////////////
// `IDisputeGame` impl //
////////////////////////////////////////////////////////////////
/**
* @inheritdoc IDisputeGame
*/
function gameType() public pure override returns (GameType gameType_) {
gameType_ = GameTypes.FAULT;
}
/**
* @inheritdoc IDisputeGame
*/
function createdAt() external view returns (Timestamp createdAt_) {
createdAt_ = gameStart;
}
/**
* @inheritdoc IDisputeGame
*/
function resolve() external returns (GameStatus status_) {
// TODO - Resolve the game
status = GameStatus.IN_PROGRESS;
status_ = status;
}
/**
* @inheritdoc IDisputeGame
*/
function rootClaim() public pure returns (Claim rootClaim_) {
rootClaim_ = Claim.wrap(_getArgFixedBytes(0x00));
}
/**
* @inheritdoc IDisputeGame
*/
function extraData() public pure returns (bytes memory extraData_) {
// The extra data starts at the second word within the cwia calldata.
// TODO: What data do we need to pass along to this contract from the factory?
// Block hash, preimage data, etc.?
extraData_ = _getArgDynBytes(0x20, 0x20);
}
/**
* @inheritdoc IDisputeGame
*/
function gameData()
external
pure
returns (
GameType gameType_,
Claim rootClaim_,
bytes memory extraData_
)
{
gameType_ = gameType();
rootClaim_ = rootClaim();
extraData_ = extraData();
}
/**
* @inheritdoc IInitializable
*/
function initialize() external {
// Set the game start
gameStart = Timestamp.wrap(uint64(block.timestamp));
// Set the game status
status = GameStatus.IN_PROGRESS;
// Set the root claim
claimData.push(
ClaimData({
parentIndex: type(uint32).max,
claim: rootClaim(),
position: ROOT_POSITION,
clock: LibClock.wrap(Duration.wrap(0), Timestamp.wrap(uint64(block.timestamp))),
countered: false
})
);
}
/**
* @inheritdoc IVersioned
*/
function version() external pure override returns (string memory version_) {
version_ = VERSION;
}
}
...@@ -20,44 +20,44 @@ interface IDisputeGame is IInitializable, IVersioned { ...@@ -20,44 +20,44 @@ interface IDisputeGame is IInitializable, IVersioned {
/** /**
* @notice Returns the timestamp that the DisputeGame contract was created at. * @notice Returns the timestamp that the DisputeGame contract was created at.
* @return _createdAt The timestamp that the DisputeGame contract was created at. * @return createdAt_ The timestamp that the DisputeGame contract was created at.
*/ */
function createdAt() external view returns (Timestamp _createdAt); function createdAt() external view returns (Timestamp createdAt_);
/** /**
* @notice Returns the current status of the game. * @notice Returns the current status of the game.
* @return _status The current status of the game. * @return status_ The current status of the game.
*/ */
function status() external view returns (GameStatus _status); function status() external view returns (GameStatus status_);
/** /**
* @notice Getter for the game type. * @notice Getter for the game type.
* @dev `clones-with-immutable-args` argument #1 * @dev `clones-with-immutable-args` argument #1
* @dev The reference impl should be entirely different depending on the type (fault, validity) * @dev The reference impl should be entirely different depending on the type (fault, validity)
* i.e. The game type should indicate the security model. * i.e. The game type should indicate the security model.
* @return _gameType The type of proof system being used. * @return gameType_ The type of proof system being used.
*/ */
function gameType() external pure returns (GameType _gameType); function gameType() external pure returns (GameType gameType_);
/** /**
* @notice Getter for the root claim. * @notice Getter for the root claim.
* @dev `clones-with-immutable-args` argument #2 * @dev `clones-with-immutable-args` argument #2
* @return _rootClaim The root claim of the DisputeGame. * @return rootClaim_ The root claim of the DisputeGame.
*/ */
function rootClaim() external pure returns (Claim _rootClaim); function rootClaim() external pure returns (Claim rootClaim_);
/** /**
* @notice Getter for the extra data. * @notice Getter for the extra data.
* @dev `clones-with-immutable-args` argument #3 * @dev `clones-with-immutable-args` argument #3
* @return _extraData Any extra data supplied to the dispute game contract by the creator. * @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/ */
function extraData() external pure returns (bytes memory _extraData); function extraData() external pure returns (bytes memory extraData_);
/** /**
* @notice Returns the address of the `BondManager` used. * @notice Returns the address of the `BondManager` used.
* @return _bondManager The address of the `BondManager` used. * @return bondManager_ The address of the `BondManager` used.
*/ */
function bondManager() external view returns (IBondManager _bondManager); function bondManager() external view returns (IBondManager bondManager_);
/** /**
* @notice If all necessary information has been gathered, this function should mark the game * @notice If all necessary information has been gathered, this function should mark the game
...@@ -65,22 +65,25 @@ interface IDisputeGame is IInitializable, IVersioned { ...@@ -65,22 +65,25 @@ interface IDisputeGame is IInitializable, IVersioned {
* the resolved game. It is at this stage that the bonds should be awarded to the * the resolved game. It is at this stage that the bonds should be awarded to the
* necessary parties. * necessary parties.
* @dev May only be called if the `status` is `IN_PROGRESS`. * @dev May only be called if the `status` is `IN_PROGRESS`.
* @return _status The status of the game after resolution. * @return status_ The status of the game after resolution.
*/ */
function resolve() external returns (GameStatus _status); function resolve() external returns (GameStatus status_);
/** /**
* @notice A compliant implementation of this interface should return the components of the * @notice A compliant implementation of this interface should return the components of the
* game UUID's preimage provided in the cwia payload. The preimage of the UUID is * game UUID's preimage provided in the cwia payload. The preimage of the UUID is
* constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes * constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes
* concatenation. * concatenation.
* @return gameType_ The type of proof system being used.
* @return rootClaim_ The root claim of the DisputeGame.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/ */
function gameData() function gameData()
external external
pure pure
returns ( returns (
GameType _gameType, GameType gameType_,
Claim _rootClaim, Claim rootClaim_,
bytes memory _extraData bytes memory extraData_
); );
} }
...@@ -29,6 +29,12 @@ interface IDisputeGameFactory { ...@@ -29,6 +29,12 @@ interface IDisputeGameFactory {
*/ */
event ImplementationSet(address indexed impl, GameType indexed gameType); event ImplementationSet(address indexed impl, GameType indexed gameType);
/**
* @notice the total number of dispute games created by this factory.
* @return _gameCount The total number of dispute games created by this factory.
*/
function gameCount() external view returns (uint256 _gameCount);
/** /**
* @notice `games` queries an internal a mapping that maps the hash of * @notice `games` queries an internal a mapping that maps the hash of
* `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone. * `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
......
...@@ -11,85 +11,39 @@ import { IDisputeGame } from "./IDisputeGame.sol"; ...@@ -11,85 +11,39 @@ import { IDisputeGame } from "./IDisputeGame.sol";
*/ */
interface IFaultDisputeGame is IDisputeGame { interface IFaultDisputeGame is IDisputeGame {
/** /**
* @notice Emitted when a subclaim is disagreed upon by `claimant` * @notice The `ClaimData` struct represents the data associated with a Claim.
* @dev Disagreeing with a subclaim is akin to attacking it. * @dev TODO: Pack `Clock` and `Position` into the same slot. Should require 4 64 bit arms.
* @param claimHash The unique ClaimHash that is being disagreed upon * @dev TODO: Add bond ID information.
* @param pivot The claim for the following pivot (disagreement = go left)
* @param claimant The address of the claimant
*/ */
event Attack(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant); struct ClaimData {
uint32 parentIndex;
bool countered;
Claim claim;
Position position;
Clock clock;
}
/** /**
* @notice Emitted when a subclaim is agreed upon by `claimant` * @notice Emitted when a new claim is added to the DAG by `claimant`
* @dev Agreeing with a subclaim is akin to defending it. * @param parentIndex The index within the `claimData` array of the parent claim
* @param claimHash The unique ClaimHash that is being agreed upon * @param pivot The claim being added
* @param pivot The claim for the following pivot (agreement = go right)
* @param claimant The address of the claimant * @param claimant The address of the claimant
*/ */
event Defend(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant); event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/**
* @notice Maps a unique ClaimHash to a Claim.
* @param claimHash The unique ClaimHash
* @return claim The Claim associated with the ClaimHash
*/
function claims(ClaimHash claimHash) external view returns (Claim claim);
/**
* @notice Maps a unique ClaimHash to its parent.
* @param claimHash The unique ClaimHash
* @return parent The parent ClaimHash of the passed ClaimHash
*/
function parents(ClaimHash claimHash) external view returns (ClaimHash parent);
/**
* @notice Maps a unique ClaimHash to its Position.
* @param claimHash The unique ClaimHash
* @return position The Position associated with the ClaimHash
*/
function positions(ClaimHash claimHash) external view returns (Position position);
/**
* @notice Maps a unique ClaimHash to a Bond.
* @param claimHash The unique ClaimHash
* @return bond The Bond associated with the ClaimHash
*/
function bonds(ClaimHash claimHash) external view returns (BondAmount bond);
/**
* @notice Maps a unique ClaimHash its chess clock.
* @param claimHash The unique ClaimHash
* @return clock The chess clock associated with the ClaimHash
*/
function clocks(ClaimHash claimHash) external view returns (Clock clock);
/**
* @notice Maps a unique ClaimHash to its reference counter.
* @param claimHash The unique ClaimHash
* @return _rc The reference counter associated with the ClaimHash
*/
function rc(ClaimHash claimHash) external view returns (uint64 _rc);
/**
* @notice Maps a unique ClaimHash to a boolean indicating whether or not it has been countered.
* @param claimHash The unique claimHash
* @return _countered Whether or not `claimHash` has been countered
*/
function countered(ClaimHash claimHash) external view returns (bool _countered);
/** /**
* @notice Disagree with a subclaim * @notice Attack a disagreed upon `Claim`.
* @param disagreement The ClaimHash of the disagreement * @param _parentIndex Index of the `Claim` to attack in `claimData`.
* @param pivot The claimed pivot * @param _pivot The `Claim` at the relative attack position.
*/ */
function attack(ClaimHash disagreement, Claim pivot) external; function attack(uint256 _parentIndex, Claim _pivot) external payable;
/** /**
* @notice Agree with a subclaim * @notice Defend an agreed upon `Claim`.
* @param agreement The ClaimHash of the agreement * @param _parentIndex Index of the claim to defend in `claimData`.
* @param pivot The claimed pivot * @param _pivot The `Claim` at the relative defense position.
*/ */
function defend(ClaimHash agreement, Claim pivot) external; function defend(uint256 _parentIndex, Claim _pivot) external payable;
/** /**
* @notice Perform the final step via an on-chain fault proof processor * @notice Perform the final step via an on-chain fault proof processor
...@@ -97,7 +51,15 @@ interface IFaultDisputeGame is IDisputeGame { ...@@ -97,7 +51,15 @@ interface IFaultDisputeGame is IDisputeGame {
* a step in the fault proof program on-chain. The interface of the fault proof * a step in the fault proof program on-chain. The interface of the fault proof
* processor contract should be generic enough such that we can use different * processor contract should be generic enough such that we can use different
* fault proof VMs (MIPS, RiscV5, etc.) * fault proof VMs (MIPS, RiscV5, etc.)
* @param disagreement The ClaimHash of the disagreement * @param _prestateIndex The index of the prestate of the step within `claimData`.
* @param _parentIndex The index of the parent claim within `claimData`.
* @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex`
* @param _proof Proof to access memory leaf nodes in the VM.
*/ */
function step(ClaimHash disagreement) external; function step(
uint256 _prestateIndex,
uint256 _parentIndex,
bytes calldata _stateData,
bytes calldata _proof
) external;
} }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibClock
* @notice This library contains helper functions for working with the `Clock` type.
*/
library LibClock {
/**
* @notice Packs a `Duration` and `Timestamp` into a `Clock` type.
* @param _duration The `Duration` to pack into the `Clock` type.
* @param _timestamp The `Timestamp` to pack into the `Clock` type.
* @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
*/
function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) {
assembly {
clock_ := or(shl(0x40, _duration), _timestamp)
}
}
/**
* @notice Pull the `Duration` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Duration` out of.
* @return duration_ The `Duration` pulled out of `_clock`.
*/
function duration(Clock _clock) internal pure returns (Duration duration_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`.
assembly {
duration_ := shr(0x40, _clock)
}
}
/**
* @notice Pull the `Timestamp` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Timestamp` out of.
* @return timestamp_ The `Timestamp` pulled out of `_clock`.
*/
function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) {
// Clean the high-order 192 bits by shifting the clock left and then right again, leaving
// only the `timestamp`.
assembly {
timestamp_ := shr(0xC0, shl(0xC0, _clock))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title Hashing
* @notice This library contains all of the hashing utilities used in the Cannon contracts.
*/
library LibHashing {
/**
* @notice Hashes a claim and a position together.
* @param _claim A Claim type.
* @param _position The position of `claim`.
* @return claimHash_ A hash of abi.encodePacked(claim, position);
*/
function hashClaimPos(Claim _claim, Position _position) internal pure returns (ClaimHash claimHash_) {
assembly {
mstore(0x00, _claim)
mstore(0x20, _position)
claimHash_ := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibPosition
* @notice This library contains helper functions for working with the `Position` type.
*/
library LibPosition {
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly {
position_ := or(shl(0x40, _depth), _indexAtDepth)
}
}
/**
* @notice Pulls the `depth` out of a packed `Position` type.
* @param _position The position to get the `depth` of.
* @return depth_ The `depth` of the `position`.
*/
function depth(Position _position) internal pure returns (uint64 depth_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `depth`.
assembly {
depth_ := shr(0x40, _position)
}
}
/**
* @notice Pulls the `indexAtDepth` out of a packed `Position` type.
* @param _position The position to get the `indexAtDepth` of.
* @return indexAtDepth_ The `indexAtDepth` of the `position`.
*/
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) {
// Clean the high-order 192 bits by shifting the position left and then right again, leaving
// only the `indexAtDepth`.
assembly {
indexAtDepth_ := shr(0xC0, shl(0xC0, _position))
}
}
/**
* @notice Get the position to the left of `position`.
* @param _position The position to get the left position of.
* @return left_ The position to the left of `position`.
*/
function left(Position _position) internal pure returns (Position left_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Left = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 }
assembly {
left_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, _indexAtDepth))
}
}
/**
* @notice Get the position to the right of `position`.
* @param _position The position to get the right position of.
* @return right_ The position to the right of `position`.
*/
function right(Position _position) internal pure returns (Position right_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Right = { depth: position.depth + 1, indexAtDepth: position.indexAtDepth * 2 + 1 }
assembly {
right_ := or(shl(0x40, add(_depth, 0x01)), add(shl(0x01, _indexAtDepth), 0x01))
}
}
/**
* @notice Get the parent position of `position`.
* @param _position The position to get the parent position of.
* @return parent_ The parent position of `position`.
*/
function parent(Position _position) internal pure returns (Position parent_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Parent = { depth: position.depth - 1, indexAtDepth: position.indexAtDepth / 2 }
assembly {
parent_ := or(shl(0x40, sub(_depth, 0x01)), shr(0x01, _indexAtDepth))
}
}
/**
* @notice Get the deepest, right most index relative to the `position`.
* @param _position The position to get the relative deepest, right most index of.
* @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most index relative to the `position`.
*/
function rightIndex(Position _position, uint256 _maxDepth) internal pure returns (uint64 rightIndex_) {
assembly {
rightIndex_ := shr(0xC0, shl(0xC0, _position))
// Walk down to the max depth by moving right
for { let i := shr(0x40, _position) } lt(i, sub(_maxDepth, 0x01)) { i := add(i, 0x01) } {
rightIndex_ := add(0x01, shl(0x01, rightIndex_))
}
}
}
/**
* @notice Get the attack position relative to `position`.
* @param _position The position to get the relative attack position of.
* @return attack_ The attack position relative to `position`.
*/
function attack(Position _position) internal pure returns (Position attack_) {
return left(_position);
}
/**
* @notice Get the defense position relative to `position`.
* @param _position The position to get the relative defense position of.
* @return defense_ The defense position relative to `position`.
*/
function defend(Position _position) internal pure returns (Position defense_) {
uint64 _depth = depth(_position);
uint64 _indexAtDepth = indexAtDepth(_position);
// Defend = { depth: position.depth + 1, indexAtDepth: ((position.indexAtDepth / 2) * 2 + 1) * 2 }
assembly {
defense_ := or(shl(0x40, add(_depth, 0x01)), shl(0x01, add(0x01, shl(0x01, shr(0x01, _indexAtDepth)))))
}
}
}
...@@ -112,20 +112,50 @@ library Bytes { ...@@ -112,20 +112,50 @@ library Bytes {
* @return Resulting nibble array. * @return Resulting nibble array.
*/ */
function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) { function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
uint256 bytesLength = _bytes.length; bytes memory _nibbles;
bytes memory nibbles = new bytes(bytesLength * 2); assembly {
bytes1 b; // Grab a free memory offset for the new array
_nibbles := mload(0x40)
for (uint256 i = 0; i < bytesLength; ) {
b = _bytes[i]; // Load the length of the passed bytes array from memory
nibbles[i * 2] = b >> 4; let bytesLength := mload(_bytes)
nibbles[i * 2 + 1] = b & 0x0f;
unchecked { // Calculate the length of the new nibble array
++i; // This is the length of the input array times 2
let nibblesLength := shl(0x01, bytesLength)
// Update the free memory pointer to allocate memory for the new array.
// To do this, we add the length of the new array + 32 bytes for the array length
// rounded up to the nearest 32 byte boundary to the current free memory pointer.
mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F))))
// Store the length of the new array in memory
mstore(_nibbles, nibblesLength)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(_nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
} }
} }
return _nibbles;
return nibbles;
} }
/** /**
......
...@@ -39,6 +39,27 @@ error CannotDefendRootClaim(); ...@@ -39,6 +39,27 @@ error CannotDefendRootClaim();
*/ */
error ClaimAlreadyExists(); error ClaimAlreadyExists();
/**
* @notice Thrown when a given claim is invalid (0).
*/
error InvalidClaim();
/**
* @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
* the game is not in progress.
*/
error GameNotInProgress();
/**
* @notice Thrown when a move is attempted to be made after the clock has timed out.
*/
error ClockTimeExceeded();
/**
* @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
*/
error GameDepthExceeded();
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors // // `AttestationDisputeGame` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
......
...@@ -39,11 +39,11 @@ type Duration is uint64; ...@@ -39,11 +39,11 @@ type Duration is uint64;
* ┌────────────┬────────────────┐ * ┌────────────┬────────────────┐
* │ Bits │ Value │ * │ Bits │ Value │
* ├────────────┼────────────────┤ * ├────────────┼────────────────┤
* │ [0, 128) │ Duration │ * │ [0, 64) │ Duration │
* │ [128, 256) │ Timestamp │ * │ [64, 128) │ Timestamp │
* └────────────┴────────────────┘ * └────────────┴────────────────┘
*/ */
type Clock is uint256; type Clock is uint128;
/** /**
* @notice A `Position` represents a position of a claim within the game tree. * @notice A `Position` represents a position of a claim within the game tree.
...@@ -51,11 +51,16 @@ type Clock is uint256; ...@@ -51,11 +51,16 @@ type Clock is uint256;
* ┌────────────┬────────────────┐ * ┌────────────┬────────────────┐
* │ Bits │ Value │ * │ Bits │ Value │
* ├────────────┼────────────────┤ * ├────────────┼────────────────┤
* │ [0, 128) │ Depth │ * │ [0, 64) │ Depth │
* │ [128, 256) │ Index at depth │ * │ [64, 128) │ Index at depth │
* └────────────┴────────────────┘ * └────────────┴────────────────┘
*/ */
type Position is uint256; type Position is uint128;
/**
* @notice A `GameType` represents the type of game being played.
*/
type GameType is uint8;
/** /**
* @notice The current status of the dispute game. * @notice The current status of the dispute game.
...@@ -70,13 +75,22 @@ enum GameStatus { ...@@ -70,13 +75,22 @@ enum GameStatus {
} }
/** /**
* @notice The type of proof system being used. * @title GameTypes
* @notice A library that defines the IDs of games that can be played.
*/
library GameTypes {
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes fault proofs.
*/
GameType internal constant FAULT = GameType.wrap(0);
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes validity proofs.
*/
GameType internal constant VALIDITY = GameType.wrap(1);
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
*/ */
enum GameType { GameType internal constant ATTESTATION = GameType.wrap(2);
// The game will use a `IDisputeGame` implementation that utilizes fault proofs.
FAULT,
// The game will use a `IDisputeGame` implementation that utilizes validity proofs.
VALIDITY,
// The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
ATTESTATION
} }
...@@ -182,49 +182,6 @@ contract Bytes_slice_Test is Test { ...@@ -182,49 +182,6 @@ contract Bytes_slice_Test is Test {
} }
contract Bytes_toNibbles_Test is Test { contract Bytes_toNibbles_Test is Test {
/**
* @notice Diffs the test Solidity version of `toNibbles` against the Yul version.
*
* @param _bytes The `bytes` array to convert to nibbles.
*
* @return Yul version of `toNibbles` applied to `_bytes`.
*/
function _toNibblesYul(bytes memory _bytes) internal pure returns (bytes memory) {
// Allocate memory for the `nibbles` array.
bytes memory nibbles = new bytes(_bytes.length << 1);
assembly {
// Load the length of the passed bytes array from memory
let bytesLength := mload(_bytes)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles;
}
/** /**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of * @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
* 10 nibbles corresponding to the input data. * 10 nibbles corresponding to the input data.
...@@ -272,11 +229,49 @@ contract Bytes_toNibbles_Test is Test { ...@@ -272,11 +229,49 @@ contract Bytes_toNibbles_Test is Test {
} }
/** /**
* @notice Test that the `toNibbles` function in the `Bytes` library is equivalent to the Yul * @notice Tests that the `toNibbles` function correctly updates the free memory pointer depending
* implementation. * on the length of the resulting array.
*/ */
function testDiff_toNibbles_succeeds(bytes memory _input) public { function testFuzz_toNibbles_memorySafety_succeeds(bytes memory _input) public {
assertEq(Bytes.toNibbles(_input), _toNibblesYul(_input)); // Grab the free memory pointer before the `toNibbles` operation
uint64 initPtr;
assembly {
initPtr := mload(0x40)
}
uint64 expectedPtr = uint64(initPtr + 0x20 + ((_input.length * 2 + 0x1F) & ~uint256(0x1F)));
// Ensure that all memory outside of the expected range is safe.
vm.expectSafeMemory(initPtr, expectedPtr);
// Pull out each individual nibble from the input bytes array
bytes memory nibbles = Bytes.toNibbles(_input);
// Grab the free memory pointer after the `toNibbles` operation
uint64 finalPtr;
assembly {
finalPtr := mload(0x40)
}
// The free memory pointer should have been updated properly
if (_input.length == 0) {
// If the input length is zero, only 32 bytes of memory should have been allocated.
assertEq(finalPtr, initPtr + 0x20);
} else {
// If the input length is greater than zero, the memory allocated should be the
// length of the input * 2 + 32 bytes for the length field.
//
// Note that we use a slightly less efficient, but equivalent method of rounding
// up `_length` to the next multiple of 32 than is used in the `toNibbles` function.
// This is to diff test the method used in `toNibbles`.
uint64 _expectedPtr = uint64(initPtr + 0x20 + (((_input.length * 2 + 0x1F) >> 5) << 5));
assertEq(finalPtr, _expectedPtr);
// Sanity check for equivalence of the rounding methods.
assertEq(_expectedPtr, expectedPtr);
}
// The nibbles length should be equal to `_length * 2`
assertEq(nibbles.length, _input.length << 1);
} }
} }
......
...@@ -7,10 +7,10 @@ import "../libraries/DisputeErrors.sol"; ...@@ -7,10 +7,10 @@ import "../libraries/DisputeErrors.sol";
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol"; import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { IDisputeGame } from "../dispute/interfaces/IDisputeGame.sol"; import { IDisputeGame } from "../dispute/interfaces/IDisputeGame.sol";
import { Proxy } from "../universal/Proxy.sol";
contract DisputeGameFactory_Test is Test { contract DisputeGameFactory_Initializer is Test {
DisputeGameFactory factory; DisputeGameFactory factory;
FakeClone fakeClone;
event DisputeGameCreated( event DisputeGameCreated(
address indexed disputeProxy, address indexed disputeProxy,
...@@ -20,8 +20,24 @@ contract DisputeGameFactory_Test is Test { ...@@ -20,8 +20,24 @@ contract DisputeGameFactory_Test is Test {
event ImplementationSet(address indexed impl, GameType indexed gameType); event ImplementationSet(address indexed impl, GameType indexed gameType);
function setUp() public { function setUp() public virtual {
factory = new DisputeGameFactory(address(this)); Proxy proxy = new Proxy(address(this));
DisputeGameFactory impl = new DisputeGameFactory();
proxy.upgradeToAndCall({
_implementation: address(impl),
_data: abi.encodeCall(impl.initialize, (address(this)))
});
factory = DisputeGameFactory(address(proxy));
vm.label(address(factory), "DisputeGameFactoryProxy");
}
}
contract DisputeGameFactory_Test is DisputeGameFactory_Initializer {
FakeClone fakeClone;
function setUp() public override {
super.setUp();
fakeClone = new FakeClone(); fakeClone = new FakeClone();
} }
...@@ -35,11 +51,11 @@ contract DisputeGameFactory_Test is Test { ...@@ -35,11 +51,11 @@ contract DisputeGameFactory_Test is Test {
bytes calldata extraData bytes calldata extraData
) public { ) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values. // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2))); GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Set all three implementations to the same `FakeClone` contract. // Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) { for (uint8 i; i < 3; i++) {
factory.setImplementation(GameType(i), IDisputeGame(address(fakeClone))); factory.setImplementation(GameType.wrap(i), IDisputeGame(address(fakeClone)));
} }
vm.expectEmit(false, true, true, false); vm.expectEmit(false, true, true, false);
...@@ -48,6 +64,8 @@ contract DisputeGameFactory_Test is Test { ...@@ -48,6 +64,8 @@ contract DisputeGameFactory_Test is Test {
// Ensure that the dispute game was assigned to the `disputeGames` mapping. // Ensure that the dispute game was assigned to the `disputeGames` mapping.
assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy)); assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy));
assertEq(factory.gameCount(), 1);
assertEq(address(factory.disputeGameList(0)), address(proxy));
} }
/** /**
...@@ -60,7 +78,7 @@ contract DisputeGameFactory_Test is Test { ...@@ -60,7 +78,7 @@ contract DisputeGameFactory_Test is Test {
bytes calldata extraData bytes calldata extraData
) public { ) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values. // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2))); GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt)); vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt));
factory.create(gt, rootClaim, extraData); factory.create(gt, rootClaim, extraData);
...@@ -75,11 +93,11 @@ contract DisputeGameFactory_Test is Test { ...@@ -75,11 +93,11 @@ contract DisputeGameFactory_Test is Test {
bytes calldata extraData bytes calldata extraData
) public { ) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values. // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2))); GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Set all three implementations to the same `FakeClone` contract. // Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) { for (uint8 i; i < 3; i++) {
factory.setImplementation(GameType(i), IDisputeGame(address(fakeClone))); factory.setImplementation(GameType.wrap(i), IDisputeGame(address(fakeClone)));
} }
// Create our first dispute game - this should succeed. // Create our first dispute game - this should succeed.
...@@ -104,17 +122,17 @@ contract DisputeGameFactory_Test is Test { ...@@ -104,17 +122,17 @@ contract DisputeGameFactory_Test is Test {
* @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`. * @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
*/ */
function test_setImplementation_succeeds() public { function test_setImplementation_succeeds() public {
// There should be no implementation for the `GameType.FAULT` enum value, it has not been set. // There should be no implementation for the `GameTypes.FAULT` enum value, it has not been set.
assertEq(address(factory.gameImpls(GameType.FAULT)), address(0)); assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(0));
vm.expectEmit(true, true, true, true, address(factory)); vm.expectEmit(true, true, true, true, address(factory));
emit ImplementationSet(address(1), GameType.FAULT); emit ImplementationSet(address(1), GameTypes.FAULT);
// Set the implementation for the `GameType.FAULT` enum value. // Set the implementation for the `GameTypes.FAULT` enum value.
factory.setImplementation(GameType.FAULT, IDisputeGame(address(1))); factory.setImplementation(GameTypes.FAULT, IDisputeGame(address(1)));
// Ensure that the implementation for the `GameType.FAULT` enum value is set. // Ensure that the implementation for the `GameTypes.FAULT` enum value is set.
assertEq(address(factory.gameImpls(GameType.FAULT)), address(1)); assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(1));
} }
/** /**
...@@ -124,7 +142,7 @@ contract DisputeGameFactory_Test is Test { ...@@ -124,7 +142,7 @@ contract DisputeGameFactory_Test is Test {
// Ensure that the `setImplementation` function reverts when called by a non-owner. // Ensure that the `setImplementation` function reverts when called by a non-owner.
vm.prank(address(0)); vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner"); vm.expectRevert("Ownable: caller is not the owner");
factory.setImplementation(GameType.FAULT, IDisputeGame(address(1))); factory.setImplementation(GameTypes.FAULT, IDisputeGame(address(1)));
} }
/** /**
...@@ -137,7 +155,7 @@ contract DisputeGameFactory_Test is Test { ...@@ -137,7 +155,7 @@ contract DisputeGameFactory_Test is Test {
bytes calldata extraData bytes calldata extraData
) public { ) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values. // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2))); GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
assertEq( assertEq(
Hash.unwrap(factory.getGameUUID(gt, rootClaim, extraData)), Hash.unwrap(factory.getGameUUID(gt, rootClaim, extraData)),
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import "../libraries/DisputeTypes.sol";
/**
* @notice Tests for `LibClock`
*/
contract LibClock_Test is Test {
/**
* @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
*/
function testFuzz_duration_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Duration.unwrap(LibClock.duration(clock)), Duration.unwrap(_duration));
}
/**
* @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
*/
function testFuzz_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Timestamp.unwrap(LibClock.timestamp(clock)), Timestamp.unwrap(_timestamp));
}
}
This diff is collapsed.
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "ADMIN", "governanceTokenOwner": "ADMIN",
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment