Skip to content

Commit c30f4fb

Browse files
committed
Adds Transaction to lighting-block-sync::convert
1 parent 801d6e5 commit c30f4fb

File tree

1 file changed

+178
-4
lines changed

1 file changed

+178
-4
lines changed

lightning-block-sync/src/convert.rs

Lines changed: 178 additions & 4 deletions
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,86 @@ impl TryInto<Txid> for JsonResponse {
181181
}
182182
}
183183

184+
impl TryInto<Transaction> for JsonResponse {
185+
type Error = std::io::Error;
186+
fn try_into(self) -> std::io::Result<Transaction> {
187+
let hex_tx = if self.0.is_object() {
188+
// result is json encoded
189+
match &self.0["hex"] {
190+
// result has hex field
191+
serde_json::Value::String(hex_data) => match self.0["complete"] {
192+
// result may or may not be signed (e.g. signrawtransactionwithwallet)
193+
serde_json::Value::Bool(x) => {
194+
if x == false {
195+
return Err(std::io::Error::new(
196+
std::io::ErrorKind::InvalidData,
197+
"transaction couldn't be signed",
198+
));
199+
} else {
200+
hex_data
201+
}
202+
}
203+
// result if a complete transaction (e.g. getrawtranaction)
204+
_ => hex_data,
205+
},
206+
207+
// result is an error
208+
_ => match &self.0["error"] {
209+
serde_json::Value::String(error_data) => {
210+
return Err(std::io::Error::new(
211+
std::io::ErrorKind::InvalidData,
212+
format!(
213+
"trying to construct the transaction returned an error: {}",
214+
error_data
215+
),
216+
))
217+
}
218+
_ => {
219+
return Err(std::io::Error::new(
220+
std::io::ErrorKind::InvalidData,
221+
"no hex nor error fields in the provided JSON object",
222+
))
223+
}
224+
},
225+
}
226+
} else {
227+
// result is plain text
228+
match self.0.as_str() {
229+
Some(hex_tx) => hex_tx,
230+
231+
None => {
232+
return Err(std::io::Error::new(
233+
std::io::ErrorKind::InvalidData,
234+
"expected JSON string",
235+
))
236+
}
237+
}
238+
};
239+
240+
match Vec::<u8>::from_hex(hex_tx) {
241+
Err(_) => Err(std::io::Error::new(
242+
std::io::ErrorKind::InvalidData,
243+
"invalid hex data",
244+
)),
245+
Ok(tx_data) => match encode::deserialize(&tx_data) {
246+
Err(_) => Err(std::io::Error::new(
247+
std::io::ErrorKind::InvalidData,
248+
"invalid transaction",
249+
)),
250+
Ok(tx) => Ok(tx),
251+
},
252+
}
253+
}
254+
}
255+
184256
#[cfg(test)]
185257
pub(crate) mod tests {
186258
use super::*;
187259
use bitcoin::blockdata::constants::genesis_block;
188-
use bitcoin::consensus::encode;
189260
use bitcoin::hashes::Hash;
190261
use bitcoin::network::constants::Network;
262+
use serde_json::value::Number;
263+
use serde_json::Value;
191264

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

0 commit comments

Comments
 (0)