Skip to content

Commit 3108313

Browse files
committed
Merge #38: Extract commands execution logic into an actions module and delegate from command handlers
0ca9f6e delegate command execution to actions module (ivanlele) Pull request description: One of the PRs in a series addressing issue #28. This PR moves the command execution logic into a separate actions module, with command handlers now delegating to it. I also tidied up a few lines where it seemed right ACKs for top commit: apoelstra: ACK 0ca9f6e; successfully ran local tests; awesome. Thanks for moving this into its own PR :P Tree-SHA512: c6c51eaa95734d2e3e905e6b6835679274ff0fca0176b9ec8ed251abfe4915d5b68b1a033e9ca1739d95a85fd34aba1d90ae7159c448afa62aa6c9cf3c6c0ddd
2 parents 147f98e + 0ca9f6e commit 3108313

28 files changed

Lines changed: 2051 additions & 1991 deletions

src/actions/address.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use elements::bitcoin::{secp256k1, PublicKey};
2+
use elements::{Address, Script};
3+
4+
use crate::address::{AddressInfo, Addresses};
5+
use crate::Network;
6+
7+
#[derive(Debug, thiserror::Error)]
8+
pub enum AddressError {
9+
#[error("invalid blinder hex: {0}")]
10+
BlinderHex(hex::FromHexError),
11+
12+
#[error("invalid blinder: {0}")]
13+
BlinderInvalid(secp256k1::Error),
14+
15+
#[error("invalid pubkey: {0}")]
16+
PubkeyInvalid(elements::bitcoin::key::ParsePublicKeyError),
17+
18+
#[error("invalid script hex: {0}")]
19+
ScriptHex(hex::FromHexError),
20+
21+
#[error("can't create addresses without a pubkey")]
22+
MissingInput,
23+
24+
#[error("invalid address format: {0}")]
25+
AddressParse(elements::address::AddressError),
26+
27+
#[error("no address provided")]
28+
NoAddressProvided,
29+
30+
#[error("addresses always have params")]
31+
AddressesAlwaysHaveParams,
32+
}
33+
34+
/// Create addresses from a public key or script.
35+
pub fn address_create(
36+
pubkey_hex: Option<&str>,
37+
script_hex: Option<&str>,
38+
blinder_hex: Option<&str>,
39+
network: Network,
40+
) -> Result<Addresses, AddressError> {
41+
let blinder = blinder_hex
42+
.map(|b| {
43+
let bytes = hex::decode(b).map_err(AddressError::BlinderHex)?;
44+
secp256k1::PublicKey::from_slice(&bytes).map_err(AddressError::BlinderInvalid)
45+
})
46+
.transpose()?;
47+
48+
let created = if let Some(pubkey_hex) = pubkey_hex {
49+
let pubkey: PublicKey = pubkey_hex.parse().map_err(AddressError::PubkeyInvalid)?;
50+
Addresses::from_pubkey(&pubkey, blinder, network)
51+
} else if let Some(script_hex) = script_hex {
52+
let script_bytes = hex::decode(script_hex).map_err(AddressError::ScriptHex)?;
53+
let script: Script = script_bytes.into();
54+
Addresses::from_script(&script, blinder, network)
55+
} else {
56+
return Err(AddressError::MissingInput);
57+
};
58+
59+
Ok(created)
60+
}
61+
62+
/// Inspect an address and return detailed information.
63+
pub fn address_inspect(address_str: &str) -> Result<AddressInfo, AddressError> {
64+
let address: Address = address_str.parse().map_err(AddressError::AddressParse)?;
65+
let script_pk = address.script_pubkey();
66+
67+
let mut info = AddressInfo {
68+
network: Network::from_params(address.params)
69+
.ok_or(AddressError::AddressesAlwaysHaveParams)?,
70+
script_pub_key: hal::tx::OutputScriptInfo {
71+
hex: Some(script_pk.to_bytes().into()),
72+
asm: Some(script_pk.asm()),
73+
address: None,
74+
type_: None,
75+
},
76+
type_: None,
77+
pubkey_hash: None,
78+
script_hash: None,
79+
witness_pubkey_hash: None,
80+
witness_script_hash: None,
81+
witness_program_version: None,
82+
blinding_pubkey: address.blinding_pubkey,
83+
unconfidential: if address.blinding_pubkey.is_some() {
84+
Some(Address {
85+
params: address.params,
86+
payload: address.payload.clone(),
87+
blinding_pubkey: None,
88+
})
89+
} else {
90+
None
91+
},
92+
};
93+
94+
use elements::address::Payload;
95+
use elements::hashes::Hash;
96+
use elements::{WPubkeyHash, WScriptHash};
97+
98+
match address.payload {
99+
Payload::PubkeyHash(pkh) => {
100+
info.type_ = Some("p2pkh".to_owned());
101+
info.pubkey_hash = Some(pkh);
102+
}
103+
Payload::ScriptHash(sh) => {
104+
info.type_ = Some("p2sh".to_owned());
105+
info.script_hash = Some(sh);
106+
}
107+
Payload::WitnessProgram {
108+
version,
109+
program,
110+
} => {
111+
let version = version.to_u8() as usize;
112+
info.witness_program_version = Some(version);
113+
114+
if version == 0 {
115+
if program.len() == 20 {
116+
info.type_ = Some("p2wpkh".to_owned());
117+
info.witness_pubkey_hash =
118+
Some(WPubkeyHash::from_slice(&program).expect("size 20"));
119+
} else if program.len() == 32 {
120+
info.type_ = Some("p2wsh".to_owned());
121+
info.witness_script_hash =
122+
Some(WScriptHash::from_slice(&program).expect("size 32"));
123+
} else {
124+
info.type_ = Some("invalid-witness-program".to_owned());
125+
}
126+
} else {
127+
info.type_ = Some("unknown-witness-program-version".to_owned());
128+
}
129+
}
130+
}
131+
132+
Ok(info)
133+
}

