Skip to content

Commit

Permalink
rpc+wallet: add send transaction rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
octobocto committed Nov 7, 2024
1 parent 2b11429 commit f3ff469
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 8 deletions.
15 changes: 15 additions & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use nom::{
IResult,
};

use crate::proto::common::Hex;
use crate::types::{
SidechainDeclaration, SidechainDescription, SidechainNumber, SidechainProposal,
};
Expand Down Expand Up @@ -262,6 +263,20 @@ pub fn create_m5_deposit_output(
}
}

pub fn create_op_return_output(message: Hex) -> TxOut {
let mut script_bytes = vec![OP_RETURN.to_u8()];
let hex_value = message.hex.as_ref().expect("hex value must be present");
script_bytes.push(hex_value.len() as u8);
script_bytes.extend(hex_value.as_bytes());

let script_pubkey = ScriptBuf::from_bytes(script_bytes);

TxOut {
script_pubkey,
value: Amount::ZERO,
}
}

fn parse_m1_propose_sidechain(input: &[u8]) -> IResult<&[u8], CoinbaseMessage> {
let (input, sidechain_number) = take(1usize)(input)?;
let sidechain_number = sidechain_number[0];
Expand Down
79 changes: 73 additions & 6 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, sync::Arc, vec};
use std::{collections::HashMap, str::FromStr, sync::Arc, vec};

use bdk_wallet::chain::{ChainPosition, ConfirmationBlockTime};
use bitcoin::{
Expand Down Expand Up @@ -29,9 +29,13 @@ use crate::{
},
mainchain::{
create_sidechain_proposal_response, get_bmm_h_star_commitment_response,
get_ctip_response::Ctip, get_sidechain_proposals_response::SidechainProposal,
get_sidechains_response::SidechainInfo, server::ValidatorService,
wallet_service_server::WalletService, wallet_transaction::Confirmation,
get_ctip_response::Ctip,
get_sidechain_proposals_response::SidechainProposal,
get_sidechains_response::SidechainInfo,
send_transaction_request::{self},
server::ValidatorService,
wallet_service_server::WalletService,
wallet_transaction::Confirmation,
BroadcastWithdrawalBundleRequest, BroadcastWithdrawalBundleResponse,
CreateBmmCriticalDataTransactionRequest, CreateBmmCriticalDataTransactionResponse,
CreateDepositTransactionRequest, CreateDepositTransactionResponse,
Expand All @@ -44,8 +48,8 @@ use crate::{
GetCoinbasePsbtResponse, GetCtipRequest, GetCtipResponse, GetSidechainProposalsRequest,
GetSidechainProposalsResponse, GetSidechainsRequest, GetSidechainsResponse,
GetTwoWayPegDataRequest, GetTwoWayPegDataResponse, ListTransactionsRequest,
ListTransactionsResponse, Network, SubscribeEventsRequest, SubscribeEventsResponse,
WalletTransaction,
ListTransactionsResponse, Network, SendTransactionRequest, SendTransactionResponse,
SubscribeEventsRequest, SubscribeEventsResponse, WalletTransaction,
},
},
types::{Event, SidechainNumber},
Expand Down Expand Up @@ -855,6 +859,7 @@ impl WalletService for Arc<crate::wallet::Wallet> {
value_sats,
fee_sats,
} = request.into_inner();

let sidechain_number = sidechain_id
.ok_or_else(|| missing_field::<CreateDepositTransactionRequest>("sidechain_id"))
.map(SidechainNumber::try_from)?
Expand Down Expand Up @@ -973,6 +978,68 @@ impl WalletService for Arc<crate::wallet::Wallet> {
};
Ok(tonic::Response::new(response))
}

async fn send_transaction(
&self,
request: tonic::Request<SendTransactionRequest>,
) -> Result<tonic::Response<SendTransactionResponse>, tonic::Status> {
let SendTransactionRequest {
destinations,
fee_rate,
op_return_message,
} = request.into_inner();

// Extract fee rate per vbyte
let fee_rate_per_vbyte = match fee_rate {
Some(fee_rate) => match fee_rate.fee {
Some(send_transaction_request::fee_rate::Fee::SatPerVbyte(rate)) => {
Some(Amount::from_sat(rate))
}
_ => None,
},
None => None,
};

// Or hard-coded fee
let fee = match fee_rate {
Some(fee_rate) => match fee_rate.fee {
Some(send_transaction_request::fee_rate::Fee::Sats(sats)) => {
Some(Amount::from_sat(sats))
}
_ => None,
},
None => None,
};

// Parse and validate all destination addresses, but assume network valid
let destinations_validated = destinations
.iter()
.map(|(address, amount)| {
bdk_wallet::bitcoin::Address::from_str(address)
.map_err(|e| {
tonic::Status::invalid_argument(format!(
"could not parse bitcoin address: {}",
e
))
})
.map(|addr| (addr.assume_checked(), *amount))
})
.collect::<Result<HashMap<bdk_wallet::bitcoin::Address, u64>, tonic::Status>>()?;

let txid = self
.send_wallet_transaction(
destinations_validated,
fee_rate_per_vbyte,
fee,
op_return_message,
)
.await
.map_err(|err| err.into_status())?;

let txid = ReverseHex::encode(&txid);
let response = SendTransactionResponse { txid: Some(txid) };
Ok(tonic::Response::new(response))
}
}

