Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Real-time Sync: Integrate changes on the SDK #578

Merged
merged 2 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,310 changes: 953 additions & 357 deletions cli/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ typedef struct wire_cst_config {
int32_t network;
uint64_t payment_timeout_sec;
uint32_t zero_conf_min_fee_rate_msat;
struct wire_cst_list_prim_u_8_strict *sync_service_url;
uint64_t *zero_conf_max_amount_sat;
struct wire_cst_list_prim_u_8_strict *breez_api_key;
} wire_cst_config;
Expand Down
8 changes: 8 additions & 0 deletions lib/bindings/src/breez_sdk_liquid.udl
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ dictionary Config {
LiquidNetwork network;
u64 payment_timeout_sec;
u32 zero_conf_min_fee_rate_msat;
string sync_service_url;
string? breez_api_key;
string? cache_dir;
u64? zero_conf_max_amount_sat;
Expand Down Expand Up @@ -563,6 +564,7 @@ enum PaymentState {
"TimedOut",
"Refundable",
"RefundPending",
"Recoverable",
};

dictionary RefundableSwap {
Expand Down Expand Up @@ -668,6 +670,12 @@ callback interface Signer {

[Throws=SignerError]
sequence<u8> hmac_sha256(sequence<u8> msg, string derivation_path);

[Throws=SignerError]
sequence<u8> ecies_encrypt(sequence<u8> msg);

[Throws=SignerError]
sequence<u8> ecies_decrypt(sequence<u8> msg);
};

interface BindingLiquidSdk {
Expand Down
2 changes: 1 addition & 1 deletion lib/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ electrum-client = { version = "0.19.0" }
zbase32 = "0.1.2"
x509-parser = { version = "0.16.0" }
tempfile = "3"
tonic = { version = "0.12.3", features = ["tls"] }
prost = "0.13.3"
ecies = "0.2.7"
semver = "1.0.23"
lazy_static = "1.5.0"
tonic = { version = "0.12.3", features = ["tls", "tls-webpki-roots"] }

[dev-dependencies]
paste = "1.0.15"
Expand Down
9 changes: 8 additions & 1 deletion lib/core/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::*;
use glob::glob;
use std::env;
use std::os::unix::process::CommandExt as _;
use std::process::Command;
use std::result::Result::Ok;

/// Adds a temporary workaround for an issue with the Rust compiler and Android
Expand Down Expand Up @@ -35,8 +37,13 @@ fn setup_x86_64_android_workaround() {
fn compile_protos() -> Result<()> {
tonic_build::configure()
.build_server(false)
.out_dir("./src/sync/model")
.out_dir("src/sync/model")
.compile_protos(&["src/sync/proto/sync.proto"], &["src/sync/proto"])?;
Command::new("rustfmt")
.arg("--edition")
.arg("2021")
.arg("src/sync/model/sync.rs")
.exec();
Ok(())
}

Expand Down
38 changes: 28 additions & 10 deletions lib/core/src/chain_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ impl ChainSwapHandler {
.fetch_chain_swap_by_id(id)?
.ok_or(anyhow!("No ongoing Chain Swap found for ID {id}"))?;

if let Some(sync_state) = self.persister.get_sync_state_by_data_id(&swap.id)? {
if !sync_state.is_local {
let status = &update.status;
let swap_state = ChainSwapStates::from_str(status)
.map_err(|_| anyhow!("Invalid ChainSwapState for Chain Swap {id}: {status}"))?;

match swap_state {
// If the swap is not local (pulled from real-time sync) we do not claim twice
ChainSwapStates::TransactionServerMempool
| ChainSwapStates::TransactionServerConfirmed => {
return Ok(());
}
_ => {}
}
}
}

match swap.direction {
Direction::Incoming => self.on_new_incoming_status(&swap, update).await,
Direction::Outgoing => self.on_new_outgoing_status(&swap, update).await,
Expand Down Expand Up @@ -1132,6 +1149,11 @@ impl ChainSwapHandler {
to_state: PaymentState,
) -> Result<(), PaymentError> {
match (from_state, to_state) {
(Recoverable, Pending | Refundable | RefundPending | Failed | Complete) => Ok(()),
(_, Recoverable) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Recoverable state"),
}),

(_, Created) => Err(PaymentError::Generic {
err: "Cannot transition to Created state".to_string(),
}),
Expand Down Expand Up @@ -1356,10 +1378,7 @@ impl ChainSwapHandler {

#[cfg(test)]
mod tests {
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use std::collections::{HashMap, HashSet};

use anyhow::Result;

Expand All @@ -1370,16 +1389,15 @@ mod tests {
},
test_utils::{
chain_swap::{new_chain_swap, new_chain_swap_handler},
persist::new_persister,
persist::create_persister,
},
};

#[tokio::test]
async fn test_chain_swap_state_transitions() -> Result<()> {
let (_temp_dir, storage) = new_persister()?;
let storage = Arc::new(storage);
create_persister!(persister);

let chain_swap_handler = new_chain_swap_handler(storage.clone())?;
let chain_swap_handler = new_chain_swap_handler(persister.clone())?;

// Test valid combinations of states
let all_states = HashSet::from([Created, Pending, Complete, TimedOut, Failed]);
Expand All @@ -1403,7 +1421,7 @@ mod tests {
for allowed_state in allowed_states {
let chain_swap =
new_chain_swap(Direction::Incoming, Some(*first_state), false, None);
storage.insert_chain_swap(&chain_swap)?;
persister.insert_chain_swap(&chain_swap)?;

assert!(chain_swap_handler
.update_swap_info(&chain_swap.id, *allowed_state, None, None, None, None)
Expand All @@ -1427,7 +1445,7 @@ mod tests {
for disallowed_state in disallowed_states {
let chain_swap =
new_chain_swap(Direction::Incoming, Some(*first_state), false, None);
storage.insert_chain_swap(&chain_swap)?;
persister.insert_chain_swap(&chain_swap)?;

assert!(chain_swap_handler
.update_swap_info(&chain_swap.id, *disallowed_state, None, None, None, None)
Expand Down
11 changes: 11 additions & 0 deletions lib/core/src/frb_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,7 @@ impl CstDecode<crate::model::PaymentState> for i32 {
4 => crate::model::PaymentState::TimedOut,
5 => crate::model::PaymentState::Refundable,
6 => crate::model::PaymentState::RefundPending,
7 => crate::model::PaymentState::Recoverable,
_ => unreachable!("Invalid variant for PaymentState: {}", self),
}
}
Expand Down Expand Up @@ -2343,6 +2344,7 @@ impl SseDecode for crate::model::Config {
let mut var_network = <crate::model::LiquidNetwork>::sse_decode(deserializer);
let mut var_paymentTimeoutSec = <u64>::sse_decode(deserializer);
let mut var_zeroConfMinFeeRateMsat = <u32>::sse_decode(deserializer);
let mut var_syncServiceUrl = <String>::sse_decode(deserializer);
let mut var_zeroConfMaxAmountSat = <Option<u64>>::sse_decode(deserializer);
let mut var_breezApiKey = <Option<String>>::sse_decode(deserializer);
return crate::model::Config {
Expand All @@ -2354,6 +2356,7 @@ impl SseDecode for crate::model::Config {
network: var_network,
payment_timeout_sec: var_paymentTimeoutSec,
zero_conf_min_fee_rate_msat: var_zeroConfMinFeeRateMsat,
sync_service_url: var_syncServiceUrl,
zero_conf_max_amount_sat: var_zeroConfMaxAmountSat,
breez_api_key: var_breezApiKey,
};
Expand Down Expand Up @@ -3627,6 +3630,7 @@ impl SseDecode for crate::model::PaymentState {
4 => crate::model::PaymentState::TimedOut,
5 => crate::model::PaymentState::Refundable,
6 => crate::model::PaymentState::RefundPending,
7 => crate::model::PaymentState::Recoverable,
_ => unreachable!("Invalid variant for PaymentState: {}", inner),
};
}
Expand Down Expand Up @@ -4506,6 +4510,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::Config {
self.zero_conf_min_fee_rate_msat
.into_into_dart()
.into_dart(),
self.sync_service_url.into_into_dart().into_dart(),
self.zero_conf_max_amount_sat.into_into_dart().into_dart(),
self.breez_api_key.into_into_dart().into_dart(),
]
Expand Down Expand Up @@ -5617,6 +5622,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentState {
Self::TimedOut => 4.into_dart(),
Self::Refundable => 5.into_dart(),
Self::RefundPending => 6.into_dart(),
Self::Recoverable => 7.into_dart(),
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -6555,6 +6561,7 @@ impl SseEncode for crate::model::Config {
<crate::model::LiquidNetwork>::sse_encode(self.network, serializer);
<u64>::sse_encode(self.payment_timeout_sec, serializer);
<u32>::sse_encode(self.zero_conf_min_fee_rate_msat, serializer);
<String>::sse_encode(self.sync_service_url, serializer);
<Option<u64>>::sse_encode(self.zero_conf_max_amount_sat, serializer);
<Option<String>>::sse_encode(self.breez_api_key, serializer);
}
Expand Down Expand Up @@ -7584,6 +7591,7 @@ impl SseEncode for crate::model::PaymentState {
crate::model::PaymentState::TimedOut => 4,
crate::model::PaymentState::Refundable => 5,
crate::model::PaymentState::RefundPending => 6,
crate::model::PaymentState::Recoverable => 7,
_ => {
unimplemented!("");
}
Expand Down Expand Up @@ -8599,6 +8607,7 @@ mod io {
network: self.network.cst_decode(),
payment_timeout_sec: self.payment_timeout_sec.cst_decode(),
zero_conf_min_fee_rate_msat: self.zero_conf_min_fee_rate_msat.cst_decode(),
sync_service_url: self.sync_service_url.cst_decode(),
zero_conf_max_amount_sat: self.zero_conf_max_amount_sat.cst_decode(),
breez_api_key: self.breez_api_key.cst_decode(),
}
Expand Down Expand Up @@ -10029,6 +10038,7 @@ mod io {
network: Default::default(),
payment_timeout_sec: Default::default(),
zero_conf_min_fee_rate_msat: Default::default(),
sync_service_url: core::ptr::null_mut(),
zero_conf_max_amount_sat: core::ptr::null_mut(),
breez_api_key: core::ptr::null_mut(),
}
Expand Down Expand Up @@ -12027,6 +12037,7 @@ mod io {
network: i32,
payment_timeout_sec: u64,
zero_conf_min_fee_rate_msat: u32,
sync_service_url: *mut wire_cst_list_prim_u_8_strict,
zero_conf_max_amount_sat: *mut u64,
breez_api_key: *mut wire_cst_list_prim_u_8_strict,
}
Expand Down
16 changes: 14 additions & 2 deletions lib/core/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::utils;
// Both use f64 for the maximum precision when converting between units
pub const STANDARD_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
pub const LOWBALL_FEE_RATE_SAT_PER_VBYTE: f64 = 0.01;
const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";

/// Configuration for the Liquid SDK
#[derive(Clone, Debug, Serialize)]
Expand All @@ -47,6 +48,8 @@ pub struct Config {
pub payment_timeout_sec: u64,
/// Zero-conf minimum accepted fee-rate in millisatoshis per vbyte
pub zero_conf_min_fee_rate_msat: u32,
/// The url of the real-time sync service
pub sync_service_url: String,
/// Maximum amount in satoshi to accept zero-conf payments with
/// Defaults to [crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT]
pub zero_conf_max_amount_sat: Option<u64>,
Expand All @@ -65,6 +68,7 @@ impl Config {
network: LiquidNetwork::Mainnet,
payment_timeout_sec: 15,
zero_conf_min_fee_rate_msat: DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET,
sync_service_url: BREEZ_SYNC_SERVICE_URL.to_string(),
zero_conf_max_amount_sat: None,
breez_api_key: Some(breez_api_key),
}
Expand All @@ -80,6 +84,7 @@ impl Config {
network: LiquidNetwork::Testnet,
payment_timeout_sec: 15,
zero_conf_min_fee_rate_msat: DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET,
sync_service_url: BREEZ_SYNC_SERVICE_URL.to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same service URL for mainnet/testnet, is this going to cause a conflict if using the same mnemonic for both? I imagine it will merge swap data from both together...

zero_conf_max_amount_sat: None,
breez_api_key,
}
Expand Down Expand Up @@ -259,10 +264,10 @@ pub trait Signer: Send + Sync {
fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError>;

/// Encrypts a message using (ECIES)[ecies::encrypt]
fn ecies_encrypt(&self, msg: &[u8]) -> Result<Vec<u8>, SignerError>;
fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;

/// Decrypts a message using (ECIES)[ecies::decrypt]
fn ecies_decrypt(&self, msg: &[u8]) -> Result<Vec<u8>, SignerError>;
fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError>;
}

/// An argument when calling [crate::sdk::LiquidSdk::connect].
Expand Down Expand Up @@ -1013,6 +1018,12 @@ pub enum PaymentState {
///
/// When the refund tx is broadcast, `refund_tx_id` is set in the swap.
RefundPending = 6,

/// ## Recoverable Swaps
///
/// The status for swaps that have been synced in, and whose information is recoverable from
/// chain
Recoverable = 7,
}
impl ToSql for PaymentState {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Expand All @@ -1030,6 +1041,7 @@ impl FromSql for PaymentState {
4 => Ok(PaymentState::TimedOut),
5 => Ok(PaymentState::Refundable),
6 => Ok(PaymentState::RefundPending),
7 => Ok(PaymentState::Recoverable),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
Expand Down
6 changes: 3 additions & 3 deletions lib/core/src/persist/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ impl Persister {
mod tests {
use anyhow::Result;

use crate::test_utils::persist::new_persister;
use crate::test_utils::persist::create_persister;

#[test]
fn test_next_expired_reserved_address() -> Result<()> {
let (_temp_dir, storage) = new_persister()?;
create_persister!(storage);
let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n";

storage.insert_or_update_reserved_address(address, 100)?;
Expand All @@ -131,7 +131,7 @@ mod tests {

#[test]
fn test_delete_reserved_address() -> Result<()> {
let (_temp_dir, storage) = new_persister()?;
create_persister!(storage);
let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n";

storage.insert_or_update_reserved_address(address, 100)?;
Expand Down
6 changes: 3 additions & 3 deletions lib/core/src/persist/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ mod tests {

use crate::{
model::PaymentState,
test_utils::persist::{new_persister, new_receive_swap, new_send_swap},
test_utils::persist::{create_persister, new_receive_swap, new_send_swap},
};

#[test]
fn test_backup_and_restore() -> Result<()> {
let (_local_temp_dir, local) = new_persister()?;
create_persister!(local);

local.insert_send_swap(&new_send_swap(Some(PaymentState::Pending)))?;
local.insert_receive_swap(&new_receive_swap(Some(PaymentState::Pending)))?;
Expand All @@ -55,7 +55,7 @@ mod tests {
local.backup(backup_path.clone())?;
assert!(backup_path.exists());

let (_remote_temp_dir, remote) = new_persister()?;
create_persister!(remote);

remote.restore_from_backup(backup_path)?;
assert_eq!(remote.list_ongoing_swaps()?.len(), 2);
Expand Down
Loading
Loading