src/actions/block.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
use elements::encode::deserialize;
2+
use elements::{dynafed, Block, BlockExtData, BlockHeader};
3+
4+
use crate::block::{BlockHeaderInfo, BlockInfo, ParamsInfo, ParamsType};
5+
use crate::Network;
6+
7+
#[derive(Debug, serde::Serialize)]
8+
#[serde(untagged)]
9+
pub enum BlockDecodeOutput {
10+
Info(BlockInfo),
11+
Header(BlockHeaderInfo),
12+
}
13+
14+
#[derive(Debug, thiserror::Error)]
15+
pub enum BlockError {
16+
#[error("can't provide transactions both in JSON and raw.")]
17+
ConflictingTransactions,
18+
19+
#[error("no transactions provided.")]
20+
NoTransactions,
21+
22+
#[error("failed to deserialize transaction: {0}")]
23+
TransactionDeserialize(super::tx::TxError),
24+
25+
#[error("invalid raw transaction: {0}")]
26+
InvalidRawTransaction(elements::encode::Error),
27+
28+
#[error("invalid block format: {0}")]
29+
BlockDeserialize(elements::encode::Error),
30+
31+
#[error("could not decode raw block hex: {0}")]
32+
CouldNotDecodeRawBlockHex(hex::FromHexError),
33+
34+
#[error("invalid json JSON input: {0}")]
35+
InvalidJsonInput(serde_json::Error),
36+
37+
#[error("{field} missing in {context}")]
38+
MissingField {
39+
field: String,
40+
context: String,
41+
},
42+
}
43+
44+
fn create_params(info: ParamsInfo) -> Result<dynafed::Params, BlockError> {
45+
match info.params_type {
46+
ParamsType::Null => Ok(dynafed::Params::Null),
47+
ParamsType::Compact => Ok(dynafed::Params::Compact {
48+
signblockscript: info
49+
.signblockscript
50+
.ok_or_else(|| BlockError::MissingField {
51+
field: "signblockscript".to_string(),
52+
context: "compact params".to_string(),
53+
})?
54+
.0
55+
.into(),
56+
signblock_witness_limit: info.signblock_witness_limit.ok_or_else(|| {
57+
BlockError::MissingField {
58+
field: "signblock_witness_limit".to_string(),
59+
context: "compact params".to_string(),
60+
}
61+
})?,
62+
elided_root: info.elided_root.ok_or_else(|| BlockError::MissingField {
63+
field: "elided_root".to_string(),
64+
context: "compact params".to_string(),
65+
})?,
66+
}),
67+
ParamsType::Full => Ok(dynafed::Params::Full(dynafed::FullParams::new(
68+
info.signblockscript
69+
.ok_or_else(|| BlockError::MissingField {
70+
field: "signblockscript".to_string(),
71+
context: "full params".to_string(),
72+
})?
73+
.0
74+
.into(),
75+
info.signblock_witness_limit.ok_or_else(|| BlockError::MissingField {
76+
field: "signblock_witness_limit".to_string(),
77+
context: "full params".to_string(),
78+
})?,
79+
info.fedpeg_program
80+
.ok_or_else(|| BlockError::MissingField {
81+
field: "fedpeg_program".to_string(),
82+
context: "full params".to_string(),
83+
})?
84+
.0
85+
.into(),
86+
info.fedpeg_script
87+
.ok_or_else(|| BlockError::MissingField {
88+
field: "fedpeg_script".to_string(),
89+
context: "full params".to_string(),
90+
})?
91+
.0,
92+
info.extension_space
93+
.ok_or_else(|| BlockError::MissingField {
94+
field: "extension space".to_string(),
95+
context: "full params".to_string(),
96+
})?
97+
.into_iter()
98+
.map(|b| b.0)
99+
.collect(),
100+
))),
101+
}
102+
}
103+
104+
fn create_block_header(info: BlockHeaderInfo) -> Result<BlockHeader, BlockError> {
105+
Ok(BlockHeader {
106+
version: info.version,
107+
prev_blockhash: info.previous_block_hash,
108+
merkle_root: info.merkle_root,
109+
time: info.time,
110+
height: info.height,
111+
ext: if info.dynafed {
112+
BlockExtData::Dynafed {
113+
current: create_params(info.dynafed_current.ok_or_else(|| {
114+
BlockError::MissingField {
115+
field: "current".to_string(),
116+
context: "dynafed params".to_string(),
117+
}
118+
})?)?,
119+
proposed: create_params(info.dynafed_proposed.ok_or_else(|| {
120+
BlockError::MissingField {
121+
field: "proposed".to_string(),
122+
context: "dynafed params".to_string(),
123+
}
124+
})?)?,
125+
signblock_witness: info
126+
.dynafed_witness
127+
.ok_or_else(|| BlockError::MissingField {
128+
field: "witness".to_string(),
129+
context: "dynafed params".to_string(),
130+
})?
131+
.into_iter()
132+
.map(|b| b.0)
133+
.collect(),
134+
}
135+
} else {
136+
BlockExtData::Proof {
137+
challenge: info
138+
.legacy_challenge
139+
.ok_or_else(|| BlockError::MissingField {
140+
field: "challenge".to_string(),
141+
context: "proof params".to_string(),
142+
})?
143+
.0
144+
.into(),
145+
solution: info
146+
.legacy_solution
147+
.ok_or_else(|| BlockError::MissingField {
148+
field: "solution".to_string(),
149+
context: "proof params".to_string(),
150+
})?
151+
.0
152+
.into(),
153+
}
154+
},
155+
})
156+
}
157+
158+
/// Create a block from block info.
159+
pub fn block_create(info: BlockInfo) -> Result<Block, BlockError> {
160+
let header = create_block_header(info.header)?;
161+
let txdata = match (info.transactions, info.raw_transactions) {
162+
(Some(_), Some(_)) => return Err(BlockError::ConflictingTransactions),
163+
(None, None) => return Err(BlockError::NoTransactions),
164+
(Some(infos), None) => infos
165+
.into_iter()
166+
.map(super::tx::tx_create)
167+
.collect::<Result<Vec<_>, _>>()
168+
.map_err(BlockError::TransactionDeserialize)?,
169+
(None, Some(raws)) => raws
170+
.into_iter()
171+
.map(|r| deserialize(&r.0).map_err(BlockError::InvalidRawTransaction))
172+
.collect::<Result<Vec<_>, _>>()?,
173+
};
174+
Ok(Block {
175+
header,
176+
txdata,
177+
})
178+
}
179+
180+
/// Decode a raw block and return block info or header info.
181+
pub fn block_decode(
182+
raw_block_hex: &str,
183+
network: Network,
184+
txids_only: bool,
185+
) -> Result<BlockDecodeOutput, BlockError> {
186+
use crate::GetInfo;
187+
188+
let raw_block = hex::decode(raw_block_hex).map_err(BlockError::CouldNotDecodeRawBlockHex)?;
189+
190+
if txids_only {
191+
let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
192+
let info = BlockInfo {
193+
header: block.header.get_info(network),
194+
txids: Some(block.txdata.iter().map(|t| t.txid()).collect()),
195+
transactions: None,
196+
raw_transactions: None,
197+
};
198+
Ok(BlockDecodeOutput::Info(info))
199+
} else {
200+
let header: BlockHeader = match deserialize(&raw_block) {
201+
Ok(header) => header,
202+
Err(_) => {
203+
let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
204+
block.header
205+
}
206+
};
207+
let info = header.get_info(network);
208+
Ok(BlockDecodeOutput::Header(info))
209+
}
210+
}

src/actions/keypair.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use elements::bitcoin::secp256k1::{self, rand};
2+
3+
#[derive(serde::Serialize)]
4+
pub struct KeypairInfo {
5+
pub secret: secp256k1::SecretKey,
6+
pub x_only: secp256k1::XOnlyPublicKey,
7+
pub parity: secp256k1::Parity,
8+
}
9+
10+
/// Generate a random keypair.
11+
pub fn keypair_generate() -> KeypairInfo {
12+
let (secret, public) = secp256k1::generate_keypair(&mut rand::thread_rng());
13+
let (x_only, parity) = public.x_only_public_key();
14+
15+
KeypairInfo {
16+
secret,
17+
x_only,
18+
parity,
19+
}
20+
}

src/actions/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod address;
2+
pub mod block;
3+
pub mod keypair;
4+
pub mod simplicity;
5+
pub mod tx;

0 commit comments

Comments
 (0)