Skip to content

Commit dc7fa85

Browse files
committed
multi: implement create_deposit_transaction
1 parent a69646e commit dc7fa85

File tree

9 files changed

+294
-60
lines changed

9 files changed

+294
-60
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
password=password
99
signetblocktime=60
1010
signetchallenge=00141f61d57873d70d28bd28b3c9f9d6bf818b5a0d6a
11+
acceptnonstdtxn=1 # Important! Otherwise Core rejects OP_DRIVECHAIN TXs
1112
1213
# this can also be set to a different address, as long
1314
# as you set the CLI arg for bip300301_enforcer
@@ -35,7 +36,7 @@ $ cargo run -- --help
3536
# Adjust these parameters to match your local Bitcoin
3637
# Core instance
3738
$ cargo run -- \
38-
--node-rpc-port=38332 \
39+
--node-rpc-addr-=localhost:38332 \
3940
--node-rpc-user=user \
4041
--node-rpc-pass=password \
4142
--node-zmq-addr-sequence=tcp://0.0.0.0:29000

src/convert.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
pub fn bdk_block_hash_to_bitcoin_block_hash(hash: bdk::bitcoin::BlockHash) -> bitcoin::BlockHash {
2+
use bdk::bitcoin::hashes::Hash as _;
3+
let bytes = hash.as_byte_array().to_vec();
4+
5+
use bitcoin::hashes::sha256d::Hash;
6+
use bitcoin::hashes::Hash as Hm;
7+
let hash: bitcoin::hashes::sha256d::Hash = Hash::from_slice(&bytes).unwrap();
8+
9+
bitcoin::BlockHash::from_raw_hash(hash)
10+
}
11+
12+
pub fn bdk_txid_to_bitcoin_txid(hash: bdk::bitcoin::Txid) -> bitcoin::Txid {
13+
use bdk::bitcoin::hashes::Hash as _;
14+
let bytes = hash.as_byte_array().to_vec();
15+
16+
use bitcoin::hashes::sha256d::Hash;
17+
use bitcoin::hashes::Hash as _;
18+
let hash: bitcoin::hashes::sha256d::Hash = Hash::from_slice(&bytes).unwrap();
19+
20+
bitcoin::Txid::from_raw_hash(hash)
21+
}
22+
23+
pub fn bitcoin_txid_to_bdk_txid(hash: bitcoin::Txid) -> bdk::bitcoin::Txid {
24+
use bitcoin::hashes::Hash as _;
25+
let bytes = hash.as_byte_array().to_vec();
26+
27+
use bdk::bitcoin::hashes::sha256d::Hash;
28+
use bdk::bitcoin::hashes::Hash as _;
29+
let hash: bdk::bitcoin::hashes::sha256d::Hash = Hash::from_slice(&bytes).unwrap();
30+
31+
bdk::bitcoin::Txid::from_raw_hash(hash)
32+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use tower_http::trace::{DefaultOnFailure, DefaultOnResponse, TraceLayer};
1010
use tracing_subscriber::{filter as tracing_filter, layer::SubscriberExt};
1111

1212
mod cli;
13+
mod convert;
1314
mod messages;
1415
mod proto;
1516
mod rpc_client;

src/messages.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use bitcoin::script::{Instruction, Instructions};
12
use bitcoin::{
23
hashes::Hash,
34
opcodes::{
45
all::{OP_NOP5, OP_PUSHBYTES_1, OP_RETURN},
56
OP_TRUE,
67
},
7-
script::{Instruction, Instructions, PushBytesBuf},
8+
script::PushBytesBuf,
89
Amount, Opcode, Script, ScriptBuf, Transaction, TxOut,
910
};
1011
use byteorder::{ByteOrder, LittleEndian};
@@ -218,6 +219,24 @@ pub fn parse_op_drivechain(input: &[u8]) -> IResult<&[u8], u8> {
218219
Ok((input, sidechain_number))
219220
}
220221

222+
pub fn create_m5_deposit_output(
223+
sidechain_number: SidechainNumber,
224+
old_ctip_amount: Amount,
225+
amount: Amount,
226+
) -> TxOut {
227+
let script_pubkey = ScriptBuf::from_bytes(vec![
228+
OP_DRIVECHAIN.to_u8(),
229+
OP_PUSHBYTES_1.to_u8(),
230+
sidechain_number.into(),
231+
OP_TRUE.to_u8(),
232+
]);
233+
TxOut {
234+
script_pubkey,
235+
// All deposits INCREASE the amount locked in the OP_DRIVECHAIN output.
236+
value: old_ctip_amount + amount,
237+
}
238+
}
239+
221240
fn parse_m1_propose_sidechain(input: &[u8]) -> IResult<&[u8], CoinbaseMessage> {
222241
let (input, sidechain_number) = take(1usize)(input)?;
223242
let sidechain_number = sidechain_number[0];

src/server.rs

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ use futures::{stream::BoxStream, StreamExt, TryStreamExt as _};
3434
use miette::{IntoDiagnostic, Result};
3535
use tonic::{Request, Response, Status};
3636

37-
use crate::types;
3837
pub use crate::validator::Validator;
38+
use crate::{convert, types};
3939

4040
fn invalid_field_value<Message>(field_name: &str, value: &str) -> tonic::Status
4141
where
@@ -53,26 +53,6 @@ where
5353
tonic::Status::invalid_argument(err.to_string())
5454
}
5555

56-
fn bdk_block_hash_to_bitcoin_block_hash(hash: bdk::bitcoin::BlockHash) -> bitcoin::BlockHash {
57-
let bytes = hash.as_byte_array().to_vec();
58-
59-
use bitcoin::hashes::sha256d::Hash;
60-
use bitcoin::hashes::Hash as Hm;
61-
let hash: bitcoin::hashes::sha256d::Hash = Hash::from_slice(&bytes).unwrap();
62-
63-
bitcoin::BlockHash::from_raw_hash(hash)
64-
}
65-
66-
fn bdk_txid_to_bitcoin_txid(hash: bdk::bitcoin::Txid) -> bitcoin::Txid {
67-
let bytes = hash.as_byte_array().to_vec();
68-
69-
use bitcoin::hashes::sha256d::Hash;
70-
use bitcoin::hashes::Hash as Hm;
71-
let hash: bitcoin::hashes::sha256d::Hash = Hash::from_slice(&bytes).unwrap();
72-
73-
bitcoin::Txid::from_raw_hash(hash)
74-
}
75-
7656
trait IntoStatus {
7757
fn into_status(self) -> tonic::Status;
7858
}
@@ -417,16 +397,16 @@ impl ValidatorService for Validator {
417397
})
418398
.transpose()?
419399
.map(|bytes| {
420-
bdk_block_hash_to_bitcoin_block_hash(bdk::bitcoin::BlockHash::from_byte_array(
421-
bytes,
422-
))
400+
convert::bdk_block_hash_to_bitcoin_block_hash(
401+
bdk::bitcoin::BlockHash::from_byte_array(bytes),
402+
)
423403
});
424404

