depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit d815bbb983a93222ac62d3735762f2b7549222d5
parent 1a6786fdcd94d8bcdd8149176b4a6bf6e19f9ff5
Author: Antoine A <>
Date:   Wed,  5 Jan 2022 13:09:53 +0100

Improve documentation

Diffstat:
DREADME | 0
AREADME.md | 13+++++++++++++
Mbtc-wire/src/config.rs | 6++++--
Mbtc-wire/src/fail_point.rs | 4++--
Mbtc-wire/src/main.rs | 60+++++++++++++++++-------------------------------------------
Mbtc-wire/src/reconnect.rs | 36++++++++++++++++++++----------------
Mbtc-wire/src/rpc.rs | 28+++++++++++++++++-----------
Mbtc-wire/src/rpc_utils.rs | 7++-----
Mbtc-wire/src/status.rs | 8++++++--
Abtc-wire/src/taler_util.rs | 37+++++++++++++++++++++++++++++++++++++
Mscript/setup.sh | 4++--
Mscript/test_btc_fail.sh | 1+
Mscript/test_btc_fork.sh | 1+
Mscript/test_btc_lifetime.sh | 1+
Mscript/test_btc_reconnect.sh | 1+
Mscript/test_btc_stress.sh | 1+
Mscript/test_btc_wire.sh | 1+
Mscript/test_gateway.sh | 1+
Auri-pack/README.md | 25+++++++++++++++++++++++++
Muri-pack/benches/pack.rs | 8+++++++-
Muri-pack/src/lib.rs | 44++++++++++++++++++++++++++++++++++----------
Muri-pack/src/main.rs | 8+++++---
Awire-gateway/README.md | 32++++++++++++++++++++++++++++++++
Rwire-gateway/db/schema.sql -> wire-gateway/db/btc.sql | 0
Awire-gateway/db/common.sql | 22++++++++++++++++++++++
25 files changed, 252 insertions(+), 97 deletions(-)

