diff --git a/Cargo.lock b/Cargo.lock index 2a7812e..ed8c4a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2483,6 +2483,7 @@ dependencies = [ "grin_keychain 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)", "grin_secp256k1zkp", "grin_servers", + "grin_store 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)", "grin_util 5.1.1", "grin_wallet_api", "grin_wallet_impls", diff --git a/Cargo.toml b/Cargo.toml index 1d704e8..e9ef822 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ grin_core = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alp grin_chain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_keychain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_servers = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } +grin_store = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 681f04d..765adf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ mod onion; mod rpc; mod secp; mod server; +mod store; mod types; mod wallet; diff --git a/src/onion.rs b/src/onion.rs index 935778f..a81c066 100644 --- a/src/onion.rs +++ b/src/onion.rs @@ -4,7 +4,7 @@ use crate::types::Payload; use crate::onion::OnionError::{InvalidKeyLength, SerializationError}; use chacha20::cipher::{NewCipher, StreamCipher}; use chacha20::{ChaCha20, Key, Nonce}; -use grin_core::ser::{self, ProtocolVersion, Writeable, Writer}; +use grin_core::ser::{self, ProtocolVersion, Readable, Reader, Writeable, Writer}; use grin_util::{self, ToHex}; use hmac::digest::InvalidLength; use hmac::{Hmac, Mac}; @@ -122,6 +122,23 @@ impl Writeable for Onion { } } +impl Readable for Onion { + fn read(reader: &mut R) -> Result { + let ephemeral_pubkey = PublicKey::read(reader)?; + let commit = Commitment::read(reader)?; + let mut enc_payloads: Vec = Vec::new(); + let len = reader.read_u64()?; + for _ in 0..len { + enc_payloads.push(RawBytes::read(reader)?); + } + Ok(Onion { + ephemeral_pubkey, + commit, + enc_payloads, + }) + } +} + impl serde::ser::Serialize for Onion { fn serialize(&self, serializer: S) -> Result where diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..f04971a --- /dev/null +++ b/src/store.rs @@ -0,0 +1,152 @@ +use crate::onion::Onion; +use crate::secp::{self, Commitment, RangeProof, SecretKey}; + +use grin_core::core::Input; +use grin_core::ser::{self, ProtocolVersion, Readable, Reader, Writeable, Writer}; +use grin_store::{self as store, Store}; +use grin_util::ToHex; +use thiserror::Error; + +const DB_NAME: &str = "swap"; +const STORE_SUBPATH: &str = "swaps"; + +const CURRENT_VERSION: u8 = 0; +const SWAP_PREFIX: u8 = b'S'; + +/// Data needed to swap a single output. +#[derive(Clone, Debug, PartialEq)] +pub struct SwapData { + /// The total excess for the output commitment + pub excess: SecretKey, + /// The derived output commitment after applying excess and fee + pub output_commit: Commitment, + /// The rangeproof, included only for the final hop (node N) + pub rangeproof: Option, + /// Transaction input being spent + pub input: Input, + /// Transaction fee + pub fee: u64, + /// The remaining onion after peeling off our layer + pub onion: Onion, + // todo: include a SwapStatus enum value +} + +impl Writeable for SwapData { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + writer.write_u8(CURRENT_VERSION)?; + writer.write_fixed_bytes(&self.excess)?; + writer.write_fixed_bytes(&self.output_commit)?; + + // todo: duplicated in payload. Can we impl Writeable for Option? + match &self.rangeproof { + Some(proof) => { + writer.write_u8(1)?; + proof.write(writer)?; + } + None => writer.write_u8(0)?, + }; + + self.input.write(writer)?; + writer.write_u64(self.fee.into())?; + self.onion.write(writer)?; + + Ok(()) + } +} + +impl Readable for SwapData { + fn read(reader: &mut R) -> Result { + let version = reader.read_u8()?; + if version != CURRENT_VERSION { + return Err(ser::Error::UnsupportedProtocolVersion); + } + + let excess = secp::read_secret_key(reader)?; + let output_commit = Commitment::read(reader)?; + let rangeproof = if reader.read_u8()? == 0 { + None + } else { + Some(RangeProof::read(reader)?) + }; + let input = Input::read(reader)?; + let fee = reader.read_u64()?; + let onion = Onion::read(reader)?; + + Ok(SwapData { + excess, + output_commit, + rangeproof, + input, + fee, + onion, + }) + } +} + +/// Storage facility for swap data. +pub struct SwapStore { + db: Store, +} + +/// Store error types +#[derive(Clone, Error, Debug, PartialEq)] +pub enum StoreError { + #[error("Error occurred while attempting to open db: {0}")] + OpenError(store::lmdb::Error), + #[error("Serialization error occurred: {0}")] + SerializationError(ser::Error), + #[error("Error occurred while attempting to read from db: {0}")] + ReadError(store::lmdb::Error), + #[error("Error occurred while attempting to write to db: {0}")] + WriteError(store::lmdb::Error), +} + +impl From for StoreError { + fn from(e: ser::Error) -> StoreError { + StoreError::SerializationError(e) + } +} + +impl SwapStore { + /// Create new chain store + #[allow(dead_code)] + pub fn new(db_root: &str) -> Result { + let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None) + .map_err(StoreError::OpenError)?; + Ok(SwapStore { db }) + } + + /// Writes a single key-value pair to the database + fn write>( + &self, + prefix: u8, + k: K, + value: &Vec, + ) -> Result<(), store::lmdb::Error> { + let batch = self.db.batch()?; + batch.put(&store::to_key(prefix, k)[..], &value[..])?; + batch.commit() + } + + /// Reads a single value by key + fn read + Copy, V: Readable>(&self, prefix: u8, k: K) -> Result { + store::option_to_not_found(self.db.get_ser(&store::to_key(prefix, k)[..], None), || { + format!("{}:{}", prefix, k.to_hex()) + }) + .map_err(StoreError::ReadError) + } + + /// Saves a swap to the database + #[allow(dead_code)] + pub fn save_swap(&self, s: &SwapData) -> Result<(), StoreError> { + let data = ser::ser_vec(&s, ProtocolVersion::local())?; + self.write(SWAP_PREFIX, &s.output_commit, &data) + .map_err(StoreError::WriteError) + } + + /// Reads a swap from the database + #[allow(dead_code)] + pub fn get_swap(&self, commit: &Commitment) -> Result { + self.read(SWAP_PREFIX, commit) + } +}