├── .bazelignore ├── .bazelrc ├── .bazelversion ├── .github ├── actions │ └── gomodtidy │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh └── workflows │ ├── fuzz.yml │ └── go.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── BUILD.bazel ├── LICENSE.md ├── MODULE.bazel ├── MODULE.bazel.lock ├── README.md ├── api ├── BUILD.bazel ├── backend │ ├── BUILD.bazel │ └── backend.go ├── db │ ├── BUILD.bazel │ ├── db.go │ ├── db_test.go │ └── schema.go ├── server │ ├── BUILD.bazel │ ├── methods.go │ └── server.go └── types.go ├── assertions ├── BUILD.bazel ├── confirmation.go ├── manager.go ├── manager_test.go ├── poster.go ├── poster_test.go ├── sync.go └── sync_test.go ├── chain-abstraction ├── BUILD.bazel ├── execution_state.go ├── interfaces.go └── sol-implementation │ ├── BUILD.bazel │ ├── assertion_chain.go │ ├── assertion_chain_helper_test.go │ ├── assertion_chain_test.go │ ├── edge_challenge_manager.go │ ├── edge_challenge_manager_test.go │ ├── fast_confirm.go │ ├── fifo_lock.go │ ├── fifo_lock_test.go │ ├── metrics_contract_backend.go │ ├── tracked_contract_backend.go │ ├── tracked_contract_backend_test.go │ ├── transact.go │ ├── types.go │ └── types_test.go ├── challenge-manager ├── BUILD.bazel ├── chain-watcher │ ├── BUILD.bazel │ ├── watcher.go │ └── watcher_test.go ├── challenge-tree │ ├── BUILD.bazel │ ├── add_edge.go │ ├── ancestors.go │ ├── ancestors_test.go │ ├── compute_ancestors_test.go │ ├── local_timer.go │ ├── local_timer_test.go │ ├── mock │ │ ├── BUILD.bazel │ │ └── edge.go │ ├── paths.go │ ├── paths_edge_cases_test.go │ ├── paths_test.go │ ├── tree.go │ └── tree_test.go ├── challenges.go ├── edge-tracker │ ├── BUILD.bazel │ ├── challenge_confirmation.go │ ├── fsm_states.go │ ├── tracker.go │ └── transition_table.go ├── manager.go ├── manager_test.go ├── stack.go └── types │ ├── BUILD.bazel │ ├── interfaces.go │ └── mode.go ├── codecov.yml ├── containers ├── BUILD.bazel ├── events │ ├── BUILD.bazel │ ├── producer.go │ └── producer_test.go ├── fsm │ ├── BUILD.bazel │ ├── fsm.go │ └── fsm_test.go ├── option │ ├── BUILD.bazel │ └── option_type.go ├── slice.go ├── slice_test.go └── threadsafe │ ├── BUILD.bazel │ ├── collections_test.go │ ├── lru_map.go │ ├── lru_set.go │ ├── map.go │ ├── map_test.go │ ├── set.go │ ├── set_test.go │ ├── slice.go │ └── slice_test.go ├── docs ├── ARCHITECTURE.md ├── audits │ └── TrailOfBitsAudit.pdf ├── diagrams │ ├── api.drawio.png │ ├── bold-internals.drawio.png │ ├── edge-tracker-fsm.drawio.png │ └── nitro-integration.drawio.png └── research-specs │ ├── BOLDChallengeProtocol.pdf │ ├── Economics.pdf │ └── TechnicalDeepDive.pdf ├── go.mod ├── go.sum ├── layer2-state-provider ├── BUILD.bazel ├── history_commitment_provider.go ├── history_commitment_provider_test.go └── provider.go ├── logs └── ephemeral │ ├── BUILD.bazel │ ├── log.go │ └── log_test.go ├── math ├── BUILD.bazel ├── intlog2.go ├── intlog2_test.go ├── math.go └── math_test.go ├── nogo.json ├── runtime ├── BUILD.bazel ├── retry.go └── retry_test.go ├── solgen ├── BUILD.bazel ├── gen.go ├── go │ ├── accessorsgen │ │ ├── BUILD.bazel │ │ └── accessorsgen.go │ ├── assertionStakingPoolgen │ │ ├── BUILD.bazel │ │ └── assertionStakingPoolgen.go │ ├── basegen │ │ ├── BUILD.bazel │ │ └── basegen.go │ ├── bridgegen │ │ ├── BUILD.bazel │ │ └── bridgegen.go │ ├── chaingen │ │ ├── BUILD.bazel │ │ └── chaingen.go │ ├── challengeV2gen │ │ ├── BUILD.bazel │ │ └── challengeV2gen.go │ ├── challengegen │ │ ├── BUILD.bazel │ │ └── challengegen.go │ ├── commongen │ │ ├── BUILD.bazel │ │ └── commongen.go │ ├── contractsgen │ │ ├── BUILD.bazel │ │ └── contractsgen.go │ ├── express_lane_auctiongen │ │ ├── BUILD.bazel │ │ └── express_lane_auctiongen.go │ ├── externalgen │ │ ├── BUILD.bazel │ │ └── externalgen.go │ ├── handlergen │ │ ├── BUILD.bazel │ │ └── handlergen.go │ ├── interfacesgen │ │ ├── BUILD.bazel │ │ └── interfacesgen.go │ ├── librariesgen │ │ ├── BUILD.bazel │ │ └── librariesgen.go │ ├── mocksgen │ │ ├── BUILD.bazel │ │ └── mocksgen.go │ ├── node_interfacegen │ │ ├── BUILD.bazel │ │ └── node_interfacegen.go │ ├── ospgen │ │ ├── BUILD.bazel │ │ └── ospgen.go │ ├── precompilesgen │ │ ├── BUILD.bazel │ │ └── precompilesgen.go │ ├── proxiesgen │ │ ├── BUILD.bazel │ │ └── proxiesgen.go │ ├── rollupgen │ │ ├── BUILD.bazel │ │ └── rollupgen.go │ ├── stategen │ │ ├── BUILD.bazel │ │ └── stategen.go │ ├── test_helpersgen │ │ ├── BUILD.bazel │ │ └── test_helpersgen.go │ ├── testgen │ │ ├── BUILD.bazel │ │ └── testgen.go │ └── yulgen │ │ ├── BUILD.bazel │ │ └── yulgen.go └── main.go ├── state-commitments ├── history │ ├── BUILD.bazel │ ├── history_commitment.go │ └── history_commitment_test.go ├── inclusion-proofs │ ├── BUILD.bazel │ ├── inclusion_proofs.go │ └── inclusion_proofs_test.go ├── legacy │ ├── BUILD.bazel │ ├── legacy.go │ └── legacy_test.go └── prefix-proofs │ ├── BUILD.bazel │ ├── merkle_expansions.go │ ├── merkle_expansions_test.go │ ├── prefix_proofs.go │ ├── prefix_proofs_test.go │ └── testdata │ └── fuzz │ └── FuzzVerifyPrefixProof_Go │ ├── 75d9022918aebc68769044c9043bd11a47046e9a443da304857347818e0bb256 │ └── 94136d3efec8e976 ├── testing ├── BUILD.bazel ├── casttest │ ├── BUILD.bazel │ └── safe.go ├── endtoend │ ├── BUILD.bazel │ ├── README.md │ ├── backend │ │ ├── BUILD.bazel │ │ ├── anvil_local.go │ │ ├── anvil_local_test.go │ │ ├── anvil_priv_keys.go │ │ ├── backend.go │ │ └── simulated.go │ ├── e2e_crash_test.go │ ├── e2e_delegated_staking_test.go │ ├── e2e_test.go │ ├── expectations.go │ ├── headers.go │ └── helpers_test.go ├── integration │ ├── BUILD.bazel │ └── prefixproofs_test.go ├── mocks │ ├── BUILD.bazel │ ├── mocks.go │ └── state-provider │ │ ├── BUILD.bazel │ │ ├── execution_engine.go │ │ ├── execution_engine_test.go │ │ ├── history_provider.go │ │ ├── history_provider_test.go │ │ ├── layer2_state_provider.go │ │ └── layer2_state_provider_test.go ├── rollup_config.go ├── setup │ ├── BUILD.bazel │ ├── rollup_stack.go │ └── simulated_backend_wrapper.go └── tx.go ├── third_party ├── BUILD.bazel ├── com_github_datadog_zstd.patch └── com_github_ethereum_go_ethereum_secp256k1.patch ├── time ├── BUILD.bazel ├── time_reference.go └── time_reference_test.go ├── util ├── BUILD.bazel ├── backend.go └── stopwaiter │ ├── BUILD.bazel │ └── stopwaiter.go └── yarn.lock /.bazelignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/.bazelignore -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Abseil requires c++14 or greater. 2 | build --cxxopt=-std=c++20 3 | build --host_cxxopt=-std=c++20 4 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.4.1 2 | -------------------------------------------------------------------------------- /.github/actions/gomodtidy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | COPY entrypoint.sh /entrypoint.sh 4 | 5 | ENTRYPOINT ["/entrypoint.sh"] 6 | -------------------------------------------------------------------------------- /.github/actions/gomodtidy/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Go mod tidy checker' 2 | description: 'Checks that `go mod tidy` has been applied.' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/actions/gomodtidy/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | set -e 3 | export PATH="$PATH:/usr/local/go/bin" 4 | 5 | cd "$GITHUB_WORKSPACE" 6 | 7 | cp go.mod go.mod.orig 8 | cp go.sum go.sum.orig 9 | 10 | go mod tidy -compat=1.17 11 | 12 | echo "Checking go.mod and go.sum:" 13 | checks=0 14 | if [ "$(diff -s go.mod.orig go.mod | grep -c 'Files go.mod.orig and go.mod are identical')" = 1 ]; then 15 | echo "- go.mod is up to date." 16 | checks=$((checks + 1)) 17 | else 18 | echo "- go.mod is NOT up to date." 19 | fi 20 | 21 | if [ "$(diff -s go.sum.orig go.sum | grep -c 'Files go.sum.orig and go.sum are identical')" = 1 ]; then 22 | echo "- go.sum is up to date." 23 | checks=$((checks + 1)) 24 | else 25 | echo "- go.sum is NOT up to date." 26 | fi 27 | 28 | if [ $checks -eq 2 ]; then 29 | exit 0 30 | fi 31 | 32 | # Notify of any issues. 33 | echo "Run 'go mod tidy' to update." 34 | exit 1 35 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: "fuzz" 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "36 2 * * 1,4" 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | list: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 10 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: "stable" 20 | - id: list 21 | uses: shogo82148/actions-go-fuzz/list@v1 22 | outputs: 23 | fuzz-tests: ${{steps.list.outputs.fuzz-tests}} 24 | 25 | fuzz: 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 360 28 | needs: list 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | include: ${{fromJson(needs.list.outputs.fuzz-tests)}} 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: actions/setup-go@v5 36 | with: 37 | go-version: "stable" 38 | - uses: shogo82148/actions-go-fuzz/run@v1 39 | with: 40 | packages: ${{ matrix.package }} 41 | fuzz-regexp: ${{ matrix.func }} 42 | fuzz-time: "1m" 43 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: ["*"] 8 | 9 | jobs: 10 | formatting: 11 | name: Formatting 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Go mod tidy checker 18 | id: gomodtidy 19 | uses: ./.github/actions/gomodtidy 20 | 21 | gosec: 22 | name: Gosec scan 23 | runs-on: ubuntu-latest 24 | env: 25 | GO111MODULE: on 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - name: Set up Go 1.23 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version: 1.23.x 33 | - name: Run Gosec Security Scanner 34 | run: | # https://github.com/securego/gosec/issues/469 35 | export PATH=$PATH:$(go env GOPATH)/bin 36 | go install github.com/securego/gosec/v2/cmd/gosec@v2.21.4 37 | gosec -exclude=G307,G115 ./... 38 | 39 | lint: 40 | name: Lint 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Set up Go 1.23 47 | uses: actions/setup-go@v5 48 | with: 49 | go-version: 1.23.x 50 | 51 | - name: Golangci-lint 52 | uses: golangci/golangci-lint-action@v8 53 | with: 54 | version: latest 55 | args: --timeout=10m 56 | 57 | build: 58 | name: Build and Test 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Check out code into the Go module directory 62 | uses: actions/checkout@v4 63 | with: 64 | submodules: true 65 | 66 | - name: Setup node/yarn 67 | uses: actions/setup-node@v3 68 | with: 69 | node-version: 18 70 | cache: "yarn" 71 | cache-dependency-path: "**/contracts/yarn.lock" 72 | 73 | - name: Install 74 | run: cd contracts && yarn install 75 | 76 | - name: Build solidity contracts 77 | run: yarn --cwd contracts build 78 | 79 | - name: Set up Go 1.23 80 | uses: actions/setup-go@v5 81 | with: 82 | go-version: 1.23.x 83 | id: go 84 | 85 | - name: Install Foundry 86 | uses: foundry-rs/foundry-toolchain@v1 87 | with: 88 | version: nightly 89 | 90 | - name: Build yul contracts 91 | run: yarn --cwd contracts build:forge:yul 92 | 93 | - name: Get dependencies 94 | run: | 95 | go get -v -t -d ./... 96 | 97 | - name: AbiGen 98 | run: go run ./solgen/main.go 99 | 100 | - name: Build 101 | run: go build -v ./... 102 | 103 | - name: Test 104 | run: ANVIL=$(which anvil) go test -v -covermode=atomic -coverprofile=coverage.out -timeout=20m ./... 105 | 106 | - name: Upload coverage reports to Codecov 107 | uses: codecov/codecov-action@v4 108 | env: 109 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 110 | 111 | bazel: 112 | name: Bazel 113 | runs-on: ubuntu-latest 114 | steps: 115 | - uses: actions/checkout@v4 116 | with: 117 | submodules: true 118 | 119 | - uses: bazelbuild/setup-bazelisk@v3 120 | - name: Mount bazel cache 121 | uses: actions/cache@v3 122 | with: 123 | path: "~/.cache/bazel" 124 | key: bazel 125 | - name: build 126 | run: bazel build //... 127 | - name: test 128 | run: bazel test //... --build_manual_tests --test_output=all 129 | 130 | # Foundry is required for end to end tests 131 | - name: Install Foundry 132 | uses: foundry-rs/foundry-toolchain@v1 133 | with: 134 | version: nightly 135 | 136 | - name: coverage 137 | run: bazel coverage --combined_report=lcov //... 138 | 139 | - name: Upload coverage reports to Codecov 140 | uses: codecov/codecov-action@v4 141 | env: 142 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea 17 | 18 | # bazel stuff 19 | bazel-* 20 | 21 | node_modules/* 22 | tmp/* 23 | node_modules/ 24 | contracts/cache 25 | contracts/out 26 | contracts/formatter.sh 27 | contracts/build 28 | contracts/test/storage/*-old 29 | contracts/test/signatures/*-old 30 | contracts/coverage 31 | contracts/lcov.info 32 | contracts/forge-cache/ 33 | 34 | .DS_Store 35 | .env -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "safe-smart-account"] 2 | path = safe-smart-account 3 | url = https://github.com/safe-global/safe-smart-account.git 4 | [submodule "contracts"] 5 | path = contracts 6 | url = https://github.com/OffchainLabs/nitro-contracts.git 7 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - asciicheck 5 | - errorlint 6 | - gocritic 7 | - gosec 8 | - nilerr 9 | - misspell 10 | settings: 11 | errcheck: 12 | check-type-assertions: true 13 | gocritic: 14 | disabled-checks: 15 | - ifElseChain 16 | - assignOp 17 | - unlambda 18 | - exitAfterDefer 19 | disabled-tags: 20 | - experimental 21 | - opinionated 22 | gosec: 23 | excludes: 24 | - G404 25 | govet: 26 | disable: 27 | - shadow 28 | - fieldalignment 29 | enable-all: true 30 | exclusions: 31 | generated: lax 32 | presets: 33 | - comments 34 | - common-false-positives 35 | - legacy 36 | - std-error-handling 37 | rules: 38 | - linters: 39 | - staticcheck 40 | path: _test\.go 41 | paths: 42 | - go-ethereum 43 | - fastcache 44 | - third_party$ 45 | - builtin$ 46 | - examples$ 47 | formatters: 48 | enable: 49 | - gci 50 | - gofmt 51 | settings: 52 | gci: 53 | sections: 54 | - standard 55 | - default 56 | - prefix(github.com/ethereum/go-ethereum) 57 | - prefix(github.com/offchainlabs) 58 | exclusions: 59 | generated: lax 60 | paths: 61 | - go-ethereum 62 | - fastcache 63 | - third_party$ 64 | - builtin$ 65 | - examples$ 66 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | # gazelle:ignore 2 | load("@gazelle//:def.bzl", "gazelle") 3 | load("@rules_go//go:def.bzl", "TOOLS_NOGO", "nogo") 4 | 5 | # gazelle:prefix github.com/offchainlabs/bold 6 | gazelle(name = "gazelle") 7 | 8 | gazelle( 9 | name = "gazelle-update-repos", 10 | args = [ 11 | "-from_file=go.mod", 12 | "-to_macro=deps.bzl%go_dependencies", 13 | "-prune", 14 | ], 15 | command = "update-repos", 16 | ) 17 | 18 | nogo( 19 | name = "nogo", 20 | config = ":nogo.json", 21 | visibility = ["//visibility:public"], 22 | deps = TOOLS_NOGO, 23 | ) 24 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "bold", 3 | version = "0.0.1", 4 | ) 5 | 6 | bazel_dep(name = "rules_go", version = "0.50.1") 7 | bazel_dep(name = "gazelle", version = "0.39.1") 8 | bazel_dep(name = "protobuf", version = "28.2") 9 | 10 | go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") 11 | go_sdk.download(version = "1.23.2") 12 | go_sdk.nogo(nogo = "//:nogo") 13 | 14 | go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") 15 | go_deps.from_file(go_mod = "//:go.mod") 16 | go_deps.module_override( 17 | patch_strip = 1, 18 | patches = [ 19 | "//third_party:com_github_ethereum_go_ethereum_secp256k1.patch", 20 | ], 21 | path = "github.com/ethereum/go-ethereum", 22 | ) 23 | 24 | # TODO: Remove when https://github.com/bazelbuild/rules_go/issues/4084 is resolved 25 | go_deps.module( 26 | path = "golang.org/x/tools", 27 | # curl https://sum.golang.org/lookup/golang.org/x/tools@v0.24.0 28 | sum = "h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=", 29 | version = "v0.24.0", 30 | ) 31 | use_repo( 32 | go_deps, 33 | "com_github_ccoveille_go_safecast", 34 | "com_github_ethereum_go_ethereum", 35 | "com_github_gorilla_mux", 36 | "com_github_jmoiron_sqlx", 37 | "com_github_mattn_go_sqlite3", 38 | "com_github_pkg_errors", 39 | "com_github_stretchr_testify", 40 | "org_golang_x_sync", 41 | "org_golang_x_tools", 42 | ) 43 | -------------------------------------------------------------------------------- /api/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "api", 5 | srcs = ["types.go"], 6 | importpath = "github.com/offchainlabs/bold/api", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//chain-abstraction:protocol", 10 | "@com_github_ethereum_go_ethereum//common", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /api/backend/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "backend", 5 | srcs = ["backend.go"], 6 | importpath = "github.com/offchainlabs/bold/api/backend", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//api", 10 | "//api/db", 11 | "//chain-abstraction:protocol", 12 | "//challenge-manager/chain-watcher", 13 | "//challenge-manager/edge-tracker", 14 | "//containers/option", 15 | "@com_github_ccoveille_go_safecast//:go-safecast", 16 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 17 | "@com_github_ethereum_go_ethereum//common", 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /api/db/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "db", 5 | srcs = [ 6 | "db.go", 7 | "schema.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/api/db", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//api", 13 | "//chain-abstraction:protocol", 14 | "//containers/option", 15 | "//state-commitments/history", 16 | "@com_github_ethereum_go_ethereum//common", 17 | "@com_github_jmoiron_sqlx//:sqlx", 18 | "@com_github_mattn_go_sqlite3//:go-sqlite3", 19 | ], 20 | ) 21 | 22 | go_test( 23 | name = "db_test", 24 | size = "small", 25 | srcs = ["db_test.go"], 26 | embed = [":db"], 27 | deps = [ 28 | "//api", 29 | "//chain-abstraction:protocol", 30 | "//state-commitments/history", 31 | "//testing/casttest", 32 | "@com_github_ethereum_go_ethereum//common", 33 | "@com_github_jmoiron_sqlx//:sqlx", 34 | "@com_github_mattn_go_sqlite3//:go-sqlite3", 35 | "@com_github_stretchr_testify//require", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /api/server/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "server", 5 | srcs = [ 6 | "methods.go", 7 | "server.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/api/server", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//api", 13 | "//api/backend", 14 | "//api/db", 15 | "//chain-abstraction:protocol", 16 | "//state-commitments/history", 17 | "//util/stopwaiter", 18 | "@com_github_ethereum_go_ethereum//common", 19 | "@com_github_ethereum_go_ethereum//common/hexutil", 20 | "@com_github_ethereum_go_ethereum//log", 21 | "@com_github_gorilla_mux//:mux", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /api/server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package server defines the client-facing API methods for fetching data 6 | // related to BOLD challenges. It handles HTTP methods with their requests and responses. 7 | package server 8 | 9 | import ( 10 | "context" 11 | "errors" 12 | "net/http" 13 | "time" 14 | 15 | "github.com/gorilla/mux" 16 | 17 | "github.com/ethereum/go-ethereum/log" 18 | 19 | "github.com/offchainlabs/bold/api/backend" 20 | "github.com/offchainlabs/bold/util/stopwaiter" 21 | ) 22 | 23 | var apiVersion = "/api/v1" 24 | 25 | type Server struct { 26 | stopwaiter.StopWaiter 27 | srv *http.Server 28 | router *mux.Router 29 | registered bool 30 | backend backend.BusinessLogicProvider 31 | } 32 | 33 | func New(addr string, backend backend.BusinessLogicProvider) (*Server, error) { 34 | if addr == "" { 35 | addr = ":8080" 36 | } 37 | r := mux.NewRouter() 38 | 39 | s := &Server{ 40 | backend: backend, 41 | srv: &http.Server{ 42 | Handler: r, 43 | Addr: addr, 44 | WriteTimeout: 15 * time.Second, 45 | ReadTimeout: 30 * time.Second, 46 | ReadHeaderTimeout: 30 * time.Second, 47 | }, 48 | router: r, 49 | } 50 | if err := s.registerMethods(); err != nil { 51 | return nil, err 52 | } 53 | return s, nil 54 | } 55 | 56 | func (s *Server) Start(ctx context.Context) error { 57 | s.StopWaiter.Start(ctx, s) 58 | go func() { 59 | <-ctx.Done() 60 | if err := s.srv.Shutdown(ctx); err != nil { 61 | log.Error("Could not shutdown API server", "err", err) 62 | } 63 | }() 64 | return s.srv.ListenAndServe() 65 | } 66 | 67 | func (s *Server) Addr() string { 68 | return s.srv.Addr 69 | } 70 | 71 | func (s *Server) registerMethods() error { 72 | if s.registered { 73 | return errors.New("API server methods already registered") 74 | } 75 | 76 | r := s.router.PathPrefix(apiVersion).Subrouter() 77 | r.HandleFunc("/healthz", s.Healthz).Methods("GET") 78 | r.HandleFunc("/assertions", s.ListAssertions).Methods("GET") 79 | r.HandleFunc("/assertions/{identifier}", s.AssertionByIdentifier).Methods("GET") 80 | r.HandleFunc("/challenge/{assertion-hash}/edges", s.AllChallengeEdges).Methods("GET") 81 | r.HandleFunc("/challenge/{assertion-hash}/edges/id/{edge-id}", s.EdgeByIdentifier).Methods("GET") 82 | r.HandleFunc("/challenge/{assertion-hash}/edges/history/{history-commitment}", s.EdgeByHistoryCommitment).Methods("GET") 83 | r.HandleFunc("/challenge/{assertion-hash}/ministakes", s.MiniStakes).Methods("GET") 84 | r.HandleFunc("/tracked/royal-edges", s.RoyalTrackedChallengeEdges).Methods("GET") 85 | r.HandleFunc("/state-provider/requests/collect-machine-hashes", s.CollectMachineHashes).Methods("GET") 86 | s.registered = true 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /assertions/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "assertions", 5 | srcs = [ 6 | "confirmation.go", 7 | "manager.go", 8 | "poster.go", 9 | "sync.go", 10 | ], 11 | importpath = "github.com/offchainlabs/bold/assertions", 12 | visibility = ["//visibility:public"], 13 | deps = [ 14 | "//api", 15 | "//api/db", 16 | "//chain-abstraction:protocol", 17 | "//chain-abstraction/sol-implementation", 18 | "//challenge-manager/types", 19 | "//containers", 20 | "//containers/option", 21 | "//containers/threadsafe", 22 | "//layer2-state-provider", 23 | "//logs/ephemeral", 24 | "//runtime", 25 | "//solgen/go/rollupgen", 26 | "//util/stopwaiter", 27 | "@com_github_ccoveille_go_safecast//:go-safecast", 28 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 29 | "@com_github_ethereum_go_ethereum//common", 30 | "@com_github_ethereum_go_ethereum//log", 31 | "@com_github_ethereum_go_ethereum//metrics", 32 | "@com_github_ethereum_go_ethereum//rpc", 33 | "@com_github_pkg_errors//:errors", 34 | ], 35 | ) 36 | 37 | go_test( 38 | name = "assertions_test", 39 | size = "small", 40 | srcs = [ 41 | "manager_test.go", 42 | "poster_test.go", 43 | "sync_test.go", 44 | ], 45 | embed = [":assertions"], 46 | deps = [ 47 | "//chain-abstraction:protocol", 48 | "//challenge-manager", 49 | "//challenge-manager/types", 50 | "//containers/threadsafe", 51 | "//runtime", 52 | "//solgen/go/bridgegen", 53 | "//solgen/go/mocksgen", 54 | "//solgen/go/rollupgen", 55 | "//testing", 56 | "//testing/casttest", 57 | "//testing/mocks/state-provider", 58 | "//testing/setup:setup_lib", 59 | "@com_github_ethereum_go_ethereum//accounts/abi", 60 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 61 | "@com_github_ethereum_go_ethereum//common", 62 | "@com_github_stretchr_testify//require", 63 | ], 64 | ) 65 | -------------------------------------------------------------------------------- /assertions/poster_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package assertions_test 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 15 | 16 | "github.com/offchainlabs/bold/assertions" 17 | protocol "github.com/offchainlabs/bold/chain-abstraction" 18 | cm "github.com/offchainlabs/bold/challenge-manager" 19 | "github.com/offchainlabs/bold/challenge-manager/types" 20 | "github.com/offchainlabs/bold/solgen/go/mocksgen" 21 | challenge_testing "github.com/offchainlabs/bold/testing" 22 | statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" 23 | "github.com/offchainlabs/bold/testing/setup" 24 | ) 25 | 26 | func TestPostAssertion(t *testing.T) { 27 | ctx := context.Background() 28 | setup, err := setup.ChainsWithEdgeChallengeManager( 29 | // setup.WithMockBridge(), 30 | setup.WithMockOneStepProver(), 31 | setup.WithChallengeTestingOpts( 32 | challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ 33 | BlockChallengeHeight: 64, 34 | BigStepChallengeHeight: 32, 35 | SmallStepChallengeHeight: 32, 36 | }), 37 | ), 38 | ) 39 | require.NoError(t, err) 40 | 41 | bridgeBindings, err := mocksgen.NewBridgeStub(setup.Addrs.Bridge, setup.Backend) 42 | require.NoError(t, err) 43 | 44 | msgCount, err := bridgeBindings.SequencerMessageCount(setup.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) 45 | require.NoError(t, err) 46 | require.Equal(t, uint64(1), msgCount.Uint64()) 47 | 48 | aliceChain := setup.Chains[0] 49 | 50 | stateManagerOpts := setup.StateManagerOpts 51 | stateManagerOpts = append( 52 | stateManagerOpts, 53 | statemanager.WithNumBatchesRead(5), 54 | ) 55 | stateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) 56 | require.NoError(t, err) 57 | 58 | // Set MinimumGapToBlockCreationTime as 1 second to verify that a new assertion is only posted after 1 sec has passed 59 | // from parent assertion creation. This will make the test run for ~19 seconds as the parent assertion time is 60 | // ~18 seconds in the future 61 | assertionManager, err := assertions.NewManager( 62 | aliceChain, 63 | stateManager, 64 | "alice", 65 | types.DefensiveMode, 66 | assertions.WithPollingInterval(time.Millisecond*200), 67 | assertions.WithAverageBlockCreationTime(time.Second), 68 | assertions.WithMinimumGapToParentAssertion(time.Second), 69 | ) 70 | require.NoError(t, err) 71 | 72 | chalManager, err := cm.NewChallengeStack( 73 | aliceChain, 74 | stateManager, 75 | cm.StackWithMode(types.DefensiveMode), 76 | cm.StackWithName("alice"), 77 | cm.OverrideAssertionManager(assertionManager), 78 | ) 79 | require.NoError(t, err) 80 | chalManager.Start(ctx) 81 | 82 | preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) 83 | require.NoError(t, err) 84 | postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState) 85 | require.NoError(t, err) 86 | 87 | time.Sleep(time.Second) 88 | 89 | posted, err := assertionManager.PostAssertion(ctx) 90 | require.NoError(t, err) 91 | require.Equal(t, true, posted.IsSome()) 92 | creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id()) 93 | require.NoError(t, err) 94 | require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState)) 95 | } 96 | -------------------------------------------------------------------------------- /chain-abstraction/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "protocol", 5 | srcs = [ 6 | "execution_state.go", 7 | "interfaces.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/chain-abstraction", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//containers/option", 13 | "//solgen/go/challengegen", 14 | "//solgen/go/rollupgen", 15 | "//state-commitments/history", 16 | "@com_github_ccoveille_go_safecast//:go-safecast", 17 | "@com_github_ethereum_go_ethereum//:go-ethereum", 18 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 19 | "@com_github_ethereum_go_ethereum//common", 20 | "@com_github_ethereum_go_ethereum//core/types", 21 | "@com_github_ethereum_go_ethereum//crypto", 22 | "@com_github_ethereum_go_ethereum//rpc", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /chain-abstraction/execution_state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package protocol a series of interfaces for interacting with Arbitrum chains' rollup 6 | // and challenge contracts via a developer-friendly, high-level API. 7 | package protocol 8 | 9 | import ( 10 | "encoding/binary" 11 | "math" 12 | 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/crypto" 15 | 16 | "github.com/offchainlabs/bold/solgen/go/challengegen" 17 | "github.com/offchainlabs/bold/solgen/go/rollupgen" 18 | ) 19 | 20 | type GoGlobalState struct { 21 | BlockHash common.Hash `json:"blockHash"` 22 | SendRoot common.Hash `json:"sendRoot"` 23 | Batch uint64 `json:"batch"` 24 | PosInBatch uint64 `json:"positionInBatch"` 25 | } 26 | 27 | func GoGlobalStateFromSolidity(globalState rollupgen.GlobalState) GoGlobalState { 28 | return GoGlobalState{ 29 | BlockHash: globalState.Bytes32Vals[0], 30 | SendRoot: globalState.Bytes32Vals[1], 31 | Batch: globalState.U64Vals[0], 32 | PosInBatch: globalState.U64Vals[1], 33 | } 34 | } 35 | 36 | func u64ToBe(x uint64) []byte { 37 | data := make([]byte, 8) 38 | binary.BigEndian.PutUint64(data, x) 39 | return data 40 | } 41 | 42 | func ComputeSimpleMachineChallengeHash( 43 | execState *ExecutionState, 44 | ) common.Hash { 45 | return execState.GlobalState.Hash() 46 | } 47 | 48 | func (s GoGlobalState) Hash() common.Hash { 49 | data := []byte("Global state:") 50 | data = append(data, s.BlockHash.Bytes()...) 51 | data = append(data, s.SendRoot.Bytes()...) 52 | data = append(data, u64ToBe(s.Batch)...) 53 | data = append(data, u64ToBe(s.PosInBatch)...) 54 | return crypto.Keccak256Hash(data) 55 | } 56 | 57 | func (s GoGlobalState) AsSolidityStruct() challengegen.GlobalState { 58 | return challengegen.GlobalState{ 59 | Bytes32Vals: [2][32]byte{s.BlockHash, s.SendRoot}, 60 | U64Vals: [2]uint64{s.Batch, s.PosInBatch}, 61 | } 62 | } 63 | 64 | func (s GoGlobalState) Equals(other GoGlobalState) bool { 65 | // This is correct because we don't have any pointers or slices 66 | return s == other 67 | } 68 | 69 | type MachineStatus uint8 70 | 71 | const ( 72 | MachineStatusRunning MachineStatus = 0 73 | MachineStatusFinished MachineStatus = 1 74 | MachineStatusErrored MachineStatus = 2 75 | ) 76 | 77 | type ExecutionState struct { 78 | GlobalState GoGlobalState 79 | MachineStatus MachineStatus 80 | EndHistoryRoot common.Hash 81 | } 82 | 83 | func GoExecutionStateFromSolidity(executionState rollupgen.AssertionState) *ExecutionState { 84 | return &ExecutionState{ 85 | GlobalState: GoGlobalStateFromSolidity(executionState.GlobalState), 86 | MachineStatus: MachineStatus(executionState.MachineStatus), 87 | EndHistoryRoot: executionState.EndHistoryRoot, 88 | } 89 | } 90 | 91 | func (s *ExecutionState) AsSolidityStruct() rollupgen.AssertionState { 92 | return rollupgen.AssertionState{ 93 | GlobalState: rollupgen.GlobalState(s.GlobalState.AsSolidityStruct()), 94 | MachineStatus: uint8(s.MachineStatus), 95 | EndHistoryRoot: s.EndHistoryRoot, 96 | } 97 | } 98 | 99 | func (s *ExecutionState) Equals(other *ExecutionState) bool { 100 | return s.MachineStatus == other.MachineStatus && s.GlobalState.Equals(other.GlobalState) && s.EndHistoryRoot == other.EndHistoryRoot 101 | } 102 | 103 | // RequiredBatches determines the batch count required to reach the execution state. 104 | // If the machine errored or the state is after the beginning of the batch, 105 | // the current batch is required to reach the state. 106 | // That's because if the machine errored, it might've read the current batch before erroring, 107 | // and if it's in the middle of a batch, it had to read prior parts of the batch to get there. 108 | // However, if the machine finished successfully and the new state is the start of the batch, 109 | // it hasn't read the batch yet, as it just finished the last batch. 110 | // 111 | // This logic is replicated in Solidity in a few places; search for RequiredBatches to find them. 112 | func (s *ExecutionState) RequiredBatches() uint64 { 113 | count := s.GlobalState.Batch 114 | if (s.MachineStatus == MachineStatusErrored || s.GlobalState.PosInBatch > 0) && count < math.MaxUint64 { 115 | // The current batch was read 116 | count++ 117 | } 118 | return count 119 | } 120 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "sol-implementation", 5 | srcs = [ 6 | "assertion_chain.go", 7 | "edge_challenge_manager.go", 8 | "fast_confirm.go", 9 | "fifo_lock.go", 10 | "metrics_contract_backend.go", 11 | "tracked_contract_backend.go", 12 | "transact.go", 13 | "types.go", 14 | ], 15 | importpath = "github.com/offchainlabs/bold/chain-abstraction/sol-implementation", 16 | visibility = ["//visibility:public"], 17 | deps = [ 18 | "//chain-abstraction:protocol", 19 | "//challenge-manager/challenge-tree", 20 | "//challenge-manager/edge-tracker", 21 | "//containers", 22 | "//containers/option", 23 | "//containers/threadsafe", 24 | "//runtime", 25 | "//solgen/go/bridgegen", 26 | "//solgen/go/challengeV2gen", 27 | "//solgen/go/contractsgen", 28 | "//solgen/go/mocksgen", 29 | "//solgen/go/ospgen", 30 | "//solgen/go/rollupgen", 31 | "//solgen/go/testgen", 32 | "//state-commitments/history", 33 | "@com_github_ccoveille_go_safecast//:go-safecast", 34 | "@com_github_ethereum_go_ethereum//:go-ethereum", 35 | "@com_github_ethereum_go_ethereum//accounts/abi", 36 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 37 | "@com_github_ethereum_go_ethereum//common", 38 | "@com_github_ethereum_go_ethereum//core/types", 39 | "@com_github_ethereum_go_ethereum//crypto", 40 | "@com_github_ethereum_go_ethereum//log", 41 | "@com_github_ethereum_go_ethereum//metrics", 42 | "@com_github_ethereum_go_ethereum//rpc", 43 | "@com_github_pkg_errors//:errors", 44 | ], 45 | ) 46 | 47 | go_test( 48 | name = "sol-implementation_test", 49 | size = "small", 50 | srcs = [ 51 | "assertion_chain_helper_test.go", 52 | "assertion_chain_test.go", 53 | "edge_challenge_manager_test.go", 54 | "fifo_lock_test.go", 55 | "tracked_contract_backend_test.go", 56 | "types_test.go", 57 | ], 58 | embed = [":sol-implementation"], 59 | deps = [ 60 | "//chain-abstraction:protocol", 61 | "//containers/option", 62 | "//layer2-state-provider", 63 | "//solgen/go/bridgegen", 64 | "//solgen/go/mocksgen", 65 | "//solgen/go/rollupgen", 66 | "//state-commitments/history", 67 | "//testing", 68 | "//testing/mocks/state-provider", 69 | "//testing/setup:setup_lib", 70 | "@com_github_ethereum_go_ethereum//:go-ethereum", 71 | "@com_github_ethereum_go_ethereum//accounts/abi", 72 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 73 | "@com_github_ethereum_go_ethereum//common", 74 | "@com_github_ethereum_go_ethereum//core/types", 75 | "@com_github_ethereum_go_ethereum//rpc", 76 | "@com_github_stretchr_testify//require", 77 | ], 78 | ) 79 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/assertion_chain_helper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import protocol "github.com/offchainlabs/bold/chain-abstraction" 8 | 9 | func (a *AssertionChain) SetBackend(b protocol.ChainBackend) { 10 | a.backend = b 11 | } 12 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/fifo_lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import ( 8 | "sync" 9 | ) 10 | 11 | type FIFO struct { 12 | lock sync.Mutex 13 | queue chan struct{} 14 | waitQueue chan chan struct{} 15 | } 16 | 17 | func NewFIFO(capacity int) *FIFO { 18 | return &FIFO{ 19 | queue: make(chan struct{}, 1), 20 | waitQueue: make(chan chan struct{}, capacity), 21 | } 22 | } 23 | 24 | func (f *FIFO) Lock() bool { 25 | waitCh := make(chan struct{}) 26 | f.lock.Lock() 27 | select { 28 | // If the queue is empty, we can lock immediately 29 | case f.queue <- struct{}{}: 30 | f.lock.Unlock() 31 | // If the queue is not empty, we need to wait our turn 32 | default: 33 | // We add our wait channel to the wait queue 34 | if len(f.waitQueue) == cap(f.waitQueue) { 35 | f.lock.Unlock() 36 | // If the wait queue is full, lock acquisition fails 37 | return false 38 | } 39 | f.waitQueue <- waitCh 40 | f.lock.Unlock() 41 | // We wait for our turn 42 | <-waitCh 43 | } 44 | // Lock acquisition succeeded 45 | return true 46 | } 47 | 48 | func (f *FIFO) Unlock() { 49 | f.lock.Lock() 50 | defer f.lock.Unlock() 51 | select { 52 | // If the queue is not empty, we unlock and signal the next waiter 53 | case <-f.queue: 54 | // If there are waiters, we signal the next one 55 | if len(f.waitQueue) > 0 { 56 | // We pop the next waiter from the queue 57 | nextWaitCh := <-f.waitQueue 58 | // We acquire the lock for the next waiter 59 | f.queue <- struct{}{} 60 | // We signal the next waiter 61 | close(nextWaitCh) 62 | } 63 | default: 64 | panic("attempt to unlock unlocked mutex") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/fifo_lock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import ( 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestFIFOSequentialLocking(t *testing.T) { 16 | f := NewFIFO(10) 17 | var output []int 18 | var wg sync.WaitGroup 19 | wg.Add(10) 20 | for i := 0; i < 10; i++ { 21 | a := i 22 | go func() { 23 | f.Lock() 24 | output = append(output, a) 25 | wg.Done() 26 | }() 27 | time.Sleep(time.Millisecond) 28 | } 29 | for i := 0; i < 10; i++ { 30 | f.Unlock() 31 | time.Sleep(time.Millisecond) 32 | } 33 | wg.Wait() 34 | require.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, output) 35 | } 36 | 37 | func TestFIFOUnlockPanic(t *testing.T) { 38 | defer func() { 39 | if r := recover(); r == nil { 40 | t.Error("Unlock did not panic") 41 | } 42 | }() 43 | 44 | fifo := NewFIFO(1) 45 | 46 | // Try to unlock when the FIFO is already unlocked 47 | fifo.Unlock() 48 | } 49 | 50 | func TestFIFOOnlyOneLockAllowed(t *testing.T) { 51 | fifo := NewFIFO(1) 52 | 53 | // Acquire the lock 54 | fifo.Lock() 55 | 56 | // Try to acquire the lock from another goroutine 57 | doneCh := make(chan struct{}) 58 | go func() { 59 | defer close(doneCh) 60 | fifo.Lock() 61 | defer fifo.Unlock() 62 | }() 63 | 64 | // Wait for some time to ensure that the second goroutine doesn't acquire the lock 65 | select { 66 | case <-doneCh: 67 | t.Error("Second lock acquisition didn't fail within the expected time") 68 | case <-time.After(time.Millisecond * 100): 69 | t.Log("As expected, was not able to acquire the second lock") 70 | } 71 | 72 | // Release the lock 73 | fifo.Unlock() 74 | } 75 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/metrics_contract_backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "math/big" 11 | 12 | "github.com/ethereum/go-ethereum" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/core/types" 15 | "github.com/ethereum/go-ethereum/metrics" 16 | 17 | protocol "github.com/offchainlabs/bold/chain-abstraction" 18 | ) 19 | 20 | var _ protocol.ChainBackend = &MetricsContractBackend{} 21 | 22 | type MetricsContractBackend struct { 23 | protocol.ChainBackend 24 | } 25 | 26 | func NewMetricsContractBackend(backend protocol.ChainBackend) *MetricsContractBackend { 27 | return &MetricsContractBackend{ 28 | ChainBackend: backend, 29 | } 30 | } 31 | 32 | func (t *MetricsContractBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 33 | data := call.Data 34 | if len(data) >= 4 { // if there's a method selector 35 | methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector 36 | metrics.GetOrRegisterCounter("arb/backend/call_contract/"+methodHash+"/count", nil).Inc(1) 37 | } 38 | return t.ChainBackend.CallContract(ctx, call, blockNumber) 39 | } 40 | 41 | func (t *MetricsContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { 42 | metrics.GetOrRegisterCounter("arb/backend/code_at/count", nil).Inc(1) 43 | return t.ChainBackend.CodeAt(ctx, contract, blockNumber) 44 | } 45 | 46 | func (t *MetricsContractBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { 47 | metrics.GetOrRegisterCounter("arb/backend/header_by_number/count", nil).Inc(1) 48 | return t.ChainBackend.HeaderByNumber(ctx, number) 49 | } 50 | 51 | func (t *MetricsContractBackend) HeaderU64(ctx context.Context) (uint64, error) { 52 | metrics.GetOrRegisterCounter("arb/backend/header_by_number/count", nil).Inc(1) 53 | return t.ChainBackend.HeaderU64(ctx) 54 | } 55 | 56 | func (t *MetricsContractBackend) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { 57 | metrics.GetOrRegisterCounter("arb/backend/pending_code_at/count", nil).Inc(1) 58 | return t.ChainBackend.PendingCodeAt(ctx, account) 59 | } 60 | 61 | func (t *MetricsContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 62 | metrics.GetOrRegisterCounter("arb/backend/pending_code_at/count", nil).Inc(1) 63 | return t.ChainBackend.PendingNonceAt(ctx, account) 64 | } 65 | 66 | func (t *MetricsContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 67 | metrics.GetOrRegisterCounter("arb/backend/suggest_gas_price/count", nil).Inc(1) 68 | return t.ChainBackend.SuggestGasPrice(ctx) 69 | } 70 | 71 | func (t *MetricsContractBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { 72 | metrics.GetOrRegisterCounter("arb/backend/suggest_gas_tip_cap/count", nil).Inc(1) 73 | return t.ChainBackend.SuggestGasTipCap(ctx) 74 | } 75 | 76 | func (t *MetricsContractBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { 77 | data := call.Data 78 | if len(data) >= 4 { // if there's a method selector 79 | methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector 80 | metrics.GetOrRegisterCounter("arb/backend/estimate_gas/"+methodHash+"/count", nil).Inc(1) 81 | } 82 | return t.ChainBackend.EstimateGas(ctx, call) 83 | } 84 | 85 | func (t *MetricsContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { 86 | if tx != nil && len(tx.Data()) >= 4 { 87 | methodHash := fmt.Sprintf("%#x", tx.Data()[:4]) 88 | metrics.GetOrRegisterCounter("arb/backend/send_transaction/"+methodHash+"/count", nil).Inc(1) 89 | } 90 | return t.ChainBackend.SendTransaction(ctx, tx) 91 | } 92 | 93 | func (t *MetricsContractBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { 94 | metrics.GetOrRegisterCounter("arb/backend/filter_logs/count", nil).Inc(1) 95 | return t.ChainBackend.FilterLogs(ctx, query) 96 | } 97 | 98 | func (t *MetricsContractBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { 99 | metrics.GetOrRegisterCounter("arb/backend/subscribe_filter_logs/count", nil).Inc(1) 100 | return t.ChainBackend.SubscribeFilterLogs(ctx, query, ch) 101 | } 102 | 103 | func (t *MetricsContractBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { 104 | metrics.GetOrRegisterCounter("arb/backend/transaction_receipt/count", nil).Inc(1) 105 | return t.ChainBackend.TransactionReceipt(ctx, txHash) 106 | } 107 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/tracked_contract_backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "math/big" 11 | "sort" 12 | "sync" 13 | 14 | "github.com/ethereum/go-ethereum" 15 | "github.com/ethereum/go-ethereum/core/types" 16 | 17 | protocol "github.com/offchainlabs/bold/chain-abstraction" 18 | ) 19 | 20 | // TrackedContractBackend implements a wrapper around a chain backend interface 21 | // which can keep track of the number of calls and transactions made per 22 | // method to a contract along with gas costs for transactions. These can then be 23 | // printed out to a destination for analysis. 24 | type TrackedContractBackend struct { 25 | protocol.ChainBackend 26 | metrics map[string]*MethodMetrics // method hash -> metrics 27 | mu sync.RWMutex 28 | } 29 | 30 | type MethodMetrics struct { 31 | Calls int // Total number of calls to the method. 32 | Txs int // Total number of transactions to the method. 33 | GasCosts []big.Int // Gas costs for each tx. 34 | } 35 | 36 | func NewTrackedContractBackend(backend protocol.ChainBackend) *TrackedContractBackend { 37 | return &TrackedContractBackend{ 38 | ChainBackend: backend, 39 | metrics: make(map[string]*MethodMetrics), 40 | } 41 | } 42 | 43 | func (t *TrackedContractBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 44 | data := call.Data 45 | if len(data) >= 4 { // if there's a method selector 46 | methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector 47 | t.mu.Lock() 48 | metric, ok := t.metrics[methodHash] 49 | if !ok { 50 | metric = &MethodMetrics{} 51 | t.metrics[methodHash] = metric 52 | } 53 | metric.Calls++ 54 | // Assuming gas cost for call can be added here if needed 55 | t.mu.Unlock() 56 | } 57 | return t.ChainBackend.CallContract(ctx, call, blockNumber) 58 | } 59 | 60 | func (t *TrackedContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { 61 | if tx != nil && len(tx.Data()) >= 4 { 62 | methodHash := fmt.Sprintf("%#x", tx.Data()[:4]) 63 | t.mu.Lock() 64 | metric, ok := t.metrics[methodHash] 65 | if !ok { 66 | metric = &MethodMetrics{} 67 | t.metrics[methodHash] = metric 68 | } 69 | metric.Txs++ 70 | gasCost := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) 71 | metric.GasCosts = append(metric.GasCosts, *gasCost) 72 | t.mu.Unlock() 73 | } 74 | return t.ChainBackend.SendTransaction(ctx, tx) 75 | } 76 | 77 | // Computes a median of big integers (gas costs) 78 | func median(gasCosts []big.Int) *big.Int { 79 | if len(gasCosts) == 0 { 80 | return nil 81 | } 82 | sorted := make([]big.Int, len(gasCosts)) 83 | copy(sorted, gasCosts) 84 | sort.Slice(sorted, func(i, j int) bool { 85 | return sorted[i].Cmp(&sorted[j]) < 0 86 | }) 87 | mid := len(sorted) / 2 88 | if len(sorted)%2 == 0 { 89 | return new(big.Int).Add(&sorted[mid-1], &sorted[mid]).Div(&sorted[mid], big.NewInt(2)) 90 | } 91 | return &sorted[mid] 92 | } 93 | 94 | func (t *TrackedContractBackend) PrintMetrics() { 95 | t.mu.RLock() 96 | defer t.mu.RUnlock() 97 | for methodHash, metrics := range t.metrics { 98 | fmt.Printf("Method: %s\n", methodHash) 99 | fmt.Printf("Calls: %d\n", metrics.Calls) 100 | fmt.Printf("Transactions: %d\n", metrics.Txs) 101 | if med := median(metrics.GasCosts); med != nil { 102 | fmt.Printf("Median Gas Cost: %s\n", med.String()) 103 | } else { 104 | fmt.Println("Median Gas Cost: N/A") 105 | } 106 | fmt.Println("-----------") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /chain-abstraction/sol-implementation/types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package solimpl 6 | 7 | import protocol "github.com/offchainlabs/bold/chain-abstraction" 8 | 9 | var ( 10 | _ = protocol.SpecEdge(&specEdge{}) 11 | _ = protocol.SpecChallengeManager(&specChallengeManager{}) 12 | ) 13 | -------------------------------------------------------------------------------- /challenge-manager/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "challenge-manager", 5 | srcs = [ 6 | "challenges.go", 7 | "manager.go", 8 | "stack.go", 9 | ], 10 | importpath = "github.com/offchainlabs/bold/challenge-manager", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//api/backend", 14 | "//api/db", 15 | "//api/server", 16 | "//assertions", 17 | "//chain-abstraction:protocol", 18 | "//challenge-manager/chain-watcher", 19 | "//challenge-manager/edge-tracker", 20 | "//challenge-manager/types", 21 | "//containers", 22 | "//containers/events", 23 | "//containers/option", 24 | "//containers/threadsafe", 25 | "//layer2-state-provider", 26 | "//runtime", 27 | "//time", 28 | "//util/stopwaiter", 29 | "@com_github_ccoveille_go_safecast//:go-safecast", 30 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 31 | "@com_github_ethereum_go_ethereum//common", 32 | "@com_github_ethereum_go_ethereum//core/types", 33 | "@com_github_ethereum_go_ethereum//log", 34 | "@com_github_ethereum_go_ethereum//metrics", 35 | "@com_github_pkg_errors//:errors", 36 | ], 37 | ) 38 | 39 | go_test( 40 | name = "challenge-manager_test", 41 | size = "small", 42 | srcs = ["manager_test.go"], 43 | embed = [":challenge-manager"], 44 | deps = [ 45 | "//chain-abstraction:protocol", 46 | "//challenge-manager/chain-watcher", 47 | "//challenge-manager/edge-tracker", 48 | "//challenge-manager/types", 49 | "//containers/option", 50 | "//layer2-state-provider", 51 | "//solgen/go/rollupgen", 52 | "//testing/mocks", 53 | "//testing/setup:setup_lib", 54 | "//time", 55 | "@com_github_ethereum_go_ethereum//common", 56 | "@com_github_stretchr_testify//require", 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /challenge-manager/chain-watcher/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "chain-watcher", 5 | srcs = ["watcher.go"], 6 | importpath = "github.com/offchainlabs/bold/challenge-manager/chain-watcher", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//api", 10 | "//api/db", 11 | "//chain-abstraction:protocol", 12 | "//chain-abstraction/sol-implementation", 13 | "//challenge-manager/challenge-tree", 14 | "//containers/option", 15 | "//containers/threadsafe", 16 | "//layer2-state-provider", 17 | "//logs/ephemeral", 18 | "//runtime", 19 | "//solgen/go/challengeV2gen", 20 | "//util/stopwaiter", 21 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 22 | "@com_github_ethereum_go_ethereum//common", 23 | "@com_github_ethereum_go_ethereum//log", 24 | "@com_github_ethereum_go_ethereum//metrics", 25 | "@com_github_pkg_errors//:errors", 26 | ], 27 | ) 28 | 29 | go_test( 30 | name = "chain-watcher_test", 31 | size = "small", 32 | srcs = ["watcher_test.go"], 33 | embed = [":chain-watcher"], 34 | deps = [ 35 | "//chain-abstraction:protocol", 36 | "//containers/option", 37 | "//containers/threadsafe", 38 | "//layer2-state-provider", 39 | "//solgen/go/challengeV2gen", 40 | "//testing/mocks", 41 | "@com_github_ethereum_go_ethereum//common", 42 | "@com_github_ethereum_go_ethereum//core/types", 43 | "@com_github_stretchr_testify//require", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /challenge-manager/challenge-tree/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "challenge-tree", 5 | srcs = [ 6 | "add_edge.go", 7 | "ancestors.go", 8 | "local_timer.go", 9 | "paths.go", 10 | "tree.go", 11 | ], 12 | importpath = "github.com/offchainlabs/bold/challenge-manager/challenge-tree", 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | "//chain-abstraction:protocol", 16 | "//containers", 17 | "//containers/option", 18 | "//containers/threadsafe", 19 | "//layer2-state-provider", 20 | "//math", 21 | "@com_github_ethereum_go_ethereum//common", 22 | "@com_github_pkg_errors//:errors", 23 | ], 24 | ) 25 | 26 | go_test( 27 | name = "challenge-tree_test", 28 | size = "small", 29 | srcs = [ 30 | "ancestors_test.go", 31 | "compute_ancestors_test.go", 32 | "local_timer_test.go", 33 | "paths_edge_cases_test.go", 34 | "paths_test.go", 35 | "tree_test.go", 36 | ], 37 | embed = [":challenge-tree"], 38 | deps = [ 39 | "//chain-abstraction:protocol", 40 | "//challenge-manager/challenge-tree/mock", 41 | "//containers/option", 42 | "//containers/threadsafe", 43 | "//layer2-state-provider", 44 | "//testing/mocks", 45 | "@com_github_ethereum_go_ethereum//common", 46 | "@com_github_stretchr_testify//require", 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /challenge-manager/challenge-tree/local_timer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package challengetree 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "math" 11 | 12 | protocol "github.com/offchainlabs/bold/chain-abstraction" 13 | "github.com/offchainlabs/bold/containers/option" 14 | "github.com/offchainlabs/bold/containers/threadsafe" 15 | ) 16 | 17 | // Gets the local timer of an edge at a block number, T. If T is earlier than the edge's creation, 18 | // this function will return 0. 19 | func (ht *RoyalChallengeTree) LocalTimer(ctx context.Context, e protocol.ReadOnlyEdge, blockNum uint64) (uint64, error) { 20 | createdAtBlock, err := e.CreatedAtBlock() 21 | if err != nil { 22 | return 0, err 23 | } 24 | if blockNum <= createdAtBlock { 25 | return 0, nil 26 | } 27 | status, err := e.Status(ctx) 28 | if err != nil { 29 | return 0, err 30 | } 31 | if status == protocol.EdgeConfirmed { 32 | return math.MaxUint64, nil 33 | } 34 | // If no rival at a block num, then the local timer is defined 35 | // as t - t_creation(e). 36 | unrivaled, err := ht.UnrivaledAtBlockNum(e, blockNum) 37 | if err != nil { 38 | return 0, err 39 | } 40 | if unrivaled { 41 | return blockNum - createdAtBlock, nil 42 | } 43 | // Else we return the earliest created rival's block number: t_rival - t_creation(e). 44 | // This unwrap is safe because the edge has rivals at this point due to the check above. 45 | earliest := ht.EarliestCreatedRivalBlockNumber(e) 46 | tRival := earliest.Unwrap() 47 | if createdAtBlock >= tRival { 48 | return 0, nil 49 | } 50 | return tRival - createdAtBlock, nil 51 | } 52 | 53 | // Gets the minimum creation block number across all of an edge's rivals. If an edge 54 | // has no rivals, this minimum is undefined. 55 | func (ht *RoyalChallengeTree) EarliestCreatedRivalBlockNumber(e protocol.ReadOnlyEdge) option.Option[uint64] { 56 | rivals := ht.rivalsWithCreationTimes(e) 57 | creationBlocks := make([]uint64, len(rivals)) 58 | earliestCreatedRivalBlock := option.None[uint64]() 59 | for i, r := range rivals { 60 | creationBlocks[i] = uint64(r.createdAtBlock) 61 | if earliestCreatedRivalBlock.IsNone() { 62 | earliestCreatedRivalBlock = option.Some(uint64(r.createdAtBlock)) 63 | } else if uint64(r.createdAtBlock) < earliestCreatedRivalBlock.Unwrap() { 64 | earliestCreatedRivalBlock = option.Some(uint64(r.createdAtBlock)) 65 | } 66 | } 67 | return earliestCreatedRivalBlock 68 | } 69 | 70 | // Determines if an edge was unrivaled at a block num T. If any rival existed 71 | // for the edge at T, this function will return false. 72 | func (ht *RoyalChallengeTree) UnrivaledAtBlockNum(e protocol.ReadOnlyEdge, blockNum uint64) (bool, error) { 73 | createdAtBlock, err := e.CreatedAtBlock() 74 | if err != nil { 75 | return false, err 76 | } 77 | if blockNum < createdAtBlock { 78 | return false, fmt.Errorf( 79 | "edge creation block %d less than specified %d", 80 | createdAtBlock, 81 | blockNum, 82 | ) 83 | } 84 | rivals := ht.rivalsWithCreationTimes(e) 85 | if len(rivals) == 0 { 86 | return true, nil 87 | } 88 | for _, r := range rivals { 89 | // If a rival existed before or at the time of the edge's 90 | // creation, we then return false. 91 | if uint64(r.createdAtBlock) <= blockNum { 92 | return false, nil 93 | } 94 | } 95 | return true, nil 96 | } 97 | 98 | // Contains a rival edge's id and its creation block number. 99 | type rival struct { 100 | id protocol.EdgeId 101 | createdAtBlock creationTime 102 | } 103 | 104 | // Computes the set of rivals with their creation block number for an edge being tracked 105 | // by the challenge tree. We do this by computing the mutual id of the edge and fetching 106 | // all edge ids that share the same one from a set the challenge tree keeps track of. 107 | // We exclude the specified edge from the returned list of rivals. 108 | func (ht *RoyalChallengeTree) rivalsWithCreationTimes(eg protocol.ReadOnlyEdge) []*rival { 109 | rivals := make([]*rival, 0) 110 | key := buildEdgeCreationTimeKey(eg.OriginId(), eg.MutualId()) 111 | mutuals := ht.edgeCreationTimes.Get(key) 112 | if mutuals == nil { 113 | ht.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) 114 | return rivals 115 | } 116 | _ = mutuals.ForEach(func(rivalId protocol.EdgeId, t creationTime) error { 117 | if rivalId == eg.Id() { 118 | return nil 119 | } 120 | rivals = append(rivals, &rival{ 121 | id: rivalId, 122 | createdAtBlock: t, 123 | }) 124 | return nil 125 | }) 126 | return rivals 127 | } 128 | -------------------------------------------------------------------------------- /challenge-manager/challenge-tree/mock/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "mock", 5 | testonly = True, 6 | srcs = ["edge.go"], 7 | importpath = "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock", 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "//chain-abstraction:protocol", 11 | "//containers/option", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /challenge-manager/edge-tracker/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "edge-tracker", 5 | srcs = [ 6 | "challenge_confirmation.go", 7 | "fsm_states.go", 8 | "tracker.go", 9 | "transition_table.go", 10 | ], 11 | importpath = "github.com/offchainlabs/bold/challenge-manager/edge-tracker", 12 | visibility = ["//visibility:public"], 13 | deps = [ 14 | "//chain-abstraction:protocol", 15 | "//challenge-manager/challenge-tree", 16 | "//containers", 17 | "//containers/events", 18 | "//containers/fsm", 19 | "//containers/option", 20 | "//layer2-state-provider", 21 | "//math", 22 | "//runtime", 23 | "//state-commitments/history", 24 | "//time", 25 | "@com_github_ccoveille_go_safecast//:go-safecast", 26 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 27 | "@com_github_ethereum_go_ethereum//common", 28 | "@com_github_ethereum_go_ethereum//core/types", 29 | "@com_github_ethereum_go_ethereum//log", 30 | "@com_github_ethereum_go_ethereum//metrics", 31 | "@com_github_pkg_errors//:errors", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /challenge-manager/edge-tracker/fsm_states.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package edgetracker 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // State defines a finite state machine that aids 12 | // in deciding a challenge edge tracker's actions. 13 | type State uint8 14 | 15 | const ( 16 | // Start state of 0 can never happen to avoid silly mistakes with default Go values. 17 | _ State = iota 18 | // The start state of the tracker. 19 | EdgeStarted 20 | // The edge being tracked is at a one step proof. 21 | EdgeAtOneStepProof 22 | // The tracker is adding a subchallenge leaf on the edge's subchallenge. 23 | EdgeAddingSubchallengeLeaf 24 | // The tracker is attempting a bisection move. 25 | EdgeBisecting 26 | // Intermediary state in which an edge is doing nothing else but awaiting confirmation 27 | // whenever it is possible. 28 | EdgeAwaitingChallengeCompletion 29 | ) 30 | 31 | // String turns an edge tracker state into a readable string. 32 | func (s State) String() string { 33 | switch s { 34 | case EdgeStarted: 35 | return "started" 36 | case EdgeAtOneStepProof: 37 | return "one_step_proof" 38 | case EdgeAddingSubchallengeLeaf: 39 | return "adding_subchallenge_leaf" 40 | case EdgeBisecting: 41 | return "bisecting" 42 | case EdgeAwaitingChallengeCompletion: 43 | return "awaiting_challenge_completion" 44 | default: 45 | return "invalid" 46 | } 47 | } 48 | 49 | // Defines structs that characterize actions an edge tracker 50 | // can take to transition between states in its finite state machine. 51 | type edgeTrackerAction interface { 52 | fmt.Stringer 53 | isEdgeTrackerAction() bool 54 | } 55 | 56 | // Transitions the edge tracker back to a start state. 57 | type edgeBackToStart struct{} 58 | 59 | // Tracker will act if the edge is at a one step proof. 60 | type edgeHandleOneStepProof struct{} 61 | 62 | // Tracker will add a subchallenge on its edge's subchallenge. 63 | type edgeOpenSubchallengeLeaf struct{} 64 | 65 | // Tracker will attempt to bisect its edge. 66 | type edgeBisect struct{} 67 | 68 | type edgeAwaitChallengeCompletion struct{} 69 | 70 | func (edgeBackToStart) String() string { 71 | return "back_to_start" 72 | } 73 | func (edgeHandleOneStepProof) String() string { 74 | return "check_one_step_proof" 75 | } 76 | func (edgeOpenSubchallengeLeaf) String() string { 77 | return "open_subchallenge_leaf" 78 | } 79 | func (edgeBisect) String() string { 80 | return "bisect" 81 | } 82 | func (edgeAwaitChallengeCompletion) String() string { 83 | return "await_challenge_completion" 84 | } 85 | 86 | func (edgeBackToStart) isEdgeTrackerAction() bool { 87 | return true 88 | } 89 | func (edgeHandleOneStepProof) isEdgeTrackerAction() bool { 90 | return true 91 | } 92 | func (edgeOpenSubchallengeLeaf) isEdgeTrackerAction() bool { 93 | return true 94 | } 95 | func (edgeBisect) isEdgeTrackerAction() bool { 96 | return true 97 | } 98 | func (edgeAwaitChallengeCompletion) isEdgeTrackerAction() bool { 99 | return true 100 | } 101 | -------------------------------------------------------------------------------- /challenge-manager/edge-tracker/transition_table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package edgetracker 6 | 7 | import ( 8 | "github.com/offchainlabs/bold/containers/fsm" 9 | ) 10 | 11 | func newEdgeTrackerFsm( 12 | startState State, 13 | fsmOpts ...fsm.Opt[edgeTrackerAction, State], 14 | ) (*fsm.Fsm[edgeTrackerAction, State], error) { 15 | transitions := []*fsm.Event[edgeTrackerAction, State]{ 16 | { 17 | // Returns the tracker to the very beginning. Several states can cause 18 | // this, including challenge moves. 19 | Typ: edgeBackToStart{}, 20 | From: []State{ 21 | EdgeBisecting, 22 | EdgeStarted, 23 | EdgeAtOneStepProof, 24 | EdgeAddingSubchallengeLeaf, 25 | }, 26 | To: EdgeStarted, 27 | }, 28 | { 29 | // The tracker will take some action if it has reached a one-step-proof 30 | // in a small step challenge. 31 | Typ: edgeHandleOneStepProof{}, 32 | From: []State{EdgeStarted, EdgeAtOneStepProof}, 33 | To: EdgeAtOneStepProof, 34 | }, 35 | { 36 | // The tracker will add a subchallenge leaf to its edge's subchallenge. 37 | Typ: edgeOpenSubchallengeLeaf{}, 38 | From: []State{EdgeStarted, EdgeAddingSubchallengeLeaf}, 39 | To: EdgeAddingSubchallengeLeaf, 40 | }, 41 | // Challenge moves. 42 | { 43 | Typ: edgeBisect{}, 44 | From: []State{EdgeStarted, EdgeBisecting}, 45 | To: EdgeBisecting, 46 | }, 47 | // Terminal state, awaiting confirmation. 48 | { 49 | Typ: edgeAwaitChallengeCompletion{}, 50 | From: []State{EdgeStarted, EdgeBisecting, EdgeAddingSubchallengeLeaf, EdgeAwaitingChallengeCompletion, EdgeAtOneStepProof}, 51 | To: EdgeAwaitingChallengeCompletion, 52 | }, 53 | } 54 | return fsm.New(startState, transitions, fsmOpts...) 55 | } 56 | -------------------------------------------------------------------------------- /challenge-manager/types/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "types", 5 | srcs = [ 6 | "interfaces.go", 7 | "mode.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/challenge-manager/types", 10 | visibility = ["//visibility:public"], 11 | deps = ["//chain-abstraction:protocol"], 12 | ) 13 | -------------------------------------------------------------------------------- /challenge-manager/types/interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package types includes types and interfaces specific to the challenge manager instance. 6 | package types 7 | 8 | import ( 9 | "context" 10 | 11 | protocol "github.com/offchainlabs/bold/chain-abstraction" 12 | ) 13 | 14 | // RivalHandler is the interface between the challenge manager and the assertion 15 | // manager. 16 | // 17 | // The challenge manager implements the interface promising to handle opening 18 | // challenges on correct rival assertions, and the assertion manager is 19 | // responsible for posting correct rival assertions, and notifying the rival 20 | // handler of the existence of the correct rival assertion. 21 | type RivalHandler interface { 22 | // HandleCorrectRival is called when the assertion manager has posted a correct 23 | // rival assertion on the chain. 24 | HandleCorrectRival(context.Context, protocol.AssertionHash) error 25 | } 26 | -------------------------------------------------------------------------------- /challenge-manager/types/mode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package types 6 | 7 | type Mode uint8 8 | 9 | const ( 10 | // Watchtower: don't do anything on L1, but log if there's a bad assertion 11 | WatchTowerMode Mode = iota 12 | // Defensive: stake if there's a bad assertion 13 | DefensiveMode 14 | // Resolve nodes: stay staked on the latest node and resolve any unconfirmed 15 | // nodes, challenging bad assertions 16 | ResolveMode 17 | // Make nodes: continually create new nodes, challenging bad assertions 18 | MakeMode 19 | ) 20 | 21 | // SupportsStaking returns true if the mode supports staking 22 | func (m Mode) SupportsStaking() bool { 23 | return m >= MakeMode 24 | } 25 | 26 | // SupportsPostingRivals returns true if the mode supports posting rival 27 | // assertions. 28 | func (m Mode) SupportsPostingRivals() bool { 29 | return m >= DefensiveMode 30 | } 31 | 32 | // SupportsPostingChallenges returns true if the mode supports posting 33 | // challenging edges. 34 | func (m Mode) SupportsPostingChallenges() bool { 35 | return m > DefensiveMode 36 | } 37 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: false 5 | patch: false 6 | github_checks: 7 | annotations: false 8 | comment: 9 | layout: "diff" 10 | behavior: once 11 | after_n_builds: 2 -------------------------------------------------------------------------------- /containers/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "containers", 5 | srcs = ["slice.go"], 6 | importpath = "github.com/offchainlabs/bold/containers", 7 | visibility = ["//visibility:public"], 8 | ) 9 | 10 | go_test( 11 | name = "containers_test", 12 | size = "small", 13 | srcs = ["slice_test.go"], 14 | embed = [":containers"], 15 | deps = ["@com_github_stretchr_testify//require"], 16 | ) 17 | -------------------------------------------------------------------------------- /containers/events/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "events", 5 | srcs = ["producer.go"], 6 | importpath = "github.com/offchainlabs/bold/containers/events", 7 | visibility = ["//visibility:public"], 8 | ) 9 | 10 | go_test( 11 | name = "events_test", 12 | size = "small", 13 | srcs = ["producer_test.go"], 14 | embed = [":events"], 15 | deps = ["@com_github_stretchr_testify//require"], 16 | ) 17 | -------------------------------------------------------------------------------- /containers/events/producer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package events 6 | 7 | import ( 8 | "context" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const ( 14 | defaultBroadcastTimeout = time.Millisecond * 500 15 | defaultSubscriptionBufferSize = 10 16 | ) 17 | 18 | // Producer manages event subscriptions and broadcasts events to them. 19 | type Producer[T any] struct { 20 | sync.RWMutex 21 | subscriptionBufferSize int 22 | subs []*Subscription[T] 23 | doneListener chan subId // channel to listen for IDs of subscriptions to be remove. 24 | broadcastTimeout time.Duration // maximum duration to wait for an event to be sent. 25 | } 26 | 27 | type ProducerOpt[T any] func(*Producer[T]) 28 | 29 | // WithBroadcastTimeout enables the amount of time the broadcaster will wait to send 30 | // to each subscriber before dropping the send. 31 | func WithBroadcastTimeout[T any](timeout time.Duration) ProducerOpt[T] { 32 | return func(ep *Producer[T]) { 33 | ep.broadcastTimeout = timeout 34 | } 35 | } 36 | 37 | // WithSubscriptionBuffer customizes the size of the subscription buffer channel. 38 | func WithSubscriptionBuffer[T any](size int) ProducerOpt[T] { 39 | return func(ep *Producer[T]) { 40 | ep.subscriptionBufferSize = size 41 | } 42 | } 43 | 44 | func NewProducer[T any](opts ...ProducerOpt[T]) *Producer[T] { 45 | producer := &Producer[T]{ 46 | subs: make([]*Subscription[T], 0), 47 | subscriptionBufferSize: defaultSubscriptionBufferSize, 48 | doneListener: make(chan subId, 100), 49 | broadcastTimeout: defaultBroadcastTimeout, 50 | } 51 | for _, opt := range opts { 52 | opt(producer) 53 | } 54 | return producer 55 | } 56 | 57 | // Start begins listening for subscription cancelation requests or context cancelation. 58 | func (ep *Producer[T]) Start(ctx context.Context) { 59 | for { 60 | select { 61 | case id := <-ep.doneListener: 62 | ep.Lock() 63 | // Check if id overflows the length of the slice. 64 | if int(id) >= len(ep.subs) { 65 | ep.Unlock() 66 | continue 67 | } 68 | // Otherwise, clear the subscription from the list. 69 | ep.subs = append(ep.subs[:id], ep.subs[id+1:]...) 70 | ep.Unlock() 71 | case <-ctx.Done(): 72 | close(ep.doneListener) 73 | ep.subs = nil 74 | return 75 | } 76 | } 77 | } 78 | 79 | // Subscribe returns a handle to a new event subscription, 80 | // adding it to the list of active subscriptions. 81 | func (ep *Producer[T]) Subscribe() *Subscription[T] { 82 | ep.Lock() 83 | defer ep.Unlock() 84 | sub := &Subscription[T]{ 85 | id: subId(len(ep.subs)), // Assign a unique ID based on the current count of subscriptions 86 | events: make(chan T), 87 | done: ep.doneListener, 88 | } 89 | ep.subs = append(ep.subs, sub) 90 | return sub 91 | } 92 | 93 | // Broadcast sends an event to all active subscriptions, respecting a configured timeout or context. 94 | // It spawns goroutines to send events to each subscription so as to not block the producer to submitting 95 | // to all consumers. Broadcast should be used if not all consumers are expected to consume the event, 96 | // within a reasonable time, or if the configured broadcast timeout is short enough. 97 | func (ep *Producer[T]) Broadcast(ctx context.Context, event T) { 98 | ep.RLock() 99 | defer ep.RUnlock() 100 | for _, sub := range ep.subs { 101 | go func(listener *Subscription[T]) { 102 | select { 103 | case listener.events <- event: 104 | case <-time.After(ep.broadcastTimeout): 105 | case <-ctx.Done(): 106 | } 107 | }(sub) 108 | } 109 | } 110 | 111 | type subId int 112 | 113 | // Subscription defines a generic handle to a subscription of 114 | // events from a producer. 115 | type Subscription[T any] struct { 116 | id subId 117 | events chan T 118 | done chan subId 119 | } 120 | 121 | // Next waits for the next event or context cancelation, returning the event or an error. 122 | func (es *Subscription[T]) Next(ctx context.Context) (T, bool) { 123 | var zeroVal T 124 | for { 125 | select { 126 | case ev := <-es.events: 127 | return ev, false 128 | case <-ctx.Done(): 129 | es.done <- es.id 130 | close(es.events) 131 | return zeroVal, true 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /containers/events/producer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package events 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestSubscribe(t *testing.T) { 16 | producer := NewProducer[int]() 17 | sub := producer.Subscribe() 18 | require.Equal(t, 1, len(producer.subs)) 19 | require.NotNil(t, sub) 20 | } 21 | 22 | func TestBroadcast(t *testing.T) { 23 | producer := NewProducer[int]() 24 | sub := producer.Subscribe() 25 | done := make(chan bool) 26 | go func() { 27 | event, shouldEnd := sub.Next(context.Background()) 28 | require.False(t, shouldEnd) 29 | require.Equal(t, 42, event) 30 | done <- true 31 | }() 32 | ctx := context.Background() 33 | producer.Broadcast(ctx, 42) 34 | select { 35 | case <-done: 36 | case <-time.After(2 * time.Second): 37 | t.Fatal("Test timed out waiting for event") 38 | } 39 | } 40 | 41 | func TestBroadcastTimeout(t *testing.T) { 42 | timeout := 50 * time.Millisecond 43 | producer := NewProducer(WithBroadcastTimeout[int](timeout)) 44 | sub := producer.Subscribe() 45 | 46 | go func() { 47 | // Delay sending to simulate timeout scenario 48 | time.Sleep(100 * time.Millisecond) 49 | sub.events <- 42 50 | }() 51 | 52 | event, shouldEnd := sub.Next(context.Background()) 53 | require.False(t, shouldEnd) 54 | require.Equal(t, 42, event) 55 | } 56 | 57 | func TestEventProducer_Start(t *testing.T) { 58 | ctx, cancel := context.WithCancel(context.Background()) 59 | producer := NewProducer[int]() 60 | go producer.Start(ctx) 61 | 62 | sub := producer.Subscribe() 63 | 64 | // Simulate removing the subscription. 65 | cancel() 66 | _, shouldEnd := sub.Next(ctx) 67 | if !shouldEnd { 68 | t.Error("Expected to end after context cancellation") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /containers/fsm/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "fsm", 5 | srcs = ["fsm.go"], 6 | importpath = "github.com/offchainlabs/bold/containers/fsm", 7 | visibility = ["//visibility:public"], 8 | deps = ["@com_github_pkg_errors//:errors"], 9 | ) 10 | 11 | go_test( 12 | name = "fsm_test", 13 | size = "small", 14 | srcs = ["fsm_test.go"], 15 | embed = [":fsm"], 16 | deps = ["@com_github_stretchr_testify//require"], 17 | ) 18 | -------------------------------------------------------------------------------- /containers/option/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "option", 5 | srcs = ["option_type.go"], 6 | importpath = "github.com/offchainlabs/bold/containers/option", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /containers/option/option_type.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package option defines a generic option type as a way of representing 6 | // "nothingness" or "something" in a type-safe way. This is useful for 7 | // representing optional values without the need for nil checks or pointers. 8 | package option 9 | 10 | type Option[T any] struct { 11 | value *T 12 | } 13 | 14 | func None[T any]() Option[T] { 15 | return Option[T]{nil} 16 | } 17 | 18 | func Some[T any](x T) Option[T] { 19 | return Option[T]{&x} 20 | } 21 | 22 | func (x Option[T]) IsNone() bool { 23 | return x.value == nil 24 | } 25 | 26 | func (x Option[T]) IsSome() bool { 27 | return x.value != nil 28 | } 29 | 30 | func (x Option[T]) Unwrap() T { 31 | return *x.value 32 | } 33 | -------------------------------------------------------------------------------- /containers/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package containers defines generic data structures to be used in the BoLD 6 | // repository. 7 | package containers 8 | 9 | import "fmt" 10 | 11 | // Trunc truncates a byte slice to 4 bytes and pretty-prints as a hex string. 12 | func Trunc(b []byte) string { 13 | if len(b) < 4 { 14 | return fmt.Sprintf("%#x", b) 15 | } 16 | return fmt.Sprintf("%#x", b[:4]) 17 | } 18 | 19 | // Reverse a generic slice. 20 | func Reverse[T any](s []T) { 21 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 22 | s[i], s[j] = s[j], s[i] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /containers/slice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package containers 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestReverse(t *testing.T) { 14 | type testCase[T any] struct { 15 | items []T 16 | wanted []T 17 | } 18 | testCases := []testCase[uint64]{ 19 | { 20 | items: []uint64{}, 21 | wanted: []uint64{}, 22 | }, 23 | { 24 | items: []uint64{1}, 25 | wanted: []uint64{1}, 26 | }, 27 | { 28 | items: []uint64{1, 2, 3}, 29 | wanted: []uint64{3, 2, 1}, 30 | }, 31 | } 32 | for _, tt := range testCases { 33 | items := tt.items 34 | Reverse(items) 35 | require.Equal(t, tt.wanted, items) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /containers/threadsafe/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "threadsafe", 5 | srcs = [ 6 | "lru_map.go", 7 | "lru_set.go", 8 | "map.go", 9 | "set.go", 10 | "slice.go", 11 | ], 12 | importpath = "github.com/offchainlabs/bold/containers/threadsafe", 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | "//containers/option", 16 | "@com_github_ethereum_go_ethereum//common/lru", 17 | "@com_github_ethereum_go_ethereum//metrics", 18 | ], 19 | ) 20 | 21 | go_test( 22 | name = "threadsafe_test", 23 | size = "small", 24 | srcs = [ 25 | "collections_test.go", 26 | "map_test.go", 27 | "set_test.go", 28 | "slice_test.go", 29 | ], 30 | embed = [":threadsafe"], 31 | deps = ["@com_github_stretchr_testify//require"], 32 | ) 33 | -------------------------------------------------------------------------------- /containers/threadsafe/collections_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestMap(t *testing.T) { 14 | m := NewMap[string, uint64]() 15 | t.Run("Get", func(t *testing.T) { 16 | _, ok := m.TryGet("foo") 17 | require.Equal(t, false, ok) 18 | m.Put("foo", 5) 19 | got, ok := m.TryGet("foo") 20 | require.Equal(t, true, ok) 21 | require.Equal(t, uint64(5), got) 22 | require.Equal(t, uint64(5), m.Get("foo")) 23 | }) 24 | t.Run("Delete", func(t *testing.T) { 25 | m.Delete("foo") 26 | _, ok := m.TryGet("foo") 27 | require.Equal(t, false, ok) 28 | }) 29 | t.Run("ForEach", func(t *testing.T) { 30 | m.Put("foo", 5) 31 | m.Put("bar", 10) 32 | var total uint64 33 | err := m.ForEach(func(_ string, v uint64) error { 34 | total += v 35 | return nil 36 | }) 37 | require.NoError(t, err) 38 | require.Equal(t, uint64(15), total) 39 | }) 40 | } 41 | 42 | func TestSet(t *testing.T) { 43 | m := NewSet[uint64]() 44 | t.Run("Has", func(t *testing.T) { 45 | ok := m.Has(5) 46 | require.Equal(t, false, ok) 47 | m.Insert(5) 48 | ok = m.Has(5) 49 | require.Equal(t, true, ok) 50 | }) 51 | t.Run("Delete", func(t *testing.T) { 52 | m.Delete(5) 53 | ok := m.Has(5) 54 | require.Equal(t, false, ok) 55 | }) 56 | t.Run("ForEach", func(t *testing.T) { 57 | m.Insert(5) 58 | m.Insert(10) 59 | var total uint64 60 | m.ForEach(func(elem uint64) { 61 | total += elem 62 | }) 63 | require.Equal(t, uint64(15), total) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /containers/threadsafe/lru_map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ethereum/go-ethereum/common/lru" 11 | "github.com/ethereum/go-ethereum/metrics" 12 | ) 13 | 14 | type LruMap[K comparable, V any] struct { 15 | sync.RWMutex 16 | items lru.BasicLRU[K, V] 17 | gauge *metrics.Gauge 18 | } 19 | 20 | type LruMapOpt[K comparable, V any] func(*LruMap[K, V]) 21 | 22 | func LruMapWithMetric[K comparable, V any](name string) LruMapOpt[K, V] { 23 | return func(m *LruMap[K, V]) { 24 | gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_lru_map/"+name, nil) 25 | m.gauge = gauge 26 | } 27 | } 28 | 29 | func NewLruMap[K comparable, V any](capacity int, opts ...LruMapOpt[K, V]) *LruMap[K, V] { 30 | m := &LruMap[K, V]{items: lru.NewBasicLRU[K, V](capacity)} 31 | for _, opt := range opts { 32 | opt(m) 33 | } 34 | return m 35 | } 36 | 37 | func (s *LruMap[K, V]) IsEmpty() bool { 38 | s.RLock() 39 | defer s.RUnlock() 40 | return s.items.Len() == 0 41 | } 42 | 43 | func (s *LruMap[K, V]) Put(k K, v V) { 44 | s.Lock() 45 | defer s.Unlock() 46 | s.items.Add(k, v) 47 | if s.gauge != nil { 48 | (*s.gauge).Inc(1) 49 | } 50 | } 51 | 52 | func (s *LruMap[K, V]) Has(k K) bool { 53 | s.RLock() 54 | defer s.RUnlock() 55 | return s.items.Contains(k) 56 | } 57 | 58 | func (s *LruMap[K, V]) NumItems() int { 59 | s.RLock() 60 | defer s.RUnlock() 61 | return s.items.Len() 62 | } 63 | 64 | func (s *LruMap[K, V]) TryGet(k K) (V, bool) { 65 | s.RLock() 66 | defer s.RUnlock() 67 | return s.items.Get(k) 68 | } 69 | 70 | func (s *LruMap[K, V]) Delete(k K) { 71 | s.Lock() 72 | defer s.Unlock() 73 | s.items.Remove(k) 74 | if s.gauge != nil { 75 | (*s.gauge).Dec(1) 76 | } 77 | } 78 | 79 | func (s *LruMap[K, V]) ForEach(fn func(k K, v V) error) error { 80 | s.RLock() 81 | defer s.RUnlock() 82 | for _, k := range s.items.Keys() { 83 | v, _ := s.items.Get(k) 84 | if err := fn(k, v); err != nil { 85 | return err 86 | } 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /containers/threadsafe/lru_set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ethereum/go-ethereum/common/lru" 11 | "github.com/ethereum/go-ethereum/metrics" 12 | ) 13 | 14 | type LruSet[T comparable] struct { 15 | sync.RWMutex 16 | items lru.BasicLRU[T, bool] 17 | gauge *metrics.Gauge 18 | } 19 | 20 | type LruSetOpt[T comparable] func(*LruSet[T]) 21 | 22 | func LruSetWithMetric[T comparable](name string) LruSetOpt[T] { 23 | return func(s *LruSet[T]) { 24 | gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_lru_set/"+name, nil) 25 | s.gauge = gauge 26 | } 27 | } 28 | 29 | func NewLruSet[T comparable](capacity int, opts ...LruSetOpt[T]) *LruSet[T] { 30 | s := &LruSet[T]{ 31 | items: lru.NewBasicLRU[T, bool](capacity), 32 | } 33 | for _, opt := range opts { 34 | opt(s) 35 | } 36 | return s 37 | } 38 | 39 | func (s *LruSet[T]) Insert(t T) { 40 | s.Lock() 41 | defer s.Unlock() 42 | s.items.Add(t, true) 43 | if s.gauge != nil { 44 | (*s.gauge).Inc(1) 45 | } 46 | } 47 | 48 | func (s *LruSet[T]) NumItems() int { 49 | s.RLock() 50 | defer s.RUnlock() 51 | return s.items.Len() 52 | } 53 | 54 | func (s *LruSet[T]) Has(t T) bool { 55 | s.RLock() 56 | defer s.RUnlock() 57 | return s.items.Contains(t) 58 | } 59 | 60 | func (s *LruSet[T]) Delete(t T) { 61 | s.Lock() 62 | defer s.Unlock() 63 | s.items.Remove(t) 64 | if s.gauge != nil { 65 | (*s.gauge).Dec(1) 66 | } 67 | } 68 | 69 | func (s *LruSet[T]) ForEach(fn func(elem T)) { 70 | s.RLock() 71 | defer s.RUnlock() 72 | for _, elem := range s.items.Keys() { 73 | fn(elem) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /containers/threadsafe/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ethereum/go-ethereum/metrics" 11 | ) 12 | 13 | type Map[K comparable, V any] struct { 14 | sync.RWMutex 15 | items map[K]V 16 | gauge *metrics.Gauge 17 | } 18 | 19 | type MapOpt[K comparable, V any] func(*Map[K, V]) 20 | 21 | func MapWithMetric[K comparable, V any](name string) MapOpt[K, V] { 22 | return func(m *Map[K, V]) { 23 | gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_map/"+name, nil) 24 | m.gauge = gauge 25 | } 26 | } 27 | 28 | func NewMap[K comparable, V any](opts ...MapOpt[K, V]) *Map[K, V] { 29 | m := &Map[K, V]{items: make(map[K]V)} 30 | for _, opt := range opts { 31 | opt(m) 32 | } 33 | return m 34 | } 35 | 36 | func NewMapFromItems[K comparable, V any](m map[K]V) *Map[K, V] { 37 | return &Map[K, V]{items: m} 38 | } 39 | 40 | func (s *Map[K, V]) IsEmpty() bool { 41 | s.RLock() 42 | defer s.RUnlock() 43 | return len(s.items) == 0 44 | } 45 | 46 | func (s *Map[K, V]) Put(k K, v V) { 47 | s.Lock() 48 | defer s.Unlock() 49 | s.items[k] = v 50 | if s.gauge != nil { 51 | (*s.gauge).Inc(1) 52 | } 53 | } 54 | 55 | func (s *Map[K, V]) Has(k K) bool { 56 | s.RLock() 57 | defer s.RUnlock() 58 | _, ok := s.items[k] 59 | return ok 60 | } 61 | 62 | func (s *Map[K, V]) NumItems() uint64 { 63 | s.RLock() 64 | defer s.RUnlock() 65 | return uint64(len(s.items)) 66 | } 67 | 68 | func (s *Map[K, V]) TryGet(k K) (V, bool) { 69 | s.RLock() 70 | defer s.RUnlock() 71 | item, ok := s.items[k] 72 | return item, ok 73 | } 74 | 75 | func (s *Map[K, V]) Get(k K) V { 76 | s.RLock() 77 | defer s.RUnlock() 78 | return s.items[k] 79 | } 80 | 81 | func (s *Map[K, V]) Delete(k K) { 82 | s.Lock() 83 | defer s.Unlock() 84 | delete(s.items, k) 85 | if s.gauge != nil { 86 | (*s.gauge).Dec(1) 87 | } 88 | } 89 | 90 | func (s *Map[K, V]) ForEach(fn func(k K, v V) error) error { 91 | s.RLock() 92 | defer s.RUnlock() 93 | for k, v := range s.items { 94 | if err := fn(k, v); err != nil { 95 | return err 96 | } 97 | } 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /containers/threadsafe/map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "errors" 9 | "testing" 10 | ) 11 | 12 | func TestNewMap(t *testing.T) { 13 | m := NewMap[int, string]() 14 | if !m.IsEmpty() { 15 | t.Errorf("Expected map to be empty") 16 | } 17 | } 18 | 19 | func TestNewMapFromItems(t *testing.T) { 20 | initialItems := map[int]string{1: "one", 2: "two"} 21 | m := NewMapFromItems(initialItems) 22 | 23 | if m.NumItems() != 2 { 24 | t.Errorf("Expected 2 items, got %d", m.NumItems()) 25 | } 26 | 27 | if val, ok := m.TryGet(1); !ok || val != "one" { 28 | t.Errorf("Expected 'one', got %s", val) 29 | } 30 | 31 | if val, ok := m.TryGet(2); !ok || val != "two" { 32 | t.Errorf("Expected 'two', got %s", val) 33 | } 34 | } 35 | 36 | func TestPutAndGet(t *testing.T) { 37 | m := NewMap[int, string]() 38 | m.Put(1, "one") 39 | if val := m.Get(1); val != "one" { 40 | t.Errorf("Expected 'one', got %s", val) 41 | } 42 | } 43 | 44 | func TestHas(t *testing.T) { 45 | m := NewMap[int, string]() 46 | m.Put(1, "one") 47 | if !m.Has(1) { 48 | t.Errorf("Expected key to exist") 49 | } 50 | } 51 | 52 | func TestNumItems(t *testing.T) { 53 | m := NewMap[int, string]() 54 | m.Put(1, "one") 55 | m.Put(2, "two") 56 | if m.NumItems() != 2 { 57 | t.Errorf("Expected 2 items, got %d", m.NumItems()) 58 | } 59 | } 60 | 61 | func TestTryGet(t *testing.T) { 62 | m := NewMap[int, string]() 63 | m.Put(1, "one") 64 | val, ok := m.TryGet(1) 65 | if !ok || val != "one" { 66 | t.Errorf("Expected 'one', got %s", val) 67 | } 68 | } 69 | 70 | func TestDelete(t *testing.T) { 71 | m := NewMap[int, string]() 72 | m.Put(1, "one") 73 | m.Delete(1) 74 | if m.Has(1) { 75 | t.Errorf("Expected key to be deleted") 76 | } 77 | } 78 | 79 | func TestForEach(t *testing.T) { 80 | m := NewMap[int, string]() 81 | m.Put(1, "one") 82 | m.Put(2, "two") 83 | err := m.ForEach(func(k int, v string) error { 84 | if v == "three" { 85 | return errors.New("should not have 'three'") 86 | } 87 | return nil 88 | }) 89 | if err != nil { 90 | t.Errorf("ForEach errored: %v", err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /containers/threadsafe/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ethereum/go-ethereum/metrics" 11 | ) 12 | 13 | type Set[T comparable] struct { 14 | sync.RWMutex 15 | items map[T]bool 16 | gauge *metrics.Gauge 17 | } 18 | 19 | type SetOpt[T comparable] func(*Set[T]) 20 | 21 | func SetWithMetric[T comparable](name string) SetOpt[T] { 22 | return func(s *Set[T]) { 23 | gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_set/"+name, nil) 24 | s.gauge = gauge 25 | } 26 | } 27 | 28 | func NewSet[T comparable](opts ...SetOpt[T]) *Set[T] { 29 | s := &Set[T]{ 30 | items: make(map[T]bool), 31 | } 32 | for _, opt := range opts { 33 | opt(s) 34 | } 35 | return s 36 | } 37 | 38 | func (s *Set[T]) Insert(t T) { 39 | s.Lock() 40 | defer s.Unlock() 41 | s.items[t] = true 42 | if s.gauge != nil { 43 | (*s.gauge).Inc(1) 44 | } 45 | } 46 | 47 | func (s *Set[T]) NumItems() uint64 { 48 | s.RLock() 49 | defer s.RUnlock() 50 | return uint64(len(s.items)) 51 | } 52 | 53 | func (s *Set[T]) Has(t T) bool { 54 | s.RLock() 55 | defer s.RUnlock() 56 | return s.items[t] 57 | } 58 | 59 | func (s *Set[T]) Delete(t T) { 60 | s.Lock() 61 | defer s.Unlock() 62 | delete(s.items, t) 63 | if s.gauge != nil { 64 | (*s.gauge).Dec(1) 65 | } 66 | } 67 | 68 | func (s *Set[T]) ForEach(fn func(elem T)) { 69 | s.RLock() 70 | defer s.RUnlock() 71 | for elem := range s.items { 72 | fn(elem) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /containers/threadsafe/set_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestNewSet(t *testing.T) { 12 | s := NewSet[int]() 13 | if s.NumItems() != 0 { 14 | t.Errorf("Expected 0 items, got %d", s.NumItems()) 15 | } 16 | } 17 | 18 | func TestInsert(t *testing.T) { 19 | s := NewSet[int]() 20 | s.Insert(1) 21 | if s.NumItems() != 1 { 22 | t.Errorf("Expected 1 item, got %d", s.NumItems()) 23 | } 24 | } 25 | 26 | func TestHasSet(t *testing.T) { 27 | s := NewSet[int]() 28 | s.Insert(1) 29 | if !s.Has(1) { 30 | t.Errorf("Expected item to exist") 31 | } 32 | } 33 | 34 | func TestDeleteSet(t *testing.T) { 35 | s := NewSet[int]() 36 | s.Insert(1) 37 | s.Delete(1) 38 | if s.Has(1) { 39 | t.Errorf("Expected item to be deleted") 40 | } 41 | } 42 | 43 | func TestNumItemsSet(t *testing.T) { 44 | s := NewSet[int]() 45 | s.Insert(1) 46 | s.Insert(2) 47 | if s.NumItems() != 2 { 48 | t.Errorf("Expected 2 items, got %d", s.NumItems()) 49 | } 50 | } 51 | 52 | func TestForEachSet(t *testing.T) { 53 | s := NewSet[int]() 54 | s.Insert(1) 55 | s.Insert(2) 56 | count := 0 57 | s.ForEach(func(elem int) { 58 | count++ 59 | }) 60 | if count != 2 { 61 | t.Errorf("Expected to iterate 2 times, iterated %d times", count) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /containers/threadsafe/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package threadsafe defines generic, threadsafe analogues of common data structures 6 | // in Go such as maps, slices, and sets for use in BoLD with an intuitive API. 7 | package threadsafe 8 | 9 | import ( 10 | "sync" 11 | 12 | "github.com/offchainlabs/bold/containers/option" 13 | ) 14 | 15 | type Slice[V any] struct { 16 | sync.RWMutex 17 | items []V 18 | } 19 | 20 | func NewSlice[V any]() *Slice[V] { 21 | return &Slice[V]{items: make([]V, 0)} 22 | } 23 | 24 | func (s *Slice[V]) Push(v V) { 25 | s.Lock() 26 | defer s.Unlock() 27 | s.items = append(s.items, v) 28 | } 29 | 30 | func (s *Slice[V]) Get(i int) option.Option[V] { 31 | s.RLock() 32 | defer s.RUnlock() 33 | if i >= len(s.items) { 34 | return option.None[V]() 35 | } 36 | return option.Some(s.items[i]) 37 | } 38 | 39 | func (s *Slice[V]) Len() int { 40 | s.RLock() 41 | defer s.RUnlock() 42 | return len(s.items) 43 | } 44 | 45 | func (s *Slice[V]) Find(fn func(idx int, elem V) bool) bool { 46 | s.RLock() 47 | defer s.RUnlock() 48 | for ii, vv := range s.items { 49 | if fn(ii, vv) { 50 | return true 51 | } 52 | } 53 | return false 54 | } 55 | -------------------------------------------------------------------------------- /containers/threadsafe/slice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package threadsafe 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestNewSlice(t *testing.T) { 12 | s := NewSlice[int]() 13 | if s.Len() != 0 { 14 | t.Errorf("Expected length to be 0, got %d", s.Len()) 15 | } 16 | } 17 | 18 | func TestPush(t *testing.T) { 19 | s := NewSlice[int]() 20 | s.Push(1) 21 | if s.Len() != 1 { 22 | t.Errorf("Expected length to be 1, got %d", s.Len()) 23 | } 24 | } 25 | 26 | func TestLen(t *testing.T) { 27 | s := NewSlice[int]() 28 | s.Push(1) 29 | s.Push(2) 30 | if s.Len() != 2 { 31 | t.Errorf("Expected length to be 2, got %d", s.Len()) 32 | } 33 | } 34 | 35 | func TestFind(t *testing.T) { 36 | s := NewSlice[int]() 37 | s.Push(1) 38 | s.Push(2) 39 | s.Push(3) 40 | 41 | found := s.Find(func(idx int, elem int) bool { 42 | return elem == 2 43 | }) 44 | 45 | if !found { 46 | t.Errorf("Expected to find the element") 47 | } 48 | 49 | notFound := s.Find(func(idx int, elem int) bool { 50 | return elem == 4 51 | }) 52 | 53 | if notFound { 54 | t.Errorf("Did not expect to find the element") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # BOLD Architecture 2 | 3 | When explaining how all components of BOLD fit together, it’s helpful to consider the software that runs an actual validator for Arbitrum: Arbitrum Nitro. Some of its responsibilities are: 4 | 5 | - To **execute incoming transactions** from an Inbox contract and produce post-states according to the L2 state transition 6 | - To **submit transaction batches** to Ethereum L1 to a contract known as the SequencerInbox 7 | - To validate transactions and submit claims known as **assertions** about the L2 state at frequent intervals to Ethereum, to a contract known as `RollupCore.sol` 8 | - To **challenge** incorrect assertions posted by dishonest validators to `RollupCore.sol` 9 | 10 | ![Image](diagrams/nitro-integration.drawio.png) 11 | 12 | Most importantly, Arbitrum technology is moving into a more modular architecture in which specific components can be run as standalone binaries, allowing for greater resilience and easier changes in the future. BOLD implements a component of Nitro responsible for posting assertions and challenging invalid ones, and therefore depends on other parts of the Nitro node to provide it with the state data it needs. The BOLD repository is a standalone Github project separate from Nitro, as it can be plugged into Nitro chains as a dependency to perform its roles. 13 | 14 | ## How it Works 15 | 16 | The **BOLD Challenge Manager** above interacts with a few items from an Arbitrum node, allowing it to make challenge moves and submit history commitments to the contracts on Ethereum. In our code, abstract away those L2 node components via a small interface called the **L2 State Provider**. 17 | 18 | Recall the core primitive of the protocol are **challenge edges**, where an edge represents a start and end history commitment to an Arbitrum chain. Participants in the protocol can make **moves** on challenges, and validators can **participate in many challenges concurrently.** That is, our software needs to track edges within a challenge and their lifecycle in order to win against malicious entities. 19 | 20 | Our detailed architecture looks like this: 21 | 22 | ![Image](diagrams/bold-internals.drawio.png) 23 | 24 | Here's how it works: 25 | 26 | 1. An **Assertion Poster** frequently takes validated messages from the L2 validator and posts them onchain 27 | 2. An **Assertion Scanner** checks smart contracts on Ethereum for newly posted assertions and compares them against the local Nitro node’s DB. This is done via an abstraction known as a **L2 State Provider**. This is within `assertions/scanner.go` 28 | 3. If we disagree with an assertion, our **Challenge Manager** submits an onchain challenge by creating a level zero edge, and tracks that edge as a go routine 29 | 4. Our **Chain Watcher**, in the background, scans for other created edges onchain and spawns edge tracker goroutines for honest edges we are not yet tracking 30 | 31 | Each edge is tracked as a goroutine called an **Edge Tracker**. Edge trackers are self-contained goroutines that wake up at specified tick intervals and use a finite state machine to decide what challenge move to take. For example, after an edge is created, it will try to bisect, confirm via one step proof, or create a subchallenge depending on its current state. 32 | 33 | Here's what the finite state machine of an edge tracker looks like, with its final state being `Confirmed`: 34 | 35 | ![Image](diagrams/edge-tracker-fsm.drawio.png) 36 | 37 | Once a level zero edge for a challenge on an assertion is confirmed, the assertion can then be confirmed and the honest party will emerge successful. -------------------------------------------------------------------------------- /docs/audits/TrailOfBitsAudit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/audits/TrailOfBitsAudit.pdf -------------------------------------------------------------------------------- /docs/diagrams/api.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/diagrams/api.drawio.png -------------------------------------------------------------------------------- /docs/diagrams/bold-internals.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/diagrams/bold-internals.drawio.png -------------------------------------------------------------------------------- /docs/diagrams/edge-tracker-fsm.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/diagrams/edge-tracker-fsm.drawio.png -------------------------------------------------------------------------------- /docs/diagrams/nitro-integration.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/diagrams/nitro-integration.drawio.png -------------------------------------------------------------------------------- /docs/research-specs/BOLDChallengeProtocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/research-specs/BOLDChallengeProtocol.pdf -------------------------------------------------------------------------------- /docs/research-specs/Economics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/research-specs/Economics.pdf -------------------------------------------------------------------------------- /docs/research-specs/TechnicalDeepDive.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/docs/research-specs/TechnicalDeepDive.pdf -------------------------------------------------------------------------------- /layer2-state-provider/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "layer2-state-provider", 5 | srcs = [ 6 | "history_commitment_provider.go", 7 | "provider.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/layer2-state-provider", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//api", 13 | "//api/db", 14 | "//chain-abstraction:protocol", 15 | "//containers/option", 16 | "//state-commitments/history", 17 | "//state-commitments/prefix-proofs", 18 | "@com_github_ccoveille_go_safecast//:go-safecast", 19 | "@com_github_ethereum_go_ethereum//accounts/abi", 20 | "@com_github_ethereum_go_ethereum//common", 21 | "@com_github_ethereum_go_ethereum//metrics", 22 | ], 23 | ) 24 | 25 | go_test( 26 | name = "layer2-state-provider_test", 27 | size = "small", 28 | srcs = ["history_commitment_provider_test.go"], 29 | embed = [":layer2-state-provider"], 30 | deps = [ 31 | "//containers/option", 32 | "@com_github_stretchr_testify//require", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /logs/ephemeral/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "ephemeral", 5 | srcs = ["log.go"], 6 | importpath = "github.com/offchainlabs/bold/logs/ephemeral", 7 | visibility = ["//visibility:public"], 8 | deps = ["@com_github_ethereum_go_ethereum//log"], 9 | ) 10 | 11 | go_test( 12 | name = "ephemeral_test", 13 | size = "small", 14 | srcs = ["log_test.go"], 15 | embed = [":ephemeral"], 16 | deps = ["@com_github_ethereum_go_ethereum//log"], 17 | ) 18 | -------------------------------------------------------------------------------- /logs/ephemeral/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package ephemeral 6 | 7 | import ( 8 | "strings" 9 | "time" 10 | 11 | "github.com/ethereum/go-ethereum/log" 12 | ) 13 | 14 | // EphemeralErrorHandler handles errors that are ephemeral in nature i.e. these are errors 15 | // that we would like to log as a warning unless they repeat for more than a certain duration of time. 16 | type EphemeralErrorHandler struct { 17 | Duration time.Duration 18 | ErrorString string 19 | FirstOccurrence *time.Time 20 | 21 | IgnoreDuration time.Duration 22 | IgnoredErrLogLevel func(string, ...interface{}) // Default IgnoredErrLogLevel is log.Debug 23 | } 24 | 25 | func NewEphemeralErrorHandler(duration time.Duration, errorString string, ignoreDuration time.Duration) *EphemeralErrorHandler { 26 | return &EphemeralErrorHandler{ 27 | Duration: duration, 28 | ErrorString: errorString, 29 | FirstOccurrence: &time.Time{}, 30 | IgnoreDuration: ignoreDuration, 31 | IgnoredErrLogLevel: log.Debug, 32 | } 33 | } 34 | 35 | // LogLevel method defaults to returning the input currentLogLevel if the given error doesnt contain the errorSubstring, 36 | // but if it does, then returns one of the corresponding loglevels as follows 37 | // - IgnoredErrLogLevel - if the error has been repeating for less than the IgnoreDuration of time. Defaults to log.Debug 38 | // - log.Warn - if the error has been repeating for less than the given duration of time 39 | // - log.Error - Otherwise 40 | // 41 | // # Usage Examples 42 | // 43 | // ephemeralErrorHandler.Loglevel(err, log.Error)("msg") 44 | // ephemeralErrorHandler.Loglevel(err, log.Error)("msg", "key1", val1, "key2", val2) 45 | // ephemeralErrorHandler.Loglevel(err, log.Error)("msg", "key1", val1) 46 | func (h *EphemeralErrorHandler) LogLevel(err error, currentLogLevel func(msg string, ctx ...interface{})) func(string, ...interface{}) { 47 | if h.ErrorString != "" && !strings.Contains(err.Error(), h.ErrorString) { 48 | h.Reset() 49 | return currentLogLevel 50 | } 51 | 52 | if h.FirstOccurrence.Equal((time.Time{})) { 53 | *h.FirstOccurrence = time.Now() 54 | } 55 | 56 | if h.IgnoreDuration != 0 && time.Since(*h.FirstOccurrence) < h.IgnoreDuration { 57 | if h.IgnoredErrLogLevel != nil { 58 | return h.IgnoredErrLogLevel 59 | } 60 | return log.Debug 61 | } 62 | 63 | if time.Since(*h.FirstOccurrence) < h.Duration { 64 | return log.Warn 65 | } 66 | return log.Error 67 | } 68 | 69 | func (h *EphemeralErrorHandler) Reset() { 70 | *h.FirstOccurrence = time.Time{} 71 | } 72 | -------------------------------------------------------------------------------- /logs/ephemeral/log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package ephemeral 6 | 7 | import ( 8 | "errors" 9 | "reflect" 10 | "testing" 11 | "time" 12 | 13 | "github.com/ethereum/go-ethereum/log" 14 | ) 15 | 16 | func compareFunctions(f1, f2 func(msg string, ctx ...interface{})) bool { 17 | return reflect.ValueOf(f1).Pointer() == reflect.ValueOf(f2).Pointer() 18 | } 19 | func TestSimple(t *testing.T) { 20 | allErrHandler := NewEphemeralErrorHandler(2500*time.Millisecond, "", time.Second) 21 | err := errors.New("sample error") 22 | logLevel := allErrHandler.LogLevel(err, log.Error) 23 | if !compareFunctions(log.Debug, logLevel) { 24 | t.Fatalf("incorrect loglevel output. Want: Debug") 25 | } 26 | 27 | time.Sleep(1 * time.Second) 28 | logLevel = allErrHandler.LogLevel(err, log.Error) 29 | if !compareFunctions(log.Warn, logLevel) { 30 | t.Fatalf("incorrect loglevel output. Want: Warn") 31 | } 32 | 33 | time.Sleep(2 * time.Second) 34 | logLevel = allErrHandler.LogLevel(err, log.Error) 35 | if !compareFunctions(log.Error, logLevel) { 36 | t.Fatalf("incorrect loglevel output. Want: Error") 37 | } 38 | } 39 | 40 | func TestComplex(t *testing.T) { 41 | // Simulation: errorA happens continuously for 2 seconds and then errorB happens 42 | errorAHandler := NewEphemeralErrorHandler(time.Second, "errorA", 0) 43 | errorBHandler := NewEphemeralErrorHandler(1500*time.Millisecond, "errorB", 0) 44 | 45 | // Computes result of chaining two ephemeral error handlers for a given recurring error 46 | chainingErrHandlers := func(err error) func(string, ...interface{}) { 47 | logLevel := log.Error 48 | logLevel = errorAHandler.LogLevel(err, logLevel) 49 | logLevel = errorBHandler.LogLevel(err, logLevel) 50 | return logLevel 51 | } 52 | 53 | errA := errors.New("this is a sample errorA") 54 | if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { 55 | t.Fatalf("incorrect loglevel output. Want: Warn") 56 | } 57 | time.Sleep(2 * time.Second) 58 | if !compareFunctions(log.Error, chainingErrHandlers(errA)) { 59 | t.Fatalf("incorrect loglevel output. Want: Error") 60 | } 61 | 62 | errB := errors.New("this is a sample errorB") 63 | if !compareFunctions(log.Warn, chainingErrHandlers(errB)) { 64 | t.Fatalf("incorrect loglevel output. Want: Warn") 65 | } 66 | if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { 67 | t.Fatalf("incorrect loglevel output. Want: Warn") 68 | } 69 | 70 | errC := errors.New("random error") 71 | if !compareFunctions(log.Error, chainingErrHandlers(errC)) { 72 | t.Fatalf("incorrect loglevel output. Want: Error") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /math/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "math", 5 | srcs = [ 6 | "intlog2.go", 7 | "math.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/math", 10 | visibility = ["//visibility:public"], 11 | ) 12 | 13 | go_test( 14 | name = "math_test", 15 | size = "small", 16 | srcs = [ 17 | "intlog2_test.go", 18 | "math_test.go", 19 | ], 20 | embed = [":math"], 21 | deps = [ 22 | "//testing/casttest", 23 | "@com_github_stretchr_testify//require", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /math/intlog2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package math 6 | 7 | import "math/bits" 8 | 9 | // Log2Floor returns the integer logarithm base 2 of u (rounded down). 10 | func Log2Floor(u uint64) int { 11 | if u == 0 { 12 | panic("log2 undefined for non-positive values") 13 | } 14 | return bits.Len64(u) - 1 15 | } 16 | 17 | // Log2Ceil returns the integer logarithm base 2 of u (rounded up). 18 | func Log2Ceil(u uint64) int { 19 | r := Log2Floor(u) 20 | if isPowerOfTwo(u) { 21 | return r 22 | } 23 | return r + 1 24 | } 25 | 26 | func isPowerOfTwo(u uint64) bool { 27 | return u&(u-1) == 0 28 | } 29 | -------------------------------------------------------------------------------- /math/intlog2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package math 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/offchainlabs/bold/testing/casttest" 15 | ) 16 | 17 | var benchResult int 18 | 19 | func TestUnsingedIntegerLog2Floor(t *testing.T) { 20 | type log2TestCase struct { 21 | input uint64 22 | expected int 23 | } 24 | 25 | testCases := []log2TestCase{ 26 | {input: 1, expected: 0}, 27 | {input: 2, expected: 1}, 28 | {input: 4, expected: 2}, 29 | {input: 6, expected: 2}, 30 | {input: 8, expected: 3}, 31 | {input: 24601, expected: 14}, 32 | } 33 | for _, tc := range testCases { 34 | t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { 35 | res := Log2Floor(tc.input) 36 | require.Equal(t, tc.expected, res) 37 | }) 38 | } 39 | } 40 | 41 | func TestUnsingedIntegerLog2FloorPanicsOnZero(t *testing.T) { 42 | require.Panics(t, func() { 43 | Log2Floor(0) 44 | }) 45 | } 46 | 47 | func FuzzUnsingedIntegerLog2Floor(f *testing.F) { 48 | testcases := []uint64{0, 2, 4, 6, 8} 49 | for _, tc := range testcases { 50 | f.Add(tc) 51 | } 52 | f.Fuzz(func(t *testing.T, input uint64) { 53 | if input == 0 { 54 | require.Panics(t, func() { 55 | Log2Floor(input) 56 | }) 57 | t.Skip() 58 | } 59 | r := Log2Floor(input) 60 | fr := math.Log2(float64(input)) 61 | require.Equal(t, int(math.Floor(fr)), r) 62 | }) 63 | } 64 | 65 | func BenchmarkUnsingedIntegerLog2Floor(b *testing.B) { 66 | var r int 67 | for i := 1; i < b.N; i++ { 68 | r = Log2Floor(casttest.ToUint64(b, i)) 69 | } 70 | benchResult = r 71 | } 72 | 73 | func BenchmarkMathLog2Floor(b *testing.B) { 74 | var r int 75 | for i := 1; i < b.N; i++ { 76 | r = int(math.Log2(float64(i))) 77 | } 78 | benchResult = r 79 | } 80 | 81 | func TestUnsingedIntegerLog2Ceil(t *testing.T) { 82 | type log2TestCase struct { 83 | input uint64 84 | expected int 85 | } 86 | 87 | testCases := []log2TestCase{ 88 | {input: 1, expected: 0}, 89 | {input: 2, expected: 1}, 90 | {input: 4, expected: 2}, 91 | {input: 6, expected: 3}, 92 | {input: 8, expected: 3}, 93 | {input: 24601, expected: 15}, 94 | } 95 | for _, tc := range testCases { 96 | t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { 97 | res := Log2Ceil(tc.input) 98 | require.Equal(t, tc.expected, res) 99 | }) 100 | } 101 | } 102 | 103 | func TestUnsingedIntegerLog2CeilPanicsOnZero(t *testing.T) { 104 | require.Panics(t, func() { 105 | Log2Ceil(0) 106 | }) 107 | } 108 | 109 | func FuzzUnsingedIntegerLog2Ceil(f *testing.F) { 110 | testcases := []uint64{0, 2, 4, 6, 8} 111 | for _, tc := range testcases { 112 | f.Add(tc) 113 | } 114 | f.Fuzz(func(t *testing.T, input uint64) { 115 | if input == 0 { 116 | require.Panics(t, func() { 117 | Log2Ceil(input) 118 | }) 119 | t.Skip() 120 | } 121 | r := Log2Ceil(input) 122 | fr := math.Log2(float64(input)) 123 | require.Equal(t, int(math.Ceil(fr)), r) 124 | }) 125 | } 126 | 127 | func BenchmarkUnsingedIntegerLog2Ceil(b *testing.B) { 128 | var r int 129 | for i := 1; i < b.N; i++ { 130 | r = Log2Ceil(casttest.ToUint64(b, i)) 131 | } 132 | benchResult = r 133 | } 134 | 135 | func BenchmarkMathLog2Ceil(b *testing.B) { 136 | var r int 137 | for i := 1; i < b.N; i++ { 138 | r = int(math.Ceil(math.Log2(float64(i)))) 139 | } 140 | benchResult = r 141 | } 142 | -------------------------------------------------------------------------------- /math/math.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package math defines utilities for performing operations critical to the 6 | // computations performed during a challenge in BOLD. 7 | package math 8 | 9 | import ( 10 | "errors" 11 | "math" 12 | "math/bits" 13 | ) 14 | 15 | var ErrUnableToBisect = errors.New("unable to bisect") 16 | 17 | // Unsigned is a generic constraint for all unsigned numeric primitives. 18 | type Unsigned interface { 19 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 20 | } 21 | 22 | func Bisect(pre, post uint64) (uint64, error) { 23 | if pre+2 > post { 24 | return 0, ErrUnableToBisect 25 | } 26 | if pre+2 == post { 27 | return pre + 1, nil 28 | } 29 | matchingBits := bits.LeadingZeros64((post - 1) ^ pre) 30 | mask := uint64(math.MaxUint64) << (63 - matchingBits) 31 | return (post - 1) & mask, nil 32 | } 33 | -------------------------------------------------------------------------------- /math/math_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package math 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestBisectionPoint(t *testing.T) { 14 | type bpTestCase struct { 15 | pre uint64 16 | post uint64 17 | expected uint64 18 | } 19 | 20 | errorTestCases := []bpTestCase{ 21 | {12, 13, 0}, 22 | {13, 9, 0}, 23 | } 24 | for _, testCase := range errorTestCases { 25 | _, err := Bisect(testCase.pre, testCase.post) 26 | require.ErrorIs(t, err, ErrUnableToBisect, testCase) 27 | } 28 | testCases := []bpTestCase{ 29 | {0, 2, 1}, 30 | {1, 3, 2}, 31 | {31, 33, 32}, 32 | {32, 34, 33}, 33 | {13, 15, 14}, 34 | {0, 9, 8}, 35 | {0, 13, 8}, 36 | {0, 15, 8}, 37 | {13, 17, 16}, 38 | {13, 31, 16}, 39 | {15, 31, 16}, 40 | } 41 | for _, testCase := range testCases { 42 | res, err := Bisect(testCase.pre, testCase.post) 43 | require.NoError(t, err, testCase) 44 | require.Equal(t, testCase.expected, res) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nogo.json: -------------------------------------------------------------------------------- 1 | { 2 | "asmdecl": { 3 | "exclude_files": { 4 | "external/.*": "Third party code" 5 | } 6 | }, 7 | "assign": { 8 | "exclude_files": { 9 | "external/.*": "Third party code" 10 | } 11 | }, 12 | "atomicassign": { 13 | "exclude_files": { 14 | "external/.*": "Third party code" 15 | } 16 | }, 17 | "bools": { 18 | "exclude_files": { 19 | "external/.*": "Third party code" 20 | } 21 | }, 22 | "buildssa": { 23 | "exclude_files": { 24 | "external/.*": "Third party code" 25 | } 26 | }, 27 | "buildtag": { 28 | "exclude_files": { 29 | "external/.*": "Third party code" 30 | } 31 | }, 32 | "composites": { 33 | "exclude_files": { 34 | "external/.*": "Third party code" 35 | } 36 | }, 37 | "ctrlflow": { 38 | "exclude_files": { 39 | "external/.*": "Third party code" 40 | } 41 | }, 42 | "copylocks": { 43 | "exclude_files": { 44 | "external/.*": "Third party code" 45 | } 46 | }, 47 | "deepequalerrors": { 48 | "exclude_files": { 49 | "external/.*": "Third party code" 50 | } 51 | }, 52 | "errorsas": { 53 | "exclude_files": { 54 | "external/.*": "Third party code" 55 | } 56 | }, 57 | "httpresponse": { 58 | "exclude_files": { 59 | "external/.*": "Third party code" 60 | } 61 | }, 62 | "ifaceassert": { 63 | "exclude_files": { 64 | "external/.*": "Third party code" 65 | } 66 | }, 67 | "loopclosure": { 68 | "exclude_files": { 69 | "external/.*": "Third party code" 70 | } 71 | }, 72 | "lostcancel": { 73 | "exclude_files": { 74 | "external/.*": "Third party code" 75 | } 76 | }, 77 | "nilfunc": { 78 | "exclude_files": { 79 | "external/.*": "Third party code" 80 | } 81 | }, 82 | "nilness": { 83 | "exclude_files": { 84 | "external/.*": "Third party code", 85 | "cgo/.*": "Third party code" 86 | } 87 | }, 88 | "pkgfact": { 89 | "exclude_files": { 90 | "external/.*": "Third party code" 91 | } 92 | }, 93 | "printf": { 94 | "exclude_files": { 95 | "external/.*": "Third party code" 96 | } 97 | }, 98 | "shadow": { 99 | "exclude_files": { 100 | "external/.*": "Third party code", 101 | "cgo/.*": "Third party code", 102 | "validator/.*test\\.go": "disabling for tests", 103 | "protocol/.*test\\.go": "disabling for tests" 104 | } 105 | }, 106 | "shift": { 107 | "exclude_files": { 108 | "external/.*": "Third party code" 109 | } 110 | }, 111 | "sortslice": { 112 | "exclude_files": { 113 | "external/.*": "Third party code" 114 | } 115 | }, 116 | "stdmethods": { 117 | "exclude_files": { 118 | "external/.*": "Third party code" 119 | } 120 | }, 121 | "stringintconv": { 122 | "exclude_files": { 123 | "external/.*": "Third party code" 124 | } 125 | }, 126 | "structtag": { 127 | "exclude_files": { 128 | "external/.*": "Third party code" 129 | } 130 | }, 131 | "testinggoroutine": { 132 | "exclude_files": { 133 | "external/.*": "Third party code" 134 | } 135 | }, 136 | "tests": { 137 | "exclude_files": { 138 | "external/.*": "Third party code" 139 | } 140 | }, 141 | "unmarshal": { 142 | "exclude_files": { 143 | "external/.*": "Third party code" 144 | } 145 | }, 146 | "unreachable": { 147 | "exclude_files": { 148 | "external/.*": "Third party code" 149 | } 150 | }, 151 | "unsafeptr": { 152 | "exclude_files": { 153 | "external/.*": "Third party code" 154 | } 155 | }, 156 | "unusedresult": { 157 | "exclude_files": { 158 | "external/.*": "Third party code" 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /runtime/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "runtime", 5 | srcs = ["retry.go"], 6 | importpath = "github.com/offchainlabs/bold/runtime", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//logs/ephemeral", 10 | "@com_github_ethereum_go_ethereum//log", 11 | "@com_github_ethereum_go_ethereum//metrics", 12 | ], 13 | ) 14 | 15 | go_test( 16 | name = "runtime_test", 17 | size = "small", 18 | srcs = ["retry_test.go"], 19 | embed = [":runtime"], 20 | deps = ["@com_github_stretchr_testify//require"], 21 | ) 22 | -------------------------------------------------------------------------------- /runtime/retry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package runtime defines utilities that deal with managing lifecycles of 6 | // functions and important behaviors at the application runtime, such as 7 | // retrying errored functions until they succeed. 8 | package retry 9 | 10 | import ( 11 | "context" 12 | "strings" 13 | "time" 14 | 15 | "github.com/ethereum/go-ethereum/log" 16 | "github.com/ethereum/go-ethereum/metrics" 17 | 18 | "github.com/offchainlabs/bold/logs/ephemeral" 19 | ) 20 | 21 | const defaultSleepTime = time.Second * 30 22 | 23 | var ( 24 | retryCounter = metrics.NewRegisteredCounter("arb/validator/runtime/retry", nil) 25 | ) 26 | 27 | type RetryConfig struct { 28 | sleepTime time.Duration 29 | LevelWarningError string // can be extended to a list or regex if demanded in future, currently supporting for one error 30 | } 31 | 32 | type Opt func(*RetryConfig) 33 | 34 | // WithInterval specifies how often to retry an errored function. 35 | func WithInterval(d time.Duration) Opt { 36 | return func(rc *RetryConfig) { 37 | rc.sleepTime = d 38 | } 39 | } 40 | 41 | func UntilSucceedsMultipleReturnValue[T, U any](ctx context.Context, fn func() (T, U, error), opts ...Opt) (T, U, error) { 42 | cfg := &RetryConfig{ 43 | sleepTime: defaultSleepTime, 44 | } 45 | for _, o := range opts { 46 | o(cfg) 47 | } 48 | count := 0 49 | // Retry until succeeds is usually used for cases where its believed that retrying a function will most likely succeed 50 | // or the function has a some chance of failing even if it's not expected to fail, based on this assumption, 51 | // we use a commonEphemeralErrorHandler to log the errors at warn level for the first 10 minutes 52 | // and only after that we log the errors at error level. 53 | commonEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(time.Minute*10, "", 0) 54 | for { 55 | if ctx.Err() != nil { 56 | return zeroVal[T](), zeroVal[U](), ctx.Err() 57 | } 58 | got, got2, err := fn() 59 | if err != nil { 60 | count++ 61 | logLevel := log.Error 62 | logLevel = commonEphemeralErrorHandler.LogLevel(err, logLevel) 63 | if cfg.LevelWarningError != "" && strings.Contains(err.Error(), cfg.LevelWarningError) { 64 | logLevel = log.Warn 65 | } 66 | logLevel("Could not succeed function after retries", 67 | "retryCount", count, 68 | "err", err, 69 | ) 70 | retryCounter.Inc(1) 71 | select { 72 | case <-ctx.Done(): 73 | return zeroVal[T](), zeroVal[U](), ctx.Err() 74 | case <-time.After(cfg.sleepTime): 75 | } 76 | continue 77 | } 78 | commonEphemeralErrorHandler.Reset() 79 | return got, got2, nil 80 | } 81 | } 82 | 83 | // UntilSucceeds retries the given function until it succeeds or the context is cancelled. 84 | func UntilSucceeds[T any](ctx context.Context, fn func() (T, error), opts ...Opt) (T, error) { 85 | result, _, err := UntilSucceedsMultipleReturnValue(ctx, func() (T, struct{}, error) { 86 | got, err := fn() 87 | return got, struct{}{}, err 88 | }, opts...) 89 | return result, err 90 | } 91 | 92 | func zeroVal[T any]() T { 93 | var result T 94 | return result 95 | } 96 | -------------------------------------------------------------------------------- /runtime/retry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package retry 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestRetryUntilSucceeds(t *testing.T) { 15 | hello := func() (string, error) { 16 | return "hello", nil 17 | } 18 | 19 | ctx := context.Background() 20 | got, err := UntilSucceeds(ctx, hello) 21 | require.NoError(t, err) 22 | require.Equal(t, "hello", got) 23 | 24 | newCtx, cancel := context.WithCancel(ctx) 25 | cancel() 26 | _, err = UntilSucceeds(newCtx, hello) 27 | require.ErrorContains(t, err, "context canceled") 28 | } 29 | -------------------------------------------------------------------------------- /solgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_library( 4 | name = "solgen_lib", 5 | srcs = [ 6 | "gen.go", 7 | "main.go", 8 | ], 9 | importpath = "github.com/offchainlabs/bold/solgen", 10 | visibility = ["//visibility:private"], 11 | deps = ["@com_github_ethereum_go_ethereum//accounts/abi/bind"], 12 | ) 13 | 14 | go_binary( 15 | name = "solgen", 16 | embed = [":solgen_lib"], 17 | visibility = ["//visibility:public"], 18 | ) 19 | -------------------------------------------------------------------------------- /solgen/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Offchain Labs, Inc. 2 | // For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE 3 | 4 | package main 5 | -------------------------------------------------------------------------------- /solgen/go/accessorsgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "accessorsgen", 5 | srcs = ["accessorsgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/accessorsgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/assertionStakingPoolgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "assertionStakingPoolgen", 5 | srcs = ["assertionStakingPoolgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/assertionStakingPoolgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/basegen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "basegen", 5 | srcs = ["basegen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/basegen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/bridgegen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "bridgegen", 5 | srcs = ["bridgegen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/bridgegen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/chaingen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "chaingen", 5 | srcs = ["chaingen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/chaingen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/challengeV2gen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "challengeV2gen", 5 | srcs = ["challengeV2gen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/challengeV2gen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/challengegen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "challengegen", 5 | srcs = ["challengegen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/challengegen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/commongen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "commongen", 5 | srcs = ["commongen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/commongen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/contractsgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "contractsgen", 5 | srcs = ["contractsgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/contractsgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/express_lane_auctiongen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "express_lane_auctiongen", 5 | srcs = ["express_lane_auctiongen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/express_lane_auctiongen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/externalgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "externalgen", 5 | srcs = ["externalgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/externalgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/handlergen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "handlergen", 5 | srcs = ["handlergen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/handlergen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/interfacesgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "interfacesgen", 5 | srcs = ["interfacesgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/interfacesgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/librariesgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "librariesgen", 5 | srcs = ["librariesgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/librariesgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/mocksgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "mocksgen", 5 | srcs = ["mocksgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/mocksgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/node_interfacegen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "node_interfacegen", 5 | srcs = ["node_interfacegen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/node_interfacegen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/ospgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "ospgen", 5 | srcs = ["ospgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/ospgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/precompilesgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "precompilesgen", 5 | srcs = ["precompilesgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/precompilesgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/proxiesgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "proxiesgen", 5 | srcs = ["proxiesgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/proxiesgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/rollupgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "rollupgen", 5 | srcs = ["rollupgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/rollupgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/stategen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "stategen", 5 | srcs = ["stategen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/stategen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/test_helpersgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "test_helpersgen", 5 | srcs = ["test_helpersgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/test_helpersgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/testgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "testgen", 5 | srcs = ["testgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/testgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /solgen/go/yulgen/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "yulgen", 5 | srcs = ["yulgen.go"], 6 | importpath = "github.com/offchainlabs/bold/solgen/go/yulgen", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ethereum_go_ethereum//:go-ethereum", 10 | "@com_github_ethereum_go_ethereum//accounts/abi", 11 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 12 | "@com_github_ethereum_go_ethereum//common", 13 | "@com_github_ethereum_go_ethereum//core/types", 14 | "@com_github_ethereum_go_ethereum//event", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /state-commitments/history/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "history", 5 | srcs = ["history_commitment.go"], 6 | importpath = "github.com/offchainlabs/bold/state-commitments/history", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//math", 10 | "@com_github_ccoveille_go_safecast//:go-safecast", 11 | "@com_github_ethereum_go_ethereum//common", 12 | "@com_github_ethereum_go_ethereum//crypto", 13 | ], 14 | ) 15 | 16 | go_test( 17 | name = "history_test", 18 | size = "small", 19 | srcs = ["history_commitment_test.go"], 20 | embed = [":history"], 21 | deps = [ 22 | "//state-commitments/legacy", 23 | "//state-commitments/prefix-proofs", 24 | "//testing/casttest", 25 | "@com_github_ethereum_go_ethereum//common", 26 | "@com_github_ethereum_go_ethereum//crypto", 27 | "@com_github_stretchr_testify//require", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /state-commitments/inclusion-proofs/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | package_group( 4 | name = "friends", 5 | packages = ["//state-commitments/legacy"], 6 | ) 7 | 8 | go_library( 9 | name = "inclusion-proofs", 10 | srcs = ["inclusion_proofs.go"], 11 | importpath = "github.com/offchainlabs/bold/state-commitments/inclusion-proofs", 12 | visibility = [":friends"], 13 | deps = [ 14 | "//state-commitments/prefix-proofs", 15 | "@com_github_ethereum_go_ethereum//common", 16 | "@com_github_ethereum_go_ethereum//crypto", 17 | "@com_github_pkg_errors//:errors", 18 | ], 19 | ) 20 | 21 | go_test( 22 | name = "inclusion-proofs_test", 23 | size = "small", 24 | srcs = ["inclusion_proofs_test.go"], 25 | embed = [":inclusion-proofs"], 26 | deps = [ 27 | "//state-commitments/prefix-proofs", 28 | "//testing/casttest", 29 | "@com_github_ethereum_go_ethereum//common", 30 | "@com_github_stretchr_testify//require", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /state-commitments/inclusion-proofs/inclusion_proofs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package inclusionproofs defines a series of utilities for generating and 6 | // verifying traditional Merkle proofs of data. 7 | package inclusionproofs 8 | 9 | import ( 10 | "runtime" 11 | "sync" 12 | 13 | "github.com/pkg/errors" 14 | 15 | "github.com/ethereum/go-ethereum/common" 16 | "github.com/ethereum/go-ethereum/crypto" 17 | 18 | prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" 19 | ) 20 | 21 | var ( 22 | ErrProofTooLong = errors.New("merkle proof too long") 23 | ErrInvalidLeaves = errors.New("invalid number of leaves for merkle tree") 24 | ) 25 | 26 | // FullTree generates a Merkle tree from a list of leaves. 27 | func FullTree(leaves []common.Hash) ([][]common.Hash, error) { 28 | msb, err := prefixproofs.MostSignificantBit(uint64(len(leaves))) 29 | if err != nil { 30 | return nil, err 31 | } 32 | lsb, err := prefixproofs.LeastSignificantBit(uint64(len(leaves))) 33 | if err != nil { 34 | return nil, err 35 | } 36 | maxLevel := msb + 1 37 | if msb == lsb { 38 | maxLevel = msb 39 | } 40 | 41 | layers := make([][]common.Hash, maxLevel+1) 42 | layers[0] = leaves 43 | l := uint64(1) 44 | 45 | prevLayer := leaves 46 | for len(prevLayer) > 1 { 47 | nextLayer := make([]common.Hash, (len(prevLayer)+1)/2) 48 | for i := 0; i < len(nextLayer); i++ { 49 | if 2*i+1 < len(prevLayer) { 50 | nextLayer[i] = crypto.Keccak256Hash(prevLayer[2*i].Bytes(), prevLayer[2*i+1].Bytes()) 51 | } else { 52 | nextLayer[i] = crypto.Keccak256Hash(prevLayer[2*i].Bytes(), (common.Hash{}).Bytes()) 53 | } 54 | } 55 | layers[l] = nextLayer 56 | prevLayer = nextLayer 57 | l++ 58 | } 59 | return layers, nil 60 | } 61 | 62 | // GenerateInclusionProof from a list of Merkle leaves at a specified index. 63 | func GenerateInclusionProof(leaves []common.Hash, idx uint64) ([]common.Hash, error) { 64 | if len(leaves) == 0 { 65 | return nil, ErrInvalidLeaves 66 | } 67 | if idx >= uint64(len(leaves)) { 68 | return nil, ErrInvalidLeaves 69 | } 70 | if len(leaves) == 1 { 71 | return make([]common.Hash, 0), nil 72 | } 73 | rehashed := make([]common.Hash, len(leaves)) 74 | var waitGroup sync.WaitGroup 75 | gomaxprocs := runtime.GOMAXPROCS(-1) 76 | waitGroup.Add(gomaxprocs) 77 | batchSize := len(leaves) / gomaxprocs 78 | batchRemainder := len(leaves) % gomaxprocs 79 | for i := 0; i < gomaxprocs-1; i++ { 80 | start := i * batchSize 81 | go func() { 82 | defer waitGroup.Done() 83 | for j := start; j < start+batchSize; j++ { 84 | rehashed[j] = crypto.Keccak256Hash(leaves[j].Bytes()) 85 | } 86 | }() 87 | } 88 | start := (gomaxprocs - 1) * batchSize 89 | go func() { 90 | defer waitGroup.Done() 91 | for j := start; j < start+batchSize+batchRemainder; j++ { 92 | rehashed[j] = crypto.Keccak256Hash(leaves[j].Bytes()) 93 | } 94 | }() 95 | waitGroup.Wait() 96 | 97 | fullT, err := FullTree(rehashed) 98 | if err != nil { 99 | return nil, err 100 | } 101 | maxLevel, err := prefixproofs.MostSignificantBit(uint64(len(rehashed)) - 1) 102 | if err != nil { 103 | return nil, err 104 | } 105 | proof := make([]common.Hash, maxLevel+1) 106 | 107 | for level := uint64(0); level <= maxLevel; level++ { 108 | levelIndex := idx >> level 109 | counterpartIndex := levelIndex ^ 1 110 | layer := fullT[level] 111 | counterpart := common.Hash{} 112 | if counterpartIndex <= uint64(len(layer))-1 { 113 | counterpart = layer[counterpartIndex] 114 | } 115 | proof[level] = counterpart 116 | } 117 | 118 | return proof, nil 119 | } 120 | 121 | // CalculateRootFromProof calculates a Merkle root from a Merkle proof, index, and leaf. 122 | func CalculateRootFromProof(proof []common.Hash, index uint64, leaf common.Hash) (common.Hash, error) { 123 | if len(proof) > 256 { 124 | return common.Hash{}, ErrProofTooLong 125 | } 126 | h := crypto.Keccak256Hash(leaf[:]) 127 | for i := 0; i < len(proof); i++ { 128 | node := proof[i] 129 | if index&(1< 0) 28 | 29 | computedRoot, err := CalculateRootFromProof(proof, index, leaves[index]) 30 | require.NoError(t, err) 31 | 32 | exp := prefixproofs.NewEmptyMerkleExpansion() 33 | for _, r := range leaves { 34 | exp, err = prefixproofs.AppendLeaf(exp, r) 35 | require.NoError(t, err) 36 | } 37 | 38 | root, err := prefixproofs.Root(exp) 39 | require.NoError(t, err) 40 | 41 | t.Run("proof verifies", func(t *testing.T) { 42 | require.Equal(t, root, computedRoot) 43 | }) 44 | t.Run("first leaf proof", func(t *testing.T) { 45 | index = uint64(0) 46 | proof, err = GenerateInclusionProof(leaves, index) 47 | require.NoError(t, err) 48 | require.Equal(t, true, len(proof) > 0) 49 | computedRoot, err = CalculateRootFromProof(proof, index, leaves[index]) 50 | require.NoError(t, err) 51 | require.Equal(t, root, computedRoot) 52 | }) 53 | t.Run("last leaf proof", func(t *testing.T) { 54 | index = casttest.ToUint64(t, len(leaves)-1) 55 | proof, err = GenerateInclusionProof(leaves, index) 56 | require.NoError(t, err) 57 | require.Equal(t, true, len(proof) > 0) 58 | computedRoot, err = CalculateRootFromProof(proof, index, leaves[index]) 59 | require.NoError(t, err) 60 | require.Equal(t, root, computedRoot) 61 | }) 62 | t.Run("Invalid inputs", func(t *testing.T) { 63 | // Empty tree should not generate a proof. 64 | _, err := GenerateInclusionProof([]common.Hash{}, 0) 65 | require.Equal(t, ErrInvalidLeaves, err) 66 | 67 | // Index greater than the number of leaves should not generate a proof. 68 | _, err = GenerateInclusionProof(leaves, uint64(len(leaves))) 69 | require.Equal(t, ErrInvalidLeaves, err) 70 | 71 | // Proof with more than 256 elements should not calculate a root... 72 | _, err = CalculateRootFromProof(make([]common.Hash, 257), 0, common.Hash{}) 73 | require.Equal(t, ErrProofTooLong, err) 74 | 75 | // ... but proof with exactly 256 elements should be OK. 76 | _, err = CalculateRootFromProof(make([]common.Hash, 256), 0, common.Hash{}) 77 | require.NotEqual(t, ErrProofTooLong, err) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /state-commitments/legacy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "legacy", 5 | srcs = ["legacy.go"], 6 | importpath = "github.com/offchainlabs/bold/state-commitments/legacy", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//state-commitments/inclusion-proofs", 10 | "//state-commitments/prefix-proofs", 11 | "@com_github_ccoveille_go_safecast//:go-safecast", 12 | "@com_github_ethereum_go_ethereum//common", 13 | ], 14 | ) 15 | 16 | go_test( 17 | name = "legacy_test", 18 | size = "small", 19 | srcs = ["legacy_test.go"], 20 | embed = [":legacy"], 21 | deps = [ 22 | "//state-commitments/inclusion-proofs", 23 | "@com_github_ethereum_go_ethereum//common", 24 | "@com_github_stretchr_testify//require", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /state-commitments/legacy/legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package history defines the primitive HistoryCommitment type in the BoLD 6 | // protocol. 7 | package legacy 8 | 9 | import ( 10 | "errors" 11 | "sync" 12 | 13 | "github.com/ccoveille/go-safecast" 14 | 15 | "github.com/ethereum/go-ethereum/common" 16 | 17 | inclusionproofs "github.com/offchainlabs/bold/state-commitments/inclusion-proofs" 18 | prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" 19 | ) 20 | 21 | var ( 22 | emptyCommit = LegacyHistory{} 23 | ) 24 | 25 | // LegacyHistory defines a Merkle accumulator over a list of leaves, which 26 | // are understood to be state roots in the goimpl. A history commitment contains 27 | // a "height" value, which can refer to a height of an assertion in the assertions 28 | // tree, or a "step" of WAVM states in a big step or small step subchallenge. 29 | // A commitment contains a Merkle root over the list of leaves, and can optionally 30 | // provide a proof that the last leaf in the accumulator Merkleizes into the 31 | // specified root hash, which is required when verifying challenge creation invariants. 32 | type LegacyHistory struct { 33 | Height uint64 34 | Merkle common.Hash 35 | FirstLeaf common.Hash 36 | LastLeafProof []common.Hash 37 | FirstLeafProof []common.Hash 38 | LastLeaf common.Hash 39 | } 40 | 41 | func NewLegacy(leaves []common.Hash) (LegacyHistory, error) { 42 | if len(leaves) == 0 { 43 | return emptyCommit, errors.New("must commit to at least one leaf") 44 | } 45 | var waitGroup sync.WaitGroup 46 | waitGroup.Add(3) 47 | 48 | var firstLeafProof []common.Hash 49 | var err1 error 50 | go func() { 51 | defer waitGroup.Done() 52 | firstLeafProof, err1 = inclusionproofs.GenerateInclusionProof(leaves, 0) 53 | }() 54 | 55 | var lastLeafProof []common.Hash 56 | var err2 error 57 | go func() { 58 | defer waitGroup.Done() 59 | lastLeafProof, err2 = inclusionproofs.GenerateInclusionProof(leaves, uint64(len(leaves))-1) 60 | }() 61 | 62 | var root common.Hash 63 | var err3 error 64 | go func() { 65 | defer waitGroup.Done() 66 | exp := prefixproofs.NewEmptyMerkleExpansion() 67 | for _, r := range leaves { 68 | exp, err3 = prefixproofs.AppendLeaf(exp, r) 69 | if err3 != nil { 70 | return 71 | } 72 | } 73 | root, err3 = prefixproofs.Root(exp) 74 | }() 75 | waitGroup.Wait() 76 | 77 | if err1 != nil { 78 | return emptyCommit, err1 79 | } 80 | if err2 != nil { 81 | return emptyCommit, err2 82 | } 83 | if err3 != nil { 84 | return emptyCommit, err3 85 | } 86 | hU64, err := safecast.ToUint64(len(leaves) - 1) 87 | if err != nil { 88 | return emptyCommit, err 89 | } 90 | 91 | return LegacyHistory{ 92 | Merkle: root, 93 | Height: hU64, 94 | FirstLeaf: leaves[0], 95 | LastLeaf: leaves[len(leaves)-1], 96 | FirstLeafProof: firstLeafProof, 97 | LastLeafProof: lastLeafProof, 98 | }, nil 99 | } 100 | -------------------------------------------------------------------------------- /state-commitments/legacy/legacy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package legacy 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/ethereum/go-ethereum/common" 14 | 15 | inclusionproofs "github.com/offchainlabs/bold/state-commitments/inclusion-proofs" 16 | ) 17 | 18 | func TestHistoryCommitment_LeafProofs(t *testing.T) { 19 | leaves := make([]common.Hash, 8) 20 | for i := 0; i < len(leaves); i++ { 21 | leaves[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) 22 | } 23 | history, err := NewLegacy(leaves) 24 | require.NoError(t, err) 25 | require.Equal(t, history.FirstLeaf, leaves[0]) 26 | require.Equal(t, history.LastLeaf, leaves[len(leaves)-1]) 27 | 28 | computed, err := inclusionproofs.CalculateRootFromProof(history.LastLeafProof, history.Height, history.LastLeaf) 29 | require.NoError(t, err) 30 | require.Equal(t, history.Merkle, computed) 31 | computed, err = inclusionproofs.CalculateRootFromProof(history.FirstLeafProof, 0, history.FirstLeaf) 32 | require.NoError(t, err) 33 | require.Equal(t, history.Merkle, computed) 34 | } 35 | -------------------------------------------------------------------------------- /state-commitments/prefix-proofs/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | package_group( 4 | name = "friends", 5 | packages = [ 6 | "//layer2-state-provider", 7 | "//state-commitments/history", 8 | "//state-commitments/inclusion-proofs", 9 | "//state-commitments/legacy", 10 | "//testing/integration", 11 | ], 12 | ) 13 | 14 | go_library( 15 | name = "prefix-proofs", 16 | srcs = [ 17 | "merkle_expansions.go", 18 | "prefix_proofs.go", 19 | ], 20 | importpath = "github.com/offchainlabs/bold/state-commitments/prefix-proofs", 21 | visibility = [":friends"], 22 | deps = [ 23 | "@com_github_ccoveille_go_safecast//:go-safecast", 24 | "@com_github_ethereum_go_ethereum//common", 25 | "@com_github_ethereum_go_ethereum//crypto", 26 | "@com_github_pkg_errors//:errors", 27 | ], 28 | ) 29 | 30 | go_test( 31 | name = "prefix-proofs_test", 32 | size = "small", 33 | srcs = [ 34 | "merkle_expansions_test.go", 35 | "prefix_proofs_test.go", 36 | ], 37 | data = glob(["testdata/**"]), 38 | embed = [":prefix-proofs"], 39 | deps = [ 40 | "//chain-abstraction:protocol", 41 | "//containers/option", 42 | "//layer2-state-provider", 43 | "//solgen/go/mocksgen", 44 | "//testing/casttest", 45 | "//testing/mocks/state-provider", 46 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 47 | "@com_github_ethereum_go_ethereum//common", 48 | "@com_github_ethereum_go_ethereum//common/hexutil", 49 | "@com_github_ethereum_go_ethereum//core/types", 50 | "@com_github_ethereum_go_ethereum//crypto", 51 | "@com_github_ethereum_go_ethereum//ethclient/simulated", 52 | "@com_github_stretchr_testify//require", 53 | ], 54 | ) 55 | -------------------------------------------------------------------------------- /state-commitments/prefix-proofs/merkle_expansions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package prefixproofs 6 | 7 | import ( 8 | "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | type MerkleExpansion []common.Hash 12 | 13 | func NewEmptyMerkleExpansion() MerkleExpansion { 14 | return []common.Hash{} 15 | } 16 | 17 | func (me MerkleExpansion) Clone() MerkleExpansion { 18 | return append([]common.Hash{}, me...) 19 | } 20 | 21 | func (me MerkleExpansion) Compact() ([]common.Hash, uint64) { 22 | var comp []common.Hash 23 | size := uint64(0) 24 | for level, h := range me { 25 | if h != (common.Hash{}) { 26 | comp = append(comp, h) 27 | size += 1 << level 28 | } 29 | } 30 | return comp, size 31 | } 32 | 33 | func MerkleExpansionFromCompact(comp []common.Hash, size uint64) (MerkleExpansion, uint64) { 34 | var me []common.Hash 35 | numRead := uint64(0) 36 | i := uint64(1) 37 | for i <= size { 38 | if i&size != 0 { 39 | numRead++ 40 | me = append(me, comp[0]) 41 | comp = comp[1:] 42 | } else { 43 | me = append(me, common.Hash{}) 44 | } 45 | i <<= 1 46 | } 47 | return me, numRead 48 | } 49 | 50 | func ExpansionFromLeaves(leaves []common.Hash) (MerkleExpansion, error) { 51 | ret := NewEmptyMerkleExpansion() 52 | for _, leaf := range leaves { 53 | appended, err := AppendLeaf(ret, leaf) 54 | if err != nil { 55 | return nil, err 56 | } 57 | ret = appended 58 | } 59 | return ret, nil 60 | } 61 | 62 | type MerkleExpansionRootFetcherFunc = func(leaves []common.Hash, upTo uint64) (common.Hash, error) 63 | 64 | func RootFetcherFromExpansion(leaves []common.Hash, upTo uint64) (common.Hash, error) { 65 | exp, err := ExpansionFromLeaves(leaves[:upTo]) 66 | if err != nil { 67 | return common.Hash{}, err 68 | } 69 | return Root(exp) 70 | } 71 | -------------------------------------------------------------------------------- /state-commitments/prefix-proofs/merkle_expansions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package prefixproofs 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/ethereum/go-ethereum/crypto" 13 | ) 14 | 15 | func TestMerkleExpansion(t *testing.T) { 16 | me := NewEmptyMerkleExpansion() 17 | 18 | h0 := crypto.Keccak256Hash([]byte{0}) 19 | me, err := AppendCompleteSubTree(me, 0, h0) 20 | require.NoError(t, err) 21 | root, err := Root(me) 22 | require.NoError(t, err) 23 | require.Equal(t, h0, root) 24 | compUncompTest(t, me) 25 | 26 | h1 := crypto.Keccak256Hash([]byte{1}) 27 | me, err = AppendCompleteSubTree(me, 0, h1) 28 | require.NoError(t, err) 29 | root, err = Root(me) 30 | require.NoError(t, err) 31 | require.Equal(t, crypto.Keccak256Hash(h0.Bytes(), h1.Bytes()), root) 32 | compUncompTest(t, me) 33 | 34 | me2 := me.Clone() 35 | h2 := crypto.Keccak256Hash([]byte{2}) 36 | h3 := crypto.Keccak256Hash([]byte{2}) 37 | h23 := crypto.Keccak256Hash(h2.Bytes(), h3.Bytes()) 38 | me, err = AppendCompleteSubTree(me, 1, h23) 39 | require.NoError(t, err) 40 | root, err = Root(me) 41 | require.NoError(t, err) 42 | root2, err := Root(me2) 43 | require.NoError(t, err) 44 | require.Equal(t, crypto.Keccak256Hash(root2.Bytes(), h23.Bytes()), root) 45 | compUncompTest(t, me) 46 | 47 | me4 := me.Clone() 48 | me, err = AppendCompleteSubTree(me2, 0, h2) 49 | require.NoError(t, err) 50 | me, err = AppendCompleteSubTree(me, 0, h3) 51 | require.NoError(t, err) 52 | root, err = Root(me) 53 | require.NoError(t, err) 54 | root4, err := Root(me4) 55 | require.NoError(t, err) 56 | require.Equal(t, root, root4) 57 | compUncompTest(t, me) 58 | } 59 | 60 | func compUncompTest(t *testing.T, me MerkleExpansion) { 61 | t.Helper() 62 | comp, compSz := me.Compact() 63 | me2, _ := MerkleExpansionFromCompact(comp, compSz) 64 | root, err := Root(me) 65 | require.NoError(t, err) 66 | root2, err := Root(me2) 67 | require.NoError(t, err) 68 | require.Equal(t, root, root2) 69 | } 70 | -------------------------------------------------------------------------------- /state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/75d9022918aebc68769044c9043bd11a47046e9a443da304857347818e0bb256: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("0X8d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") 3 | uint64(4) 4 | string("0X0A67518C3889d1662998592B444f649ed6518ff579C361f7ff588ABe702B435C") 5 | uint64(8) 6 | string("0X000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") 7 | string("0Xf33B5757f8CC18013A35072A9d7015CeB97f82Af403C903B2e1d118d135831") 8 | -------------------------------------------------------------------------------- /state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/94136d3efec8e976: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("0X8d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") 3 | uint64(4) 4 | string("0X0A67518C3889d1662998592B444f649ed6518ff579C361f7ff588ABe702B435C") 5 | uint64(8) 6 | string("0X000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") 7 | string("0Xf33B5757f8CC18013A35072A9d7015CeB97f82Af403C903B2e1d118D135831") 8 | -------------------------------------------------------------------------------- /testing/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "testing", 5 | testonly = 1, 6 | srcs = [ 7 | "rollup_config.go", 8 | "tx.go", 9 | ], 10 | importpath = "github.com/offchainlabs/bold/testing", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//chain-abstraction:protocol", 14 | "//solgen/go/rollupgen", 15 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 16 | "@com_github_ethereum_go_ethereum//common", 17 | "@com_github_ethereum_go_ethereum//core/types", 18 | "@com_github_pkg_errors//:errors", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /testing/casttest/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "casttest", 5 | srcs = ["safe.go"], 6 | importpath = "github.com/offchainlabs/bold/testing/casttest", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_ccoveille_go_safecast//:go-safecast", 10 | "@com_github_stretchr_testify//require", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /testing/casttest/safe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package casttest exposes test helper functions to wrap safecast calls. 6 | package casttest 7 | 8 | import ( 9 | "testing" 10 | 11 | "github.com/ccoveille/go-safecast" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | // ToUint wraps safecast.ToUint with a test assertion. 16 | func ToUint[T safecast.Type](t testing.TB, i T) uint { 17 | t.Helper() 18 | u, err := safecast.ToUint(i) 19 | require.NoError(t, err) 20 | return u 21 | } 22 | 23 | // ToUint8 wraps safecast.ToUint8 with a test assertion. 24 | func ToUint8[T safecast.Type](t testing.TB, i T) uint8 { 25 | t.Helper() 26 | u, err := safecast.ToUint8(i) 27 | require.NoError(t, err) 28 | return u 29 | } 30 | 31 | // ToUint64 wraps safecast.ToUint64 with a test assertion. 32 | func ToUint64[T safecast.Type](t testing.TB, i T) uint64 { 33 | t.Helper() 34 | u, err := safecast.ToUint64(i) 35 | require.NoError(t, err) 36 | return u 37 | } 38 | 39 | // ToInt wraps safecast.ToInt with a test assertion. 40 | func ToInt[T safecast.Type](t testing.TB, i T) int { 41 | t.Helper() 42 | u, err := safecast.ToInt(i) 43 | require.NoError(t, err) 44 | return u 45 | } 46 | 47 | // ToInt64 wraps safecast.ToInt64 with a test assertion. 48 | func ToInt64[T safecast.Type](t testing.TB, i T) int64 { 49 | t.Helper() 50 | u, err := safecast.ToInt64(i) 51 | require.NoError(t, err) 52 | return u 53 | } 54 | -------------------------------------------------------------------------------- /testing/endtoend/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | test_suite( 4 | name = "endtoend_suite", 5 | tags = ["manual"], 6 | tests = [ 7 | ":endtoend_test", 8 | "//testing/endtoend/backend:backend_test", 9 | ], 10 | ) 11 | 12 | go_test( 13 | name = "endtoend_test", 14 | size = "large", 15 | srcs = [ 16 | "e2e_crash_test.go", 17 | "e2e_delegated_staking_test.go", 18 | "e2e_test.go", 19 | "helpers_test.go", 20 | ], 21 | embed = [":endtoend"], 22 | tags = [ 23 | "exclusive-if-local", 24 | "manual", 25 | ], 26 | deps = [ 27 | "//chain-abstraction:protocol", 28 | "//chain-abstraction/sol-implementation", 29 | "//challenge-manager", 30 | "//challenge-manager/types", 31 | "//runtime", 32 | "//solgen/go/bridgegen", 33 | "//solgen/go/challengeV2gen", 34 | "//solgen/go/mocksgen", 35 | "//solgen/go/rollupgen", 36 | "//testing", 37 | "//testing/endtoend/backend", 38 | "//testing/mocks/state-provider", 39 | "//testing/setup:setup_lib", 40 | "@com_github_ethereum_go_ethereum//:go-ethereum", 41 | "@com_github_ethereum_go_ethereum//accounts/abi", 42 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 43 | "@com_github_ethereum_go_ethereum//common", 44 | "@com_github_ethereum_go_ethereum//core/types", 45 | "@com_github_stretchr_testify//require", 46 | "@org_golang_x_sync//errgroup", 47 | ], 48 | ) 49 | 50 | go_library( 51 | name = "endtoend", 52 | testonly = 1, 53 | srcs = [ 54 | "expectations.go", 55 | "headers.go", 56 | ], 57 | importpath = "github.com/offchainlabs/bold/testing/endtoend", 58 | visibility = ["//visibility:public"], 59 | deps = [ 60 | "//chain-abstraction:protocol", 61 | "//runtime", 62 | "//solgen/go/challengeV2gen", 63 | "//solgen/go/rollupgen", 64 | "//testing/endtoend/backend", 65 | "//testing/setup:setup_lib", 66 | "//util/stopwaiter", 67 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 68 | "@com_github_ethereum_go_ethereum//common", 69 | "@com_github_ethereum_go_ethereum//core/types", 70 | "@com_github_stretchr_testify//require", 71 | ], 72 | ) 73 | -------------------------------------------------------------------------------- /testing/endtoend/README.md: -------------------------------------------------------------------------------- 1 | # End to End Testing 2 | 3 | ## Host Requirements 4 | 5 | The following tools are required to be on the host system and are not provided by bazel. 6 | 7 | - [foundry](https://github.com/foundry-rs/foundry) -- Specifically the `anvil` tool. 8 | 9 | ## Running Tests 10 | 11 | - Running with bazel is recommended, but not required. 12 | - The ANVIL environment variable is required or the test will attempt to guess where anvil is installed. 13 | 14 | Note: The test which depend on running anvil should be run exclusively. Bazel targets that depend 15 | on anvil should include "exclusive-if-local" or "exclusive" tags. 16 | 17 | **Running with Bazel** 18 | 19 | ``` 20 | bazel test //testing/endtoend:endtoend_suite --test_env=ANVIL=$(which anvil) --test_output=all 21 | ``` 22 | 23 | **Running with Go** 24 | 25 | ``` 26 | ANVIL=$(which anvil) go test ./testing/endtoend/... 27 | ``` 28 | -------------------------------------------------------------------------------- /testing/endtoend/backend/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "backend", 5 | testonly = 1, 6 | srcs = [ 7 | "anvil_local.go", 8 | "anvil_priv_keys.go", 9 | "backend.go", 10 | "simulated.go", 11 | ], 12 | importpath = "github.com/offchainlabs/bold/testing/endtoend/backend", 13 | visibility = ["//testing/endtoend:__subpackages__"], 14 | deps = [ 15 | "//chain-abstraction:protocol", 16 | "//solgen/go/mocksgen", 17 | "//solgen/go/rollupgen", 18 | "//testing", 19 | "//testing/setup:setup_lib", 20 | "//util", 21 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 22 | "@com_github_ethereum_go_ethereum//common", 23 | "@com_github_ethereum_go_ethereum//common/hexutil", 24 | "@com_github_ethereum_go_ethereum//core/types", 25 | "@com_github_ethereum_go_ethereum//crypto", 26 | "@com_github_ethereum_go_ethereum//ethclient", 27 | "@com_github_ethereum_go_ethereum//rpc", 28 | "@com_github_pkg_errors//:errors", 29 | ], 30 | ) 31 | 32 | go_test( 33 | name = "backend_test", 34 | size = "small", 35 | srcs = ["anvil_local_test.go"], 36 | embed = [":backend"], 37 | tags = [ 38 | "exclusive-if-local", 39 | "manual", 40 | ], 41 | visibility = ["//testing/endtoend:__subpackages__"], 42 | ) 43 | -------------------------------------------------------------------------------- /testing/endtoend/backend/anvil_local_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package backend 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | ) 11 | 12 | func TestLocalAnvilLoadAccounts(t *testing.T) { 13 | a, err := NewAnvilLocal(context.Background()) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | if err := a.loadAccounts(); err != nil { 18 | t.Fatal(err) 19 | } 20 | if len(a.accounts) == 0 { 21 | t.Error("No accounts generated") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /testing/endtoend/backend/anvil_priv_keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package backend 6 | 7 | // From: https://github.com/foundry-rs/foundry/tree/master/crates/anvil 8 | var anvilPrivKeyHexStrings = []string{ 9 | "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 10 | "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", 11 | "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", 12 | "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", 13 | "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", 14 | "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", 15 | "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", 16 | "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", 17 | "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", 18 | "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", 19 | "0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897", 20 | "0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82", 21 | "0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1", 22 | "0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd", 23 | "0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa", 24 | "0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61", 25 | "0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0", 26 | "0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd", 27 | "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", 28 | "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e", 29 | "0xeaa861a9a01391ed3d587d8a5a84ca56ee277629a8b02c22093a419bf240e65d", 30 | "0xc511b2aa70776d4ff1d376e8537903dae36896132c90b91d52c1dfbae267cd8b", 31 | "0x224b7eb7449992aac96d631d9677f7bf5888245eef6d6eeda31e62d2f29a83e4", 32 | "0x4624e0802698b9769f5bdb260a3777fbd4941ad2901f5966b854f953497eec1b", 33 | "0x375ad145df13ed97f8ca8e27bb21ebf2a3819e9e0a06509a812db377e533def7", 34 | "0x18743e59419b01d1d846d97ea070b5a3368a3e7f6f0242cf497e1baac6972427", 35 | "0xe383b226df7c8282489889170b0f68f66af6459261f4833a781acd0804fafe7a", 36 | "0xf3a6b71b94f5cd909fb2dbb287da47badaa6d8bcdc45d595e2884835d8749001", 37 | "0x4e249d317253b9641e477aba8dd5d8f1f7cf5250a5acadd1229693e262720a19", 38 | "0x233c86e887ac435d7f7dc64979d7758d69320906a0d340d2b6518b0fd20aa998", 39 | "0x85a74ca11529e215137ccffd9c95b2c72c5fb0295c973eb21032e823329b3d2d", 40 | "0xac8698a440d33b866b6ffe8775621ce1a4e6ebd04ab7980deb97b3d997fc64fb", 41 | "0xf076539fbce50f0513c488f32bf81524d30ca7a29f400d68378cc5b1b17bc8f2", 42 | "0x5544b8b2010dbdbef382d254802d856629156aba578f453a76af01b81a80104e", 43 | "0x47003709a0a9a4431899d4e014c1fd01c5aad19e873172538a02370a119bae11", 44 | "0x9644b39377553a920edc79a275f45fa5399cbcf030972f771d0bca8097f9aad3", 45 | "0xcaa7b4a2d30d1d565716199f068f69ba5df586cf32ce396744858924fdf827f0", 46 | "0xfc5a028670e1b6381ea876dd444d3faaee96cffae6db8d93ca6141130259247c", 47 | "0x5b92c5fe82d4fabee0bc6d95b4b8a3f9680a0ed7801f631035528f32c9eb2ad5", 48 | "0xb68ac4aa2137dd31fd0732436d8e59e959bb62b4db2e6107b15f594caf0f405f", 49 | } 50 | -------------------------------------------------------------------------------- /testing/endtoend/backend/backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package backend 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 | "github.com/ethereum/go-ethereum/common" 12 | 13 | protocol "github.com/offchainlabs/bold/chain-abstraction" 14 | challenge_testing "github.com/offchainlabs/bold/testing" 15 | "github.com/offchainlabs/bold/testing/setup" 16 | ) 17 | 18 | type Backend interface { 19 | // Start sets up the backend and waits until the process is in a ready state. 20 | Start(ctx context.Context) error 21 | // Client returns the backend's client. 22 | Client() protocol.ChainBackend 23 | // Accounts managed by the backend. 24 | Accounts() []*bind.TransactOpts 25 | // DeployRollup contract, if not already deployed. 26 | DeployRollup(ctx context.Context, opts ...challenge_testing.Opt) (*setup.RollupAddresses, error) 27 | // Contract addresses relevant to the challenge protocol. 28 | ContractAddresses() *setup.RollupAddresses 29 | // Commit a tx to the backend, if possible (simulated backend requires this) 30 | Commit() common.Hash 31 | } 32 | -------------------------------------------------------------------------------- /testing/endtoend/backend/simulated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package backend 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 12 | "github.com/ethereum/go-ethereum/common" 13 | 14 | protocol "github.com/offchainlabs/bold/chain-abstraction" 15 | challenge_testing "github.com/offchainlabs/bold/testing" 16 | "github.com/offchainlabs/bold/testing/setup" 17 | ) 18 | 19 | var _ Backend = &LocalSimulatedBackend{} 20 | 21 | type LocalSimulatedBackend struct { 22 | blockTime time.Duration 23 | setup *setup.ChainSetup 24 | } 25 | 26 | func (l *LocalSimulatedBackend) Start(ctx context.Context) error { 27 | // Advance blocks in the background. 28 | go func() { 29 | ticker := time.NewTicker(l.blockTime) 30 | defer ticker.Stop() 31 | for { 32 | select { 33 | case <-ticker.C: 34 | l.setup.Backend.Commit() 35 | case <-ctx.Done(): 36 | return 37 | } 38 | } 39 | }() 40 | return nil 41 | } 42 | 43 | func (l *LocalSimulatedBackend) Stop(ctx context.Context) error { 44 | return nil 45 | } 46 | 47 | func (l *LocalSimulatedBackend) Client() protocol.ChainBackend { 48 | return l.setup.Backend 49 | } 50 | 51 | func (l *LocalSimulatedBackend) Commit() common.Hash { 52 | return l.setup.Backend.Commit() 53 | } 54 | 55 | func (l *LocalSimulatedBackend) Accounts() []*bind.TransactOpts { 56 | accs := make([]*bind.TransactOpts, len(l.setup.Accounts)) 57 | for i := 0; i < len(l.setup.Accounts); i++ { 58 | accs[i] = l.setup.Accounts[i].TxOpts 59 | } 60 | return accs 61 | } 62 | 63 | func (l *LocalSimulatedBackend) ContractAddresses() *setup.RollupAddresses { 64 | return l.setup.Addrs 65 | } 66 | 67 | func (l *LocalSimulatedBackend) DeployRollup(_ context.Context, _ ...challenge_testing.Opt) (*setup.RollupAddresses, error) { 68 | // No-op, as the sim backend deploys the rollup on initialization. 69 | return l.setup.Addrs, nil 70 | } 71 | 72 | func NewSimulated(blockTime time.Duration, opts ...setup.Opt) (*LocalSimulatedBackend, error) { 73 | setup, err := setup.ChainsWithEdgeChallengeManager(opts...) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return &LocalSimulatedBackend{blockTime: blockTime, setup: setup}, nil 78 | } 79 | -------------------------------------------------------------------------------- /testing/endtoend/headers.go: -------------------------------------------------------------------------------- 1 | package endtoend 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethereum/go-ethereum/core/types" 7 | 8 | "github.com/offchainlabs/bold/testing/endtoend/backend" 9 | "github.com/offchainlabs/bold/util/stopwaiter" 10 | ) 11 | 12 | type simpleHeaderProvider struct { 13 | stopwaiter.StopWaiter 14 | b backend.Backend 15 | chs []chan<- *types.Header 16 | } 17 | 18 | func (s *simpleHeaderProvider) Start(ctx context.Context) { 19 | s.StopWaiter.Start(ctx, s) 20 | s.LaunchThread(s.listenToHeaders) 21 | } 22 | 23 | func (s *simpleHeaderProvider) listenToHeaders(ctx context.Context) { 24 | ch := make(chan *types.Header, 100) 25 | sub, err := s.b.Client().SubscribeNewHead(ctx, ch) 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer sub.Unsubscribe() 30 | for { 31 | select { 32 | case header := <-ch: 33 | for _, sch := range s.chs { 34 | sch <- header 35 | } 36 | case <-sub.Err(): 37 | case <-ctx.Done(): 38 | return 39 | } 40 | } 41 | } 42 | 43 | func (s *simpleHeaderProvider) StopAndWait() { 44 | s.StopWaiter.StopAndWait() 45 | } 46 | 47 | func (s *simpleHeaderProvider) Subscribe(requireBlockNrUpdates bool) (<-chan *types.Header, func()) { 48 | ch := make(chan *types.Header, 100) 49 | s.chs = append(s.chs, ch) 50 | return ch, func() { 51 | s.removeChannel(ch) 52 | close(ch) 53 | } 54 | } 55 | 56 | func (s *simpleHeaderProvider) removeChannel(ch chan<- *types.Header) { 57 | for i, sch := range s.chs { 58 | if sch == ch { 59 | s.chs = append(s.chs[:i], s.chs[i+1:]...) 60 | return 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testing/integration/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_test") 2 | 3 | go_test( 4 | name = "integration_test", 5 | size = "small", 6 | srcs = ["prefixproofs_test.go"], 7 | deps = [ 8 | "//chain-abstraction:protocol", 9 | "//containers/option", 10 | "//layer2-state-provider", 11 | "//solgen/go/mocksgen", 12 | "//state-commitments/history", 13 | "//state-commitments/prefix-proofs", 14 | "//testing/mocks/state-provider", 15 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 16 | "@com_github_ethereum_go_ethereum//common", 17 | "@com_github_ethereum_go_ethereum//core/types", 18 | "@com_github_ethereum_go_ethereum//crypto", 19 | "@com_github_ethereum_go_ethereum//ethclient/simulated", 20 | "@com_github_stretchr_testify//require", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /testing/mocks/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "mocks", 5 | testonly = 1, 6 | srcs = ["mocks.go"], 7 | importpath = "github.com/offchainlabs/bold/testing/mocks", 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "//api/db", 11 | "//chain-abstraction:protocol", 12 | "//containers/option", 13 | "//layer2-state-provider", 14 | "//solgen/go/rollupgen", 15 | "//state-commitments/history", 16 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 17 | "@com_github_ethereum_go_ethereum//common", 18 | "@com_github_ethereum_go_ethereum//core/types", 19 | "@com_github_ethereum_go_ethereum//rpc", 20 | "@com_github_stretchr_testify//mock", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /testing/mocks/state-provider/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "state-provider", 5 | testonly = 1, 6 | srcs = [ 7 | "execution_engine.go", 8 | "history_provider.go", 9 | "layer2_state_provider.go", 10 | ], 11 | importpath = "github.com/offchainlabs/bold/testing/mocks/state-provider", 12 | visibility = ["//visibility:public"], 13 | deps = [ 14 | "//api/db", 15 | "//chain-abstraction:protocol", 16 | "//containers/option", 17 | "//layer2-state-provider", 18 | "//state-commitments/history", 19 | "//testing", 20 | "//testing/casttest", 21 | "@com_github_ccoveille_go_safecast//:go-safecast", 22 | "@com_github_ethereum_go_ethereum//accounts/abi", 23 | "@com_github_ethereum_go_ethereum//common", 24 | ], 25 | ) 26 | 27 | go_test( 28 | name = "state-provider_test", 29 | size = "small", 30 | srcs = [ 31 | "execution_engine_test.go", 32 | "history_provider_test.go", 33 | "layer2_state_provider_test.go", 34 | ], 35 | embed = [":state-provider"], 36 | deps = [ 37 | "//chain-abstraction:protocol", 38 | "//containers/option", 39 | "//layer2-state-provider", 40 | "//testing", 41 | "@com_github_ethereum_go_ethereum//common", 42 | "@com_github_ethereum_go_ethereum//crypto", 43 | "@com_github_stretchr_testify//require", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /testing/mocks/state-provider/execution_engine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | package stateprovider 5 | 6 | import ( 7 | "encoding/binary" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | 12 | protocol "github.com/offchainlabs/bold/chain-abstraction" 13 | ) 14 | 15 | type Machine interface { 16 | CurrentStepNum() uint64 17 | GetExecutionState() *protocol.ExecutionState 18 | Hash() common.Hash 19 | IsStopped() bool 20 | Clone() Machine 21 | Step(steps uint64) error 22 | OneStepProof() ([]byte, error) 23 | } 24 | 25 | type SimpleMachine struct { 26 | step uint64 27 | state *protocol.ExecutionState 28 | maxBatchesRead *big.Int 29 | } 30 | 31 | func NewSimpleMachine(startingState *protocol.ExecutionState, maxBatchesRead *big.Int) *SimpleMachine { 32 | stateCopy := *startingState 33 | if maxBatchesRead != nil { 34 | maxBatchesRead = new(big.Int).Set(maxBatchesRead) 35 | } 36 | return &SimpleMachine{ 37 | step: 0, 38 | state: &stateCopy, 39 | maxBatchesRead: maxBatchesRead, 40 | } 41 | } 42 | 43 | func (m *SimpleMachine) CurrentStepNum() uint64 { 44 | return m.step 45 | } 46 | 47 | func (m *SimpleMachine) GetExecutionState() *protocol.ExecutionState { 48 | stateCopy := *m.state 49 | return &stateCopy 50 | } 51 | 52 | func (m *SimpleMachine) Hash() common.Hash { 53 | return m.GetExecutionState().GlobalState.Hash() 54 | } 55 | 56 | func (m *SimpleMachine) IsStopped() bool { 57 | if m.step == 0 && m.state.MachineStatus == protocol.MachineStatusFinished { 58 | if m.maxBatchesRead == nil || new(big.Int).SetUint64(m.state.GlobalState.Batch).Cmp(m.maxBatchesRead) < 0 { 59 | // Kickstart the machine at step 0 60 | return false 61 | } 62 | } 63 | return m.state.MachineStatus != protocol.MachineStatusRunning 64 | } 65 | 66 | func (m *SimpleMachine) Clone() Machine { 67 | newMachine := *m 68 | stateCopy := *m.state 69 | newMachine.state = &stateCopy 70 | return &newMachine 71 | } 72 | 73 | // End the batch after 2000 steps. This results in 11 blocks for an honest validator. 74 | // This constant must be synchronized with the one in execution/engine.go 75 | const stepsPerBatch = 2000 76 | 77 | func (m *SimpleMachine) Step(steps uint64) error { 78 | for ; steps > 0; steps-- { 79 | if m.IsStopped() { 80 | m.step += steps 81 | return nil 82 | } 83 | m.state.MachineStatus = protocol.MachineStatusRunning 84 | m.step++ 85 | m.state.GlobalState.PosInBatch++ 86 | if m.state.GlobalState.PosInBatch%stepsPerBatch == 0 { 87 | m.state.GlobalState.Batch++ 88 | m.state.GlobalState.PosInBatch = 0 89 | m.state.MachineStatus = protocol.MachineStatusFinished 90 | } 91 | if m.Hash()[0] == 0 { 92 | m.state.MachineStatus = protocol.MachineStatusFinished 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func (m *SimpleMachine) OneStepProof() ([]byte, error) { 99 | proof := make([]byte, 16) 100 | binary.BigEndian.PutUint64(proof[:8], m.state.GlobalState.Batch) 101 | binary.BigEndian.PutUint64(proof[8:], m.state.GlobalState.PosInBatch) 102 | return proof, nil 103 | } 104 | 105 | // VerifySimpleMachineOneStepProof checks the claimed post-state root results from executing 106 | // a specified pre-state hash. 107 | func VerifySimpleMachineOneStepProof(beforeStateRoot common.Hash, claimedAfterStateRoot common.Hash, step uint64, maxBatchesRead *big.Int, proof []byte) bool { 108 | if len(proof) != 16 { 109 | return false 110 | } 111 | batch := binary.BigEndian.Uint64(proof[:8]) 112 | posInBatch := binary.BigEndian.Uint64(proof[8:]) 113 | state := &protocol.ExecutionState{ 114 | GlobalState: protocol.GoGlobalState{ 115 | Batch: batch, 116 | PosInBatch: posInBatch, 117 | }, 118 | MachineStatus: protocol.MachineStatusRunning, 119 | } 120 | mach := NewSimpleMachine(state, maxBatchesRead) 121 | mach.step = step 122 | if step == 0 || mach.Hash()[0] == 0 { 123 | mach.state.MachineStatus = protocol.MachineStatusFinished 124 | } 125 | if mach.Hash() != beforeStateRoot { 126 | return false 127 | } 128 | err := mach.Step(1) 129 | if err != nil { 130 | return false 131 | } 132 | return mach.Hash() == claimedAfterStateRoot 133 | } 134 | -------------------------------------------------------------------------------- /testing/mocks/state-provider/execution_engine_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | package stateprovider 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/ethereum/go-ethereum/common" 12 | 13 | protocol "github.com/offchainlabs/bold/chain-abstraction" 14 | ) 15 | 16 | func TestExecutionEngine(t *testing.T) { 17 | startState := &protocol.ExecutionState{ 18 | GlobalState: protocol.GoGlobalState{}, 19 | MachineStatus: protocol.MachineStatusFinished, 20 | } 21 | machine := NewSimpleMachine(startState, nil) 22 | for i := uint64(0); i < 100; i++ { 23 | require.Equal(t, i, machine.CurrentStepNum()) 24 | 25 | thisHash := machine.Hash() 26 | stopped := machine.IsStopped() 27 | osp, err := machine.OneStepProof() 28 | require.NoError(t, err) 29 | err = machine.Step(1) 30 | require.NoError(t, err) 31 | nextHash := machine.Hash() 32 | 33 | require.Equal(t, thisHash == nextHash, stopped) 34 | 35 | if !VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, osp) { 36 | t.Fatal(i) 37 | } 38 | 39 | // verify that bad proofs get rejected 40 | fakeProof := append([]byte{}, osp...) 41 | fakeProof = append(fakeProof, 0) 42 | if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { 43 | t.Fatal(i) 44 | } 45 | 46 | fakeProof = append([]byte{}, osp...) 47 | fakeProof[0] ^= 1 48 | if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { 49 | t.Fatal(i) 50 | } 51 | 52 | fakeProof = append([]byte{}, osp...) 53 | fakeProof = fakeProof[len(fakeProof)-1:] 54 | if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { 55 | t.Fatal(i) 56 | } 57 | 58 | if thisHash != nextHash && VerifySimpleMachineOneStepProof(thisHash, thisHash, i, nil, osp) { 59 | t.Fatal(i) 60 | } 61 | if VerifySimpleMachineOneStepProof(thisHash, common.Hash{}, i, nil, osp) { 62 | t.Fatal(i) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /testing/mocks/state-provider/history_provider.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package stateprovider 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | 12 | protocol "github.com/offchainlabs/bold/chain-abstraction" 13 | "github.com/offchainlabs/bold/containers/option" 14 | l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" 15 | ) 16 | 17 | // Collects a list of machine hashes at a message number based on some configuration parameters. 18 | func (s *L2StateBackend) CollectMachineHashes( 19 | ctx context.Context, cfg *l2stateprovider.HashCollectorConfig, 20 | ) ([]common.Hash, error) { 21 | // We step through the machine in our desired increments, and gather the 22 | // machine hashes along the way for the history commitment. 23 | machine, err := s.machineAtBlock(ctx, uint64(cfg.BlockChallengeHeight)) 24 | if err != nil { 25 | return nil, err 26 | } 27 | // Advance the machine to the start index. 28 | if machErr := machine.Step(uint64(cfg.MachineStartIndex)); machErr != nil { 29 | return nil, machErr 30 | } 31 | hashes := make([]common.Hash, 0, cfg.NumDesiredHashes) 32 | hashes = append(hashes, s.getMachineHash(machine, uint64(cfg.BlockChallengeHeight))) 33 | for i := uint64(1); i < cfg.NumDesiredHashes; i++ { 34 | if stepErr := machine.Step(uint64(cfg.StepSize)); stepErr != nil { 35 | return nil, stepErr 36 | } 37 | hashes = append(hashes, s.getMachineHash(machine, uint64(cfg.BlockChallengeHeight))) 38 | } 39 | return hashes, nil 40 | } 41 | 42 | // CollectProof Collects osp of at a message number and OpcodeIndex . 43 | func (s *L2StateBackend) CollectProof( 44 | ctx context.Context, 45 | assertionMetadata *l2stateprovider.AssociatedAssertionMetadata, 46 | blockChallengeHeight l2stateprovider.Height, 47 | machineIndex l2stateprovider.OpcodeIndex, 48 | ) ([]byte, error) { 49 | machine, err := s.machineAtBlock(ctx, uint64(blockChallengeHeight)) 50 | if err != nil { 51 | return nil, err 52 | } 53 | err = machine.Step(uint64(machineIndex)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return machine.OneStepProof() 58 | } 59 | 60 | // Computes a block history commitment from a start L2 message to an end L2 message index 61 | // and up to a required batch index. The hashes used for this commitment are the machine hashes 62 | // at each message number. 63 | func (s *L2StateBackend) L2MessageStatesUpTo( 64 | ctx context.Context, 65 | fromState protocol.GoGlobalState, 66 | batchLimit l2stateprovider.Batch, 67 | toHeight option.Option[l2stateprovider.Height], 68 | ) ([]common.Hash, error) { 69 | var to l2stateprovider.Height 70 | if !toHeight.IsNone() { 71 | to = toHeight.Unwrap() 72 | } else { 73 | blockChallengeLeafHeight := s.challengeLeafHeights[0] 74 | to = l2stateprovider.Height(blockChallengeLeafHeight) 75 | } 76 | return s.statesUpTo(uint64(fromState.PosInBatch), uint64(to), uint64(fromState.Batch), uint64(batchLimit)) 77 | } 78 | -------------------------------------------------------------------------------- /testing/mocks/state-provider/layer2_state_provider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | package stateprovider 5 | 6 | import ( 7 | "context" 8 | "crypto/rand" 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "math" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/require" 16 | 17 | "github.com/ethereum/go-ethereum/common" 18 | "github.com/ethereum/go-ethereum/crypto" 19 | 20 | protocol "github.com/offchainlabs/bold/chain-abstraction" 21 | l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" 22 | challenge_testing "github.com/offchainlabs/bold/testing" 23 | ) 24 | 25 | func mockMachineAtBlock(_ context.Context, block uint64) (Machine, error) { 26 | blockBytes := make([]uint8, 8) 27 | binary.BigEndian.PutUint64(blockBytes, block) 28 | startState := &protocol.ExecutionState{ 29 | GlobalState: protocol.GoGlobalState{ 30 | BlockHash: crypto.Keccak256Hash(blockBytes), 31 | }, 32 | MachineStatus: protocol.MachineStatusFinished, 33 | } 34 | return NewSimpleMachine(startState, nil), nil 35 | } 36 | func setupStates(t *testing.T, numStates, divergenceHeight uint64) ([]*protocol.ExecutionState, []common.Hash) { 37 | t.Helper() 38 | states := make([]*protocol.ExecutionState, numStates) 39 | roots := make([]common.Hash, numStates) 40 | for i := uint64(0); i < numStates; i++ { 41 | var blockHash common.Hash 42 | if divergenceHeight == 0 || i < divergenceHeight { 43 | blockHash = crypto.Keccak256Hash([]byte(fmt.Sprintf("%d", i))) 44 | } else { 45 | junkRoot := make([]byte, 32) 46 | _, err := rand.Read(junkRoot) 47 | require.NoError(t, err) 48 | blockHash = crypto.Keccak256Hash(junkRoot) 49 | } 50 | state := &protocol.ExecutionState{ 51 | GlobalState: protocol.GoGlobalState{ 52 | BlockHash: blockHash, 53 | Batch: 0, 54 | PosInBatch: i, 55 | }, 56 | MachineStatus: protocol.MachineStatusFinished, 57 | } 58 | if i+1 == numStates { 59 | state.GlobalState.Batch = 1 60 | state.GlobalState.PosInBatch = 0 61 | } 62 | states[i] = state 63 | roots[i] = protocol.ComputeSimpleMachineChallengeHash(state) 64 | } 65 | return states, roots 66 | } 67 | 68 | func newTestingMachine( 69 | assertionChainExecutionStates []*protocol.ExecutionState, 70 | opts ...Opt, 71 | ) (*L2StateBackend, error) { 72 | if len(assertionChainExecutionStates) == 0 { 73 | return nil, errors.New("must have execution states") 74 | } 75 | stateRoots := make([]common.Hash, len(assertionChainExecutionStates)) 76 | var lastBatch uint64 = math.MaxUint64 77 | var lastPosInBatch uint64 = math.MaxUint64 78 | for i := 0; i < len(stateRoots); i++ { 79 | state := assertionChainExecutionStates[i] 80 | if state.GlobalState.Batch == lastBatch && state.GlobalState.PosInBatch == lastPosInBatch { 81 | return nil, fmt.Errorf("execution states %v and %v have the same batch %v and position in batch %v", i-1, i, lastBatch, lastPosInBatch) 82 | } 83 | lastBatch = state.GlobalState.Batch 84 | lastPosInBatch = state.GlobalState.PosInBatch 85 | stateRoots[i] = protocol.ComputeSimpleMachineChallengeHash(state) 86 | } 87 | s := &L2StateBackend{ 88 | stateRoots: stateRoots, 89 | executionStates: assertionChainExecutionStates, 90 | machineAtBlock: func(context.Context, uint64) (Machine, error) { 91 | return nil, errors.New("state manager created with NewWithAssertionStates() cannot provide machines") 92 | }, 93 | numBigSteps: 1, 94 | challengeLeafHeights: []l2stateprovider.Height{ 95 | challenge_testing.LevelZeroBlockEdgeHeight, 96 | challenge_testing.LevelZeroBigStepEdgeHeight, 97 | challenge_testing.LevelZeroSmallStepEdgeHeight, 98 | }, 99 | } 100 | for _, o := range opts { 101 | o(s) 102 | } 103 | return s, nil 104 | } 105 | -------------------------------------------------------------------------------- /testing/rollup_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | package challenge_testing 6 | 7 | import ( 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | 12 | protocol "github.com/offchainlabs/bold/chain-abstraction" 13 | "github.com/offchainlabs/bold/solgen/go/rollupgen" 14 | ) 15 | 16 | const ( 17 | LevelZeroBlockEdgeHeight = 1 << 5 18 | LevelZeroBigStepEdgeHeight = 1 << 5 19 | LevelZeroSmallStepEdgeHeight = 1 << 5 20 | MaxDataSize = 117964 21 | ) 22 | 23 | type Opt func(c *rollupgen.Config) 24 | 25 | func WithNumBigStepLevels(num uint8) Opt { 26 | return func(c *rollupgen.Config) { 27 | c.NumBigStepLevel = num 28 | } 29 | } 30 | 31 | func WithLayerZeroHeights(h *protocol.LayerZeroHeights) Opt { 32 | return func(c *rollupgen.Config) { 33 | c.LayerZeroBlockEdgeHeight = new(big.Int).SetUint64(h.BlockChallengeHeight.Uint64()) 34 | c.LayerZeroBigStepEdgeHeight = new(big.Int).SetUint64(h.BigStepChallengeHeight.Uint64()) 35 | c.LayerZeroSmallStepEdgeHeight = new(big.Int).SetUint64(h.SmallStepChallengeHeight.Uint64()) 36 | } 37 | } 38 | 39 | func WithConfirmPeriodBlocks(num uint64) Opt { 40 | return func(c *rollupgen.Config) { 41 | c.ConfirmPeriodBlocks = num 42 | } 43 | } 44 | 45 | func WithChallengeGracePeriodBlocks(num uint64) Opt { 46 | return func(c *rollupgen.Config) { 47 | c.ChallengeGracePeriodBlocks = num 48 | } 49 | } 50 | 51 | func WithBaseStakeValue(num *big.Int) Opt { 52 | return func(c *rollupgen.Config) { 53 | c.BaseStake = num 54 | } 55 | } 56 | 57 | func WithChainConfig(cfg string) Opt { 58 | return func(c *rollupgen.Config) { 59 | c.ChainConfig = cfg 60 | } 61 | } 62 | 63 | func GenerateRollupConfig( 64 | prod bool, 65 | wasmModuleRoot common.Hash, 66 | rollupOwner common.Address, 67 | chainId *big.Int, 68 | loserStakeEscrow common.Address, 69 | miniStakeValues []*big.Int, 70 | stakeToken common.Address, 71 | genesisExecutionState rollupgen.AssertionState, 72 | genesisInboxCount *big.Int, 73 | anyTrustFastConfirmer common.Address, 74 | opts ...Opt, 75 | ) rollupgen.Config { 76 | var confirmPeriod uint64 77 | if prod { 78 | confirmPeriod = 45818 79 | } else { 80 | confirmPeriod = 25 81 | } 82 | 83 | var gracePeriod uint64 84 | if prod { 85 | gracePeriod = 14400 86 | } else { 87 | gracePeriod = 3 88 | } 89 | 90 | cfg := rollupgen.Config{ 91 | MiniStakeValues: miniStakeValues, 92 | ConfirmPeriodBlocks: confirmPeriod, 93 | StakeToken: stakeToken, 94 | BaseStake: big.NewInt(1), 95 | WasmModuleRoot: wasmModuleRoot, 96 | Owner: rollupOwner, 97 | LoserStakeEscrow: loserStakeEscrow, 98 | ChainId: chainId, 99 | ChainConfig: "{ 'config': 'Test config'}", 100 | MinimumAssertionPeriod: big.NewInt(75), 101 | ValidatorAfkBlocks: 201600, 102 | SequencerInboxMaxTimeVariation: rollupgen.ISequencerInboxMaxTimeVariation{ 103 | DelayBlocks: big.NewInt(60 * 60 * 24 / 15), 104 | FutureBlocks: big.NewInt(12), 105 | DelaySeconds: big.NewInt(60 * 60 * 24), 106 | FutureSeconds: big.NewInt(60 * 60), 107 | }, 108 | LayerZeroBlockEdgeHeight: big.NewInt(LevelZeroBlockEdgeHeight), 109 | LayerZeroBigStepEdgeHeight: big.NewInt(LevelZeroBigStepEdgeHeight), 110 | LayerZeroSmallStepEdgeHeight: big.NewInt(LevelZeroSmallStepEdgeHeight), 111 | GenesisAssertionState: genesisExecutionState, 112 | GenesisInboxCount: genesisInboxCount, 113 | AnyTrustFastConfirmer: anyTrustFastConfirmer, 114 | NumBigStepLevel: 1, 115 | ChallengeGracePeriodBlocks: gracePeriod, 116 | } 117 | for _, o := range opts { 118 | o(&cfg) 119 | } 120 | return cfg 121 | } 122 | -------------------------------------------------------------------------------- /testing/setup/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "setup_lib", 5 | testonly = 1, 6 | srcs = [ 7 | "rollup_stack.go", 8 | "simulated_backend_wrapper.go", 9 | ], 10 | importpath = "github.com/offchainlabs/bold/testing/setup", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//chain-abstraction:protocol", 14 | "//chain-abstraction/sol-implementation", 15 | "//layer2-state-provider", 16 | "//runtime", 17 | "//solgen/go/bridgegen", 18 | "//solgen/go/challengeV2gen", 19 | "//solgen/go/contractsgen", 20 | "//solgen/go/mocksgen", 21 | "//solgen/go/ospgen", 22 | "//solgen/go/proxiesgen", 23 | "//solgen/go/rollupgen", 24 | "//solgen/go/yulgen", 25 | "//testing", 26 | "//testing/mocks/state-provider", 27 | "@com_github_ethereum_go_ethereum//:go-ethereum", 28 | "@com_github_ethereum_go_ethereum//accounts/abi", 29 | "@com_github_ethereum_go_ethereum//accounts/abi/bind", 30 | "@com_github_ethereum_go_ethereum//common", 31 | "@com_github_ethereum_go_ethereum//core/types", 32 | "@com_github_ethereum_go_ethereum//crypto", 33 | "@com_github_ethereum_go_ethereum//ethclient/simulated", 34 | "@com_github_ethereum_go_ethereum//log", 35 | "@com_github_ethereum_go_ethereum//rpc", 36 | "@com_github_pkg_errors//:errors", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /testing/tx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package challenge_testing includes all non-production code used in BoLD. 6 | package challenge_testing 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "time" 12 | 13 | "github.com/pkg/errors" 14 | 15 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 16 | "github.com/ethereum/go-ethereum/common" 17 | "github.com/ethereum/go-ethereum/core/types" 18 | ) 19 | 20 | func TxSucceeded( 21 | ctx context.Context, 22 | tx *types.Transaction, 23 | addr common.Address, 24 | backend bind.DeployBackend, 25 | err error, 26 | ) error { 27 | if err != nil { 28 | return fmt.Errorf("error submitting tx: %w", err) 29 | } 30 | if waitErr := WaitForTx(ctx, backend, tx); waitErr != nil { 31 | return errors.Wrap(waitErr, "error waiting for tx to be mined") 32 | } 33 | receipt, err := backend.TransactionReceipt(ctx, tx.Hash()) 34 | if err != nil { 35 | return err 36 | } 37 | if receipt.Status != types.ReceiptStatusSuccessful { 38 | return errors.New("tx receipt not successful") 39 | } 40 | code, err := backend.CodeAt(ctx, addr, nil) 41 | if err != nil { 42 | return err 43 | } 44 | if len(code) == 0 { 45 | return errors.New("contract not deployed") 46 | } 47 | return nil 48 | } 49 | 50 | type committer interface { 51 | Commit() common.Hash 52 | } 53 | 54 | // WaitForTx to be mined. This method will trigger .Commit() on a simulated backend. 55 | func WaitForTx(ctx context.Context, be bind.DeployBackend, tx *types.Transaction) error { 56 | if simulated, ok := be.(committer); ok { 57 | simulated.Commit() 58 | } 59 | 60 | ctx, cancel := context.WithTimeout(ctx, time.Minute) 61 | defer cancel() 62 | 63 | _, err := bind.WaitMined(ctx, be, tx) 64 | 65 | return err 66 | } 67 | -------------------------------------------------------------------------------- /third_party/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffchainLabs/bold/301f479f4bb09a6ab77bab324a441e6e83cddf2b/third_party/BUILD.bazel -------------------------------------------------------------------------------- /third_party/com_github_datadog_zstd.patch: -------------------------------------------------------------------------------- 1 | diff --git a/BUILD.bazel b/BUILD.bazel 2 | new file mode 100644 3 | index 0000000..f15d38e 4 | --- /dev/null 5 | +++ b/BUILD.bazel 6 | @@ -0,0 +1,131 @@ 7 | +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 8 | +load("@rules_cc//cc:defs.bzl", "cc_library") 9 | + 10 | +cc_library( 11 | + name = "zstd_cc", 12 | + srcs = [ 13 | + "bitstream.h", 14 | + "clevels.h", 15 | + "compiler.h", 16 | + "cover.c", 17 | + "cover.h", 18 | + "cpu.h", 19 | + "debug.c", 20 | + "debug.h", 21 | + "divsufsort.c", 22 | + "divsufsort.h", 23 | + "entropy_common.c", 24 | + "error_private.c", 25 | + "error_private.h", 26 | + "fastcover.c", 27 | + "fse.h", 28 | + "fse_compress.c", 29 | + "fse_decompress.c", 30 | + "hist.c", 31 | + "hist.h", 32 | + "huf.h", 33 | + "huf_compress.c", 34 | + "huf_decompress.c", 35 | + "huf_decompress_amd64.S", 36 | + "mem.h", 37 | + "pool.c", 38 | + "pool.h", 39 | + "portability_macros.h", 40 | + "threading.c", 41 | + "threading.h", 42 | + "xxhash.c", 43 | + "xxhash.h", 44 | + "zbuff.h", 45 | + "zbuff_common.c", 46 | + "zbuff_compress.c", 47 | + "zbuff_decompress.c", 48 | + "zdict.c", 49 | + "zdict.h", 50 | + "zstd.h", 51 | + "zstd_common.c", 52 | + "zstd_compress.c", 53 | + "zstd_compress_internal.h", 54 | + "zstd_compress_literals.c", 55 | + "zstd_compress_literals.h", 56 | + "zstd_compress_sequences.c", 57 | + "zstd_compress_sequences.h", 58 | + "zstd_compress_superblock.c", 59 | + "zstd_compress_superblock.h", 60 | + "zstd_cwksp.h", 61 | + "zstd_ddict.c", 62 | + "zstd_ddict.h", 63 | + "zstd_decompress.c", 64 | + "zstd_decompress_block.c", 65 | + "zstd_decompress_block.h", 66 | + "zstd_decompress_internal.h", 67 | + "zstd_deps.h", 68 | + "zstd_double_fast.c", 69 | + "zstd_double_fast.h", 70 | + "zstd_errors.h", 71 | + "zstd_fast.c", 72 | + "zstd_fast.h", 73 | + "zstd_internal.h", 74 | + "zstd_lazy.c", 75 | + "zstd_lazy.h", 76 | + "zstd_ldm.c", 77 | + "zstd_ldm.h", 78 | + "zstd_ldm_geartab.h", 79 | + "zstd_legacy.h", 80 | + "zstd_opt.c", 81 | + "zstd_opt.h", 82 | + "zstd_trace.h", 83 | + "zstd_v01.c", 84 | + "zstd_v01.h", 85 | + "zstd_v02.c", 86 | + "zstd_v02.h", 87 | + "zstd_v03.c", 88 | + "zstd_v03.h", 89 | + "zstd_v04.c", 90 | + "zstd_v04.h", 91 | + "zstd_v05.c", 92 | + "zstd_v05.h", 93 | + "zstd_v06.c", 94 | + "zstd_v06.h", 95 | + "zstd_v07.c", 96 | + "zstd_v07.h", 97 | + "zstdmt_compress.c", 98 | + "zstdmt_compress.h", 99 | + ], 100 | + copts = ["-DZSTD_LEGACY_SUPPORT=4"], 101 | + 102 | +) 103 | +go_library( 104 | + name = "zstd", 105 | + srcs = [ 106 | + "errors.go", 107 | + "zstd.go", 108 | + "zstd_bulk.go", 109 | + "zstd_ctx.go", 110 | + "zstd_stream.go", 111 | + "zstd.h", 112 | + ], 113 | + cgo = True, 114 | + cdeps = [":zstd_cc"], 115 | + copts = ["-DZSTD_LEGACY_SUPPORT=4"], 116 | + importpath = "github.com/DataDog/zstd", 117 | + visibility = ["//visibility:public"], 118 | +) 119 | + 120 | +alias( 121 | + name = "go_default_library", 122 | + actual = ":zstd", 123 | + visibility = ["//visibility:public"], 124 | +) 125 | + 126 | +go_test( 127 | + name = "zstd_test", 128 | + srcs = [ 129 | + "errors_test.go", 130 | + "helpers_test.go", 131 | + "zstd_bullk_test.go", 132 | + "zstd_ctx_test.go", 133 | + "zstd_stream_test.go", 134 | + "zstd_test.go", 135 | + ], 136 | + embed = [":zstd"], 137 | +) 138 | diff --git a/WORKSPACE b/WORKSPACE 139 | new file mode 100644 140 | index 0000000..dea5b27 141 | --- /dev/null 142 | +++ b/WORKSPACE 143 | @@ -0,0 +1,2 @@ 144 | +# DO NOT EDIT: automatically generated WORKSPACE file for go_repository rule 145 | +workspace(name = "com_github_datadog_zstd") 146 | -- 147 | 2.34.1 148 | -------------------------------------------------------------------------------- /third_party/com_github_ethereum_go_ethereum_secp256k1.patch: -------------------------------------------------------------------------------- 1 | diff --color -ruN a/crypto/secp256k1/BUILD.bazel b/crypto/secp256k1/BUILD.bazel 2 | --- a/crypto/secp256k1/BUILD.bazel 2021-10-14 20:32:30.202922024 -0500 3 | +++ b/crypto/secp256k1/BUILD.bazel 2021-10-14 20:30:17.921027939 -0500 4 | @@ -11,10 +11,11 @@ 5 | "scalar_mult_nocgo.go", 6 | "secp256.go", 7 | ], 8 | + cdeps = ["//crypto/secp256k1/libsecp256k1:hdrs"], 9 | cgo = True, 10 | copts = [ 11 | - "-Icrypto/secp256k1/libsecp256k1", 12 | - "-Icrypto/secp256k1/libsecp256k1/src", 13 | + "-Iexternal/gazelle~~go_deps~com_github_ethereum_go_ethereum/crypto/secp256k1/libsecp256k1", 14 | + "-Iexternal/gazelle~~go_deps~com_github_ethereum_go_ethereum/crypto/secp256k1/libsecp256k1/src", 15 | ], 16 | importpath = "github.com/ethereum/go-ethereum/crypto/secp256k1", 17 | visibility = ["//visibility:public"], 18 | diff --color -ruN a/crypto/secp256k1/libsecp256k1/BUILD.bazel b/crypto/secp256k1/libsecp256k1/BUILD.bazel 19 | --- a/crypto/secp256k1/libsecp256k1/BUILD.bazel 1969-12-31 18:00:00.000000000 -0600 20 | +++ b/crypto/secp256k1/libsecp256k1/BUILD.bazel 2021-10-14 12:54:27.704265206 -0500 21 | @@ -0,0 +1,37 @@ 22 | +cc_library( 23 | + name = "hdrs", 24 | + hdrs = [ 25 | + "include/secp256k1.h", 26 | + "include/secp256k1_recovery.h", 27 | + "src/ecdsa.h", 28 | + "src/ecdsa_impl.h", 29 | + "src/eckey.h", 30 | + "src/eckey_impl.h", 31 | + "src/ecmult.h", 32 | + "src/ecmult_const.h", 33 | + "src/ecmult_const_impl.h", 34 | + "src/ecmult_gen.h", 35 | + "src/ecmult_gen_impl.h", 36 | + "src/ecmult_impl.h", 37 | + "src/field.h", 38 | + "src/field_5x52.h", 39 | + "src/field_5x52_impl.h", 40 | + "src/field_5x52_int128_impl.h", 41 | + "src/field_impl.h", 42 | + "src/group.h", 43 | + "src/group_impl.h", 44 | + "src/hash.h", 45 | + "src/hash_impl.h", 46 | + "src/modules/recovery/main_impl.h", 47 | + "src/num.h", 48 | + "src/num_impl.h", 49 | + "src/scalar.h", 50 | + "src/scalar_4x64.h", 51 | + "src/scalar_4x64_impl.h", 52 | + "src/scalar_impl.h", 53 | + "src/secp256k1.c", 54 | + "src/util.h", 55 | + ], 56 | + visibility = ["//visibility:public"], 57 | +) 58 | + 59 | -------------------------------------------------------------------------------- /time/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "time", 5 | srcs = ["time_reference.go"], 6 | importpath = "github.com/offchainlabs/bold/time", 7 | visibility = ["//visibility:public"], 8 | ) 9 | 10 | go_test( 11 | name = "time_test", 12 | size = "small", 13 | srcs = ["time_reference_test.go"], 14 | embed = [":time"], 15 | ) 16 | -------------------------------------------------------------------------------- /time/time_reference.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | 5 | // Package time defines abstractions for time-related operations in BoLD. 6 | package time 7 | 8 | import ( 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Reference interface { 14 | Get() time.Time 15 | Sleep(duration time.Duration) 16 | SleepUntil(wakeTime time.Time) 17 | NewTicker(duration time.Duration) GenericTimeTicker 18 | } 19 | 20 | type GenericTimeTicker interface { 21 | C() <-chan time.Time 22 | Stop() 23 | } 24 | 25 | type realTimeReference struct{} 26 | 27 | func NewRealTimeReference() Reference { 28 | return realTimeReference{} 29 | } 30 | 31 | func (realTimeReference) Get() time.Time { 32 | return time.Now() 33 | } 34 | 35 | func (realTimeReference) Sleep(duration time.Duration) { 36 | time.Sleep(duration) 37 | } 38 | 39 | func (realTimeReference) SleepUntil(wakeTime time.Time) { 40 | time.Sleep(time.Until(wakeTime)) 41 | } 42 | 43 | func (realTimeReference) NewTicker(duration time.Duration) GenericTimeTicker { 44 | return newRealTimeTicker(duration) 45 | } 46 | 47 | type realTimeTicker struct { 48 | ticker *time.Ticker 49 | } 50 | 51 | func newRealTimeTicker(duration time.Duration) *realTimeTicker { 52 | return &realTimeTicker{time.NewTicker(duration)} 53 | } 54 | 55 | func (ticker *realTimeTicker) C() <-chan time.Time { 56 | return ticker.ticker.C 57 | } 58 | 59 | func (ticker *realTimeTicker) Stop() { 60 | ticker.ticker.Stop() 61 | } 62 | 63 | type ArtificialTimeReference struct { 64 | mutex sync.RWMutex 65 | current time.Time 66 | changedChan chan struct{} // every time the time changes, this is closed and re-generated 67 | } 68 | 69 | func NewArtificialTimeReference() *ArtificialTimeReference { 70 | return &ArtificialTimeReference{ 71 | mutex: sync.RWMutex{}, 72 | current: time.Unix(0, 0), 73 | changedChan: make(chan struct{}), 74 | } 75 | } 76 | 77 | func (atr *ArtificialTimeReference) Get() time.Time { 78 | atr.mutex.RLock() 79 | defer atr.mutex.RUnlock() 80 | return atr.current 81 | } 82 | 83 | func (atr *ArtificialTimeReference) Sleep(duration time.Duration) { 84 | atr.SleepUntil(atr.Get().Add(duration)) 85 | } 86 | 87 | func (atr *ArtificialTimeReference) SleepUntil(wakeTime time.Time) { 88 | for { 89 | atr.mutex.RLock() 90 | current := atr.current 91 | changedChan := atr.changedChan 92 | atr.mutex.RUnlock() 93 | if !current.Before(wakeTime) { 94 | return 95 | } 96 | <-changedChan 97 | } 98 | } 99 | 100 | func (atr *ArtificialTimeReference) Set(newVal time.Time) { 101 | atr.mutex.Lock() 102 | defer atr.mutex.Unlock() 103 | atr.setLocked(newVal) 104 | } 105 | 106 | func (atr *ArtificialTimeReference) setLocked(newVal time.Time) { 107 | if newVal.Before(atr.current) { 108 | return 109 | } 110 | changed := newVal.After(atr.current) 111 | atr.current = newVal 112 | if changed { 113 | close(atr.changedChan) 114 | atr.changedChan = make(chan struct{}) 115 | } 116 | } 117 | 118 | func (atr *ArtificialTimeReference) Add(delta time.Duration) { 119 | atr.mutex.Lock() 120 | defer atr.mutex.Unlock() 121 | atr.setLocked(atr.current.Add(delta)) 122 | } 123 | 124 | func (atr *ArtificialTimeReference) NewTicker(interval time.Duration) GenericTimeTicker { 125 | ticker := &artificialTicker{ 126 | timeRef: atr, 127 | c: make(chan time.Time), 128 | interval: interval, 129 | next: atr.Get().Add(interval), 130 | stoppedChan: make(chan struct{}), 131 | stopped: false, 132 | } 133 | go func() { 134 | defer close(ticker.c) 135 | for { 136 | ticker.timeRef.mutex.RLock() 137 | current := atr.current 138 | changedChan := atr.changedChan 139 | ticker.timeRef.mutex.RUnlock() 140 | if current.Before(ticker.next) { 141 | select { 142 | case <-changedChan: 143 | case <-ticker.stoppedChan: 144 | return 145 | } 146 | } else { 147 | select { 148 | case ticker.c <- current: 149 | ticker.next = ticker.timeRef.Get().Add(ticker.interval) 150 | case <-ticker.stoppedChan: 151 | return 152 | } 153 | } 154 | } 155 | }() 156 | return ticker 157 | } 158 | 159 | type artificialTicker struct { 160 | timeRef *ArtificialTimeReference 161 | c chan time.Time 162 | interval time.Duration 163 | next time.Time 164 | closeMutex sync.Mutex 165 | stoppedChan chan struct{} 166 | stopped bool 167 | } 168 | 169 | func (ticker *artificialTicker) C() <-chan time.Time { 170 | return ticker.c 171 | } 172 | 173 | func (ticker *artificialTicker) Stop() { 174 | ticker.closeMutex.Lock() 175 | defer ticker.closeMutex.Unlock() 176 | if !ticker.stopped { 177 | ticker.stopped = true 178 | close(ticker.stoppedChan) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /time/time_reference_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024, Offchain Labs, Inc. 2 | // For license information, see: 3 | // https://github.com/offchainlabs/bold/blob/main/LICENSE.md 4 | package time 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var ( 12 | _ = Reference(&realTimeReference{}) 13 | _ = Reference(&ArtificialTimeReference{}) 14 | ) 15 | 16 | func TestRealTimeReference(t *testing.T) { 17 | rtRef := NewRealTimeReference() 18 | now := rtRef.Get() 19 | time.Sleep(time.Millisecond) 20 | newTime := rtRef.Get() 21 | if newTime.Before(now) || newTime.Equal(now) { 22 | t.Errorf("Time did not advance as expected") 23 | } 24 | } 25 | 26 | func TestRealTimeReference_SleepUntil(t *testing.T) { 27 | rtRef := NewRealTimeReference() 28 | wakeTime := rtRef.Get().Add(time.Millisecond * 10) 29 | rtRef.SleepUntil(wakeTime) 30 | now := rtRef.Get() 31 | if now.Before(wakeTime) { 32 | t.Errorf("SleepUntil did not sleep until the correct time") 33 | } 34 | } 35 | 36 | func TestRealTimeReference_NewTicker(t *testing.T) { 37 | rtRef := NewRealTimeReference() 38 | ticker := rtRef.NewTicker(time.Millisecond * 10) 39 | time.Sleep(time.Millisecond * 15) 40 | select { 41 | case <-ticker.C(): 42 | // successfully ticked 43 | default: 44 | t.Errorf("Ticker did not tick as expected") 45 | } 46 | ticker.Stop() 47 | } 48 | 49 | func TestArtificialTimeReference_GetSet(t *testing.T) { 50 | atRef := NewArtificialTimeReference() 51 | time1 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 52 | atRef.Set(time1) 53 | time2 := atRef.Get() 54 | if !time1.Equal(time2) { 55 | t.Errorf("Did not get/set time as expected") 56 | } 57 | } 58 | 59 | func TestArtificialTimeReference_SleepUntil(t *testing.T) { 60 | atRef := NewArtificialTimeReference() 61 | sleepUntil := time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) 62 | go func() { 63 | time.Sleep(time.Millisecond * 10) 64 | atRef.Set(sleepUntil) 65 | }() 66 | atRef.SleepUntil(sleepUntil) 67 | if atRef.Get().Before(sleepUntil) { 68 | t.Errorf("SleepUntil did not sleep as expected") 69 | } 70 | } 71 | 72 | func TestRealTimeReference_Sleep(t *testing.T) { 73 | rtRef := NewRealTimeReference() 74 | start := rtRef.Get() 75 | sleepDuration := time.Millisecond * 10 76 | rtRef.Sleep(sleepDuration) 77 | end := rtRef.Get() 78 | if end.Sub(start) < sleepDuration { 79 | t.Errorf("Sleep did not last for the expected duration") 80 | } 81 | } 82 | 83 | func TestArtificialTimeReference_Sleep(t *testing.T) { 84 | atRef := NewArtificialTimeReference() 85 | start := atRef.Get() 86 | sleepDuration := time.Minute 87 | go func() { 88 | time.Sleep(time.Millisecond * 10) 89 | atRef.Add(sleepDuration) 90 | }() 91 | atRef.Sleep(sleepDuration) 92 | end := atRef.Get() 93 | if end.Sub(start) < sleepDuration { 94 | t.Errorf("Sleep did not last for the expected duration") 95 | } 96 | } 97 | 98 | func TestArtificialTicker(t *testing.T) { 99 | atRef := NewArtificialTimeReference() 100 | interval := time.Minute 101 | ticker := atRef.NewTicker(interval) 102 | go func() { 103 | time.Sleep(time.Millisecond * 10) 104 | atRef.Add(interval) 105 | }() 106 | select { 107 | case <-ticker.C(): 108 | // Ticker ticked 109 | case <-time.After(time.Second): 110 | t.Errorf("Ticker did not tick as expected") 111 | } 112 | ticker.Stop() 113 | } 114 | -------------------------------------------------------------------------------- /util/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "util", 5 | srcs = ["backend.go"], 6 | importpath = "github.com/offchainlabs/bold/util", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//chain-abstraction:protocol", 10 | "@com_github_ethereum_go_ethereum//ethclient", 11 | "@com_github_ethereum_go_ethereum//rpc", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /util/backend.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math/big" 7 | 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/ethereum/go-ethereum/rpc" 10 | 11 | protocol "github.com/offchainlabs/bold/chain-abstraction" 12 | ) 13 | 14 | var ( 15 | _ protocol.ChainBackend = &BackendWrapper{ 16 | desiredBlockNum: rpc.LatestBlockNumber, 17 | } 18 | ) 19 | 20 | type ethClient = ethclient.Client 21 | type BackendWrapper struct { 22 | *ethClient 23 | desiredBlockNum rpc.BlockNumber 24 | } 25 | 26 | func NewBackendWrapper(client *ethclient.Client, desiredBlockNum rpc.BlockNumber) *BackendWrapper { 27 | return &BackendWrapper{client, desiredBlockNum} 28 | } 29 | 30 | func (b BackendWrapper) HeaderU64(ctx context.Context) (uint64, error) { 31 | header, err := b.HeaderByNumber(ctx, big.NewInt(int64(b.desiredBlockNum))) 32 | if err != nil { 33 | return 0, err 34 | } 35 | if !header.Number.IsUint64() { 36 | return 0, errors.New("block number is not uint64") 37 | } 38 | return header.Number.Uint64(), nil 39 | } 40 | -------------------------------------------------------------------------------- /util/stopwaiter/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "stopwaiter", 5 | srcs = ["stopwaiter.go"], 6 | importpath = "github.com/offchainlabs/bold/util/stopwaiter", 7 | visibility = ["//visibility:public"], 8 | deps = ["@com_github_ethereum_go_ethereum//log"], 9 | ) 10 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------