Skip to content

Commit

Permalink
feat: add atomic swap htlc sending and claiming (#3552)
Browse files Browse the repository at this point in the history
Description
---
This Pr adds in the ability to send and claim HTLC transactions. 
This is added to the console wallet UI and GRPC commands. 

UI:
`tari_console_wallet --command "init-sha-atomic-swap <Amount> <Bob pubkey> <Message>"`
This is used to initialize an atomic swap for the XTR side
`tari_console_wallet --command "finalise-sha-atomic-swap <Output hash> <pre_image hex>"`
This is used to finalize and claim an HTLC transaction for the XTR side. 

GRPC:
`rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse);`
This is used to initialize an atomic swap for the XTR side
`rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse);`
This is used to finalize and claim an HTLC transaction for the XTR side. 

Motivation and Context
---
This is the first step in allowing Atomic swaps for Tari and coins that have SHA256 as a pre_image. 
Currently, the available commands in this PR and tools available we can do the happy path of an atomic swap for:
Bitcoin 
Bitcoin Cash (Bitcoin ABC, Bitcoin Unlimited, Bitcoin XT)
Decred 
Litecoin
Monacoin
Particl
Qtum
Vertcoin
Viacoin
Zcoin

How Has This Been Tested?
---
Added unit and cucumber tests for the Tari side. 
Did a manual XTR BTC atomic swap
XTR UTXO hash: 
`186a95239d16ebf800cb3b7991521585b569253255002b009b16f11faab8d115`
BTC tx_ids: 
`1f770ce82aeb1f6c0d0345a6a88f1b3eb2e226595063491c93ad442625f678ae`
`2b6e2e2566577e8e52da685e2abb4252b98fc4a60dc9a1b09a56e6b003900bc9`
  • Loading branch information
SWvheerden authored Nov 16, 2021
1 parent 433bc46 commit a185506
Show file tree
Hide file tree
Showing 24 changed files with 5,740 additions and 459 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion applications/tari_app_grpc/proto/wallet.proto
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ service Wallet {
rpc CancelTransaction (CancelTransactionRequest) returns (CancelTransactionResponse);
// Will triggger a complete revalidation of all wallet outputs.
rpc RevalidateAllTransactions (RevalidateRequest) returns (RevalidateResponse);
// This will send a XTR SHA Atomic swap transaction
rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse);
// This will claim a XTR SHA Atomic swap transaction
rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse);
}

message GetVersionRequest { }
Expand All @@ -76,6 +80,10 @@ message TransferRequest {
repeated PaymentRecipient recipients = 1;
}

message SendShaAtomicSwapRequest {
PaymentRecipient recipient = 1;
}

