Merge pull request #333 from ethcore/rpc_raw_continue
`createrawtransaction` method completed
This commit is contained in:
commit
e3c1399b9e
|
@ -20,3 +20,6 @@ pub const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;
|
|||
/// Threshold for `nLockTime`: below this value it is interpreted as block number,
|
||||
/// otherwise as UNIX timestamp.
|
||||
pub const LOCKTIME_THRESHOLD: u32 = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
/// Number of Satoshis in single coin
|
||||
pub const SATOSHIS_IN_COIN: u64 = 100_000_000;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use v1::traits::Raw;
|
||||
use v1::types::{RawTransaction, TransactionInput, TransactionOutputs, Transaction, GetRawTransactionResponse};
|
||||
use v1::types::H256;
|
||||
use v1::helpers::errors::{execution, invalid_params};
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use ser::{Reader, serialize, deserialize};
|
||||
use v1::traits::Raw;
|
||||
use v1::types::{RawTransaction, TransactionInput, TransactionOutput, TransactionOutputs, Transaction, GetRawTransactionResponse};
|
||||
use v1::types::H256;
|
||||
use v1::helpers::errors::{execution, invalid_params};
|
||||
use chain::Transaction as GlobalTransaction;
|
||||
use sync;
|
||||
use ser::{Reader, deserialize};
|
||||
use primitives::bytes::Bytes as GlobalBytes;
|
||||
use primitives::hash::H256 as GlobalH256;
|
||||
use sync;
|
||||
|
||||
pub struct RawClient<T: RawClientCoreApi> {
|
||||
core: T,
|
||||
|
@ -15,6 +16,7 @@ pub struct RawClient<T: RawClientCoreApi> {
|
|||
|
||||
pub trait RawClientCoreApi: Send + Sync + 'static {
|
||||
fn accept_transaction(&self, transaction: GlobalTransaction) -> Result<GlobalH256, String>;
|
||||
fn create_raw_transaction(&self, inputs: Vec<TransactionInput>, outputs: TransactionOutputs, lock_time: Trailing<u32>) -> Result<GlobalTransaction, String>;
|
||||
}
|
||||
|
||||
pub struct RawClientCore {
|
||||
|
@ -27,12 +29,74 @@ impl RawClientCore {
|
|||
local_sync_node: local_sync_node,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_create_raw_transaction(inputs: Vec<TransactionInput>, outputs: TransactionOutputs, lock_time: Trailing<u32>) -> Result<GlobalTransaction, String> {
|
||||
use chain;
|
||||
use keys;
|
||||
use global_script::Builder as ScriptBuilder;
|
||||
|
||||
// to make lock_time work at least one input must have sequnce < SEQUENCE_FINAL
|
||||
let lock_time = lock_time.0;
|
||||
let default_sequence = if lock_time != 0 { chain::constants::SEQUENCE_FINAL - 1 } else { chain::constants::SEQUENCE_FINAL };
|
||||
|
||||
// prepare inputs
|
||||
let inputs: Vec<_> = inputs.into_iter()
|
||||
.map(|input| chain::TransactionInput {
|
||||
previous_output: chain::OutPoint {
|
||||
hash: Into::<GlobalH256>::into(input.txid).reversed(),
|
||||
index: input.vout,
|
||||
},
|
||||
script_sig: GlobalBytes::new(), // default script
|
||||
sequence: input.sequence.unwrap_or(default_sequence),
|
||||
}).collect();
|
||||
|
||||
// prepare outputs
|
||||
let outputs: Vec<_> = outputs.outputs.into_iter()
|
||||
.map(|output| match output {
|
||||
TransactionOutput::Address(with_address) => {
|
||||
let amount_in_satoshis = (with_address.amount * (chain::constants::SATOSHIS_IN_COIN as f64)) as u64;
|
||||
let script = match (*with_address.address).kind {
|
||||
keys::Type::P2PKH => ScriptBuilder::build_p2pkh(&(*with_address.address).hash),
|
||||
keys::Type::P2SH => ScriptBuilder::build_p2sh(&(*with_address.address).hash),
|
||||
};
|
||||
|
||||
chain::TransactionOutput {
|
||||
value: amount_in_satoshis,
|
||||
script_pubkey: script.to_bytes(),
|
||||
}
|
||||
},
|
||||
TransactionOutput::ScriptData(with_script_data) => {
|
||||
let script = ScriptBuilder::default()
|
||||
.return_bytes(&*with_script_data.script_data)
|
||||
.into_script();
|
||||
|
||||
chain::TransactionOutput {
|
||||
value: 0,
|
||||
script_pubkey: script.to_bytes(),
|
||||
}
|
||||
},
|
||||
}).collect();
|
||||
|
||||
// now construct && serialize transaction
|
||||
let transaction = GlobalTransaction {
|
||||
version: 1,
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
lock_time: lock_time,
|
||||
};
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawClientCoreApi for RawClientCore {
|
||||
fn accept_transaction(&self, transaction: GlobalTransaction) -> Result<GlobalH256, String> {
|
||||
self.local_sync_node.accept_transaction(transaction)
|
||||
}
|
||||
|
||||
fn create_raw_transaction(&self, inputs: Vec<TransactionInput>, outputs: TransactionOutputs, lock_time: Trailing<u32>) -> Result<GlobalTransaction, String> {
|
||||
RawClientCore::do_create_raw_transaction(inputs, outputs, lock_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RawClient<T> where T: RawClientCoreApi {
|
||||
|
@ -52,8 +116,17 @@ impl<T> Raw for RawClient<T> where T: RawClientCoreApi {
|
|||
.map_err(|e| execution(e))
|
||||
}
|
||||
|
||||
fn create_raw_transaction(&self, _inputs: Vec<TransactionInput>, _outputs: TransactionOutputs, _lock_time: Trailing<u32>) -> Result<RawTransaction, Error> {
|
||||
rpc_unimplemented!()
|
||||
fn create_raw_transaction(&self, inputs: Vec<TransactionInput>, outputs: TransactionOutputs, lock_time: Trailing<u32>) -> Result<RawTransaction, Error> {
|
||||
// reverse hashes of inputs
|
||||
let inputs: Vec<_> = inputs.into_iter()
|
||||
.map(|mut input| {
|
||||
input.txid = input.txid.reversed();
|
||||
input
|
||||
}).collect();
|
||||
|
||||
let transaction = try!(self.core.create_raw_transaction(inputs, outputs, lock_time).map_err(|e| execution(e)));
|
||||
let transaction = serialize(&transaction);
|
||||
Ok(transaction.into())
|
||||
}
|
||||
|
||||
fn decode_raw_transaction(&self, _transaction: RawTransaction) -> Result<Transaction, Error> {
|
||||
|
@ -67,10 +140,12 @@ impl<T> Raw for RawClient<T> where T: RawClientCoreApi {
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use jsonrpc_macros::Trailing;
|
||||
use jsonrpc_core::{IoHandler, GenericIoHandler};
|
||||
use chain::Transaction;
|
||||
use primitives::hash::H256 as GlobalH256;
|
||||
use v1::traits::Raw;
|
||||
use v1::types::{TransactionInput, TransactionOutputs};
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -82,12 +157,20 @@ pub mod tests {
|
|||
fn accept_transaction(&self, transaction: Transaction) -> Result<GlobalH256, String> {
|
||||
Ok(transaction.hash())
|
||||
}
|
||||
|
||||
fn create_raw_transaction(&self, _inputs: Vec<TransactionInput>, _outputs: TransactionOutputs, _lock_time: Trailing<u32>) -> Result<Transaction, String> {
|
||||
Ok("0100000001ad9d38823d95f31dc6c0cb0724c11a3cf5a466ca4147254a10cd94aade6eb5b3230000006b483045022100b7683165c3ecd57b0c44bf6a0fb258dc08c328458321c8fadc2b9348d4e66bd502204fd164c58d1a949a4d39bb380f8f05c9f6b3e9417f06bf72e5c068428ca3578601210391c35ac5ee7cf82c5015229dcff89507f83f9b8c952b8fecfa469066c1cb44ccffffffff0170f30500000000001976a914801da3cb2ed9e44540f4b982bde07cd3fbae264288ac00000000".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl RawClientCoreApi for ErrorRawClientCore {
|
||||
fn accept_transaction(&self, _transaction: Transaction) -> Result<GlobalH256, String> {
|
||||
Err("error".to_owned())
|
||||
}
|
||||
|
||||
fn create_raw_transaction(&self, _inputs: Vec<TransactionInput>, _outputs: TransactionOutputs, _lock_time: Trailing<u32>) -> Result<Transaction, String> {
|
||||
Err("error".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -127,4 +210,66 @@ pub mod tests {
|
|||
|
||||
assert_eq!(r#"{"jsonrpc":"2.0","error":{"code":-32015,"message":"Execution error.","data":"\"error\""},"id":1}"#, &sample);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn createrawtransaction_contents() {
|
||||
use chain;
|
||||
use primitives::bytes::Bytes as GlobalBytes;
|
||||
use v1::types::{TransactionInput, TransactionOutput, TransactionOutputs, TransactionOutputWithAddress};
|
||||
|
||||
// https://webbtc.com/tx/4dbbc65cf8eff9a04752bf493232e0b82488308f72f2afb497f36bbddada500c
|
||||
let mut original_transaction: chain::Transaction = "0100000001ad9d38823d95f31dc6c0cb0724c11a3cf5a466ca4147254a10cd94aade6eb5b3230000006b483045022100b7683165c3ecd57b0c44bf6a0fb258dc08c328458321c8fadc2b9348d4e66bd502204fd164c58d1a949a4d39bb380f8f05c9f6b3e9417f06bf72e5c068428ca3578601210391c35ac5ee7cf82c5015229dcff89507f83f9b8c952b8fecfa469066c1cb44ccffffffff0170f30500000000001976a914801da3cb2ed9e44540f4b982bde07cd3fbae264288ac00000000".into();
|
||||
// since createrawtransction creates unsigned transaction:
|
||||
original_transaction.inputs[0].script_sig = GlobalBytes::new();
|
||||
|
||||
let inputs = vec![TransactionInput {
|
||||
txid: "b3b56edeaa94cd104a254741ca66a4f53c1ac12407cbc0c61df3953d82389dad".into(),
|
||||
vout: 35,
|
||||
sequence: None,
|
||||
}];
|
||||
let outputs = TransactionOutputs {
|
||||
outputs: vec![TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: "1CgQzxyMMrtoDEBJPtFUkZ5zHcZiDSFtC8".into(),
|
||||
amount: 0.00390000,
|
||||
})]
|
||||
};
|
||||
let raw_transaction = RawClientCore::do_create_raw_transaction(inputs, outputs, Trailing(0)).unwrap();
|
||||
assert_eq!(raw_transaction, original_transaction);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn createrawtransaction_success() {
|
||||
let client = RawClient::new(SuccessRawClientCore::default());
|
||||
let handler = IoHandler::new();
|
||||
handler.add_delegate(client.to_delegate());
|
||||
|
||||
let sample = handler.handle_request_sync(&(r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "createrawtransaction",
|
||||
"params": [[{"txid":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","vout":0}],{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":0.01}],
|
||||
"id": 1
|
||||
}"#)
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(r#"{"jsonrpc":"2.0","result":"0100000001ad9d38823d95f31dc6c0cb0724c11a3cf5a466ca4147254a10cd94aade6eb5b3230000006b483045022100b7683165c3ecd57b0c44bf6a0fb258dc08c328458321c8fadc2b9348d4e66bd502204fd164c58d1a949a4d39bb380f8f05c9f6b3e9417f06bf72e5c068428ca3578601210391c35ac5ee7cf82c5015229dcff89507f83f9b8c952b8fecfa469066c1cb44ccffffffff0170f30500000000001976a914801da3cb2ed9e44540f4b982bde07cd3fbae264288ac00000000","id":1}"#, &sample);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn createrawtransaction_error() {
|
||||
let client = RawClient::new(ErrorRawClientCore::default());
|
||||
let handler = IoHandler::new();
|
||||
handler.add_delegate(client.to_delegate());
|
||||
|
||||
let sample = handler.handle_request_sync(&(r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "createrawtransaction",
|
||||
"params": [[{"txid":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","vout":0}],{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":0.01}],
|
||||
"id": 1
|
||||
}"#)
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(r#"{"jsonrpc":"2.0","error":{"code":-32015,"message":"Execution error.","data":"\"error\""},"id":1}"#, &sample);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::ops;
|
||||
use std::str::FromStr;
|
||||
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
||||
use global_script::ScriptAddress;
|
||||
|
@ -22,6 +23,12 @@ impl Address {
|
|||
kind: address.kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize_from_string<E>(value: &str) -> Result<Address, E> where E: ::serde::de::Error {
|
||||
GlobalAddress::from_str(value)
|
||||
.map_err(|err| E::invalid_value(&format!("error {} parsing address {}", err, value)))
|
||||
.map(|address| Address(address))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Address {
|
||||
|
@ -40,9 +47,7 @@ impl Deserialize for Address {
|
|||
type Value = Address;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Address, E> where E: ::serde::de::Error {
|
||||
GlobalAddress::from_str(value)
|
||||
.map_err(|err| E::invalid_value(&format!("error {} parsing address {}", err, value)))
|
||||
.map(|address| Address(address))
|
||||
Address::deserialize_from_string(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +61,14 @@ impl<T> From<T> for Address where GlobalAddress: From<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Address {
|
||||
type Target = GlobalAddress;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
///! Serializable wrapper around vector of bytes
|
||||
use std::ops;
|
||||
use rustc_serialize::hex::{ToHex, FromHex};
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error};
|
||||
use serde::de::Visitor;
|
||||
|
@ -66,6 +67,14 @@ impl Visitor for BytesVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Bytes {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -23,7 +23,8 @@ pub use self::get_tx_out_set_info_response::GetTxOutSetInfoResponse;
|
|||
pub use self::hash::{H160, H256};
|
||||
pub use self::script::ScriptType;
|
||||
pub use self::transaction::{RawTransaction, Transaction, TransactionInput, TransactionOutput,
|
||||
TransactionInputScript, TransactionOutputScript, SignedTransactionInput, GetRawTransactionResponse,
|
||||
TransactionOutputWithAddress, TransactionOutputWithScriptData, TransactionInputScript,
|
||||
TransactionOutputScript, SignedTransactionInput, GetRawTransactionResponse,
|
||||
SignedTransactionOutput, TransactionOutputs};
|
||||
pub use self::uint::U256;
|
||||
pub use self::nodes::{AddNodeOperation, NodeInfo};
|
||||
|
|
|
@ -19,15 +19,31 @@ pub struct TransactionInput {
|
|||
pub sequence: Option<u32>,
|
||||
}
|
||||
|
||||
/// Transaction output
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TransactionOutput {
|
||||
/// Transaction output of form "address": amount
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TransactionOutputWithAddress {
|
||||
/// Receiver' address
|
||||
pub address: Address,
|
||||
/// Amount in BTC
|
||||
pub amount: f64,
|
||||
}
|
||||
|
||||
/// Trasaction output of form "data": serialized(output script data)
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TransactionOutputWithScriptData {
|
||||
/// Serialized script data
|
||||
pub script_data: Bytes,
|
||||
}
|
||||
|
||||
/// Transaction output
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TransactionOutput {
|
||||
/// Of form address: amount
|
||||
Address(TransactionOutputWithAddress),
|
||||
/// Of form data: script_data_bytes
|
||||
ScriptData(TransactionOutputWithScriptData),
|
||||
}
|
||||
|
||||
/// Transaction outputs, which serializes/deserializes as KV-map
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TransactionOutputs {
|
||||
|
@ -147,8 +163,16 @@ impl Serialize for TransactionOutputs {
|
|||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
||||
let mut state = try!(serializer.serialize_map(Some(self.len())));
|
||||
for output in &self.outputs {
|
||||
try!(serializer.serialize_map_key(&mut state, &output.address));
|
||||
try!(serializer.serialize_map_value(&mut state, &output.amount));
|
||||
match output {
|
||||
&TransactionOutput::Address(ref address_output) => {
|
||||
try!(serializer.serialize_map_key(&mut state, &address_output.address));
|
||||
try!(serializer.serialize_map_value(&mut state, &address_output.amount));
|
||||
},
|
||||
&TransactionOutput::ScriptData(ref script_output) => {
|
||||
try!(serializer.serialize_map_key(&mut state, "data"));
|
||||
try!(serializer.serialize_map_value(&mut state, &script_output.script_data));
|
||||
},
|
||||
}
|
||||
}
|
||||
serializer.serialize_map_end(state)
|
||||
}
|
||||
|
@ -166,11 +190,20 @@ impl Deserialize for TransactionOutputs {
|
|||
fn visit_map<V>(&mut self, mut visitor: V) -> Result<TransactionOutputs, V::Error> where V: MapVisitor {
|
||||
let mut outputs: Vec<TransactionOutput> = Vec::with_capacity(visitor.size_hint().0);
|
||||
|
||||
while let Some((address, amount)) = try!(visitor.visit()) {
|
||||
outputs.push(TransactionOutput {
|
||||
while let Some(key) = try!(visitor.visit_key::<String>()) {
|
||||
if &key == "data" {
|
||||
let value: Bytes = try!(visitor.visit_value());
|
||||
outputs.push(TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
||||
script_data: value,
|
||||
}));
|
||||
} else {
|
||||
let address = try!(Address::deserialize_from_string(&key));
|
||||
let amount: f64 = try!(visitor.visit_value());
|
||||
outputs.push(TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: address,
|
||||
amount: amount,
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
try!(visitor.end());
|
||||
|
@ -215,59 +248,51 @@ mod tests {
|
|||
txinput);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_output_serialize() {
|
||||
let txout = TransactionOutput {
|
||||
address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(),
|
||||
amount: 123.45,
|
||||
};
|
||||
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"address":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","amount":123.45}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_output_deserialize() {
|
||||
let txout = TransactionOutput {
|
||||
address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(),
|
||||
amount: 123.45,
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_str::<TransactionOutput>(r#"{"address":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","amount":123.45}"#).unwrap(),
|
||||
txout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_outputs_serialize() {
|
||||
let txout = TransactionOutputs {
|
||||
outputs: vec![
|
||||
TransactionOutput {
|
||||
TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(),
|
||||
amount: 123.45,
|
||||
},
|
||||
TransactionOutput {
|
||||
}),
|
||||
TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into(),
|
||||
amount: 67.89,
|
||||
},
|
||||
}),
|
||||
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
||||
script_data: Bytes::new(vec![1, 2, 3, 4]),
|
||||
}),
|
||||
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
||||
script_data: Bytes::new(vec![5, 6, 7, 8]),
|
||||
}),
|
||||
]
|
||||
};
|
||||
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":123.45,"1H5m1XzvHsjWX3wwU781ubctznEpNACrNC":67.89}"#);
|
||||
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":123.45,"1H5m1XzvHsjWX3wwU781ubctznEpNACrNC":67.89,"data":"01020304","data":"05060708"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_outputs_deserialize() {
|
||||
let txout = TransactionOutputs {
|
||||
outputs: vec![
|
||||
TransactionOutput {
|
||||
TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(),
|
||||
amount: 123.45,
|
||||
},
|
||||
TransactionOutput {
|
||||
}),
|
||||
TransactionOutput::Address(TransactionOutputWithAddress {
|
||||
address: "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into(),
|
||||
amount: 67.89,
|
||||
},
|
||||
}),
|
||||
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
||||
script_data: Bytes::new(vec![1, 2, 3, 4]),
|
||||
}),
|
||||
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
||||
script_data: Bytes::new(vec![5, 6, 7, 8]),
|
||||
}),
|
||||
]
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_str::<TransactionOutputs>(r#"{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":123.45,"1H5m1XzvHsjWX3wwU781ubctznEpNACrNC":67.89}"#).unwrap(),
|
||||
serde_json::from_str::<TransactionOutputs>(r#"{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa":123.45,"1H5m1XzvHsjWX3wwU781ubctznEpNACrNC":67.89,"data":"01020304","data":"05060708"}"#).unwrap(),
|
||||
txout);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use bytes::Bytes;
|
||||
use {Opcode, Script, Num};
|
||||
use keys::AddressHash;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
|
@ -7,6 +8,24 @@ pub struct Builder {
|
|||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build_p2pkh(address: &AddressHash) -> Script {
|
||||
Builder::default()
|
||||
.push_opcode(Opcode::OP_DUP)
|
||||
.push_opcode(Opcode::OP_HASH160)
|
||||
.push_bytes(&**address)
|
||||
.push_opcode(Opcode::OP_EQUALVERIFY)
|
||||
.push_opcode(Opcode::OP_CHECKSIG)
|
||||
.into_script()
|
||||
}
|
||||
|
||||
pub fn build_p2sh(address: &AddressHash) -> Script {
|
||||
Builder::default()
|
||||
.push_opcode(Opcode::OP_HASH160)
|
||||
.push_bytes(&**address)
|
||||
.push_opcode(Opcode::OP_EQUAL)
|
||||
.into_script()
|
||||
}
|
||||
|
||||
pub fn push_opcode(mut self, opcode: Opcode) -> Self {
|
||||
self.data.push(opcode as u8);
|
||||
self
|
||||
|
@ -63,6 +82,12 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn return_bytes(mut self, bytes: &[u8]) -> Self {
|
||||
self.data.push(Opcode::OP_RETURN as u8);
|
||||
self.data.extend_from_slice(bytes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push_invalid_opcode(mut self) -> Self {
|
||||
self.data.push(0xff);
|
||||
self
|
||||
|
|
|
@ -699,13 +699,7 @@ OP_ADD
|
|||
#[test]
|
||||
fn test_extract_destinations_pub_key_hash() {
|
||||
let address = Address::from("13NMTpfNVVJQTNH4spP4UeqBGqLdqDo27S").hash;
|
||||
let script = Builder::default()
|
||||
.push_opcode(Opcode::OP_DUP)
|
||||
.push_opcode(Opcode::OP_HASH160)
|
||||
.push_bytes(&*address)
|
||||
.push_opcode(Opcode::OP_EQUALVERIFY)
|
||||
.push_opcode(Opcode::OP_CHECKSIG)
|
||||
.into_script();
|
||||
let script = Builder::build_p2pkh(&address);
|
||||
assert_eq!(script.script_type(), ScriptType::PubKeyHash);
|
||||
assert_eq!(script.extract_destinations(), Ok(vec![
|
||||
ScriptAddress::new_p2pkh(address),
|
||||
|
@ -715,11 +709,7 @@ OP_ADD
|
|||
#[test]
|
||||
fn test_extract_destinations_script_hash() {
|
||||
let address = Address::from("13NMTpfNVVJQTNH4spP4UeqBGqLdqDo27S").hash;
|
||||
let script = Builder::default()
|
||||
.push_opcode(Opcode::OP_HASH160)
|
||||
.push_bytes(&*address)
|
||||
.push_opcode(Opcode::OP_EQUAL)
|
||||
.into_script();
|
||||
let script = Builder::build_p2sh(&address);
|
||||
assert_eq!(script.script_type(), ScriptType::ScriptHash);
|
||||
assert_eq!(script.extract_destinations(), Ok(vec![
|
||||
ScriptAddress::new_p2sh(address),
|
||||
|
|
Loading…
Reference in New Issue