commit 8b4288f34ff7dcbde255cb417445bb09b58a5c33
parent 3826f041a9b3ca885d0ca8910d1f65ccd8f1c910
Author: Antoine A <>
Date: Wed, 1 Dec 2021 17:25:30 +0100
Add packed reserve_base_url support and fix amount conversion
Diffstat:
8 files changed, 525 insertions(+), 341 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -208,9 +208,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
-version = "2.33.3"
+version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap",
@@ -230,9 +230,9 @@ dependencies = [
[[package]]
name = "crc32fast"
-version = "1.2.2"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
+checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
dependencies = [
"cfg-if",
]
@@ -1324,9 +1324,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
name = "uri-pack"
version = "0.1.0"
dependencies = [
+ "criterion",
"csv",
- "idna",
- "percent-encoding",
+ "fastrand",
"serde_json",
"thiserror",
"url",
@@ -1531,5 +1531,6 @@ dependencies = [
"serde_with",
"thiserror",
"tokio",
+ "uri-pack",
"url",
]
diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml
@@ -14,7 +14,7 @@ rustyline = "9.0.0"
bech32 = "0.8.1"
# Secure random
rand = { version = "0.8.4", features = ["getrandom"] }
-# Fast unsecure random
+# Fast insecure random
fastrand = "1.5.0"
# Serialization library
serde = { version = "1.0.130", features = ["derive"] }
diff --git a/uri-pack/Cargo.toml b/uri-pack/Cargo.toml
@@ -15,5 +15,12 @@ csv = "1.1.6"
serde_json = "1.0.72"
# Url parser
url = "2.2.2"
-idna = "0.2.3"
-percent-encoding = "2.1.0"
+# statistics-driven micro-benchmarks
+criterion = "0.3.5"
+# Fast insecure random
+fastrand = "1.5.0"
+
+[[bench]]
+name = "pack"
+harness = false
+
diff --git a/uri-pack/benches/pack.rs b/uri-pack/benches/pack.rs
@@ -0,0 +1,36 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
+use uri_pack::{pack_uri, unpack_uri};
+
+fn rand_compat(size: usize) -> String {
+ String::from_utf8(
+ std::iter::repeat_with(|| fastrand::u8(b' '..=b'~'))
+ .take(size)
+ .collect(),
+ )
+ .unwrap()
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let mut group = c.benchmark_group("Uri");
+ for size in [50, 500, 4048].iter() {
+ group.throughput(Throughput::Bytes(*size as u64));
+ group.bench_with_input(BenchmarkId::new("pack", size), size, |b, &size| {
+ b.iter_batched(
+ || rand_compat(size),
+ |uri| pack_uri(&uri).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ group.bench_with_input(BenchmarkId::new("unpack", size), size, |b, &size| {
+ b.iter_batched(
+ || pack_uri(&rand_compat(size)).unwrap(),
+ |(packed, len)| unpack_uri(&packed, len),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ }
+ group.finish();
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/uri-pack/src/lib.rs b/uri-pack/src/lib.rs
@@ -0,0 +1,324 @@
+/// Reserved 5 bit code for extended packing
+const EXTENDED: u8 = 31;
+
+/// Packed ascii as 5 or 11 bits
+#[derive(Debug, Clone, Copy)]
+pub enum Packed {
+ Simple(u8), // u5: 0..32
+ Extended(u8), // u6: 0..54
+}
+
+/// Pack an URI ascii char
+/// Panic if char not supported
+fn pack_ascii(c: u8) -> Packed {
+ let simple = match c {
+ b'a'..=b'z' => c - b'a',
+ b'.' => 26,
+ b'/' => 27,
+ b'-' => 28,
+ b'_' => 29,
+ b'%' => 30,
+ c => {
+ let extended = match c {
+ b'A'..=b'Z' => c - b'A',
+ b'0'..=b'9' => c - b'0' + 26,
+ b'!' => 36,
+ b'"' => 37,
+ b'#' => 38,
+ b'$' => 39,
+ b'&' => 40,
+ b'\'' => 41,
+ b'(' => 42,
+ b')' => 43,
+ b'*' => 44,
+ b'+' => 45,
+ b',' => 46,
+ b':' => 47,
+ b';' => 48,
+ b'<' => 49,
+ b'=' => 50,
+ b'>' => 51,
+ b'?' => 52,
+ b'@' => 53,
+ b'[' => 54,
+ b'\\' => 55,
+ b']' => 56,
+ b'^' => 57,
+ b'`' => 58,
+ b'{' => 59,
+ b'|' => 60,
+ b'}' => 61,
+ b'~' => 62,
+ b' ' => 63,
+ _ => unreachable!(),
+ };
+ return Packed::Extended(extended);
+ }
+ };
+ Packed::Simple(simple)
+}
+
+/// Unpack an URI ascii char
+/// Panic if char not supported
+fn unpack_ascii(c: Packed) -> u8 {
+ match c {
+ Packed::Simple(c) => match c {
+ 0..=25 => b'a' + c,
+ 26 => b'.',
+ 27 => b'/',
+ 28 => b'-',
+ 29 => b'_',
+ 30 => b'%',
+ _ => unreachable!(),
+ },
+ Packed::Extended(c) => match c {
+ 0..=25 => b'A' + c,
+ 26..=35 => b'0' + c - 26,
+ 36 => b'!',
+ 37 => b'"',
+ 38 => b'#',
+ 39 => b'$',
+ 40 => b'&',
+ 41 => b'\'',
+ 42 => b'(',
+ 43 => b')',
+ 44 => b'*',
+ 45 => b'+',
+ 46 => b',',
+ 47 => b':',
+ 48 => b';',
+ 49 => b'<',
+ 50 => b'=',
+ 51 => b'>',
+ 52 => b'?',
+ 53 => b'@',
+ 54 => b'[',
+ 55 => b'\\',
+ 56 => b']',
+ 57 => b'^',
+ 58 => b'`',
+ 59 => b'{',
+ 60 => b'|',
+ 61 => b'}',
+ 62 => b'~',
+ 63 => b' ',
+ _ => unreachable!(),
+ },
+ }
+}
+/// Check if an ascii char is packable
+fn supported_ascii(c: &u8) -> bool {
+ (b' '..=b'~').contains(c)
+}
+
+#[derive(Debug, Clone, Copy, thiserror::Error)]
+pub enum EncodeErr {
+ #[error("{0} is not a valid uri char")]
+ UnsupportedChar(u8),
+}
+
+#[derive(Debug, Clone, Copy, thiserror::Error)]
+pub enum DecodeErr {
+ #[error("An extended encoded char have been passed as an simple one")]
+ ExpectedExtended,
+ #[error("{0} is not an simple encoded char")]
+ UnexpectedSimpleChar(u8),
+ #[error("{0} is not an extended encoded char")]
+ UnexpectedExtendedChar(u8),
+ #[error("Missing bits")]
+ UnexpectedEOF,
+}
+
+/// Pack an uri string into an optimized binary format
+pub fn pack_uri(uri: &str) -> Result<(Vec<u8>, usize), EncodeErr> {
+ let len = uri.as_bytes().len();
+ let mut vec = Vec::with_capacity(len);
+
+ if let Some(char) = uri.as_bytes().iter().find(|c| !supported_ascii(c)) {
+ return Err(EncodeErr::UnsupportedChar(*char));
+ }
+
+ // Holds pending bits beginning from the most significant bits
+ // If buff contains 10110:
+ // buff_bits ─────
+ // buff 10110000
+ let (mut buff, mut buff_bits) = (0u8, 0u8);
+
+ let mut write_bits = |nb: u8, mut nb_bits: u8| {
+ // buff_bits ─────
+ // buff 10110000
+ // nb_bits ────
+ // nb 00011010
+ //
+ // ───
+ // writable 00011010
+ // ───
+ // rmv_right 00001101
+ // ───
+ // rmv_left 10100000
+ // ───
+ // align 00000101
+ //
+ // buff_bits ────────
+ // buff 10110101
+ // nb_bits -
+ // nb 00011010
+ while nb_bits > 0 {
+ // Amount of bits we can write in buff
+ let writable = (8 - buff_bits).min(nb_bits);
+ // Remove non writable bits
+ let rmv_right = nb >> nb_bits - writable;
+ let rmv_left = rmv_right << 8 - writable;
+ // Align remaining bits with buff blank bits
+ let align = rmv_left >> buff_bits;
+
+ // Write bits in buff
+ buff = buff | align;
+ buff_bits += writable;
+ nb_bits -= writable;
+
+ // Store buff if full
+ if buff_bits == 8 {
+ vec.push(buff);
+ buff = 0;
+ buff_bits = 0;
+ }
+ }
+ };
+
+ for c in uri.bytes() {
+ match pack_ascii(c) {
+ Packed::Simple(nb) => write_bits(nb, 5),
+ Packed::Extended(nb) => {
+ write_bits(EXTENDED, 5);
+ write_bits(nb, 6);
+ }
+ }
+ }
+
+ // Push pending buffer if not empty
+ if buff_bits > 0 {
+ vec.push(buff);
+ }
+
+ return Ok((vec, len));
+}
+
+/// Unpack an uri string from its optimized binary format
+pub fn unpack_uri(bytes: &[u8], len: usize) -> Result<String, DecodeErr> {
+ let mut buf = String::with_capacity(len);
+ let mut iter = bytes.iter();
+
+ // Holds pending bits beginning from the most significant bits
+ // If buff contains 10110:
+ // buff_bits ─────
+ // buff 10110000
+ let (mut buff, mut buff_bits) = (0u8, 0u8);
+
+ let mut read_nb = |mut nb_bits: u8| -> Result<u8, DecodeErr> {
+ // buff_bits ─────
+ // buff 10110000
+ // nb_bits ───
+ // nb 00000000
+ //
+ // ───
+ // readable 10110000
+ // ───
+ // rmv_left 01100000
+ // ───
+ // align 00000011
+ //
+ // buff_bits ─
+ // buff 10110000
+ // nb_bits
+ // nb 00000011
+
+ let mut nb = 8;
+ while nb_bits > 0 {
+ // Load buff if empty
+ if buff_bits == 0 {
+ buff = *iter.next().ok_or(DecodeErr::UnexpectedEOF)?;
+ buff_bits = 8;
+ }
+ // Amount of bits we can read from buff
+ let readable = buff_bits.min(nb_bits);
+ // Remove non writable bits
+ let rmv_left = buff << 8 - buff_bits;
+ // Align remaining bits with nb blank bits
+ let align = rmv_left >> (8 - readable);
+ // Read bits from buff
+ nb = (nb << readable) | align;
+ buff_bits -= readable;
+ nb_bits -= readable;
+ }
+ return Ok(nb);
+ };
+
+ for _ in 0..len {
+ let encoded = match read_nb(5)? {
+ EXTENDED => Packed::Extended(read_nb(6)?),
+ nb => Packed::Simple(nb),
+ };
+ buf.push(unpack_ascii(encoded) as char);
+ }
+
+ return Ok(buf);
+}
+
+#[cfg(test)]
+mod test {
+ use std::str::FromStr;
+
+ use serde_json::Value;
+
+ use crate::{pack_ascii, pack_uri, supported_ascii, unpack_ascii, unpack_uri};
+
+ #[test]
+ /// Check support every ascii graphic character and space
+ fn supported() {
+ for c in (0..=255u8).filter(supported_ascii) {
+ assert_eq!(unpack_ascii(pack_ascii(c)), c);
+ }
+ }
+
+ #[test]
+ /// Check error on unsupported char
+ fn unsupported() {
+ for c in (0..=255u8).filter(|c| !supported_ascii(c)) {
+ let string = String::from(c as char);
+ assert!(pack_uri(&string).is_err());
+ }
+ }
+
+ #[test]
+ fn url_simple() {
+ let mut majestic =
+ csv::Reader::from_reader(include_str!("majestic_million.csv").as_bytes());
+ for record in majestic.records() {
+ let domain = &record.unwrap()[2];
+ let (encoded, len) = pack_uri(domain).unwrap();
+ let decoded = unpack_uri(&encoded, len).unwrap();
+ assert_eq!(domain, decoded);
+ }
+ }
+
+ #[test]
+ fn url_complex() {
+ let mut json = Value::from_str(include_str!("urltestdata.json"))
+ .expect("JSON parse error in urltestdata.json");
+ for entry in json.as_array_mut().unwrap() {
+ if entry.is_string() {
+ continue; // ignore comments
+ }
+
+ let href = entry.get("href").and_then(|it| it.as_str()).unwrap_or("");
+ if href.chars().any(|c| !c.is_ascii_graphic() || c != ' ') {
+ continue; // extended ascii
+ }
+ let (encoded, len) = pack_uri(&href).expect(&format!("Failed to encode {}", &href));
+ let decoded =
+ unpack_uri(&encoded, len).expect(&format!("Failed to decode encoded {}", &href));
+ assert_eq!(href, decoded);
+ }
+ }
+}
diff --git a/uri-pack/src/main.rs b/uri-pack/src/main.rs
@@ -1,3 +1,5 @@
+use uri_pack::pack_uri;
+
fn main() {
let mut majestic = csv::Reader::from_reader(include_str!("majestic_million.csv").as_bytes());
let mut ascii_counter = [0u64; 255];
@@ -11,7 +13,7 @@ fn main() {
}
count += 1;
before += domain.as_bytes().len();
- after += encode_str(domain).unwrap().len();
+ after += pack_uri(domain).unwrap().0.len();
}
let sum: u64 = ascii_counter.iter().sum();
for (ascii, count) in ascii_counter
@@ -31,250 +33,3 @@ fn main() {
}
println!("\nBefore: {} After: {}", before / count, after / count);
}
-
-#[derive(Debug, Clone, Copy)]
-pub enum Encoded {
- Simple(u8), // u5: 0..32
- Extended(u8), // u6: 0..54
-}
-
-pub fn encode_ascii(c: u8) -> Encoded {
- let simple = match c {
- b'a'..=b'z' => c - b'a',
- b'.' => 26,
- b'/' => 27,
- b'-' => 28,
- b'_' => 29,
- b'%' => 30,
- c => {
- let extended = match c {
- b'A'..=b'Z' => c as u8 - 'A' as u8,
- b'0'..=b'9' => c as u8 - '0' as u8 + 26,
- b'!' => 36,
- b'"' => 37,
- b'#' => 38,
- b'$' => 39,
- b'&' => 40,
- b'\'' => 41,
- b'(' => 42,
- b')' => 43,
- b'*' => 44,
- b'+' => 45,
- b',' => 46,
- b':' => 47,
- b';' => 48,
- b'<' => 49,
- b'=' => 50,
- b'>' => 51,
- b'?' => 52,
- b'@' => 53,
- b'[' => 54,
- b'\\' => 55,
- b']' => 56,
- b'^' => 57,
- b'`' => 58,
- b'{' => 59,
- b'|' => 60,
- b'}' => 61,
- b'~' => 62,
- b' ' => 63,
- _ => unreachable!(),
- };
- return Encoded::Extended(extended);
- }
- };
- Encoded::Simple(simple)
-}
-
-pub fn decode_ascii(c: Encoded) -> u8 {
- match c {
- Encoded::Simple(c) => match c {
- 0..=25 => b'a' + c,
- 26 => b'.',
- 27 => b'/',
- 28 => b'-',
- 29 => b'_',
- 30 => b'%',
- _ => unreachable!(),
- },
- Encoded::Extended(c) => match c {
- 0..=25 => b'A' + c,
- 26..=35 => b'0' + c - 26,
- 36 => b'!',
- 37 => b'"',
- 38 => b'#',
- 39 => b'$',
- 40 => b'&',
- 41 => b'\'',
- 42 => b'(',
- 43 => b')',
- 44 => b'*',
- 45 => b'+',
- 46 => b',',
- 47 => b':',
- 48 => b';',
- 49 => b'<',
- 50 => b'=',
- 51 => b'>',
- 52 => b'?',
- 53 => b'@',
- 54 => b'[',
- 55 => b'\\',
- 56 => b']',
- 57 => b'^',
- 58 => b'`',
- 59 => b'{',
- 60 => b'|',
- 61 => b'}',
- 62 => b'~',
- 63 => b' ',
- _ => unreachable!(),
- },
- }
-}
-
-const EXTENDED: u8 = 31;
-
-#[derive(Debug, Clone, Copy, thiserror::Error)]
-#[error("{0} is not a valid uri char")]
-pub struct EncodeErr(char);
-
-#[derive(Debug, Clone, Copy, thiserror::Error)]
-pub enum DecodeErr {
- #[error("An extended encoded char have been passed as an simple one")]
- ExpectedExtended,
- #[error("{0} is not an simple encoded char")]
- UnexpectedSimpleChar(u8),
- #[error("{0} is not an extended encoded char")]
- UnexpectedExtendedChar(u8),
-}
-
-pub fn encode_str(str: &str) -> Result<Vec<u8>, EncodeErr> {
- let mut vec = Vec::new();
-
- assert!(str.as_bytes().iter().all(|c| supported_char(*c as char)));
-
- // Amount of pending bits stored in buffer.
- let mut buffer_bits = 0u8;
- // Holds pending bits beginning from the most significant bits
- let mut buffer: u8 = 0;
- let mut write_bits = |nb: u8, mut nb_bits: u8| {
- while nb_bits > 0 {
- let writable = (8 - buffer_bits).min(nb_bits);
- let remove_right = nb_bits - writable;
- let remove_left = 8 - writable;
- let mask = ((nb >> remove_right) << (remove_left)) >> buffer_bits;
- buffer = buffer | mask;
- buffer_bits += writable;
- nb_bits -= writable;
- // Write filled byte
- if buffer_bits == 8 {
- vec.push(buffer);
- buffer = 0;
- buffer_bits = 0;
- }
- }
- };
-
- for c in str.bytes() {
- match encode_ascii(c) {
- Encoded::Simple(nb) => write_bits(nb, 5),
- Encoded::Extended(nb) => {
- write_bits(EXTENDED, 5);
- write_bits(nb, 6);
- }
- }
- }
-
- if buffer_bits > 0 {
- vec.push(buffer);
- }
-
- return Ok(vec);
-}
-
-pub fn decode_str(bytes: &[u8], len: usize) -> Result<String, DecodeErr> {
- let mut buf = String::with_capacity(len);
- let mut iter = bytes.iter();
- // Amount of pending bits stored in buffer.
- let mut buffer_bits = 0u8;
- // Holds pending bits beginning from the most significant bits
- let mut buffer: u8 = 0;
- let mut read_nb = |mut nb_bits: u8| -> u8 {
- let mut nb = 8;
- while nb_bits > 0 {
- if buffer_bits == 0 {
- buffer = *iter.next().unwrap();
- buffer_bits = 8;
- }
- let readable = buffer_bits.min(nb_bits);
- let mask = (buffer << 8 - buffer_bits) >> (8 - readable);
- nb = (nb << readable) | mask;
- buffer_bits -= readable;
- nb_bits -= readable;
- }
- return nb;
- };
-
- for _ in 0..len {
- let encoded = match read_nb(5) {
- EXTENDED => Encoded::Extended(read_nb(6)),
- nb => Encoded::Simple(nb),
- };
- buf.push(decode_ascii(encoded) as char);
- }
-
- return Ok(buf);
-}
-
-fn supported_char(c: char) -> bool {
- c.is_ascii_graphic() || c == ' '
-}
-
-#[cfg(test)]
-mod test {
- use std::str::FromStr;
-
- use serde_json::Value;
-
- use crate::{decode_ascii, decode_str, encode_ascii, encode_str, supported_char};
-
- #[test]
- /// Check support every ascii graphic character and space
- fn supported() {
- for c in (0..=255u8).filter(|c| supported_char(*c as char)) {
- assert_eq!(decode_ascii(encode_ascii(c)), c);
- }
- }
-
- #[test]
- fn url_simple() {
- let mut majestic =
- csv::Reader::from_reader(include_str!("majestic_million.csv").as_bytes());
- for record in majestic.records() {
- let domain = &record.unwrap()[2];
- let decoded = decode_str(&encode_str(domain).unwrap(), domain.len()).unwrap();
- assert_eq!(domain, decoded);
- }
- }
-
- #[test]
- fn url_complex() {
- let mut json = Value::from_str(include_str!("urltestdata.json"))
- .expect("JSON parse error in urltestdata.json");
- for entry in json.as_array_mut().unwrap() {
- if entry.is_string() {
- continue; // ignore comments
- }
-
- let href = entry.get("href").and_then(|it| it.as_str()).unwrap_or("");
- if href.chars().any(|c| !c.is_ascii_graphic() || c != ' ') {
- continue; // extended ascii
- }
- let encoded = encode_str(&href).expect(&format!("Failed to encode {}", &href));
- let decoded = decode_str(&encoded, href.len())
- .expect(&format!("Failed to decode encoded {}", &href));
- assert_eq!(href, decoded);
- }
- }
-}
diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml
@@ -32,3 +32,5 @@ rand = { version = "0.8.4", features = ["getrandom"] }
url = { version = "2.2.2", features = ["serde"] }
# Bitcoin taler util
btc-wire = { path = "../btc-wire" }
+# Optimized uri binary format
+uri-pack = { path = "../uri-pack" }
diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs
@@ -59,7 +59,7 @@ fn btc_payto_addr(url: &Url) -> Result<&str, ServerErr> {
impl Into<Amount> for BtcAmount {
fn into(self) -> Amount {
let sat = self.as_sat();
- return Amount::new("BTC", sat / 100_000_000, ((sat % 100_000_000) / 10) as u32);
+ return Amount::new("BTC", sat / 100_000_000, (sat % 100_000_000) as u32);
}
}
@@ -71,11 +71,60 @@ impl TryFrom<Amount> for BtcAmount {
return Err("Wrong currency".to_string());
}
- let sat = value.value * 100_000_000 + value.fraction as u64 * 10;
+ let sat = value.value * 100_000_000 + value.fraction as u64;
return Ok(Self::from_sat(sat));
}
}
+fn encode_info(wtid: &[u8; 32], url: &Url) -> Vec<u8> {
+ let mut buffer = Vec::new();
+ buffer.extend_from_slice(wtid);
+ let parts = format!("{}{}", url.domain().unwrap_or(""), url.path());
+ let (packed, len) = uri_pack::pack_uri(&parts).unwrap();
+ buffer.push((url.scheme() == "http:") as u8);
+ buffer.push(len as u8);
+ buffer.extend_from_slice(&packed);
+ return buffer;
+}
+
+fn decode_info(bytes: &[u8]) -> ([u8; 32], Url) {
+ let len = bytes[33] as usize;
+ let mut packed = uri_pack::unpack_uri(&bytes[34..], len).unwrap();
+ packed.insert_str(0, "://");
+ if bytes[32] == 0 {
+ packed.insert(0, 's');
+ }
+ packed.insert_str(0, "http");
+ let url = Url::parse(&packed).unwrap();
+ return (bytes[..32].try_into().unwrap(), url);
+}
+
+#[cfg(test)]
+mod test {
+ use btc_wire::test::rand_key;
+ use url::Url;
+
+ use crate::{decode_info, encode_info};
+
+ #[test]
+ fn decode_encode_info() {
+ let key = rand_key();
+ let urls = [
+ "https://git.taler.net/",
+ "https://git.taler.net/depolymerization.git/",
+ ];
+
+ for url in urls {
+ let url = Url::parse(url).unwrap();
+ let encode = encode_info(&key, &url);
+ let decode = decode_info(&encode);
+ assert_eq!(key, decode.0);
+ assert_eq!(url, decode.1);
+ dbg!(encode.len() - 32, urls.len());
+ }
+ }
+}
+
#[tokio::main]
async fn main() {
let network = dirty_guess_network();
@@ -93,91 +142,99 @@ async fn main() {
// BTC worker thread
std::thread::spawn(move || {
- let rpc = wallet_rpc(network, "wire");
- let self_addr = rpc.get_new_address(None, None).unwrap();
- let mut last_hash: Option<BlockHash> = None;
- let confirmation = 1;
-
- loop {
- let txs = rpc
- .list_since_block(last_hash.as_ref(), Some(confirmation), None, Some(true))
- .unwrap();
- last_hash = Some(txs.lastblock);
-
- // List all confirmed send and receive transactions since last check
- let txs: HashMap<Txid, Category> = txs
- .transactions
- .into_iter()
- .filter_map(|tx| {
- let cat = tx.detail.category;
- (tx.info.confirmations >= confirmation as i32
- && (cat == Category::Send || cat == Category::Receive))
- .then(|| (tx.info.txid, cat))
- })
- .collect();
+ let result: Result<(), Box<dyn std::error::Error>> = (move || {
+ let rpc = wallet_rpc(network, "wire");
+ let self_addr = rpc.get_new_address(None, None)?;
+ let mut last_hash: Option<BlockHash> = None;
+ let confirmation = 1;
+
+ loop {
+ let txs =
+ rpc.list_since_block(last_hash.as_ref(), Some(confirmation), None, Some(true))?;
+ last_hash = Some(txs.lastblock);
- for (id, category) in txs {
- match category {
- Category::Send => match rpc.get_tx_op_return(&id) {
- Ok((full, wtid)) => {
- let credit_addr = sender_address(&rpc, &full).unwrap();
- let time = full.tx.info.blocktime.unwrap();
- let date =
- Timestamp::from(SystemTime::UNIX_EPOCH + Duration::from_secs(time));
- let amount = full.tx.amount.abs().to_unsigned().unwrap().into();
- let mut lock = state.outgoing.blocking_lock();
- println!("{} >> {} {}", &self_addr, &credit_addr, &amount);
- let array: [u8; 32] = wtid[..32].try_into().unwrap();
- let wtid = Base32::from(array);
- let row_id = lock.len() as u64 + 1;
- lock.push(OutgoingTransaction {
- row_id: SafeUint64::try_from(row_id).unwrap(),
- date,
- amount,
- debit_account: btc_payto_url(&self_addr),
- credit_account: btc_payto_url(&credit_addr),
- wtid,
- });
- }
- Err(err) => match err {
- GetOpReturnErr::MissingOpReturn => {} // ignore
- err => println!("send: {} {}", id, err),
+ // List all confirmed send and receive transactions since last check
+ let txs: HashMap<Txid, Category> = txs
+ .transactions
+ .into_iter()
+ .filter_map(|tx| {
+ let cat = tx.detail.category;
+ (tx.info.confirmations >= confirmation as i32
+ && (cat == Category::Send || cat == Category::Receive))
+ .then(|| (tx.info.txid, cat))
+ })
+ .collect();
+
+ for (id, category) in txs {
+ match category {
+ Category::Send => match rpc.get_tx_op_return(&id) {
+ Ok((full, metadata)) => {
+ let (wtid, exchange_base_url) = decode_info(&metadata);
+ let credit_addr = sender_address(&rpc, &full)?;
+ let time = full.tx.info.blocktime.unwrap();
+ let date = Timestamp::from(
+ SystemTime::UNIX_EPOCH + Duration::from_secs(time),
+ );
+ let amount = full.tx.amount.abs().to_unsigned()?.into();
+ let mut lock = state.outgoing.blocking_lock();
+ println!("{} >> {} {}", &self_addr, &credit_addr, &amount);
+ let array: [u8; 32] = wtid[..32].try_into()?;
+ let wtid = Base32::from(array);
+ let row_id = lock.len() as u64 + 1;
+ lock.push(OutgoingTransaction {
+ row_id: SafeUint64::try_from(row_id)?,
+ date,
+ amount,
+ debit_account: btc_payto_url(&self_addr),
+ credit_account: btc_payto_url(&credit_addr),
+ wtid,
+ exchange_base_url,
+ });
+ }
+ Err(err) => match err {
+ GetOpReturnErr::MissingOpReturn => {} // ignore
+ err => println!("send: {} {}", id, err),
+ },
},
- },
- Category::Receive => match rpc.get_tx_segwit_key(&id) {
- Ok((full, reserve_pub)) => {
- let debit_addr = sender_address(&rpc, &full).unwrap();
- let credit_addr = full.tx.details[0].address.as_ref().unwrap();
- let time = full.tx.info.blocktime.unwrap();
- let date =
- Timestamp::from(SystemTime::UNIX_EPOCH + Duration::from_secs(time));
- let amount = full.tx.amount.to_unsigned().unwrap().into();
- let mut lock = state.incoming.blocking_lock();
- println!("{} << {} {}", &debit_addr, &credit_addr, &amount);
- let row_id = lock.len() as u64 + 1;
- lock.push(IncomingTransaction {
- row_id: SafeUint64::try_from(row_id).unwrap(),
- date,
- amount,
- reserve_pub: reserve_pub.into(),
- debit_account: btc_payto_url(&debit_addr),
- credit_account: btc_payto_url(credit_addr),
- });
- }
- Err(err) => match err {
- GetSegwitErr::Decode(
- DecodeSegWitErr::MissingSegWitAddress
- | DecodeSegWitErr::NoMagicIdMatch,
- ) => {}
- err => println!("receive: {} {}", id, err),
+ Category::Receive => match rpc.get_tx_segwit_key(&id) {
+ Ok((full, reserve_pub)) => {
+ let debit_addr = sender_address(&rpc, &full)?;
+ let credit_addr = full.tx.details[0].address.as_ref().unwrap();
+ let time = full.tx.info.blocktime.unwrap();
+ let date = Timestamp::from(
+ SystemTime::UNIX_EPOCH + Duration::from_secs(time),
+ );
+ let amount: Amount = full.tx.amount.to_unsigned().unwrap().into();
+ dbg!(full.tx.amount.to_unsigned(), amount);
+ let amount = full.tx.amount.to_unsigned()?.into();
+ let mut lock = state.incoming.blocking_lock();
+ println!("{} << {} {}", &debit_addr, &credit_addr, &amount);
+ let row_id = lock.len() as u64 + 1;
+ lock.push(IncomingTransaction {
+ row_id: SafeUint64::try_from(row_id)?,
+ date,
+ amount,
+ reserve_pub: reserve_pub.into(),
+ debit_account: btc_payto_url(&debit_addr),
+ credit_account: btc_payto_url(credit_addr),
+ });
+ }
+ Err(err) => match err {
+ GetSegwitErr::Decode(
+ DecodeSegWitErr::MissingSegWitAddress
+ | DecodeSegWitErr::NoMagicIdMatch,
+ ) => {}
+ err => println!("receive: {} {}", id, err),
+ },
},
- },
- Category::Generate | Category::Immature | Category::Orphan => {}
+ Category::Generate | Category::Immature | Category::Orphan => {}
+ }
}
+ println!("Wait for block");
+ rpc.wait_for_new_block(0).ok();
}
- println!("Wait for block");
- rpc.wait_for_new_block(0).ok();
- }
+ })();
+ dbg!(result).unwrap();
});
let addr = ([0, 0, 0, 0], 8080).into();
@@ -234,6 +291,7 @@ struct OutgoingTransaction {
wtid: ShortHashCode,
debit_account: Url,
credit_account: Url,
+ exchange_base_url: Url,
}
struct ServerState {
@@ -310,8 +368,9 @@ async fn router(
ErrorCode::GENERIC_PARAMETER_MALFORMED,
)
})?;
+ let metadata = encode_info(&request.wtid, &request.exchange_base_url);
client
- .send_op_return(&to, amount, request.wtid.as_ref())
+ .send_op_return(&to, amount, &metadata)
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, ErrorCode::INVALID))?;
let timestamp = Timestamp::now();
json_response(
@@ -361,7 +420,7 @@ async fn router(
credit_account: tx.credit_account.clone(),
wtid: tx.wtid.clone(),
debit_account: tx.debit_account.clone(),
- exchange_base_url: Url::parse("http://localhost:8080").unwrap(),
+ exchange_base_url: tx.exchange_base_url.clone(),
})
.collect();
json_response(