425405
let end_block_hash: BlockHash = end_block_hash
426406
.ok_or_else(|| missing_field::<GetTwoWayPegDataRequest>("end_block_hash"))?
427407
.decode_tonic::<GetTwoWayPegDataRequest, _>("end_block_hash")
428408
.map(bdk::bitcoin::BlockHash::from_byte_array)
429-
.map(bdk_block_hash_to_bitcoin_block_hash)?;
409+
.map(convert::bdk_block_hash_to_bitcoin_block_hash)?;
430410

431411
match self.get_two_way_peg_data(start_block_hash, end_block_hash) {
432412
Err(err) => Err(tonic::Status::from_error(Box::new(err))),
@@ -639,7 +619,7 @@ impl WalletService for Arc<crate::wallet::Wallet> {
639619

640620
// If the mainchain tip has progressed beyond this, the request is already
641621
// expired.
642-
if mainchain_tip != bdk_block_hash_to_bitcoin_block_hash(prev_bytes) {
622+
if mainchain_tip != convert::bdk_block_hash_to_bitcoin_block_hash(prev_bytes) {
643623
let message = format!(
644624
"invalid prev_bytes {}: expected {}",
645625
prev_bytes, mainchain_tip
@@ -667,7 +647,7 @@ impl WalletService for Arc<crate::wallet::Wallet> {
667647
.await
668648
.map_err(|err| err.into_status())?;
669649

670-
let txid = bdk_txid_to_bitcoin_txid(txid);
650+
let txid = convert::bdk_txid_to_bitcoin_txid(txid);
671651

672652
let response = CreateBmmCriticalDataTransactionResponse {
673653
txid: Some(ConsensusHex::encode(&txid)),
@@ -677,11 +657,65 @@ impl WalletService for Arc<crate::wallet::Wallet> {
677657

678658
async fn create_deposit_transaction(
679659
&self,
680-
_request: tonic::Request<CreateDepositTransactionRequest>,
660+
request: tonic::Request<CreateDepositTransactionRequest>,
681661
) -> std::result::Result<tonic::Response<CreateDepositTransactionResponse>, tonic::Status> {
682-
Err(tonic::Status::new(
683-
tonic::Code::Unimplemented,
684-
"not implemented",
685-
))
662+
let CreateDepositTransactionRequest {
663+
sidechain_id,
664+
address,
665+
value_sats,
666+
fee_sats,
667+
} = request.into_inner();
668+
669+
let value_sats = Amount::from_sat(value_sats);
670+
let fee_sats = match fee_sats {
671+
0 => None,
672+
fee => Some(Amount::from_sat(fee)),
673+
};
674+
675+
let sidechain_id: SidechainNumber = {
676+
<u8 as TryFrom<_>>::try_from(sidechain_id)
677+
.map_err(|_| {
678+
invalid_field_value::<CreateDepositTransactionRequest>(
679+
"sidechain_id",
680+
&sidechain_id.to_string(),
681+
)
682+
})?
683+
.into()
684+
};
685+
686+
if value_sats.to_sat() == 0 {
687+
return Err(invalid_field_value::<CreateDepositTransactionRequest>(
688+
"value_sats",
689+
&value_sats.to_string(),
690+
));
691+
}
692+
693+
if address.is_empty() {
694+
return Err(invalid_field_value::<CreateDepositTransactionRequest>(
695+
"address", &address,
696+
));
697+
}
698+
699+
if !self
700+
.is_sidechain_active(sidechain_id)
701+
.await
702+
.map_err(|err| err.into_status())?
703+
{
704+
return Err(tonic::Status::new(
705+
tonic::Code::FailedPrecondition,
706+
format!("sidechain {sidechain_id} is not active"),
707+
));
708+
}
709+
710+
let txid = self
711+
.create_deposit(sidechain_id, address, value_sats, fee_sats)
712+
.await
713+
.map_err(|err| err.into_status())?;
714+
715+
let txid = convert::bdk_txid_to_bitcoin_txid(txid);
716+
717+
let txid = ConsensusHex::encode(&txid);
718+
let response = CreateDepositTransactionResponse { txid: Some(txid) };
719+
Ok(tonic::Response::new(response))
686720
}
687721
}

src/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::fmt;
2+
use std::fmt::Display;
13
use std::num::TryFromIntError;
24

35
use bitcoin::{Amount, BlockHash, OutPoint, TxOut, Work};
@@ -14,6 +16,12 @@ pub type Hash256 = [u8; 32];
1416
#[serde(transparent)]
1517
pub struct SidechainNumber(pub u8);
1618

19+
impl Display for SidechainNumber {
20+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21+
write!(f, "{}", self.0)
22+
}
23+
}
24+
1725
impl From<u8> for SidechainNumber {
1826
#[inline(always)]
1927
fn from(sidechain_number: u8) -> Self {

src/validator/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ impl Validator {
168168
Ok(sequence_number)
169169
}
170170

171+
/// Returns `Some` with the Ctip for the given sidechain number. `None`
172+
/// if there's no Ctip for the given sidechain number.
171173
pub fn get_ctip(
172174
&self,
173175
sidechain_number: SidechainNumber,

src/wallet/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ pub fn convert_bdk_error(err: bdk::Error) -> miette::Report {
103103
Err(json_deserialize_err) => miette::Report::new(json_deserialize_err),
104104
}
105105
}
106+
107+
#[derive(Debug, Diagnostic, Error)]
108+
#[error("BDK wallet error: {0}")]
109+
#[diagnostic(code(bdk_wallet_error))]
110+
pub struct WalletError(#[from] pub bdk::Error);

0 commit comments

Comments
 (0)