message PaymentRecipient {
string address = 1;
uint64 amount = 2;
Expand All @@ -92,13 +100,31 @@ message TransferResponse {
repeated TransferResult results = 1;
}

message SendShaAtomicSwapResponse {
uint64 transaction_id = 1;
string pre_image = 2;
string output_hash = 3;
bool is_success = 4;
string failure_message = 5;
}

message TransferResult {
string address = 1;
uint64 transaction_id = 2;
bool is_success = 3;
string failure_message = 4;
}

message ClaimShaAtomicSwapRequest{
string output = 1;
string pre_image = 2;
uint64 fee_per_gram = 3;
}

message ClaimShaAtomicSwapResponse {
TransferResult results = 1;
}

message GetTransactionInfoRequest {
repeated uint64 transaction_ids = 1;
}
Expand Down Expand Up @@ -204,4 +230,4 @@ message CancelTransactionResponse {

message RevalidateRequest{}

message RevalidateResponse{}
message RevalidateResponse{}
9 changes: 7 additions & 2 deletions applications/tari_app_utilities/src/utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
use futures::future::Either;
use log::*;
use std::sync::Arc;
use tokio::{runtime, runtime::Runtime};

use tari_common::{CommsTransport, GlobalConfig, SocksAuthentication, TorControlAuthentication};
use tari_common_types::types::BlockHash;
use tari_comms::{
peer_manager::NodeId,
socks,
Expand All @@ -37,6 +36,7 @@ use tari_comms::{
};
use tari_core::tari_utilities::hex::Hex;
use tari_p2p::transport::{TorConfig, TransportType};
use tokio::{runtime, runtime::Runtime};

use crate::identity_management::load_from_json;
use tari_common_types::emoji::EmojiId;
Expand Down Expand Up @@ -175,6 +175,11 @@ pub fn parse_emoji_id_or_public_key(key: &str) -> Option<CommsPublicKey> {
.ok()
}

/// Returns a hash from a hex string
pub fn parse_hash(hash_string: &str) -> Option<BlockHash> {
BlockHash::from_hex(hash_string).ok()
}

/// Returns a CommsPublicKey from either a emoji id, a public key or node id
pub fn parse_emoji_id_or_public_key_or_node_id(key: &str) -> Option<Either<CommsPublicKey, NodeId>> {
parse_emoji_id_or_public_key(key)
Expand Down
2 changes: 2 additions & 0 deletions applications/tari_console_wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ tari_app_grpc = { path = "../tari_app_grpc" }
tari_shutdown = { path = "../../infrastructure/shutdown" }
tari_key_manager = { path = "../../base_layer/key_manager" }

sha2 = "0.9.5"
digest = "0.9.0"
chrono = { version = "0.4.19", default-features = false }
bitflags = "1.2.1"
futures = { version = "^0.3.16", default-features = false, features = ["alloc"] }
Expand Down
52 changes: 48 additions & 4 deletions applications/tari_console_wallet/src/automation/command_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::automation::{commands::WalletCommand, error::ParseError};

use chrono::{DateTime, Utc};
use core::str::SplitWhitespace;
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use tari_app_utilities::utilities::parse_emoji_id_or_public_key;
use tari_comms::multiaddr::Multiaddr;

use tari_app_utilities::utilities::{parse_emoji_id_or_public_key, parse_hash};
use tari_common_types::types::PublicKey;
use tari_comms::multiaddr::Multiaddr;
use tari_core::transactions::tari_amount::MicroTari;
use tari_crypto::tari_utilities::hex::Hex;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ParsedCommand {
pub command: WalletCommand,
pub args: Vec<ParsedArgument>,
Expand All @@ -57,6 +57,8 @@ impl Display for ParsedCommand {
SetBaseNode => "set-base-node",
SetCustomBaseNode => "set-custom-base-node",
ClearCustomBaseNode => "clear-custom-base-node",
InitShaAtomicSwap => "init-sha-atomic-swap",
FinaliseShaAtomicSwap => "finalise-sha-atomic-swap",
};

let args = self
Expand All @@ -82,6 +84,7 @@ pub enum ParsedArgument {
CSVFileName(String),
Address(Multiaddr),
Negotiated(bool),
Hash(Vec<u8>),
}

impl Display for ParsedArgument {
Expand All @@ -98,6 +101,7 @@ impl Display for ParsedArgument {
CSVFileName(v) => write!(f, "{}", v.to_string()),
Address(v) => write!(f, "{}", v.to_string()),
Negotiated(v) => write!(f, "{}", v.to_string()),
Hash(v) => write!(f, "{}", v.to_hex()),
}
}
}
Expand All @@ -124,6 +128,8 @@ pub fn parse_command(command: &str) -> Result<ParsedCommand, ParseError> {
SetBaseNode => parse_public_key_and_address(args)?,
SetCustomBaseNode => parse_public_key_and_address(args)?,
ClearCustomBaseNode => Vec::new(),
InitShaAtomicSwap => parse_init_sha_atomic_swap(args)?,
FinaliseShaAtomicSwap => parse_finalise_sha_atomic_swap(args)?,
};

Ok(ParsedCommand { command, args })
Expand Down Expand Up @@ -175,6 +181,44 @@ fn parse_public_key_and_address(mut args: SplitWhitespace) -> Result<Vec<ParsedA
Ok(parsed_args)
}

fn parse_init_sha_atomic_swap(mut args: SplitWhitespace) -> Result<Vec<ParsedArgument>, ParseError> {
let mut parsed_args = Vec::new();

// amount
let amount = args.next().ok_or_else(|| ParseError::Empty("amount".to_string()))?;
let amount = MicroTari::from_str(amount)?;
parsed_args.push(ParsedArgument::Amount(amount));

// public key/emoji id
let pubkey = args
.next()
.ok_or_else(|| ParseError::Empty("public key or emoji id".to_string()))?;
let pubkey = parse_emoji_id_or_public_key(pubkey).ok_or(ParseError::PublicKey)?;
parsed_args.push(ParsedArgument::PublicKey(pubkey));
// message
let message = args.collect::<Vec<&str>>().join(" ");
parsed_args.push(ParsedArgument::Text(message));

Ok(parsed_args)
}

fn parse_finalise_sha_atomic_swap(mut args: SplitWhitespace) -> Result<Vec<ParsedArgument>, ParseError> {
let mut parsed_args = Vec::new();
// hash
let hash = args
.next()
.ok_or_else(|| ParseError::Empty("Output hash".to_string()))?;
let hash = parse_hash(hash).ok_or(ParseError::Hash)?;
parsed_args.push(ParsedArgument::Hash(hash));

// public key
let pre_image = args.next().ok_or_else(|| ParseError::Empty("public key".to_string()))?;
let pre_image = parse_emoji_id_or_public_key(pre_image).ok_or(ParseError::PublicKey)?;
parsed_args.push(ParsedArgument::PublicKey(pre_image));

Ok(parsed_args)
}

fn parse_make_it_rain(mut args: SplitWhitespace) -> Result<Vec<ParsedArgument>, ParseError> {
let mut parsed_args = Vec::new();

Expand Down
87 changes: 86 additions & 1 deletion applications/tari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@

use super::error::CommandError;
use chrono::Utc;
use digest::Digest;
use log::*;
use sha2::Sha256;
use std::{
convert::TryFrom,
fs::File,
io::{LineWriter, Write},
str::FromStr,
time::{Duration, Instant},
};
use tari_crypto::tari_utilities::{ByteArray, Hashable};

use futures::FutureExt;
use strum_macros::{Display, EnumIter, EnumString};
Expand All @@ -51,7 +54,7 @@ use tari_core::{
tari_utilities::hex::Hex,
transactions::{
tari_amount::{uT, MicroTari, Tari},
transaction::UnblindedOutput,
transaction::{TransactionOutput, UnblindedOutput},
},
};
use tari_wallet::{
Expand Down Expand Up @@ -83,6 +86,8 @@ pub enum WalletCommand {
SetBaseNode,
SetCustomBaseNode,
ClearCustomBaseNode,
InitShaAtomicSwap,
FinaliseShaAtomicSwap,
}

#[derive(Debug, EnumString, PartialEq, Clone)]
Expand Down Expand Up @@ -124,6 +129,31 @@ fn get_transaction_parameters(
Ok((fee_per_gram, amount, dest_pubkey, message))
}

fn get_init_sha_atomic_swap_parameters(
args: Vec<ParsedArgument>,
) -> Result<(MicroTari, MicroTari, PublicKey, String), CommandError> {
// TODO: Consolidate "fee per gram" in codebase
let fee_per_gram = 25 * uT;

use ParsedArgument::*;
let amount = match args[0].clone() {
Amount(mtari) => Ok(mtari),
_ => Err(CommandError::Argument),
}?;

let dest_pubkey = match args[1].clone() {
PublicKey(key) => Ok(key),
_ => Err(CommandError::Argument),
}?;

let message = match args[2].clone() {
Text(msg) => Ok(msg),
_ => Err(CommandError::Argument),
}?;

Ok((fee_per_gram, amount, dest_pubkey, message))
}

/// Send a normal negotiated transaction to a recipient
pub async fn send_tari(
mut wallet_transaction_service: TransactionServiceHandle,
Expand All @@ -136,6 +166,45 @@ pub async fn send_tari(
.map_err(CommandError::TransactionServiceError)
}

/// publishes a tari-SHA atomic swap HTLC transaction
pub async fn init_sha_atomic_swap(
mut wallet_transaction_service: TransactionServiceHandle,
args: Vec<ParsedArgument>,
) -> Result<(TxId, PublicKey, TransactionOutput), CommandError> {
let (fee_per_gram, amount, dest_pubkey, message) = get_init_sha_atomic_swap_parameters(args)?;

let (tx_id, pre_image, output) = wallet_transaction_service
.send_sha_atomic_swap_transaction(dest_pubkey, amount, fee_per_gram, message)
.await
.map_err(CommandError::TransactionServiceError)?;
Ok((tx_id, pre_image, output))
}

/// claims a tari-SHA atomic swap HTLC transaction
pub async fn finalise_sha_atomic_swap(
mut output_service: OutputManagerHandle,
mut transaction_service: TransactionServiceHandle,
args: Vec<ParsedArgument>,
) -> Result<TxId, CommandError> {
use ParsedArgument::*;
let output = match args[0].clone() {
Hash(output) => Ok(output),
_ => Err(CommandError::Argument),
}?;

let pre_image = match args[1].clone() {
PublicKey(key) => Ok(key),
_ => Err(CommandError::Argument),
}?;
let (tx_id, fee, amount, tx) = output_service
.create_claim_sha_atomic_swap_transaction(output, pre_image, MicroTari(25))
.await?;
transaction_service
.submit_transaction(tx_id, tx, fee, amount, "Claimed HTLC atomic swap".into())
.await?;
Ok(tx_id)
}

/// Send a one-sided transaction to a recipient
pub async fn send_one_sided(
mut wallet_transaction_service: TransactionServiceHandle,
Expand Down Expand Up @@ -674,6 +743,22 @@ pub async fn command_runner(
.await?;
println!("Custom base node peer cleared from wallet database.");
},
InitShaAtomicSwap => {
let (tx_id, pre_image, output) =
init_sha_atomic_swap(transaction_service.clone(), parsed.clone().args).await?;
debug!(target: LOG_TARGET, "tari HTLC tx_id {}", tx_id);
let hash: [u8; 32] = Sha256::digest(pre_image.as_bytes()).into();
println!("pre_image hex: {}", pre_image.to_hex());
println!("pre_image hash: {}", hash.to_hex());
println!("Output hash: {}", output.hash().to_hex());
tx_ids.push(tx_id);
},
FinaliseShaAtomicSwap => {
let tx_id =
finalise_sha_atomic_swap(output_service.clone(), transaction_service.clone(), parsed.args).await?;
debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id);
tx_ids.push(tx_id);
},
}
}

Expand Down
Loading

0 comments on commit a185506

Please sign in to comment.