Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #17 from vianetwork/feat/inscriber-example-and-fix
Browse files Browse the repository at this point in the history
feat: inscriber example
  • Loading branch information
irnb authored Aug 23, 2024
2 parents 93037b7 + fc38ba4 commit b9761e8
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 84 deletions.
6 changes: 5 additions & 1 deletion core/lib/via_btc_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ required-features = ["regtest"]

[[example]]
name = "data_inscription_example"
path = "examples/data_inscription_example.rs"
path = "examples/data_inscription_example.rs"

[[example]]
name = "inscriber"
path = "examples/inscriber.rs"
75 changes: 75 additions & 0 deletions core/lib/via_btc_client/examples/inscriber.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use via_btc_client::types as inscribe_types;
use via_btc_client::{inscriber::Inscriber, types::BitcoinNetwork, types::NodeAuth};

use anyhow::{Context, Result};

#[tokio::main]
async fn main() -> Result<()> {
// get the node url and private key from the environment

// export BITCOIN_NODE_URL="http://example.com:8332"
// export BITCOIN_PRV=example_wif

let url = std::env::var("BITCOIN_NODE_URL").context("BITCOIN_NODE_URL not set")?;
let prv = std::env::var("BITCOIN_PRV").context("BITCOIN_PRV not set")?;

let mut inscriber_instance = Inscriber::new(
&url,
BitcoinNetwork::Regtest,
NodeAuth::UserPass("via".to_string(), "via".to_string()),
&prv,
None,
)
.await
.context("Failed to create Inscriber")?;

println!(
"balance: {}",
inscriber_instance
.get_balance()
.await
.context("Failed to get balance")?
);

let l1_da_batch_ref = inscribe_types::L1BatchDAReferenceInput {
l1_batch_hash: zksync_basic_types::H256([0; 32]),
l1_batch_index: zksync_basic_types::L1BatchNumber(0_u32),
da_identifier: "da_identifier_celestia".to_string(),
blob_id: "batch_temp_blob_id".to_string(),
};

let l1_batch_da_txid = inscriber_instance
.inscribe(inscribe_types::InscriptionMessage::L1BatchDAReference(
l1_da_batch_ref,
))
.await
.context("Failed to inscribe L1BatchDAReference")?;

println!("---------------------------------First Inscription---------------------------------");
let context = inscriber_instance.get_context_snapshot()?;
println!("context: {:?}", context);

let l1_da_proof_ref = inscribe_types::ProofDAReferenceInput {
l1_batch_reveal_txid: l1_batch_da_txid[1],
da_identifier: "da_identifier_celestia".to_string(),
blob_id: "proof_temp_blob_id".to_string(),
};

let _da_proof_ref_reveal_txid = inscriber_instance
.inscribe(inscribe_types::InscriptionMessage::ProofDAReference(
l1_da_proof_ref,
))
.await
.context("Failed to inscribe ProofDAReference")?;

println!(
"---------------------------------Second Inscription---------------------------------"
);
let context = inscriber_instance.get_context_snapshot()?;

println!("context: {:?}", context);

println!("---------------------------------End---------------------------------");

Ok(())
}
42 changes: 31 additions & 11 deletions core/lib/via_btc_client/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ mod rpc_client;
use crate::{
client::rpc_client::BitcoinRpcClient,
traits::{BitcoinOps, BitcoinRpc},
types::{Auth, BitcoinClientResult, BitcoinError, Network},
types::{BitcoinClientResult, BitcoinError, BitcoinNetwork, NodeAuth},
};

pub struct BitcoinClient {
rpc: Box<dyn BitcoinRpc>,
network: Network,
network: BitcoinNetwork,
}

