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

feat: inscriber example #17

Merged
merged 9 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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!(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could use the tracing crate here. It will help understand how logs will look like in production
checkout the indexer example

"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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to implement fmt::Debug for context struct, but maybe in the future refactoring or so

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, i'll add your suggestion in my next PR

thanks


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