Skip to content

Commit

Permalink
refactor: RPC client now uses bitcoincore-rpc-async
Browse files Browse the repository at this point in the history
Changes to a thin tuple struct wrapper for
`bitcoincore-rpc-async::Client`.
  • Loading branch information
storopoli committed Aug 20, 2024
1 parent db892db commit e215e09
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 569 deletions.
564 changes: 62 additions & 502 deletions crates/btcio/src/rpc/client.rs

Large diffs are not rendered by default.

67 changes: 50 additions & 17 deletions crates/btcio/src/rpc/traits.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use async_trait::async_trait;
use bitcoin::{Block, BlockHash, Network, Transaction, Txid};
use bitcoin::{Amount, Block, BlockHash, Transaction, Txid};
use bitcoincore_rpc_async::Error as RpcError;

use super::{
types::{RawUTXO, RpcBlockchainInfo},
ClientError,
};
use super::types::{RawUTXO, RpcBlockchainInfo};

/// Basic functionality that any Bitcoin client that interacts with the
/// Bitcoin network should provide.
Expand All @@ -14,6 +12,10 @@ pub trait BitcoinClient: Sync + Send + 'static {
/// to begin confirmation within conf_target blocks if possible and return
/// the number of blocks for which the estimate is valid.
///
/// # Parameters
///
/// - `conf_target`: Confirmation target in blocks.
///
/// # Note
///
/// Uses virtual transaction size as defined in
Expand All @@ -22,16 +24,33 @@ pub trait BitcoinClient: Sync + Send + 'static {
///
/// By default uses the estimate mode of `CONSERVATIVE` which is the
/// default in Bitcoin Core v27.
async fn estimate_smart_fee(&self) -> Result<u64, ClientError>;
async fn estimate_smart_fee(&self, conf_target: u16) -> Result<u64, RpcError>;

/// Gets a [`Block`] with the given hash.
async fn get_block(&self, hash: BlockHash) -> Result<Block, RpcError>;

/// Gets a [`Block`] at given height.
async fn get_block_at(&self, height: u64) -> Result<Block, ClientError>;
async fn get_block_at(&self, height: u64) -> Result<Block, RpcError>;

/// Gets the height of the most-work fully-validated chain.
///
/// # Note
///
/// The genesis block has a height of 0.
async fn get_block_count(&self) -> Result<u64, RpcError>;

/// Gets the [`BlockHash`] at given height.
async fn get_block_hash(&self, height: u64) -> Result<BlockHash, ClientError>;
async fn get_block_hash(&self, height: u64) -> Result<BlockHash, RpcError>;

async fn get_blockchain_info(&self) -> Result<RpcBlockchainInfo, RpcError>;
/// Gets various state info regarding blockchain processing.
async fn get_blockchain_info(&self) -> Result<RpcBlockchainInfo, ClientError>;

/// Generates new address under own control for the underlying Bitcoin
/// client's wallet.
async fn get_new_address(&self) -> Result<String, RpcError>;

/// Gets all transaction ids in mempool.
async fn get_raw_mempool(&self) -> Result<Vec<Txid>, RpcError>;

/// Gets the number of confirmations for a given transaction id
/// (txid).
Expand All @@ -48,20 +67,37 @@ pub trait BitcoinClient: Sync + Send + 'static {
async fn get_transaction_confirmations<T: AsRef<[u8; 32]> + Send>(
&self,
txid: T,
) -> Result<u64, ClientError>;
) -> Result<u64, RpcError>;

/// Gets all Unspent Transaction Outputs (UTXOs) for the underlying Bitcoin
/// client's wallet.
async fn get_utxos(&self) -> Result<Vec<RawUTXO>, ClientError>;
async fn get_utxos(&self) -> Result<Vec<RawUTXO>, RpcError>;

/// Gets all transactions in blocks since block [`Blockhash`].
async fn list_since_block(&self, block_hash: BlockHash)
-> Result<Vec<(String, u64)>, RpcError>;

