Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions circuits/proof-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ pub struct ProofDescResponse {
pub struct OperatorProofRequest {
pub instance_id: String,
pub graph_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gateway_address: Option<String>,
pub operator_committed_blockhash: String,
pub execution_layer_block_number: i64,
pub watchtower_challenge_txids: Vec<Option<String>>,
Expand Down Expand Up @@ -251,6 +253,8 @@ pub struct OperatorProofResponse {
pub struct WatchtowerProofRequest {
pub instance_id: String,
pub graph_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gateway_address: Option<String>,
pub public_key: String,
pub challenge_init_txid: String,
pub execution_layer_block_number: i64,
Expand All @@ -266,6 +270,8 @@ pub struct WatchtowerProofResponse {
pub struct OperatorProofTimeoutUpdateRequest {
pub instance_id: String,
pub graph_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gateway_address: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OperatorProofTimeoutUpdateResponse {
Expand All @@ -279,6 +285,8 @@ pub struct OperatorProofTimeoutUpdateResponse {
pub struct WatchtowerProofTimeoutUpdateRequest {
pub instance_id: String,
pub graph_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gateway_address: Option<String>,
pub public_key: String,
}

Expand Down
8 changes: 7 additions & 1 deletion deployment/regtest/proof-builder-rpc/.env.proof-builder-rpc
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ ZKM_PROOF_POLL_INTERVAL=2000
# The log directory.
ESPLORA_URL=http://localhost:13002
BITCOIN_NETWORK=regtest
DATABASE_URL=sqlite:/tmp/.bitvm-node-sd.sqlite
DATABASE_URL=sqlite:/tmp/.bitvm-node-sd.sqlite

# GOAT contracts used for live Operator/Watchtower authorization.
GOAT_NETWORK=test
GOAT_CHAIN_URL=https://rpc.testnet3.goat.network
# Proof Builder accepts a comma-separated list; each Node still configures one Gateway address.
GOAT_GATEWAY_CONTRACT_ADDRESS=0x26Aa99d72f5f85D60A1B44773B3897793981B7F7
5 changes: 0 additions & 5 deletions deployment/regtest/proof-builder-rpc/proof-builder.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
[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
Expand Down
3 changes: 3 additions & 0 deletions node/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2435,6 +2435,7 @@ pub async fn get_watchtower_commitment(
let payload = WatchtowerProofRequest {
instance_id: instance_id.to_string(),
graph_id: graph_id.to_string(),
gateway_address: Some(env::get_goat_gateway_contract_from_env().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
Expand Down Expand Up @@ -2743,6 +2744,7 @@ pub async fn get_operator_proof(
let payload = OperatorProofRequest {
instance_id: instance_id.to_string(),
graph_id: graph_id.to_string(),
gateway_address: Some(env::get_goat_gateway_contract_from_env().to_string()),
operator_committed_blockhash,
execution_layer_block_number: graph.proceed_withdraw_height,
watchtower_challenge_txids,
Expand Down Expand Up @@ -3890,6 +3892,7 @@ pub async fn notify_to_cancel_proof_task(
let payload = WatchtowerProofTimeoutUpdateRequest {
instance_id: graph.instance_id.to_string(),
graph_id: graph.graph_id.to_string(),
gateway_address: Some(env::get_goat_gateway_contract_from_env().to_string()),
public_key: get_node_pubkey()?.to_string(),
};
let auth_keypair = get_bitvm_key()?;
Expand Down
1 change: 1 addition & 0 deletions proof-builder-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ clap = { workspace = true }
futures = { workspace = true }
dotenv = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
store = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-util.workspace = true
Expand Down
40 changes: 30 additions & 10 deletions proof-builder-rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,48 @@ BITCOIN_NETWORK=testnet4 cargo build -r

## Deployment

Initial parameters are read from `proof-builder.toml` (see [proof-builder.toml](./proof-builder.toml)). After that, they will be loaded from the database.
Initial parameters are read from `proof-builder.toml` (see
[proof-builder.toml.example](./proof-builder.toml.example)). After that, they will be loaded from
the database.

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:
The four Operator/Watchtower task endpoints require signed requests and live GOAT contract
authorization. Configure the read-only GOAT connection before startup:

```toml
[api_auth]
trusted_operator_public_keys = ["<operator-public-key>"]
trusted_watchtower_public_keys = ["<watchtower-public-key>"]
```dotenv
GOAT_NETWORK=test
GOAT_CHAIN_URL=https://rpc.testnet3.goat.network
GOAT_GATEWAY_CONTRACT_ADDRESS=<gateway-address-1>,<gateway-address-2>
```

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.
Proof Builder accepts one or more comma-separated Gateway addresses. All configured Gateways use
the same GOAT network and RPC. It discovers the CommitteeManagement contract through each Gateway
and exits if any configured contract set cannot be initialized. Operator requests require graph
ownership and a matching instance/graph relation. Operator registration and stake are enforced
when graphs are admitted and are not rechecked for existing proof tasks.
Watchtower requests require membership in the current global Watchtower registry and the request
public key must match the authenticated signer. Contract authorization is queried for every
request; failures return HTTP 503 rather than falling back to stale authorization data.

Each authenticated request selects a configured deployment with the signed `gateway_address`
field. For staged upgrades, the field may be omitted while Proof Builder has exactly one Gateway;
it is required when multiple Gateways are configured. Nodes continue to use one
`GOAT_GATEWAY_CONTRACT_ADDRESS` value and add it to every Operator/Watchtower proof request.

Nodes sign requests with their existing `BITVM_SECRET`; no GOAT or Bitcoin 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.

To enable multiple Gateways without interrupting proof polling, first upgrade Proof Builder with a
single Gateway, then upgrade all Nodes to send `gateway_address`, and finally configure the
comma-separated Gateway list.

## 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:
Expand Down
6 changes: 0 additions & 6 deletions proof-builder-rpc/proof-builder.toml.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
[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
Expand Down
Loading
Loading