diff --git a/crates/bitvm2/src/committee/api.rs b/crates/bitvm2/src/committee/api.rs index 5da72cbf..442315fe 100644 --- a/crates/bitvm2/src/committee/api.rs +++ b/crates/bitvm2/src/committee/api.rs @@ -1,7 +1,7 @@ use crate::types::Bitvm2Graph; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use bitcoin::PublicKey; -use bitcoin::{hex::FromHex, key::Keypair, TapSighashType, Witness}; +use bitcoin::{TapSighashType, Witness, hex::FromHex, key::Keypair}; use goat::connectors::{connector_0::Connector0, connector_5::Connector5, connector_d::ConnectorD}; use goat::contexts::base::generate_n_of_n_public_key; use goat::transactions::signing_musig2::{ @@ -11,7 +11,7 @@ use goat::transactions::{ base::BaseTransaction, pre_signed::PreSignedTransaction, pre_signed_musig2::get_nonce_message, signing_musig2::generate_taproot_partial_signature, }; -use musig2::{secp256k1::schnorr::Signature, AggNonce, PartialSignature, PubNonce, SecNonce}; +use musig2::{AggNonce, PartialSignature, PubNonce, SecNonce, secp256k1::schnorr::Signature}; use sha2::{Digest, Sha256}; pub const COMMITTEE_PRE_SIGN_NUM: usize = 5; diff --git a/crates/bitvm2/src/committee/mod.rs b/crates/bitvm2/src/committee/mod.rs index 3b048931..3a281e03 100644 --- a/crates/bitvm2/src/committee/mod.rs +++ b/crates/bitvm2/src/committee/mod.rs @@ -1,7 +1,7 @@ mod api; pub use api::{ - committee_pre_sign, generate_keypair_from_seed, generate_nonce_from_seed, key_aggregation, - nonce_aggregation, nonces_aggregation, push_committee_pre_signatures, - signature_aggregation_and_push, COMMITTEE_PRE_SIGN_NUM, + COMMITTEE_PRE_SIGN_NUM, committee_pre_sign, generate_keypair_from_seed, + generate_nonce_from_seed, key_aggregation, nonce_aggregation, nonces_aggregation, + push_committee_pre_signatures, signature_aggregation_and_push, }; diff --git a/crates/bitvm2/src/keys.rs b/crates/bitvm2/src/keys.rs index 597f2edd..fcd2f41d 100644 --- a/crates/bitvm2/src/keys.rs +++ b/crates/bitvm2/src/keys.rs @@ -1,10 +1,10 @@ use super::{ - committee::{generate_keypair_from_seed, generate_nonce_from_seed, COMMITTEE_PRE_SIGN_NUM}, + committee::{COMMITTEE_PRE_SIGN_NUM, generate_keypair_from_seed, generate_nonce_from_seed}, operator::generate_wots_keys, types::{WotsPublicKeys, WotsSecretKeys}, }; use bitcoin::key::Keypair; -use musig2::{secp256k1::schnorr::Signature, PubNonce, SecNonce}; +use musig2::{PubNonce, SecNonce, secp256k1::schnorr::Signature}; use sha2::{Digest, Sha256}; use uuid::Uuid; diff --git a/crates/bitvm2/src/operator/api.rs b/crates/bitvm2/src/operator/api.rs index 5b8cbe74..f29a52b9 100644 --- a/crates/bitvm2/src/operator/api.rs +++ b/crates/bitvm2/src/operator/api.rs @@ -1,19 +1,19 @@ use crate::types::{ - get_magic_bytes, Bitvm2Graph, Bitvm2Parameters, CustomInputs, Groth16Proof, - Groth16WotsPublicKeys, Groth16WotsSignatures, PublicInputs, VerifyingKey, WotsPublicKeys, - WotsSecretKeys, + Bitvm2Graph, Bitvm2Parameters, CustomInputs, Groth16Proof, Groth16WotsPublicKeys, + Groth16WotsSignatures, PublicInputs, VerifyingKey, WotsPublicKeys, WotsSecretKeys, + get_magic_bytes, }; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use bitcoin::Transaction; -use bitcoin::{key::Keypair, Amount, OutPoint, Witness, XOnlyPublicKey}; +use bitcoin::{Amount, OutPoint, Witness, XOnlyPublicKey, key::Keypair}; use bitvm::chunk::api::{ - api_generate_full_tapscripts, api_generate_partial_script, generate_signatures_lit, - type_conversion_utils::utils_raw_witnesses_from_signatures, NUM_HASH, NUM_PUBS, NUM_U256, + NUM_HASH, NUM_PUBS, NUM_U256, api_generate_full_tapscripts, api_generate_partial_script, + generate_signatures_lit, type_conversion_utils::utils_raw_witnesses_from_signatures, }; use bitvm::signatures::{ - signing_winternitz::{WinternitzPublicKey, WinternitzSecret, WinternitzSigningInputs, LOG_D}, + signing_winternitz::{LOG_D, WinternitzPublicKey, WinternitzSecret, WinternitzSigningInputs}, winternitz::Parameters, - wots_api::{wots256, wots_hash}, + wots_api::{wots_hash, wots256}, }; use bitvm::treepp::*; use goat::commitments::{CommitmentMessageId, KICKOFF_MSG_SIZE, NUM_KICKOFF}; @@ -28,8 +28,8 @@ use goat::transactions::{ assert::assert_final::AssertFinalTransaction, assert::assert_initial::AssertInitialTransaction, assert::utils::{ - convert_to_connector_c_commits_public_key, AllCommitConnectorsE, AssertCommitConnectorsF, - COMMIT_TX_NUM, + AllCommitConnectorsE, AssertCommitConnectorsF, COMMIT_TX_NUM, + convert_to_connector_c_commits_public_key, }, base::Input, challenge::ChallengeTransaction, diff --git a/crates/bitvm2/src/pegin.rs b/crates/bitvm2/src/pegin.rs index 4b4e1925..0b355ac1 100644 --- a/crates/bitvm2/src/pegin.rs +++ b/crates/bitvm2/src/pegin.rs @@ -1,6 +1,6 @@ use bitcoin::opcodes::all::OP_RETURN; use bitcoin::script::Instruction; -use bitcoin::{script, Network, Script}; +use bitcoin::{Network, Script, script}; use crate::types::get_magic_bytes; diff --git a/crates/bitvm2/src/types.rs b/crates/bitvm2/src/types.rs index ad55ab66..7615966f 100644 --- a/crates/bitvm2/src/types.rs +++ b/crates/bitvm2/src/types.rs @@ -1,7 +1,7 @@ use bitcoin::TapNodeHash; -use bitcoin::{key::Keypair, Address, Amount, Network, PrivateKey, PublicKey, XOnlyPublicKey}; +use bitcoin::{Address, Amount, Network, PrivateKey, PublicKey, XOnlyPublicKey, key::Keypair}; use bitvm::chunk::api::{ - PublicKeys as ApiWotsPublicKeys, Signatures as ApiWotsSignatures, NUM_HASH, NUM_PUBS, NUM_U256, + NUM_HASH, NUM_PUBS, NUM_U256, PublicKeys as ApiWotsPublicKeys, Signatures as ApiWotsSignatures, }; use bitvm::signatures::signing_winternitz::{WinternitzPublicKey, WinternitzSecret}; use goat::commitments::NUM_KICKOFF; @@ -15,7 +15,7 @@ use goat::transactions::{ kick_off::KickOffTransaction, peg_in::peg_in::PegInTransaction, peg_out_confirm::PreKickoffTransaction, take_1::Take1Transaction, take_2::Take2Transaction, }; -use rand::{distributions::Alphanumeric, Rng}; +use rand::{Rng, distributions::Alphanumeric}; use secp256k1::SECP256K1; use serde::{Deserialize, Serialize}; @@ -172,7 +172,7 @@ pub fn get_magic_bytes(net: &Network) -> Vec { } pub mod node_serializer { - use serde::{self, ser::Error, Deserialize, Deserializer, Serializer}; + use serde::{self, Deserialize, Deserializer, Serializer, ser::Error}; use std::str::FromStr; pub mod address { @@ -203,7 +203,7 @@ pub mod node_serializer { use crate::types::WotsPublicKeys; use bitvm::chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256}; use bitvm::signatures::signing_winternitz::WinternitzPublicKey; - use bitvm::signatures::wots_api::{wots256, wots_hash}; + use bitvm::signatures::wots_api::{wots_hash, wots256}; use goat::commitments::NUM_KICKOFF; use std::collections::HashMap; @@ -214,17 +214,17 @@ pub mod node_serializer { let mut pubkeys_map: HashMap>> = HashMap::new(); let mut index = 0; // wots pk for groth16 proof - for pk in pubkeys.1 .0 { + for pk in pubkeys.1.0 { let v: Vec> = pk.iter().map(|x| x.to_vec()).collect(); pubkeys_map.insert(index, v); index += 1; } - for pk in pubkeys.1 .1 { + for pk in pubkeys.1.1 { let v: Vec> = pk.iter().map(|x| x.to_vec()).collect(); pubkeys_map.insert(index, v); index += 1; } - for pk in pubkeys.1 .2 { + for pk in pubkeys.1.2 { let v: Vec> = pk.iter().map(|x| x.to_vec()).collect(); pubkeys_map.insert(index, v); index += 1; @@ -359,11 +359,11 @@ pub mod node_serializer { pub mod wots_seckeys { use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeTuple; - use serde::{de::Error as DeError, ser::Error, Deserializer, Serializer}; + use serde::{Deserializer, Serializer, de::Error as DeError, ser::Error}; use std::fmt; use crate::types::{ - Groth16WotsSecretKeys, KickoffWotsSecretKeys, WotsSecretKeys, NUM_KICKOFF, NUM_SIGS, + Groth16WotsSecretKeys, KickoffWotsSecretKeys, NUM_KICKOFF, NUM_SIGS, WotsSecretKeys, }; pub fn serialize(keys: &WotsSecretKeys, serializer: S) -> Result @@ -445,7 +445,7 @@ pub mod node_serializer { #[cfg(test)] mod tests { use crate::operator::generate_wots_keys; - use crate::types::{node_serializer, WotsPublicKeys, WotsSecretKeys}; + use crate::types::{WotsPublicKeys, WotsSecretKeys, node_serializer}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/crates/bitvm2/src/verifier/api.rs b/crates/bitvm2/src/verifier/api.rs index 949cd585..b2c3a38a 100644 --- a/crates/bitvm2/src/verifier/api.rs +++ b/crates/bitvm2/src/verifier/api.rs @@ -1,14 +1,15 @@ use crate::types::{ Bitvm2Graph, Groth16WotsPublicKeys, Groth16WotsSignatures, VerifyingKey, WotsPublicKeys, }; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use bitcoin::{Address, Amount, Transaction}; use bitvm::chunk::api::{ - type_conversion_utils::{script_to_witness, utils_signatures_from_raw_witnesses, RawWitness}, - validate_assertions, NUM_TAPS, + NUM_TAPS, + type_conversion_utils::{RawWitness, script_to_witness, utils_signatures_from_raw_witnesses}, + validate_assertions, }; use bitvm::treepp::*; -use goat::connectors::connector_c::{get_commit_from_assert_commit_tx, ConnectorC}; +use goat::connectors::connector_c::{ConnectorC, get_commit_from_assert_commit_tx}; use goat::transactions::assert::utils::*; use goat::transactions::{base::BaseTransaction, pre_signed::PreSignedTransaction}; diff --git a/crates/client/src/chain/goat_adaptor.rs b/crates/client/src/chain/goat_adaptor.rs index f3c449a8..3f47702a 100644 --- a/crates/client/src/chain/goat_adaptor.rs +++ b/crates/client/src/chain/goat_adaptor.rs @@ -5,13 +5,13 @@ use crate::chain::goat_adaptor::IGateway::IGatewayInstance; use alloy::primitives::TxHash; use alloy::{ eips::BlockNumberOrTag, - network::{eip2718::Encodable2718, Ethereum, EthereumWallet, NetworkWallet}, + network::{Ethereum, EthereumWallet, NetworkWallet, eip2718::Encodable2718}, primitives::{Address as EvmAddress, Bytes, ChainId, FixedBytes, U256}, providers::{Provider, ProviderBuilder, RootProvider}, rpc::types::TransactionRequest, - signers::{local::PrivateKeySigner, Signer}, + signers::{Signer, local::PrivateKeySigner}, sol, - transports::http::{reqwest::Url, Client, Http}, + transports::http::{Client, Http, reqwest::Url}, }; use anyhow::format_err; use async_trait::async_trait; @@ -282,7 +282,7 @@ impl ChainAdaptor for GoatAdaptor { .await?; Ok(WithdrawData { pegin_txid: res._0.0, - operator_address: res._1.0 .0, + operator_address: res._1.0.0, status: res._2.into(), instance_id: Uuid::from_slice(res._3.as_slice())?, lock_amount: res._4, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index e7f1b0a5..db89b721 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1,6 +1,6 @@ use crate::chain::chain::Chain; use crate::chain::chain_adaptor::{ - get_chain_adaptor, BitcoinTx, GoatNetwork, OperatorData, PeginData, + BitcoinTx, GoatNetwork, OperatorData, PeginData, get_chain_adaptor, }; use crate::chain::goat_adaptor::GoatInitConfig; use crate::esplora::get_esplora_url; diff --git a/crates/identity/src/musig2.rs b/crates/identity/src/musig2.rs index 4ddd46b8..c155b38a 100644 --- a/crates/identity/src/musig2.rs +++ b/crates/identity/src/musig2.rs @@ -2,8 +2,8 @@ use rand::RngCore; use secp256k1::{Secp256k1, SecretKey}; use std::string::String; -use musig2::k256::PublicKey; use musig2::KeyAggContext; +use musig2::k256::PublicKey; use musig2::{ CompactSignature, FirstRound, PartialSignature, PubNonce, SecNonceSpices, SecondRound, }; diff --git a/crates/spv/src/header_chain.rs b/crates/spv/src/header_chain.rs index 3ac756af..c1d980fc 100644 --- a/crates/spv/src/header_chain.rs +++ b/crates/spv/src/header_chain.rs @@ -1,7 +1,7 @@ use bitcoin::{ + BlockHash, CompactTarget, TxMerkleNode, block::{Header, Version}, hashes::Hash, - BlockHash, CompactTarget, TxMerkleNode, }; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; diff --git a/crates/spv/src/merkle_tree.rs b/crates/spv/src/merkle_tree.rs index bb58ecc4..7fed80ee 100644 --- a/crates/spv/src/merkle_tree.rs +++ b/crates/spv/src/merkle_tree.rs @@ -137,9 +137,9 @@ pub fn verify_merkle_proof( mod tests { use crate::transaction::CircuitTransaction; + use bitcoin::Block; use bitcoin::hashes::Hash; use bitcoin::hex::FromHex; - use bitcoin::Block; use super::*; diff --git a/crates/store/src/ipfs.rs b/crates/store/src/ipfs.rs index 3c3fc98b..c18a72ed 100644 --- a/crates/store/src/ipfs.rs +++ b/crates/store/src/ipfs.rs @@ -1,7 +1,7 @@ -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use futures::TryStreamExt; -use reqwest::multipart::{Form, Part}; use reqwest::Client; +use reqwest::multipart::{Form, Part}; use serde::Deserialize; use std::path::Path; use tokio::fs::File; diff --git a/crates/store/src/localdb.rs b/crates/store/src/localdb.rs index 88602945..9810406d 100644 --- a/crates/store/src/localdb.rs +++ b/crates/store/src/localdb.rs @@ -1,13 +1,13 @@ use crate::schema::NODE_STATUS_OFFLINE; use crate::schema::NODE_STATUS_ONLINE; use crate::{ - GrapRpcQueryData, Graph, Instance, Message, Node, NodesOverview, NonceCollect, - NonceCollectMetaData, PubKeyCollect, PubKeyCollectMetaData, COMMITTEE_PRE_SIGN_NUM, + COMMITTEE_PRE_SIGN_NUM, GrapRpcQueryData, Graph, Instance, Message, Node, NodesOverview, + NonceCollect, NonceCollectMetaData, PubKeyCollect, PubKeyCollectMetaData, }; use sqlx::migrate::Migrator; use sqlx::pool::PoolConnection; use sqlx::types::Uuid; -use sqlx::{migrate::MigrateDatabase, Row, Sqlite, SqliteConnection, SqlitePool, Transaction}; +use sqlx::{Row, Sqlite, SqliteConnection, SqlitePool, Transaction, migrate::MigrateDatabase}; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Clone)] diff --git a/node/Cargo.toml b/node/Cargo.toml index 39a5e35a..f2679f44 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -76,6 +76,9 @@ sha2 = { workspace = true } bincode = "1.3.3" bitcoin-script = { workspace = true } once_cell = { workspace = true } +ark-bn254 = { workspace = true } +ark-groth16 = { workspace = true } +ark-serialize = { workspace = true } [dev-dependencies] tempfile = "3.19.1" diff --git a/node/src/action.rs b/node/src/action.rs index a4dffc07..60c92d5c 100644 --- a/node/src/action.rs +++ b/node/src/action.rs @@ -1,30 +1,22 @@ use crate::env; use crate::middleware::AllBehaviours; -use crate::rpc_service::current_time_secs; +use crate::utils::{statics::*, *}; use anyhow::Result; use bitcoin::PublicKey; -use bitcoin::{key::Keypair, Amount, Network, OutPoint, Txid}; +use bitcoin::{Amount, Network, Txid}; use bitvm2_lib::actors::Actor; use bitvm2_lib::keys::*; -use bitvm2_lib::types::{ - Bitvm2Graph, Bitvm2Parameters, CustomInputs, Groth16Proof, PublicInputs, VerifyingKey, -}; +use bitvm2_lib::types::{Bitvm2Graph, Bitvm2Parameters, CustomInputs}; use bitvm2_lib::verifier::export_challenge_tx; use bitvm2_lib::{committee::*, operator::*, verifier::*}; -use client::client::BitVM2Client; -use goat::transactions::disprove::DisproveTransaction; -use goat::transactions::take_1::Take1Transaction; -use goat::transactions::take_2::Take2Transaction; use goat::transactions::{assert::utils::COMMIT_TX_NUM, pre_signed::PreSignedTransaction}; use libp2p::gossipsub::MessageId; -use libp2p::{gossipsub, PeerId, Swarm}; +use libp2p::{PeerId, Swarm, gossipsub}; use musig2::{AggNonce, PartialSignature, PubNonce, SecNonce}; use reqwest::Request; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use statics::*; -use std::str::FromStr; -use store::{Graph, GraphStatus}; +use store::GraphStatus; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize)] @@ -182,539 +174,6 @@ impl GOATMessage { } } -pub mod statics { - use once_cell::sync::Lazy; - use std::sync::Mutex; - use uuid::Uuid; - - // operator node can only process one graph at a time - pub static OPERATOR_CURRENT_GRAPH: Lazy>> = - Lazy::new(|| Mutex::new(None)); - pub fn try_start_new_graph(instance_id: Uuid, graph_id: Uuid) -> bool { - let mut current = OPERATOR_CURRENT_GRAPH.lock().unwrap(); - if current.is_none() { - *current = Some((instance_id, graph_id)); - true - } else { - false - } - } - pub fn finish_current_graph_processing(instance_id: Uuid, graph_id: Uuid) { - let mut current = OPERATOR_CURRENT_GRAPH.lock().unwrap(); - if *current == Some((instance_id, graph_id)) { - *current = None; - } - } - pub fn is_processing_graph() -> bool { - OPERATOR_CURRENT_GRAPH.lock().unwrap().is_some() - } - pub fn current_processing_graph() -> Option<(Uuid, Uuid)> { - *OPERATOR_CURRENT_GRAPH.lock().unwrap() - } - pub fn force_stop_current_graph() { - *OPERATOR_CURRENT_GRAPH.lock().unwrap() = None; - } -} - -#[allow(unused_variables, dead_code)] -pub mod todo_funcs { - use super::*; - use crate::env::*; - use bitcoin::{ - Address, EcdsaSighashType, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, - }; - use bitcoin_script::{script, Script}; - use bitvm::chunk::api::NUM_TAPS; - use bitvm2_lib::types::WotsPublicKeys; - use client::chain::chain_adaptor::WithdrawStatus; - use esplora_client::Utxo; - use goat::constants::{CONNECTOR_3_TIMELOCK, CONNECTOR_4_TIMELOCK}; - use goat::transactions::base::Input; - use goat::transactions::signing::populate_p2wsh_witness; - use goat::utils::num_blocks_per_network; - use std::fs::{self, File}; - use std::io::{BufReader, BufWriter}; - use std::path::Path; - - /// Determines whether the operator should participate in generating a new graph. - /// - /// Conditions: - /// - Participation should be attempted as often as possible. - /// - Only one graph can be generated at a time; generation must be sequential, not parallel. - /// - If the remaining funds are less than the required stake-amount, operator should not participate. - pub async fn should_generate_graph( - client: &BitVM2Client, - create_graph_prepare_data: &CreateGraphPrepare, - ) -> Result> { - if is_processing_graph() { - return Ok(false); - }; - let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); - let utxos = client.esplora.get_address_utxo(node_address).await?; - let utxo_spent_fee = Amount::from_sat( - (get_fee_rate(client).await? * 2.0 * CHEKSIG_P2WSH_INPUT_VBYTES as f64).ceil() as u64, - ); - let total_effective_balance: Amount = - utxos.iter().map(|utxo| utxo.value - utxo_spent_fee).sum(); - Ok(total_effective_balance - > get_stake_amount(create_graph_prepare_data.pegin_amount.to_sat())) - } - - /// Checks whether the status of the graph (identified by instance ID and graph ID) - /// on the Layer 2 contract is currently `Initialized`. - pub async fn is_withdraw_initialized_on_l2( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, - ) -> Result> { - let withdraw_status = - client.chain_service.adaptor.get_withdraw_data(graph_id).await?.status; - Ok(withdraw_status == WithdrawStatus::Initialized) - } - - /// Checks whether the timelock for the specified kickoff transaction has expired, - /// indicating that the `take1` transaction can now be sent. - /// - /// The timelock duration is a fixed constant (goat::constants::CONNECTOR_3_TIMELOCK) - pub async fn is_take1_timelock_expired( - client: &BitVM2Client, - kickoff_txid: Txid, - ) -> Result> { - let lock_blocks = num_blocks_per_network(get_network(), CONNECTOR_3_TIMELOCK); - let tx_status = client.esplora.get_tx_status(&kickoff_txid).await?; - match tx_status.block_height { - Some(tx_height) => { - let current_height = client.esplora.get_height().await?; - Ok(current_height > tx_height + lock_blocks) - } - _ => Ok(false), - } - } - - /// Checks whether the timelock for the specified assert-final transaction has expired, - /// allowing the `take2` transaction to proceed. - /// - /// The timelock duration is a fixed constant (goat::constants::CONNECTOR_4_TIMELOCK) - pub async fn is_take2_timelock_expired( - client: &BitVM2Client, - assert_final_txid: Txid, - ) -> Result> { - let lock_blocks = num_blocks_per_network(get_network(), CONNECTOR_4_TIMELOCK); - let tx_status = client.esplora.get_tx_status(&assert_final_txid).await?; - match tx_status.block_height { - Some(tx_height) => { - let current_height = client.esplora.get_height().await?; - Ok(current_height > tx_height + lock_blocks) - } - _ => Ok(false), - } - } - - /// Calculates the required stake amount for the operator. - /// - /// Formula: - /// stake_amount = fixed_min_stake_amount + (pegin_amount * stake_rate) - pub fn get_stake_amount(pegin_amount: u64) -> Amount { - Amount::from_sat(MIN_SATKE_AMOUNT + pegin_amount * STAKE_RATE / RATE_MULTIPLIER) - } - - /// Calculates the required challenge amount, which is based on the stake amount. - /// - /// Formula: - /// challenge_amount = fixed_min_challenge_amount + (pegin_amount * challenge_rate) - pub fn get_challenge_amount(pegin_amount: u64) -> Amount { - Amount::from_sat(MIN_CHALLENGE_AMOUNT + pegin_amount * CHALLENGE_RATE / RATE_MULTIPLIER) - } - - /// Selects suitable UTXOs from the operator’s available funds to construct inputs - /// for the pre-kickoff transaction. - /// - /// Notes: - /// - UTXOs must be sent to a dedicated P2WSH address, generated at node startup from operator-pubkey - /// - The same P2WSH address is also used for change output. - /// - Returns None if operator does not have enough btc - pub async fn select_operator_inputs( - client: &BitVM2Client, - stake_amount: Amount, - ) -> Result, Box> { - let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); - let fee_rate = get_fee_rate(client).await?; - match get_proper_utxo_set( - client, - PRE_KICKOFF_BASE_VBYTES, - node_address.clone(), - stake_amount, - fee_rate, - ) - .await? - { - Some((inputs, fee_amount, _)) => Ok(Some(CustomInputs { - inputs, - input_amount: stake_amount, - fee_amount, - change_address: node_address, - })), - _ => Ok(None), - } - } - - /// Loads partial scripts from a local cache file. - /// If cache file does not exist, generate partial scripts by vk an cache it - pub fn get_partial_scripts() -> Result, Box> { - let scripts_cache_path = SCRIPT_CACHE_FILE_NAME; - if Path::new(scripts_cache_path).exists() { - let file = File::open(scripts_cache_path)?; - let reader = BufReader::new(file); - let scripts_bytes: Vec = bincode::deserialize_from(reader).unwrap(); - Ok(scripts_bytes.into_iter().map(|x| script! {}.push_script(x)).collect()) - } else { - let partial_scripts = generate_partial_scripts(&get_vk()?); - if let Some(parent) = Path::new(scripts_cache_path).parent() { - fs::create_dir_all(parent).unwrap(); - }; - let file = File::create(scripts_cache_path).unwrap(); - let scripts_bytes: Vec = - partial_scripts.iter().map(|scr| scr.clone().compile()).collect(); - let writer = BufWriter::new(file); - bincode::serialize_into(writer, &scripts_bytes)?; - Ok(partial_scripts) - } - } - - pub async fn get_fee_rate(client: &BitVM2Client) -> Result> { - let res = client.esplora.get_fee_estimates().await?; - Ok(*res.get(&DEFAULT_CONFIRMATION_TARGET).ok_or(format!( - "fee for {} confirmation target not found", - DEFAULT_CONFIRMATION_TARGET - ))?) - } - - /// Broadcasts a raw transaction to the Bitcoin network using the mempool API. - /// - /// Requirements: - /// - The mempool API URL must be configured. - /// - The transaction should already be fully signed. - pub async fn broadcast_tx( - client: &BitVM2Client, - tx: &Transaction, - ) -> Result<(), Box> { - Ok(client.esplora.broadcast(tx).await?) - } - - /// Signs and broadcasts pre-kickoff transaction. - /// - /// All inputs of pre-kickoff transaction should be utxo belonging to node-address - pub async fn sign_and_broadcast_prekickoff_tx( - client: &BitVM2Client, - node_keypair: Keypair, - prekickoff_tx: Transaction, - ) -> Result<(), Box> { - let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); - let mut prekickoff_tx = prekickoff_tx; - for i in 0..prekickoff_tx.input.len() { - let prev_outpoint = &prekickoff_tx.input[i].previous_output; - let prev_tx = client - .esplora - .get_tx(&prev_outpoint.txid) - .await? - .ok_or(format!("previous tx {} not found", prev_outpoint.txid))?; - let prev_output = &prev_tx.output.get(prev_outpoint.vout as usize).ok_or(format!( - "previous tx {} does not have vout {}", - prev_outpoint.txid, prev_outpoint.vout - ))?; - if prev_output.script_pubkey != node_address.script_pubkey() { - return Err(format!( - "previous outpoint {}:{} not belong to this node", - prev_outpoint.txid, prev_outpoint.vout - ) - .into()); - }; - node_sign( - &mut prekickoff_tx, - i, - prev_output.value, - EcdsaSighashType::All, - &node_keypair, - )?; - } - broadcast_tx(client, &prekickoff_tx).await?; - Ok(()) - } - - /// Completes and broadcasts a challenge transaction. - /// - /// This involves: - /// - Selecting UTXOs with sufficient amount (may include change), - /// - Signing the transaction, - /// - Broadcasting it to the network. - /// - /// Notes: - /// - The challenge node must have pre-funded a P2WSH address during startup. - pub async fn complete_and_broadcast_challenge_tx( - client: &BitVM2Client, - node_keypair: Keypair, - challenge_tx: Transaction, - challenge_amount: Amount, - ) -> Result> { - let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); - let fee_rate = get_fee_rate(client).await?; - let mut challenge_tx = challenge_tx; - match get_proper_utxo_set( - client, - CHALLENGE_BASE_VBYTES, - node_address.clone(), - challenge_amount, - fee_rate, - ) - .await? - { - Some((inputs, _, change_amount)) => { - for input in &inputs { - challenge_tx.input.push(TxIn { - previous_output: input.outpoint, - script_sig: ScriptBuf::new(), - sequence: Sequence::MAX, - witness: Witness::default(), - }); - } - if change_amount > Amount::from_sat(DUST_AMOUNT) { - challenge_tx.output.push(TxOut { - script_pubkey: node_address.script_pubkey(), - value: challenge_amount, - }); - }; - for i in 0..inputs.len() { - node_sign( - &mut challenge_tx, - i + 1, - inputs[i].amount, - EcdsaSighashType::All, - &node_keypair, - )?; - } - broadcast_tx(client, &challenge_tx).await?; - Ok(challenge_tx.compute_txid()) - } - _ => Err(format!("insufficient btc, please fund {} first", node_address).into()), - } - } - - /// Returns: - /// - `Ok(None)` if given address does not have enough btc, - /// - `Ok(Some((utxos, fee_amount, change_amount)))` - pub async fn get_proper_utxo_set( - client: &BitVM2Client, - base_vbytes: u64, - address: Address, - target_amount: Amount, - fee_rate: f64, - ) -> Result, Amount, Amount)>, Box> { - fn estimate_tx_vbytes(base_vbytes: u64, extra_inputs: usize, extra_outputs: usize) -> u64 { - // p2wsh inputs/outputs - base_vbytes - + (extra_inputs as u64 * CHEKSIG_P2WSH_INPUT_VBYTES) - + (extra_outputs as u64 * P2WSH_OUTPUT_VBYTES) - } - fn to_input(utxos: Vec) -> Vec { - utxos - .into_iter() - .map(|utxo| Input { - outpoint: OutPoint { txid: utxo.txid, vout: utxo.vout }, - amount: utxo.value, - }) - .collect() - } - - let utxos = client.esplora.get_address_utxo(address).await?; - let mut sorted_utxos = utxos; - sorted_utxos.sort_by(|a, b| b.value.cmp(&a.value)); - - let mut selected = Vec::new(); - let mut total_value = Amount::ZERO; - - for utxo in sorted_utxos.into_iter().take(MAX_CUSTOM_INPUTS) { - selected.push(utxo.clone()); - total_value += utxo.value; - - let num_inputs = selected.len(); - let num_outputs = 1; // change - let tx_vbytes = estimate_tx_vbytes(base_vbytes, num_inputs, num_outputs); - let fee = Amount::from_sat((tx_vbytes as f64 * fee_rate).ceil() as u64); - - if total_value >= target_amount + fee { - let change = total_value - target_amount - fee; - return Ok(Some((to_input(selected), fee, change))); - } - } - - Ok(None) - } - - /// Returns the address to receive disprove reward, which is a P2WSH address - /// generated by the challenge node at startup. - pub fn disprove_reward_address() -> Result> { - Ok(node_p2wsh_address(get_network(), &get_node_pubkey()?)) - } - - pub fn node_p2wsh_script(pubkey: &PublicKey) -> ScriptBuf { - script! { - { *pubkey } - OP_CHECKSIG - } - .compile() - } - pub fn node_p2wsh_address(network: Network, pubkey: &PublicKey) -> Address { - Address::p2wsh(&node_p2wsh_script(pubkey), network) - } - pub fn node_sign( - tx: &mut Transaction, - input_index: usize, - input_value: Amount, - sighash_type: EcdsaSighashType, - node_keypair: &Keypair, - ) -> Result<(), Box> { - let node_pubkey = get_node_pubkey()?; - populate_p2wsh_witness( - tx, - input_index, - sighash_type, - &node_p2wsh_script(&node_pubkey), - input_value, - &vec![node_keypair], - ); - Ok(()) - } - - /// Determines whether the challenger should challenge a kickoff - /// - /// Conditions: - /// - If kickoff is invalid - /// - If challenger has enough fund - /// - Participation should be attempted as often as possible. - /// - /// A kickoff transaction is considered invalid if: - /// - It has already been broadcast on Layer 1, - /// - But the corresponding graph status on Layer 2 is not `Initialized`. - pub async fn should_challenge( - client: &BitVM2Client, - challenge_amount: Amount, - instance_id: Uuid, - graph_id: Uuid, - graph: &Bitvm2Graph, - ) -> Result> { - // check if kickoff is confirmed on L1 - let kickoff_txid = graph.kickoff.tx().compute_txid(); - if let None = client.esplora.get_tx(&kickoff_txid).await? { - return Ok(false); - } - - // check if withdraw is initialized on L2 - let withdraw_status = - client.chain_service.adaptor.get_withdraw_data(graph_id).await?.status; - if withdraw_status == WithdrawStatus::Initialized { - return Ok(false); - }; - - let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); - let utxos = client.esplora.get_address_utxo(node_address).await?; - let utxo_spent_fee = Amount::from_sat( - (get_fee_rate(client).await? * 2.0 * CHEKSIG_P2WSH_INPUT_VBYTES as f64).ceil() as u64, - ); - let total_effective_balance: Amount = - utxos.iter().map(|utxo| utxo.value - utxo_spent_fee).sum(); - Ok(total_effective_balance > challenge_amount) - } - - /// Validates whether the given kickoff transaction has been confirmed on Layer 1. - pub async fn tx_on_chain( - client: &BitVM2Client, - txid: &Txid, - ) -> Result> { - match client.esplora.get_tx(txid).await? { - Some(_) => Ok(true), - _ => Ok(false), - } - } - - /// Validates whether the given challenge transaction has been confirmed on Layer 1. - pub async fn validate_challenge( - client: &BitVM2Client, - kickoff_txid: &Txid, - challenge_txid: &Txid, - ) -> Result> { - let challenge_tx = match client.esplora.get_tx(challenge_txid).await? { - Some(tx) => tx, - _ => return Ok(false), - }; - let expected_challenge_input_0 = OutPoint { txid: *kickoff_txid, vout: 1 }; - Ok(challenge_tx.input[0].previous_output == expected_challenge_input_0) - } - - /// Validates whether the given disprove transaction has been confirmed on Layer 1. - pub async fn validate_disprove( - client: &BitVM2Client, - assert_final_txid: &Txid, - disprove_txid: &Txid, - ) -> Result> { - let disprove_tx = match client.esplora.get_tx(disprove_txid).await? { - Some(tx) => tx, - _ => return Ok(false), - }; - let expected_disprove_input_0 = OutPoint { txid: *assert_final_txid, vout: 1 }; - Ok(disprove_tx.input[0].previous_output == expected_disprove_input_0) - } - - /// Validates the provided assert-commit transactions. - /// - /// Steps: - /// - Extract the Groth16 proof from the witness fields of the provided transactions, - /// - Verify the validity of the proof. - /// - /// Returns: - /// - `Ok(None)` if the assert is valid, - /// - `Ok(Some((index, disprove_script)))` if invalid, providing the witness info for later disprove. - /// - pub async fn validate_assert( - client: &BitVM2Client, - assert_commit_txns: &[Txid; COMMIT_TX_NUM], - wots_pubkeys: WotsPublicKeys, - ) -> Result, Box> { - let mut txs = Vec::with_capacity(COMMIT_TX_NUM); - for txid in assert_commit_txns.iter() { - let tx = match client.esplora.get_tx(txid).await? { - Some(v) => v, - _ => return Ok(None), // nothing to disprove if assert-commit-txns not on chain - }; - txs.push(tx); - } - let assert_commit_txns: [Transaction; COMMIT_TX_NUM] = - txs.try_into().map_err(|_| "assert-commit-tx num mismatch")?; - let proof_sigs = extract_proof_sigs_from_assert_commit_txns(assert_commit_txns)?; - let disprove_scripts = generate_disprove_scripts(&get_partial_scripts()?, &wots_pubkeys); - let disprove_scripts: [Script; NUM_TAPS] = - disprove_scripts.try_into().map_err(|_| "disprove script num mismatch")?; - Ok(verify_proof(&get_vk()?, proof_sigs, &disprove_scripts, &wots_pubkeys)) - } - - pub fn client() -> Result> { - Err("TODO".into()) - } - - /// Retrieves the Groth16 proof, public inputs, and verifying key - /// for the given graph. - /// - /// These are fetched via the ProofNetwork SDK. - pub fn get_groth16_proof( - instance_id: Uuid, - graph_id: Uuid, - ) -> Result<(Groth16Proof, PublicInputs, VerifyingKey), Box> { - Err("TODO".into()) - } - pub fn get_vk() -> Result> { - Err("TODO".into()) - } -} - /// Filter the message and dispatch message to different handlers, like rpc handler, or other peers /// * database: inner_rpc: Write or Read. /// * peers: send @@ -745,7 +204,7 @@ pub async fn recv_and_dispatch( println!("Handle message: {:?}", message); let content: GOATMessageContent = message.to_typed()?; // TODO: validate message - let client = todo_funcs::client()?; + let client = client()?; match (content, actor) { // pegin // CreateInstance sent by bootnode @@ -775,7 +234,7 @@ pub async fn recv_and_dispatch( .await?; let collected_keys = get_committee_pubkeys(&client, receive_data.instance_id).await?; if collected_keys.len() == receive_data.committee_members_num - && todo_funcs::should_generate_graph(&client, &receive_data).await? + && should_generate_graph(&client, &receive_data).await? { let graph_id = Uuid::new_v4(); if try_start_new_graph(receive_data.instance_id, graph_id) { @@ -783,13 +242,11 @@ pub async fn recv_and_dispatch( let keypair = master_key.keypair_for_graph(graph_id); let (_, operator_wots_pubkeys) = master_key.wots_keypair_for_graph(graph_id); let committee_agg_pubkey = key_aggregation(&collected_keys); - let disprove_scripts = generate_disprove_scripts( - &todo_funcs::get_partial_scripts()?, - &operator_wots_pubkeys, - ); - let operator_inputs = todo_funcs::select_operator_inputs( + let disprove_scripts = + generate_disprove_scripts(&get_partial_scripts()?, &operator_wots_pubkeys); + let operator_inputs = select_operator_inputs( &client, - todo_funcs::get_stake_amount(receive_data.pegin_amount.to_sat()), + get_stake_amount(receive_data.pegin_amount.to_sat()), ) .await? .ok_or("operator doesn't have enough fund")?; @@ -798,12 +255,8 @@ pub async fn recv_and_dispatch( depositor_evm_address: receive_data.depositor_evm_address, pegin_amount: receive_data.pegin_amount, user_inputs: receive_data.user_inputs, - stake_amount: todo_funcs::get_stake_amount( - receive_data.pegin_amount.to_sat(), - ), - challenge_amount: todo_funcs::get_challenge_amount( - receive_data.pegin_amount.to_sat(), - ), + stake_amount: get_stake_amount(receive_data.pegin_amount.to_sat()), + challenge_amount: get_challenge_amount(receive_data.pegin_amount.to_sat()), committee_pubkeys: collected_keys, committee_agg_pubkey, operator_pubkey: keypair.public_key().into(), @@ -936,17 +389,13 @@ pub async fn recv_and_dispatch( let prekickoff_tx = graph.pre_kickoff.tx().clone(); let node_keypair = OperatorMasterKey::new(env::get_bitvm_key()?).master_keypair(); - todo_funcs::sign_and_broadcast_prekickoff_tx( - &client, - node_keypair, - prekickoff_tx, - ) - .await?; + sign_and_broadcast_prekickoff_tx(&client, node_keypair, prekickoff_tx).await?; let message_content = GOATMessageContent::GraphFinalize(GraphFinalize { instance_id: receive_data.instance_id, graph_id: receive_data.graph_id, graph, }); + // TODO: ipfs send_to_peer(swarm, GOATMessage::from_typed(Actor::All, &message_content)?)?; force_stop_current_graph(); } @@ -970,7 +419,7 @@ pub async fn recv_and_dispatch( let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; if graph.parameters.operator_pubkey == env::get_node_pubkey()? - && todo_funcs::is_withdraw_initialized_on_l2( + && is_withdraw_initialized_on_l2( &client, receive_data.instance_id, receive_data.graph_id, @@ -991,7 +440,7 @@ pub async fn recv_and_dispatch( &operator_wots_pubkeys, kickoff_commit_data, )?; - todo_funcs::broadcast_tx(&client, &kickoff_tx).await?; + broadcast_tx(&client, &kickoff_tx).await?; // malicious Operator may not broadcast kickoff to the p2p network // Relayer will monitor all graphs & broadcast KickoffSent } @@ -1000,7 +449,7 @@ pub async fn recv_and_dispatch( (GOATMessageContent::KickoffSent(receive_data), Actor::Challenger) => { let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::should_challenge( + if should_challenge( &client, Amount::from_sat(graph.challenge.min_crowdfunding_amount()), receive_data.instance_id, @@ -1011,7 +460,7 @@ pub async fn recv_and_dispatch( { let (challenge_tx, challenge_amount) = export_challenge_tx(&mut graph)?; let node_keypair = ChallengerMasterKey::new(env::get_bitvm_key()?).master_keypair(); - let challenge_txid = todo_funcs::complete_and_broadcast_challenge_tx( + let challenge_txid = complete_and_broadcast_challenge_tx( &client, node_keypair, challenge_tx, @@ -1038,14 +487,13 @@ pub async fn recv_and_dispatch( let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; if graph.parameters.operator_pubkey == env::get_node_pubkey()? - && todo_funcs::is_take1_timelock_expired(&client, graph.take1.tx().compute_txid()) - .await? + && is_take1_timelock_expired(&client, graph.take1.tx().compute_txid()).await? { let master_key = OperatorMasterKey::new(env::get_bitvm_key()?); let keypair = master_key.keypair_for_graph(receive_data.graph_id); let take1_tx = operator_sign_take1(keypair, &mut graph)?; let take1_txid = take1_tx.compute_txid(); - todo_funcs::broadcast_tx(&client, &take1_tx).await?; + broadcast_tx(&client, &take1_tx).await?; let message_content = GOATMessageContent::Take1Sent(Take1Sent { instance_id: receive_data.instance_id, graph_id: receive_data.graph_id, @@ -1068,7 +516,7 @@ pub async fn recv_and_dispatch( let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; if graph.parameters.operator_pubkey == env::get_node_pubkey()? - && todo_funcs::validate_challenge( + && validate_challenge( &client, &graph.kickoff.tx().compute_txid(), &receive_data.challenge_txid, @@ -1080,15 +528,15 @@ pub async fn recv_and_dispatch( let (operator_wots_seckeys, operator_wots_pubkeys) = master_key.wots_keypair_for_graph(receive_data.graph_id); let (proof, pubin, vk) = - todo_funcs::get_groth16_proof(receive_data.instance_id, receive_data.graph_id)?; + get_groth16_proof(receive_data.instance_id, receive_data.graph_id)?; let proof_sigs = sign_proof(&vk, proof, pubin, &operator_wots_seckeys); let (assert_init_tx, assert_commit_txns, assert_final_tx) = operator_sign_assert(keypair, &mut graph, &operator_wots_pubkeys, proof_sigs)?; - todo_funcs::broadcast_tx(&client, &assert_init_tx).await?; + broadcast_tx(&client, &assert_init_tx).await?; for tx in assert_commit_txns { - todo_funcs::broadcast_tx(&client, &tx).await?; + broadcast_tx(&client, &tx).await?; } - todo_funcs::broadcast_tx(&client, &assert_final_tx).await?; + broadcast_tx(&client, &assert_final_tx).await?; update_graph_status_or_ipfs_base( &client, receive_data.graph_id, @@ -1105,17 +553,14 @@ pub async fn recv_and_dispatch( let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; if graph.parameters.operator_pubkey == env::get_node_pubkey()? - && todo_funcs::is_take2_timelock_expired( - &client, - graph.assert_final.tx().compute_txid(), - ) - .await? + && is_take2_timelock_expired(&client, graph.assert_final.tx().compute_txid()) + .await? { let master_key = OperatorMasterKey::new(env::get_bitvm_key()?); let keypair = master_key.keypair_for_graph(receive_data.graph_id); let take2_tx = operator_sign_take2(keypair, &mut graph)?; let take2_txid = take2_tx.compute_txid(); - todo_funcs::broadcast_tx(&client, &take2_tx).await?; + broadcast_tx(&client, &take2_tx).await?; let message_content = GOATMessageContent::Take2Sent(Take2Sent { instance_id: receive_data.instance_id, graph_id: receive_data.graph_id, @@ -1136,7 +581,7 @@ pub async fn recv_and_dispatch( (GOATMessageContent::AssertSent(receive_data), Actor::Challenger) => { let mut graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if let Some(disprove_witness) = todo_funcs::validate_assert( + if let Some(disprove_witness) = validate_assert( &client, &receive_data.assert_commit_txids, graph.parameters.operator_wots_pubkeys.clone(), @@ -1144,7 +589,7 @@ pub async fn recv_and_dispatch( .await? { let disprove_scripts = generate_disprove_scripts( - &todo_funcs::get_partial_scripts()?, + &get_partial_scripts()?, &graph.parameters.operator_wots_pubkeys, ); let disprove_scripts_bytes = @@ -1155,10 +600,10 @@ pub async fn recv_and_dispatch( disprove_witness, disprove_scripts_bytes, &assert_wots_pubkeys, - todo_funcs::disprove_reward_address()?, + disprove_reward_address()?, )?; let disprove_txid = disprove_tx.compute_txid(); - todo_funcs::broadcast_tx(&client, &disprove_tx).await?; + broadcast_tx(&client, &disprove_tx).await?; let message_content = GOATMessageContent::DisproveSent(DisproveSent { instance_id: receive_data.instance_id, graph_id: receive_data.graph_id, @@ -1179,7 +624,7 @@ pub async fn recv_and_dispatch( (GOATMessageContent::Take1Sent(receive_data), Actor::Relayer) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; let take1_txid = graph.take1.tx().compute_txid(); - if todo_funcs::tx_on_chain(&client, &take1_txid).await? { + if tx_on_chain(&client, &take1_txid).await? { finish_withdraw_happy_path(&client, &receive_data.graph_id, &graph.take1).await?; update_graph_status_or_ipfs_base( &client, @@ -1193,7 +638,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::Take2Sent(receive_data), Actor::Relayer) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::tx_on_chain(&client, &graph.take2.tx().compute_txid()).await? { + if tx_on_chain(&client, &graph.take2.tx().compute_txid()).await? { finish_withdraw_unhappy_path(&client, &receive_data.graph_id, &graph.take2).await?; update_graph_status_or_ipfs_base( &client, @@ -1207,7 +652,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::DisproveSent(receive_data), Actor::Relayer) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::validate_disprove( + if validate_disprove( &client, &graph.assert_final.tx().compute_txid(), &receive_data.disprove_txid, @@ -1229,7 +674,7 @@ pub async fn recv_and_dispatch( // Other participants update graph status (GOATMessageContent::KickoffSent(receive_data), _) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::tx_on_chain(&client, &graph.kickoff.tx().compute_txid()).await? { + if tx_on_chain(&client, &graph.kickoff.tx().compute_txid()).await? { update_graph_status_or_ipfs_base( &client, receive_data.graph_id, @@ -1241,7 +686,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::ChallengeSent(receive_data), _) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::validate_challenge( + if validate_challenge( &client, &graph.kickoff.tx().compute_txid(), &receive_data.challenge_txid, @@ -1259,7 +704,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::Take1Sent(receive_data), _) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::tx_on_chain(&client, &graph.take1.tx().compute_txid()).await? { + if tx_on_chain(&client, &graph.take1.tx().compute_txid()).await? { update_graph_status_or_ipfs_base( &client, receive_data.graph_id, @@ -1272,7 +717,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::Take2Sent(receive_data), _) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::tx_on_chain(&client, &graph.take2.tx().compute_txid()).await? { + if tx_on_chain(&client, &graph.take2.tx().compute_txid()).await? { update_graph_status_or_ipfs_base( &client, receive_data.graph_id, @@ -1285,7 +730,7 @@ pub async fn recv_and_dispatch( } (GOATMessageContent::DisproveSent(receive_data), _) => { let graph = get_graph(&client, receive_data.instance_id, receive_data.graph_id).await?; - if todo_funcs::validate_disprove( + if validate_disprove( &client, &graph.assert_final.tx().compute_txid(), &receive_data.disprove_txid, @@ -1338,219 +783,3 @@ where let txt = resp.text().await?; Ok(serde_json::from_str(txt.as_str())?) } - -/// l2 support -pub async fn finish_withdraw_happy_path( - client: &BitVM2Client, - graph_id: &Uuid, - take1: &Take1Transaction, -) -> Result<(), Box> { - Ok(client.finish_withdraw_happy_path(graph_id, take1.tx()).await?) -} -pub async fn finish_withdraw_unhappy_path( - client: &BitVM2Client, - graph_id: &Uuid, - take2: &Take2Transaction, -) -> Result<(), Box> { - Ok(client.finish_withdraw_unhappy_path(graph_id, take2.tx()).await?) -} - -pub async fn finish_withdraw_disproved( - client: &BitVM2Client, - graph_id: &Uuid, - disprove: &DisproveTransaction, -) -> Result<(), Box> { - Ok(client.finish_withdraw_disproved(graph_id, disprove.tx()).await?) -} - -/// db support -pub async fn store_committee_pub_nonces( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, - committee_pubkey: PublicKey, - pub_nonces: [PubNonce; COMMITTEE_PRE_SIGN_NUM], -) -> Result<(), Box> { - let mut storage_process = client.local_db.acquire().await?; - let nonces_vec: Vec = pub_nonces.iter().map(|v| v.to_string()).collect(); - let nonces_arr: [String; COMMITTEE_PRE_SIGN_NUM] = - nonces_vec.try_into().map_err(|v: Vec| { - format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) - })?; - Ok(storage_process - .store_nonces(instance_id, graph_id, &[nonces_arr], committee_pubkey.to_string(), &[]) - .await?) -} -pub async fn get_committee_pub_nonces( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, -) -> Result, Box> { - let mut storage_process = client.local_db.acquire().await?; - match storage_process.get_nonces(instance_id, graph_id).await? { - None => { - Err(format!("instance id:{}, graph id:{} not found ", instance_id, graph_id).into()) - } - Some(nonce_collect) => { - let mut res: Vec<[PubNonce; COMMITTEE_PRE_SIGN_NUM]> = vec![]; - for nonces_item in nonce_collect.nonces { - let nonce_vec: Vec = nonces_item - .iter() - .map(|v| PubNonce::from_str(v).expect("fail to decode pub nonce")) - .collect(); - res.push(nonce_vec.try_into().map_err(|v: Vec| { - format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) - })?) - } - Ok(res) - } - } -} - -pub async fn store_committee_pubkeys( - client: &BitVM2Client, - instance_id: Uuid, - pubkey: PublicKey, -) -> Result<(), Box> { - let mut storage_process = client.local_db.acquire().await?; - Ok(storage_process.store_pubkeys(instance_id, &vec![pubkey.to_string()]).await?) -} -pub async fn get_committee_pubkeys( - client: &BitVM2Client, - instance_id: Uuid, -) -> Result, Box> { - let mut storage_process = client.local_db.acquire().await?; - match storage_process.get_pubkeys(instance_id).await? { - None => Ok(vec![]), - Some(meta_data) => Ok(meta_data - .pubkeys - .iter() - .map(|v| PublicKey::from_str(v).expect("fail to decode to public key")) - .collect()), - } -} - -pub async fn store_committee_partial_sigs( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, - committee_pubkey: PublicKey, - partial_sigs: [PartialSignature; COMMITTEE_PRE_SIGN_NUM], -) -> Result<(), Box> { - let mut storage_process = client.local_db.acquire().await?; - let signs_vec: Vec = partial_sigs.iter().map(|v| hex::encode(v.serialize())).collect(); - let signs_arr: [String; COMMITTEE_PRE_SIGN_NUM] = - signs_vec.try_into().map_err(|v: Vec| { - format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) - })?; - - Ok(storage_process - .store_nonces(instance_id, graph_id, &[], committee_pubkey.to_string(), &vec![signs_arr]) - .await?) -} - -pub async fn get_committee_partial_sigs( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, -) -> Result, Box> { - let mut storage_process = client.local_db.acquire().await?; - match storage_process.get_nonces(instance_id, graph_id).await? { - None => { - Err(format!("instance id:{}, graph id:{} not found ", instance_id, graph_id).into()) - } - Some(nonce_collect) => { - let mut res: Vec<[PartialSignature; COMMITTEE_PRE_SIGN_NUM]> = vec![]; - for signs_item in nonce_collect.partial_sigs { - let signs_vec: Vec = signs_item - .iter() - .map(|v| PartialSignature::from_str(v).expect("fail to decode pub nonce")) - .collect(); - res.push(signs_vec.try_into().map_err(|v: Vec| { - format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) - })?) - } - Ok(res) - } - } -} - -pub async fn update_graph_status_or_ipfs_base( - client: &BitVM2Client, - graph_id: Uuid, - graph_state: Option, - ipfs_base_url: Option, -) -> Result<(), Box> { - let mut storage_process = client.local_db.acquire().await?; - Ok(storage_process - .update_graph_status_or_ipfs_base(graph_id, graph_state, ipfs_base_url) - .await?) -} -pub async fn store_graph( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, - graph: &Bitvm2Graph, - status: Option, -) -> Result<(), Box> { - let mut storage_process = client.local_db.acquire().await?; - let assert_commit_txids: Vec = - graph.assert_commit.commit_txns.iter().map(|v| v.tx().compute_txid().to_string()).collect(); - storage_process - .update_graph(Graph { - graph_id, - instance_id, - graph_ipfs_base_url: "".to_string(), //TODO - pegin_txid: graph.pegin.tx().compute_txid().to_string(), - amount: graph.parameters.pegin_amount.to_sat() as i64, - status: status.unwrap_or_else(|| GraphStatus::OperatorPresigned.to_string()), - kickoff_txid: Some(graph.kickoff.tx().compute_txid().to_string()), - challenge_txid: Some(graph.challenge.tx().compute_txid().to_string()), - take1_txid: Some(graph.take1.tx().compute_txid().to_string()), - assert_init_txid: Some(graph.assert_init.tx().compute_txid().to_string()), - assert_commit_txids: Some(format!("{:?}", assert_commit_txids)), - assert_final_txid: Some(graph.assert_final.tx().compute_txid().to_string()), - take2_txid_txid: Some(graph.take2.tx().compute_txid().to_string()), - disprove_txid: Some(graph.disprove.tx().compute_txid().to_string()), - operator: graph.parameters.operator_pubkey.to_string(), - raw_data: Some(serde_json::to_string(&graph).expect("to json string")), - created_at: current_time_secs(), - updated_at: current_time_secs(), - }) - .await?; - - Ok(()) -} - -#[allow(dead_code)] -pub async fn update_graph( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, - graph: &Bitvm2Graph, - status: Option, -) -> Result<(), Box> { - store_graph(client, instance_id, graph_id, graph, status).await -} - -pub async fn get_graph( - client: &BitVM2Client, - instance_id: Uuid, - graph_id: Uuid, -) -> Result> { - let mut storage_process = client.local_db.acquire().await?; - let graph = storage_process.get_graph(&graph_id).await?; - if graph.instance_id.ne(&instance_id) { - return Err(format!( - "grap with graph_id:{} has instance_id:{} not match expec instance:{}", - graph_id, graph.instance_id, instance_id - ) - .into()); - } - - if graph.raw_data.is_none() { - return Err(format!("grap with graph_id:{} raw data is none", graph_id).into()); - } - let res: Bitvm2Graph = serde_json::from_str(graph.raw_data.unwrap().as_str())?; - Ok(res) -} diff --git a/node/src/bitcoin/checker.rs b/node/src/bitcoin/checker.rs index 7e68ce99..48067174 100644 --- a/node/src/bitcoin/checker.rs +++ b/node/src/bitcoin/checker.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use bitcoin::hashes::Hash; use bitcoin::{Amount, Block, Network, Transaction, Txid}; use esplora_client::AsyncClient; @@ -139,7 +139,7 @@ pub async fn check_pegin_tx( mod tests { use super::*; use esplora_client::Builder; - use futures::{stream, StreamExt}; + use futures::{StreamExt, stream}; #[tokio::test] async fn test_check_pegin_tx() { // tx: https://mempool.space/testnet/tx/e413208c6644d51f4f3adf3a5aad425da817ac825e56352e7164de1e2a4d9394 diff --git a/node/src/env.rs b/node/src/env.rs index a9893039..8777e3b5 100644 --- a/node/src/env.rs +++ b/node/src/env.rs @@ -1,6 +1,7 @@ +#![allow(dead_code)] use alloy::eips::BlockNumberOrTag; use alloy::primitives::Address as EvmAddress; -use bitcoin::{key::Keypair, Network, PublicKey}; +use bitcoin::{Network, PublicKey, key::Keypair}; use bitvm2_lib::keys::NodeMasterKey; use client::chain::goat_adaptor::GoatInitConfig; use reqwest::Url; diff --git a/node/src/main.rs b/node/src/main.rs index d4f433ee..00e53d78 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -1,8 +1,8 @@ #![feature(trivial_bounds)] use base64::Engine; -use clap::{command, Parser, Subcommand}; -use libp2p::futures::StreamExt; +use clap::{Parser, Subcommand, command}; use libp2p::PeerId; +use libp2p::futures::StreamExt; use libp2p::{gossipsub, kad, mdns, multiaddr::Protocol, noise, swarm::SwarmEvent, tcp, yamux}; use libp2p_metrics::Registry; use std::collections::HashMap; @@ -22,6 +22,7 @@ mod metrics_service; mod middleware; mod relayer_action; mod rpc_service; +mod utils; use crate::action::GOATMessage; use crate::middleware::behaviour::AllBehavioursEvent; diff --git a/node/src/metrics_service.rs b/node/src/metrics_service.rs index a01e3d95..97970cbc 100644 --- a/node/src/metrics_service.rs +++ b/node/src/metrics_service.rs @@ -6,15 +6,15 @@ use std::{ use crate::rpc_service::AppState; use axum::middleware::Next; use axum::{ - extract::Request, extract::State, http::StatusCode, response::IntoResponse, routing::get, - Router, + Router, extract::Request, extract::State, http::StatusCode, response::IntoResponse, + routing::get, }; use http::HeaderMap; use libp2p_metrics::Registry; use prometheus_client::encoding::text::encode; use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::family::Family; -use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; +use prometheus_client::metrics::histogram::{Histogram, exponential_buckets}; use tokio::net::TcpListener; use tokio::time::Instant; diff --git a/node/src/relayer_action.rs b/node/src/relayer_action.rs index 96d94d1d..53b8ac77 100644 --- a/node/src/relayer_action.rs +++ b/node/src/relayer_action.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use bitvm2_lib::actors::Actor; use client::client::BitVM2Client; use goat::{ @@ -10,11 +11,12 @@ use uuid::Uuid; use crate::{ action::{ - send_to_peer, todo_funcs::tx_on_chain, AssertSent, GOATMessage, GOATMessageContent, - KickoffReady, KickoffSent, Take1Ready, Take2Ready, + AssertSent, GOATMessage, GOATMessageContent, KickoffReady, KickoffSent, Take1Ready, + Take2Ready, send_to_peer, }, env::get_network, middleware::AllBehaviours, + utils::tx_on_chain, }; mod todo_funcs { diff --git a/node/src/rpc_service/handler/bitvm2_handler.rs b/node/src/rpc_service/handler/bitvm2_handler.rs index 680f3c00..20d869f5 100644 --- a/node/src/rpc_service/handler/bitvm2_handler.rs +++ b/node/src/rpc_service/handler/bitvm2_handler.rs @@ -1,8 +1,8 @@ use crate::rpc_service::bitvm2::*; use crate::rpc_service::node::ALIVE_TIME_JUDGE_THRESHOLD; -use crate::rpc_service::{current_time_secs, AppState}; -use axum::extract::{Path, Query, State}; +use crate::rpc_service::{AppState, current_time_secs}; use axum::Json; +use axum::extract::{Path, Query, State}; use bitcoin::Txid; use esplora_client::AsyncClient; use http::StatusCode; diff --git a/node/src/rpc_service/handler/node_handler.rs b/node/src/rpc_service/handler/node_handler.rs index 2ab1d40e..5d536b70 100644 --- a/node/src/rpc_service/handler/node_handler.rs +++ b/node/src/rpc_service/handler/node_handler.rs @@ -1,14 +1,14 @@ use crate::rpc_service::node::{ - NodeDesc, NodeListResponse, NodeOverViewResponse, NodeQueryParams, UpdateOrInsertNodeRequest, - ALIVE_TIME_JUDGE_THRESHOLD, + ALIVE_TIME_JUDGE_THRESHOLD, NodeDesc, NodeListResponse, NodeOverViewResponse, NodeQueryParams, + UpdateOrInsertNodeRequest, }; -use crate::rpc_service::{current_time_secs, AppState}; -use axum::extract::{Path, Query, State}; +use crate::rpc_service::{AppState, current_time_secs}; use axum::Json; +use axum::extract::{Path, Query, State}; use http::StatusCode; use std::sync::Arc; use std::time::UNIX_EPOCH; -use store::{Node, NODE_STATUS_OFFLINE, NODE_STATUS_ONLINE}; +use store::{NODE_STATUS_OFFLINE, NODE_STATUS_ONLINE, Node}; #[axum::debug_handler] pub async fn create_node( diff --git a/node/src/rpc_service/mod.rs b/node/src/rpc_service/mod.rs index 1982577e..fd5560a8 100644 --- a/node/src/rpc_service/mod.rs +++ b/node/src/rpc_service/mod.rs @@ -5,7 +5,7 @@ mod handler; mod node; use crate::env::get_bitvm2_client_config; -use crate::metrics_service::{metrics_handler, metrics_middleware, MetricsState}; +use crate::metrics_service::{MetricsState, metrics_handler, metrics_middleware}; use crate::rpc_service::handler::{bitvm2_handler::*, node_handler::*}; use axum::body::Body; use axum::extract::Request; @@ -13,9 +13,8 @@ use axum::middleware::Next; use axum::response::Response; use axum::routing::put; use axum::{ - middleware, + Router, middleware, routing::{get, post}, - Router, }; use bitcoin::Network; use bitvm2_lib::actors::Actor; diff --git a/node/src/utils.rs b/node/src/utils.rs new file mode 100644 index 00000000..b5c07137 --- /dev/null +++ b/node/src/utils.rs @@ -0,0 +1,813 @@ +use crate::action::CreateGraphPrepare; +use crate::env::*; +use crate::rpc_service::current_time_secs; +use ark_serialize::CanonicalDeserialize; +use bitcoin::key::Keypair; +use bitcoin::{ + Address, Amount, EcdsaSighashType, Network, OutPoint, PublicKey, ScriptBuf, Sequence, + Transaction, TxIn, TxOut, Txid, Witness, +}; +use bitcoin_script::{Script, script}; +use bitvm::chunk::api::NUM_TAPS; +use bitvm2_lib::committee::COMMITTEE_PRE_SIGN_NUM; +use bitvm2_lib::operator::{generate_disprove_scripts, generate_partial_scripts}; +use bitvm2_lib::types::{ + Bitvm2Graph, CustomInputs, Groth16Proof, PublicInputs, VerifyingKey, WotsPublicKeys, +}; +use bitvm2_lib::verifier::{extract_proof_sigs_from_assert_commit_txns, verify_proof}; +use client::chain::chain_adaptor::WithdrawStatus; +use client::client::BitVM2Client; +use esplora_client::Utxo; +use goat::constants::{CONNECTOR_3_TIMELOCK, CONNECTOR_4_TIMELOCK}; +use goat::transactions::assert::utils::COMMIT_TX_NUM; +use goat::transactions::base::Input; +use goat::transactions::disprove::DisproveTransaction; +use goat::transactions::pre_signed::PreSignedTransaction; +use goat::transactions::signing::populate_p2wsh_witness; +use goat::transactions::take_1::Take1Transaction; +use goat::transactions::take_2::Take2Transaction; +use goat::utils::num_blocks_per_network; +use musig2::{PartialSignature, PubNonce}; +use statics::*; +use std::fs::{self, File}; +use std::io::{BufReader, BufWriter}; +use std::path::Path; +use std::str::FromStr; +use store::{Graph, GraphStatus}; +use uuid::Uuid; + +pub mod statics { + use once_cell::sync::Lazy; + use std::sync::Mutex; + use uuid::Uuid; + + // operator node can only process one graph at a time + pub static OPERATOR_CURRENT_GRAPH: Lazy>> = + Lazy::new(|| Mutex::new(None)); + pub fn try_start_new_graph(instance_id: Uuid, graph_id: Uuid) -> bool { + let mut current = OPERATOR_CURRENT_GRAPH.lock().unwrap(); + if current.is_none() { + *current = Some((instance_id, graph_id)); + true + } else { + false + } + } + #[allow(dead_code)] + pub fn finish_current_graph_processing(instance_id: Uuid, graph_id: Uuid) { + let mut current = OPERATOR_CURRENT_GRAPH.lock().unwrap(); + if *current == Some((instance_id, graph_id)) { + *current = None; + } + } + pub fn is_processing_graph() -> bool { + OPERATOR_CURRENT_GRAPH.lock().unwrap().is_some() + } + pub fn current_processing_graph() -> Option<(Uuid, Uuid)> { + *OPERATOR_CURRENT_GRAPH.lock().unwrap() + } + pub fn force_stop_current_graph() { + *OPERATOR_CURRENT_GRAPH.lock().unwrap() = None; + } +} + +/// Determines whether the operator should participate in generating a new graph. +/// +/// Conditions: +/// - Participation should be attempted as often as possible. +/// - Only one graph can be generated at a time; generation must be sequential, not parallel. +/// - If the remaining funds are less than the required stake-amount, operator should not participate. +pub async fn should_generate_graph( + client: &BitVM2Client, + create_graph_prepare_data: &CreateGraphPrepare, +) -> Result> { + if is_processing_graph() { + return Ok(false); + }; + let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); + let utxos = client.esplora.get_address_utxo(node_address).await?; + let utxo_spent_fee = Amount::from_sat( + (get_fee_rate(client).await? * 2.0 * CHEKSIG_P2WSH_INPUT_VBYTES as f64).ceil() as u64, + ); + let total_effective_balance: Amount = + utxos.iter().map(|utxo| utxo.value - utxo_spent_fee).sum(); + Ok(total_effective_balance > get_stake_amount(create_graph_prepare_data.pegin_amount.to_sat())) +} + +/// Checks whether the status of the graph (identified by instance ID and graph ID) +/// on the Layer 2 contract is currently `Initialized`. +pub async fn is_withdraw_initialized_on_l2( + client: &BitVM2Client, + _instance_id: Uuid, + graph_id: Uuid, +) -> Result> { + let withdraw_status = client.chain_service.adaptor.get_withdraw_data(graph_id).await?.status; + Ok(withdraw_status == WithdrawStatus::Initialized) +} + +/// Checks whether the timelock for the specified kickoff transaction has expired, +/// indicating that the `take1` transaction can now be sent. +/// +/// The timelock duration is a fixed constant (goat::constants::CONNECTOR_3_TIMELOCK) +pub async fn is_take1_timelock_expired( + client: &BitVM2Client, + kickoff_txid: Txid, +) -> Result> { + let lock_blocks = num_blocks_per_network(get_network(), CONNECTOR_3_TIMELOCK); + let tx_status = client.esplora.get_tx_status(&kickoff_txid).await?; + match tx_status.block_height { + Some(tx_height) => { + let current_height = client.esplora.get_height().await?; + Ok(current_height > tx_height + lock_blocks) + } + _ => Ok(false), + } +} + +/// Checks whether the timelock for the specified assert-final transaction has expired, +/// allowing the `take2` transaction to proceed. +/// +/// The timelock duration is a fixed constant (goat::constants::CONNECTOR_4_TIMELOCK) +pub async fn is_take2_timelock_expired( + client: &BitVM2Client, + assert_final_txid: Txid, +) -> Result> { + let lock_blocks = num_blocks_per_network(get_network(), CONNECTOR_4_TIMELOCK); + let tx_status = client.esplora.get_tx_status(&assert_final_txid).await?; + match tx_status.block_height { + Some(tx_height) => { + let current_height = client.esplora.get_height().await?; + Ok(current_height > tx_height + lock_blocks) + } + _ => Ok(false), + } +} + +/// Calculates the required stake amount for the operator. +/// +/// Formula: +/// stake_amount = fixed_min_stake_amount + (pegin_amount * stake_rate) +pub fn get_stake_amount(pegin_amount: u64) -> Amount { + Amount::from_sat(MIN_SATKE_AMOUNT + pegin_amount * STAKE_RATE / RATE_MULTIPLIER) +} + +/// Calculates the required challenge amount, which is based on the stake amount. +/// +/// Formula: +/// challenge_amount = fixed_min_challenge_amount + (pegin_amount * challenge_rate) +pub fn get_challenge_amount(pegin_amount: u64) -> Amount { + Amount::from_sat(MIN_CHALLENGE_AMOUNT + pegin_amount * CHALLENGE_RATE / RATE_MULTIPLIER) +} + +/// Selects suitable UTXOs from the operator’s available funds to construct inputs +/// for the pre-kickoff transaction. +/// +/// Notes: +/// - UTXOs must be sent to a dedicated P2WSH address, generated at node startup from operator-pubkey +/// - The same P2WSH address is also used for change output. +/// - Returns None if operator does not have enough btc +pub async fn select_operator_inputs( + client: &BitVM2Client, + stake_amount: Amount, +) -> Result, Box> { + let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); + let fee_rate = get_fee_rate(client).await?; + match get_proper_utxo_set( + client, + PRE_KICKOFF_BASE_VBYTES, + node_address.clone(), + stake_amount, + fee_rate, + ) + .await? + { + Some((inputs, fee_amount, _)) => Ok(Some(CustomInputs { + inputs, + input_amount: stake_amount, + fee_amount, + change_address: node_address, + })), + _ => Ok(None), + } +} + +/// Loads partial scripts from a local cache file. +/// If cache file does not exist, generate partial scripts by vk an cache it +pub fn get_partial_scripts() -> Result, Box> { + let scripts_cache_path = SCRIPT_CACHE_FILE_NAME; + if Path::new(scripts_cache_path).exists() { + let file = File::open(scripts_cache_path)?; + let reader = BufReader::new(file); + let scripts_bytes: Vec = bincode::deserialize_from(reader).unwrap(); + Ok(scripts_bytes.into_iter().map(|x| script! {}.push_script(x)).collect()) + } else { + let partial_scripts = generate_partial_scripts(&get_vk()?); + if let Some(parent) = Path::new(scripts_cache_path).parent() { + fs::create_dir_all(parent).unwrap(); + }; + let file = File::create(scripts_cache_path).unwrap(); + let scripts_bytes: Vec = + partial_scripts.iter().map(|scr| scr.clone().compile()).collect(); + let writer = BufWriter::new(file); + bincode::serialize_into(writer, &scripts_bytes)?; + Ok(partial_scripts) + } +} + +pub async fn get_fee_rate(client: &BitVM2Client) -> Result> { + let res = client.esplora.get_fee_estimates().await?; + Ok(*res + .get(&DEFAULT_CONFIRMATION_TARGET) + .ok_or(format!("fee for {} confirmation target not found", DEFAULT_CONFIRMATION_TARGET))?) +} + +/// Broadcasts a raw transaction to the Bitcoin network using the mempool API. +/// +/// Requirements: +/// - The mempool API URL must be configured. +/// - The transaction should already be fully signed. +pub async fn broadcast_tx( + client: &BitVM2Client, + tx: &Transaction, +) -> Result<(), Box> { + Ok(client.esplora.broadcast(tx).await?) +} + +/// Signs and broadcasts pre-kickoff transaction. +/// +/// All inputs of pre-kickoff transaction should be utxo belonging to node-address +pub async fn sign_and_broadcast_prekickoff_tx( + client: &BitVM2Client, + node_keypair: Keypair, + prekickoff_tx: Transaction, +) -> Result<(), Box> { + let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); + let mut prekickoff_tx = prekickoff_tx; + for i in 0..prekickoff_tx.input.len() { + let prev_outpoint = &prekickoff_tx.input[i].previous_output; + let prev_tx = client + .esplora + .get_tx(&prev_outpoint.txid) + .await? + .ok_or(format!("previous tx {} not found", prev_outpoint.txid))?; + let prev_output = &prev_tx.output.get(prev_outpoint.vout as usize).ok_or(format!( + "previous tx {} does not have vout {}", + prev_outpoint.txid, prev_outpoint.vout + ))?; + if prev_output.script_pubkey != node_address.script_pubkey() { + return Err(format!( + "previous outpoint {}:{} not belong to this node", + prev_outpoint.txid, prev_outpoint.vout + ) + .into()); + }; + node_sign(&mut prekickoff_tx, i, prev_output.value, EcdsaSighashType::All, &node_keypair)?; + } + broadcast_tx(client, &prekickoff_tx).await?; + Ok(()) +} + +/// Completes and broadcasts a challenge transaction. +/// +/// This involves: +/// - Selecting UTXOs with sufficient amount (may include change), +/// - Signing the transaction, +/// - Broadcasting it to the network. +/// +/// Notes: +/// - The challenge node must have pre-funded a P2WSH address during startup. +pub async fn complete_and_broadcast_challenge_tx( + client: &BitVM2Client, + node_keypair: Keypair, + challenge_tx: Transaction, + challenge_amount: Amount, +) -> Result> { + let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); + let fee_rate = get_fee_rate(client).await?; + let mut challenge_tx = challenge_tx; + match get_proper_utxo_set( + client, + CHALLENGE_BASE_VBYTES, + node_address.clone(), + challenge_amount, + fee_rate, + ) + .await? + { + Some((inputs, _, change_amount)) => { + for input in &inputs { + challenge_tx.input.push(TxIn { + previous_output: input.outpoint, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::default(), + }); + } + if change_amount > Amount::from_sat(DUST_AMOUNT) { + challenge_tx.output.push(TxOut { + script_pubkey: node_address.script_pubkey(), + value: challenge_amount, + }); + }; + for i in 0..inputs.len() { + node_sign( + &mut challenge_tx, + i + 1, + inputs[i].amount, + EcdsaSighashType::All, + &node_keypair, + )?; + } + broadcast_tx(client, &challenge_tx).await?; + Ok(challenge_tx.compute_txid()) + } + _ => Err(format!("insufficient btc, please fund {} first", node_address).into()), + } +} + +/// Returns: +/// - `Ok(None)` if given address does not have enough btc, +/// - `Ok(Some((utxos, fee_amount, change_amount)))` +pub async fn get_proper_utxo_set( + client: &BitVM2Client, + base_vbytes: u64, + address: Address, + target_amount: Amount, + fee_rate: f64, +) -> Result, Amount, Amount)>, Box> { + fn estimate_tx_vbytes(base_vbytes: u64, extra_inputs: usize, extra_outputs: usize) -> u64 { + // p2wsh inputs/outputs + base_vbytes + + (extra_inputs as u64 * CHEKSIG_P2WSH_INPUT_VBYTES) + + (extra_outputs as u64 * P2WSH_OUTPUT_VBYTES) + } + fn to_input(utxos: Vec) -> Vec { + utxos + .into_iter() + .map(|utxo| Input { + outpoint: OutPoint { txid: utxo.txid, vout: utxo.vout }, + amount: utxo.value, + }) + .collect() + } + + let utxos = client.esplora.get_address_utxo(address).await?; + let mut sorted_utxos = utxos; + sorted_utxos.sort_by(|a, b| b.value.cmp(&a.value)); + + let mut selected = Vec::new(); + let mut total_value = Amount::ZERO; + + for utxo in sorted_utxos.into_iter().take(MAX_CUSTOM_INPUTS) { + selected.push(utxo.clone()); + total_value += utxo.value; + + let num_inputs = selected.len(); + let num_outputs = 1; // change + let tx_vbytes = estimate_tx_vbytes(base_vbytes, num_inputs, num_outputs); + let fee = Amount::from_sat((tx_vbytes as f64 * fee_rate).ceil() as u64); + + if total_value >= target_amount + fee { + let change = total_value - target_amount - fee; + return Ok(Some((to_input(selected), fee, change))); + } + } + + Ok(None) +} + +/// Returns the address to receive disprove reward, which is a P2WSH address +/// generated by the challenge node at startup. +pub fn disprove_reward_address() -> Result> { + Ok(node_p2wsh_address(get_network(), &get_node_pubkey()?)) +} + +pub fn node_p2wsh_script(pubkey: &PublicKey) -> ScriptBuf { + script! { + { *pubkey } + OP_CHECKSIG + } + .compile() +} +pub fn node_p2wsh_address(network: Network, pubkey: &PublicKey) -> Address { + Address::p2wsh(&node_p2wsh_script(pubkey), network) +} +pub fn node_sign( + tx: &mut Transaction, + input_index: usize, + input_value: Amount, + sighash_type: EcdsaSighashType, + node_keypair: &Keypair, +) -> Result<(), Box> { + let node_pubkey = get_node_pubkey()?; + populate_p2wsh_witness( + tx, + input_index, + sighash_type, + &node_p2wsh_script(&node_pubkey), + input_value, + &vec![node_keypair], + ); + Ok(()) +} + +/// Determines whether the challenger should challenge a kickoff +/// +/// Conditions: +/// - If kickoff is invalid +/// - If challenger has enough fund +/// - Participation should be attempted as often as possible. +/// +/// A kickoff transaction is considered invalid if: +/// - It has already been broadcast on Layer 1, +/// - But the corresponding graph status on Layer 2 is not `Initialized`. +pub async fn should_challenge( + client: &BitVM2Client, + challenge_amount: Amount, + _instance_id: Uuid, + graph_id: Uuid, + graph: &Bitvm2Graph, +) -> Result> { + // check if kickoff is confirmed on L1 + let kickoff_txid = graph.kickoff.tx().compute_txid(); + if let None = client.esplora.get_tx(&kickoff_txid).await? { + return Ok(false); + } + + // check if withdraw is initialized on L2 + let withdraw_status = client.chain_service.adaptor.get_withdraw_data(graph_id).await?.status; + if withdraw_status == WithdrawStatus::Initialized { + return Ok(false); + }; + + let node_address = node_p2wsh_address(get_network(), &get_node_pubkey()?); + let utxos = client.esplora.get_address_utxo(node_address).await?; + let utxo_spent_fee = Amount::from_sat( + (get_fee_rate(client).await? * 2.0 * CHEKSIG_P2WSH_INPUT_VBYTES as f64).ceil() as u64, + ); + let total_effective_balance: Amount = + utxos.iter().map(|utxo| utxo.value - utxo_spent_fee).sum(); + Ok(total_effective_balance > challenge_amount) +} + +/// Validates whether the given kickoff transaction has been confirmed on Layer 1. +pub async fn tx_on_chain( + client: &BitVM2Client, + txid: &Txid, +) -> Result> { + match client.esplora.get_tx(txid).await? { + Some(_) => Ok(true), + _ => Ok(false), + } +} + +/// Validates whether the given challenge transaction has been confirmed on Layer 1. +pub async fn validate_challenge( + client: &BitVM2Client, + kickoff_txid: &Txid, + challenge_txid: &Txid, +) -> Result> { + let challenge_tx = match client.esplora.get_tx(challenge_txid).await? { + Some(tx) => tx, + _ => return Ok(false), + }; + let expected_challenge_input_0 = OutPoint { txid: *kickoff_txid, vout: 1 }; + Ok(challenge_tx.input[0].previous_output == expected_challenge_input_0) +} + +/// Validates whether the given disprove transaction has been confirmed on Layer 1. +pub async fn validate_disprove( + client: &BitVM2Client, + assert_final_txid: &Txid, + disprove_txid: &Txid, +) -> Result> { + let disprove_tx = match client.esplora.get_tx(disprove_txid).await? { + Some(tx) => tx, + _ => return Ok(false), + }; + let expected_disprove_input_0 = OutPoint { txid: *assert_final_txid, vout: 1 }; + Ok(disprove_tx.input[0].previous_output == expected_disprove_input_0) +} + +/// Validates the provided assert-commit transactions. +/// +/// Steps: +/// - Extract the Groth16 proof from the witness fields of the provided transactions, +/// - Verify the validity of the proof. +/// +/// Returns: +/// - `Ok(None)` if the assert is valid, +/// - `Ok(Some((index, disprove_script)))` if invalid, providing the witness info for later disprove. +/// +pub async fn validate_assert( + client: &BitVM2Client, + assert_commit_txns: &[Txid; COMMIT_TX_NUM], + wots_pubkeys: WotsPublicKeys, +) -> Result, Box> { + let mut txs = Vec::with_capacity(COMMIT_TX_NUM); + for txid in assert_commit_txns.iter() { + let tx = match client.esplora.get_tx(txid).await? { + Some(v) => v, + _ => return Ok(None), // nothing to disprove if assert-commit-txns not on chain + }; + txs.push(tx); + } + let assert_commit_txns: [Transaction; COMMIT_TX_NUM] = + txs.try_into().map_err(|_| "assert-commit-tx num mismatch")?; + let proof_sigs = extract_proof_sigs_from_assert_commit_txns(assert_commit_txns)?; + let disprove_scripts = generate_disprove_scripts(&get_partial_scripts()?, &wots_pubkeys); + let disprove_scripts: [Script; NUM_TAPS] = + disprove_scripts.try_into().map_err(|_| "disprove script num mismatch")?; + Ok(verify_proof(&get_vk()?, proof_sigs, &disprove_scripts, &wots_pubkeys)) +} + +pub fn client() -> Result> { + Err("TODO".into()) +} + +/// Retrieves the Groth16 proof, public inputs, and verifying key +/// for the given graph. +/// +/// These are fetched via the ProofNetwork SDK. +pub fn get_groth16_proof( + _instance_id: Uuid, + _graph_id: Uuid, +) -> Result<(Groth16Proof, PublicInputs, VerifyingKey), Box> { + let mock_proof_bytes: Vec = [ + 162, 50, 57, 98, 3, 171, 250, 108, 49, 206, 73, 126, 25, 35, 178, 148, 35, 219, 98, 90, + 122, 177, 16, 91, 233, 215, 222, 12, 72, 184, 53, 2, 62, 166, 50, 68, 98, 171, 218, 218, + 151, 177, 133, 223, 129, 53, 114, 236, 181, 215, 223, 91, 102, 225, 52, 122, 122, 206, 36, + 122, 213, 38, 186, 170, 235, 210, 179, 221, 122, 37, 74, 38, 79, 0, 26, 94, 59, 146, 46, + 252, 70, 153, 236, 126, 194, 169, 17, 144, 100, 218, 118, 22, 99, 226, 132, 40, 24, 248, + 232, 197, 195, 220, 254, 52, 36, 248, 18, 167, 167, 206, 108, 29, 120, 188, 18, 78, 86, 8, + 121, 217, 144, 185, 122, 58, 12, 34, 44, 6, 233, 80, 177, 183, 5, 8, 150, 74, 241, 141, 65, + 150, 35, 98, 15, 150, 137, 254, 132, 167, 228, 104, 63, 133, 11, 209, 39, 79, 138, 185, 88, + 20, 242, 102, 69, 73, 243, 88, 29, 91, 127, 157, 82, 192, 52, 95, 143, 49, 227, 83, 19, 26, + 108, 63, 232, 213, 169, 64, 221, 159, 214, 220, 246, 174, 35, 43, 143, 80, 168, 142, 29, + 103, 179, 58, 235, 33, 163, 198, 255, 188, 20, 3, 91, 47, 158, 122, 226, 201, 175, 138, 18, + 24, 178, 219, 78, 12, 96, 10, 2, 133, 35, 230, 149, 235, 206, 1, 177, 211, 245, 168, 74, + 62, 25, 115, 70, 42, 38, 131, 92, 103, 103, 176, 212, 223, 177, 242, 94, 14, + ] + .to_vec(); + let mock_scalar = [ + 232, 255, 255, 239, 147, 245, 225, 67, 145, 112, 185, 121, 72, 232, 51, 40, 93, 88, 129, + 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48, + ] + .to_vec(); + let proof: ark_groth16::Proof = + ark_groth16::Proof::deserialize_uncompressed(&mock_proof_bytes[..])?; + let scalar: ark_bn254::Fr = ark_bn254::Fr::deserialize_uncompressed(&mock_scalar[..])?; + Ok((proof, vec![scalar], get_vk()?)) +} +pub fn get_vk() -> Result> { + let mock_vk_bytes = [ + 115, 158, 251, 51, 106, 255, 102, 248, 22, 171, 229, 158, 80, 192, 240, 217, 99, 162, 65, + 107, 31, 137, 197, 79, 11, 210, 74, 65, 65, 203, 243, 14, 123, 2, 229, 125, 198, 247, 76, + 241, 176, 116, 6, 3, 241, 1, 134, 195, 39, 5, 124, 47, 31, 43, 164, 48, 120, 207, 150, 125, + 108, 100, 48, 155, 137, 132, 16, 193, 139, 74, 179, 131, 42, 119, 25, 185, 98, 13, 235, + 118, 92, 11, 154, 142, 134, 220, 191, 220, 169, 250, 244, 104, 123, 7, 247, 33, 178, 155, + 121, 59, 75, 188, 206, 198, 182, 97, 0, 64, 231, 45, 55, 92, 100, 17, 56, 159, 79, 13, 219, + 221, 33, 39, 193, 24, 36, 58, 105, 8, 70, 206, 176, 209, 146, 45, 201, 157, 226, 84, 213, + 135, 143, 178, 156, 112, 137, 246, 123, 248, 215, 168, 51, 95, 177, 47, 57, 29, 199, 224, + 98, 48, 144, 253, 15, 201, 192, 142, 62, 143, 13, 228, 89, 51, 58, 6, 226, 139, 99, 207, + 22, 113, 215, 79, 91, 158, 166, 210, 28, 90, 218, 111, 151, 4, 55, 230, 76, 90, 209, 149, + 113, 248, 245, 50, 231, 137, 51, 157, 40, 29, 184, 198, 201, 108, 199, 89, 67, 136, 239, + 96, 216, 237, 172, 29, 84, 3, 128, 240, 2, 218, 169, 217, 118, 179, 34, 226, 19, 227, 59, + 193, 131, 108, 20, 113, 46, 170, 196, 156, 45, 39, 151, 218, 22, 132, 250, 209, 183, 46, + 249, 115, 239, 14, 176, 200, 134, 158, 148, 139, 212, 167, 152, 205, 183, 236, 242, 176, + 96, 177, 187, 184, 252, 14, 226, 127, 127, 173, 147, 224, 220, 8, 29, 63, 73, 215, 92, 161, + 110, 20, 154, 131, 23, 217, 116, 145, 196, 19, 167, 84, 185, 16, 89, 175, 180, 110, 116, + 57, 198, 237, 147, 183, 164, 169, 220, 172, 52, 68, 175, 113, 244, 62, 104, 134, 215, 99, + 132, 199, 139, 172, 108, 143, 25, 238, 201, 128, 85, 24, 73, 30, 186, 142, 186, 201, 79, 3, + 176, 185, 70, 66, 89, 127, 188, 158, 209, 83, 17, 22, 187, 153, 8, 63, 58, 174, 236, 132, + 226, 43, 145, 97, 242, 198, 117, 105, 161, 21, 241, 23, 84, 32, 62, 155, 245, 172, 30, 78, + 41, 199, 219, 180, 149, 193, 163, 131, 237, 240, 46, 183, 186, 42, 201, 49, 249, 142, 188, + 59, 212, 26, 253, 23, 27, 205, 231, 163, 76, 179, 135, 193, 152, 110, 91, 5, 218, 67, 204, + 164, 128, 183, 221, 82, 16, 72, 249, 111, 118, 182, 24, 249, 91, 215, 215, 155, 2, 0, 0, 0, + 0, 0, 0, 0, 212, 110, 6, 228, 73, 146, 46, 184, 158, 58, 94, 4, 141, 241, 158, 0, 175, 140, + 72, 75, 52, 6, 72, 49, 112, 215, 21, 243, 151, 67, 106, 22, 158, 237, 80, 204, 41, 128, 69, + 52, 154, 189, 124, 203, 35, 107, 132, 241, 234, 31, 3, 165, 87, 58, 10, 92, 252, 227, 214, + 99, 176, 66, 118, 22, 177, 20, 120, 198, 252, 236, 7, 148, 207, 78, 152, 132, 94, 207, 50, + 243, 4, 169, 146, 240, 79, 98, 0, 212, 106, 137, 36, 193, 21, 175, 180, 1, 26, 107, 39, + 198, 89, 152, 26, 220, 138, 105, 243, 45, 63, 106, 163, 80, 74, 253, 176, 207, 47, 52, 7, + 84, 59, 151, 47, 178, 165, 112, 251, 161, + ] + .to_vec(); + Ok(ark_groth16::VerifyingKey::deserialize_uncompressed(&mock_vk_bytes[..])?) +} + +/// l2 support +pub async fn finish_withdraw_happy_path( + client: &BitVM2Client, + graph_id: &Uuid, + take1: &Take1Transaction, +) -> Result<(), Box> { + Ok(client.finish_withdraw_happy_path(graph_id, take1.tx()).await?) +} +pub async fn finish_withdraw_unhappy_path( + client: &BitVM2Client, + graph_id: &Uuid, + take2: &Take2Transaction, +) -> Result<(), Box> { + Ok(client.finish_withdraw_unhappy_path(graph_id, take2.tx()).await?) +} + +pub async fn finish_withdraw_disproved( + client: &BitVM2Client, + graph_id: &Uuid, + disprove: &DisproveTransaction, +) -> Result<(), Box> { + Ok(client.finish_withdraw_disproved(graph_id, disprove.tx()).await?) +} + +/// db support +pub async fn store_committee_pub_nonces( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, + committee_pubkey: PublicKey, + pub_nonces: [PubNonce; COMMITTEE_PRE_SIGN_NUM], +) -> Result<(), Box> { + let mut storage_process = client.local_db.acquire().await?; + let nonces_vec: Vec = pub_nonces.iter().map(|v| v.to_string()).collect(); + let nonces_arr: [String; COMMITTEE_PRE_SIGN_NUM] = + nonces_vec.try_into().map_err(|v: Vec| { + format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) + })?; + Ok(storage_process + .store_nonces(instance_id, graph_id, &[nonces_arr], committee_pubkey.to_string(), &[]) + .await?) +} +pub async fn get_committee_pub_nonces( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, +) -> Result, Box> { + let mut storage_process = client.local_db.acquire().await?; + match storage_process.get_nonces(instance_id, graph_id).await? { + None => { + Err(format!("instance id:{}, graph id:{} not found ", instance_id, graph_id).into()) + } + Some(nonce_collect) => { + let mut res: Vec<[PubNonce; COMMITTEE_PRE_SIGN_NUM]> = vec![]; + for nonces_item in nonce_collect.nonces { + let nonce_vec: Vec = nonces_item + .iter() + .map(|v| PubNonce::from_str(v).expect("fail to decode pub nonce")) + .collect(); + res.push(nonce_vec.try_into().map_err(|v: Vec| { + format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) + })?) + } + Ok(res) + } + } +} + +pub async fn store_committee_pubkeys( + client: &BitVM2Client, + instance_id: Uuid, + pubkey: PublicKey, +) -> Result<(), Box> { + let mut storage_process = client.local_db.acquire().await?; + Ok(storage_process.store_pubkeys(instance_id, &vec![pubkey.to_string()]).await?) +} +pub async fn get_committee_pubkeys( + client: &BitVM2Client, + instance_id: Uuid, +) -> Result, Box> { + let mut storage_process = client.local_db.acquire().await?; + match storage_process.get_pubkeys(instance_id).await? { + None => Ok(vec![]), + Some(meta_data) => Ok(meta_data + .pubkeys + .iter() + .map(|v| PublicKey::from_str(v).expect("fail to decode to public key")) + .collect()), + } +} + +pub async fn store_committee_partial_sigs( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, + committee_pubkey: PublicKey, + partial_sigs: [PartialSignature; COMMITTEE_PRE_SIGN_NUM], +) -> Result<(), Box> { + let mut storage_process = client.local_db.acquire().await?; + let signs_vec: Vec = partial_sigs.iter().map(|v| hex::encode(v.serialize())).collect(); + let signs_arr: [String; COMMITTEE_PRE_SIGN_NUM] = + signs_vec.try_into().map_err(|v: Vec| { + format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) + })?; + + Ok(storage_process + .store_nonces(instance_id, graph_id, &[], committee_pubkey.to_string(), &vec![signs_arr]) + .await?) +} + +pub async fn get_committee_partial_sigs( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, +) -> Result, Box> { + let mut storage_process = client.local_db.acquire().await?; + match storage_process.get_nonces(instance_id, graph_id).await? { + None => { + Err(format!("instance id:{}, graph id:{} not found ", instance_id, graph_id).into()) + } + Some(nonce_collect) => { + let mut res: Vec<[PartialSignature; COMMITTEE_PRE_SIGN_NUM]> = vec![]; + for signs_item in nonce_collect.partial_sigs { + let signs_vec: Vec = signs_item + .iter() + .map(|v| PartialSignature::from_str(v).expect("fail to decode pub nonce")) + .collect(); + res.push(signs_vec.try_into().map_err(|v: Vec| { + format!("length wrong: expect {}, real {}", COMMITTEE_PRE_SIGN_NUM, v.len()) + })?) + } + Ok(res) + } + } +} + +pub async fn update_graph_status_or_ipfs_base( + client: &BitVM2Client, + graph_id: Uuid, + graph_state: Option, + ipfs_base_url: Option, +) -> Result<(), Box> { + let mut storage_process = client.local_db.acquire().await?; + Ok(storage_process + .update_graph_status_or_ipfs_base(graph_id, graph_state, ipfs_base_url) + .await?) +} +pub async fn store_graph( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, + graph: &Bitvm2Graph, + status: Option, +) -> Result<(), Box> { + let mut storage_process = client.local_db.acquire().await?; + let assert_commit_txids: Vec = + graph.assert_commit.commit_txns.iter().map(|v| v.tx().compute_txid().to_string()).collect(); + storage_process + .update_graph(Graph { + graph_id, + instance_id, + graph_ipfs_base_url: "".to_string(), //TODO + pegin_txid: graph.pegin.tx().compute_txid().to_string(), + amount: graph.parameters.pegin_amount.to_sat() as i64, + status: status.unwrap_or_else(|| GraphStatus::OperatorPresigned.to_string()), + kickoff_txid: Some(graph.kickoff.tx().compute_txid().to_string()), + challenge_txid: Some(graph.challenge.tx().compute_txid().to_string()), + take1_txid: Some(graph.take1.tx().compute_txid().to_string()), + assert_init_txid: Some(graph.assert_init.tx().compute_txid().to_string()), + assert_commit_txids: Some(format!("{:?}", assert_commit_txids)), + assert_final_txid: Some(graph.assert_final.tx().compute_txid().to_string()), + take2_txid_txid: Some(graph.take2.tx().compute_txid().to_string()), + disprove_txid: Some(graph.disprove.tx().compute_txid().to_string()), + operator: graph.parameters.operator_pubkey.to_string(), + raw_data: Some(serde_json::to_string(&graph).expect("to json string")), + created_at: current_time_secs(), + updated_at: current_time_secs(), + }) + .await?; + + Ok(()) +} + +#[allow(dead_code)] +pub async fn update_graph( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, + graph: &Bitvm2Graph, + status: Option, +) -> Result<(), Box> { + store_graph(client, instance_id, graph_id, graph, status).await +} + +pub async fn get_graph( + client: &BitVM2Client, + instance_id: Uuid, + graph_id: Uuid, +) -> Result> { + let mut storage_process = client.local_db.acquire().await?; + let graph = storage_process.get_graph(&graph_id).await?; + if graph.instance_id.ne(&instance_id) { + return Err(format!( + "grap with graph_id:{} has instance_id:{} not match expec instance:{}", + graph_id, graph.instance_id, instance_id + ) + .into()); + } + + if graph.raw_data.is_none() { + return Err(format!("grap with graph_id:{} raw data is none", graph_id).into()); + } + let res: Bitvm2Graph = serde_json::from_str(graph.raw_data.unwrap().as_str())?; + Ok(res) +}