/// Lists transactions in the underlying Bitcoin client's wallet.
///
/// # Parameters
///
/// - `count`: The number of transactions to list. If `None`, assumes a maximum of 10
/// transactions.
async fn list_transactions(&self, count: Option<u32>) -> Result<Vec<(String, u64)>, RpcError>;

/// Lists all wallets in the underlying Bitcoin client.
async fn list_wallets(&self) -> Result<Vec<String>, RpcError>;

/// Sends a raw transaction to the network.
///
/// # Parameters
///
/// - `tx`: The raw transaction to send. This should be a byte array containing the serialized
/// raw transaction data.
async fn send_raw_transaction<T: AsRef<[u8]> + Send>(&self, tx: T)
-> Result<Txid, ClientError>;
async fn send_raw_transaction<T: AsRef<[u8]> + Send>(&self, tx: T) -> Result<Txid, RpcError>;

/// Sends an amount to a given address.
async fn send_to_address(&self, address: &str, amount: Amount) -> Result<Txid, RpcError>;

/// Signs a transaction using the keys available in the underlying Bitcoin
/// client's wallet and returns a signed transaction.
Expand All @@ -73,8 +109,5 @@ pub trait BitcoinClient: Sync + Send + 'static {
async fn sign_raw_transaction_with_wallet(
&self,
tx: Transaction,
) -> Result<Transaction, ClientError>;

/// Returns the [`Network`] of the underlying Bitcoin client.
fn get_network(&self) -> Network;
) -> Result<Transaction, RpcError>;
}
29 changes: 10 additions & 19 deletions crates/btcio/src/writer/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{sync::Arc, time::Duration};
use alpen_express_rpc_types::L1Status;
use anyhow::anyhow;
use bitcoin::{consensus::deserialize, Txid};
use bitcoincore_rpc_async::Error as RpcError;
use tokio::sync::RwLock;
use tracing::*;

Expand All @@ -12,7 +13,7 @@ use alpen_express_db::{
};

use crate::{
rpc::{traits::BitcoinClient, ClientError},
rpc::traits::BitcoinClient,
writer::utils::{get_blob_by_idx, get_l1_tx},
};

Expand Down Expand Up @@ -87,44 +88,34 @@ pub async fn broadcaster_task<D: SequencerDatabase + Send + Sync + 'static>(
}
curr_idx += 1;
}
Err(SendError::MissingOrInvalidInput) => {
Err(e) => {
// DEBUG: What the hell is this?
// FIXME: remove me
#[cfg(debug_assertions)]
dbg!(&e);
// Means need to Resign/Republish
blobentry.status = BlobL1Status::NeedsResign;

db.sequencer_store()
.update_blob_by_idx(curr_idx, blobentry)?;
}
Err(SendError::Other(e)) => {
warn!(%e, "Error sending !");
// TODO: Maybe retry or return?
}
}
} else {
debug!(%curr_idx, "No blob found");
}
}
}

enum SendError {
MissingOrInvalidInput,
Other(String),
}

async fn send_commit_reveal_txs(
commit_tx_raw: Vec<u8>,
reveal_tx_raw: Vec<u8>,
client: &impl BitcoinClient,
) -> Result<(), SendError> {
) -> Result<(), RpcError> {
send_tx(commit_tx_raw, client).await?;
send_tx(reveal_tx_raw, client).await?;
Ok(())
}