diff --git a/README b/README diff --git a/README.md b/README.md @@ -0,0 +1,13 @@ +# Depolymerization + +## Install + +``` +make install +``` + +## Test + +``` +make test +``` diff --git a/btc-wire/src/config.rs b/btc-wire/src/config.rs @@ -8,7 +8,8 @@ use std::{ use bitcoin::Network; use taler_log::log::error; -fn rpc_dir(network: Network) -> &'static str { +/// Default chain dir <https://github.com/bitcoin/bitcoin/blob/master/doc/files.md#data-directory-location> +fn chain_dir(network: Network) -> &'static str { match network { Network::Bitcoin => "main", Network::Testnet => "testnet3", @@ -17,6 +18,7 @@ fn rpc_dir(network: Network) -> &'static str { } } +/// Default rpc port <https://github.com/bitcoin/bitcoin/blob/master/share/examples/bitcoin.conf> fn rpc_port(network: Network) -> u16 { match network { Network::Bitcoin => 8332, @@ -80,7 +82,7 @@ impl BitcoinConfig { Ok(Self { network, addr, - dir: data_dir.as_ref().join(rpc_dir(network)), + dir: data_dir.as_ref().join(chain_dir(network)), }) } } diff --git a/btc-wire/src/fail_point.rs b/btc-wire/src/fail_point.rs @@ -1,6 +1,7 @@ #[cfg(feature = "fail")] use taler_log::log::warn; +/// Inject random failure when 'fail' feature is used #[allow(unused_variables)] pub fn fail_point(msg: &'static str, prob: f32) -> Result<(), &'static str> { #[cfg(feature = "fail")] @@ -11,4 +12,4 @@ pub fn fail_point(msg: &'static str, prob: f32) -> Result<(), &'static str> { }; return Ok(()); -} -\ No newline at end of file +} diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -1,4 +1,4 @@ -use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, Network, SignedAmount, Txid}; +use bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, Network, Txid}; use btc_wire::{ config::BitcoinConfig, rpc::{self, BtcRpc, Category, ErrorCode}, @@ -19,46 +19,23 @@ use std::{ use taler_api::api_common::{base32, Amount}; use taler_config::Config; use taler_log::log::{error, info, warn}; +use taler_util::btc_payto_url; use url::Url; use crate::{ fail_point::fail_point, info::{encode_info, Info}, status::{BounceStatus, TxStatus}, + taler_util::{btc_payto_addr, btc_to_taler, taler_to_btc}, }; mod fail_point; mod info; mod reconnect; mod status; +mod taler_util; -fn btc_payto_url(addr: &Address) -> Url { - Url::from_str(&format!("payto://bitcoin/{}", addr)).unwrap() -} - -fn btc_payto_addr(url: &Url) -> Result<Address, String> { - if url.domain() != Some("bitcoin") { - return Err("".to_string()); - } - let str = url.path().trim_start_matches('/'); - return Address::from_str(str).map_err(|_| "".to_string()); -} - -fn btc_amount_to_taler_amount(amount: &SignedAmount) -> Amount { - let unsigned = amount.abs().to_unsigned().unwrap(); - let sat = unsigned.as_sat(); - return Amount::new("BTC", sat / 100_000_000, (sat % 100_000_000) as u32); -} - -fn taler_amount_to_btc_amount(amount: &Amount) -> Result<BtcAmount, String> { - if amount.currency != "BTC" { - return Err("Wrong currency".to_string()); - } - - let sat = amount.value * 100_000_000 + amount.fraction as u64; - return Ok(BtcAmount::from_sat(sat)); -} - +/// Retrieve last stored hash fn last_hash(db: &mut Client) -> Result<Option<BlockHash>, postgres::Error> { Ok(db .query_opt("SELECT value FROM state WHERE name='last_hash'", &[])? @@ -67,7 +44,7 @@ fn last_hash(db: &mut Client) -> Result<Option<BlockHash>, postgres::Error> { /// Listen for new proposed transactions and announce them on the bitcoin network fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) { - // Send a transaction on the blockchain, return true if more transactions with the same status remains + /// Send a transaction on the blockchain, return true if more transactions with the same status remains fn send( db: &mut Client, rpc: &mut BtcRpc, @@ -82,7 +59,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) )?; if let Some(row) = &row { let id: i32 = row.get(0); - let amount = taler_amount_to_btc_amount(&Amount::from_str(row.get(1))?)?; + let amount = taler_to_btc(&Amount::from_str(row.get(1))?)?; let wtid: &[u8] = row.get(2); let addr: Address = btc_payto_addr(&Url::parse(row.get(3))?)?; let exchange_base_url: Url = Url::parse(row.get(4))?; @@ -100,7 +77,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) "UPDATE tx_out SET status=$1, txid=$2 WHERE id=$3", &[&(TxStatus::Sent as i16), &tx_id.as_ref(), &id], )?; - let amount = btc_amount_to_taler_amount(&amount.to_signed().unwrap()); + let amount = btc_to_taler(&amount.to_signed().unwrap()); info!(">> {} {} in {} to {}", amount, base32(wtid), tx_id, addr); } Err(e) => { @@ -116,7 +93,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) Ok(row.is_some()) } - // Bounce a transaction on the blockchain, return true if more bounce with the same status remains + /// Bounce a transaction on the blockchain, return true if more bounce with the same status remains fn bounce( db: &mut Client, rpc: &mut BtcRpc, @@ -166,7 +143,7 @@ fn worker(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, config: &Config) // TODO check if transactions are abandoned - let mut lifetime = config.btc_lifetime.clone(); + let mut lifetime = config.btc_lifetime; // Alway start with a sync work let mut skip_notification = true; @@ -287,7 +264,7 @@ fn sync_chain( match info { Info::Transaction { wtid, .. } => { let addr = full.details[0].address.as_ref().unwrap(); - let amount = btc_amount_to_taler_amount(&full.amount); + let amount = btc_to_taler(&full.amount); let row = tx.query_opt( "SELECT id, status FROM tx_out WHERE wtid=$1 FOR UPDATE", @@ -381,7 +358,7 @@ fn sync_chain( let debit_addr = sender_address(rpc, &full)?; let credit_addr = full.details[0].address.as_ref().unwrap(); let date = SystemTime::UNIX_EPOCH + Duration::from_secs(full.time); - let amount = btc_amount_to_taler_amount(&full.amount); + let amount = btc_to_taler(&full.amount); let nb = db.execute("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (reserve_pub) DO NOTHING ", &[ &date, &amount.to_string(), &reserve_pub.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref() ])?; @@ -441,6 +418,7 @@ fn sync_chain( Ok(()) } +/// Wait for new block and notify arrival with postgreSQL notifications fn block_listener(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql) { loop { let result: Result<(), Box<dyn std::error::Error>> = (|| { @@ -487,15 +465,11 @@ fn main() { let mut rpc = BtcRpc::common(&btc_config).unwrap(); rpc.load_wallet(&config.btc_wallet).ok(); - let rpc_listener = AutoReconnectRPC::new( - btc_config.clone(), - &config.btc_wallet, - Duration::from_secs(5), - ); - let rpc_worker = AutoReconnectRPC::new(btc_config, &config.btc_wallet, Duration::from_secs(5)); + let rpc_listener = AutoReconnectRPC::new(btc_config.clone(), &config.btc_wallet); + let rpc_worker = AutoReconnectRPC::new(btc_config, &config.btc_wallet); - let db_listener = AutoReconnectSql::new(&config.db_url, Duration::from_secs(5)); - let db_worker = AutoReconnectSql::new(&config.db_url, Duration::from_secs(5)); + let db_listener = AutoReconnectSql::new(&config.db_url); + let db_worker = AutoReconnectSql::new(&config.db_url); std::thread::spawn(move || block_listener(rpc_listener, db_listener)); worker(rpc_worker, db_worker, config); } diff --git a/btc-wire/src/reconnect.rs b/btc-wire/src/reconnect.rs @@ -1,86 +1,90 @@ +//! Wrapper for RPC or PostgreSQL connection which automatically reconnect on error + use std::time::Duration; use btc_wire::{config::BitcoinConfig, rpc::BtcRpc}; use postgres::{Client, NoTls}; use taler_log::log::error; +const RECONNECT_DELAY: Duration = Duration::from_secs(5); + +/// auto-reconnect RPC pub struct AutoReconnectRPC { - delay: Duration, config: BitcoinConfig, wallet: String, client: BtcRpc, } impl AutoReconnectRPC { - pub fn new(config: BitcoinConfig, wallet: impl Into<String>, delay: Duration) -> Self { + pub fn new(config: BitcoinConfig, wallet: impl Into<String>) -> Self { let wallet: String = wallet.into(); Self { - client: Self::connect(&config, &wallet, delay), + client: Self::connect(&config, &wallet), wallet, - delay, config, } } /// Connect a new client, loop on error - fn connect(config: &BitcoinConfig, wallet: &str, delay: Duration) -> BtcRpc { + fn connect(config: &BitcoinConfig, wallet: &str) -> BtcRpc { loop { match BtcRpc::wallet(config, wallet) { Ok(mut new) => match new.net_info() { Ok(_) => return new, Err(err) => { error!("connect: RPC - {}", err); - std::thread::sleep(delay); + std::thread::sleep(RECONNECT_DELAY); } }, Err(err) => { error!("connect: RPC - {}", err); - std::thread::sleep(delay); + std::thread::sleep(RECONNECT_DELAY); } } } } + /// Get a mutable connection, block until a connection can be established pub fn client(&mut self) -> &mut BtcRpc { if self.client.net_info().is_err() { - self.client = Self::connect(&self.config, &self.wallet, self.delay); + self.client = Self::connect(&self.config, &self.wallet); } &mut self.client } } +/// auto-reconnect SQL pub struct AutoReconnectSql { - delay: Duration, config: String, client: Client, } impl AutoReconnectSql { - pub fn new(config: impl Into<String>, delay: Duration) -> Self { + pub fn new(config: impl Into<String>) -> Self { let config: String = config.into(); Self { - client: Self::connect(&config, delay), + client: Self::connect(&config), config, - delay, } } /// Connect a new client, loop on error - fn connect(config: &str, delay: Duration) -> Client { + fn connect(config: &str) -> Client { loop { match Client::connect(config, NoTls) { Ok(new) => return new, Err(err) => { error!("connect: DB - {}", err); - std::thread::sleep(delay); + std::thread::sleep(RECONNECT_DELAY); } } } } + /// Get a mutable connection, block until a connection can be established pub fn client(&mut self) -> &mut Client { - if self.client.is_valid(self.delay).is_err() { - self.client = Self::connect(&self.config, self.delay); + if self.client.is_valid(RECONNECT_DELAY).is_err() { + self.client = Self::connect(&self.config); } &mut self.client } diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs @@ -1,20 +1,23 @@ +//! This is a very simple RPC client designed only for a specific bitcoind version +//! and to use on an secure localhost connection to a trusted node +//! +//! No http format of body length check as we trust the node output +//! No asynchronous request as bitcoind put requests in a queue and process +//! them synchronously and we do not want to fill this queue +//! +//! We only parse the thing we actually use, this reduce memory usage and +//! make our code more compatible with future deprecation + use bitcoin::{hashes::hex::ToHex, Address, Amount, BlockHash, SignedAmount, Txid}; use serde_json::{json, Value}; use std::{ fmt::Debug, io::{self, BufRead, BufReader, Write}, - net::TcpStream, + net::TcpStream, ops::Not, }; use crate::config::BitcoinConfig; -// This is a very simple RPC client designed only for a specific bitcoind version -// and to use on an secure localhost connection to a trusted node -// -// No http format of body length check as we trust the node output -// No asynchronous request as bitcoind put requests in a queue and process -// them synchronously and we do not want to fill this queue - #[derive(Debug, serde::Serialize)] struct BtcRequest<'a, T: serde::Serialize> { method: &'a str, @@ -49,6 +52,7 @@ pub type Result<T> = std::result::Result<T, Error>; const EMPTY: [(); 0] = []; +/// Bitcoin RPC connection pub struct BtcRpc { path: String, id: u64, @@ -57,10 +61,12 @@ pub struct BtcRpc { } impl BtcRpc { + /// Start a RPC connection pub fn common(config: &BitcoinConfig) -> io::Result<Self> { Self::new(config, None) } + /// Start a wallet RPC connection pub fn wallet(config: &BitcoinConfig, wallet: &str) -> io::Result<Self> { Self::new(config, Some(wallet)) } @@ -144,7 +150,7 @@ impl BtcRpc { } } - pub fn net_info(&mut self) -> Result<Empty> { + pub fn net_info(&mut self) -> Result<Nothing> { self.call("getnetworkinfo", &EMPTY) } @@ -164,7 +170,7 @@ impl BtcRpc { self.call("generatetoaddress", &(nb, address)) } - pub fn wait_for_new_block(&mut self, timeout: u64) -> Result<Empty> { + pub fn wait_for_new_block(&mut self, timeout: u64) -> Result<Nothing> { self.call("waitfornewblock", &[timeout]) } @@ -357,7 +363,7 @@ pub struct HexWrapper { } #[derive(Debug, serde::Deserialize)] -pub struct Empty {} +pub struct Nothing {} /// Bitcoin RPC error codes <https://github.com/bitcoin/bitcoin/blob/master/src/rpc/protocol.h> #[derive(Debug, Clone, Copy, PartialEq, Eq, serde_repr::Deserialize_repr)] diff --git a/btc-wire/src/rpc_utils.rs b/btc-wire/src/rpc_utils.rs @@ -7,8 +7,8 @@ use crate::rpc::{self, BtcRpc, TransactionFull}; pub const CLIENT: &str = "client"; pub const WIRE: &str = "wire"; +/// Default bitcoin data_dir <https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md> pub fn default_data_dir() -> PathBuf { - // https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#:~:text=By%20default%2C%20the%20configuration%20file,file%3E%20option%20in%20the%20bitcoin. if cfg!(target_os = "windows") { PathBuf::from_str(&std::env::var("APPDATA").unwrap()) .unwrap() @@ -25,16 +25,13 @@ pub fn default_data_dir() -> PathBuf { unimplemented!("Only windows, linux or macos") } } + /// Minimum dust amount to perform a transaction to a segwit address pub fn segwit_min_amount() -> Amount { // https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp return Amount::from_sat(294); } -pub fn check_address(addr: &str) -> bool { - Address::from_str(addr).is_ok() -} - /// Get the first sender address from a raw transaction pub fn sender_address(rpc: &mut BtcRpc, full: &TransactionFull) -> rpc::Result<Address> { let first = &full.decoded.vin[0]; diff --git a/btc-wire/src/status.rs b/btc-wire/src/status.rs @@ -1,7 +1,10 @@ +//! Transactions status in database + +/// Outgoing transaction status #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TxStatus { - /// Client have ask for a transaction (default status) + /// Client have ask for a transaction (default) Requested = 0, /// The wire failed to send this transaction and will try later Delayed = 1, @@ -22,10 +25,11 @@ impl TryFrom<u8> for TxStatus { } } +/// Bounce transaction status #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BounceStatus { - /// Bounce have been requested (default status) + /// Bounce have been requested (default) Requested = 0, /// The wire failed to send this bounce and will try later Delayed = 1, diff --git a/btc-wire/src/taler_util.rs b/btc-wire/src/taler_util.rs @@ -0,0 +1,37 @@ +//! Utils function to convert taler API types to bitcoin API types + +use bitcoin::{Address, Amount as BtcAmount, SignedAmount}; +use std::str::FromStr; +use taler_api::api_common::Amount; +use url::Url; + +/// Generate a payto uri from a btc address +pub fn btc_payto_url(addr: &Address) -> Url { + Url::from_str(&format!("payto://bitcoin/{}", addr)).unwrap() +} + +/// Extract a btc address from a payto uri +pub fn btc_payto_addr(url: &Url) -> Result<Address, String> { + if url.domain() != Some("bitcoin") { + return Err("".to_string()); + } + let str = url.path().trim_start_matches('/'); + return Address::from_str(str).map_err(|_| "".to_string()); +} + +/// Transform a btc amount into a taler amount +pub fn btc_to_taler(amount: &SignedAmount) -> Amount { + let unsigned = amount.abs().to_unsigned().unwrap(); + let sat = unsigned.as_sat(); + return Amount::new("BTC", sat / 100_000_000, (sat % 100_000_000) as u32); +} + +/// Transform a taler amount into a btc amount +pub fn taler_to_btc(amount: &Amount) -> Result<BtcAmount, String> { + if amount.currency != "BTC" { + return Err("Wrong currency".to_string()); + } + + let sat = amount.value * 100_000_000 + amount.fraction as u64; + return Ok(BtcAmount::from_sat(sat)); +} diff --git a/script/setup.sh b/script/setup.sh @@ -60,12 +60,12 @@ function setup_db() { echo "port=5454" >> $DB_DIR/postgresql.conf pg_ctl start -D $DB_DIR > /dev/null echo "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'" | psql -p 5454 postgres > /dev/null - psql $DB_URL < wire-gateway/db/schema.sql &> /dev/null + psql $DB_URL < wire-gateway/db/${SCHEMA:-common.sql} &> /dev/null } # Erase database function reset_db() { - psql $DB_URL < wire-gateway/db/schema.sql > /dev/null + psql $DB_URL < wire-gateway/db/${SCHEMA:-common.sql} > /dev/null } # ----- Bitcoin node ----- # diff --git a/script/test_btc_fail.sh b/script/test_btc_fail.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup fail -----" echo "Load config file" diff --git a/script/test_btc_fork.sh b/script/test_btc_fork.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup -----" echo "Load config file" diff --git a/script/test_btc_lifetime.sh b/script/test_btc_lifetime.sh @@ -7,6 +7,7 @@ CONFIG=taler_lifetime.conf set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup -----" echo "Load config file" diff --git a/script/test_btc_reconnect.sh b/script/test_btc_reconnect.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup -----" echo "Load config file" diff --git a/script/test_btc_stress.sh b/script/test_btc_stress.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup stressed -----" echo "Load config file" diff --git a/script/test_btc_wire.sh b/script/test_btc_wire.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql echo "----- Setup -----" echo "Load config file" diff --git a/script/test_gateway.sh b/script/test_gateway.sh @@ -18,6 +18,7 @@ trap cleanup EXIT source "${BASH_SOURCE%/*}/setup.sh" ADDRESS=mpTJZxWPerz1Gife6mQSdHT8mMuJK6FP85 +SCHEMA=btc.sql echo "----- Setup -----" echo "Load config file" diff --git a/uri-pack/README.md b/uri-pack/README.md @@ -0,0 +1,24 @@ +# uri-pack + +uri-pack is an efficient binary format for URI + +## Format + +Most commonly used characters (a-z . / - %) are encoded using 5b, remaining +ascii characters are encoded using 11b.If more than half the characters in an +uri are encoded with 5b, the encoded size is smaller than a simple ascii +format. + +On the majestic_million database, 98.77% of the domain name where smaller, +going from an average of 14b to an average of 10b. + +## Usage + +``` rust +use uri_pack::{pack_uri, unpack_uri}; + +let domain = "http://example.com/static_file/image.png"; +let encoded = pack_uri(domain).unwrap(); +let decoded = unpack_uri(&encoded).unwrap(); +assert_eq!(domain, decoded); +``` +\ No newline at end of file diff --git a/uri-pack/benches/pack.rs b/uri-pack/benches/pack.rs @@ -1,5 +1,11 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use uri_pack::{pack_uri, unpack_uri, PACKED}; +use uri_pack::{pack_uri, unpack_uri}; + +/// Ascii char that can be packed into 5 bits +pub(crate) const PACKED: [u8; 30] = [ + b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', + b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'.', b'/', b'-', b'%', +]; fn rand_compat(size: usize) -> String { String::from_utf8( diff --git a/uri-pack/src/lib.rs b/uri-pack/src/lib.rs @@ -1,3 +1,28 @@ +//! +//! uri-pack is an efficient binary format for URI +//! +//! ## Format +//! +//! Most commonly used characters (a-z . / - %) are encoded using 5b, remaining +//! ascii characters are encoded using 11b.If more than half the characters in an +//! uri are encoded with 5b, the encoded size is smaller than a simple ascii +//! format. +//! +//! On the majestic_million database, 98.77% of the domain name where smaller, +//! going from an average of 14b to an average of 10b. +//! +//! ## Usage +//! +//! ``` rust +//! use uri_pack::{pack_uri, unpack_uri}; +//! +//! let domain = "http://example.com/static_file/image.png"; +//! let encoded = pack_uri(domain).unwrap(); +//! let decoded = unpack_uri(&encoded).unwrap(); +//! assert_eq!(domain, decoded); +//! ``` +//! + /// Pack an URI ascii char /// Panic if char not supported fn pack_ascii(c: u8) -> u8 { @@ -29,14 +54,9 @@ fn supported_ascii(c: &u8) -> bool { } /// Extended packing limit -pub const EXTENDED: u8 = 30; +const EXTENDED: u8 = 30; /// EOF u5 encoding -pub const TERMINATOR: u8 = 31; -/// Ascii char that can be packed into 5 bits -pub const PACKED: [u8; 30] = [ - b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', - b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'.', b'/', b'-', b'%', -]; +const TERMINATOR: u8 = 31; #[derive(Debug, Clone, Copy, thiserror::Error)] pub enum EncodeErr { @@ -165,9 +185,13 @@ mod test { use serde_json::Value; - use crate::{ - pack_ascii, pack_uri, supported_ascii, unpack_ascii, unpack_uri, EXTENDED, PACKED, - }; + use crate::{pack_ascii, pack_uri, supported_ascii, unpack_ascii, unpack_uri, EXTENDED}; + + /// Ascii char that can be packed into 5 bits + const PACKED: [u8; 30] = [ + b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'.', b'/', b'-', b'%', + ]; #[test] /// Check support every packable ascii character is packed diff --git a/uri-pack/src/main.rs b/uri-pack/src/main.rs @@ -2,6 +2,8 @@ use std::cmp::Ordering; use uri_pack::pack_uri; +/// Compute efficiency statistics of uri-pack using domains from majestic_million + fn main() { let mut majestic = csv::Reader::from_reader(include_str!("majestic_million.csv").as_bytes()); let mut ascii_counter = [0u64; 255]; @@ -20,9 +22,9 @@ fn main() { before_len += before; after_len += after; match before.cmp(&after) { - Ordering::Less => smaller += 1, + Ordering::Less => bigger += 1, Ordering::Equal => same += 1, - Ordering::Greater => bigger += 1, + Ordering::Greater => smaller += 1, } } let sum: u64 = ascii_counter.iter().sum(); @@ -44,7 +46,7 @@ fn main() { } let count = bigger + smaller + same; println!( - "\nBefore ~{}B After ~{}B\nBigger {:.2}% Same {:.2}% Smaller {:.2}%", + "\nBefore ~{}b After ~{}b\nBigger {:.2}% Same {:.2}% Smaller {:.2}%", before_len / count, after_len / count, (bigger as f32 / count as f32 * 100.), diff --git a/wire-gateway/README.md b/wire-gateway/README.md @@ -0,0 +1,31 @@ +# wire-gateway + +Rust server for [Taler Wire Gateway HTTP API](https://docs.taler.net/core/api-wire.html) + +## Database schema + +The server is wire implementation agnostic, it only require an postgres database with following schema: + +```sql +-- Incoming transactions +CREATE TABLE tx_in ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + reserve_pub BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL +); + +-- Outgoing transactions +CREATE TABLE tx_out ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + wtid BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL, + exchange_url TEXT NOT NULL, + request_uid BYTEA NOT NULL UNIQUE +); +``` +\ No newline at end of file diff --git a/wire-gateway/db/schema.sql b/wire-gateway/db/btc.sql diff --git a/wire-gateway/db/common.sql b/wire-gateway/db/common.sql @@ -0,0 +1,21 @@ +-- Incoming transactions +CREATE TABLE tx_in ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + reserve_pub BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL +); + +-- Outgoing transactions +CREATE TABLE tx_out ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + wtid BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL, + exchange_url TEXT NOT NULL, + request_uid BYTEA NOT NULL UNIQUE +); +\ No newline at end of file