diff --git a/crates/store/.sqlx/query-0d2299e1894169e179a5716f3a9e5cc2bce15d8facaf649e25d9919fb26f103e.json b/crates/store/.sqlx/query-0d2299e1894169e179a5716f3a9e5cc2bce15d8facaf649e25d9919fb26f103e.json new file mode 100644 index 00000000..2e02efd9 --- /dev/null +++ b/crates/store/.sqlx/query-0d2299e1894169e179a5716f3a9e5cc2bce15d8facaf649e25d9919fb26f103e.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT OR\n REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, amount, challenge_amount,\n status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid,\n quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid,\n challenge_txid, take2_txid, disprove_txid, watchtower_challenge_init_txid, watchtower_challenge_timeout_txids, nack_txids,\n blockhash_commit_timeout_txid, assert_init_txid, assert_commit_timeout_txids, init_withdraw_tx_hash,\n bridge_out_start_at, zkm_version, status_updated_at, proceed_withdraw_height, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 34 + }, + "nullable": [] + }, + "hash": "0d2299e1894169e179a5716f3a9e5cc2bce15d8facaf649e25d9919fb26f103e" +} diff --git a/crates/store/.sqlx/query-16561a4a7fa9c58c2813417c0c8922a0e3c41fc28cd753c03d387250542284ed.json b/crates/store/.sqlx/query-16561a4a7fa9c58c2813417c0c8922a0e3c41fc28cd753c03d387250542284ed.json deleted file mode 100644 index bb0855c6..00000000 --- a/crates/store/.sqlx/query-16561a4a7fa9c58c2813417c0c8922a0e3c41fc28cd753c03d387250542284ed.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT OR\n REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, graph_ipfs_base_url, amount, challenge_amount,\n status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid,\n quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid,\n challenge_txid, take2_txid, disprove_txid, watchtower_challenge_init_txid, watchtower_challenge_timeout_txids, nack_txids,\n blockhash_commit_timeout_txid, assert_init_txid, assert_commit_timeout_txids, init_withdraw_tx_hash,\n bridge_out_start_at, zkm_version, status_updated_at, proceed_withdraw_height, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 35 - }, - "nullable": [] - }, - "hash": "16561a4a7fa9c58c2813417c0c8922a0e3c41fc28cd753c03d387250542284ed" -} diff --git a/crates/store/.sqlx/query-44d3f6ec28b83e43933d4e30310651335c735edb3d2606d7314988ce5807e4da.json b/crates/store/.sqlx/query-44d3f6ec28b83e43933d4e30310651335c735edb3d2606d7314988ce5807e4da.json deleted file mode 100644 index d42aa5cf..00000000 --- a/crates/store/.sqlx/query-44d3f6ec28b83e43933d4e30310651335c735edb3d2606d7314988ce5807e4da.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO message_broadcast (graph_id, graph_status, msg_type, msg_times, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(graph_id, graph_status, msg_type) DO UPDATE SET updated_at = excluded.updated_at,\n msg_times = message_broadcast.msg_times + excluded.msg_times", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "44d3f6ec28b83e43933d4e30310651335c735edb3d2606d7314988ce5807e4da" -} diff --git a/crates/store/.sqlx/query-83145bbfc966e1f6fdb8f55ca2a9e11910636b3cd6149593c4909a29d025d699.json b/crates/store/.sqlx/query-83145bbfc966e1f6fdb8f55ca2a9e11910636b3cd6149593c4909a29d025d699.json deleted file mode 100644 index 576044e0..00000000 --- a/crates/store/.sqlx/query-83145bbfc966e1f6fdb8f55ca2a9e11910636b3cd6149593c4909a29d025d699.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT msg_times, updated_at\n FROM message_broadcast\n WHERE graph_id = ?\n AND graph_status = ?\n AND msg_type = ?", - "describe": { - "columns": [ - { - "name": "msg_times", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "updated_at", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 3 - }, - "nullable": [ - false, - false - ] - }, - "hash": "83145bbfc966e1f6fdb8f55ca2a9e11910636b3cd6149593c4909a29d025d699" -} diff --git a/crates/store/src/ipfs.rs b/crates/store/src/ipfs.rs deleted file mode 100644 index fd3ed74e..00000000 --- a/crates/store/src/ipfs.rs +++ /dev/null @@ -1,170 +0,0 @@ -use anyhow::{Result, bail}; -use futures::TryStreamExt; -use reqwest::Client; -use reqwest::multipart::{Form, Part}; -use serde::Deserialize; -use std::path::Path; -use tokio::fs::File; -use tokio_util::codec::{BytesCodec, FramedRead}; -use walkdir::WalkDir; - -pub struct IPFS { - pub endpoint: String, - pub client: Client, -} - -#[derive(Deserialize, Debug, PartialEq, Hash)] -#[serde(rename_all = "PascalCase")] -pub struct Link { - pub hash: String, - pub mod_time: String, - pub mode: u32, - pub name: String, - pub size: u32, - pub target: String, - #[serde(rename = "Type")] - pub type_: u32, -} - -#[derive(Deserialize, Debug, PartialEq, Hash)] -#[serde(rename_all = "PascalCase")] -pub struct Object { - pub hash: String, - pub links: Vec, -} - -#[derive(Deserialize, Debug, PartialEq, Hash)] -#[serde(rename_all = "PascalCase")] -pub struct Objects { - pub objects: Vec, -} - -/// If the name is empty, it's the directory name -#[derive(Deserialize, Debug, PartialEq, Hash)] -#[serde(rename_all = "PascalCase")] -pub struct AddedFile { - pub name: String, - pub hash: String, - pub size: String, -} - -// Collects all files and returns relative + absolute paths -async fn collect_files(base_path: &Path) -> Result
{ - let mut form = Form::new(); - - for entry in - WalkDir::new(base_path).into_iter().filter_map(Result::ok).filter(|e| e.path().is_file()) - { - let rel_path = entry.path().strip_prefix(base_path)?.to_str().unwrap().replace("\\", "/"); - let file = File::open(entry.path()).await?; - let stream = FramedRead::new(file, BytesCodec::new()) - .map_ok(|b| b.freeze()) - .map_err(std::io::Error::other); - let body = reqwest::Body::wrap_stream(stream); - let part = Part::stream(body).file_name(rel_path.clone()); - form = form.part("file", part); - } - Ok(form) -} - -impl IPFS { - pub fn new(endpoint: &str) -> Self { - let client = Client::new(); - let endpoint = endpoint.to_string(); - Self { endpoint, client } - } - - // list directory - // API: https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-ls - pub async fn ls(&self, hash: &str) -> Result { - let url = format!("{}/api/v0/ls", self.endpoint); - let form = reqwest::multipart::Form::new().text("arg", hash.to_owned()); - let response = self.client.post(url).multipart(form).send().await?; - if response.status().is_success() { - let response_body = response.text().await?; - Ok(serde_json::from_str(&response_body)?) - } else { - bail!("IPFS read failed, {response:?}") - } - } - - // Read the content - // API: https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-cat - pub async fn cat(&self, hash: &str) -> Result { - let url = format!("{}/api/v0/cat", self.endpoint); - let form = reqwest::multipart::Form::new().text("arg", hash.to_owned()); - let response = self.client.post(url).multipart(form).send().await?; - if response.status().is_success() { - let response_body = response.text().await?; - Ok(response_body.to_string()) - } else { - bail!("IPFS read failed, {response:?}") - } - } - - /// Add file to IPFS and return its ipfs url - pub async fn add(&self, base_path: &Path) -> Result> { - let url = format!("{}/api/v0/add?recursive=true&wrap-with-directory=true", self.endpoint); - - let form = collect_files(base_path).await?; - let response = self.client.post(url).multipart(form).send().await?; - if response.status().is_success() { - let response_body = response.text().await?; - println!("add: {response_body:?}"); - let shares = response_body.trim().split("\n").collect::>(); - println!("add: {shares:?}"); - let added_files = - shares.iter().map(|f| serde_json::from_str(f).unwrap()).collect::>(); - Ok(added_files) - } else { - bail!("IPFS upload failed, {response:?}") - } - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - - #[tokio::test] - #[ignore = "IPFS deprecated"] - async fn test_ipfs_add_and_get() { - println!("connecting to localhost:5001..."); - let client = IPFS::new("http://44.229.236.82:5001"); - //let client = IPFS::new("http://localhost:5001"); - - // Read single file - match client.cat("QmXxwbk8eA2bmKBy7YEjm5w1zKiG7g6ebF1JYfqWvnLnhH/assert-commit0.hex").await - { - Ok(res) => { - println!("cat: {res}",); - } - Err(e) => panic!("{e:?}"), - } - - // list directory - match client.ls("QmXxwbk8eA2bmKBy7YEjm5w1zKiG7g6ebF1JYfqWvnLnhH").await { - Ok(res) => { - println!("ls: {res:?}"); - } - Err(e) => panic!("{e:?}"), - } - - // it works, but skip for avoiding creating too much garbage - // use std::io::Write; - // let base_dir = tempfile::tempdir().unwrap(); - // vec!["1.txt", "2.txt"].iter().for_each(|name| { - // let mut file = std::fs::File::create( - // base_dir.path().join(name) - // ).unwrap(); - // let _ = writeln!(file, "GOAT Network").unwrap(); - // }); - // match client.add(base_dir.path()).await { - // Ok(hash) => { - // println!("add hash: {:?}", hash); - // // FIXME: can not read immediately. - // } - // Err(e) => panic!("error adding file: {}", e), - // } - } -} diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index 1463cd26..ceb6d9fc 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -1,4 +1,3 @@ -pub mod ipfs; pub mod localdb; mod schema; mod utils; diff --git a/crates/store/src/localdb.rs b/crates/store/src/localdb.rs index c7c5a974..a1f3ba9d 100644 --- a/crates/store/src/localdb.rs +++ b/crates/store/src/localdb.rs @@ -595,9 +595,6 @@ impl GraphUpdate { if let Some(ref sub_status) = self.sub_status { query_builder.set_field("sub_status", QueryParam::Text(sub_status.clone())); } - if let Some(ref ipfs_base_url) = self.ipfs_base_url { - query_builder.set_field("graph_ipfs_base_url", QueryParam::Text(ipfs_base_url.clone())); - } if let Some(ref challenge_txid) = self.challenge_txid { query_builder.set_field("challenge_txid", QueryParam::BTCTxid(challenge_txid.clone())); } @@ -1113,19 +1110,18 @@ impl<'a> StorageProcessor<'a> { serde_json::to_string(&graph.assert_commit_timeout_txids)?; let res = sqlx::query!( "INSERT OR - REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, graph_ipfs_base_url, amount, challenge_amount, + REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, amount, challenge_amount, status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid, quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid, challenge_txid, take2_txid, disprove_txid, watchtower_challenge_init_txid, watchtower_challenge_timeout_txids, nack_txids, blockhash_commit_timeout_txid, assert_init_txid, assert_commit_timeout_txids, init_withdraw_tx_hash, bridge_out_start_at, zkm_version, status_updated_at, proceed_withdraw_height, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", graph.graph_id, graph.instance_id, graph.kickoff_index, graph.from_addr, graph.to_addr, - graph.graph_ipfs_base_url, graph.amount, graph.challenge_amount, graph.status, @@ -1210,7 +1206,6 @@ impl<'a> StorageProcessor<'a> { kickoff_index, from_addr, to_addr, - graph_ipfs_base_url, amount, challenge_amount, status, @@ -2072,53 +2067,6 @@ impl<'a> StorageProcessor<'a> { Ok(result.rows_affected()) } - pub async fn get_message_broadcast_times( - &mut self, - graph_id: &Uuid, - graph_status: &str, - msg_type: &str, - ) -> anyhow::Result<(i64, i64)> { - let res = sqlx::query!( - "SELECT msg_times, updated_at - FROM message_broadcast - WHERE graph_id = ? - AND graph_status = ? - AND msg_type = ?", - graph_status, - graph_id, - msg_type - ) - .fetch_optional(self.conn()) - .await?; - match res { - Some(row) => Ok((row.msg_times, row.updated_at)), - None => Ok((0, 0)), - } - } - - pub async fn add_message_broadcast_times( - &mut self, - graph_id: &Uuid, - graph_status: &str, - msg_type: &str, - add_times: i64, - ) -> anyhow::Result<()> { - let current_time = get_current_timestamp_secs(); - sqlx::query!( - "INSERT INTO message_broadcast (graph_id, graph_status, msg_type, msg_times, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT(graph_id, graph_status, msg_type) DO UPDATE SET updated_at = excluded.updated_at, - msg_times = message_broadcast.msg_times + excluded.msg_times", - graph_id, - graph_status, - msg_type, - add_times, - current_time, - current_time, - ).execute(self.conn()).await?; - Ok(()) - } - pub async fn find_watch_contract( &mut self, addr: &str, diff --git a/crates/store/src/schema.rs b/crates/store/src/schema.rs index 5be4d6e7..28a98b67 100644 --- a/crates/store/src/schema.rs +++ b/crates/store/src/schema.rs @@ -394,7 +394,6 @@ pub struct Graph { pub kickoff_index: i64, pub from_addr: String, pub to_addr: String, //operator_receive_address - pub graph_ipfs_base_url: String, pub amount: i64, pub challenge_amount: i64, pub status: String, // GraphStatus diff --git a/docs/incentives.md b/docs/incentives.md new file mode 100644 index 00000000..6710baa9 --- /dev/null +++ b/docs/incentives.md @@ -0,0 +1,169 @@ +# BitVM Protocol Node Incentives and Cost Specification + +--- + +## Global Parameters + +* `FEE_RATE_L1`: Bitcoin fee rate (unit: sats / vbyte) +* Layer-1 (L1, Bitcoin) asset unit: BTC / sats +* Layer-2 (L2, GOAT Network) asset unit: stakeToken (pegBTC) + +--- + +## Challenger + +### Initiating a Challenge + +* A challenge transaction is broadcast on Layer-1. +* Layer-1 transaction fee cost: `CHALLENGE_TX_VBYTES * FEE_RATE_L1`. +* A bond must be paid on Layer-1: `CHALLENGE_BOND`. + + * The bond is paid directly to the challenged operator. + +### Disprove + +* After a challenge is initiated, a disprove transaction must be broadcast. +* The initiator of the disprove does not need to be the same entity as the challenge initiator. +* Layer-1 transaction fee cost range: + + * `DISPROVE_TX_VBYTES_MIN * FEE_RATE_L1` + * `DISPROVE_TX_VBYTES_MAX * FEE_RATE_L1` + +### Rewards Upon Success (Layer-2) + +* If the challenge succeeds: + + * The challenge initiator receives `CHALLENGE_REWARD_L2`. + * The disprove initiator receives `DISPROVE_REWARD_L2`. + +--- + +## Watchtower + +* Responsible for monitoring operator behavior during the challenge window. +* Must broadcast a `WATCHTOWER_PROOF_TX`. +* Layer-1 transaction fee cost: `WATCHTOWER_TX_VBYTES * FEE_RATE_L1`. + +--- + +## Committee / Relayer + +### Committee + +* Under normal circumstances, no transactions need to be sent. + +### Relayer + +* Committee members may be configured as relayers. +* Relayers submit graph-related transactions to Layer-2. +* Relayers receive incentives based on the number of submissions. +* Reward per submission: `RELAYER_REWARD_L2`. + +--- + +## Pegin / Pegout Fees + +### Pegin + +* When initiating a pegin, the user must pay a pegBTC fee. +* Fee rate: `PEGIN_FEE_RATE`. +* The fee is deducted from the pegin pegBTC amount. + +### Pegout + +* When executing a pegout, the operator must pay a pegBTC fee. +* Fee rate: `PEGOUT_FEE_RATE`. +* After a successful pegout, the operator receives a pegBTC reward. +* Reward rate: `PEGOUT_REWARD_RATE`. + +--- + +## Operator + +### Layer-2 Staking + +* Operators must stake on Layer-2. + +* Minimum required stake: `OPERATOR_STAKE_MIN`. + +* Protocol constraints: + + * A single operator may execute at most **two pegouts (graphs)** concurrently. + * Once any graph is successfully disproved: + + * The operator is prohibited from initiating new pegouts. + +* Therefore, the protocol requires: + + * `OPERATOR_STAKE_MIN >= 2 × OPERATOR_SLASH_AMOUNT`, + * `OPERATOR_SLASH_AMOUNT >= CHALLENGE_BOND + DISPROVE_TX_VBYTES_MAX * FEE_RATE_L1` + * to ensure that even in the worst case (two graphs being disproved simultaneously), sufficient stake remains to be slashed. + +### Malicious Behavior Penalties + +* If an operator behaves maliciously and is successfully challenged and disproved: + + * A fixed amount of stakeToken, `OPERATOR_SLASH_AMOUNT`, is deducted from the operator’s current stake. + * The slashed stakeToken is distributed to the challenge initiator and the disprove initiator. + * Any remaining portion is retained in the contract. + +### Layer-1 Fee Prefunding (Per Graph) + +* For each graph, the operator must pre-fund Layer-1 transaction fees: + + * `GRAPH_BOND_L1`. +* Fees are later refunded depending on the execution path. + +| Action | Additional Layer-1 Fee | +| ------ | ------------------------------- | +| take1 | `TAKE1_TX_VBYTES * FEE_RATE_L1` | +| skip | `SKIP_TX_VBYTES * FEE_RATE_L1` | + +--- + +## Graph and Multi-Operator Mechanism + +* Each pegin may have multiple operators generating graphs in parallel. +* Ultimately, only one operator can successfully execute `take1`. +* All other operators must execute `skip`. + +--- + +## Testnet Parameter Configuration (Current) + +### Economic and Fee Parameters + +```text +FEE_RATE_L1 = 1 sats/vbyte + +CHALLENGE_BOND = 0.01 BTC + +CHALLENGE_REWARD_L2 = 0.0125 pegBTC +DISPROVE_REWARD_L2 = 0.0025 pegBTC + +RELAYER_REWARD_L2 = not set + +OPERATOR_STAKE_MIN = 0.06 pegBTC +OPERATOR_SLASH_AMOUNT = 0.03 pegBTC + +GRAPH_BOND_L1 = 10_000 sats + +PEGIN_FEE_RATE = 0.5% +PEGOUT_FEE_RATE = not set +PEGOUT_REWARD_RATE = 0.3% +``` + +### Transaction Size Parameters (Testnet) + +```text +CHALLENGE_TX_VBYTES = 1_000 + +DISPROVE_TX_VBYTES_MIN = 500 +DISPROVE_TX_VBYTES_MAX = 1_000_000 + +WATCHTOWER_TX_VBYTES = 4_000 + +TAKE1_TX_VBYTES = 2_000 +SKIP_TX_VBYTES = 300 +``` + diff --git a/node/README.md b/node/README.md index 924d7702..84969cba 100644 --- a/node/README.md +++ b/node/README.md @@ -1,359 +1,902 @@ -# Node +# BitVM2 Node + +The main node implementation for GOAT Network's BitVM2 bridge protocol. This module handles P2P networking, message processing, scheduled tasks, and RPC services for secure cross-chain asset transfers between Bitcoin and GOAT L2. + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Actor System](#actor-system) +- [Core Workflows](#core-workflows) + - [Peg-in (Bridge-In)](#peg-in-bridge-in) + - [Peg-out (Bridge-Out)](#peg-out-bridge-out) + - [Challenge Process](#challenge-process) +- [Graph State Machine](#graph-state-machine) +- [Graph Storage & Synchronization](#graph-storage--synchronization) +- [Message System](#message-system) +- [Scheduled Tasks](#scheduled-tasks) +- [Module Structure](#module-structure) +- [Build & Run](#build--run) +- [Configuration](#configuration) +- [RPC API](#rpc-api) -## Build +--- -```bash -BITCOIN_NETWORK=regtest cargo build -r +## Overview + +The BitVM2 Node (`bitvm2-noded`) is a multi-role distributed node that participates in the BitVM2 cross-chain bridge protocol. It enables trustless Bitcoin-to-L2 transfers through a combination of: + +- **Multi-signature Consensus**: Committee-based transaction presigning +- **Optimistic Verification**: Watchtower monitoring with dispute resolution +- **ZK Proofs**: Cryptographic verification of state transitions +- **P2P Graph Distribution**: Decentralized graph data synchronization via relayer nodes + +### Key Capabilities + +| Capability | Description | +|------------|-------------| +| **P2P Communication** | libp2p-based gossip network (Kademlia DHT + Gossipsub) | +| **Chain Monitoring** | Bitcoin and GOAT L2 event watching | +| **Graph Management** | Transaction graph creation, signing, and lifecycle tracking | +| **Graph Synchronization** | P2P-based graph distribution with relayer node support | +| **Challenge Processing** | Watchtower challenges and dispute resolution | +| **Proof Coordination** | ZK proof generation via proof-builder-rpc | + +--- + +## Architecture + +### Fig-01-1-System-Architecture + +```mermaid +flowchart TB + subgraph External["External Systems"] + BTC["Bitcoin Network"] + GOAT["GOAT L2 Chain"] + PB["Proof Builder RPC"] + end + + subgraph Node["bitvm2-noded"] + subgraph Input["Input Layer"] + EW["Event Watch Task"] + P2P["P2P Swarm (libp2p)"] + RPC["RPC Service (Axum)"] + end + + subgraph Core["Core Processing"] + MH["Message Handler"] + DISP["Role Dispatcher"] + end + + subgraph Tasks["Background Tasks"] + GM["Graph Maintenance"] + IM["Instance Maintenance"] + NM["Node Maintenance"] + SPV["SPV Maintenance"] + end + + subgraph Storage["Persistence Layer"] + DB[(SQLite Database)] + GD[("graph_raw_data\n(Graph JSON)")] + GM_TBL[("graph\n(Metadata)")] + end + end + + BTC --> EW + GOAT --> EW + EW --> MH + P2P --> MH + RPC --> MH + MH --> DISP + DISP --> GM + DISP --> IM + GM --> DB + IM --> DB + NM --> DB + SPV --> DB + GM --> GD + GM --> GM_TBL + GM --> PB ``` -## Graph State Machines +**Description**: The system employs a three-layer architecture. The Input Layer receives events from external chains, P2P network messages, and RPC requests. The Core Processing Layer dispatches messages to role-specific handlers. The Background Tasks Layer manages graph state machines, instance lifecycles, node health, and SPV header chain updates. All state is persisted to SQLite database, with graph data stored in a dedicated `graph_raw_data` table. + +### Fig-01-2-Component-Interaction + +```mermaid +flowchart LR + subgraph Actors["Actor Roles"] + C["Committee"] + O["Operator"] + CH["Challenger"] + W["Watchtower"] + R["Relayer\n(Committee + Flag)"] + end + + subgraph Network["P2P Topics"] + TC["/goat/topic/Committee"] + TO["/goat/topic/Operator"] + TCH["/goat/topic/Challenger"] + TW["/goat/topic/Watchtower"] + TA["/goat/topic/All"] + end + + C <--> TC + O <--> TO + CH <--> TCH + W <--> TW + C & O & CH & W & R <--> TA + R -.->|"SyncGraph"| TA +``` + +**Description**: Each role subscribes to its corresponding gossipsub topic for message exchange. All roles subscribe to the `/All` topic for broadcast messages. Relayer nodes (Committee members with `ENABLE_RELAYER=true`) respond to graph synchronization requests and distribute graph data across the network. + +--- -Based on `crates/store/src/schema.rs::GraphStatus` and `node/src/scheduled_tasks/graph_maintenance_tasks.rs`. +## Actor System -### Main Graph Status Transitions +BitVM2 employs four primary actor roles plus an optional relayer capability: -From `crates/store/src/schema.rs::GraphStatus`: +### Fig-02-1-Actor-Roles ```mermaid +classDiagram + class Actor { + <> + Committee + Operator + Challenger + Watchtower + All + } + + class Committee { + +generate_nonces() + +presign_transactions() + +endorse_graph() + +confirm_pegin() + } + + class Operator { + +create_graph() + +push_data_to_l2() + +broadcast_kickoff() + +send_take1_take2() + } + + class Challenger { + +monitor_timeouts() + +submit_disprove() + } + + class Watchtower { + +monitor_block_headers() + +submit_challenge() + +ack_nack_response() + } + + class Relayer { + +respond_sync_request() + +distribute_graph_data() + +cache_all_graphs() + } + + Actor <|-- Committee + Actor <|-- Operator + Actor <|-- Challenger + Actor <|-- Watchtower + Committee <|-- Relayer : ENABLE_RELAYER=true +``` + +**Description**: The class diagram shows the inheritance relationship between the Actor enumeration and role implementations. Relayer is a special mode of Committee nodes enabled via environment variable. + +### Role Responsibilities + +| Role | Responsibilities | Key Messages | +|------|------------------|--------------| +| **Committee** | Multi-sig committee member, responsible for presigning and graph endorsement | `NonceGeneration`, `CommitteePresign`, `EndorseGraph` | +| **Operator** | Bridge operator, creates graphs and executes withdrawal transactions | `CreateGraph`, `KickoffSent`, `Take1Sent`, `Take2Sent` | +| **Challenger** | Dispute challenger, monitors timeouts and submits disproofs | `DisproveReady`, `DisproveSent` | +| **Watchtower** | Chain monitor, validates block headers and submits challenges | `WatchtowerChallengeSent` | +| **Relayer** | Graph data distributor, responds to sync requests | `SyncGraphRequest`, `SyncGraph` | + --- -title: Graph Lifecycle - GraphStatus Transitions + +## Core Workflows + +### Peg-in (Bridge-In) + +Users deposit BTC into the bridge contract and receive equivalent assets on GOAT L2. + +#### Fig-03-1-Pegin-Sequence + +```mermaid +sequenceDiagram + autonumber + participant User + participant GOAT as GOAT L2 + participant EW as Event Watch + participant Committee + participant Operator + participant BTC as Bitcoin + participant DB as SQLite DB + + User->>GOAT: Initiate BridgeInRequest + GOAT-->>EW: Trigger BridgeInRequest event + EW->>Committee: PeginRequest message + + Committee->>Committee: Validate fees and availability + Committee->>Operator: ConfirmInstance + + Operator->>Operator: Create SimplifiedBitvm2Graph + Operator->>DB: Store graph_raw_data (JSON) + Operator->>Committee: CreateGraph (with graph data) + + loop Multi-sig rounds + Committee->>Committee: NonceGeneration + Committee->>Committee: CommitteePresign + end + + Committee->>Operator: EndorseGraph (graph endorsement) + Operator->>GOAT: PostGraphData + Operator->>BTC: Broadcast PreKickoff transaction + BTC-->>EW: PreKickoff confirmed + + Note over User,DB: Peg-in complete, user receives assets on L2 +``` + +**Description**: The Peg-in flow consists of three phases: (1) Request Phase - user initiates request on L2, committee validates; (2) Graph Construction Phase - Operator creates transaction graph (stored locally in SQLite), committee performs MuSig2 multi-signing; (3) Confirmation Phase - data posted to L2, PreKickoff transaction confirms to complete the process. + +#### Fig-03-2-Instance-BridgeIn-Status + +```mermaid +stateDiagram-v2 + [*] --> UserIniting: User initiates request + UserIniting --> UserInited: Event detected + UserInited --> CommitteesAnswered: Enough committee responses + UserInited --> NoEnoughCommitteesAnswered: Window timeout + UserInited --> UserDiscarded: UTXO spent + + CommitteesAnswered --> UserBroadcastPeginPrepare: User broadcasts + UserBroadcastPeginPrepare --> Presigned: All committees signed + UserBroadcastPeginPrepare --> PresignedFailed: Signing failed + + Presigned --> RelayerL1Broadcasted: Relayer broadcasts + RelayerL1Broadcasted --> RelayerL2Minted: L2 minting success + RelayerL1Broadcasted --> RelayerL2MintedFailed: L2 minting failed + + UserBroadcastPeginPrepare --> Timeout: Timeout + Timeout --> UserCanceled: User cancels + + RelayerL2Minted --> [*] + UserCanceled --> [*] +``` + +**Description**: This state diagram shows the complete lifecycle of a bridge-in instance, from initial request through committee validation, signing, and final minting on L2. + +### Peg-out (Bridge-Out) + +Users initiate withdrawal on GOAT L2 and receive BTC on Bitcoin. Two paths exist: + +#### Fig-03-3-Pegout-Happy-Path + +```mermaid +sequenceDiagram + autonumber + participant User + participant GOAT as GOAT L2 + participant Operator + participant BTC as Bitcoin + + User->>GOAT: InitWithdraw (withdrawal request) + GOAT-->>Operator: Withdrawal event detected + + Operator->>BTC: Broadcast Kickoff transaction + Note over Operator,BTC: Wait for Timelock expiry + + alt No challenge (Happy Path) + Operator->>BTC: Broadcast Take1 transaction + BTC->>User: BTC transferred to user address + end +``` + +**Description**: The Happy Path is the simplest route - when no Watchtower challenges the Operator's claim, the Operator completes the withdrawal via Take1 transaction after the Timelock expires. + +#### Fig-03-4-Pegout-Challenge-Path + +```mermaid +sequenceDiagram + autonumber + participant User + participant GOAT as GOAT L2 + participant Operator + participant Watchtower + participant Challenger + participant BTC as Bitcoin + + User->>GOAT: InitWithdraw + Operator->>BTC: Kickoff transaction + + rect rgb(255, 240, 240) + Note over Operator,Watchtower: Challenge Phase + Operator->>BTC: WatchtowerChallengeInitTx + + loop For each Watchtower index + Watchtower->>BTC: WatchtowerChallengeTx + alt Operator accepts + Operator->>BTC: ACK response + else Operator rejects + Operator->>BTC: NACK response + Challenger->>BTC: DisproveTx + end + end + + Operator->>BTC: CommitBlockHashTx + Operator->>BTC: AssertInitTx + Operator->>BTC: AssertCommitTx + end + + alt All challenges passed + Operator->>BTC: Take2 transaction + BTC->>User: BTC transferred to user address + else Challenge failed + Challenger->>BTC: DisproveTx + Note over User,BTC: User funds safe, Operator penalized + end +``` + +**Description**: The Challenge Path is triggered when Watchtowers detect anomalies. It contains three sub-phases: (1) Watchtower Challenge - watchers submit challenges; (2) BlockHash Commit - Operator commits block hash; (3) Assert Commit - Operator commits assertion proofs. Failure in any phase triggers Disprove. + +### Challenge Process + +#### Fig-03-5-Challenge-Substatus + +```mermaid +flowchart TB + subgraph WTC["Watchtower Challenge Phase"] + WTC1["OperatorInit"] --> WTC2{"Watchtower response?"} + WTC2 -->|"Submit challenge"| WTC3["Challenge"] + WTC2 -->|"Timeout"| WTC4["ChallengeTimeout"] + WTC3 --> WTC5{"Operator response?"} + WTC5 -->|"ACK"| WTC6["OperatorACK"] + WTC5 -->|"NACK"| WTC7["OperatorNACK"] + WTC5 -->|"Timeout"| WTC8["ACKTimeout"] + end + + subgraph CBH["CommitBlockHash Phase"] + CBH1["WatchtowerChallengeProcessed"] + CBH2["OperatorCommit"] + CBH3["OperatorCommitTimeout"] + CBH1 --> CBH2 + CBH1 --> CBH3 + end + + subgraph AC["Assert Commit Phase"] + AC1["OperatorInit"] + AC2["OperatorCommit"] + AC3["OperatorCommitTimeout"] + AC1 --> AC2 + AC1 --> AC3 + end + + WTC6 --> CBH1 + CBH2 --> AC1 + AC2 --> TAKE2["Take2 Success"] + + WTC4 & WTC7 & WTC8 & CBH3 & AC3 --> DISP["Disprove"] +``` + +**Description**: The challenge process tracks three parallel state machines. Any timeout or failure in sub-phases triggers the Disprove path, ensuring system security. + --- + +## Graph State Machine + +Graph is the core data structure in BitVM2, representing a set of presigned Bitcoin transactions. + +### Fig-04-1-Graph-Lifecycle + +```mermaid stateDiagram-v2 - [*] --> OperatorPresigned: Create graph - - OperatorPresigned --> CommitteePresigned: Committee presigns + [*] --> OperatorPresigned: Operator creates graph + + OperatorPresigned --> CommitteePresigned: Collected enough committee signatures OperatorPresigned --> Obsoleted: PreKickoff on-chain but data not posted - + CommitteePresigned --> OperatorDataPushed: Operator pushes L2 data CommitteePresigned --> Obsoleted: PreKickoff on-chain but data not posted - - OperatorDataPushed --> PreKickoff: PreKickoff tx confirmed on Bitcoin - OperatorDataPushed --> Obsoleted: Pegin not withdrawable & no withdraw request - + + OperatorDataPushed --> PreKickoff: PreKickoff tx confirmed + OperatorDataPushed --> Obsoleted: Pegin not withdrawable and no withdrawal request + PreKickoff --> OperatorKickOff: Kickoff tx broadcast - PreKickoff --> Skipped: Guardian/ForceSkip triggered - - OperatorKickOff --> OperatorTake1: Timeout without challenge + PreKickoff --> Skipped: Guardian intervention or force skip + + OperatorKickOff --> OperatorTake1: Timelock expired without challenge OperatorKickOff --> Challenge: WatchtowerChallengeInit confirmed - - Challenge --> Disprove: Disprove needed
(challenge/timeout detected) - Challenge --> OperatorTake2: Normal completion
(all challenges passed) - - OperatorTake1 --> [*] - OperatorTake2 --> [*] - Skipped --> [*] - Obsoleted --> [*] - Disprove --> [*] - - note right of Challenge - Sub-phases tracked by ChallengeSubStatus: - 1. Watchtower Challenge Phase - - Watchers may challenge - - Operator ACK/NACK responses - 2. CommitBlockHash Phase - - Operator commits blockhash - 3. Assert Commit Phase - - Operator commits assertions - end note - - note right of Disprove - Triggered when any challenge - or timeout detected during - Challenge phase sub-phases - end note - - note right of Obsoleted - Reimbursement by other operators - or graph data not posted in time - end note - - note right of OperatorPresigned - Frontend-only states for UI: - - Created, Presigned, L2Recorded - - OperatorKickOffing, Challenging - - Disproving - end note + + Challenge --> OperatorTake2: All challenge phases completed normally + Challenge --> Disprove: WT-Ack/BlockHash-Commit/Assert Timeout or Fraud scripts detected + + OperatorTake1 --> [*]: Happy Path complete + OperatorTake2 --> [*]: Challenge Path complete + Skipped --> [*]: Graph skipped + Obsoleted --> [*]: Graph obsoleted + Disprove --> [*]: Dispute resolved ``` -### Challenge to OperatorTake2 Flow +**Description**: Graph goes through 9 main states. The normal flow starts from `OperatorPresigned`, proceeds through signature collection, data publishing, and Kickoff, finally completing via Take1 (Happy Path) or Take2 (Challenge Path). Abnormal cases enter `Obsoleted`, `Skipped`, or `Disprove` terminal states. + +### Fig-04-2-Graph-Status-Transitions -The path from Challenge → OperatorTake2 requires successful completion of three sub-phases: +| Source State | Target State | Trigger Condition | +|--------------|--------------|-------------------| +| `OperatorPresigned` | `CommitteePresigned` | Received enough committee presignatures | +| `OperatorPresigned` | `Obsoleted` | PreKickoff on-chain but graph data not posted | +| `CommitteePresigned` | `OperatorDataPushed` | Operator successfully pushed data to L2 | +| `OperatorDataPushed` | `PreKickoff` | PreKickoff tx confirmed on Bitcoin | +| `OperatorDataPushed` | `Obsoleted` | Pegin not withdrawable and no withdrawal request | +| `PreKickoff` | `OperatorKickOff` | Kickoff tx broadcast successfully | +| `PreKickoff` | `Skipped` | Guardian intervention or force skip | +| `OperatorKickOff` | `OperatorTake1` | Timelock expired without challenge | +| `OperatorKickOff` | `Challenge` | WatchtowerChallengeInit tx confirmed | +| `Challenge` | `OperatorTake2` | All sub-phases completed normally | +| `Challenge` | `Disprove` | Timeout or challenge failure detected | + +### Fig-04-3-Challenge-SubStatus-ER ```mermaid ---- -title: Challenge Phase Flow to OperatorTake2 ---- -stateDiagram-v2 - [*] --> Challenge - - Challenge --> WTPhase: Enter Watchtower Challenge Phase - Challenge --> Disprove: Challenge/Timeout
detected - - WTPhase --> WTInit: WatchtowerChallengeInitTx
confirmed - WTInit --> WTChallenge: Watchtowers may challenge - WTInit --> WTTimeout: No watchtower
challenges - - WTChallenge --> WTAllACK: All watchtowers ACK - WTChallenge --> Disprove: Any NACK or timeout - - WTTimeout --> Disprove: Timeout expired - - WTAllACK --> BlockHashPhase: Proceed to BlockHash Phase - - BlockHashPhase --> BlockHashWait: Wait for
WatchtowerChallenge
completion - BlockHashWait --> BlockHashCommit: Operator commits
blockhash - BlockHashWait --> Disprove: Commit timeout - - BlockHashCommit --> AssertPhase: Proceed to Assert Phase - - AssertPhase --> AssertInit: AssertInitTx confirmed - AssertInit --> AssertCommit: Operator commits
assertions - AssertInit --> Disprove: Assert timeout - - AssertCommit --> CheckComplete: All phases complete? - - CheckComplete --> OperatorTake2: Yes - All conditions met
✓ WatchtowerChallengeNormalFinished
✓ BlockHash committed
✓ Assertions committed - CheckComplete --> Disprove: No - Missing conditions - - OperatorTake2 --> [*] - Disprove --> [*] - - note right of Challenge - Initial state when - WatchtowerChallengeInit - confirmed on Bitcoin - end note - - note right of WTPhase - Monitor each watchtower - for challenges or timeout - end note - - note right of BlockHashPhase - Only proceeds after - WatchtowerChallengeStatus - reaches normal finish - end note - - note right of AssertPhase - Operator must commit - all assertions within - timelock window - end note +erDiagram + ChallengeSubStatus { + WatchtowerChallengeStatus watchtower_challenge_status + CommitBlockHashStatus commit_blockhash_status + AssertCommitStatus assert_commit_status + DisproveTxType disprove_type + int disprove_index + } + + WatchtowerChallengeStatus { + enum None + enum OperatorInit + enum WatchtowerChallenge + enum WatchtowerChallengeTimeout + enum OperatorACKTimeout + enum WatchtowerChallengeNormalFinished + enum WatchtowerChallengeDisproveFinished + } + + CommitBlockHashStatus { + enum None + enum WatchtowerChallengeProcessed + enum OperatorCommit + enum OperatorCommitTimeout + } + + AssertCommitStatus { + enum None + enum OperatorInit + enum OperatorCommit + enum OperatorCommitTimeout + } + + ChallengeSubStatus ||--|| WatchtowerChallengeStatus : contains + ChallengeSubStatus ||--|| CommitBlockHashStatus : contains + ChallengeSubStatus ||--|| AssertCommitStatus : contains ``` -**Transition Conditions to OperatorTake2:** -- `watchtower_challenge_status == WatchtowerChallengeNormalFinished` -- `commit_blockhash_status == OperatorCommit` -- `assert_commit_status == OperatorCommit` -- `disprove_type == None` (no errors detected) +**Description**: `ChallengeSubStatus` aggregates three parallel status trackers. Only when `watchtower_challenge_status == NormalFinished`, `commit_blockhash_status == OperatorCommit`, and `assert_commit_status == OperatorCommit` can the system transition to `OperatorTake2` state. + +--- + +## Graph Storage & Synchronization -### Challenge Phase: WatchtowerChallengeStatus +Graph data is stored locally in SQLite and distributed via P2P messaging. -From `src/scheduled_tasks/graph_maintenance_tasks.rs::WatchtowerChallengeStatus`: +### Fig-05-1-Graph-Storage-Architecture ```mermaid ---- -title: WatchtowerChallengeStatus - Challenge Phase Tracking ---- -stateDiagram-v2 - [*] --> None - - None --> OperatorInit: WatchtowerChallengeInitTx confirmed - - OperatorInit --> WatchtowerChallenge: Watchtowers may challenge - OperatorInit --> WatchtowerChallengeTimeout: No watchtower challenges - - WatchtowerChallenge --> WatchtowerChallengeNormalFinished: All watchtowers ACK - WatchtowerChallenge --> WatchtowerChallengeDisproveFinished: Any NACK/timeout - WatchtowerChallenge --> OperatorACKTimeout: Operator ACK timeout - - WatchtowerChallengeTimeout --> WatchtowerChallengeDisproveFinished - OperatorACKTimeout --> WatchtowerChallengeDisproveFinished - - WatchtowerChallengeNormalFinished --> [*] - WatchtowerChallengeDisproveFinished --> [*] - - note right of OperatorInit - Each watchtower index has item status: - - OperatorInit → Challenge → OperatorACK/NACK - - OperatorInit → ChallengeTimeout - end note +flowchart TB + subgraph Storage["SQLite Database"] + GT[("graph table\n(Metadata, TxIDs, Status)")] + GRD[("graph_raw_data table\n(Serialized JSON)")] + end + + subgraph Operations["Graph Operations"] + CREATE["store_graph()"] + READ["get_graph()"] + PARSE["parse_graph_raw_data()"] + SERIALIZE["serialize_graph_raw_data()"] + end + + subgraph P2P["P2P Synchronization"] + REQ["SyncGraphRequest"] + RESP["SyncGraph"] + RELAY["Relayer Nodes"] + end + + CREATE -->|"Atomic Transaction"| GT + CREATE -->|"JSON Serialization"| GRD + READ --> GT + READ --> GRD + PARSE -->|"spawn_blocking"| GRD + SERIALIZE -->|"spawn_blocking"| GRD + + REQ -->|"Broadcast to All"| RELAY + RELAY -->|"Lookup Local DB"| GRD + RELAY -->|"Respond with Graph"| RESP ``` -### Challenge Phase: CommitBlockHashStatus +**Description**: Graph storage uses a two-table approach: `graph` for metadata/transaction IDs and `graph_raw_data` for full serialized graph JSON. Large graph serialization uses `spawn_blocking` to prevent async runtime blocking. P2P synchronization enables nodes to request missing graphs from relayer nodes. -From `src/scheduled_tasks/graph_maintenance_tasks.rs::CommitBlockHashStatus`: +### Fig-05-2-Graph-Sync-Sequence ```mermaid ---- -title: CommitBlockHashStatus - Operator Commitment Tracking ---- -stateDiagram-v2 - [*] --> None - - None --> WatchtowerChallengeProcessed: Monitor challenges start - - WatchtowerChallengeProcessed --> OperatorCommit: Operator commits blockhash - WatchtowerChallengeProcessed --> OperatorCommitTimeout: Timelock expires - - OperatorCommit --> [*] - OperatorCommitTimeout --> [*] - - note right of WatchtowerChallengeProcessed - Waits for WatchtowerChallengeStatus - to complete before proceeding - end note +sequenceDiagram + autonumber + participant NodeA as Node A (Missing Graph) + participant P2P as P2P Network + participant Relayer as Relayer Node + participant DB as Relayer DB + + NodeA->>NodeA: Check local DB for graph + Note over NodeA: Graph NOT FOUND + + NodeA->>P2P: SyncGraphRequest(instance_id, graph_id) + P2P->>Relayer: Broadcast to All topic + + Relayer->>Relayer: is_relayer() check + Note over Relayer: ENABLE_RELAYER=true + + Relayer->>DB: Query graph_raw_data + DB-->>Relayer: Return serialized graph + + Relayer->>P2P: SyncGraph(instance_id, graph_id, graph_data) + P2P->>NodeA: Deliver response + + NodeA->>NodeA: Validate graph_id on GOAT chain + NodeA->>NodeA: store_graph() locally + Note over NodeA: Graph synchronized! ``` -### Challenge Phase: AssertCommitStatus +**Description**: When a node lacks required graph data, it broadcasts a `SyncGraphRequest`. Relayer nodes (Committee members with `ENABLE_RELAYER=true`) respond with the full graph data via `SyncGraph` message. The requesting node validates and stores the graph locally. -From `src/scheduled_tasks/graph_maintenance_tasks.rs::AssertCommitStatus`: +### Graph Data Tables + +#### graph table (Metadata) + +| Column | Type | Description | +|--------|------|-------------| +| `graph_id` | UUID | Primary key | +| `instance_id` | UUID | Associated bridge instance | +| `status` | String | Current GraphStatus | +| `sub_status` | String | Challenge sub-status JSON | +| `operator_pubkey` | String | Operator's public key | +| `*_txid` | Txid | Transaction IDs for all graph transactions | +| `created_at`, `updated_at` | i64 | Timestamps | + +#### graph_raw_data table (Full Graph) + +| Column | Type | Description | +|--------|------|-------------| +| `graph_id` | UUID | Primary key (FK to graph) | +| `raw_data` | TEXT | JSON-serialized SimplifiedBitvm2Graph | +| `created_at`, `updated_at` | i64 | Timestamps | -```mermaid ---- -title: AssertCommitStatus - Assert Phase Tracking --- -stateDiagram-v2 - [*] --> None - - None --> OperatorInit: AssertInitTx confirmed - - OperatorInit --> OperatorCommit: Operator commits assertion - OperatorInit --> OperatorCommitTimeout: Timelock expires - - OperatorCommit --> [*] - OperatorCommitTimeout --> [*] -``` -### Per-Watchtower Item Status +## Message System -From `src/scheduled_tasks/graph_maintenance_tasks.rs::WatchtowerChallengeItemStatus`: +### Fig-06-1-Message-Structure ```mermaid +classDiagram + class GOATMessage { + +Actor actor + +GOATMessageContent content + +serialize_message() Vec~u8~ + +deserialize_message() GOATMessage + } + + class GOATMessageContent { + <> + PeginRequest + CreateGraph + ConfirmInstance + NonceGeneration + CommitteePresign + EndorseGraph + GraphFinalize + KickoffReady + KickoffSent + ChallengeSent + DisproveReady + DisproveSent + Take1Sent + Take2Sent + SyncGraphRequest + SyncGraph + ... + } + + class Actor { + <> + Committee + Operator + Challenger + Watchtower + All + } + + GOATMessage --> Actor : target + GOATMessage --> GOATMessageContent : payload +``` + +**Description**: The message system uses role-based addressing. Each message specifies a target role and content type, broadcast via Gossipsub to the corresponding topic. Messages are serialized using `serde_json` with `spawn_blocking` for large payloads. + +### Message Categories + +#### Peg-in Messages + +| Message | Sender | Receiver | Description | +|---------|--------|----------|-------------| +| `PeginRequest` | EventWatch | Committee | Peg-in request notification | +| `ConfirmInstance` | Committee | Operator | Instance creation confirmation | +| `CreateGraph` | Operator | Committee | Graph creation request (includes full graph) | +| `NonceGeneration` | Committee | Committee/Operator | MuSig2 nonce broadcast | +| `CommitteePresign` | Committee | Committee/Operator | Presignature broadcast | +| `EndorseGraph` | Committee | Operator | Graph endorsement | +| `GraphFinalize` | Operator | All | Graph completion notification | + +#### Peg-out Messages + +| Message | Sender | Receiver | Description | +|---------|--------|----------|-------------| +| `KickoffReady` | System | Operator | Kickoff ready notification | +| `KickoffSent` | Operator | All | Kickoff transaction broadcast | +| `Take1Ready` | System | Operator | Take1 ready (Happy Path) | +| `Take1Sent` | Operator | All | Take1 transaction broadcast | +| `Take2Ready` | System | Operator | Take2 ready (Challenge Path) | +| `Take2Sent` | Operator | All | Take2 transaction broadcast | + +#### Challenge Messages + +| Message | Sender | Receiver | Description | +|---------|--------|----------|-------------| +| `WatchtowerChallengeInitSent` | Operator | Watchtower | WT challenge initialization | +| `WatchtowerChallengeSent` | Watchtower | Operator | WT challenge submission | +| `WatchtowerChallengeTimeout` | System | Operator | WT challenge timeout | +| `OperatorAckTimeout` | System | Challenger | Operator ACK timeout | +| `DisproveReady` | System | Challenger | Disprove ready | +| `DisproveSent` | Challenger | All | Disprove transaction broadcast | + +#### Synchronization Messages + +| Message | Sender | Receiver | Description | +|---------|--------|----------|-------------| +| `SyncGraphRequest` | Any Node | All | Request missing graph data | +| `SyncGraph` | Relayer | All | Respond with full graph data | + --- -title: WatchtowerChallengeItemStatus - Individual Watchtower Tracking ---- -stateDiagram-v2 - [*] --> None - - None --> OperatorInit: WatchtowerChallengeInitTx confirmed - - OperatorInit --> Challenge: Watchtower sends challenge tx - OperatorInit --> ChallengeTimeout: Timelock expires without challenge - - Challenge --> OperatorACK: Operator accepts challenge claim - Challenge --> OperatorNACK: Operator rejects challenge claim - - OperatorACK --> [*] - OperatorNACK --> [*] - ChallengeTimeout --> [*] - - note right of OperatorInit - Watchtower index state is tracked in - WTInitTxVoutMonitorData.data_map - end note + +## Scheduled Tasks + +### Fig-07-1-Task-Overview + +```mermaid +flowchart TB + subgraph EventWatch["Event Watch Task (Continuous)"] + EW1["fetch_and_handle_gateway_events"] + EW2["fetch_and_handle_bridge_out_events"] + EW1 --> MSG1["PeginRequest"] + EW2 --> MSG2["InitWithdraw detection"] + end + + subgraph GraphMaint["Graph Maintenance (20s interval)"] + GM1["detect_init_withdraw_call"] + GM2["detect_kickoff"] + GM3["detect_take1_or_challenge"] + GM4["process_graph_challenge"] + GM5["scan_obsolete_sibling_graphs"] + + GM1 -->|"KickoffReady"| GM2 + GM2 -->|"Status update"| GM3 + GM3 -->|"Challenge"| GM4 + GM3 -->|"Take1Ready"| T1["Take1 processing"] + GM4 -->|"Take2Ready"| T2["Take2 processing"] + GM4 -->|"DisproveReady"| DP["Disprove processing"] + end + + subgraph InstMaint["Instance Maintenance (20s interval)"] + IM1["instance_answers_monitor"] + IM2["instance_window_expiration_monitor"] + IM3["instance_btc_tx_monitor"] + IM4["instance_bridge_out_monitor"] + end + + subgraph Other["Other Tasks"] + NM["Node Maintenance"] + SPV["SPV Header Updates"] + end + + EventWatch --> GraphMaint + GraphMaint --> InstMaint ``` -## Core Data Structures +**Description**: The system runs 5 types of background tasks. Event Watch continuously monitors chain events; Graph Maintenance executes graph state machine transitions every 20 seconds; Instance Maintenance manages instance lifecycles; Node Maintenance maintains node health; SPV Maintenance updates the Bitcoin header chain. -### ChallengeSubStatus +### Task Responsibilities -From `src/scheduled_tasks/graph_maintenance_tasks.rs`: +| Task | Interval | Responsibility | +|------|----------|----------------| +| `run_watch_event_task` | Continuous | Monitor GOAT L2 and Bitcoin events | +| `detect_init_withdraw_call` | 20s | Detect withdrawal requests, send KickoffReady | +| `detect_kickoff` | 20s | Monitor Kickoff transaction confirmations | +| `detect_take1_or_challenge` | 20s | Check Happy Path or Challenge | +| `process_graph_challenge` | 20s | Process Challenge sub-phases | +| `instance_answers_monitor` | 20s | Track committee responses | +| `instance_window_expiration_monitor` | 20s | Handle response window timeouts | +| `spv_header_hash_update` | Periodic | Update SPV header hashes | -```rust -pub struct ChallengeSubStatus { - pub watchtower_challenge_status: WatchtowerChallengeStatus, - pub commit_blockhash_status: CommitBlockHashStatus, - pub assert_commit_status: AssertCommitStatus, - pub disprove_type: Option, - pub disprove_index: i32, -} +--- -// Helper methods: -pub fn is_watchtower_challenge_normal_finished(&self) -> bool -pub fn is_disproved(&self) -> bool -pub fn is_normal_finished(&self) -> bool -pub fn is_assert_commit_normal_finished(&self) -> bool +## Module Structure + +``` +node/src/ +├── main.rs # Entry point, starts 4 async tasks +├── lib.rs # Module exports +├── action.rs # Message type definitions (45+ types) +├── handle.rs # Message dispatch and role handlers +├── p2p_msg_handler.rs # P2P message processing interface +├── env.rs # Environment configuration (40+ variables) +├── utils.rs # Graph state management & storage utilities +├── error.rs # Error types +├── vk.rs # Verification key management +├── metrics_service.rs # Prometheus metrics +├── rpc_service/ # REST API implementation +│ ├── mod.rs # Service orchestration +│ ├── bitvm2.rs # BitVM2-specific endpoints +│ ├── routes.rs # HTTP route definitions +│ ├── validation.rs # Input validation +│ └── handler/ # Request handlers +├── middleware/ # P2P network layer +│ ├── swarm.rs # BitvmNetworkManager, libp2p configuration +│ └── behaviour.rs # Kademlia + Gossipsub behavior +└── scheduled_tasks/ # Background tasks + ├── mod.rs # Task exports + ├── event_watch_task.rs # Chain event monitoring (51KB) + ├── graph_maintenance_tasks.rs # Graph state machine (72KB) + ├── instance_maintenance_tasks.rs # Instance lifecycle + ├── node_maintenance_tasks.rs # Node health + └── spv_maintenance_tasks.rs # SPV updates ``` -### WTInitTxVoutMonitorData +--- + +## Build & Run + +### Prerequisites -From `src/scheduled_tasks/graph_maintenance_tasks.rs`: +- Rust nightly-2025-06-30+ +- ZKM toolchain (optional, for proof generation) -```rust -pub struct WTInitTxVoutMonitorData { - pub data_map: IndexMap, - pub require_disproved_indexes: Vec, - pub commit_blockhash_status: CommitBlockHashStatus, - pub is_challenge_timeout_sent: bool, -} +### Build Commands + +```bash +# Build for regtest +BITCOIN_NETWORK=regtest cargo build -r + +# Build for testnet4 +BITCOIN_NETWORK=testnet4 cargo build -r + +# Build only the node binary +cargo build -r -p bitvm2-noded ``` -- `data_map`: Tracks status for each watchtower index -- `require_disproved_indexes`: Indices requiring disprove (populated for items in OperatorInit or Challenge status) -- `commit_blockhash_status`: Synchronized with WatchtowerChallengeStatus -- `is_challenge_timeout_sent`: Flag for timeout message tracking - -### Status Enums - -```rust -pub enum WatchtowerChallengeStatus { - None, - OperatorInit, - WatchtowerChallenge, - WatchtowerChallengeTimeout, - OperatorACKTimeout, - WatchtowerChallengeNormalFinished, - WatchtowerChallengeDisproveFinished, -} - -pub enum CommitBlockHashStatus { - None, - WatchtowerChallengeProcessed, - OperatorCommit, - OperatorCommitTimeout, -} - -pub enum AssertCommitStatus { - None, - OperatorInit, - OperatorCommit, - OperatorCommitTimeout, -} - -pub enum WatchtowerChallengeItemStatus { - None, - OperatorInit, - Challenge, - ChallengeTimeout, - OperatorACK, - OperatorNACK, -} +### Generate Node Keys + +```bash +# Generate P2P peer key +bitvm2-noded key peer +# Output: +# PEER_KEY= +# PEER_ID= + +# Generate funding address (for Operator/Challenger) +bitvm2-noded key funding-address +# Output: +# Funding P2WSH address: bc1q... +``` + +### Start Node + +```bash +bitvm2-noded \ + --rpc-addr 0.0.0.0:8080 \ + --db-path ./node.db \ + --p2p-port 4001 \ + --bootnodes /ip4/x.x.x.x/tcp/4001/p2p/ ``` -## Key Implementation Files +### CLI Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--rpc-addr` | RPC service bind address | `0.0.0.0:8080` | +| `--db-path` | SQLite database path | `sqlite:/tmp/bitvm2-node.db` | +| `--p2p-port` | P2P listen port | `0` (random) | +| `--bootnodes` | Bootstrap node addresses | - | +| `--metrics-path` | Prometheus metrics endpoint | `/metrics` | +| `--enable-kademlia` | Enable Kademlia DHT | `true` | + +--- + +## Configuration -### Main Files +### Environment Variables -- `src/action.rs` - Message handling - - `recv_and_dispatch()` - Routes incoming messages by type - - Handles WatchtowerChallengeSent, OperatorAckTimeout, etc. +| Variable | Required | Description | Default | +|----------|----------|-------------|---------| +| `ACTOR` | Yes | Node role: `Committee`, `Operator`, `Challenger`, `Watchtower` | `Challenger` | +| `BITCOIN_NETWORK` | Yes | Bitcoin network: `bitcoin`, `testnet4`, `signet`, `regtest` | `testnet4` | +| `GOAT_NETWORK` | Yes | GOAT network: `main`, `test` | `test` | +| `GOAT_CHAIN_URL` | Yes | GOAT L2 RPC endpoint | - | +| `GOAT_GATEWAY_CONTRACT_ADDRESS` | Yes | Gateway contract address | - | +| `BITVM_SECRET` | Yes | Node private key or seed (`seed:xxx` format) | - | +| `PEER_KEY` | Yes | libp2p node key (Base64 encoded) | - | +| `GOAT_PRIVATE_KEY` | Conditional | GOAT chain private key (required for Committee) | - | +| `GOAT_ADDRESS` | Conditional | GOAT address (required for Operator/Challenger) | - | +| `ENABLE_RELAYER` | No | Enable relayer mode for Committee nodes | `false` | +| `BTC_CHAIN_URL` | No | Bitcoin Esplora API endpoint | Public Esplora | +| `GOAT_PROOF_BUILD_URL` | No | Proof Builder RPC endpoint | - | +| `NODE_NAME` | No | Node display name | `ZKM` | +| `OPERATOR_NODE_SERVICE_FEE` | No | Operator service fee rate | `0.001` | -- `src/scheduled_tasks/graph_maintenance_tasks.rs` - Challenge phase monitoring - - `process_watchtower_challenge_monitoring()` - Tracks watchtower challenges per index - - `process_commit_blockhash_monitoring()` - Monitors operator blockhash commitment - - `process_assert_commit_monitoring()` - Tracks assertion phase - - `detect_take1()` - Checks happy path withdrawal conditions - - `detect_take2()` - Checks disprove path withdrawal conditions +### Relayer Configuration -- `src/utils.rs` - Graph state updates - - `refresh_graph()` - Scans Bitcoin and updates graph status - - `get_watchtower_commitment()` - Fetches watchtower proofs - - `get_operator_proof()` - Fetches operator proofs +To run a Committee node as a graph data relayer: + +```bash +export ACTOR=Committee +export ENABLE_RELAYER=true +export GOAT_PRIVATE_KEY= +# ... other required variables +``` + +Relayer nodes should: +- Run 24/7 for network reliability +- Have sufficient storage for all graph data +- Be geographically distributed for redundancy + +--- + +## RPC API + +### Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/node` | GET | Get current node information | +| `/nodes` | GET | List connected nodes | +| `/nodes/overview` | GET | Node statistics overview | +| `/instances` | GET | List all instances | +| `/instance/:id` | GET | Get instance details | +| `/instances/overview` | GET | Instance statistics overview | +| `/graphs` | GET | List all graphs | +| `/graph/:id` | GET | Get graph details | +| `/graph/:id/txn` | GET | Get graph transaction list | +| `/graph/:id/tx/:txid` | GET | Get specific transaction | +| `/graph/ready_to_kickoff` | GET | Get graphs ready for kickoff | +| `/bridge_in_request` | POST | Initiate Bridge-In request | +| `/bridge_out_init` | POST | Initiate Bridge-Out request | +| `/challenge` | POST | Submit challenge | +| `/metrics` | GET | Prometheus metrics | + +--- -### Data Definitions +## Related Documentation -- `crates/store/src/schema.rs` - Core enums - - `GraphStatus` - Main graph states - - Related `DisproveTxType`, `ProofState` enums +- [Architecture Document](../docs/ARCHITECTURE.md) - Full system architecture +- [Circuits README](../circuits/README.md) - ZK proof generation +- [Deployment Guide](../deployment/README.md) - Deployment instructions +- [Incentives](../docs/incentives.md) - Incentives \ No newline at end of file diff --git a/node/src/action.rs b/node/src/action.rs index edc1ca6c..759b33d7 100644 --- a/node/src/action.rs +++ b/node/src/action.rs @@ -22,7 +22,6 @@ use musig2::{PartialSignature, PubNonce}; use secp256k1::schnorr::Signature as SchnorrSignature; use serde::{Deserialize, Serialize}; use store::MessageState; -use store::ipfs::IPFS; use store::localdb::LocalDB; use tracing::warn; use uuid::Uuid; @@ -307,7 +306,6 @@ pub async fn handle_self_p2p_msg( btc_client: &BTCClient, goat_client: &GOATClient, http_client: &HttpAsyncClient, - ipfs: &IPFS, actor: Actor, from_peer_id: PeerId, id: MessageId, @@ -334,7 +332,6 @@ pub async fn handle_self_p2p_msg( btc_client, goat_client, http_client, - ipfs, actor.clone(), from_peer_id, id.clone(), @@ -383,7 +380,6 @@ pub async fn recv_and_dispatch( btc_client: &BTCClient, goat_client: &GOATClient, http_client: &HttpAsyncClient, - ipfs: &IPFS, actor: Actor, from_peer_id: PeerId, id: MessageId, @@ -401,7 +397,6 @@ pub async fn recv_and_dispatch( btc_client, goat_client, http_client, - ipfs, actor, from_peer_id, id, diff --git a/node/src/env.rs b/node/src/env.rs index 3b8c6dbc..3648787d 100644 --- a/node/src/env.rs +++ b/node/src/env.rs @@ -192,11 +192,6 @@ pub fn get_peer_id() -> String { key_pair.public().to_peer_id().to_string() } -pub fn get_ipfs_url() -> String { - let default_url: &str = "http://44.229.236.82:5001"; - std::env::var(ENV_IPFS_ENDPOINT).unwrap_or(default_url.to_string()) -} - pub fn is_relayer() -> bool { let enable_relayer = match std::env::var(ENV_ENABLE_RELAYER) { Ok(value) => value.to_lowercase() == "true", diff --git a/node/src/handle.rs b/node/src/handle.rs index c18af214..1bbc7192 100644 --- a/node/src/handle.rs +++ b/node/src/handle.rs @@ -23,7 +23,6 @@ use goat::transactions::pre_signed_musig2::verify_public_nonce; use libp2p::gossipsub::MessageId; use libp2p::{PeerId, Swarm}; use store::GraphStatus; -use store::ipfs::IPFS; use store::localdb::LocalDB; use uuid::Uuid; @@ -33,7 +32,6 @@ pub struct HandlerContext<'a> { pub btc_client: &'a BTCClient, pub goat_client: &'a GOATClient, pub http_client: &'a HttpAsyncClient, - pub ipfs: &'a IPFS, pub actor: Actor, pub from_peer_id: PeerId, pub id: MessageId, @@ -1162,7 +1160,7 @@ async fn handle_nonce_generation_operator( pub_nonces.clone(), ) .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to IPFS, broadcast GraphFinalize + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local db, broadcast GraphFinalize // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them try_finalize_graph( @@ -1284,7 +1282,7 @@ async fn handle_committee_presign_operator( committee_partial_sigs.clone(), ) .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to IPFS, broadcast GraphFinalize + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them try_finalize_graph(ctx.swarm, ctx.local_db, ctx.goat_client, instance_id, graph_id, None, true) @@ -1350,7 +1348,7 @@ async fn handle_endorse_graph_operator( committee_sig_for_graph.to_owned(), ) .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to IPFS, broadcast GraphFinalize + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them try_finalize_graph( @@ -1375,7 +1373,7 @@ async fn handle_graph_finalize_committee( endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], ) -> Result<()> { // received from Operator - // 1. check graph data & ipfs cid + // 1. check graph data if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await { if should_ignore_invalid_graph( @@ -1463,7 +1461,7 @@ async fn handle_graph_finalize_default( endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], ) -> Result<()> { // received from Operator - // 1. check graph data & ipfs cid + // 1. check graph data if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await { if should_ignore_invalid_graph( diff --git a/node/src/main.rs b/node/src/main.rs index 777df3ba..bfcf7d03 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -2,8 +2,8 @@ use base64::Engine; use bitvm2_lib::actors::Actor; use bitvm2_noded::env::{ - self, ENV_PEER_KEY, check_node_info, get_btc_url_from_env, get_goat_network, get_ipfs_url, - get_network, get_node_pubkey, goat_config_from_env, + self, ENV_PEER_KEY, check_node_info, get_btc_url_from_env, get_goat_network, get_network, + get_node_pubkey, goat_config_from_env, }; use clap::{Parser, Subcommand}; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; @@ -11,7 +11,6 @@ use libp2p::PeerId; use libp2p_metrics::Registry; use std::error::Error; use std::sync::{Arc, Mutex}; -use store::ipfs::IPFS; use tracing_subscriber::EnvFilter; use bitvm2_noded::utils::{ @@ -149,7 +148,6 @@ async fn main() -> Result<(), Box> { btc_client: BTCClient::new(get_network(), get_btc_url_from_env().as_deref()), goat_client: GOATClient::new(env::goat_config_from_env().await, env::get_goat_network()), http_client: HttpAsyncClient::new(None), - ipfs: IPFS::new(&get_ipfs_url()), }; let actor_clone1 = actor.clone(); diff --git a/node/src/p2p_msg_handler.rs b/node/src/p2p_msg_handler.rs index 86feec02..868f3ed5 100644 --- a/node/src/p2p_msg_handler.rs +++ b/node/src/p2p_msg_handler.rs @@ -9,7 +9,6 @@ use client::http_client::async_client::HttpAsyncClient; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; use libp2p::PeerId; use libp2p::gossipsub::MessageId; -use store::ipfs::IPFS; use store::localdb::LocalDB; pub struct BitvmNodeProcessor { @@ -17,7 +16,6 @@ pub struct BitvmNodeProcessor { pub btc_client: BTCClient, pub goat_client: GOATClient, pub http_client: HttpAsyncClient, - pub ipfs: IPFS, } impl P2pMessageHandler for BitvmNodeProcessor { async fn recv_and_dispatch( @@ -34,7 +32,6 @@ impl P2pMessageHandler for BitvmNodeProcessor { &self.btc_client, &self.goat_client, &self.http_client, - &self.ipfs, actor, from_peer_id, id, @@ -74,7 +71,6 @@ impl P2pMessageHandler for BitvmNodeProcessor { &self.btc_client, &self.goat_client, &self.http_client, - &self.ipfs, actor, peer_id, GOATMessage::default_message_id(), diff --git a/node/src/rpc_service/handler/bitvm2_handler.rs b/node/src/rpc_service/handler/bitvm2_handler.rs index c42056c7..4715193a 100644 --- a/node/src/rpc_service/handler/bitvm2_handler.rs +++ b/node/src/rpc_service/handler/bitvm2_handler.rs @@ -712,7 +712,6 @@ pub async fn get_instances_overview( /// "kickoff_index": 10, /// "from_addr": "0x1234567890abcdef1234567890abcdef12345678", /// "to_addr": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", -/// "graph_ipfs_base_url": "https://ipfs.io/ipfs/Qm...", /// "amount": 2000000, /// "challenge_amount": 1000000, /// "status": "OperatorPresigned", @@ -806,7 +805,6 @@ pub async fn get_graph( /// "kickoff_index": 10, /// "from_addr": "0x1234567890abcdef1234567890abcdef12345678", /// "to_addr": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", -/// "graph_ipfs_base_url": "https://ipfs.io/ipfs/Qm...", /// "amount": 2000000, /// "challenge_amount": 1000000, /// "status": "OperatorPresigned", @@ -910,7 +908,6 @@ pub async fn get_graphs( /// "kickoff_index": 10, /// "from_addr": "0x1234567890abcdef1234567890abcdef12345678", /// "to_addr": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", -/// "graph_ipfs_base_url": "", /// "amount": 2000000, /// "challenge_amount": 1000000, /// "status": "OperatorDataPushed", diff --git a/node/src/rpc_service/mod.rs b/node/src/rpc_service/mod.rs index 9cb9e31a..01a42333 100644 --- a/node/src/rpc_service/mod.rs +++ b/node/src/rpc_service/mod.rs @@ -551,7 +551,6 @@ mod tests { kickoff_index: 0, from_addr: graph_from.clone(), to_addr: graph_to.clone(), - graph_ipfs_base_url: "".to_string(), amount: bridge_in_amount, challenge_amount: bridge_in_amount, status: graph_status.clone(), @@ -588,7 +587,6 @@ mod tests { kickoff_index: 0, from_addr: graph_from.clone(), to_addr: graph_to.clone(), - graph_ipfs_base_url: "".to_string(), amount: bridge_in_amount, challenge_amount: bridge_in_amount, status: GraphStatus::CommitteePresigned.to_string(), diff --git a/node/src/utils.rs b/node/src/utils.rs index d79dc287..16dc6c4f 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -59,7 +59,6 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use store::ipfs::IPFS; use store::localdb::{ GraphQuery, GraphUpdate, InstanceQuery, InstanceUpdate, LocalDB, StorageProcessor, }; @@ -128,9 +127,6 @@ pub mod todo_funcs { // todo!("get min required watchtower number") 1 } - pub async fn publish_graph_to_ipfs(ipfs: &IPFS, graph: &Bitvm2Graph) -> Result { - todo!("publish graph to ipfs") - } pub async fn validate_init_graph( local_db: &LocalDB, btc_client: &BTCClient, @@ -3281,14 +3277,6 @@ pub async fn get_bitvm2_graph_from_db( Err(anyhow!("graph:{graph_id} not found")) } -pub async fn publish_graph_to_ipfs( - _ipfs: &IPFS, - _graph_id: Uuid, - _graph: &Bitvm2Graph, -) -> Result { - todo!("publish_graph_to_ipfs") -} - pub async fn get_graph_status( local_db: &LocalDB, instance_id: Uuid, @@ -3770,7 +3758,6 @@ pub async fn store_graph(local_db: &LocalDB, simple_graph: &SimplifiedBitvm2Grap kickoff_index: graph_nonce as i64, from_addr: "".to_string(), to_addr: "".to_string(), - graph_ipfs_base_url: "".to_string(), amount: bitvm2_graph.parameters.instance_parameters.pegin_amount.to_sat() as i64, challenge_amount: bitvm2_graph.parameters.challenge_amount.to_sat() as i64, status, diff --git a/scripts/bitcoin-init.sh b/scripts/bitcoin-init.sh index 9d5b486d..19724d30 100755 --- a/scripts/bitcoin-init.sh +++ b/scripts/bitcoin-init.sh @@ -51,4 +51,4 @@ apt-get update apt-get install -y procps # A terminal-based program (like watch, top, less, etc.) runs in an environment, TERM environment variable should be set export TERM=xterm -watch -n 600 "$BTC -generate 1 && $BTC walletpassphrase $WALLET_PASSPHRASE 600 && $BTC keypoolrefill" +watch -n 60 "$BTC -generate 1 && $BTC walletpassphrase $WALLET_PASSPHRASE 600 && $BTC keypoolrefill"