diff --git a/.gitmodules b/.gitmodules index 7c78791c78..c3cb5fc5fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,3 +20,6 @@ [submodule "nitro-testnode"] path = nitro-testnode url = https://github.com/OffchainLabs/nitro-testnode.git +[submodule "bold"] + path = bold + url = https://github.com/OffchainLabs/bold.git diff --git a/Dockerfile b/Dockerfile index 96bcb22952..f6028a190c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,6 +75,7 @@ COPY ./contracts/package.json ./contracts/yarn.lock ./contracts/ COPY ./solgen/gen.go ./solgen/ COPY ./fastcache ./fastcache COPY ./go-ethereum ./go-ethereum +COPY ./bold ./bold COPY --from=brotli-wasm-export / target/ COPY --from=contracts-builder workspace/contracts/build/contracts/src/precompiles/ contracts/build/contracts/src/precompiles/ COPY --from=contracts-builder workspace/.make/ .make/ @@ -177,6 +178,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ COPY go.mod go.sum ./ COPY go-ethereum/go.mod go-ethereum/go.sum go-ethereum/ COPY fastcache/go.mod fastcache/go.sum fastcache/ +COPY bold/go.mod bold/go.sum bold/ RUN go mod download COPY . ./ COPY --from=contracts-builder workspace/contracts/build/ contracts/build/ diff --git a/bold b/bold new file mode 160000 index 0000000000..ce48f994c0 --- /dev/null +++ b/bold @@ -0,0 +1 @@ +Subproject commit ce48f994c0cead5cfb724ced42e5541a24d8c4e0 diff --git a/go.mod b/go.mod index cdfae4df16..18bdeaddb1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,10 @@ replace github.com/VictoriaMetrics/fastcache => ./fastcache replace github.com/ethereum/go-ethereum => ./go-ethereum +replace github.com/OffchainLabs/bold => ./bold + require ( + github.com/OffchainLabs/bold v0.0.0-00010101000000-000000000000 github.com/alicebob/miniredis/v2 v2.21.0 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 github.com/andybalholm/brotli v1.0.4 @@ -16,10 +19,11 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10 github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 github.com/cavaliergopher/grab/v3 v3.0.1 + github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/codeclysm/extract/v3 v3.0.2 github.com/dgraph-io/badger/v3 v3.2103.2 github.com/enescakir/emoji v1.0.0 - github.com/ethereum/go-ethereum v1.10.26 + github.com/ethereum/go-ethereum v1.12.0 github.com/fatih/structtag v1.2.0 github.com/gdamore/tcell/v2 v2.6.0 github.com/google/go-cmp v0.5.9 @@ -66,7 +70,7 @@ require ( github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -74,7 +78,6 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -232,7 +235,7 @@ require ( github.com/quic-go/webtransport-go v0.5.2 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rhnvrm/simples3 v0.6.1 // indirect - github.com/rivo/uniseg v0.4.3 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/samber/lo v1.36.0 // indirect @@ -247,6 +250,7 @@ require ( github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.7.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.7.0 // indirect @@ -286,13 +290,12 @@ require ( ) require ( - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect - github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-redis/redis/v8 v8.11.4 github.com/go-stack/stack v1.8.1 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect @@ -301,7 +304,7 @@ require ( github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c + github.com/holiman/uint256 v1.2.2 github.com/huin/goupnp v1.1.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -311,11 +314,11 @@ require ( github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rs/cors v1.7.0 // indirect - github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/status-im/keycard-go v0.2.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tklauser/go-sysconf v0.3.5 // indirect - github.com/tklauser/numcpus v0.2.2 // indirect + github.com/tklauser/go-sysconf v0.3.11 // indirect + github.com/tklauser/numcpus v0.6.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect golang.org/x/crypto v0.7.0 golang.org/x/net v0.8.0 // indirect diff --git a/go.sum b/go.sum index db81b3a07e..e28fdcc6dc 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,6 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -167,8 +165,8 @@ github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcug github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -386,8 +384,8 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -613,8 +611,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.2 h1:TXKcSGc2WaxPD2+bmzAsVthL4+pEN0YwXcL5qED83vk= +github.com/holiman/uint256 v1.2.2/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= @@ -1461,8 +1459,9 @@ github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8d github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703 h1:ZyM/+FYnpbZsFWuCohniM56kRoHRB4r5EuIzXEYkpxo= github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1486,8 +1485,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -1571,10 +1570,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= @@ -1657,6 +1656,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -1921,6 +1922,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1960,7 +1962,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1979,6 +1980,7 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= @@ -2204,6 +2206,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go new file mode 100644 index 0000000000..1a79ff507c --- /dev/null +++ b/staker/challenge-cache/cache.go @@ -0,0 +1,226 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +/* +* Package challengecache stores validator state roots for L2 states within +challenges in text files using a directory hierarchy structure for efficient lookup. Each file +contains a list of state roots (32 byte hashes), concatenated together as bytes. +Using this structure, we can namespace state roots by message number and big step challenge. + +Once a validator computes the set of machine state roots for a given challenge move the first time, +it will write the roots to this filesystem hierarchy for fast access next time these roots are needed. + +Use cases: +- State roots for a big step challenge from message N to N+1 +- State roots 0 to M for a big step challenge from message N to N+1 +- State roots for a small step challenge from message N to N+1, and big step M to M+1 +- State roots 0 to P for a small step challenge from message N to N+1, and big step M to M+1 + + wavm-module-root-0xab/ + message-num-70/ + roots.txt + big-step-100/ + roots.txt + +We namespace top-level block challenges by wavm module root. Then, we can retrieve +the state roots for any data within a challenge or associated subchallenge based on the hierarchy above. +*/ + +package challengecache + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + protocol "github.com/OffchainLabs/bold/chain-abstraction" + "github.com/OffchainLabs/bold/containers/option" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrNotFoundInCache = errors.New("no found in challenge cache") + ErrFileAlreadyExists = errors.New("file already exists") + ErrNoStateRoots = errors.New("no state roots being written") + stateRootsFileName = "state-roots" + wavmModuleRootPrefix = "wavm-module-root" + messageNumberPrefix = "message-num" + bigStepPrefix = "big-step" +) + +// HistoryCommitmentCacher can retrieve history commitment state roots given lookup keys. +type HistoryCommitmentCacher interface { + Get(lookup *Key, readUpTo protocol.Height) ([]common.Hash, error) + Put(lookup *Key, stateRoots []common.Hash) error +} + +// Cache for history commitments on disk. +type Cache struct { + baseDir string +} + +// New cache from a base directory path. +func New(baseDir string) *Cache { + return &Cache{ + baseDir: baseDir, + } +} + +// Key for cache lookups includes the wavm module root of a challenge, as well +// as the heights for messages and big steps as needed. +type Key struct { + WavmModuleRoot common.Hash + MessageHeight protocol.Height + BigStepHeight option.Option[protocol.Height] +} + +// Get a list of state roots from the cache up to a certain index. State roots are saved as files in the directory +// hierarchy for the cache. If a file is not present, ErrNotFoundInCache +// is returned. +func (c *Cache) Get( + lookup *Key, + readUpTo protocol.Height, +) ([]common.Hash, error) { + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return nil, err + } + if _, err := os.Stat(fName); err != nil { + return nil, ErrNotFoundInCache + } + f, err := os.Open(fName) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after reading", "err", err, "file", fName) + } + }() + return readStateRoots(f, readUpTo) +} + +// Put a list of state roots into the cache. +// State roots are saved as files in a directory hierarchy for the cache. +// This function first creates a temporary file, writes the state roots to it, and then renames the file +// to the final directory to ensure atomic writes. +func (c *Cache) Put(lookup *Key, stateRoots []common.Hash) error { + // We should error if trying to put 0 state roots to disk. + if len(stateRoots) == 0 { + return ErrNoStateRoots + } + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return err + } + // We create a tmp file to write our state roots to first. If writing fails, + // we don't want to leave a half-written file in our cache directory. + // Once writing succeeds, we rename in an atomic operation to the correct file name + // in the cache directory hierarchy. + tmp := os.TempDir() + tmpFName := filepath.Join(tmp, fName) + dir := filepath.Dir(tmpFName) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("could not make tmp directory %s: %w", dir, err) + } + f, err := os.Create(tmpFName) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after writing", "err", err, "file", fName) + } + }() + if err := writeStateRoots(f, stateRoots); err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(fName), os.ModePerm); err != nil { + return fmt.Errorf("could not make file directory %s: %w", fName, err) + } + // If the file writing was successful, we rename the file from the tmp directory + // into our cache directory. This is an atomic operation. + // For more information on this atomic write pattern, see: + // https://stackoverflow.com/questions/2333872/how-to-make-file-creation-an-atomic-operation + return os.Rename(tmpFName /* old */, fName /* new */) +} + +// Reads 32 bytes at a time from a reader up to a specified height. If none, then read all. +func readStateRoots(r io.Reader, readUpTo protocol.Height) ([]common.Hash, error) { + br := bufio.NewReader(r) + stateRoots := make([]common.Hash, 0) + buf := make([]byte, 0, 32) + totalRead := uint64(0) + for { + n, err := br.Read(buf[:cap(buf)]) + if err != nil { + // If we try to read but reach EOF, we break out of the loop. + if err == io.EOF { + break + } + return nil, err + } + buf = buf[:n] + if n != 32 { + return nil, fmt.Errorf("expected to read 32 bytes, got %d bytes", n) + } + stateRoots = append(stateRoots, common.BytesToHash(buf)) + if totalRead >= uint64(readUpTo) { + return stateRoots, nil + } + totalRead++ + } + if readUpTo >= protocol.Height(len(stateRoots)) { + return nil, fmt.Errorf( + "wanted to read up to %d, but only read %d state roots", + readUpTo, + len(stateRoots), + ) + } + return stateRoots, nil +} + +func writeStateRoots(w io.Writer, stateRoots []common.Hash) error { + for i, rt := range stateRoots { + n, err := w.Write(rt[:]) + if err != nil { + return err + } + if n != len(rt) { + return fmt.Errorf( + "for state root %d, wrote %d bytes, expected to write %d bytes", + i, + n, + len(rt), + ) + } + } + return nil +} + +/* +* +When provided with a cache lookup struct, this function determines the file path +for the data requested within the cache directory hierarchy. The folder structure +for a given filesystem challenge cache will look as follows: + + wavm-module-root-0xab/ + message-num-70/ + roots.txt + big-step-100/ + roots.txt +*/ +func determineFilePath(baseDir string, lookup *Key) (string, error) { + key := make([]string, 0) + key = append(key, fmt.Sprintf("%s-%s", wavmModuleRootPrefix, lookup.WavmModuleRoot.Hex())) + key = append(key, fmt.Sprintf("%s-%d", messageNumberPrefix, lookup.MessageHeight)) + if !lookup.BigStepHeight.IsNone() { + bigStepHeight := lookup.BigStepHeight.Unwrap() + key = append(key, fmt.Sprintf("%s-%d", bigStepPrefix, bigStepHeight)) + } + key = append(key, stateRootsFileName) + return filepath.Join(baseDir, filepath.Join(key...)), nil +} diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go new file mode 100644 index 0000000000..b9fec74b9c --- /dev/null +++ b/staker/challenge-cache/cache_test.go @@ -0,0 +1,323 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package challengecache + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "testing" + + protocol "github.com/OffchainLabs/bold/chain-abstraction" + "github.com/OffchainLabs/bold/containers/option" + "github.com/ethereum/go-ethereum/common" +) + +var _ HistoryCommitmentCacher = (*Cache)(nil) + +func TestCache(t *testing.T) { + basePath := t.TempDir() + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + t.Fatal(err) + } + }) + cache := New(basePath) + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + BigStepHeight: option.Some(protocol.Height(0)), + } + t.Run("Not found", func(t *testing.T) { + _, err := cache.Get(key, protocol.Height(0)) + if !errors.Is(err, ErrNotFoundInCache) { + t.Fatal(err) + } + }) + t.Run("Putting empty root fails", func(t *testing.T) { + if err := cache.Put(key, []common.Hash{}); !errors.Is(err, ErrNoStateRoots) { + t.Fatalf("Unexpected error: %v", err) + } + }) + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + err := cache.Put(key, want) + if err != nil { + t.Fatal(err) + } + got, err := cache.Get(key, protocol.Height(2)) + if err != nil { + t.Fatal(err) + } + if len(got) != len(want) { + t.Fatalf("Wrong number of roots. Expected %d, got %d", len(want), len(got)) + } + for i, rt := range got { + if rt != want[i] { + t.Fatalf("Wrong root. Expected %#x, got %#x", want[i], rt) + } + } +} + +func TestReadWriteStateRoots(t *testing.T) { + t.Run("read up to, but had empty reader", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + _, err := readStateRoots(b, protocol.Height(100)) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "only read 0 state roots") { + t.Fatal("Unexpected error") + } + }) + t.Run("read single root", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + want := common.BytesToHash([]byte("foo")) + b.Write(want.Bytes()) + roots, err := readStateRoots(b, protocol.Height(0)) + if err != nil { + t.Fatal(err) + } + if len(roots) == 0 { + t.Fatal("Got no roots") + } + if roots[0] != want { + t.Fatalf("Wrong root. Expected %#x, got %#x", want, roots[0]) + } + }) + t.Run("Three roots exist, want to read only two", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + foo := common.BytesToHash([]byte("foo")) + bar := common.BytesToHash([]byte("bar")) + baz := common.BytesToHash([]byte("baz")) + b.Write(foo.Bytes()) + b.Write(bar.Bytes()) + b.Write(baz.Bytes()) + roots, err := readStateRoots(b, protocol.Height(1)) + if err != nil { + t.Fatal(err) + } + if len(roots) != 2 { + t.Fatalf("Expected two roots, got %d", len(roots)) + } + if roots[0] != foo { + t.Fatalf("Wrong root. Expected %#x, got %#x", foo, roots[0]) + } + if roots[1] != bar { + t.Fatalf("Wrong root. Expected %#x, got %#x", bar, roots[1]) + } + }) + t.Run("Fails to write enough data to writer", func(t *testing.T) { + m := &mockWriter{wantErr: true} + err := writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + m = &mockWriter{wantErr: false, numWritten: 16} + err = writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "expected to write 32 bytes") { + t.Fatalf("Got wrong error kind: %v", err) + } + }) +} + +type mockWriter struct { + wantErr bool + numWritten int +} + +func (m *mockWriter) Write(_ []byte) (n int, err error) { + if m.wantErr { + return 0, errors.New("something went wrong") + } + return m.numWritten, nil +} + +type mockReader struct { + wantErr bool + err error + roots []common.Hash + readIdx int + bytesRead int +} + +func (m *mockReader) Read(out []byte) (n int, err error) { + if m.wantErr { + return 0, m.err + } + if m.readIdx == len(m.roots) { + return 0, io.EOF + } + copy(out, m.roots[m.readIdx].Bytes()) + m.readIdx++ + return m.bytesRead, nil +} + +func Test_readStateRoots(t *testing.T) { + t.Run("Unexpected error", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, roots: want, err: errors.New("foo")} + _, err := readStateRoots(m, protocol.Height(1)) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "foo") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("EOF, but did not read as much as was expected", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, roots: want, err: io.EOF} + _, err := readStateRoots(m, protocol.Height(100)) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "wanted to read up to 100, but only read 0 state roots") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads wrong number of bytes", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, roots: want, bytesRead: 16} + _, err := readStateRoots(m, protocol.Height(2)) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "expected to read 32 bytes, got 16") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads all until EOF", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, roots: want, bytesRead: 32} + got, err := readStateRoots(m, protocol.Height(2)) + if err != nil { + t.Fatal(err) + } + if len(want) != len(got) { + t.Fatal("Wrong number of roots") + } + for i, rt := range got { + if rt != want[i] { + t.Fatal("Wrong root") + } + } + }) +} + +func Test_determineFilePath(t *testing.T) { + type args struct { + baseDir string + key *Key + } + tests := []struct { + name string + args args + want string + wantErr bool + errContains string + }{ + { + name: "OK", + args: args{ + baseDir: "", + key: &Key{ + MessageHeight: 100, + BigStepHeight: option.Some(protocol.Height(50)), + }, + }, + want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/message-num-100/big-step-50/state-roots", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := determineFilePath(tt.args.baseDir, tt.args.key) + if (err != nil) != tt.wantErr { + t.Logf("got: %v, and key %+v, got %s", err, tt.args.key, got) + if !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("Expected %s, got %s", tt.errContains, err.Error()) + } + t.Errorf("determineFilePath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf( + "determineFilePath() = %v, want %v", + got, + tt.want, + ) + } + }) + } +} + +func BenchmarkCache_Read_32Mb(b *testing.B) { + b.StopTimer() + basePath, err := ioutil.TempDir("", "*") + if err != nil { + b.Fatal(err) + } + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + b.Fatal(err) + } + b.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + b.Fatal(err) + } + }) + cache := New(basePath) + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + BigStepHeight: option.Some(protocol.Height(0)), + } + numRoots := 1 << 20 + roots := make([]common.Hash, numRoots) + for i := range roots { + roots[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + } + if err = cache.Put(key, roots); err != nil { + b.Fatal(err) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + readUpTo := protocol.Height(1 << 20) + roots, err := cache.Get(key, readUpTo) + if err != nil { + b.Fatal(err) + } + if len(roots) != numRoots { + b.Fatalf("Wrong number of roots. Expected %d, got %d", numRoots, len(roots)) + } + } +} diff --git a/staker/manager.go b/staker/manager.go new file mode 100644 index 0000000000..a0a1af0a5a --- /dev/null +++ b/staker/manager.go @@ -0,0 +1,83 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package staker + +import ( + "context" + solimpl "github.com/OffchainLabs/bold/chain-abstraction/sol-implementation" + challengemanager "github.com/OffchainLabs/bold/challenge-manager" + "github.com/OffchainLabs/bold/challenge-manager/types" + "github.com/OffchainLabs/bold/solgen/go/challengeV2gen" + "github.com/OffchainLabs/bold/solgen/go/rollupgen" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/nitro/arbutil" +) + +func NewManager( + ctx context.Context, + rollupAddress common.Address, + txOpts *bind.TransactOpts, + callOpts bind.CallOpts, + client arbutil.L1Interface, + statelessBlockValidator *StatelessBlockValidator, + historyCacheBaseDir string, +) (*challengemanager.Manager, error) { + chain, err := solimpl.NewAssertionChain( + ctx, + rollupAddress, + txOpts, + client, + ) + if err != nil { + return nil, err + } + userLogic, err := rollupgen.NewRollupUserLogic( + rollupAddress, client, + ) + if err != nil { + return nil, err + } + challengeManagerAddr, err := userLogic.RollupUserLogicCaller.ChallengeManager( + &bind.CallOpts{Context: ctx}, + ) + if err != nil { + return nil, err + } + managerBinding, err := challengeV2gen.NewEdgeChallengeManager(challengeManagerAddr, client) + if err != nil { + return nil, err + } + bigStepEdgeHeight, err := managerBinding.LAYERZEROBIGSTEPEDGEHEIGHT(&callOpts) + if err != nil { + return nil, err + } + smallStepEdgeHeight, err := managerBinding.LAYERZEROSMALLSTEPEDGEHEIGHT(&callOpts) + if err != nil { + return nil, err + } + stateManager, err := NewStateManager( + statelessBlockValidator, + nil, + smallStepEdgeHeight.Uint64(), + bigStepEdgeHeight.Uint64()*smallStepEdgeHeight.Uint64(), + historyCacheBaseDir, + ) + if err != nil { + return nil, err + } + manager, err := challengemanager.New( + ctx, + chain, + client, + stateManager, + rollupAddress, + challengemanager.WithMode(types.MakeMode), + ) + if err != nil { + return nil, err + } + return manager, nil +} diff --git a/staker/state_provider.go b/staker/state_provider.go new file mode 100644 index 0000000000..8caaaa3bb4 --- /dev/null +++ b/staker/state_provider.go @@ -0,0 +1,668 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package staker + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + protocol "github.com/OffchainLabs/bold/chain-abstraction" + "github.com/OffchainLabs/bold/containers/option" + l2stateprovider "github.com/OffchainLabs/bold/layer2-state-provider" + "github.com/OffchainLabs/bold/solgen/go/rollupgen" + commitments "github.com/OffchainLabs/bold/state-commitments/history" + prefixproofs "github.com/OffchainLabs/bold/state-commitments/prefix-proofs" + + "github.com/offchainlabs/nitro/arbutil" + challengecache "github.com/offchainlabs/nitro/staker/challenge-cache" + "github.com/offchainlabs/nitro/validator" +) + +var _ l2stateprovider.Provider = (*StateManager)(nil) + +// Defines the ABI encoding structure for submission of prefix proofs to the protocol contracts +var ( + b32Arr, _ = abi.NewType("bytes32[]", "", nil) + // ProofArgs for submission to the protocol. + ProofArgs = abi.Arguments{ + {Type: b32Arr, Name: "prefixExpansion"}, + {Type: b32Arr, Name: "prefixProof"}, + } +) + +var ErrChainCatchingUp = errors.New("chain catching up") + +type StateManager struct { + validator *StatelessBlockValidator + blockValidator *BlockValidator + numOpcodesPerBigStep uint64 + maxWavmOpcodes uint64 + historyCache challengecache.HistoryCommitmentCacher +} + +func NewStateManager(val *StatelessBlockValidator, blockValidator *BlockValidator, numOpcodesPerBigStep uint64, maxWavmOpcodes uint64, cacheBaseDir string) (*StateManager, error) { + historyCache := challengecache.New(cacheBaseDir) + return &StateManager{ + validator: val, + blockValidator: blockValidator, + numOpcodesPerBigStep: numOpcodesPerBigStep, + maxWavmOpcodes: maxWavmOpcodes, + historyCache: historyCache, + }, nil +} + +// ExecutionStateMsgCount If the state manager locally has this validated execution state. +// Returns ErrNoExecutionState if not found, or ErrChainCatchingUp if not yet +// validated / syncing. +func (s *StateManager) ExecutionStateMsgCount(ctx context.Context, state *protocol.ExecutionState) (uint64, error) { + if state.GlobalState.PosInBatch != 0 { + return 0, fmt.Errorf("position in batch must be zero, but got %d", state.GlobalState.PosInBatch) + } + if state.GlobalState.Batch == 1 && state.GlobalState.PosInBatch == 0 { + // TODO: 1 is correct? + return 1, nil + } + batch := state.GlobalState.Batch - 1 + messageCount, err := s.validator.inboxTracker.GetBatchMessageCount(batch) + if err != nil { + return 0, err + } + validatedExecutionState, err := s.executionStateAtMessageNumberImpl(ctx, uint64(messageCount)-1) + if err != nil { + return 0, err + } + if validatedExecutionState.GlobalState.Batch < batch { + return 0, ErrChainCatchingUp + } + res, err := s.validator.streamer.ResultAtCount(messageCount) + if err != nil { + return 0, err + } + if res.BlockHash != state.GlobalState.BlockHash || res.SendRoot != state.GlobalState.SendRoot { + return 0, l2stateprovider.ErrNoExecutionState + } + return uint64(messageCount), nil +} + +// ExecutionStateAtMessageNumber Produces the l2 state to assert at the message number specified. +// Makes sure that PosInBatch is always 0 +func (s *StateManager) ExecutionStateAtMessageNumber(ctx context.Context, messageNumber uint64) (*protocol.ExecutionState, error) { + executionState, err := s.executionStateAtMessageNumberImpl(ctx, messageNumber) + if err != nil { + return nil, err + } + if executionState.GlobalState.PosInBatch != 0 { + executionState.GlobalState.Batch++ + executionState.GlobalState.PosInBatch = 0 + } + return executionState, nil +} + +func (s *StateManager) executionStateAtMessageNumberImpl(ctx context.Context, messageNumber uint64) (*protocol.ExecutionState, error) { + batch, err := s.findBatchAfterMessageCount(arbutil.MessageIndex(messageNumber)) + if err != nil { + return &protocol.ExecutionState{}, err + } + batchMsgCount, err := s.validator.inboxTracker.GetBatchMessageCount(batch) + if err != nil { + return &protocol.ExecutionState{}, err + } + if batchMsgCount <= arbutil.MessageIndex(messageNumber) { + batch++ + } + globalState, err := s.getInfoAtMessageCountAndBatch(arbutil.MessageIndex(messageNumber), batch) + if err != nil { + return &protocol.ExecutionState{}, err + } + return &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState(globalState), + MachineStatus: protocol.MachineStatusFinished, // TODO: Why hardcode? + }, nil +} + +// HistoryCommitmentAtMessage Produces a block history commitment of messageCount. +func (s *StateManager) HistoryCommitmentAtMessage(ctx context.Context, messageNumber uint64) (commitments.History, error) { + batch, err := s.findBatchAfterMessageCount(arbutil.MessageIndex(messageNumber)) + if err != nil { + return commitments.History{}, err + } + batchMsgCount, err := s.validator.inboxTracker.GetBatchMessageCount(messageNumber) + if err != nil { + return commitments.History{}, err + } + if batchMsgCount <= arbutil.MessageIndex(messageNumber) { + batch++ + } + stateRoot, err := s.getHashAtMessageCountAndBatch(ctx, arbutil.MessageIndex(messageNumber), batch) + if err != nil { + return commitments.History{}, err + } + return commitments.New([]common.Hash{stateRoot}) +} + +func (s *StateManager) HistoryCommitmentAtBatch(ctx context.Context, batchNumber uint64) (commitments.History, error) { + batchMsgCount, err := s.validator.inboxTracker.GetBatchMessageCount(batchNumber) + if err != nil { + return commitments.History{}, err + } + res, err := s.validator.streamer.ResultAtCount(batchMsgCount - 1) + if err != nil { + return commitments.History{}, err + } + state := validator.GoGlobalState{ + BlockHash: res.BlockHash, + SendRoot: res.SendRoot, + Batch: batchNumber, + PosInBatch: 0, + } + machineHash := crypto.Keccak256Hash([]byte("Machine finished:"), state.Hash().Bytes()) + return commitments.New([]common.Hash{machineHash}) +} + +// BigStepCommitmentUpTo Produces a big step history commitment from big step 0 to toBigStep within block +// challenge heights blockHeight and blockHeight+1. +func (s *StateManager) BigStepCommitmentUpTo(ctx context.Context, wasmModuleRoot common.Hash, messageNumber uint64, toBigStep uint64) (commitments.History, error) { + result, err := s.intermediateBigStepLeaves(ctx, wasmModuleRoot, messageNumber, toBigStep) + if err != nil { + return commitments.History{}, err + } + return commitments.New(result) +} + +// SmallStepCommitmentUpTo Produces a small step history commitment from small step 0 to N between +// big steps bigStep to bigStep+1 within block challenge heights blockHeight to blockHeight+1. +func (s *StateManager) SmallStepCommitmentUpTo(ctx context.Context, wasmModuleRoot common.Hash, messageNumber uint64, bigStep uint64, toSmallStep uint64) (commitments.History, error) { + result, err := s.intermediateSmallStepLeaves(ctx, wasmModuleRoot, messageNumber, bigStep, toSmallStep) + if err != nil { + return commitments.History{}, err + } + return commitments.New(result) +} + +// HistoryCommitmentUpToBatch Produces a block challenge history commitment in a certain inclusive block range, +// but padding states with duplicates after the first state with a batch count of at least the specified max. +func (s *StateManager) HistoryCommitmentUpToBatch(ctx context.Context, messageNumberStart uint64, messageNumberEnd uint64, nextBatchCount uint64) (commitments.History, error) { + stateRoots, err := s.statesUpTo(messageNumberStart, messageNumberEnd, nextBatchCount) + if err != nil { + return commitments.History{}, err + } + return commitments.New(stateRoots) +} + +// BigStepLeafCommitment Produces a big step history commitment for all big steps within block +// challenge heights blockHeight to blockHeight+1. +func (s *StateManager) BigStepLeafCommitment(ctx context.Context, wasmModuleRoot common.Hash, messageNumber uint64) (commitments.History, error) { + // Number of big steps between assertion heights A and B will be + // fixed. It is simply the max number of opcodes + // per block divided by the size of a big step. + numBigSteps := s.maxWavmOpcodes / s.numOpcodesPerBigStep + return s.BigStepCommitmentUpTo(ctx, wasmModuleRoot, messageNumber, numBigSteps) +} + +// SmallStepLeafCommitment Produces a small step history commitment for all small steps between +// big steps bigStep to bigStep+1 within block challenge heights blockHeight to blockHeight+1. +func (s *StateManager) SmallStepLeafCommitment(ctx context.Context, wasmModuleRoot common.Hash, messageNumber uint64, bigStep uint64) (commitments.History, error) { + return s.SmallStepCommitmentUpTo( + ctx, + wasmModuleRoot, + messageNumber, + bigStep, + s.numOpcodesPerBigStep, + ) +} + +// PrefixProofUpToBatch Produces a prefix proof in a block challenge from height A to B, +// but padding states with duplicates after the first state with a batch count of at least the specified max. +func (s *StateManager) PrefixProofUpToBatch( + ctx context.Context, + startHeight, + fromMessageNumber, + toMessageNumber, + batchCount uint64, +) ([]byte, error) { + if toMessageNumber > batchCount { + return nil, errors.New("toMessageNumber should not be greater than batchCount") + } + states, err := s.statesUpTo(startHeight, toMessageNumber, batchCount) + if err != nil { + return nil, err + } + loSize := fromMessageNumber + 1 - startHeight + hiSize := toMessageNumber + 1 - startHeight + return s.getPrefixProof(loSize, hiSize, states) +} + +// BigStepPrefixProof Produces a big step prefix proof from height A to B for heights fromBlockChallengeHeight to H+1 +// within a block challenge. +func (s *StateManager) BigStepPrefixProof( + ctx context.Context, + wasmModuleRoot common.Hash, + messageNumber uint64, + fromBigStep uint64, + toBigStep uint64, +) ([]byte, error) { + prefixLeaves, err := s.intermediateBigStepLeaves(ctx, wasmModuleRoot, messageNumber, toBigStep) + if err != nil { + return nil, err + } + loSize := fromBigStep + 1 + hiSize := toBigStep + 1 + return s.getPrefixProof(loSize, hiSize, prefixLeaves) +} + +// SmallStepPrefixProof Produces a small step prefix proof from height A to B for big step S to S+1 and +// block challenge height heights H to H+1. +func (s *StateManager) SmallStepPrefixProof(ctx context.Context, wasmModuleRoot common.Hash, messageNumber uint64, bigStep uint64, fromSmallStep uint64, toSmallStep uint64) ([]byte, error) { + prefixLeaves, err := s.intermediateSmallStepLeaves(ctx, wasmModuleRoot, messageNumber, bigStep, toSmallStep) + if err != nil { + return nil, err + } + loSize := fromSmallStep + 1 + hiSize := toSmallStep + 1 + return s.getPrefixProof(loSize, hiSize, prefixLeaves) +} + +// Like abi.NewType but panics if it fails for use in constants +func newStaticType(t string, internalType string, components []abi.ArgumentMarshaling) abi.Type { + ty, err := abi.NewType(t, internalType, components) + if err != nil { + panic(err) + } + return ty +} + +var bytes32Type = newStaticType("bytes32", "", nil) +var uint64Type = newStaticType("uint64", "", nil) +var uint8Type = newStaticType("uint8", "", nil) + +var WasmModuleProofAbi = abi.Arguments{ + { + Name: "lastHash", + Type: bytes32Type, + }, + { + Name: "assertionExecHash", + Type: bytes32Type, + }, + { + Name: "inboxAcc", + Type: bytes32Type, + }, +} + +var ExecutionStateAbi = abi.Arguments{ + { + Name: "b1", + Type: bytes32Type, + }, + { + Name: "b2", + Type: bytes32Type, + }, + { + Name: "u1", + Type: uint64Type, + }, + { + Name: "u2", + Type: uint64Type, + }, + { + Name: "status", + Type: uint8Type, + }, +} + +func (s *StateManager) OneStepProofData( + ctx context.Context, + wasmModuleRoot common.Hash, + postState rollupgen.ExecutionState, + messageNumber, + bigStep, + smallStep uint64, +) (*protocol.OneStepData, []common.Hash, []common.Hash, error) { + endCommit, err := s.SmallStepCommitmentUpTo( + ctx, + wasmModuleRoot, + messageNumber, + bigStep, + smallStep+1, + ) + if err != nil { + return nil, nil, nil, err + } + startCommit, err := s.SmallStepCommitmentUpTo( + ctx, + wasmModuleRoot, + messageNumber, + bigStep, + smallStep, + ) + if err != nil { + return nil, nil, nil, err + } + + step := bigStep*s.numOpcodesPerBigStep + smallStep + + entry, err := s.validator.CreateReadyValidationEntry(ctx, arbutil.MessageIndex(messageNumber)) + if err != nil { + return nil, nil, nil, err + } + input, err := entry.ToInput() + if err != nil { + return nil, nil, nil, err + } + execRun, err := s.validator.execSpawner.CreateExecutionRun(wasmModuleRoot, input).Await(ctx) + if err != nil { + return nil, nil, nil, err + } + + oneStepProofPromise := execRun.GetProofAt(step) + oneStepProof, err := oneStepProofPromise.Await(ctx) + if err != nil { + return nil, nil, nil, err + } + + machineStepPromise := execRun.GetStepAt(step) + machineStep, err := machineStepPromise.Await(ctx) + if err != nil { + return nil, nil, nil, err + } + beforeHash := machineStep.Hash + if beforeHash != startCommit.LastLeaf { + return nil, nil, nil, fmt.Errorf("machine executed to start step %v hash %v but expected %v", step, beforeHash, startCommit.LastLeaf) + } + + machineStepPromise = execRun.GetStepAt(step + 1) + machineStep, err = machineStepPromise.Await(ctx) + if err != nil { + return nil, nil, nil, err + } + afterHash := machineStep.Hash + if afterHash != endCommit.LastLeaf { + return nil, nil, nil, fmt.Errorf("machine executed to end step %v hash %v but expected %v", step+1, beforeHash, endCommit.LastLeaf) + } + + data := &protocol.OneStepData{ + BeforeHash: startCommit.LastLeaf, + Proof: oneStepProof, + } + return data, startCommit.LastLeafProof, endCommit.LastLeafProof, nil +} + +func (s *StateManager) AgreesWithHistoryCommitment( + ctx context.Context, + wasmModuleRoot common.Hash, + assertionInboxMaxCount uint64, + parentAssertionAfterStateBatch uint64, + edgeType protocol.EdgeType, + heights protocol.OriginHeights, + history l2stateprovider.History, +) (bool, error) { + var localCommit commitments.History + var err error + switch edgeType { + case protocol.BlockChallengeEdge: + localCommit, err = s.HistoryCommitmentUpToBatch(ctx, parentAssertionAfterStateBatch, parentAssertionAfterStateBatch+history.Height, assertionInboxMaxCount) + if err != nil { + return false, err + } + case protocol.BigStepChallengeEdge: + localCommit, err = s.BigStepCommitmentUpTo( + ctx, + wasmModuleRoot, + uint64(heights.BlockChallengeOriginHeight), + history.Height, + ) + if err != nil { + return false, err + } + case protocol.SmallStepChallengeEdge: + localCommit, err = s.SmallStepCommitmentUpTo( + ctx, + wasmModuleRoot, + uint64(heights.BlockChallengeOriginHeight), + uint64(heights.BigStepChallengeOriginHeight), + history.Height, + ) + if err != nil { + return false, err + } + default: + return false, errors.New("unsupported edge type") + } + return localCommit.Height == history.Height && localCommit.Merkle == history.MerkleRoot, nil +} + +func (s *StateManager) getPrefixProof(loSize uint64, hiSize uint64, leaves []common.Hash) ([]byte, error) { + prefixExpansion, err := prefixproofs.ExpansionFromLeaves(leaves[:loSize]) + if err != nil { + return nil, err + } + prefixProof, err := prefixproofs.GeneratePrefixProof( + loSize, + prefixExpansion, + leaves[loSize:hiSize], + prefixproofs.RootFetcherFromExpansion, + ) + if err != nil { + return nil, err + } + _, numRead := prefixproofs.MerkleExpansionFromCompact(prefixProof, loSize) + onlyProof := prefixProof[numRead:] + return ProofArgs.Pack(&prefixExpansion, &onlyProof) +} + +func (s *StateManager) intermediateBigStepLeaves(ctx context.Context, wasmModuleRoot common.Hash, blockHeight uint64, toBigStep uint64) ([]common.Hash, error) { + cacheKey := &challengecache.Key{ + WavmModuleRoot: wasmModuleRoot, + MessageHeight: protocol.Height(blockHeight), + BigStepHeight: option.None[protocol.Height](), + } + cachedRoots, err := s.historyCache.Get(cacheKey, protocol.Height(toBigStep)) + if err == nil { + return cachedRoots, nil + } + entry, err := s.validator.CreateReadyValidationEntry(ctx, arbutil.MessageIndex(blockHeight)) + if err != nil { + return nil, err + } + input, err := entry.ToInput() + if err != nil { + return nil, err + } + execRun, err := s.validator.execSpawner.CreateExecutionRun(wasmModuleRoot, input).Await(ctx) + if err != nil { + return nil, err + } + bigStepLeaves := execRun.GetBigStepLeavesUpTo(toBigStep, s.numOpcodesPerBigStep) + result, err := bigStepLeaves.Await(ctx) + if err != nil { + return nil, err + } + // TODO: Hacky workaround to avoid saving a history commitment to height 0. + if len(result) > 1 { + if err := s.historyCache.Put(cacheKey, result); err != nil { + if !errors.Is(err, challengecache.ErrFileAlreadyExists) { + return nil, err + } + } + } + return result, nil +} + +func (s *StateManager) intermediateSmallStepLeaves(ctx context.Context, wasmModuleRoot common.Hash, blockHeight uint64, bigStep uint64, toSmallStep uint64) ([]common.Hash, error) { + cacheKey := &challengecache.Key{ + WavmModuleRoot: wasmModuleRoot, + MessageHeight: protocol.Height(blockHeight), + BigStepHeight: option.Some[protocol.Height](protocol.Height(bigStep)), + } + cachedRoots, err := s.historyCache.Get(cacheKey, protocol.Height(toSmallStep)) + if err == nil { + return cachedRoots, nil + } + entry, err := s.validator.CreateReadyValidationEntry(ctx, arbutil.MessageIndex(blockHeight)) + if err != nil { + return nil, err + } + input, err := entry.ToInput() + if err != nil { + return nil, err + } + execRun, err := s.validator.execSpawner.CreateExecutionRun(wasmModuleRoot, input).Await(ctx) + if err != nil { + return nil, err + } + smallStepLeaves := execRun.GetSmallStepLeavesUpTo(bigStep, toSmallStep, s.numOpcodesPerBigStep) + result, err := smallStepLeaves.Await(ctx) + if err != nil { + return nil, err + } + // TODO: Hacky workaround to avoid saving a history commitment to height 0. + if len(result) > 1 { + if err := s.historyCache.Put(cacheKey, result); err != nil { + if !errors.Is(err, challengecache.ErrFileAlreadyExists) { + return nil, err + } + } + } + return result, nil +} + +// TODO: Rename block to message. +func (s *StateManager) statesUpTo(blockStart uint64, blockEnd uint64, nextBatchCount uint64) ([]common.Hash, error) { + if blockEnd < blockStart { + return nil, fmt.Errorf("end block %v is less than start block %v", blockEnd, blockStart) + } + batch, err := s.findBatchAfterMessageCount(arbutil.MessageIndex(blockStart)) + if err != nil { + return nil, err + } + // TODO: Document why we cannot validate genesis. + if batch == 0 { + batch += 1 + } + // The size is the number of elements being committed to. For example, if the height is 7, there will + // be 8 elements being committed to from [0, 7] inclusive. + desiredStatesLen := int(blockEnd - blockStart + 1) + var stateRoots []common.Hash + var lastStateRoot common.Hash + + // TODO: Document why we cannot validate genesis. + if blockStart == 0 { + blockStart += 1 + } + for i := blockStart; i <= blockEnd; i++ { + batchMsgCount, err := s.validator.inboxTracker.GetBatchMessageCount(batch) + if err != nil { + return nil, err + } + if batchMsgCount <= arbutil.MessageIndex(i) { + batch++ + } + gs, err := s.getInfoAtMessageCountAndBatch(arbutil.MessageIndex(i), batch) + if err != nil { + return nil, err + } + if gs.Batch >= nextBatchCount { + if gs.Batch > nextBatchCount || gs.PosInBatch > 0 { + return nil, fmt.Errorf("overran next batch count %v with global state batch %v position %v", nextBatchCount, gs.Batch, gs.PosInBatch) + } + break + } + stateRoot := crypto.Keccak256Hash([]byte("Machine finished:"), gs.Hash().Bytes()) + stateRoots = append(stateRoots, stateRoot) + lastStateRoot = stateRoot + } + for len(stateRoots) < desiredStatesLen { + stateRoots = append(stateRoots, lastStateRoot) + } + return stateRoots, nil +} + +func (s *StateManager) findBatchAfterMessageCount(msgCount arbutil.MessageIndex) (uint64, error) { + if msgCount == 0 { + return 0, nil + } + low := uint64(0) + batchCount, err := s.validator.inboxTracker.GetBatchCount() + if err != nil { + return 0, err + } + high := batchCount + for { + // Binary search invariants: + // - messageCount(high) >= msgCount + // - messageCount(low-1) < msgCount + // - high >= low + if high < low { + return 0, fmt.Errorf("when attempting to find batch for message count %v high %v < low %v", msgCount, high, low) + } + mid := (low + high) / 2 + batchMsgCount, err := s.validator.inboxTracker.GetBatchMessageCount(mid) + if err != nil { + // TODO: There is a circular dep with the error in inbox_tracker.go, we + // should move it somewhere else and use errors.Is. + if strings.Contains(err.Error(), "accumulator not found") { + high = mid + } else { + return 0, fmt.Errorf("failed to get batch metadata while binary searching: %w", err) + } + } + if batchMsgCount < msgCount { + low = mid + 1 + } else if batchMsgCount == msgCount { + return mid + 1, nil + } else if mid == low { // batchMsgCount > msgCount + return mid, nil + } else { // batchMsgCount > msgCount + high = mid + } + } +} + +func (s *StateManager) getHashAtMessageCountAndBatch(_ context.Context, messageCount arbutil.MessageIndex, batch uint64) (common.Hash, error) { + gs, err := s.getInfoAtMessageCountAndBatch(messageCount, batch) + if err != nil { + return common.Hash{}, err + } + return crypto.Keccak256Hash([]byte("Machine finished:"), gs.Hash().Bytes()), nil +} + +func (s *StateManager) getInfoAtMessageCountAndBatch(messageCount arbutil.MessageIndex, batch uint64) (validator.GoGlobalState, error) { + globalState, err := s.findGlobalStateFromMessageCountAndBatch(messageCount, batch) + if err != nil { + return validator.GoGlobalState{}, err + } + return globalState, nil +} + +func (s *StateManager) findGlobalStateFromMessageCountAndBatch(count arbutil.MessageIndex, batch uint64) (validator.GoGlobalState, error) { + var prevBatchMsgCount arbutil.MessageIndex + var err error + if batch > 0 { + prevBatchMsgCount, err = s.validator.inboxTracker.GetBatchMessageCount(batch - 1) + if err != nil { + return validator.GoGlobalState{}, err + } + if prevBatchMsgCount > count { + return validator.GoGlobalState{}, errors.New("bad batch provided") + } + } + res, err := s.validator.streamer.ResultAtCount(count) + if err != nil { + return validator.GoGlobalState{}, err + } + return validator.GoGlobalState{ + BlockHash: res.BlockHash, + SendRoot: res.SendRoot, + Batch: batch, + PosInBatch: uint64(count - prevBatchMsgCount), + }, nil +} diff --git a/system_tests/assertion_on_large_number_of_batch_test.go b/system_tests/assertion_on_large_number_of_batch_test.go new file mode 100644 index 0000000000..cb59206a02 --- /dev/null +++ b/system_tests/assertion_on_large_number_of_batch_test.go @@ -0,0 +1,235 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE + +//go:build assertion_on_large_number_of_batch_test +// +build assertion_on_large_number_of_batch_test + +package arbtest + +import ( + "context" + "encoding/json" + "math" + "math/big" + "os" + "testing" + "time" + + "github.com/OffchainLabs/bold/assertions" + protocol "github.com/OffchainLabs/bold/chain-abstraction" + solimpl "github.com/OffchainLabs/bold/chain-abstraction/sol-implementation" + "github.com/OffchainLabs/bold/solgen/go/mocksgen" + "github.com/OffchainLabs/bold/solgen/go/rollupgen" + challenge_testing "github.com/OffchainLabs/bold/testing" + "github.com/OffchainLabs/bold/testing/setup" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/staker" + "github.com/offchainlabs/nitro/validator/server_common" + "github.com/offchainlabs/nitro/validator/valnode" +) + +var ( + blockChallengeLeafHeight = uint64(1 << 5) // 32 + bigStepChallengeLeafHeight = uint64(1 << 11) // 2048 + smallStepChallengeLeafHeight = uint64(1 << 20) // 1048576 +) + +// Helps in testing the feasibility of assertion after the protocol upgrade. +func TestAssertionOnLargeNumberOfBatch(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l2node, assertionChain := setupAndPostBatches(t, ctx) + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + stateless, err := staker.NewStatelessBlockValidator( + l2node.InboxReader, + l2node.InboxTracker, + l2node.TxStreamer, + l2node.Execution.Recorder, + l2node.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = stateless.Start(ctx) + Require(t, err) + + manager, err := staker.NewStateManager(stateless, nil, numOpcodesPerBigStepTest, maxWavmOpcodesTest, t.TempDir()) + Require(t, err) + + poster := assertions.NewPoster( + assertionChain, + manager, + "test", + time.Second, + ) + _, err = poster.PostAssertion(ctx) + Require(t, err) +} + +func setupAndPostBatches(t *testing.T, ctx context.Context) (*arbnode.Node, protocol.Protocol) { + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.LvlInfo) + log.Root().SetHandler(glogger) + + initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) + l1Info := NewL1TestInfo(t) + l1Info.GenerateGenesisAccount("deployer", initialBalance) + l1Info.GenerateGenesisAccount("asserter", initialBalance) + l1Info.GenerateGenesisAccount("sequencer", initialBalance) + l1Info.GenerateGenesisAccount("RollupOwner", initialBalance) + + chainConfig := params.ArbitrumDevTestChainConfig() + l1Info, l1Backend, _, _ := createTestL1BlockChain(t, l1Info) + conf := arbnode.ConfigDefaultL1Test() + conf.BlockValidator.Enable = false + conf.BatchPoster.Enable = false + conf.InboxReader.CheckDelay = time.Second + + var valStack *node.Node + _, valStack = createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + configByValidationNode(t, conf, valStack) + + l1TransactionOpts := l1Info.GetDefaultTransactOpts("RollupOwner", ctx) + stakeToken, tx, tokenBindings, err := mocksgen.DeployTestWETH9( + &l1TransactionOpts, + l1Backend, + "Weth", + "WETH", + ) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1Backend, tx) + Require(t, err) + value, _ := new(big.Int).SetString("10000", 10) + l1TransactionOpts.Value = value + tx, err = tokenBindings.Deposit(&l1TransactionOpts) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1Backend, tx) + Require(t, err) + l1TransactionOpts.Value = nil + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1Backend, tx) + Require(t, err) + rollupAddresses, assertionChain := deployBoldContracts(t, ctx, l1Info, l1Backend, chainConfig.ChainID, stakeToken) + l1Info.SetContract("Bridge", rollupAddresses.Bridge) + l1Info.SetContract("SequencerInbox", rollupAddresses.SequencerInbox) + l1Info.SetContract("Inbox", rollupAddresses.Inbox) + initMessage := getInitMessage(ctx, t, l1Backend, rollupAddresses) + + sequencerTxOpts := l1Info.GetDefaultTransactOpts("sequencer", ctx) + + bridgeAddr, seqInbox, seqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) + + l2Info, l2Stack, l2ChainDb, l2ArbDb, l2Blockchain := createL2BlockChainWithStackConfig(t, nil, "", chainConfig, initMessage, nil, nil) + rollupAddresses.Bridge = bridgeAddr + rollupAddresses.SequencerInbox = seqInboxAddr + + fatalErrChan := make(chan error, 10) + l2Node, err := arbnode.CreateNode(ctx, l2Stack, l2ChainDb, l2ArbDb, NewFetcherFromConfig(conf), l2Blockchain, l1Backend, rollupAddresses, nil, nil, nil, fatalErrChan) + Require(t, err) + err = l2Node.Start(ctx) + Require(t, err) + + l2Info.GenerateAccount("Destination") + + rollup, err := rollupgen.NewRollupAdminLogic(l2Node.DeployInfo.Rollup, l1Backend) + Require(t, err) + deployAuth := l1Info.GetDefaultTransactOpts("RollupOwner", ctx) + tx, err = rollup.SetMinimumAssertionPeriod(&deployAuth, big.NewInt(1)) + Require(t, err) + + for i := 0; i <= int(math.Pow(2, 26)); i++ { + makeBatch(t, l2Node, l2Info, l1Backend, &sequencerTxOpts, seqInbox, seqInboxAddr, -1) + } + return l2Node, assertionChain +} + +func deployBoldContracts( + t *testing.T, + ctx context.Context, + l1info info, + backend *ethclient.Client, + chainId *big.Int, + stakeToken common.Address, +) (*chaininfo.RollupAddresses, *solimpl.AssertionChain) { + l1TransactionOpts := l1info.GetDefaultTransactOpts("RollupOwner", ctx) + locator, err := server_common.NewMachineLocator("") + Require(t, err) + + cfg := challenge_testing.GenerateRollupConfig( + false, + locator.LatestWasmModuleRoot(), + l1TransactionOpts.From, + chainId, + common.Address{}, + big.NewInt(1), + stakeToken, + challenge_testing.WithLevelZeroHeights(&challenge_testing.LevelZeroHeights{ + BlockChallengeHeight: blockChallengeLeafHeight, + BigStepChallengeHeight: bigStepChallengeLeafHeight, + SmallStepChallengeHeight: smallStepChallengeLeafHeight, + }), + ) + config, err := json.Marshal(params.ArbitrumDevTestChainConfig()) + if err != nil { + return nil, nil + } + cfg.ChainConfig = string(config) + + addresses, err := setup.DeployFullRollupStack( + ctx, + backend, + &l1TransactionOpts, + l1info.GetAddress("sequencer"), + cfg, + false, + ) + Require(t, err) + + asserter := l1info.GetDefaultTransactOpts("asserter", ctx) + chain, err := solimpl.NewAssertionChain( + ctx, + addresses.Rollup, + &asserter, + backend, + ) + Require(t, err) + + chalManager, err := chain.SpecChallengeManager(ctx) + Require(t, err) + chalManagerAddr := chalManager.Address() + seed, _ := new(big.Int).SetString("1000", 10) + value, _ := new(big.Int).SetString("10000", 10) + tokenBindings, err := mocksgen.NewTestWETH9(stakeToken, backend) + Require(t, err) + tx, err := tokenBindings.TestWETH9Transactor.Transfer(&l1TransactionOpts, asserter.From, seed) + Require(t, err) + EnsureTxSucceeded(ctx, backend, tx) + tx, err = tokenBindings.TestWETH9Transactor.Approve(&asserter, addresses.Rollup, value) + Require(t, err) + EnsureTxSucceeded(ctx, backend, tx) + tx, err = tokenBindings.TestWETH9Transactor.Approve(&asserter, chalManagerAddr, value) + Require(t, err) + EnsureTxSucceeded(ctx, backend, tx) + + return &chaininfo.RollupAddresses{ + Bridge: addresses.Bridge, + Inbox: addresses.Inbox, + SequencerInbox: addresses.SequencerInbox, + Rollup: addresses.Rollup, + ValidatorUtils: addresses.ValidatorUtils, + ValidatorWalletCreator: addresses.ValidatorWalletCreator, + DeployedAt: addresses.DeployedAt, + }, chain +} diff --git a/system_tests/manager_test.go b/system_tests/manager_test.go new file mode 100644 index 0000000000..9bd72d1578 --- /dev/null +++ b/system_tests/manager_test.go @@ -0,0 +1,409 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package arbtest + +import ( + "context" + "github.com/offchainlabs/nitro/util/testhelpers" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos/l2pricing" + "github.com/offchainlabs/nitro/staker" + "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/valnode" + + protocol "github.com/OffchainLabs/bold/chain-abstraction" + commitments "github.com/OffchainLabs/bold/state-commitments/history" + prefixproofs "github.com/OffchainLabs/bold/state-commitments/prefix-proofs" +) + +const numOpcodesPerBigStepTest = uint64(4) +const maxWavmOpcodesTest = uint64(20) + +func TestExecutionStateMsgCount(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + res, err := l2node.TxStreamer.ResultAtCount(1) + Require(t, err) + msgCount, err := manager.ExecutionStateMsgCount(ctx, &protocol.ExecutionState{GlobalState: protocol.GoGlobalState{Batch: 1, BlockHash: res.BlockHash}}) + Require(t, err) + if msgCount != 1 { + Fail(t, "Unexpected msg batch", msgCount, "(expected 1)") + } +} + +func TestExecutionStateAtMessageNumber(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + res, err := l2node.TxStreamer.ResultAtCount(1) + Require(t, err) + expectedState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + Batch: 1, + BlockHash: res.BlockHash, + }, + MachineStatus: protocol.MachineStatusFinished, + } + executionState, err := manager.ExecutionStateAtMessageNumber(ctx, 1) + Require(t, err) + if !reflect.DeepEqual(executionState, expectedState) { + Fail(t, "Unexpected executionState", executionState, "(expected ", expectedState, ")") + } + Require(t, err) +} + +func TestHistoryCommitmentUpTo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + res1, err := l2node.TxStreamer.ResultAtCount(1) + Require(t, err) + expectedHistoryCommitment, err := commitments.New( + []common.Hash{ + crypto.Keccak256Hash( + []byte("Machine finished:"), + validator.GoGlobalState{ + BlockHash: res1.BlockHash, + SendRoot: res1.SendRoot, + Batch: 1, + PosInBatch: 0, + }.Hash().Bytes(), + ), + }, + ) + Require(t, err) + historyCommitment, err := manager.HistoryCommitmentAtMessage(ctx, 1) + Require(t, err) + if !reflect.DeepEqual(historyCommitment, expectedHistoryCommitment) { + Fail(t, "Unexpected HistoryCommitment", historyCommitment, "(expected ", expectedHistoryCommitment, ")") + } +} + +func TestBigStepCommitmentUpTo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + commitment, err := manager.BigStepCommitmentUpTo(ctx, common.Hash{}, 1, 3) + Require(t, err) + if commitment.Height != 3 { + Fail(t, "Unexpected commitment height", commitment.Height, "(expected ", 3, ")") + } +} + +func TestSmallStepCommitmentUpTo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + commitment, err := manager.SmallStepCommitmentUpTo(ctx, common.Hash{}, 1, 3, 2) + Require(t, err) + if commitment.Height != 2 { + Fail(t, "Unexpected commitment height", commitment.Height, "(expected ", 2, ")") + } +} + +func TestHistoryCommitmentUpToBatch(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + res1, err := l2node.TxStreamer.ResultAtCount(1) + Require(t, err) + expectedHistoryCommitment, err := commitments.New( + []common.Hash{ + crypto.Keccak256Hash( + []byte("Machine finished:"), + validator.GoGlobalState{ + BlockHash: res1.BlockHash, + SendRoot: res1.SendRoot, + Batch: 1, + PosInBatch: 0, + }.Hash().Bytes(), + ), + crypto.Keccak256Hash( + []byte("Machine finished:"), + validator.GoGlobalState{ + BlockHash: res1.BlockHash, + SendRoot: res1.SendRoot, + Batch: 1, + PosInBatch: 0, + }.Hash().Bytes(), + ), + }, + ) + Require(t, err) + historyCommitment, err := manager.HistoryCommitmentUpToBatch(ctx, 1, 2, 2) + Require(t, err) + if !reflect.DeepEqual(historyCommitment, expectedHistoryCommitment) { + Fail(t, "Unexpected HistoryCommitment", historyCommitment, "(expected ", expectedHistoryCommitment, ")") + } +} + +func TestBigStepLeafCommitment(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + commitment, err := manager.BigStepLeafCommitment(ctx, common.Hash{}, 1) + Require(t, err) + numBigSteps := maxWavmOpcodesTest / numOpcodesPerBigStepTest + if commitment.Height != numBigSteps { + Fail(t, "Unexpected commitment height", commitment.Height, "(expected ", numBigSteps, ")") + } +} + +func TestSmallStepLeafCommitment(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + commitment, err := manager.SmallStepLeafCommitment(ctx, common.Hash{}, 1, 3) + Require(t, err) + if commitment.Height != numOpcodesPerBigStepTest { + Fail(t, "Unexpected commitment height", commitment.Height, "(expected ", numOpcodesPerBigStepTest, ")") + } +} + +func TestAllPrefixProofs(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + + from := uint64(1) + to := uint64(3) + + loCommit, err := manager.HistoryCommitmentUpToBatch(ctx, 1, from, 10) + Require(t, err) + hiCommit, err := manager.HistoryCommitmentUpToBatch(ctx, 1, to, 10) + Require(t, err) + packedProof, err := manager.PrefixProofUpToBatch(ctx, 1, from, to, 10) + Require(t, err) + + data, err := staker.ProofArgs.Unpack(packedProof) + Require(t, err) + preExpansion, ok := data[0].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + proof, ok := data[1].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + + preExpansionHashes := make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof := make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + + err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: loCommit.Merkle, + PreSize: from, + PostRoot: hiCommit.Merkle, + PostSize: to, + PreExpansion: preExpansionHashes, + PrefixProof: prefixProof, + }) + Require(t, err) + + bigFrom := uint64(1) + + bigCommit, err := manager.BigStepLeafCommitment(ctx, common.Hash{}, from) + Require(t, err) + + bigBisectCommit, err := manager.BigStepCommitmentUpTo(ctx, common.Hash{}, from, bigFrom) + Require(t, err) + if bigFrom != bigBisectCommit.Height { + Fail(t, "Unexpected bigBisectCommit Height", bigBisectCommit.Height, "(expected ", bigFrom, ")") + } + if bigCommit.FirstLeaf != bigBisectCommit.FirstLeaf { + Fail(t, "Unexpected bigBisectCommit FirstLeaf", bigBisectCommit.FirstLeaf, "(expected ", bigCommit.FirstLeaf, ")") + } + + bigProof, err := manager.BigStepPrefixProof(ctx, common.Hash{}, from, bigFrom, bigCommit.Height) + Require(t, err) + + data, err = staker.ProofArgs.Unpack(bigProof) + Require(t, err) + preExpansion, ok = data[0].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + proof, ok = data[1].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + + preExpansionHashes = make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof = make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + + computed, err := prefixproofs.Root(preExpansionHashes) + Require(t, err) + if bigBisectCommit.Merkle != computed { + Fail(t, "Unexpected bigBisectCommit Merkle", bigBisectCommit.Merkle, "(expected ", computed, ")") + } + + err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: bigBisectCommit.Merkle, + PreSize: bigFrom + 1, + PostRoot: bigCommit.Merkle, + PostSize: bigCommit.Height + 1, + PreExpansion: preExpansionHashes, + PrefixProof: prefixProof, + }) + Require(t, err) + + smallCommit, err := manager.SmallStepLeafCommitment(ctx, common.Hash{}, from, bigFrom) + Require(t, err) + + smallFrom := uint64(2) + + smallBisectCommit, err := manager.SmallStepCommitmentUpTo(ctx, common.Hash{}, from, bigFrom, smallFrom) + Require(t, err) + if smallBisectCommit.Height != smallFrom { + Fail(t, "Unexpected smallBisectCommit Height", smallBisectCommit.Height, "(expected ", smallFrom, ")") + } + if smallBisectCommit.FirstLeaf != smallCommit.FirstLeaf { + Fail(t, "Unexpected smallBisectCommit FirstLeaf", smallBisectCommit.FirstLeaf, "(expected ", smallCommit.FirstLeaf, ")") + } + + smallProof, err := manager.SmallStepPrefixProof(ctx, common.Hash{}, from, bigFrom, smallFrom, smallCommit.Height) + Require(t, err) + + data, err = staker.ProofArgs.Unpack(smallProof) + Require(t, err) + preExpansion, ok = data[0].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + proof, ok = data[1].([][32]byte) + if !ok { + Fatal(t, "bad output from packedProof") + } + + preExpansionHashes = make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof = make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + + computed, err = prefixproofs.Root(preExpansionHashes) + Require(t, err) + if smallBisectCommit.Merkle != computed { + Fail(t, "Unexpected smallBisectCommit Merkle", smallBisectCommit.Merkle, "(expected ", computed, ")") + } + + err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: smallBisectCommit.Merkle, + PreSize: smallFrom + 1, + PostRoot: smallCommit.Merkle, + PostSize: smallCommit.Height + 1, + PreExpansion: preExpansionHashes, + PrefixProof: prefixProof, + }) + Require(t, err) +} + +func TestPrefixProofUpToBatchInvalidBatchCount(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l2node, l1stack, manager := setupManger(t, ctx) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + + _, err := manager.PrefixProofUpToBatch(ctx, 0, 0, 2, 1) + if err == nil || !strings.Contains(err.Error(), "toMessageNumber should not be greater than batchCount") { + Fail(t, "batch count", 1, "less than toMessageNumber", 2, "should not be allowed") + } +} +func setupManger(t *testing.T, ctx context.Context) (*arbnode.Node, *node.Node, *staker.StateManager) { + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + l2chainConfig := params.ArbitrumDevTestChainConfig() + l2info := NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(l2chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + _, l2node, l2client, _, l1info, _, l1client, l1stack := createTestNodeOnL1WithConfigImpl(t, ctx, true, nil, l2chainConfig, nil, nil, l2info) + BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000)), l1info, l2info, l1client, l2client, ctx) + l2info.GenerateAccount("BackgroundUser") + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + tx := l2info.PrepareTx("Faucet", "BackgroundUser", l2info.TransferGas, balance, nil) + err := l2client.SendTransaction(ctx, tx) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + for i := uint64(0); i < 10; i++ { + l2info.Accounts["BackgroundUser"].Nonce = i + tx = l2info.PrepareTx("BackgroundUser", "BackgroundUser", l2info.TransferGas, common.Big0, nil) + err = l2client.SendTransaction(ctx, tx) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + } + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + stateless, err := staker.NewStatelessBlockValidator( + l2node.InboxReader, + l2node.InboxTracker, + l2node.TxStreamer, + l2node.Execution.Recorder, + l2node.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = stateless.Start(ctx) + Require(t, err) + manager, err := staker.NewStateManager(stateless, nil, numOpcodesPerBigStepTest, maxWavmOpcodesTest, t.TempDir()) + Require(t, err) + return l2node, l1stack, manager +} + +func Fail(t *testing.T, printables ...interface{}) { + t.Helper() + testhelpers.FailImpl(t, printables...) +} diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index d9c302b33f..2fd3a92ab0 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -116,6 +116,16 @@ func (r *mockExecRun) GetStepAt(position uint64) containers.PromiseInterface[*va }, nil) } +func (r *mockExecRun) GetBigStepLeavesUpTo(toBigStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + // TODO: Add mock implementation for GetBigStepLeavesUpTo + return containers.NewReadyPromise[[]common.Hash](nil, nil) +} + +func (r *mockExecRun) GetSmallStepLeavesUpTo(bigStep uint64, toSmallStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + // TODO: Add mock implementation for GetSmallStepLeavesUpTo + return containers.NewReadyPromise[[]common.Hash](nil, nil) +} + func (r *mockExecRun) GetLastStep() containers.PromiseInterface[*validator.MachineStepResult] { return r.GetStepAt(mockExecLastPos) } diff --git a/validator/interface.go b/validator/interface.go index 5785ac4de1..385604e9d0 100644 --- a/validator/interface.go +++ b/validator/interface.go @@ -4,6 +4,7 @@ import ( "context" "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/util/containers" ) @@ -29,6 +30,8 @@ type ExecutionSpawner interface { type ExecutionRun interface { GetStepAt(uint64) containers.PromiseInterface[*MachineStepResult] + GetBigStepLeavesUpTo(uint64, uint64) containers.PromiseInterface[[]common.Hash] + GetSmallStepLeavesUpTo(uint64, uint64, uint64) containers.PromiseInterface[[]common.Hash] GetLastStep() containers.PromiseInterface[*MachineStepResult] GetProofAt(uint64) containers.PromiseInterface[[]byte] PrepareRange(uint64, uint64) containers.PromiseInterface[struct{}] diff --git a/validator/server_api/valiation_api.go b/validator/server_api/valiation_api.go index ca5aafcee2..094f7e473d 100644 --- a/validator/server_api/valiation_api.go +++ b/validator/server_api/valiation_api.go @@ -142,6 +142,32 @@ func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position u return MachineStepResultToJson(res), nil } +func (a *ExecServerAPI) GetBigStepLeavesUpTo(ctx context.Context, execid uint64, toBigStep uint64, numOpcodesPerBigStep uint64) ([]common.Hash, error) { + run, err := a.getRun(execid) + if err != nil { + return nil, err + } + bigStepLeavesUpTo := run.GetBigStepLeavesUpTo(toBigStep, numOpcodesPerBigStep) + res, err := bigStepLeavesUpTo.Await(ctx) + if err != nil { + return nil, err + } + return res, nil +} + +func (a *ExecServerAPI) GetSmallStepLeavesUpTo(ctx context.Context, execid uint64, bigStep uint64, toSmallStep uint64, numOpcodesPerBigStep uint64) ([]common.Hash, error) { + run, err := a.getRun(execid) + if err != nil { + return nil, err + } + smallStepLeavesUpTo := run.GetSmallStepLeavesUpTo(bigStep, toSmallStep, numOpcodesPerBigStep) + res, err := smallStepLeavesUpTo.Await(ctx) + if err != nil { + return nil, err + } + return res, nil +} + func (a *ExecServerAPI) GetProofAt(ctx context.Context, execid uint64, position uint64) (string, error) { run, err := a.getRun(execid) if err != nil { diff --git a/validator/server_api/validation_client.go b/validator/server_api/validation_client.go index d6143ca917..326cdb8c2f 100644 --- a/validator/server_api/validation_client.go +++ b/validator/server_api/validation_client.go @@ -177,6 +177,28 @@ func (r *ExecutionClientRun) GetStepAt(pos uint64) containers.PromiseInterface[* }) } +func (r *ExecutionClientRun) GetBigStepLeavesUpTo(toBigStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + return stopwaiter.LaunchPromiseThread[[]common.Hash](r, func(ctx context.Context) ([]common.Hash, error) { + var resJson []common.Hash + err := r.client.client.CallContext(ctx, &resJson, Namespace+"_getBigStepLeavesUpTo", r.id, toBigStep, numOpcodesPerBigStep) + if err != nil { + return nil, err + } + return resJson, err + }) +} + +func (r *ExecutionClientRun) GetSmallStepLeavesUpTo(bigStep uint64, toSmallStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + return stopwaiter.LaunchPromiseThread[[]common.Hash](r, func(ctx context.Context) ([]common.Hash, error) { + var resJson []common.Hash + err := r.client.client.CallContext(ctx, &resJson, Namespace+"_getSmallStepLeavesUpTo", r.id, bigStep, toSmallStep, numOpcodesPerBigStep) + if err != nil { + return nil, err + } + return resJson, err + }) +} + func (r *ExecutionClientRun) GetProofAt(pos uint64) containers.PromiseInterface[[]byte] { return stopwaiter.LaunchPromiseThread[[]byte](r, func(ctx context.Context) ([]byte, error) { var resString string diff --git a/validator/server_arb/execution_run.go b/validator/server_arb/execution_run.go index 255d42ab16..0ca939db7c 100644 --- a/validator/server_arb/execution_run.go +++ b/validator/server_arb/execution_run.go @@ -8,6 +8,8 @@ import ( "fmt" "sync" + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" @@ -50,35 +52,76 @@ func (e *executionRun) PrepareRange(start uint64, end uint64) containers.Promise func (e *executionRun) GetStepAt(position uint64) containers.PromiseInterface[*validator.MachineStepResult] { return stopwaiter.LaunchPromiseThread[*validator.MachineStepResult](e, func(ctx context.Context) (*validator.MachineStepResult, error) { - var machine MachineInterface - var err error - if position == ^uint64(0) { - machine, err = e.cache.GetFinalMachine(ctx) - } else { - // todo cache last machine - machine, err = e.cache.GetMachineAt(ctx, position) - } + return e.intermediateGetStepAt(ctx, position) + }) +} + +func (e *executionRun) GetBigStepLeavesUpTo(toBigStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + return stopwaiter.LaunchPromiseThread[[]common.Hash](e, func(ctx context.Context) ([]common.Hash, error) { + var stateRoots []common.Hash + machine, err := e.cache.GetMachineAt(ctx, 0) if err != nil { return nil, err } - machineStep := machine.GetStepCount() - if position != machineStep { - machineRunning := machine.IsRunning() - if machineRunning || machineStep > position { - return nil, fmt.Errorf("machine is in wrong position want: %d, got: %d", position, machine.GetStepCount()) + if !machine.IsRunning() { + return stateRoots, nil + } + for i := uint64(0); i <= toBigStep; i++ { + position := i * numOpcodesPerBigStep + if err = machine.Step(ctx, position); err != nil { + return nil, err } - + stateRoots = append(stateRoots, machine.Hash()) } - result := &validator.MachineStepResult{ - Position: machineStep, - Status: validator.MachineStatus(machine.Status()), - GlobalState: machine.GetGlobalState(), - Hash: machine.Hash(), + return stateRoots, nil + }) +} + +func (e *executionRun) GetSmallStepLeavesUpTo(bigStep uint64, toSmallStep uint64, numOpcodesPerBigStep uint64) containers.PromiseInterface[[]common.Hash] { + return stopwaiter.LaunchPromiseThread[[]common.Hash](e, func(ctx context.Context) ([]common.Hash, error) { + var stateRoots []common.Hash + fromSmall := bigStep * numOpcodesPerBigStep + toSmall := fromSmall + toSmallStep + for i := fromSmall; i <= toSmall; i++ { + machineStep, err := e.intermediateGetStepAt(ctx, i) + if err != nil { + return nil, err + } + stateRoots = append(stateRoots, machineStep.Hash) } - return result, nil + return stateRoots, nil }) } +func (e *executionRun) intermediateGetStepAt(ctx context.Context, position uint64) (*validator.MachineStepResult, error) { + var machine MachineInterface + var err error + if position == ^uint64(0) { + machine, err = e.cache.GetFinalMachine(ctx) + } else { + // todo cache last machina + machine, err = e.cache.GetMachineAt(ctx, position) + } + if err != nil { + return nil, err + } + machineStep := machine.GetStepCount() + if position != machineStep { + machineRunning := machine.IsRunning() + if machineRunning || machineStep > position { + return nil, fmt.Errorf("machine is in wrong position want: %d, got: %d", position, machine.GetStepCount()) + } + + } + result := &validator.MachineStepResult{ + Position: machineStep, + Status: validator.MachineStatus(machine.Status()), + GlobalState: machine.GetGlobalState(), + Hash: machine.Hash(), + } + return result, nil +} + func (e *executionRun) GetProofAt(position uint64) containers.PromiseInterface[[]byte] { return stopwaiter.LaunchPromiseThread[[]byte](e, func(ctx context.Context) ([]byte, error) { machine, err := e.cache.GetMachineAt(ctx, position)