diff --git a/.claude/commands/bridge-out.md b/.claude/commands/bridge-out.md index abb60388c..743a35685 100644 --- a/.claude/commands/bridge-out.md +++ b/.claude/commands/bridge-out.md @@ -25,11 +25,11 @@ Initiate Bridge Out via the `bridge-out` binary (payInvoice quote -> swap initia 3. Check if the `bridge-out` binary exists at `./bin/bridge-out`. If not, run the install script to download it: ```bash - .claude/commands/install-bitvm2.sh install + .claude/commands/install-bitvm.sh install ``` To upgrade to the latest version: ```bash - .claude/commands/install-bitvm2.sh upgrade + .claude/commands/install-bitvm.sh upgrade ``` The script auto-detects the platform (x86_64-linux / aarch64-macos), downloads from GitHub Releases, verifies the sha256 checksum, and installs all binaries to `./bin/`. diff --git a/.claude/commands/challenge.md b/.claude/commands/challenge.md index ad4a4bf96..c3bf74b79 100644 --- a/.claude/commands/challenge.md +++ b/.claude/commands/challenge.md @@ -31,7 +31,7 @@ Use the `/run-challenger-node` skill to start one, or see `deployment/README.md` 5. Check if the `challenge` binary exists at `./bin/challenge`. If not, run: ```bash - .claude/commands/install-bitvm2.sh install + .claude/commands/install-bitvm.sh install ``` 6. Run the command using the pre-built binary: diff --git a/.claude/commands/install-bitvm2.sh b/.claude/commands/install-bitvm.sh similarity index 98% rename from .claude/commands/install-bitvm2.sh rename to .claude/commands/install-bitvm.sh index 2628dbd4f..401ab4dca 100755 --- a/.claude/commands/install-bitvm2.sh +++ b/.claude/commands/install-bitvm.sh @@ -4,7 +4,7 @@ set -euo pipefail REPO="GOATNetwork/bitvm2-node" API_URL="https://api.github.com/repos/${REPO}/releases" INSTALL_DIR="./bin" -VERSION_FILE=".bitvm2-version" +VERSION_FILE=".bitvm-version" usage() { cat </bitvm2-nodes/challenge_0/.env.challenge_0 .env + cp deployment//bitvm-noded/challenge_0/.env.challenge_0 .env ``` -3. Check if the `bitvm2-noded` binary exists at `./bin/bitvm2-noded`. If not, run: +3. Check if the `bitvm-noded` binary exists at `./bin/bitvm-noded`. If not, run: ```bash - .claude/commands/install-bitvm2.sh install + .claude/commands/install-bitvm.sh install ``` 4. Start the challenger node: ```bash -./bin/bitvm2-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" +./bin/bitvm-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" ``` To run in the background: ```bash -nohup ./bin/bitvm2-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" >challenger_$(date +'%Y%m%d').log 2>&1 & +nohup ./bin/bitvm-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" >challenger_$(date +'%Y%m%d').log 2>&1 & ``` 5. Verify the node is running: @@ -49,9 +49,9 @@ Should return `Hello, World!`. ### Example (testnet4) ```bash -cp deployment/testnet4/bitvm2-nodes/challenge_0/.env.challenge_0 .env +cp deployment/testnet4/bitvm-noded/challenge_0/.env.challenge_0 .env # Edit .env to fill in BITVM_SECRET, GOAT_ADDRESS, PEER_KEY -./bin/bitvm2-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8449 --bootnodes /ip4/34.215.238.232/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv +./bin/bitvm-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm-node.db --p2p-port 8449 --bootnodes /ip4/34.215.238.232/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv ``` For full deployment documentation, see `deployment/README.md` (section **Challenger**). diff --git a/.claude/commands/run-operator-node.md b/.claude/commands/run-operator-node.md index 79323c658..b82f3c840 100644 --- a/.claude/commands/run-operator-node.md +++ b/.claude/commands/run-operator-node.md @@ -1,4 +1,4 @@ -Run a BitVM2 Operator node locally. +Run a BitVM Operator node locally. The Operator manages bridge operations, kickoff processing, and pegout (Gateway.initWithdraw). @@ -8,11 +8,11 @@ The Operator manages bridge operations, kickoff processing, and pegout (Gateway. - **network**: Which network? `testnet4` or `regtest` - **rpc_addr**: RPC listen address. Default: `127.0.0.1:8902` - **p2p_port**: P2P listen port. Default: `8445` (testnet4) or `8446` (regtest) - - **db_path**: SQLite database path. Default: `sqlite:$PWD/bitvm2-node.db` + - **db_path**: SQLite database path. Default: `sqlite:$PWD/bitvm-node.db` 2. Ensure the `.env` file exists in the working directory. Template configs are at: - - **testnet4**: `deployment/testnet4/bitvm2-nodes/operator_0/.env.operator_0` - - **regtest**: `deployment/regtest/bitvm2-nodes/operator_0/.env.operator_0` + - **testnet4**: `deployment/testnet4/bitvm-noded/operator_0/.env.operator_0` + - **regtest**: `deployment/regtest/bitvm-noded/operator_0/.env.operator_0` The user **must** fill in these required secrets: - `BITVM_SECRET` - Operator BTC private key (hex) @@ -24,23 +24,23 @@ The Operator manages bridge operations, kickoff processing, and pegout (Gateway. Copy the template if needed: ```bash - cp deployment//bitvm2-nodes/operator_0/.env.operator_0 .env + cp deployment//bitvm-noded/operator_0/.env.operator_0 .env ``` -3. Check if the `bitvm2-noded` binary exists at `./bin/bitvm2-noded`. If not, run: +3. Check if the `bitvm-noded` binary exists at `./bin/bitvm-noded`. If not, run: ```bash - .claude/commands/install-bitvm2.sh install + .claude/commands/install-bitvm.sh install ``` 4. Start the operator node: ```bash -./bin/bitvm2-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" +./bin/bitvm-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" ``` To run in the background: ```bash -nohup ./bin/bitvm2-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" >operator_$(date +'%Y%m%d').log 2>&1 & +nohup ./bin/bitvm-noded --rpc-addr --db-path --p2p-port --bootnodes "$BOOTNODES" >operator_$(date +'%Y%m%d').log 2>&1 & ``` 5. Verify the node is running: @@ -52,9 +52,9 @@ Should return `Hello, World!`. ### Example (testnet4) ```bash -cp deployment/testnet4/bitvm2-nodes/operator_0/.env.operator_0 .env +cp deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 .env # Edit .env to fill in BITVM_SECRET, GOAT_ADDRESS, PEER_KEY, GOAT_PRIVATE_KEY -./bin/bitvm2-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 --bootnodes /ip4/34.215.238.232/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv +./bin/bitvm-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm-node.db --p2p-port 8445 --bootnodes /ip4/34.215.238.232/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv ``` For full deployment documentation, see `deployment/README.md` (section **Operator**). diff --git a/.claude/commands/upgrade.md b/.claude/commands/upgrade.md index 29de3063e..1663841fb 100644 --- a/.claude/commands/upgrade.md +++ b/.claude/commands/upgrade.md @@ -1,19 +1,19 @@ -Upgrade or install bitvm2-node binaries via `install-bitvm2.sh`. +Upgrade or install bitvm-node binaries via `install-bitvm.sh`. ## Instructions 1. Check the currently installed version: ```bash - .claude/commands/install-bitvm2.sh version + .claude/commands/install-bitvm.sh version ``` 2. If $ARGUMENTS contains a target version (e.g. `v0.3.2`), use that version. Otherwise, upgrade to the latest release. 3. Run the upgrade: ```bash - .claude/commands/install-bitvm2.sh upgrade $ARGUMENTS + .claude/commands/install-bitvm.sh upgrade $ARGUMENTS ``` -4. If the script is missing or not executable, inform the user that `.claude/commands/install-bitvm2.sh` is required and offer to check if it exists. +4. If the script is missing or not executable, inform the user that `.claude/commands/install-bitvm.sh` is required and offer to check if it exists. 5. Report the result to the user: what version was installed before, what version is installed now, and list the installed binaries. diff --git a/.env.template b/.env.template index 6594073d6..1489b5410 100644 --- a/.env.template +++ b/.env.template @@ -22,7 +22,7 @@ GOAT_PRIVATE_KEY= GOAT_ADDRESS= # protocol name -PROTO_NAME=bitvm2t3 +PROTO_NAME=bitvmt3 # setup bootnodes, separate multiple nodes by comma ",". BOOTNODES= \ No newline at end of file diff --git a/.github/workflows/ci_time_consuming.yml b/.github/workflows/ci_time_consuming.yml index 33a1c496b..666e18728 100644 --- a/.github/workflows/ci_time_consuming.yml +++ b/.github/workflows/ci_time_consuming.yml @@ -18,5 +18,5 @@ jobs: - name: Run all unit tests run: | source ~/.zkm-toolchain/env - cd crates/bitvm2-ga + cd crates/bitvm-gc cargo test -r --features ci-tests -- --nocapture diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd2358e22..eefb99acf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,7 +74,7 @@ jobs: - name: Collect binaries run: | BINS=( - "target/release/bitvm2-noded" + "target/release/bitvm-noded" "target/release/sequencer-set-publish" "target/release/challenge" "target/release/send-rbf" @@ -106,7 +106,7 @@ jobs: fi done - ARCHIVE_NAME="bitvm2-node-${VERSION}-${PLATFORM}.tar.gz" + ARCHIVE_NAME="bitvm-node-${VERSION}-${PLATFORM}.tar.gz" tar -czf "release-artifacts/${ARCHIVE_NAME}" -C "$STAGING_DIR" . $CHECKSUM_CMD "release-artifacts/${ARCHIVE_NAME}" > "release-artifacts/${ARCHIVE_NAME}.sha256" @@ -129,4 +129,4 @@ jobs: echo "### Version: $VERSION" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Archive:" >> $GITHUB_STEP_SUMMARY - ls -1 release-artifacts/ | sed 's/^/- /' >> $GITHUB_STEP_SUMMARY \ No newline at end of file + ls -1 release-artifacts/ | sed 's/^/- /' >> $GITHUB_STEP_SUMMARY diff --git a/Cargo.lock b/Cargo.lock index 650dcc467..1350a24db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9737,7 +9737,11 @@ dependencies = [ "bitcoin", "commit-chain", "header-chain", + "hex", + "rand 0.8.5", + "secp256k1 0.29.1", "serde", + "serde_json", "sha2 0.10.9", "state-chain", "strum 0.26.3", @@ -9766,6 +9770,7 @@ dependencies = [ "operator-proof", "prometheus-client", "proof-builder", + "secp256k1 0.29.1", "serde", "serde_json", "state-chain-proof", diff --git a/README.md b/README.md index 1a696b50f..efbd60831 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# GOAT Bitvm2 Node +# GOAT BitVM Node -GOAT Network's BitVM2 bridge implementation. See [GOAT BitVM2 Whitepaper](https://www.goat.network/bitvm2-whitepaper) for more details. +GOAT Network's BitVM bridge implementation. See [GOAT BitVM Whitepaper](https://www.goat.network/bitvm2-whitepaper) for more details. ## Layout diff --git a/audit/TLAPlus-20260710.md b/audit/TLAPlus-20260710.md index 43ac01f13..77f4ad653 100644 --- a/audit/TLAPlus-20260710.md +++ b/audit/TLAPlus-20260710.md @@ -1,10 +1,10 @@ -# BitVM2 Node — Formal Verification Audit (Round 1) +# BitVM Node — Formal Verification Audit (Round 1) **Branch:** `audit/round-1` · **Base:** `gc-v2` · **Method:** TLA+ model checking (TLC) · **Status:** all 8 formally-proven findings from this round are now **fixed and verified** in commit [`991faaa`](https://github.com/GOATNetwork/bitvm2-node/commit/991faaabdb56c747103e8f1c6d6477c638ccfc4c) ("Dev fix #418", authored independently by a teammate — not applied by this audit). Two smaller, lower-priority adjacent defects noted under Finding 8 remain open. ## Executive summary -This audit used TLA+ to formally model the BitVM2 graph's status bookkeeping (local database state), the peg-out transaction graph's timelock configuration, and — a later round — the Bitcoin transaction graph's shared connectors directly, checking each against explicit safety and liveness properties rather than relying on manual code review alone. **Eight real issues were found**, each backed by a machine-checked counterexample (not a hypothetical), and a verified-correct fix design was produced for each. Three further checks were run and each, for a specific and verifiable reason, found **no** new issue: whether having multiple independent watchtowers/verifiers introduces new problems beyond the single-actor case (Finding 5), whether `GoatTxProcessingStatus` — the last multi-writer-shaped enum in the codebase — has the same class of race as the others (Finding 8), and whether `operator_commit`'s margin against the shared `ConnectorF` UTXO — previously only a scalar Rust assertion — actually holds with real shipped values (Finding 10). +This audit used TLA+ to formally model the BitVM graph's status bookkeeping (local database state), the peg-out transaction graph's timelock configuration, and — a later round — the Bitcoin transaction graph's shared connectors directly, checking each against explicit safety and liveness properties rather than relying on manual code review alone. **Eight real issues were found**, each backed by a machine-checked counterexample (not a hypothetical), and a verified-correct fix design was produced for each. Three further checks were run and each, for a specific and verifiable reason, found **no** new issue: whether having multiple independent watchtowers/verifiers introduces new problems beyond the single-actor case (Finding 5), whether `GoatTxProcessingStatus` — the last multi-writer-shaped enum in the codebase — has the same class of race as the others (Finding 8), and whether `operator_commit`'s margin against the shared `ConnectorF` UTXO — previously only a scalar Rust assertion — actually holds with real shipped values (Finding 10). **Update: all 8 findings are now fixed.** Commit `991faaa`, contributed independently to this branch, applies real fixes for every one of them. This audit's own contribution to that fix landing was zero — the fixes were designed and written by someone else — but every fix was independently re-verified against this audit's own TLA+ models before being accepted as correct: read against the actual diff (not the commit message), and for the two timelock-margin findings (4, 9), re-checked with TLC using the *actual new shipped numbers*, not just the originally-proposed values. Each Finding below is marked **FIXED (commit `991faaa`)** with what the real applied fix looks like. In several cases the shipped fix is more thorough than what this audit's own fix design proposed (see Findings 1, 6 in particular). @@ -98,7 +98,7 @@ Before generalizing Finding 4's fix, it was necessary to check whether the real `InstanceBridgeOutStatus` (`Initialize`/`Claim`/`Timeout`/`Refund`, `crates/store/src/schema.rs:223-229`) is written from **three** independently-scheduled, uncoordinated places, none of which share a transaction spanning read+decide+write: -- The RPC-service task (`node/src/rpc_service/handler/bitvm2_handler.rs:308-368`, `bridge_out_init_tag`) — a stale-read-then-full-row-`upsert_instance`, sets `Initialize` unconditionally as part of a full-row overwrite. +- The RPC-service task (`node/src/rpc_service/handler/bitvm_handler.rs:308-368`, `bridge_out_init_tag`) — a stale-read-then-full-row-`upsert_instance`, sets `Initialize` unconditionally as part of a full-row overwrite. - The GoatChain L2-event watcher (`node/src/scheduled_tasks/event_watch_task.rs:634-669,756-763,810-815`) — a 5-second tokio task, unconditional targeted `update_instance` on `SwapClaimEvent`/`SwapRefundEvent`, sets `Claim`/`Refund` with **no status precondition** (`InstanceUpdate`'s `WHERE` clause is only `hex(instance_id)=?` — confirmed via `crates/store/src/localdb.rs` that no `with_only_if_status_in`-style guard exists anywhere in the codebase for this entity). - The maintenance task (`node/src/scheduled_tasks/instance_maintenance_tasks.rs:482-517`, `instance_bridge_out_monitor`) — a 10-second tokio task, batch-reads a stale snapshot then per-row does an unconditional targeted `update_instance` to `Timeout` with no re-check at write time. diff --git a/circuits/README.md b/circuits/README.md index 103f2d27c..031df4485 100644 --- a/circuits/README.md +++ b/circuits/README.md @@ -1,4 +1,4 @@ -# BitVM2 Circuits +# BitVM Circuits ## Overview @@ -203,7 +203,7 @@ RUST_LOG=info cargo run --package watchtower-proof --bin watchtower-proof -r -- * Simutate a withdraw challenge ```bash -cd crates/bitvm2-ga +cd crates/bitvm-gc cargo test -r test_take2 ``` @@ -251,4 +251,4 @@ input, while the dynamic public input is committed after the watchtower challeng * included-watchtower: a 256-bit bitmask; each bit flags a valid watchtower inside operator proof. * execution-layer-block-number: the block number that including `proceedWithdraw`(Peg-out) transaction of GOAT Network's execution layer(Geth). * watchtower-challenge-info: list of watchtower's challenge transaction id and compressed public key, i.e: [wachtower_info.json](./data/watchtower/watchtower_info.json). -* watchtower-challenge-init-txid: the watchtower challenge init transaction id in GOAT's BitVM2 graph. +* watchtower-challenge-init-txid: the watchtower challenge init transaction id in GOAT's BitVM graph. diff --git a/circuits/commit-chain-proof/host/src/lib.rs b/circuits/commit-chain-proof/host/src/lib.rs index e1f640633..e49bf6b20 100644 --- a/circuits/commit-chain-proof/host/src/lib.rs +++ b/circuits/commit-chain-proof/host/src/lib.rs @@ -193,6 +193,84 @@ pub fn load_upgrade_commits( Ok(commits) } +/// Validates that `commits` is the complete ordered history represented by `output`. +fn validate_full_commit_history( + commits: &[CircuitCommit], + output: &CommitChainCircuitOutput, +) -> anyhow::Result<()> { + let genesis_txid = output.chain_state.genesis_txid; + let first_commit = commits.first().context("full commit history must be non-empty")?; + anyhow::ensure!( + first_commit.commit_txn.compute_txid().to_byte_array() == genesis_txid, + "full commit history must start at the proof's fixed Genesis" + ); + + for (index, commit) in commits.iter().enumerate() { + anyhow::ensure!( + commit.genesis_txid == genesis_txid, + "full commit history commit {index} has a different Genesis" + ); + } + + for (index, commits) in commits.windows(2).enumerate() { + let previous_txid = commits[0].commit_txn.compute_txid(); + let next_input = commits[1].commit_txn.input.first().with_context(|| { + format!("full commit history commit {} has no update input", index + 1) + })?; + anyhow::ensure!( + next_input.previous_output.txid == previous_txid + && next_input.previous_output.vout == 0, + "full commit history commit {} does not spend the previous commit", + index + 1 + ); + } + + let latest_txid = + commits.last().context("full commit history must be non-empty")?.commit_txn.compute_txid(); + anyhow::ensure!( + latest_txid == output.chain_state.commit_txn.compute_txid(), + "full commit history tip does not match the proof output" + ); + Ok(()) +} + +/// Builds the complete Genesis-to-tip history for the proof being saved. +fn build_full_commit_history( + input_proof: &str, + circuit_input: &CommitChainCircuitInput, + output_public_values: &[u8], +) -> anyhow::Result> { + anyhow::ensure!(!circuit_input.commits.is_empty(), "commit proof input must be non-empty"); + let current_output = decode_commit_chain_circuit_output(output_public_values); + + let full_commits = match &circuit_input.prev_proof { + CommitChainPrevProofType::GenesisBlock => circuit_input.commits.clone(), + CommitChainPrevProofType::PrevProof => { + let previous_output = + decode_commit_chain_circuit_output(&circuit_input.zkm_public_values); + let history_path = format!("{input_proof}.all-commits"); + let mut previous_commits: Vec = serde_json::from_slice( + &std::fs::read(&history_path) + .with_context(|| format!("read {history_path} error"))?, + ) + .with_context(|| format!("parse {history_path} error"))?; + let previous_tip = previous_commits + .last() + .context("previous full commit history must be non-empty")?; + anyhow::ensure!( + previous_tip.commit_txn.compute_txid() + == previous_output.chain_state.commit_txn.compute_txid(), + "previous full commit history tip does not match the predecessor proof output" + ); + previous_commits.extend(circuit_input.commits.iter().cloned()); + previous_commits + } + }; + + validate_full_commit_history(&full_commits, ¤t_output)?; + Ok(full_commits) +} + /// A program that aggregates the proofs of the simple program. pub struct CommitChainProofBuilder { client: ProverClient, @@ -333,7 +411,7 @@ impl ProofBuilder for CommitChainProofBuilder { _cycles: u64, proof: ZKMProofWithPublicValues, ) -> anyhow::Result<(String, usize)> { - let ProofRequest::CommitChainProofRequest { output_proof, .. } = ctx else { + let ProofRequest::CommitChainProofRequest { input_proof, output_proof, .. } = ctx else { anyhow::bail!("Invalid commit chain input"); }; //fs::write(&output_proof, bincode::serialize(&proof)?)?; @@ -344,21 +422,25 @@ impl ProofBuilder for CommitChainProofBuilder { //tracing::info!("Generate proof successfully, proof: {:?}", proof); //Ok((public_value_hex, proof_size)) + let circuit_input: CommitChainCircuitInput = bincode::deserialize(input)?; + let public_values = proof.public_values.to_vec(); + let full_commits = build_full_commit_history(input_proof, &circuit_input, &public_values)?; + std::fs::write(output_proof, proof.bytes())?; - let public_value_hex = hex::encode(proof.public_values.to_vec()); + let public_value_hex = hex::encode(&public_values); let proof_size = proof.bytes().len(); let zkm_version = proof.zkm_version.clone(); - std::fs::write( - format!("{}.public_inputs.bin", output_proof), - proof.public_values.to_vec(), - )?; + std::fs::write(format!("{}.public_inputs.bin", output_proof), public_values)?; std::fs::write(format!("{}.vk_hash.bin", output_proof), self.verifying_key.bytes32())?; std::fs::write(format!("{}.zkm_version.bin", output_proof), zkm_version)?; - let circuit_input: CommitChainCircuitInput = bincode::deserialize(input)?; - std::fs::write( - format!("{}.commits", output_proof), - serde_json::to_vec(&circuit_input.commits)?, - )?; + let latest_commit = + circuit_input.commits.last().context("commit proof input must be non-empty")?; + let commits_path = format!("{output_proof}.commits"); + std::fs::write(&commits_path, serde_json::to_vec(std::slice::from_ref(latest_commit))?) + .with_context(|| format!("write {commits_path} error"))?; + let full_commits_path = format!("{output_proof}.all-commits"); + std::fs::write(&full_commits_path, serde_json::to_vec(&full_commits)?) + .with_context(|| format!("write {full_commits_path} error"))?; Ok((public_value_hex, proof_size)) } } @@ -460,7 +542,7 @@ mod tests { println!("proof: {public_input:?}, hash : {:?}", hash); //let input_path = "/home/ubuntu/data/proof-builder-rpc/circuits/data/commit-chain/commit_info.json.8"; - let input_path = "/home/ubuntu/data/stephen/bitvm2-node/circuits/data/commit-chain/commit_info.json.latest"; + let input_path = "/home/ubuntu/data/stephen/bitvm-node/circuits/data/commit-chain/commit_info.json.latest"; let input_bytes = std::fs::read(input_path).unwrap(); let commit_info: CommitInfo = serde_json::from_slice(&input_bytes).unwrap(); info!("commit info: {:?}", commit_info); diff --git a/circuits/header-chain-proof/host/Cargo.toml b/circuits/header-chain-proof/host/Cargo.toml index aacbab3eb..9704cc079 100644 --- a/circuits/header-chain-proof/host/Cargo.toml +++ b/circuits/header-chain-proof/host/Cargo.toml @@ -23,7 +23,7 @@ sha2 = { workspace = true } rand = { workspace = true } borsh = { workspace = true } -# bitvm2 node +# bitvm node header-chain = { workspace = true } bitcoin = { workspace = true } client = { workspace = true } diff --git a/circuits/proof-builder/Cargo.toml b/circuits/proof-builder/Cargo.toml index f73139360..0989dff91 100644 --- a/circuits/proof-builder/Cargo.toml +++ b/circuits/proof-builder/Cargo.toml @@ -8,6 +8,10 @@ anyhow = { workspace = true } thiserror = { workspace = true } sha2 = { workspace = true } serde.workspace = true +serde_json.workspace = true +secp256k1.workspace = true +hex.workspace = true +rand.workspace = true # Ziren zkm-sdk.workspace = true diff --git a/circuits/proof-builder/src/api_auth.rs b/circuits/proof-builder/src/api_auth.rs new file mode 100644 index 000000000..19cb70fd4 --- /dev/null +++ b/circuits/proof-builder/src/api_auth.rs @@ -0,0 +1,301 @@ +use anyhow::{Context, bail}; +use rand::{RngCore, rngs::OsRng}; +use secp256k1::schnorr::Signature; +use secp256k1::{Keypair, Message, PublicKey, SECP256K1, XOnlyPublicKey}; +use serde::Serialize; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::mem; +use std::str::FromStr; + +pub const AUTH_TIMESTAMP_HEADER: &str = "x-proof-auth-timestamp"; +pub const AUTH_NONCE_HEADER: &str = "x-proof-auth-nonce"; +pub const AUTH_PUBLIC_KEY_HEADER: &str = "x-proof-auth-public-key"; +pub const AUTH_SIGNATURE_HEADER: &str = "x-proof-auth-signature"; +pub const AUTH_WINDOW_SECS: i64 = 300; + +const AUTH_DOMAIN: &str = "bitvm-proof-builder-auth-v1"; +const AUTH_NONCE_LEN: usize = 16; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProofBuilderAuthRole { + Operator, + Watchtower, +} + +impl ProofBuilderAuthRole { + /// Returns the stable role label included in the signed payload. + pub fn as_str(self) -> &'static str { + match self { + Self::Operator => "operator", + Self::Watchtower => "watchtower", + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProofBuilderAuthHeaders { + pub timestamp: String, + pub nonce: String, + pub public_key: String, + pub signature: String, +} + +impl ProofBuilderAuthHeaders { + /// Converts the signed values into HTTP header name/value pairs. + pub fn to_header_pairs(&self) -> Vec<(String, String)> { + vec![ + (AUTH_TIMESTAMP_HEADER.to_string(), self.timestamp.clone()), + (AUTH_NONCE_HEADER.to_string(), self.nonce.clone()), + (AUTH_PUBLIC_KEY_HEADER.to_string(), self.public_key.clone()), + (AUTH_SIGNATURE_HEADER.to_string(), self.signature.clone()), + ] + } +} + +/// Normalizes a compressed or x-only secp256k1 public key to x-only form. +pub fn normalize_public_key(value: &str) -> anyhow::Result { + if let Ok(public_key) = XOnlyPublicKey::from_str(value) { + return Ok(public_key); + } + let public_key = PublicKey::from_str(value).context("invalid secp256k1 public key")?; + Ok(public_key.x_only_public_key().0) +} + +/// Signs one Proof Builder request with a fresh nonce and the caller's node key. +#[allow(clippy::too_many_arguments)] +pub fn sign_proof_builder_request( + keypair: &Keypair, + role: ProofBuilderAuthRole, + method: &str, + path: &str, + body: &B, +) -> anyhow::Result { + let timestamp = current_time_secs().to_string(); + let mut nonce = [0u8; AUTH_NONCE_LEN]; + OsRng.fill_bytes(&mut nonce); + let nonce = hex::encode(nonce); + let public_key = keypair.x_only_public_key().0.to_string(); + let digest = request_digest(role, method, path, ×tamp, &nonce, &public_key, body)?; + let mut auxiliary_randomness = [0u8; 32]; + OsRng.fill_bytes(&mut auxiliary_randomness); + let signature = SECP256K1.sign_schnorr_with_aux_rand( + &Message::from_digest(digest), + keypair, + &auxiliary_randomness, + ); + + Ok(ProofBuilderAuthHeaders { + timestamp, + nonce, + public_key, + signature: hex::encode(signature.as_ref()), + }) +} + +/// Verifies the cryptographic binding of one Proof Builder request. +#[allow(clippy::too_many_arguments)] +pub fn verify_proof_builder_request_signature( + role: ProofBuilderAuthRole, + method: &str, + path: &str, + timestamp: &str, + nonce: &str, + public_key: &XOnlyPublicKey, + signature: &str, + body: &B, +) -> anyhow::Result<()> { + validate_timestamp(timestamp)?; + let nonce_bytes = hex::decode(nonce).context("invalid auth nonce encoding")?; + if nonce_bytes.len() != AUTH_NONCE_LEN { + bail!("invalid auth nonce length"); + } + let signature_bytes = hex::decode(signature).context("invalid auth signature encoding")?; + let signature = Signature::from_slice(&signature_bytes).context("invalid auth signature")?; + let canonical_public_key = public_key.to_string(); + let digest = request_digest(role, method, path, timestamp, nonce, &canonical_public_key, body)?; + SECP256K1 + .verify_schnorr(&signature, &Message::from_digest(digest), public_key) + .context("auth signature verification failed") +} + +/// Validates that an authentication timestamp is canonical and within the accepted window. +pub fn validate_timestamp(timestamp: &str) -> anyhow::Result { + let timestamp_value: i64 = timestamp.parse().context("invalid auth timestamp")?; + if timestamp_value.to_string() != timestamp { + bail!("auth timestamp is not canonical"); + } + let now = current_time_secs(); + if (now - timestamp_value).abs() > AUTH_WINDOW_SECS { + bail!("auth timestamp expired"); + } + Ok(timestamp_value) +} + +/// Hashes length-prefixed request fields and canonical JSON body bytes into the signed digest. +fn request_digest( + role: ProofBuilderAuthRole, + method: &str, + path: &str, + timestamp: &str, + nonce: &str, + public_key: &str, + body: &B, +) -> anyhow::Result<[u8; 32]> { + let body_hash = Sha256::digest(canonical_json_bytes(body)?); + let mut hasher = Sha256::new(); + for field in [AUTH_DOMAIN, role.as_str(), method, path, timestamp, nonce, public_key] { + hash_field(&mut hasher, field.as_bytes()); + } + hash_field(&mut hasher, &body_hash); + Ok(hasher.finalize().into()) +} + +/// Serializes JSON objects with recursively sorted keys for cross-client digest stability. +fn canonical_json_bytes(body: &B) -> anyhow::Result> { + let mut value = + serde_json::to_value(body).context("failed to serialize authenticated request body")?; + sort_json_value(&mut value); + serde_json::to_vec(&value).context("failed to encode canonical authenticated request body") +} + +/// Recursively sorts JSON object keys while preserving array order. +fn sort_json_value(value: &mut Value) { + match value { + Value::Object(map) => { + let mut entries = mem::take(map).into_iter().collect::>(); + entries.sort_unstable_by(|left, right| left.0.cmp(&right.0)); + for (key, mut value) in entries { + sort_json_value(&mut value); + map.insert(key, value); + } + } + Value::Array(values) => values.iter_mut().for_each(sort_json_value), + _ => {} + } +} + +/// Adds one length-prefixed field to the authentication digest. +fn hash_field(hasher: &mut Sha256, field: &[u8]) { + hasher.update((field.len() as u64).to_be_bytes()); + hasher.update(field); +} + +/// Returns the current Unix time used by the authentication freshness check. +fn current_time_secs() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_secs() as i64 +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Serialize; + + #[derive(Serialize)] + struct TestBody { + graph_id: &'static str, + value: u64, + } + + #[derive(Serialize)] + struct ReorderedTestBody { + value: u64, + graph_id: &'static str, + } + + fn keypair(seed: u8) -> Keypair { + Keypair::from_seckey_slice(SECP256K1, &[seed; 32]).unwrap() + } + + #[test] + fn signed_request_verifies_and_is_bound_to_request() { + let keypair = keypair(7); + let body = TestBody { graph_id: "graph-1", value: 1 }; + let headers = sign_proof_builder_request( + &keypair, + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &body, + ) + .unwrap(); + let public_key = normalize_public_key(&headers.public_key).unwrap(); + + assert!( + verify_proof_builder_request_signature( + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &headers.timestamp, + &headers.nonce, + &public_key, + &headers.signature, + &body, + ) + .is_ok() + ); + assert!( + verify_proof_builder_request_signature( + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs_timeout", + &headers.timestamp, + &headers.nonce, + &public_key, + &headers.signature, + &body, + ) + .is_err() + ); + assert!( + verify_proof_builder_request_signature( + ProofBuilderAuthRole::Watchtower, + "POST", + "/v1/proofs/operator_proofs", + &headers.timestamp, + &headers.nonce, + &public_key, + &headers.signature, + &body, + ) + .is_err() + ); + assert!( + verify_proof_builder_request_signature( + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &headers.timestamp, + &headers.nonce, + &public_key, + &headers.signature, + &TestBody { graph_id: "graph-2", value: 1 }, + ) + .is_err() + ); + assert!( + verify_proof_builder_request_signature( + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &headers.timestamp, + &headers.nonce, + &public_key, + &headers.signature, + &ReorderedTestBody { value: 1, graph_id: "graph-1" }, + ) + .is_ok() + ); + } + + #[test] + fn public_key_normalization_accepts_compressed_and_x_only_keys() { + let keypair = keypair(9); + let expected = keypair.x_only_public_key().0; + assert_eq!(normalize_public_key(&expected.to_string()).unwrap(), expected); + assert_eq!(normalize_public_key(&keypair.public_key().to_string()).unwrap(), expected); + } +} diff --git a/circuits/proof-builder/src/lib.rs b/circuits/proof-builder/src/lib.rs index 2d7ee3023..85c3a7c7c 100644 --- a/circuits/proof-builder/src/lib.rs +++ b/circuits/proof-builder/src/lib.rs @@ -10,6 +10,8 @@ use thiserror::Error; use zkm_sdk::{HashableKey, ProverClient, ZKM_CIRCUIT_VERSION, ZKMProofWithPublicValues}; use zkm_sdk::{ZKMProvingKey, ZKMVerifyingKey}; +pub mod api_auth; + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ProofRequest { HeaderChainProofRequest { diff --git a/circuits/watchtower-proof/host/src/lib.rs b/circuits/watchtower-proof/host/src/lib.rs index a52da06da..e4a5341f5 100644 --- a/circuits/watchtower-proof/host/src/lib.rs +++ b/circuits/watchtower-proof/host/src/lib.rs @@ -109,17 +109,31 @@ pub async fn fetch_target_block( bitcoin_network: Network, ) -> anyhow::Result<(u32, Block, Transaction)> { let btc_client = client::btc_chain::BTCClient::new(bitcoin_network, Some(esplora_url)); - let latest_sequencer_commit_txid = Txid::from_str(latest_sequencer_commit_txid).unwrap(); - - let latest_sequencer_commit_tx = - btc_client.get_tx(&latest_sequencer_commit_txid).await.unwrap().unwrap(); - // TODO: replace it by `get_raw_transaction_info` - let tx_merkle_proof = - btc_client.get_merkle_proof(&latest_sequencer_commit_txid).await.unwrap().unwrap(); - - let block_pos = tx_merkle_proof.block_height; + let latest_sequencer_commit_txid = Txid::from_str(latest_sequencer_commit_txid) + .context("invalid latest sequencer commitment txid")?; + let tx_info = btc_client + .get_tx_info(&latest_sequencer_commit_txid) + .await + .with_context(|| { + format!("failed to fetch transaction info for {latest_sequencer_commit_txid}") + })? + .with_context(|| format!("transaction {latest_sequencer_commit_txid} not found"))?; + anyhow::ensure!( + tx_info.status.confirmed, + "transaction {latest_sequencer_commit_txid} is not confirmed" + ); + let block_pos = + tx_info.status.block_height.context("confirmed transaction is missing its block height")?; + let block_hash = + tx_info.status.block_hash.context("confirmed transaction is missing its block hash")?; tracing::info!("block height: {block_pos}"); - let target_block = btc_client.get_block_by_height(block_pos).await.unwrap(); + let target_block = btc_client + .get_block_by_hash(&block_hash) + .await + .with_context(|| format!("failed to fetch block {block_hash}"))? + .with_context(|| format!("block {block_hash} not found"))?; + let latest_sequencer_commit_tx = tx_info.to_tx(); + Ok((block_pos, target_block, latest_sequencer_commit_tx)) } @@ -347,3 +361,33 @@ impl ProofBuilder for WatchtowerProofBuilder { Ok((public_value_hex, proof_size)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + #[ignore = "requires public testnet4 Esplora"] + async fn fetch_target_block_from_testnet4() -> anyhow::Result<()> { + let (block_num, block, tx) = fetch_target_block( + "https://mempool.space/testnet4/api", + "c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5", + Network::Testnet4, + ) + .await?; + // println!("fetch_target_block result: {block_num:#?}, {block:#?}, {tx:#?}"); + + assert_eq!(block_num, 146920); + assert_eq!( + block.block_hash(), + bitcoin::BlockHash::from_str( + "00000000000000024f1da869c78a77e4b88043ca8ed57f76fb79d180578897a8" + )? + ); + assert_eq!( + tx.compute_txid().to_string(), + "c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5" + ); + Ok(()) + } +} diff --git a/crates/bitvm-gc/src/babe_adapter.rs b/crates/bitvm-gc/src/babe_adapter.rs index 360c74892..2dd305e9e 100644 --- a/crates/bitvm-gc/src/babe_adapter.rs +++ b/crates/bitvm-gc/src/babe_adapter.rs @@ -37,7 +37,6 @@ use verifiable_circuit_babe::verifier::BABEVerifier; /// Number of Wots96 digit signatures expected by the GOAT GC-V2 connector. pub const WOTS_SIG_COUNT: usize = Wots96::TOTAL_DIGIT_LEN as usize; pub const BABE_N_CC: usize = 181; -// TODO: use verifiable_circuit_babe::babe::M_CC instead pub const BABE_M_CC: usize = 7; pub type OpenedInstanceSeeds = Vec<(usize, u64)>; diff --git a/crates/bitvm-gc/src/verifier/api.rs b/crates/bitvm-gc/src/verifier/api.rs index 53deee6d2..92526b5f6 100644 --- a/crates/bitvm-gc/src/verifier/api.rs +++ b/crates/bitvm-gc/src/verifier/api.rs @@ -133,10 +133,6 @@ pub fn build_challenge_incomplete_kickoff_tx( Ok((tx, anchor_added)) } -pub fn verify_prover_assertion(_graph: &BitvmGcGraph, _operator_assert_txin: TxIn) -> Result { - todo!("verify operator assertion") -} - fn split_operator_assert_wots_and_extra_data( mut operator_assertion: RawWitness, ) -> Result<(RawWitness, Vec, Vec)> { diff --git a/crates/client/src/http_client/async_client.rs b/crates/client/src/http_client/async_client.rs index 87df550ad..0d2e9b452 100644 --- a/crates/client/src/http_client/async_client.rs +++ b/crates/client/src/http_client/async_client.rs @@ -67,6 +67,27 @@ impl HttpAsyncClient { response.json::().await.map_err(|e| anyhow!("failed to deserialize response:{e}")) } + /// Posts JSON while rebuilding caller-supplied headers for every retry attempt. + pub async fn post_response_json_with_dynamic_headers< + T: serde::de::DeserializeOwned, + B: serde::Serialize, + F: Fn() -> anyhow::Result>, + >( + &self, + url: &str, + body: &B, + header_factory: F, + ) -> anyhow::Result { + let response = self.post_with_retry_and_dynamic_headers(url, body, &header_factory).await?; + if !response.status().is_success() { + let status = response.status().as_u16(); + let message = response.text().await?; + return Err(Error::HttpResponse { status, message }.into()); + } + + response.json::().await.map_err(|e| anyhow!("failed to deserialize response:{e}")) + } + pub async fn post_opt_response_json( &self, url: &str, @@ -124,4 +145,35 @@ impl HttpAsyncClient { } } } + + /// Sends one JSON request and regenerates dynamic headers before each retry. + async fn post_with_retry_and_dynamic_headers< + B: serde::Serialize, + F: Fn() -> anyhow::Result>, + >( + &self, + url: &str, + body: &B, + header_factory: &F, + ) -> anyhow::Result { + let mut delay = BASE_BACKOFF_MILLIS; + let mut attempts = 0; + + loop { + let mut request = self.client.post(url).json(body); + for (name, value) in header_factory()? { + request = request.header(name, value); + } + match request.send().await? { + resp if attempts < self.max_retries + && RETRYABLE_ERROR_CODES.contains(&resp.status().as_u16()) => + { + tokio::time::sleep(delay).await; + attempts += 1; + delay *= 2; + } + resp => return Ok(resp), + } + } + } } diff --git a/crates/commit-chain/src/commit_chain.rs b/crates/commit-chain/src/commit_chain.rs index 5af821ecc..6a90e44f0 100644 --- a/crates/commit-chain/src/commit_chain.rs +++ b/crates/commit-chain/src/commit_chain.rs @@ -315,7 +315,7 @@ impl CommitChainState { &self.publisher_public_keys[..], self.threshold as usize, ); - crate::publisher::verify_p2wsh_multisig_witness( + let publisher_witness_valid = crate::publisher::verify_p2wsh_multisig_witness( &latest_commit_txn_with_wtns, 0, prevout, @@ -323,7 +323,11 @@ impl CommitChainState { &self.publisher_public_keys, self.threshold as usize, ) - .unwrap(); + .expect("Failed to verify Publisher multisig witness"); + assert!( + publisher_witness_valid, + "Publisher multisig witness does not meet threshold" + ); } let expected_next_connector_script = crate::create_sequencer_update_script( diff --git a/crates/commit-chain/src/publisher.rs b/crates/commit-chain/src/publisher.rs index 5e25fedc0..7001576be 100644 --- a/crates/commit-chain/src/publisher.rs +++ b/crates/commit-chain/src/publisher.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{collections::HashSet, error::Error}; use bitcoin::blockdata::{opcodes::all::*, script::Builder}; use bitcoin::{Amount, CompressedPublicKey, Network, ScriptBuf, Transaction, TxOut, Witness}; @@ -8,13 +8,31 @@ use bitcoin::secp256k1::{ }; use bitcoin::sighash::{EcdsaSighashType, SighashCache}; -pub fn create_sequencer_update_script(public_keys: &[PublicKey], threshold: usize) -> ScriptBuf { +/// Validates a Publisher key set before it is committed into a P2WSH multisig script. +pub fn validate_publisher_set(public_keys: &[PublicKey], threshold: usize) -> Result<(), String> { let total = public_keys.len(); println!("Multi sig: {threshold} of {total}"); - assert!( - threshold <= total, - "Threshold must be less than or equal to total number of public keys" - ); + + if public_keys.is_empty() || total > 20 || threshold == 0 || threshold > total { + return Err(format!( + "Invalid Publisher set: public key count {total}, threshold {threshold}; expected 1 to 20 public keys and a threshold between 1 and the public key count", + )); + } + + let mut unique_keys = HashSet::with_capacity(total); + for public_key in public_keys { + if !unique_keys.insert(public_key.serialize()) { + return Err(format!("Duplicate Publisher public key: {public_key}")); + } + } + + Ok(()) +} + +pub fn create_sequencer_update_script(public_keys: &[PublicKey], threshold: usize) -> ScriptBuf { + validate_publisher_set(public_keys, threshold) + .unwrap_or_else(|error| panic!("Invalid Publisher set: {error}")); + let mut redeem_script = Builder::new().push_int(threshold as i64); for pk in public_keys { redeem_script = redeem_script.push_slice(pk.serialize()); diff --git a/deployment/README.md b/deployment/README.md index 8e8e56b05..7b00e4972 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -1,6 +1,6 @@ # Deployment Guide -This document describes how to deploy the different roles in the BitVM2 system: Committee, Operator, Challenger, Watchtower, and Relayer, by manually compiling and running the [node](../node/README.md). +This document describes how to deploy the different roles in the BitVM system: Committee, Operator, Challenger, Watchtower, and Relayer, by manually compiling and running the [node](../node/README.md). ## Prerequisites @@ -25,7 +25,7 @@ Before building and running the node, ensure you have the following installed: cargo build -r --all-targets ``` - The compiled binary will be located at `target/release/bitvm2-noded`. + The compiled binary will be located at `target/release/bitvm-noded`. ## Configuration @@ -49,7 +49,7 @@ Create a `.env` file and include necessary environment variables: | `GOAT_GATEWAY_EVENT_THE_GRAPH_URL` | The Graph URL for querying Gateway events | Yes | | `BITVM_SECRET` | Secret key or seed for BTC interaction | Yes | | `GOAT_ADDRESS` | The Goat chain address of the operator/node | Yes | -| `PROTO_NAME` | The P2P protocol name (default: `bitvm2`) | Yes | +| `PROTO_NAME` | The P2P protocol name (default: `bitvm`) | Yes | | `GOAT_PRIVATE_KEY` | Private key for the Goat chain interaction | Optional | | `GOAT_SEQUENCER_SET_PUBLISHER_CONTRACT_ADDRESS` | Address of the Sequencer Set Publisher contract | Optional | | `GOAT_SEQUENCER_SET_MULTI_SIG_VERIFIER_ADDRESS` | Address of the MultiSig Verifier contract | Optional | @@ -66,7 +66,7 @@ Configure the `.env` file for the specific role and run the node. **Common Arguments:** - `--rpc-addr `: Address for the RPC server (default: `0.0.0.0:8080`) -- `--db-path `: Path to the SQLite database (default: `sqlite:/tmp/bitvm2-node.db`) +- `--db-path `: Path to the SQLite database (default: `sqlite:/tmp/bitvm-node.db`) - `--p2p-port `: Port for P2P communication (default: `0`) - `--bootnodes `: Bootnodes to connect to @@ -92,7 +92,7 @@ GOAT_ADDRESS=... GOAT_GATEWAY_CONTRACT_ADDRESS=0x21f619040AC2eAcacEF8Fe17Ae8bDF53ec69C66f GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://api.goat.0xgraph.xyz/api/public/1030419e-065f-45e9-8cf5-69c42207cbc7/subgraphs/bitvm2_gateway_ga_dev_0/0.0.3/gn GOAT_GATEWAY_EVENT_FILTER_FROM=9689342 -PROTO_NAME=bitvm2t3 +PROTO_NAME=bitvmt3 BOOTNODES=... # empty if this is the first node # optional, relayer only @@ -108,7 +108,7 @@ GOAT_SWAP_EVENT_FILTER_FROM=9368978 **Run Command:** ```bash -./target/release/bitvm2-noded \ +./target/release/bitvm-noded \ --rpc-addr 0.0.0.0:9100 \ --p2p-port 8443 \ --db-path ./committee.db @@ -136,14 +136,14 @@ GOAT_ADDRESS=... GOAT_GATEWAY_CONTRACT_ADDRESS=0x21f619040AC2eAcacEF8Fe17Ae8bDF53ec69C66f GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://api.goat.0xgraph.xyz/api/public/1030419e-065f-45e9-8cf5-69c42207cbc7/subgraphs/bitvm2_gateway_ga_dev_0/0.0.3/gn GOAT_GATEWAY_EVENT_FILTER_FROM=9689342 -PROTO_NAME=bitvm2t3 +PROTO_NAME=bitvmt3 BOOTNODES=... # empty if this is the first node ``` **Run Command:** ```bash -./target/release/bitvm2-noded \ +./target/release/bitvm-noded \ --rpc-addr 0.0.0.0:9100 \ --p2p-port 8443 \ --db-path ./operator.db @@ -171,14 +171,14 @@ GOAT_ADDRESS=... GOAT_GATEWAY_CONTRACT_ADDRESS=0x21f619040AC2eAcacEF8Fe17Ae8bDF53ec69C66f GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://api.goat.0xgraph.xyz/api/public/1030419e-065f-45e9-8cf5-69c42207cbc7/subgraphs/bitvm2_gateway_ga_dev_0/0.0.3/gn GOAT_GATEWAY_EVENT_FILTER_FROM=9689342 -PROTO_NAME=bitvm2t3 +PROTO_NAME=bitvmt3 BOOTNODES=... # empty if this is the first node ``` **Run Command:** ```bash -./target/release/bitvm2-noded \ +./target/release/bitvm-noded \ --rpc-addr 0.0.0.0:9100 \ --p2p-port 8443 \ --db-path ./challenger.db @@ -205,17 +205,17 @@ BITVM_SECRET=... # operator should setup its BTC private key GOAT_GATEWAY_CONTRACT_ADDRESS=0x21f619040AC2eAcacEF8Fe17Ae8bDF53ec69C66f GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://api.goat.0xgraph.xyz/api/public/1030419e-065f-45e9-8cf5-69c42207cbc7/subgraphs/bitvm2_gateway_ga_dev_0/0.0.3/gn GOAT_GATEWAY_EVENT_FILTER_FROM=9689342 -PROTO_NAME=bitvm2t3 +PROTO_NAME=bitvmt3 BOOTNODES=... # empty if this is the first node ``` **Run Command:** ```bash -./target/release/bitvm2-noded \ +./target/release/bitvm-noded \ --rpc-addr 0.0.0.0:9100 \ --p2p-port 8443 \ --db-path ./watchtower.db ``` -For `sequencer-set-publish` and `proof-builder-rpc` deployment and failure recovery, please refer to the [Proof Builder RPC README](../proof-builder-rpc/README.md) for detailed instructions. \ No newline at end of file +For `sequencer-set-publish` and `proof-builder-rpc` deployment and failure recovery, please refer to the [Proof Builder RPC README](../proof-builder-rpc/README.md) for detailed instructions. diff --git a/deployment/regtest/bitvm2-nodes/challenge_0/.env.challenge_0 b/deployment/regtest/bitvm-noded/challenge_0/.env.challenge_0 similarity index 96% rename from deployment/regtest/bitvm2-nodes/challenge_0/.env.challenge_0 rename to deployment/regtest/bitvm-noded/challenge_0/.env.challenge_0 index 6bdc00ed8..da66c25aa 100644 --- a/deployment/regtest/bitvm2-nodes/challenge_0/.env.challenge_0 +++ b/deployment/regtest/bitvm-noded/challenge_0/.env.challenge_0 @@ -24,4 +24,4 @@ GOAT_ADDRESS= # libp2p config PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j -PROTO_NAME=bitvm2_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest \ No newline at end of file diff --git a/deployment/regtest/bitvm-noded/challenge_0/run.sh b/deployment/regtest/bitvm-noded/challenge_0/run.sh new file mode 100644 index 000000000..aa2d1f58c --- /dev/null +++ b/deployment/regtest/bitvm-noded/challenge_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8450 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/committee_0/.env.committee_0 b/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 similarity index 96% rename from deployment/regtest/bitvm2-nodes/committee_0/.env.committee_0 rename to deployment/regtest/bitvm-noded/committee_0/.env.committee_0 index f65bac27c..73e4572e1 100644 --- a/deployment/regtest/bitvm2-nodes/committee_0/.env.committee_0 +++ b/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 @@ -28,4 +28,4 @@ GOAT_PRIVATE_KEY= # libp2p config PEER_KEY= -PROTO_NAME=bitvm2_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest \ No newline at end of file diff --git a/deployment/regtest/bitvm-noded/committee_0/run.sh b/deployment/regtest/bitvm-noded/committee_0/run.sh new file mode 100644 index 000000000..16f8d988c --- /dev/null +++ b/deployment/regtest/bitvm-noded/committee_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 0.0.0.0:8900 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8444 >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/committee_1/.env.committee_1 b/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 similarity index 96% rename from deployment/regtest/bitvm2-nodes/committee_1/.env.committee_1 rename to deployment/regtest/bitvm-noded/committee_1/.env.committee_1 index 3a615664b..5eb00df85 100644 --- a/deployment/regtest/bitvm2-nodes/committee_1/.env.committee_1 +++ b/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 @@ -24,4 +24,4 @@ GOAT_PRIVATE_KEY= # libp2p config PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j -PROTO_NAME=bitvm2_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest \ No newline at end of file diff --git a/deployment/regtest/bitvm-noded/committee_1/run.sh b/deployment/regtest/bitvm-noded/committee_1/run.sh new file mode 100644 index 000000000..49b4e315f --- /dev/null +++ b/deployment/regtest/bitvm-noded/committee_1/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8901 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/operator_0/.env.operator_0 b/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 similarity index 97% rename from deployment/regtest/bitvm2-nodes/operator_0/.env.operator_0 rename to deployment/regtest/bitvm-noded/operator_0/.env.operator_0 index 25955ff29..33b863818 100644 --- a/deployment/regtest/bitvm2-nodes/operator_0/.env.operator_0 +++ b/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 @@ -24,7 +24,7 @@ GOAT_ADDRESS= # libp2p config PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j -PROTO_NAME=bitvm2_regtest +PROTO_NAME=bitvm_regtest ## Proof GENESIS_SEQUENCER_COMMIT_TXID=0a7f66f7b4befe841ebc51a53738fa35b833f66a3b001a90e37a751bd9b7713e diff --git a/deployment/regtest/bitvm-noded/operator_0/run.sh b/deployment/regtest/bitvm-noded/operator_0/run.sh new file mode 100644 index 000000000..9de067bba --- /dev/null +++ b/deployment/regtest/bitvm-noded/operator_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8446 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/start_nodes.sh b/deployment/regtest/bitvm-noded/start_nodes.sh similarity index 100% rename from deployment/regtest/bitvm2-nodes/start_nodes.sh rename to deployment/regtest/bitvm-noded/start_nodes.sh diff --git a/deployment/regtest/bitvm-noded/stop_nodes.sh b/deployment/regtest/bitvm-noded/stop_nodes.sh new file mode 100644 index 000000000..726eadb46 --- /dev/null +++ b/deployment/regtest/bitvm-noded/stop_nodes.sh @@ -0,0 +1 @@ +killall -9 bitvm-noded \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/watchtower_0/.env.watchtower_0 b/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 similarity index 97% rename from deployment/regtest/bitvm2-nodes/watchtower_0/.env.watchtower_0 rename to deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 index bca63f8b6..c6b018615 100644 --- a/deployment/regtest/bitvm2-nodes/watchtower_0/.env.watchtower_0 +++ b/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 @@ -25,4 +25,4 @@ ALWAYS_CHALLENGE=true # libp2p config PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j -PROTO_NAME=bitvm2_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest \ No newline at end of file diff --git a/deployment/regtest/bitvm-noded/watchtower_0/run.sh b/deployment/regtest/bitvm-noded/watchtower_0/run.sh new file mode 100644 index 000000000..53faf2138 --- /dev/null +++ b/deployment/regtest/bitvm-noded/watchtower_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8904 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8448 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/challenge_0/run.sh b/deployment/regtest/bitvm2-nodes/challenge_0/run.sh deleted file mode 100644 index 8baa1a1e2..000000000 --- a/deployment/regtest/bitvm2-nodes/challenge_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8450 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/committee_0/run.sh b/deployment/regtest/bitvm2-nodes/committee_0/run.sh deleted file mode 100644 index 1a2f0e64b..000000000 --- a/deployment/regtest/bitvm2-nodes/committee_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 0.0.0.0:8900 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8444 >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/committee_1/run.sh b/deployment/regtest/bitvm2-nodes/committee_1/run.sh deleted file mode 100644 index be819402d..000000000 --- a/deployment/regtest/bitvm2-nodes/committee_1/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8901 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/operator_0/run.sh b/deployment/regtest/bitvm2-nodes/operator_0/run.sh deleted file mode 100644 index 5746ad106..000000000 --- a/deployment/regtest/bitvm2-nodes/operator_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8446 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/stop_nodes.sh b/deployment/regtest/bitvm2-nodes/stop_nodes.sh deleted file mode 100644 index 1dc6e7b54..000000000 --- a/deployment/regtest/bitvm2-nodes/stop_nodes.sh +++ /dev/null @@ -1 +0,0 @@ -killall -9 bitvm2-noded \ No newline at end of file diff --git a/deployment/regtest/bitvm2-nodes/watchtower_0/run.sh b/deployment/regtest/bitvm2-nodes/watchtower_0/run.sh deleted file mode 100644 index e55b113f5..000000000 --- a/deployment/regtest/bitvm2-nodes/watchtower_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8904 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8448 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/regtest/proof-builder-rpc/.env.proof-builder-rpc b/deployment/regtest/proof-builder-rpc/.env.proof-builder-rpc index 9b36b2471..f8f00cd9c 100644 --- a/deployment/regtest/proof-builder-rpc/.env.proof-builder-rpc +++ b/deployment/regtest/proof-builder-rpc/.env.proof-builder-rpc @@ -21,4 +21,4 @@ ZKM_PROOF_POLL_INTERVAL=2000 # The log directory. ESPLORA_URL=http://localhost:13002 BITCOIN_NETWORK=regtest -DATABASE_URL=sqlite:/tmp/.bitvm2-node-sd.sqlite \ No newline at end of file +DATABASE_URL=sqlite:/tmp/.bitvm-node-sd.sqlite \ No newline at end of file diff --git a/deployment/regtest/proof-builder-rpc/proof-builder.toml b/deployment/regtest/proof-builder-rpc/proof-builder.toml index 114bacce7..4bd2386b2 100644 --- a/deployment/regtest/proof-builder-rpc/proof-builder.toml +++ b/deployment/regtest/proof-builder-rpc/proof-builder.toml @@ -1,3 +1,8 @@ +[api_auth] +# Fill these lists with the public keys derived from the deployed nodes' BITVM_SECRET values. +trusted_operator_public_keys = [] +trusted_watchtower_public_keys = [] + [header_chain] enable = true init_input = true diff --git a/deployment/testnet4/bitvm2-nodes/challenge_0/.env.challenge_0 b/deployment/testnet4/bitvm-noded/challenge_0/.env.challenge_0 similarity index 100% rename from deployment/testnet4/bitvm2-nodes/challenge_0/.env.challenge_0 rename to deployment/testnet4/bitvm-noded/challenge_0/.env.challenge_0 diff --git a/deployment/testnet4/bitvm-noded/challenge_0/run.sh b/deployment/testnet4/bitvm-noded/challenge_0/run.sh new file mode 100644 index 000000000..c8d6aa6ff --- /dev/null +++ b/deployment/testnet4/bitvm-noded/challenge_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8449 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & diff --git a/deployment/testnet4/bitvm2-nodes/committee_0/.env.committee_0 b/deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 similarity index 100% rename from deployment/testnet4/bitvm2-nodes/committee_0/.env.committee_0 rename to deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 diff --git a/deployment/testnet4/bitvm-noded/committee_0/run.sh b/deployment/testnet4/bitvm-noded/committee_0/run.sh new file mode 100644 index 000000000..c324fb028 --- /dev/null +++ b/deployment/testnet4/bitvm-noded/committee_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 >$PWD/$(date +'%Y%m%d').log 2>&1 & diff --git a/deployment/testnet4/bitvm2-nodes/committee_1/.env.committee_1 b/deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 similarity index 100% rename from deployment/testnet4/bitvm2-nodes/committee_1/.env.committee_1 rename to deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 diff --git a/deployment/testnet4/bitvm-noded/committee_1/run.sh b/deployment/testnet4/bitvm-noded/committee_1/run.sh new file mode 100644 index 000000000..d2213ccf8 --- /dev/null +++ b/deployment/testnet4/bitvm-noded/committee_1/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8901 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8444 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/operator_0/.env.operator_0 b/deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 similarity index 100% rename from deployment/testnet4/bitvm2-nodes/operator_0/.env.operator_0 rename to deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 diff --git a/deployment/testnet4/bitvm-noded/operator_0/run.sh b/deployment/testnet4/bitvm-noded/operator_0/run.sh new file mode 100644 index 000000000..a7b0246a0 --- /dev/null +++ b/deployment/testnet4/bitvm-noded/operator_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/start_nodes.sh b/deployment/testnet4/bitvm-noded/start_nodes.sh similarity index 100% rename from deployment/testnet4/bitvm2-nodes/start_nodes.sh rename to deployment/testnet4/bitvm-noded/start_nodes.sh diff --git a/deployment/testnet4/bitvm-noded/stop_nodes.sh b/deployment/testnet4/bitvm-noded/stop_nodes.sh new file mode 100644 index 000000000..726eadb46 --- /dev/null +++ b/deployment/testnet4/bitvm-noded/stop_nodes.sh @@ -0,0 +1 @@ +killall -9 bitvm-noded \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/watchtower_0/.env.watchtower_0 b/deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 similarity index 100% rename from deployment/testnet4/bitvm2-nodes/watchtower_0/.env.watchtower_0 rename to deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 diff --git a/deployment/testnet4/bitvm-noded/watchtower_0/run.sh b/deployment/testnet4/bitvm-noded/watchtower_0/run.sh new file mode 100644 index 000000000..9f67f22b3 --- /dev/null +++ b/deployment/testnet4/bitvm-noded/watchtower_0/run.sh @@ -0,0 +1 @@ +nohup ../bitvm-noded --rpc-addr 127.0.0.1:8904 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8447 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/challenge_0/run.sh b/deployment/testnet4/bitvm2-nodes/challenge_0/run.sh deleted file mode 100644 index 93d995b21..000000000 --- a/deployment/testnet4/bitvm2-nodes/challenge_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8906 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8449 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & diff --git a/deployment/testnet4/bitvm2-nodes/committee_0/run.sh b/deployment/testnet4/bitvm2-nodes/committee_0/run.sh deleted file mode 100644 index 68772046b..000000000 --- a/deployment/testnet4/bitvm2-nodes/committee_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 >$PWD/$(date +'%Y%m%d').log 2>&1 & diff --git a/deployment/testnet4/bitvm2-nodes/committee_1/run.sh b/deployment/testnet4/bitvm2-nodes/committee_1/run.sh deleted file mode 100644 index 6b9364a68..000000000 --- a/deployment/testnet4/bitvm2-nodes/committee_1/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8901 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8444 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/operator_0/run.sh b/deployment/testnet4/bitvm2-nodes/operator_0/run.sh deleted file mode 100644 index 343709351..000000000 --- a/deployment/testnet4/bitvm2-nodes/operator_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8902 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8445 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/stop_nodes.sh b/deployment/testnet4/bitvm2-nodes/stop_nodes.sh deleted file mode 100644 index 1dc6e7b54..000000000 --- a/deployment/testnet4/bitvm2-nodes/stop_nodes.sh +++ /dev/null @@ -1 +0,0 @@ -killall -9 bitvm2-noded \ No newline at end of file diff --git a/deployment/testnet4/bitvm2-nodes/watchtower_0/run.sh b/deployment/testnet4/bitvm2-nodes/watchtower_0/run.sh deleted file mode 100644 index 861361356..000000000 --- a/deployment/testnet4/bitvm2-nodes/watchtower_0/run.sh +++ /dev/null @@ -1 +0,0 @@ -nohup ../bitvm2-noded --rpc-addr 127.0.0.1:8904 --db-path sqlite:$PWD/bitvm2-node.db --p2p-port 8447 --bootnodes $bootnode_urls >$PWD/$(date +'%Y%m%d').log 2>&1 & \ No newline at end of file diff --git a/docs/deploy_docs.sh b/docs/deploy_docs.sh index 88c620ed3..cba9e02e5 100755 --- a/docs/deploy_docs.sh +++ b/docs/deploy_docs.sh @@ -1,16 +1,16 @@ #!/bin/bash -# BitVM2 Node API Documentation Deployment Script +# BitVM Node API Documentation Deployment Script # Deploy API documentation with Docker set -e -echo "BitVM2 Documentation Deployment Script" +echo "BitVM Documentation Deployment Script" echo "" # Configuration variables DOCS_SOURCE_DIR="docs/api" -CONTAINER_NAME="${CONTAINER_NAME:-bitvm2-docs}" +CONTAINER_NAME="${CONTAINER_NAME:-bitvm-docs}" CONTAINER_PORT="${CONTAINER_PORT:-8080}" # Show usage @@ -20,7 +20,7 @@ show_usage() { echo "" echo "Options:" echo " -p, --port PORT Container port [default: 8080]" - echo " -n, --name NAME Container name [default: bitvm2-docs]" + echo " -n, --name NAME Container name [default: bitvm-docs]" echo " --help Show help information" echo "" echo "Environment Variables:" @@ -108,7 +108,7 @@ EOF # Build Docker image echo "Building Docker image..." - docker build -f scripts/DocServer_Dockerfile -t bitvm2-docs:latest . + docker build -f scripts/DocServer_Dockerfile -t bitvm-docs:latest . if [ $? -ne 0 ]; then echo "Docker image build failed!" @@ -123,7 +123,7 @@ EOF -p ${CONTAINER_PORT}:80 \ --name ${CONTAINER_NAME} \ --restart unless-stopped \ - bitvm2-docs:latest + bitvm-docs:latest if [ $? -eq 0 ]; then echo "" diff --git a/docs/generate_api_docs.sh b/docs/generate_api_docs.sh index afdd5d390..19b6d6023 100755 --- a/docs/generate_api_docs.sh +++ b/docs/generate_api_docs.sh @@ -1,11 +1,11 @@ #!/bin/bash -# BitVM2 Node API Documentation Generation Script +# BitVM Node API Documentation Generation Script # Generate complete API documentation using rust-doc set -e -echo "Starting BitVM2 Node API documentation generation..." +echo "Starting BitVM Node API documentation generation..." # Check if we're in the correct directory if [ ! -f "Cargo.toml" ]; then @@ -38,8 +38,8 @@ if [ $? -eq 0 ]; then echo "" echo "Documentation locations:" echo " - HTML documentation: $DOCS_DIR/index.html" - echo " - Main documentation: $DOCS_DIR/bitvm2_noded/index.html" - echo " - RPC service docs: $DOCS_DIR/bitvm2_noded/rpc_service/index.html" + echo " - Main documentation: $DOCS_DIR/bitvm_noded/index.html" + echo " - RPC service docs: $DOCS_DIR/bitvm_noded/rpc_service/index.html" echo "" echo "View documentation:" echo " - Open in browser: $DOCS_DIR/index.html" @@ -72,10 +72,10 @@ if [ $? -eq 0 ]; then echo " Checking for alternative locations..." # Look for documentation in other possible locations - if [ -d "$DOCS_DIR/bitvm2_noded" ]; then - echo " Found documentation in: $DOCS_DIR/bitvm2_noded/" - if [ -f "$DOCS_DIR/bitvm2_noded/index.html" ]; then - echo " Main documentation: $DOCS_DIR/bitvm2_noded/index.html" + if [ -d "$DOCS_DIR/bitvm_noded" ]; then + echo " Found documentation in: $DOCS_DIR/bitvm_noded/" + if [ -f "$DOCS_DIR/bitvm_noded/index.html" ]; then + echo " Main documentation: $DOCS_DIR/bitvm_noded/index.html" fi fi fi diff --git a/docs/serve_docs.sh b/docs/serve_docs.sh index b67e162ac..17d564ff9 100755 --- a/docs/serve_docs.sh +++ b/docs/serve_docs.sh @@ -1,6 +1,6 @@ #!/bin/bash -# BitVM2 Node API Documentation Server +# BitVM Node API Documentation Server # Use Python built-in server to provide documentation access set -e @@ -60,7 +60,7 @@ while [[ $# -gt 0 ]]; do esac done -echo "Starting BitVM2 API Documentation Server..." +echo "Starting BitVM API Documentation Server..." echo "" # Check if documentation exists diff --git a/node/src/rpc_service/handler/bitvm2_handler.rs b/node/src/rpc_service/handler/bitvm_handler.rs similarity index 99% rename from node/src/rpc_service/handler/bitvm2_handler.rs rename to node/src/rpc_service/handler/bitvm_handler.rs index 8963dc179..6979c5048 100644 --- a/node/src/rpc_service/handler/bitvm2_handler.rs +++ b/node/src/rpc_service/handler/bitvm_handler.rs @@ -765,7 +765,6 @@ pub async fn get_instance( pub async fn get_instances_overview( State(app_state): State>, ) -> ApiResult { - // todo update bridge out calc let mut storage_process = app_state.local_db.acquire().await.api_error("INSTANCE_OVERVIEW_ERROR")?; diff --git a/node/src/rpc_service/handler/mod.rs b/node/src/rpc_service/handler/mod.rs index d951a7c6e..db8d4420e 100644 --- a/node/src/rpc_service/handler/mod.rs +++ b/node/src/rpc_service/handler/mod.rs @@ -1,10 +1,10 @@ -pub mod bitvm2_handler; +pub mod bitvm_handler; pub mod debug_handler; pub mod node_handler; pub mod proof_handler; // Re-export all handler functions for better documentation visibility -pub use bitvm2_handler::*; +pub use bitvm_handler::*; pub use debug_handler::*; pub use node_handler::*; pub use proof_handler::*; diff --git a/node/src/utils.rs b/node/src/utils.rs index 55a344797..da9298939 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -88,6 +88,7 @@ use bitvm_lib::babe_adapter::{ use bitvm_lib::transactions::base::BaseTransaction; use client::goat_chain::{DisproveTxType, GraphData, PeginStatus, WithdrawStatus}; use client::http_client::async_client::HttpAsyncClient; +use proof_builder::api_auth::{ProofBuilderAuthRole, sign_proof_builder_request}; use proof_builder::{ OperatorProofRequest, OperatorProofResponse, ProofData, WatchtowerProofRequest, WatchtowerProofResponse, WatchtowerProofTimeoutUpdateRequest, @@ -2393,6 +2394,16 @@ fn gen_watchtower_commitment(graph_id: Uuid, proof_data: ProofData) -> Result( + keypair: &Keypair, + role: ProofBuilderAuthRole, + path: &str, + body: &B, +) -> Result> { + Ok(sign_proof_builder_request(keypair, role, "POST", path, body)?.to_header_pairs()) +} + /// Returns: /// - `Ok(Some(WatchtowerCommitment), _)` if watchtower proof is available /// - `Ok(None, wait_secs)` if watchtower proof is not yet available, with suggested wait time @@ -2420,15 +2431,25 @@ pub async fn get_watchtower_commitment( )?; let url = base_url.join(NODES_WATCHTOWER_BASE)?; + let payload = WatchtowerProofRequest { + instance_id: instance_id.to_string(), + graph_id: graph_id.to_string(), + public_key: env::get_node_pubkey()?.to_string(), + challenge_init_txid: challenge_init_txid.0.to_string(), + execution_layer_block_number: graph.proceed_withdraw_height, // NOTE: this number may be zero + }; + let auth_keypair = get_bitvm_key()?; let response = http_client - .post_response_json::( + .post_response_json_with_dynamic_headers::( url.as_str(), - &WatchtowerProofRequest { - instance_id: instance_id.to_string(), - graph_id: graph_id.to_string(), - public_key: env::get_node_pubkey()?.to_string(), - challenge_init_txid: challenge_init_txid.0.to_string(), - execution_layer_block_number: graph.proceed_withdraw_height, // NOTE: this number may be zero + &payload, + || { + proof_builder_auth_headers( + &auth_keypair, + ProofBuilderAuthRole::Watchtower, + NODES_WATCHTOWER_BASE, + &payload, + ) }, ) .await?; @@ -2718,23 +2739,33 @@ pub async fn get_operator_proof( .ok_or_else(|| anyhow::anyhow!("failed to get proof_build_rpc_host"))?, )?; let operator_url = base_url.join(NODES_OPERATOR_BASE)?; + let payload = OperatorProofRequest { + instance_id: instance_id.to_string(), + graph_id: graph_id.to_string(), + operator_committed_blockhash, + execution_layer_block_number: graph.proceed_withdraw_height, + watchtower_challenge_txids, + included_watchtowers, + watchtower_challenge_init_txid: watchtower_challenge_init_txid.0.to_string(), + watchtower_challenge_pubkeys: bitvm_graph + .parameters + .watchtower_pubkeys + .iter() + .map(|pk| pk.public_key(secp256k1::Parity::Even).to_string()) + .collect(), + }; + let auth_keypair = get_bitvm_key()?; let operator_response = http_client - .post_response_json::( + .post_response_json_with_dynamic_headers::( operator_url.as_str(), - &OperatorProofRequest { - instance_id: instance_id.to_string(), - graph_id: graph_id.to_string(), - operator_committed_blockhash, - execution_layer_block_number: graph.proceed_withdraw_height, - watchtower_challenge_txids, - included_watchtowers, - watchtower_challenge_init_txid: watchtower_challenge_init_txid.0.to_string(), - watchtower_challenge_pubkeys: bitvm_graph - .parameters - .watchtower_pubkeys - .iter() - .map(|pk| pk.public_key(secp256k1::Parity::Even).to_string()) - .collect(), + &payload, + || { + proof_builder_auth_headers( + &auth_keypair, + ProofBuilderAuthRole::Operator, + NODES_OPERATOR_BASE, + &payload, + ) }, ) .await?; @@ -3826,6 +3857,9 @@ pub async fn notify_to_cancel_proof_task( warn!("notify_to_cancel_proof_task: input wrong message type:{msg_type}"); return Ok(()); } + if get_actor() != Actor::Watchtower { + return Ok(()); + } let host = match get_proof_build_rpc_host() { Some(host) => Url::parse(&host)?, @@ -3852,14 +3886,23 @@ pub async fn notify_to_cancel_proof_task( let notify_result = match msg_type { MessageType::WatchtowerChallengeInitSent => { let url = host.join(PROOFS_WATCHTOWER_PROOF_TIMEOUT)?; + let payload = WatchtowerProofTimeoutUpdateRequest { + instance_id: graph.instance_id.to_string(), + graph_id: graph.graph_id.to_string(), + public_key: get_node_pubkey()?.to_string(), + }; + let auth_keypair = get_bitvm_key()?; let response = http_client - .post_response_json::( + .post_response_json_with_dynamic_headers::( url.as_str(), - &WatchtowerProofTimeoutUpdateRequest { - instance_id: graph.instance_id.to_string(), - graph_id: graph.graph_id.to_string(), - public_key: get_node_pubkey()?.to_string(), - + &payload, + || { + proof_builder_auth_headers( + &auth_keypair, + ProofBuilderAuthRole::Watchtower, + PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &payload, + ) }, ) .await?; diff --git a/node/tla/GraphLifecycle.tla b/node/tla/GraphLifecycle.tla index cfceac541..2331e5e3d 100644 --- a/node/tla/GraphLifecycle.tla +++ b/node/tla/GraphLifecycle.tla @@ -1,6 +1,6 @@ ---- MODULE GraphLifecycle ---- (***************************************************************************) -(* Formal model of a single BitVM2 graph's `status` column, built from the *) +(* Formal model of a single BitVM graph's `status` column, built from the *) (* ACTUAL Rust implementation (not the README, which was found to be *) (* stale in several ways - see git history / PR description for details). *) (* Every action below cites the exact code it models. *) diff --git a/node/tla/InstanceBridgeOutRace.tla b/node/tla/InstanceBridgeOutRace.tla index febc366dc..8e6730a37 100644 --- a/node/tla/InstanceBridgeOutRace.tla +++ b/node/tla/InstanceBridgeOutRace.tla @@ -6,7 +6,7 @@ (* `InstanceBridgeOutStatus` (crates/store/src/schema.rs:223-229) is *) (* written from three independently-scheduled, uncoordinated tasks with *) (* no shared transaction spanning read+decide+write in any of them: *) -(* - the RPC-service task (node/src/rpc_service/handler/bitvm2_handler.rs, *) +(* - the RPC-service task (node/src/rpc_service/handler/bitvm_handler.rs, *) (* `bridge_out_init_tag`) - stale-read-then-full-row-upsert, sets *) (* Initialize. *) (* - the GoatChain L2-event watcher (node/src/scheduled_tasks/ *) @@ -44,7 +44,7 @@ Init == status = "Initialize" (* Every action below is unconditional on the current status - confirmed *) (* real behavior for all three writers, not a modeling simplification. *) -RpcStaleInit == status' = "Initialize" \* bitvm2_handler.rs bridge_out_init_tag, stale full-row upsert +RpcStaleInit == status' = "Initialize" \* bitvm_handler.rs bridge_out_init_tag, stale full-row upsert WatchEventClaim == status' = "Claim" \* event_watch_task.rs handle_swap_claim_events WatchEventRefund == status' = "Refund" \* event_watch_task.rs handle_swap_refund_events MaintenanceTimeout == status' = "Timeout" \* instance_maintenance_tasks.rs instance_bridge_out_monitor diff --git a/proof-builder-rpc/Cargo.toml b/proof-builder-rpc/Cargo.toml index e478e1de6..a98bf0874 100644 --- a/proof-builder-rpc/Cargo.toml +++ b/proof-builder-rpc/Cargo.toml @@ -24,6 +24,7 @@ http-body-util.workspace = true prometheus-client = { workspace = true } uuid = { workspace = true } bitcoin = { workspace = true } +secp256k1 = { workspace = true } strum = { workspace = true } serde_json = { workspace = true } alloy-primitives = { workspace = true } diff --git a/proof-builder-rpc/README.md b/proof-builder-rpc/README.md index afcb67ec3..f606ccf4e 100644 --- a/proof-builder-rpc/README.md +++ b/proof-builder-rpc/README.md @@ -14,6 +14,24 @@ Initial parameters are read from `proof-builder.toml` (see [proof-builder.toml]( Field descriptions for the configuration are available in the circuits documentation: [circuits README](../circuits/README.md). +The four Operator/Watchtower task endpoints require signed requests. Configure the trusted node +public keys before startup; keys may be compressed or x-only secp256k1 public keys: + +```toml +[api_auth] +trusted_operator_public_keys = [""] +trusted_watchtower_public_keys = [""] +``` + +Both lists are required and must be non-empty. Nodes sign requests with their existing +`BITVM_SECRET`; no private key is configured on the Proof Builder. Deploy signing-capable nodes +before enabling the authenticated Proof Builder so that in-flight proof polling is not rejected. + +Authenticated requests carry `x-proof-auth-timestamp`, `x-proof-auth-nonce`, +`x-proof-auth-public-key`, and `x-proof-auth-signature`. The signature binds the caller role, +HTTP method, route, timestamp, nonce, and canonical JSON body. Rust callers should use +`proof_builder::api_auth::sign_proof_builder_request`; each retry must generate a new nonce. + ## Failure recovery Long-running proof tasks (stored in the `long_running_task_proof` table) — such as header-chain, commit-chain, and state-chain proofs — can be recovered from the database. Recovery notes: @@ -54,4 +72,4 @@ $CMD sign-seq --owner-btc-key-wif $PUBLISHER3 --goat-block-number $GOAT_BLOCK_NU $CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.latest" ``` -- **State Chain**: For sequencer-set genesis commitments or when re-anchoring the state chain, modify the latest record of state chain in `long_running_task_proof` with `block_end = 0` and `proof_state = 'Failed'` to trigger recovery handling. \ No newline at end of file +- **State Chain**: For sequencer-set genesis commitments or when re-anchoring the state chain, modify the latest record of state chain in `long_running_task_proof` with `block_end = 0` and `proof_state = 'Failed'` to trigger recovery handling. diff --git a/proof-builder-rpc/proof-builder.toml.example b/proof-builder-rpc/proof-builder.toml.example index 96dce504f..c51d34a0a 100644 --- a/proof-builder-rpc/proof-builder.toml.example +++ b/proof-builder-rpc/proof-builder.toml.example @@ -1,3 +1,9 @@ +[api_auth] +# Required. Accepts compressed (66 hex chars) or x-only (64 hex chars) secp256k1 public keys. +# Populate both lists before starting proof-builder-rpc; empty lists fail closed. +trusted_operator_public_keys = [] +trusted_watchtower_public_keys = [] + [header_chain] enable = false init_input = false diff --git a/proof-builder-rpc/src/api/auth.rs b/proof-builder-rpc/src/api/auth.rs new file mode 100644 index 000000000..cd5ea75a4 --- /dev/null +++ b/proof-builder-rpc/src/api/auth.rs @@ -0,0 +1,261 @@ +use crate::api::response::ErrorResponse; +use crate::config::TrustedApiKeys; +use axum::Json; +use axum::http::{HeaderMap, StatusCode}; +use proof_builder::api_auth::{ + AUTH_NONCE_HEADER, AUTH_PUBLIC_KEY_HEADER, AUTH_SIGNATURE_HEADER, AUTH_TIMESTAMP_HEADER, + AUTH_WINDOW_SECS, ProofBuilderAuthRole, normalize_public_key, + verify_proof_builder_request_signature, +}; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; +use std::sync::Mutex; + +type AuthResult = Result)>; + +pub(crate) struct RequestAuthorizer { + trusted_operator_keys: HashSet, + trusted_watchtower_keys: HashSet, + accepted_nonces: Mutex>, +} + +impl RequestAuthorizer { + /// Creates a request authorizer from validated role-specific public keys. + pub(crate) fn new(keys: TrustedApiKeys) -> Self { + Self { + trusted_operator_keys: keys.operator, + trusted_watchtower_keys: keys.watchtower, + accepted_nonces: Mutex::new(HashMap::new()), + } + } + + /// Authenticates and authorizes a request for one Proof Builder role. + pub(crate) fn authorize( + &self, + headers: &HeaderMap, + role: ProofBuilderAuthRole, + method: &str, + path: &str, + body: &B, + claimed_watchtower_public_key: Option<&str>, + ) -> AuthResult<()> { + let timestamp = required_header(headers, AUTH_TIMESTAMP_HEADER)?; + let nonce = required_header(headers, AUTH_NONCE_HEADER)?; + let signer_value = required_header(headers, AUTH_PUBLIC_KEY_HEADER)?; + let signature = required_header(headers, AUTH_SIGNATURE_HEADER)?; + let signer = normalize_public_key(signer_value) + .map_err(|_| unauthorized("invalid signer public key"))?; + + let trusted = match role { + ProofBuilderAuthRole::Operator => &self.trusted_operator_keys, + ProofBuilderAuthRole::Watchtower => &self.trusted_watchtower_keys, + }; + if !trusted.contains(&signer) { + return Err(forbidden("signer is not trusted for this role")); + } + + if let Some(claimed_public_key) = claimed_watchtower_public_key { + let claimed = normalize_public_key(claimed_public_key) + .map_err(|_| forbidden("invalid watchtower public key"))?; + if claimed != signer { + return Err(forbidden("watchtower signer does not match request public key")); + } + } + + verify_proof_builder_request_signature( + role, method, path, timestamp, nonce, &signer, signature, body, + ) + .map_err(|_| unauthorized("invalid request signature"))?; + self.record_nonce(&signer.to_string(), nonce)?; + Ok(()) + } + + /// Atomically rejects a nonce already accepted from the same signer. + fn record_nonce(&self, signer: &str, nonce: &str) -> AuthResult<()> { + let now = current_time_secs(); + let mut accepted_nonces = self + .accepted_nonces + .lock() + .map_err(|_| internal_error("authentication replay cache is unavailable"))?; + accepted_nonces.retain(|_, accepted_at| now - *accepted_at <= AUTH_WINDOW_SECS); + if accepted_nonces.insert((signer.to_string(), nonce.to_string()), now).is_some() { + return Err(unauthorized("request nonce has already been used")); + } + Ok(()) + } +} + +/// Reads one required UTF-8 authentication header. +fn required_header<'a>(headers: &'a HeaderMap, name: &str) -> AuthResult<&'a str> { + headers + .get(name) + .and_then(|value| value.to_str().ok()) + .ok_or_else(|| unauthorized(&format!("missing or invalid {name} header"))) +} + +/// Builds a 401 response for missing or invalid authentication credentials. +fn unauthorized(message: &str) -> (StatusCode, Json) { + auth_error(StatusCode::UNAUTHORIZED, message) +} + +/// Builds a 403 response for an authenticated identity without the required role or ownership. +fn forbidden(message: &str) -> (StatusCode, Json) { + auth_error(StatusCode::FORBIDDEN, message) +} + +/// Builds a 500 response when the local authentication state cannot be used safely. +fn internal_error(message: &str) -> (StatusCode, Json) { + auth_error(StatusCode::INTERNAL_SERVER_ERROR, message) +} + +/// Builds the common JSON error returned by the Proof Builder authentication boundary. +fn auth_error(status: StatusCode, message: &str) -> (StatusCode, Json) { + ( + status, + Json(ErrorResponse { + error: "PROOF_BUILDER_AUTH_ERROR".to_string(), + message: message.into(), + }), + ) +} + +/// Returns the current Unix time used to expire replay-cache entries. +fn current_time_secs() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_secs() as i64 +} + +#[cfg(test)] +mod tests { + use super::*; + use proof_builder::api_auth::{ProofBuilderAuthHeaders, sign_proof_builder_request}; + use secp256k1::{Keypair, SECP256K1}; + use serde::Serialize; + + #[derive(Serialize)] + struct TestBody { + public_key: String, + value: u64, + } + + fn keypair(seed: u8) -> Keypair { + Keypair::from_seckey_slice(SECP256K1, &[seed; 32]).unwrap() + } + + fn headers(values: &ProofBuilderAuthHeaders) -> HeaderMap { + let mut headers = HeaderMap::new(); + for (name, value) in values.to_header_pairs() { + headers.insert(name.parse::().unwrap(), value.parse().unwrap()); + } + headers + } + + fn authorizer(operator: &Keypair, watchtower: &Keypair) -> RequestAuthorizer { + RequestAuthorizer::new(TrustedApiKeys { + operator: HashSet::from([operator.x_only_public_key().0]), + watchtower: HashSet::from([watchtower.x_only_public_key().0]), + }) + } + + #[test] + fn accepts_trusted_signer_once_and_rejects_replay() { + let operator = keypair(7); + let watchtower = keypair(9); + let authorizer = authorizer(&operator, &watchtower); + let body = TestBody { public_key: operator.public_key().to_string(), value: 1 }; + let signed = sign_proof_builder_request( + &operator, + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &body, + ) + .unwrap(); + let headers = headers(&signed); + + assert!( + authorizer + .authorize( + &headers, + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &body, + None, + ) + .is_ok() + ); + assert_eq!( + authorizer + .authorize( + &headers, + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &body, + None, + ) + .unwrap_err() + .0, + StatusCode::UNAUTHORIZED + ); + } + + #[test] + fn rejects_cross_role_and_mismatched_watchtower_identity() { + let operator = keypair(7); + let watchtower = keypair(9); + let other_watchtower = keypair(11); + let authorizer = authorizer(&operator, &watchtower); + let operator_body = TestBody { public_key: watchtower.public_key().to_string(), value: 1 }; + let cross_role = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &operator_body, + ) + .unwrap(); + assert_eq!( + authorizer + .authorize( + &headers(&cross_role), + ProofBuilderAuthRole::Operator, + "POST", + "/v1/proofs/operator_proofs", + &operator_body, + None, + ) + .unwrap_err() + .0, + StatusCode::FORBIDDEN + ); + + let watchtower_body = + TestBody { public_key: other_watchtower.public_key().to_string(), value: 1 }; + let signed = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Watchtower, + "POST", + "/v1/proofs/watchtower_proofs", + &watchtower_body, + ) + .unwrap(); + assert_eq!( + authorizer + .authorize( + &headers(&signed), + ProofBuilderAuthRole::Watchtower, + "POST", + "/v1/proofs/watchtower_proofs", + &watchtower_body, + Some(&watchtower_body.public_key), + ) + .unwrap_err() + .0, + StatusCode::FORBIDDEN + ); + } +} diff --git a/proof-builder-rpc/src/api/mod.rs b/proof-builder-rpc/src/api/mod.rs index b24288b54..1a4463cd9 100644 --- a/proof-builder-rpc/src/api/mod.rs +++ b/proof-builder-rpc/src/api/mod.rs @@ -1,15 +1,18 @@ +mod auth; pub(crate) mod metrics_service; mod proof_handler; mod response; pub(crate) mod routes; mod validation; +use crate::api::auth::RequestAuthorizer; use crate::api::metrics_service::{ApiMetricsState, metrics_handler, metrics_middleware}; use crate::api::proof_handler::{ get_chain_proof_task_desc, get_operator_proof_task_desc, post_operator_proof_task, post_watchtower_proof_task, update_operator_proof_task_timeout, update_watchtower_proof_task_timeout, }; +use crate::config::TrustedApiKeys; use axum::http::{Method, StatusCode}; use axum::routing::{get, post}; use axum::{Router, middleware}; @@ -22,21 +25,31 @@ use tower_http::cors::{Any, CorsLayer}; struct ApiState { pub local_db: LocalDB, pub metrics_state: ApiMetricsState, + pub auth: RequestAuthorizer, } impl ApiState { - /// Creates shared API state from the database and metrics registry. - fn new(local_db: LocalDB, metrics_state: ApiMetricsState) -> Arc { - Arc::new(ApiState { local_db, metrics_state }) + /// Creates shared API state from the database, metrics, and trusted caller keys. + fn new( + local_db: LocalDB, + metrics_state: ApiMetricsState, + trusted_api_keys: TrustedApiKeys, + ) -> Arc { + Arc::new(ApiState { + local_db, + metrics_state, + auth: RequestAuthorizer::new(trusted_api_keys), + }) } } pub(crate) async fn serve( addr: String, local_db: LocalDB, metrics_state: ApiMetricsState, + trusted_api_keys: TrustedApiKeys, cancellation_token: CancellationToken, ) -> anyhow::Result { - let api_state = ApiState::new(local_db, metrics_state); + let api_state = ApiState::new(local_db, metrics_state, trusted_api_keys); let instrumented_routes = Router::new() .route(routes::ROOT, get(root)) .route(routes::v1::PROOFS_CHAIN_PROOFS_DESC, get(get_chain_proof_task_desc)) @@ -86,6 +99,15 @@ async fn root() -> &'static str { #[cfg(test)] mod tests { use super::*; + use proof_builder::api_auth::{ + ProofBuilderAuthHeaders, ProofBuilderAuthRole, sign_proof_builder_request, + }; + use proof_builder::{ + OperatorProofRequest, OperatorProofTimeoutUpdateRequest, WatchtowerProofRequest, + WatchtowerProofTimeoutUpdateRequest, + }; + use secp256k1::{Keypair, SECP256K1}; + use std::collections::HashSet; use tokio::io::{AsyncReadExt, AsyncWriteExt}; fn available_addr() -> String { @@ -106,6 +128,46 @@ mod tests { Ok(String::from_utf8(response)?) } + async fn post( + addr: &str, + path: &str, + body: &str, + auth: Option<&ProofBuilderAuthHeaders>, + ) -> anyhow::Result { + let mut stream = tokio::net::TcpStream::connect(addr).await?; + let auth_headers = auth + .map(|auth| { + auth.to_header_pairs() + .into_iter() + .map(|(name, value)| format!("{name}: {value}\r\n")) + .collect::() + }) + .unwrap_or_default(); + stream + .write_all( + format!( + "POST {path} HTTP/1.1\r\nHost: {addr}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n{auth_headers}Connection: close\r\n\r\n{body}", + body.len() + ) + .as_bytes(), + ) + .await?; + let mut response = Vec::new(); + stream.read_to_end(&mut response).await?; + Ok(String::from_utf8(response)?) + } + + fn keypair(seed: u8) -> Keypair { + Keypair::from_seckey_slice(SECP256K1, &[seed; 32]).unwrap() + } + + fn trusted_api_keys(operator: &Keypair, watchtower: &Keypair) -> TrustedApiKeys { + TrustedApiKeys { + operator: HashSet::from([operator.x_only_public_key().0]), + watchtower: HashSet::from([watchtower.x_only_public_key().0]), + } + } + #[tokio::test] async fn metrics_use_route_templates_and_exclude_scrapes() -> anyhow::Result<()> { let addr = available_addr(); @@ -115,6 +177,7 @@ mod tests { addr.clone(), store::create_local_db("sqlite::memory:").await, ApiMetricsState::new(), + trusted_api_keys(&keypair(7), &keypair(9)), server_token, )); tokio::time::sleep(std::time::Duration::from_millis(100)).await; @@ -141,4 +204,174 @@ mod tests { server.await??; Ok(()) } + + #[tokio::test] + async fn operator_and_watchtower_task_routes_enforce_role_authentication() -> anyhow::Result<()> + { + let operator = keypair(7); + let watchtower = keypair(9); + let other_watchtower = keypair(11); + let addr = available_addr(); + let cancellation_token = CancellationToken::new(); + let server_token = cancellation_token.clone(); + let server = tokio::spawn(serve( + addr.clone(), + store::create_local_db("sqlite::memory:").await, + ApiMetricsState::new(), + trusted_api_keys(&operator, &watchtower), + server_token, + )); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + + let instance_id = "00112233-4455-6677-8899-aabbccddeeff".to_string(); + let graph_id = "11112233-4455-6677-8899-aabbccddeeff".to_string(); + let operator_submit = OperatorProofRequest { + instance_id: instance_id.clone(), + graph_id: graph_id.clone(), + operator_committed_blockhash: "11".repeat(32), + execution_layer_block_number: 1, + watchtower_challenge_txids: vec![], + included_watchtowers: vec![], + watchtower_challenge_init_txid: "22".repeat(32), + watchtower_challenge_pubkeys: vec![], + }; + let operator_submit_body = serde_json::to_string(&operator_submit)?; + assert!( + post(&addr, routes::v1::PROOFS_OPERATOR_PROOF, &operator_submit_body, None,) + .await? + .starts_with("HTTP/1.1 401") + ); + let operator_submit_auth = sign_proof_builder_request( + &operator, + ProofBuilderAuthRole::Operator, + "POST", + routes::v1::PROOFS_OPERATOR_PROOF, + &operator_submit, + )?; + assert!( + post( + &addr, + routes::v1::PROOFS_OPERATOR_PROOF, + &operator_submit_body, + Some(&operator_submit_auth), + ) + .await? + .starts_with("HTTP/1.1 200") + ); + + let operator_timeout = OperatorProofTimeoutUpdateRequest { + instance_id: instance_id.clone(), + graph_id: graph_id.clone(), + }; + let operator_timeout_body = serde_json::to_string(&operator_timeout)?; + let wrong_role_auth = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Operator, + "POST", + routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, + &operator_timeout, + )?; + assert!( + post( + &addr, + routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, + &operator_timeout_body, + Some(&wrong_role_auth), + ) + .await? + .starts_with("HTTP/1.1 403") + ); + let operator_timeout_auth = sign_proof_builder_request( + &operator, + ProofBuilderAuthRole::Operator, + "POST", + routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, + &operator_timeout, + )?; + let operator_timeout_response = post( + &addr, + routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, + &operator_timeout_body, + Some(&operator_timeout_auth), + ) + .await?; + assert!(operator_timeout_response.starts_with("HTTP/1.1 200")); + assert!(operator_timeout_response.contains("1 rows affected")); + + let watchtower_submit = WatchtowerProofRequest { + instance_id: instance_id.clone(), + graph_id: graph_id.clone(), + public_key: watchtower.public_key().to_string(), + challenge_init_txid: "33".repeat(32), + execution_layer_block_number: 1, + }; + let watchtower_submit_body = serde_json::to_string(&watchtower_submit)?; + let watchtower_submit_auth = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Watchtower, + "POST", + routes::v1::PROOFS_WATCHTOWER_PROOF, + &watchtower_submit, + )?; + assert!( + post( + &addr, + routes::v1::PROOFS_WATCHTOWER_PROOF, + &watchtower_submit_body, + Some(&watchtower_submit_auth), + ) + .await? + .starts_with("HTTP/1.1 200") + ); + + let watchtower_timeout = WatchtowerProofTimeoutUpdateRequest { + instance_id, + graph_id, + public_key: watchtower.public_key().to_string(), + }; + let watchtower_timeout_body = serde_json::to_string(&watchtower_timeout)?; + let watchtower_timeout_auth = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Watchtower, + "POST", + routes::v1::PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &watchtower_timeout, + )?; + let watchtower_timeout_response = post( + &addr, + routes::v1::PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &watchtower_timeout_body, + Some(&watchtower_timeout_auth), + ) + .await?; + assert!(watchtower_timeout_response.starts_with("HTTP/1.1 200")); + assert!(watchtower_timeout_response.contains("1 rows affected")); + + let mismatched_timeout = WatchtowerProofTimeoutUpdateRequest { + public_key: other_watchtower.public_key().to_string(), + ..watchtower_timeout + }; + let mismatched_timeout_body = serde_json::to_string(&mismatched_timeout)?; + let mismatched_timeout_auth = sign_proof_builder_request( + &watchtower, + ProofBuilderAuthRole::Watchtower, + "POST", + routes::v1::PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &mismatched_timeout, + )?; + assert!( + post( + &addr, + routes::v1::PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &mismatched_timeout_body, + Some(&mismatched_timeout_auth), + ) + .await? + .starts_with("HTTP/1.1 403") + ); + + cancellation_token.cancel(); + server.await??; + Ok(()) + } } diff --git a/proof-builder-rpc/src/api/proof_handler.rs b/proof-builder-rpc/src/api/proof_handler.rs index 30511b924..417452474 100644 --- a/proof-builder-rpc/src/api/proof_handler.rs +++ b/proof-builder-rpc/src/api/proof_handler.rs @@ -1,6 +1,7 @@ use crate::api::ApiState; use crate::api::metrics_service::ApiResultGuard; use crate::api::response::{ApiErrorExt, ApiResult, ok_response}; +use crate::api::routes; use crate::api::validation::InputValidator; use crate::task::{ add_operator_task, add_watchtower_task, find_operator_task, find_watchtower_task, @@ -8,6 +9,8 @@ use crate::task::{ }; use axum::Json; use axum::extract::{Query, State}; +use axum::http::HeaderMap; +use proof_builder::api_auth::ProofBuilderAuthRole; use proof_builder::{ ChainProofDescRequest, OperatorProofDescRequest, OperatorProofRequest, OperatorProofResponse, OperatorProofTimeoutUpdateRequest, OperatorProofTimeoutUpdateResponse, ProofData, ProofDesc, @@ -23,6 +26,14 @@ fn record_invalid(api_result: &mut ApiResultGuard, result: Result) - result.inspect_err(|_| api_result.set("invalid")) } +/// Marks authentication failures as unauthorized API results while preserving the response. +fn record_unauthorized( + api_result: &mut ApiResultGuard, + result: Result, +) -> Result { + result.inspect_err(|_| api_result.set("unauthorized")) +} + #[axum::debug_handler] pub(super) async fn get_chain_proof_task_desc( State(api_state): State>, @@ -170,9 +181,21 @@ pub(super) async fn get_operator_proof_task_desc( #[axum::debug_handler] pub(super) async fn post_operator_proof_task( State(api_state): State>, + headers: HeaderMap, Json(payload): Json, ) -> ApiResult { let mut api_result = api_state.metrics_state.api_result("operator_submit"); + record_unauthorized( + &mut api_result, + api_state.auth.authorize( + &headers, + ProofBuilderAuthRole::Operator, + "POST", + routes::v1::PROOFS_OPERATOR_PROOF, + &payload, + None, + ), + )?; let instance_id = record_invalid( &mut api_result, InputValidator::validate_uuid(&payload.instance_id, "instance_id"), @@ -239,9 +262,21 @@ pub(super) async fn post_operator_proof_task( #[axum::debug_handler] pub(super) async fn update_operator_proof_task_timeout( State(api_state): State>, + headers: HeaderMap, Json(payload): Json, ) -> ApiResult { let mut api_result = api_state.metrics_state.api_result("operator_timeout"); + record_unauthorized( + &mut api_result, + api_state.auth.authorize( + &headers, + ProofBuilderAuthRole::Operator, + "POST", + routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, + &payload, + None, + ), + )?; let instance_id = record_invalid( &mut api_result, InputValidator::validate_uuid(&payload.instance_id, "instance_id"), @@ -283,9 +318,21 @@ pub(super) async fn update_operator_proof_task_timeout( #[axum::debug_handler] pub(super) async fn post_watchtower_proof_task( State(api_state): State>, + headers: HeaderMap, Json(payload): Json, ) -> ApiResult { let mut api_result = api_state.metrics_state.api_result("watchtower_submit"); + record_unauthorized( + &mut api_result, + api_state.auth.authorize( + &headers, + ProofBuilderAuthRole::Watchtower, + "POST", + routes::v1::PROOFS_WATCHTOWER_PROOF, + &payload, + Some(&payload.public_key), + ), + )?; let instance_id = record_invalid( &mut api_result, InputValidator::validate_uuid(&payload.instance_id, "instance_id"), @@ -357,9 +404,21 @@ pub(super) async fn post_watchtower_proof_task( #[axum::debug_handler] pub(super) async fn update_watchtower_proof_task_timeout( State(api_state): State>, + headers: HeaderMap, Json(payload): Json, ) -> ApiResult { let mut api_result = api_state.metrics_state.api_result("watchtower_timeout"); + record_unauthorized( + &mut api_result, + api_state.auth.authorize( + &headers, + ProofBuilderAuthRole::Watchtower, + "POST", + routes::v1::PROOFS_WATCHTOWER_PROOF_TIMEOUT, + &payload, + Some(&payload.public_key), + ), + )?; let instance_id = record_invalid( &mut api_result, InputValidator::validate_uuid(&payload.instance_id, "instance_id"), diff --git a/proof-builder-rpc/src/config.rs b/proof-builder-rpc/src/config.rs index 95eb73861..822219738 100644 --- a/proof-builder-rpc/src/config.rs +++ b/proof-builder-rpc/src/config.rs @@ -1,9 +1,13 @@ use anyhow::Context; use proof_builder::LongRunning; +use proof_builder::api_auth::normalize_public_key; +use secp256k1::XOnlyPublicKey; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct ProofBuilderConfig { + pub api_auth: ApiAuthConfig, pub header_chain: header_chain_proof::Args, pub commit_chain: commit_chain_proof::Args, pub state_chain: state_chain_proof::Args, @@ -11,6 +15,29 @@ pub(crate) struct ProofBuilderConfig { pub operator: operator_proof::Args, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct ApiAuthConfig { + pub trusted_operator_public_keys: Vec, + pub trusted_watchtower_public_keys: Vec, +} + +#[derive(Clone, Debug)] +pub(crate) struct TrustedApiKeys { + pub operator: HashSet, + pub watchtower: HashSet, +} + +impl ApiAuthConfig { + /// Parses and validates the fail-closed role-specific API allowlists. + pub(crate) fn trusted_keys(&self) -> anyhow::Result { + let operator = + parse_keys("trusted_operator_public_keys", &self.trusted_operator_public_keys)?; + let watchtower = + parse_keys("trusted_watchtower_public_keys", &self.trusted_watchtower_public_keys)?; + Ok(TrustedApiKeys { operator, watchtower }) + } +} + impl ProofBuilderConfig { pub(crate) fn new(url: &str) -> anyhow::Result { Self::load(url) @@ -32,3 +59,61 @@ impl ProofBuilderConfig { Ok(new_args) } } + +/// Normalizes one configured allowlist and rejects empty or invalid lists. +fn parse_keys(name: &str, values: &[String]) -> anyhow::Result> { + anyhow::ensure!(!values.is_empty(), "api_auth.{name} must not be empty"); + values + .iter() + .map(|value| { + normalize_public_key(value) + .with_context(|| format!("invalid public key in api_auth.{name}")) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use secp256k1::{Keypair, SECP256K1}; + + fn public_keys() -> (String, String) { + let operator = Keypair::from_seckey_slice(SECP256K1, &[7; 32]).unwrap(); + let watchtower = Keypair::from_seckey_slice(SECP256K1, &[9; 32]).unwrap(); + (operator.public_key().to_string(), watchtower.x_only_public_key().0.to_string()) + } + + #[test] + fn api_auth_accepts_compressed_and_x_only_keys_and_deduplicates() { + let (operator, watchtower) = public_keys(); + let config = ApiAuthConfig { + trusted_operator_public_keys: vec![operator.clone(), operator], + trusted_watchtower_public_keys: vec![watchtower], + }; + let keys = config.trusted_keys().unwrap(); + assert_eq!(keys.operator.len(), 1); + assert_eq!(keys.watchtower.len(), 1); + } + + #[test] + fn api_auth_rejects_empty_or_invalid_lists() { + let (operator, watchtower) = public_keys(); + assert!( + ApiAuthConfig { + trusted_operator_public_keys: vec![], + trusted_watchtower_public_keys: vec![watchtower.clone()], + } + .trusted_keys() + .is_err() + ); + assert!( + ApiAuthConfig { + trusted_operator_public_keys: vec![operator], + trusted_watchtower_public_keys: vec!["not-a-public-key".to_string()], + } + .trusted_keys() + .is_err() + ); + assert!(toml::from_str::("").is_err()); + } +} diff --git a/proof-builder-rpc/src/main.rs b/proof-builder-rpc/src/main.rs index 2b263215d..6cfe04102 100644 --- a/proof-builder-rpc/src/main.rs +++ b/proof-builder-rpc/src/main.rs @@ -22,7 +22,7 @@ struct Opts { pub rpc_addr: String, /// Local Sqlite database file path - #[arg(long, env, default_value = "sqlite:/tmp/bitvm2-node.db")] + #[arg(long, env, default_value = "sqlite:/tmp/bitvm-node.db")] pub database_url: String, #[arg(long, default_value = "proof-builder.toml")] @@ -35,6 +35,7 @@ async fn main() -> anyhow::Result<()> { let opt = Opts::parse(); let cfg = ProofBuilderConfig::new(&opt.config)?; + let trusted_api_keys = cfg.api_auth.trusted_keys()?; println!("proof builder config: {:?}", cfg); let _ = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).try_init(); @@ -50,7 +51,14 @@ async fn main() -> anyhow::Result<()> { let opt_rpc_addr = opt.rpc_addr.clone(); info!("start api server"); task_handles.push(tokio::spawn(async move { - match api::serve(opt_rpc_addr, local_db_clone1, api_metrics_state, cancel_token_clone).await + match api::serve( + opt_rpc_addr, + local_db_clone1, + api_metrics_state, + trusted_api_keys, + cancel_token_clone, + ) + .await { Ok(tag) => Ok(tag), Err(e) => { diff --git a/proof-builder-rpc/src/task/commit_chain_proof.rs b/proof-builder-rpc/src/task/commit_chain_proof.rs index d5970e589..88f3cec46 100644 --- a/proof-builder-rpc/src/task/commit_chain_proof.rs +++ b/proof-builder-rpc/src/task/commit_chain_proof.rs @@ -81,6 +81,13 @@ pub(crate) fn spawn_commit_chain_proof_task( } }; } + let next_commit_index = if starts_from_genesis { + commits.len() + } else { + args.start.checked_add(commits.len()).ok_or_else(|| { + anyhow::anyhow!("Commit proof next index overflow") + })? + }; let block_start = commits.first().unwrap().block_height as i64; let ctx = ProofRequest::CommitChainProofRequest { @@ -127,6 +134,7 @@ pub(crate) fn spawn_commit_chain_proof_task( cycles, CommitChainProofBuilder::name(), starts_from_genesis, + next_commit_index, proving_duration as i64, proving_time as i64, store::ProofState::Proven, diff --git a/proof-builder-rpc/src/task/mod.rs b/proof-builder-rpc/src/task/mod.rs index c240d6046..8ad5faa63 100644 --- a/proof-builder-rpc/src/task/mod.rs +++ b/proof-builder-rpc/src/task/mod.rs @@ -15,6 +15,7 @@ use crate::task::{ use ::commit_chain_proof::CommitChainProofBuilder; use ::header_chain_proof::HeaderChainProofBuilder; use ::state_chain_proof::StateChainProofBuilder; +use anyhow::Context; use bitcoin::{BlockHash, Network, Txid}; use client::btc_chain::BTCClient; use std::collections::HashSet; @@ -192,10 +193,15 @@ pub(crate) async fn run_generate_proof_tasks( pub(crate) async fn fetch_next_commit_task_index(local_db: &LocalDB) -> anyhow::Result { let mut storage_processor = local_db.acquire().await?; - let index = storage_processor - .find_all_running_task_proofs_by_name(CommitChainProofBuilder::name()) - .await?; - Ok(index.len()) + let latest_proof = storage_processor + .find_latest_long_running_task_proof_by_name(CommitChainProofBuilder::name()) + .await? + .context("latest Commit proof record is missing")?; + latest_proof + .extra + .context("latest Commit proof next index is missing")? + .parse() + .context("invalid latest Commit proof next index") } pub(crate) async fn fetch_latest_long_running_task( @@ -558,7 +564,7 @@ pub(crate) async fn create_long_running_task( .await } -/// Persists a Commit proof, replacing prior Commit records for a Genesis replay. +/// Persists a Commit proof and its next input index, replacing prior records for a replay. pub(crate) async fn create_commit_chain_proof( local_db: &LocalDB, start: i64, @@ -569,6 +575,7 @@ pub(crate) async fn create_commit_chain_proof( cycles: u64, chain_name: String, replace_existing: bool, + next_commit_index: usize, total_time_to_proof: i64, proving_time: i64, proof_state: ProofState, @@ -611,7 +618,7 @@ pub(crate) async fn create_commit_chain_proof( total_time_to_proof, proving_time, zkm_version, - extra: None, + extra: Some(next_commit_index.to_string()), created_at: current_time_secs(), updated_at: current_time_secs(), }) @@ -993,6 +1000,7 @@ mod tests { 2, CommitChainProofBuilder::name(), true, + 2, 3, 4, ProofState::Proven, @@ -1039,6 +1047,7 @@ mod tests { 2, "invalid-chain".to_string(), true, + 99, 3, 4, ProofState::Proven, @@ -1060,7 +1069,7 @@ mod tests { #[tokio::test] async fn test_add_watchtower_task() { let db_path = std::env::var("TEST_DB") - .unwrap_or("sqlite:/tmp/.bitvm2-node-sd.db?mode=rwc".to_string()); + .unwrap_or("sqlite:/tmp/.bitvm-node-sd.db?mode=rwc".to_string()); let local_db = create_local_db(&db_path).await; let instance_id = Uuid::from_str("00112233445566778899aabbccddeeff").unwrap(); let graph_id = Uuid::from_str("00112233445566778899aabbccddeeff").unwrap(); @@ -1085,7 +1094,7 @@ mod tests { async fn test_add_operator_proof() { tracing_subscriber::fmt::init(); let db_path = - std::env::var("TEST_DB").unwrap_or("sqlite:.bitvm2-node-sd.db?mode=rwc".to_string()); + std::env::var("TEST_DB").unwrap_or("sqlite:.bitvm-node-sd.db?mode=rwc".to_string()); let local_db = create_local_db(&db_path).await; let instance_id = Uuid::from_str("00112233445566778899aabbccddeeff").unwrap(); let graph_id = Uuid::from_str("00112233445566778899aabbccddeeff").unwrap(); diff --git a/scripts/bitcoin-init.sh b/scripts/bitcoin-init.sh index 19724d30d..19a5ed83e 100755 --- a/scripts/bitcoin-init.sh +++ b/scripts/bitcoin-init.sh @@ -39,12 +39,12 @@ $BTC loadwallet $WALLET_NAME $BTC --rpcwallet=$WALLET_NAME walletpassphrase $WALLET_PASSPHRASE 600 #address="bcrt1q7tr8sl50zanztcrps35hakqpe7gmfzedhhnxcspj7n0ks5lyrnhs6m8ewg" -## For bitvm2-ga tests -address_bitvm2="bcrt1qhnmlpxyxdntekge4u24m4a7yk6elc3zs4v89e7fqja8vagfnrs8sq28cwd" +## For bitvm-gc tests +address_bitvm="bcrt1qhnmlpxyxdntekge4u24m4a7yk6elc3zs4v89e7fqja8vagfnrs8sq28cwd" ## fund the address #$BTC --rpcwallet=$WALLET_NAME -generate 101 #$BTC --rpcwallet=$WALLET_NAME sendtoaddress $address 20 -$BTC --rpcwallet=$WALLET_NAME -regtest sendtoaddress $address_bitvm2 20 +$BTC --rpcwallet=$WALLET_NAME -regtest sendtoaddress $address_bitvm 20 # Install watch apt-get update