Skip to content

Commit 5718e43

Browse files
committed
Adds Transaction to lighting-block-sync::convert
Includes disclaimer in docs, see #1061 (comment)
1 parent 801d6e5 commit 5718e43

File tree

1 file changed

+165
-4
lines changed

1 file changed

+165
-4
lines changed

lightning-block-sync/src/convert.rs

+165-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use crate::{BlockHeaderData, BlockSourceError};
21
use crate::http::{BinaryResponse, JsonResponse};
32
use crate::utils::hex_to_uint256;
3+
use crate::{BlockHeaderData, BlockSourceError};
44

55
use bitcoin::blockdata::block::{Block, BlockHeader};
66
use bitcoin::consensus::encode;
77
use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
8-
use bitcoin::hashes::hex::{ToHex, FromHex};
8+
use bitcoin::hashes::hex::{FromHex, ToHex};
9+
use bitcoin::Transaction;
910

1011
use serde::Deserialize;
1112

@@ -104,7 +105,6 @@ impl TryFrom<GetHeaderResponse> for BlockHeaderData {
104105
}
105106
}
106107

107-
108108
/// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
109109
impl TryInto<Block> for JsonResponse {
110110
type Error = std::io::Error;
@@ -181,13 +181,77 @@ impl TryInto<Txid> for JsonResponse {
181181
}
182182
}
183183

184+
/// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
185+
/// (e.g. createrawtransaction). See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197
186+
impl TryInto<Transaction> for JsonResponse {
187+
type Error = std::io::Error;
188+
fn try_into(self) -> std::io::Result<Transaction> {
189+
let hex_tx = if self.0.is_object() {
190+
// result is json encoded
191+
match &self.0["hex"] {
192+
// result has hex field
193+
serde_json::Value::String(hex_data) => match self.0["complete"] {
194+
// result may or may not be signed (e.g. signrawtransactionwithwallet)
195+
serde_json::Value::Bool(x) => {
196+
if x == false {
197+
let reason = match &self.0["errors"][0]["error"] {
198+
serde_json::Value::String(x) => x.as_str(),
199+
_ => "Unknown error",
200+
};
201+
202+
return Err(std::io::Error::new(
203+
std::io::ErrorKind::InvalidData,
204+
format!("transaction couldn't be signed. {}", reason),
205+
));
206+
} else {
207+
hex_data
208+
}
209+
}
210+
// result is a complete transaction (e.g. getrawtranaction verbose)
211+
_ => hex_data,
212+
},
213+
_ => return Err(std::io::Error::new(
214+
std::io::ErrorKind::InvalidData,
215+
"expected JSON string",
216+
)),
217+
}
218+
} else {
219+
// result is plain text (e.g. getrawtransaction no verbose)
220+
match self.0.as_str() {
221+
Some(hex_tx) => hex_tx,
222+
None => {
223+
return Err(std::io::Error::new(
224+
std::io::ErrorKind::InvalidData,
225+
"expected JSON string",
226+
))
227+
}
228+
}
229+
};
230+
231+
match Vec::<u8>::from_hex(hex_tx) {
232+
Err(_) => Err(std::io::Error::new(
233+
std::io::ErrorKind::InvalidData,
234+
"invalid hex data",
235+
)),
236+
Ok(tx_data) => match encode::deserialize(&tx_data) {
237+
Err(_) => Err(std::io::Error::new(
238+
std::io::ErrorKind::InvalidData,
239+
"invalid transaction",
240+
)),
241+
Ok(tx) => Ok(tx),
242+
},
243+
}
244+
}
245+
}
246+
184247
#[cfg(test)]
185248
pub(crate) mod tests {
186249
use super::*;
187250
use bitcoin::blockdata::constants::genesis_block;
188-
use bitcoin::consensus::encode;
189251
use bitcoin::hashes::Hash;
190252
use bitcoin::network::constants::Network;
253+
use serde_json::value::Number;
254+
use serde_json::Value;
191255

192256
/// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
193257
impl From<BlockHeaderData> for serde_json::Value {
@@ -541,4 +605,101 @@ pub(crate) mod tests {
541605
Ok(txid) => assert_eq!(txid, target_txid),
542606
}
543607
}
608+
609+
// TryInto<Transaction> can be used in two ways, first with plain hex response where data is
610+
// the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
611+
// where the hex encoded transaction can be found in the hex field of the object (if present)
612+
// (e.g. as a result of signrawtransactionwithwallet).
613+
614+
// plain hex transaction
615+
616+
#[test]
617+
fn into_tx_from_json_response_with_invalid_hex_data() {
618+
let response = JsonResponse(serde_json::json!("foobar"));
619+
match TryInto::<Transaction>::try_into(response) {
620+
Err(e) => {
621+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
622+
assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
623+
}
624+
Ok(_) => panic!("Expected error"),
625+
}
626+
}
627+
628+
#[test]
629+
fn into_tx_from_json_response_with_invalid_data_type() {
630+
let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap()));
631+
match TryInto::<Transaction>::try_into(response) {
632+
Err(e) => {
633+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
634+
assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
635+
}
636+
Ok(_) => panic!("Expected error"),
637+
}
638+
}
639+
640+
#[test]
641+
fn into_tx_from_json_response_with_invalid_tx_data() {
642+
let response = JsonResponse(serde_json::json!("abcd"));
643+
match TryInto::<Transaction>::try_into(response) {
644+
Err(e) => {
645+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
646+
assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction");
647+
}
648+
Ok(_) => panic!("Expected error"),
649+
}
650+
}
651+
652+
#[test]
653+
fn into_tx_from_json_response_with_valid_tx_data_plain() {
654+
let genesis_block = genesis_block(Network::Bitcoin);
655+
let target_tx = genesis_block.txdata.get(0).unwrap();
656+
let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx)));
657+
match TryInto::<Transaction>::try_into(response) {
658+
Err(e) => panic!("Unexpected error: {:?}", e),
659+
Ok(tx) => assert_eq!(&tx, target_tx),
660+
}
661+
}
662+
663+
#[test]
664+
fn into_tx_from_json_response_with_valid_tx_data_hex_field() {
665+
let genesis_block = genesis_block(Network::Bitcoin);
666+
let target_tx = genesis_block.txdata.get(0).unwrap();
667+
let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)}));
668+
match TryInto::<Transaction>::try_into(response) {
669+
Err(e) => panic!("Unexpected error: {:?}", e),
670+
Ok(tx) => assert_eq!(&tx, target_tx),
671+
}
672+
}
673+
674+
// transaction in hex field of JSON object
675+
676+
#[test]
677+
fn into_tx_from_json_response_with_no_hex_field() {
678+
let response = JsonResponse(serde_json::json!({ "error": "foo" }));
679+
match TryInto::<Transaction>::try_into(response) {
680+
Err(e) => {
681+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
682+
assert_eq!(
683+
e.get_ref().unwrap().to_string(),
684+
"expected JSON string"
685+
);
686+
}
687+
Ok(_) => panic!("Expected error"),
688+
}
689+
}
690+
691+
#[test]
692+
fn into_tx_from_json_response_not_signed() {
693+
let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false }));
694+
match TryInto::<Transaction>::try_into(response) {
695+
Err(e) => {
696+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
697+
assert!(
698+
e.get_ref().unwrap().to_string().contains(
699+
"transaction couldn't be signed")
700+
);
701+
}
702+
Ok(_) => panic!("Expected error"),
703+
}
704+
}
544705
}

0 commit comments

Comments
 (0)