async fn send_tx(tx_raw: Vec<u8>, client: &impl BitcoinClient) -> Result<(), SendError> {
match client.send_raw_transaction(tx_raw).await {
Ok(_) => Ok(()),
Err(ClientError::Server(-27, _)) => Ok(()), // Tx already in chain
Err(ClientError::Server(-25, _)) => Err(SendError::MissingOrInvalidInput),
Err(e) => Err(SendError::Other(e.to_string())),
}
async fn send_tx(tx_raw: Vec<u8>, client: &impl BitcoinClient) -> Result<Txid, RpcError> {
client.send_raw_transaction(tx_raw).await
}
5 changes: 3 additions & 2 deletions crates/btcio/src/writer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub async fn build_inscription_txs(
rpc_client: &Arc<impl BitcoinClient>,
config: &WriterConfig,
) -> anyhow::Result<(Transaction, Transaction)> {
let network = config.network;
// let (signature, pub_key) = sign_blob_with_private_key(&payload, &config.private_key)?;
let utxos = rpc_client.get_utxos().await?;
let utxos = utxos
Expand All @@ -110,7 +111,7 @@ pub async fn build_inscription_txs(
.map_err(|e| anyhow::anyhow!("{:?}", e))?;

let fee_rate = match config.inscription_fee_policy {
InscriptionFeePolicy::Smart => rpc_client.estimate_smart_fee().await?,
InscriptionFeePolicy::Smart => rpc_client.estimate_smart_fee(1).await?,
InscriptionFeePolicy::Fixed(val) => val,
};
create_inscription_transactions(
Expand All @@ -120,7 +121,7 @@ pub async fn build_inscription_txs(
config.sequencer_address.clone(),
config.amount_for_reveal_txn,
fee_rate,
rpc_client.get_network(),
network,
)
.map_err(|e| anyhow::anyhow!(e.to_string()))
}
Expand Down
12 changes: 6 additions & 6 deletions crates/btcio/src/writer/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bitcoin::Address;
use bitcoin::{Address, Network};
use std::str::FromStr;

#[derive(Debug, Clone)]
Expand All @@ -17,14 +17,13 @@ pub struct WriterConfig {

/// How much amount(in sats) to send to reveal address
pub(super) amount_for_reveal_txn: u64,

/// The Bitcoin [`Network`] to use
pub(super) network: Network,
}

impl WriterConfig {
pub fn new(
address: String,
network: bitcoin::Network,
rollup_name: String,
) -> anyhow::Result<Self> {
pub fn new(address: String, network: Network, rollup_name: String) -> anyhow::Result<Self> {
let addr = Address::from_str(&address)?.require_network(network)?;
Ok(Self {
sequencer_address: addr,
Expand All @@ -33,6 +32,7 @@ impl WriterConfig {
inscription_fee_policy: InscriptionFeePolicy::Fixed(100),
poll_duration_ms: 1000,
amount_for_reveal_txn: 1000,
network,
})
}
}
Expand Down
70 changes: 50 additions & 20 deletions crates/btcio/src/writer/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use alpen_test_utils::ArbitraryGenerator;
use async_trait::async_trait;
use bitcoin::{consensus::deserialize, hashes::Hash, Block, BlockHash, Network, Transaction, Txid};
use bitcoin::{consensus::deserialize, hashes::Hash, Amount, Block, BlockHash, Transaction, Txid};
use bitcoincore_rpc_async::Error as RpcError;

use crate::rpc::{
traits::BitcoinClient,
types::{RawUTXO, RpcBlockchainInfo},
ClientError,
};

pub struct BitcoinDTestClient {
Expand All @@ -22,35 +22,50 @@ const TEST_BLOCKSTR: &str = "000000207d862a78fcb02ab24ebd154a20b9992af6d2f0c94d3

#[async_trait]
impl BitcoinClient for BitcoinDTestClient {
async fn get_blockchain_info(&self) -> Result<RpcBlockchainInfo, ClientError> {
Ok(ArbitraryGenerator::new().generate())
async fn estimate_smart_fee(&self, _conf_target: u16) -> Result<u64, RpcError> {
Ok(3) // hardcoded to 3 sats/vByte
}

async fn get_block_at(&self, _height: u64) -> Result<Block, ClientError> {
async fn get_block(&self, _hash: BlockHash) -> Result<Block, RpcError> {
let block: Block = deserialize(&hex::decode(TEST_BLOCKSTR).unwrap()).unwrap();
Ok(block)
}

async fn get_block_hash(&self, _h: u64) -> Result<BlockHash, ClientError> {
async fn get_block_at(&self, _height: u64) -> Result<Block, RpcError> {
let block: Block = deserialize(&hex::decode(TEST_BLOCKSTR).unwrap()).unwrap();
Ok(block)
}

async fn get_block_count(&self) -> Result<u64, RpcError> {
Ok(1)
}

async fn get_block_hash(&self, _height: u64) -> Result<BlockHash, RpcError> {
let block: Block = deserialize(&hex::decode(TEST_BLOCKSTR).unwrap()).unwrap();
Ok(block.block_hash())
}

async fn send_raw_transaction<T: AsRef<[u8]> + Send>(
&self,
_tx: T,
) -> Result<Txid, ClientError> {
Ok(Txid::from_slice(&[1u8; 32]).unwrap())
async fn get_blockchain_info(&self) -> Result<RpcBlockchainInfo, RpcError> {
Ok(ArbitraryGenerator::new().generate())
}

async fn get_new_address(&self) -> Result<String, RpcError> {
// random regtest address from https://bitcoin.stackexchange.com/q/91222
Ok("bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw".to_string())
}

async fn get_raw_mempool(&self) -> Result<Vec<Txid>, RpcError> {
Ok(vec![])
}

async fn get_transaction_confirmations<T: AsRef<[u8; 32]> + Send>(
&self,
_txid: T,
) -> Result<u64, ClientError> {
) -> Result<u64, RpcError> {
Ok(self.confs)
}

async fn get_utxos(&self) -> Result<Vec<RawUTXO>, ClientError> {
async fn get_utxos(&self) -> Result<Vec<RawUTXO>, RpcError> {
// Generate enough utxos to cover for the costs later
let utxos: Vec<_> = (1..10)
.map(|_| ArbitraryGenerator::new().generate())
Expand All @@ -67,18 +82,33 @@ impl BitcoinClient for BitcoinDTestClient {
Ok(utxos)
}

async fn estimate_smart_fee(&self) -> Result<u64, ClientError> {
Ok(3) // hardcoded to 3 sats/vByte
async fn list_since_block(
&self,
_blockhash: BlockHash,
) -> Result<Vec<(String, u64)>, RpcError> {
Ok(vec![])
}

async fn list_transactions(&self, _count: Option<u32>) -> Result<Vec<(String, u64)>, RpcError> {
Ok(vec![])
}

async fn list_wallets(&self) -> Result<Vec<String>, RpcError> {
Ok(vec![])
}

async fn send_raw_transaction<T: AsRef<[u8]> + Send>(&self, _tx: T) -> Result<Txid, RpcError> {
Ok(Txid::from_slice(&[1u8; 32]).unwrap())
}

async fn send_to_address(&self, _address: &str, _amount: Amount) -> Result<Txid, RpcError> {
Ok(Txid::from_slice(&[0u8; 32]).unwrap())
}

async fn sign_raw_transaction_with_wallet(
&self,
tx: Transaction,
) -> Result<Transaction, ClientError> {
) -> Result<Transaction, RpcError> {
Ok(tx)
}

fn get_network(&self) -> Network {
Network::Regtest
}
}
4 changes: 3 additions & 1 deletion crates/btcio/src/writer/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,18 @@ mod test {
}

fn get_config() -> WriterConfig {
let network = Network::Regtest;
let addr = Address::from_str("bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5")
.unwrap()
.require_network(Network::Regtest)
.require_network(network)
.unwrap();
WriterConfig {
sequencer_address: addr,
rollup_name: "alpen".to_string(),
inscription_fee_policy: InscriptionFeePolicy::Fixed(100),
poll_duration_ms: 1000,
amount_for_reveal_txn: 1000,
network,
}
}

Expand Down
Loading

0 comments on commit e215e09

Please sign in to comment.