#[derive(Debug, Error)]
Expand Down
90 changes: 88 additions & 2 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use bitcoin::{
OP_0,
},
transaction::Version as TxVersion,
Amount, Block, Network, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
Amount, Block, FeeRate, Network, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use miette::{miette, IntoDiagnostic, Result};
use parking_lot::{Mutex, RwLock};
Expand All @@ -50,7 +50,7 @@ use crate::{
cli::WalletConfig,
convert,
messages::{self, CoinbaseBuilder, M8_BMM_REQUEST_TAG},
proto::mainchain::WalletTransaction,
proto::common::Hex,
types::{BDKWalletTransaction, Ctip, SidechainAck, SidechainNumber, SidechainProposal},
validator::Validator,
};
Expand Down Expand Up @@ -706,6 +706,17 @@ impl Wallet {
}
}

fn create_op_return_output(message: Hex) -> bdk_wallet::bitcoin::TxOut {
let op_return_txout = messages::create_op_return_output(message);

bdk_wallet::bitcoin::TxOut {
script_pubkey: bdk_wallet::bitcoin::ScriptBuf::from_bytes(
op_return_txout.script_pubkey.to_bytes(),
),
value: op_return_txout.value,
}
}

async fn fetch_transaction(&self, txid: Txid) -> Result<bdk_wallet::bitcoin::Transaction> {
let block_hash = None;

Expand Down Expand Up @@ -1051,6 +1062,81 @@ impl Wallet {
Ok(txs)
}

async fn create_send_psbt(
&self,
destinations: HashMap<bitcoin::Address, u64>,
fee_rate_per_vbyte: Option<Amount>,
fee: Option<Amount>,
op_return_output: Option<bdk_wallet::bitcoin::TxOut>,
) -> Result<bdk_wallet::bitcoin::psbt::Psbt> {
let psbt = {
let mut wallet = self.bitcoin_wallet.lock();
let mut builder = wallet.borrow_mut().build_tx();

if let Some(op_return_output) = op_return_output {
builder.add_recipient(op_return_output.script_pubkey, op_return_output.value);
}

// Add outputs for each destination address
for (address, value) in destinations {
builder.add_recipient(address.script_pubkey(), Amount::from_sat(value));
}

if let Some(fee) = fee {
builder.fee_absolute(fee);
}

if let Some(rate) = fee_rate_per_vbyte {
let fee_rate = bitcoin::FeeRate::from_sat_per_vb(rate.to_sat() as u64)
.expect("could not create fee rate");
builder.fee_rate(fee_rate);
}

builder.finish().into_diagnostic()?
};

Ok(psbt)
}

/// Creates a transaction, sends it, and returns the TXID.
pub async fn send_wallet_transaction(
&self,
destinations: HashMap<bdk_wallet::bitcoin::Address, u64>,
fee_rate_sats_per_vbyte: Option<Amount>,
fee_rate_sats: Option<Amount>,
op_return_message: Option<Hex>,
) -> Result<bitcoin::Txid> {
let op_return_output =
op_return_message.map(|message| Self::create_op_return_output(message));

let psbt = self
.create_send_psbt(
destinations,
fee_rate_sats_per_vbyte,
fee_rate_sats,
op_return_output,
)
.await?;

tracing::debug!("Created send PSBT: {psbt}",);

let tx = self.sign_transaction(psbt)?;
let txid = tx.compute_txid();

tracing::info!("Signed send transaction: `{txid}`",);

tracing::debug!("Serialized send transaction: {}", {
let tx_bytes = bdk_wallet::bitcoin::consensus::serialize(&tx);
hex::encode(tx_bytes)
});

self.broadcast_transaction(tx).await?;

tracing::info!("Broadcasted send transaction: `{txid}`",);

Ok(convert::bdk_txid_to_bitcoin_txid(txid))
}

pub fn sync(&self) -> Result<()> {
let start = SystemTime::now();
tracing::trace!("starting wallet sync");
Expand Down

0 comments on commit f3ff469

Please sign in to comment.