impl BitcoinClient {
#[instrument(skip(auth), target = "bitcoin_client")]
pub(crate) fn new(rpc_url: &str, network: Network, auth: Auth) -> BitcoinClientResult<Self>
pub(crate) fn new(
rpc_url: &str,
network: BitcoinNetwork,
auth: NodeAuth,
) -> BitcoinClientResult<Self>
where
Self: Sized,
{
Expand Down Expand Up @@ -47,10 +51,14 @@ impl BitcoinOps for BitcoinClient {
Ok(txid)
}

// The address should be imported to the node
// bitcoin-cli createwallet "watch-only" true
// bitcoin-cli getdescriptorinfo "addr(p2wpkh address)"
// bitcoin-cli importdescriptors '[{"desc": "addr(p2wpkh address)", "timestamp": "now", "range": 1000, "watchonly": true, "label": "watch-only"}]'
#[instrument(skip(self), target = "bitcoin_client")]
async fn fetch_utxos(&self, address: &Address) -> BitcoinClientResult<Vec<(OutPoint, TxOut)>> {
debug!("Fetching UTXOs");
let outpoints = self.rpc.list_unspent(address).await?;
let outpoints = self.rpc.list_unspent_based_on_node_wallet(address).await?;

let mut utxos = Vec::with_capacity(outpoints.len());

Expand Down Expand Up @@ -94,7 +102,17 @@ impl BitcoinOps for BitcoinClient {
.await?;

match estimation.fee_rate {
Some(fee_rate) => Ok(fee_rate.to_sat()),
Some(fee_rate) => {
// convert btc/kb to sat/byte
let fee_rate_sat_kb = fee_rate.to_sat();
let fee_rate_sat_byte = fee_rate_sat_kb.checked_div(1000);
match fee_rate_sat_byte {
Some(fee_rate_sat_byte) => Ok(fee_rate_sat_byte),
None => Err(BitcoinError::FeeEstimationFailed(
"Invalid fee rate".to_string(),
)),
}
}
None => {
let err = estimation
.errors
Expand All @@ -106,7 +124,7 @@ impl BitcoinOps for BitcoinClient {
}
}

fn get_network(&self) -> Network {
fn get_network(&self) -> BitcoinNetwork {
self.network
}

Expand Down Expand Up @@ -149,6 +167,7 @@ mod tests {
impl BitcoinRpc for BitcoinRpc {
async fn get_balance(&self, address: &Address) -> BitcoinClientResult<u64>;
async fn send_raw_transaction(&self, tx_hex: &str) -> BitcoinClientResult<Txid>;
async fn list_unspent_based_on_node_wallet(&self, address: &Address) -> BitcoinClientResult<Vec<OutPoint>>;
async fn list_unspent(&self, address: &Address) -> BitcoinClientResult<Vec<OutPoint>>;
async fn get_transaction(&self, txid: &Txid) -> BitcoinClientResult<Transaction>;
async fn get_block_count(&self) -> BitcoinClientResult<u64>;
Expand All @@ -164,7 +183,7 @@ mod tests {
fn get_client_with_mock(mock_bitcoin_rpc: MockBitcoinRpc) -> BitcoinClient {
BitcoinClient {
rpc: Box::new(mock_bitcoin_rpc),
network: Network::Bitcoin,
network: BitcoinNetwork::Bitcoin,
}
}

Expand All @@ -176,7 +195,7 @@ mod tests {
let client = get_client_with_mock(mock_rpc);
let address = Address::from_str("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq")
.unwrap()
.require_network(Network::Bitcoin)
.require_network(BitcoinNetwork::Bitcoin)
.unwrap();
let balance = client.get_balance(&address).await.unwrap();
assert_eq!(balance, 1000000);
Expand Down Expand Up @@ -209,7 +228,7 @@ mod tests {
vout: 0,
};
mock_rpc
.expect_list_unspent()
.expect_list_unspent_based_on_node_wallet()
.return_once(move |_| Ok(vec![outpoint]));
mock_rpc.expect_get_transaction().return_once(|_| {
Ok(Transaction {
Expand All @@ -227,7 +246,7 @@ mod tests {

let address = Address::from_str("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq")
.unwrap()
.require_network(Network::Bitcoin)
.require_network(BitcoinNetwork::Bitcoin)
.unwrap();
let utxos = client.fetch_utxos(&address).await.unwrap();
assert_eq!(utxos.len(), 1);
Expand Down Expand Up @@ -290,6 +309,7 @@ mod tests {
let client = get_client_with_mock(mock_rpc);

let fee_rate = client.get_fee_rate(6).await.unwrap();
assert_eq!(fee_rate, 1000);
// 1000 sat/kb = 1 sat/byte
assert_eq!(fee_rate, 1);
}
}
50 changes: 44 additions & 6 deletions core/lib/via_btc_client/src/client/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::{debug, instrument};

use crate::{
traits::BitcoinRpc,
types::{Auth, BitcoinRpcResult},
types::{BitcoinRpcResult, NodeAuth},
utils::with_retry,
};

Expand All @@ -22,7 +22,7 @@ pub struct BitcoinRpcClient {

impl BitcoinRpcClient {
#[instrument(skip(auth), target = "bitcoin_client::rpc_client")]
pub fn new(url: &str, auth: Auth) -> Result<Self, bitcoincore_rpc::Error> {
pub fn new(url: &str, auth: NodeAuth) -> Result<Self, bitcoincore_rpc::Error> {
let client = Client::new(url, auth)?;
Ok(Self { client })
}
Expand All @@ -41,10 +41,20 @@ impl BitcoinRpc for BitcoinRpcClient {
async fn get_balance(&self, address: &Address) -> BitcoinRpcResult<u64> {
Self::retry_rpc(|| {
debug!("Getting balance");
let descriptor = format!("addr({})", address);
let request = vec![ScanTxOutRequest::Single(descriptor)];
let result = self.client.scan_tx_out_set_blocking(&request)?;
Ok(result.total_amount.to_sat())
let result = self.client.list_unspent(
Some(1), // minconf
None, // maxconf
Some(&[address]), // addresses
None, // include_unsafe
None, // query_options
)?;

let total_amount: u64 = result
.into_iter()
.map(|unspent| unspent.amount.to_sat())
.sum();

Ok(total_amount)
})
.await
}
Expand All @@ -60,6 +70,34 @@ impl BitcoinRpc for BitcoinRpcClient {
.await
}

#[instrument(skip(self), target = "bitcoin_client::rpc_client")]
async fn list_unspent_based_on_node_wallet(
&self,
address: &Address,
) -> BitcoinRpcResult<Vec<OutPoint>> {
Self::retry_rpc(|| {
debug!("Listing unspent outputs based on node wallet");
let result = self.client.list_unspent(
Some(1), // minconf
None, // maxconf
Some(&[address]), // addresses
None, // include_unsafe
None,
)?;

let unspent: Vec<OutPoint> = result
.into_iter()
.map(|unspent| OutPoint {
txid: unspent.txid,
vout: unspent.vout,
})
.collect();

Ok(unspent)
})
.await
}

#[instrument(skip(self), target = "bitcoin_client::rpc_client")]
async fn list_unspent(&self, address: &Address) -> BitcoinRpcResult<Vec<OutPoint>> {
Self::retry_rpc(|| {
Expand Down
10 changes: 8 additions & 2 deletions core/lib/via_btc_client/src/inscriber/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const P2WPKH_OUTPUT_BASE_SIZE: usize = 34;
// 8 + 1 + 34 = 43
const P2TR_OUTPUT_BASE_SIZE: usize = 43;

const VIRTUAL_SIZE_DIVIDER: usize = 4;

pub struct InscriberFeeCalculator {}

impl InscriberFeeCalculator {
Expand All @@ -62,7 +64,7 @@ impl InscriberFeeCalculator {
// https://en.bitcoin.it/wiki/Protocol_documentation#Common_structures
// https://btcinformation.org/en/developer-reference#p2p-network

if p2tr_inputs_count == p2tr_witness_sizes.len() as u32 {
if p2tr_inputs_count != p2tr_witness_sizes.len() as u32 {
return Err(BitcoinError::FeeEstimationFailed(
"Invalid witness sizes count".to_string(),
));
Expand All @@ -73,7 +75,11 @@ impl InscriberFeeCalculator {
let mut p2tr_input_size = 0;

for witness_size in p2tr_witness_sizes {
p2tr_input_size += P2TR_INPUT_BASE_SIZE + witness_size;
let witness_virtual_size = witness_size.checked_div(VIRTUAL_SIZE_DIVIDER).ok_or(
BitcoinError::FeeEstimationFailed("Invalid witness size".to_string()),
)?;

p2tr_input_size += P2TR_INPUT_BASE_SIZE + witness_virtual_size + 1;
}

let p2wpkh_output_size = P2WPKH_OUTPUT_BASE_SIZE * p2wpkh_outputs_count as usize;
Expand Down
Loading

0 comments on commit b9761e8

Please sign in to comment.