diff --git a/Cargo.lock b/Cargo.lock index a5f46e48f..7cd4a05df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.0" @@ -304,14 +306,14 @@ checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" [[package]] name = "bulletproofs" version = "2.0.0" -source = "git+https://github.com/eranrund/bulletproofs?rev=e8e8ef45ecc6d31f1a9525140edc977351d0f780#e8e8ef45ecc6d31f1a9525140edc977351d0f780" +source = "git+https://github.com/eranrund/bulletproofs?rev=8a7c9cdd1efafa3ad68cd65676302f925de68373#8a7c9cdd1efafa3ad68cd65676302f925de68373" dependencies = [ "byteorder", "clear_on_drop", "curve25519-dalek", "digest", "merlin", - "rand_core 0.5.1", + "rand_core 0.6.2", "serde", "serde_derive", "sha3", @@ -626,14 +628,14 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.0" +version = "4.0.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307" +checksum = "78f72b0f5a8f0cc80b7e4b6d98efe1adc0b7b151a618ecd7cd4d18e6d776e31a" dependencies = [ "byteorder", "digest", - "packed_simd", - "rand_core 0.5.1", + "packed_simd_2", + "rand_core 0.6.2", "serde", "subtle", "zeroize", @@ -820,12 +822,11 @@ dependencies = [ [[package]] name = "ed25519-dalek" version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +source = "git+https://github.com/eranrund/ed25519-dalek.git?rev=484369672f45d776fe13fdd17618aed2f4047909#484369672f45d776fe13fdd17618aed2f4047909" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", + "rand 0.8.3", "serde", "serde_bytes", "sha2", @@ -1126,6 +1127,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + [[package]] name = "ghash" version = "0.3.0" @@ -1579,6 +1591,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libsqlite3-sys" version = "0.22.1" @@ -1727,7 +1745,7 @@ dependencies = [ "mc-util-repr-bytes", "mc-util-serial", "prost", - "rand_core 0.5.1", + "rand_core 0.6.2", "zeroize", ] @@ -1781,7 +1799,7 @@ dependencies = [ "mc-util-build-script", "mc-util-build-sgx", "prost", - "rand_core 0.5.1", + "rand_core 0.6.2", "serde", ] @@ -1828,8 +1846,8 @@ dependencies = [ "mc-sgx-types", "mc-util-encodings", "prost", - "rand 0.7.3", - "rand_hc 0.2.0", + "rand 0.8.3", + "rand_hc 0.3.0", "rjson", "serde", "sha2", @@ -1867,7 +1885,7 @@ dependencies = [ "mc-util-build-info", "mc-util-logger-macros", "mc-util-serial", - "rand_core 0.5.1", + "rand_core 0.6.2", "sentry", "serde", "sha3", @@ -1957,8 +1975,8 @@ dependencies = [ "mc-util-from-random", "mc-util-serial", "mockall", - "rand 0.7.3", - "rand_hc 0.2.0", + "rand 0.8.3", + "rand_hc 0.3.0", "serde", "serde_json", ] @@ -1974,7 +1992,7 @@ dependencies = [ "hkdf", "mc-crypto-ct-aead", "mc-crypto-keys", - "rand_core 0.5.1", + "rand_core 0.6.2", ] [[package]] @@ -2042,8 +2060,8 @@ dependencies = [ "mc-crypto-digestible-signature", "mc-util-from-random", "mc-util-repr-bytes", - "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", "schnorrkel", "serde", "sha2", @@ -2064,7 +2082,7 @@ dependencies = [ "hkdf", "mc-crypto-keys", "mc-util-from-random", - "rand_core 0.5.1", + "rand_core 0.6.2", "secrecy", "serde", "sha2", @@ -2077,10 +2095,10 @@ name = "mc-crypto-rand" version = "1.0.1-pre1" dependencies = [ "cfg-if 0.1.10", - "getrandom", - "rand 0.7.3", - "rand_core 0.5.1", - "rand_hc 0.2.0", + "getrandom 0.1.15", + "rand 0.8.3", + "rand_core 0.6.2", + "rand_hc 0.3.0", ] [[package]] @@ -2239,7 +2257,7 @@ dependencies = [ "mc-util-uri", "num_cpus", "prost", - "rand 0.7.3", + "rand 0.8.3", "reqwest", "retry", "rocket", @@ -2273,8 +2291,8 @@ dependencies = [ "mc-util-serial", "mockall", "prost", - "rand 0.7.3", - "rand_core 0.5.1", + "rand 0.8.3", + "rand_core 0.6.2", ] [[package]] @@ -2297,7 +2315,7 @@ dependencies = [ "mc-util-uri", "mockall", "protobuf", - "rand 0.7.3", + "rand 0.8.3", "reqwest", "retry", "serde", @@ -2348,7 +2366,7 @@ dependencies = [ "num_cpus", "prost", "protobuf", - "rand 0.7.3", + "rand 0.8.3", "reqwest", "retry", "serde_json", @@ -2441,7 +2459,7 @@ dependencies = [ "mc-util-serial", "merlin", "prost", - "rand_core 0.5.1", + "rand_core 0.6.2", "serde", "subtle", "zeroize", @@ -2459,7 +2477,7 @@ dependencies = [ "mc-transaction-core", "mc-transaction-std", "mc-util-from-random", - "rand 0.7.3", + "rand 0.8.3", "tempdir", ] @@ -2477,8 +2495,8 @@ dependencies = [ "mc-util-from-random", "mc-util-serial", "prost", - "rand 0.7.3", - "rand_core 0.5.1", + "rand 0.8.3", + "rand_core 0.6.2", "zeroize", ] @@ -2495,7 +2513,7 @@ dependencies = [ "mc-util-build-script", "mc-util-build-sgx", "pkg-config", - "rand 0.7.3", + "rand 0.8.3", ] [[package]] @@ -2548,7 +2566,7 @@ dependencies = [ name = "mc-util-from-random" version = "1.0.1-pre1" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.6.2", ] [[package]] @@ -2572,7 +2590,7 @@ dependencies = [ "mc-util-uri", "prometheus", "protobuf", - "rand 0.6.5", + "rand 0.8.3", "sha2", "signal-hook", "subtle", @@ -2707,13 +2725,13 @@ dependencies = [ [[package]] name = "merlin" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6feca46f4fa3443a01769d768727f10c10a20fdb65e52dc16a81f0c8269bb78" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.5.1", + "rand_core 0.6.2", "zeroize", ] @@ -2945,12 +2963,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "packed_simd" -version = "0.3.3" +name = "packed_simd_2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" +checksum = "3278e0492f961fd4ae70909f56b2723a7e8d01a228427294e19cdfdebda89a17" dependencies = [ "cfg-if 0.1.10", + "libm", ] [[package]] @@ -3352,13 +3371,25 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.15", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + [[package]] name = "rand_chacha" version = "0.1.1" @@ -3379,6 +3410,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -3400,7 +3441,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.2", ] [[package]] @@ -3421,6 +3471,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -3529,7 +3588,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ - "getrandom", + "getrandom 0.1.15", "redox_syscall", "rust-argon2", ] @@ -3794,14 +3853,14 @@ dependencies = [ [[package]] name = "schnorrkel" -version = "0.9.1" -source = "git+https://github.com/w3f/schnorrkel?rev=cfdbe9ae865a4d3ffa2566d896d4dbedf5107028#cfdbe9ae865a4d3ffa2566d896d4dbedf5107028" +version = "0.10.1" +source = "git+https://github.com/mobilecoinofficial/schnorrkel?rev=fa27d0ed32d251a27399a23d3ef69611acb14d56#fa27d0ed32d251a27399a23d3ef69611acb14d56" dependencies = [ "arrayref", "arrayvec", "curve25519-dalek", "merlin", - "rand_core 0.5.1", + "rand_core 0.6.2", "sha2", "subtle", "zeroize", @@ -3962,9 +4021,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "sha2" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" +checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -5028,12 +5087,11 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" +version = "1.1.1" +source = "git+https://github.com/eranrund/x25519-dalek.git?rev=57c04e5c5aca3551c015167d8176393fbe76dc65#57c04e5c5aca3551c015167d8176393fbe76dc65" dependencies = [ "curve25519-dalek", - "rand_core 0.5.1", + "rand_core 0.6.2", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 5302027e6..2593cdfb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ overflow-checks = false prost = { git = "https://github.com/danburkert/prost", rev = "6113789f70b69709820becba4242824b4fb3ffec" } prost-derive = { git = "https://github.com/danburkert/prost", rev = "6113789f70b69709820becba4242824b4fb3ffec" } -# Patched to depend on crates that depend on digest 0.9 -bulletproofs = { git = "https://github.com/eranrund/bulletproofs", rev = "e8e8ef45ecc6d31f1a9525140edc977351d0f780" } +# Not-yet-released version that depends on newer crates. +bulletproofs = { git = "https://github.com/eranrund/bulletproofs", rev = "8a7c9cdd1efafa3ad68cd65676302f925de68373" } # We need to patch aes-gcm so we can make some fields/functions/structs pub in order to have a constant time decrypt aes-gcm = { git = "https://github.com/mobilecoinofficial/AEADs", rev = "d1a8517d3dd867ed9c5794002add67992a42f6aa" } @@ -37,3 +37,14 @@ grpcio-compiler = { git = "https://github.com/jcape/grpc-rs", rev = "2ad042e9e65 # Overridden in order to bump libsqlite3-sys. This allows us to statically link a more recent version of SQLite3. diesel = { git = "https://github.com/eranrund/diesel", rev = "25592f0383a1d1628db7d2db6c1fb02614a978c1" } + +# ed25519-dalek depends on rand 0.7 which in turns depends on a broken version of packed_simd +# This is a PR that moves it to newer rand +# See https://github.com/dalek-cryptography/ed25519-dalek/pull/160 +ed25519-dalek = { git = "https://github.com/eranrund/ed25519-dalek.git", rev = "484369672f45d776fe13fdd17618aed2f4047909" } + +# Bump curve25519-dalek version to 4.0.0-pre0 +x25519-dalek = { git = "https://github.com/eranrund/x25519-dalek.git", rev = "57c04e5c5aca3551c015167d8176393fbe76dc65" } + +# Overridden since we need a commit that uprevs a bunch of dependencies. +schnorrkel = { git = "https://github.com/mobilecoinofficial/schnorrkel", rev = "fa27d0ed32d251a27399a23d3ef69611acb14d56" } diff --git a/full-service/Cargo.toml b/full-service/Cargo.toml index f5eafe6b7..f90f4d6f4 100644 --- a/full-service/Cargo.toml +++ b/full-service/Cargo.toml @@ -50,7 +50,7 @@ hex = {version = "0.4", default-features = false } libsqlite3-sys = { version = ">=0.8.0, <=0.22.1", features = ["min_sqlite_version_3_7_16", "bundled"] } num_cpus = "1.12" prost = { version = "0.6.1", default-features = false, features = ["prost-derive"] } -rand = { version = "0.7", default-features = false } +rand = { version = "0.8", default-features = false } reqwest = { version = "0.10", default-features = false, features = ["rustls-tls", "gzip"] } retry = "1.2" rocket = { version = "0.4.5", default-features = false } diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index f1553a774..ae6705bd2 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -257,56 +257,54 @@ impl AccountModel for Account { let account_id = AccountID::from(account_key); let fb = first_block_index.unwrap_or(DEFAULT_FIRST_BLOCK_INDEX); - Ok( - conn.transaction::<(AccountID, String), WalletDbError, _>(|| { - let new_account = NewAccount { - account_id_hex: &account_id.to_string(), - account_key: &mc_util_serial::encode(account_key), /* FIXME: WS-6 - add - * encryption */ - entropy: &entropy, - key_derivation_version: key_derivation_version as i32, - main_subaddress_index: DEFAULT_SUBADDRESS_INDEX as i64, - change_subaddress_index: DEFAULT_CHANGE_SUBADDRESS_INDEX as i64, - next_subaddress_index: next_subaddress_index - .unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) - as i64, - first_block_index: fb as i64, - next_block_index: fb as i64, - import_block_index: import_block_index.map(|i| i as i64), - name, - }; - - diesel::insert_into(accounts::table) - .values(&new_account) - .execute(conn)?; + conn.transaction::<(AccountID, String), WalletDbError, _>(|| { + let new_account = NewAccount { + account_id_hex: &account_id.to_string(), + account_key: &mc_util_serial::encode(account_key), /* FIXME: WS-6 - add + * encryption */ + entropy: &entropy, + key_derivation_version: key_derivation_version as i32, + main_subaddress_index: DEFAULT_SUBADDRESS_INDEX as i64, + change_subaddress_index: DEFAULT_CHANGE_SUBADDRESS_INDEX as i64, + next_subaddress_index: next_subaddress_index + .unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) + as i64, + first_block_index: fb as i64, + next_block_index: fb as i64, + import_block_index: import_block_index.map(|i| i as i64), + name, + }; + + diesel::insert_into(accounts::table) + .values(&new_account) + .execute(conn)?; - let main_subaddress_b58 = AssignedSubaddress::create( - &account_key, - None, /* FIXME: WS-8 - Address Book Entry if details provided, or None - * always for main? */ - DEFAULT_SUBADDRESS_INDEX, - "Main", - &conn, - )?; - - let _change_subaddress_b58 = AssignedSubaddress::create( - &account_key, - None, /* FIXME: WS-8 - Address Book Entry if details provided, or None - * always for main? */ - DEFAULT_CHANGE_SUBADDRESS_INDEX, - "Change", - &conn, - )?; - - for subaddress_index in - 2..next_subaddress_index.unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) - { - AssignedSubaddress::create(&account_key, None, subaddress_index, "", &conn)?; - } + let main_subaddress_b58 = AssignedSubaddress::create( + &account_key, + None, /* FIXME: WS-8 - Address Book Entry if details provided, or None + * always for main? */ + DEFAULT_SUBADDRESS_INDEX, + "Main", + &conn, + )?; - Ok((account_id, main_subaddress_b58)) - })?, - ) + let _change_subaddress_b58 = AssignedSubaddress::create( + &account_key, + None, /* FIXME: WS-8 - Address Book Entry if details provided, or None + * always for main? */ + DEFAULT_CHANGE_SUBADDRESS_INDEX, + "Change", + &conn, + )?; + + for subaddress_index in + 2..next_subaddress_index.unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) + { + AssignedSubaddress::create(&account_key, None, subaddress_index, "", &conn)?; + } + + Ok((account_id, main_subaddress_b58)) + }) } fn import( @@ -320,7 +318,7 @@ impl AccountModel for Account { fog_authority_spki: Option, conn: &PooledConnection>, ) -> Result { - Ok(conn.transaction::(|| { + conn.transaction::(|| { let (account_id, _public_address_b58) = Account::create_from_mnemonic( mnemonic, first_block_index, @@ -332,8 +330,8 @@ impl AccountModel for Account { fog_authority_spki, conn, )?; - Ok(Account::get(&account_id, &conn)?) - })?) + Account::get(&account_id, &conn) + }) } fn import_legacy( @@ -347,7 +345,7 @@ impl AccountModel for Account { fog_authority_spki: Option, conn: &PooledConnection>, ) -> Result { - Ok(conn.transaction::(|| { + conn.transaction::(|| { let (account_id, _public_address_b58) = Account::create_from_root_entropy( root_entropy, first_block_index, @@ -359,8 +357,8 @@ impl AccountModel for Account { fog_authority_spki, conn, )?; - Ok(Account::get(&account_id, &conn)?) - })?) + Account::get(&account_id, &conn) + }) } fn list_all( @@ -440,7 +438,7 @@ impl AccountModel for Account { txos::dsl::{txo_id_hex, txos}, }; - Ok(conn.transaction::<(), WalletDbError, _>(|| { + conn.transaction::<(), WalletDbError, _>(|| { for key_image in key_images { // Get the txo by key_image let matches = crate::db::schema::txos::table @@ -489,7 +487,7 @@ impl AccountModel for Account { .set(crate::db::schema::accounts::next_block_index.eq(spent_block_index + 1)) .execute(conn)?; Ok(()) - })?) + }) } /// Delete an account. diff --git a/full-service/src/db/assigned_subaddress.rs b/full-service/src/db/assigned_subaddress.rs index 4edf0ef9b..2e9f2a56c 100644 --- a/full-service/src/db/assigned_subaddress.rs +++ b/full-service/src/db/assigned_subaddress.rs @@ -143,7 +143,7 @@ impl AssignedSubaddressModel for AssignedSubaddress { }, }; - Ok(conn.transaction::<(String, i64), WalletDbError, _>(|| { + conn.transaction::<(String, i64), WalletDbError, _>(|| { let account = Account::get(&AccountID(account_id_hex.to_string()), conn)?; let account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; @@ -228,7 +228,7 @@ impl AssignedSubaddressModel for AssignedSubaddress { } Ok((subaddress_b58, subaddress_index)) - })?) + }) } fn get( @@ -268,7 +268,7 @@ impl AssignedSubaddressModel for AssignedSubaddress { let subaddress = account_key.subaddress(index as u64); let subaddress_b58 = b58_encode(&subaddress)?; - Ok(Self::get(&subaddress_b58, &conn)?) + Self::get(&subaddress_b58, &conn) } fn find_by_subaddress_spend_public_key( diff --git a/full-service/src/db/transaction_log.rs b/full-service/src/db/transaction_log.rs index b2dcdc562..109b6a9f2 100644 --- a/full-service/src/db/transaction_log.rs +++ b/full-service/src/db/transaction_log.rs @@ -350,7 +350,7 @@ impl TransactionLogModel for TransactionLog { ) -> Result<(), WalletDbError> { use crate::db::schema::transaction_logs::dsl::{transaction_id_hex, transaction_logs}; - Ok(conn.transaction::<(), WalletDbError, _>(|| { + conn.transaction::<(), WalletDbError, _>(|| { let associated_transaction_logs = Self::select_for_txo(txo_id_hex, conn)?; for transaction_log in associated_transaction_logs { @@ -393,7 +393,7 @@ impl TransactionLogModel for TransactionLog { } } Ok(()) - })?) + }) } fn log_received( @@ -404,7 +404,7 @@ impl TransactionLogModel for TransactionLog { ) -> Result<(), WalletDbError> { use crate::db::schema::transaction_txo_types; - Ok(conn.transaction::<(), WalletDbError, _>(|| { + conn.transaction::<(), WalletDbError, _>(|| { for (subaddress_index, output_txo_ids) in subaddress_to_output_txo_ids { let txos = Txo::select_by_id(&output_txo_ids, conn)?; for (txo, _account_txo_status) in txos { @@ -459,7 +459,7 @@ impl TransactionLogModel for TransactionLog { } } Ok(()) - })?) + }) } fn log_submitted( @@ -545,7 +545,7 @@ impl TransactionLogModel for TransactionLog { } Ok(transaction_id.to_string()) })?; - Ok(TransactionLog::get(&transaction_log_id, conn)?) + TransactionLog::get(&transaction_log_id, conn) } fn delete_all_for_account( @@ -798,7 +798,7 @@ mod tests { &wallet_db.get_conn().unwrap(), ) .unwrap(); - assert_eq!(change_details.txo.value, 19990000000000); // 19.99 * MOB + assert_eq!(change_details.txo.value, 20 * MOB - MINIMUM_FEE as i64); assert_eq!( change_details .minted_from_account diff --git a/full-service/src/db/txo.rs b/full-service/src/db/txo.rs index 85276c0d1..967a571cd 100644 --- a/full-service/src/db/txo.rs +++ b/full-service/src/db/txo.rs @@ -936,13 +936,13 @@ impl TxoModel for Txo { confirmation: &TxOutConfirmationNumber, conn: &PooledConnection>, ) -> Result { - Ok(conn.transaction::(|| { + conn.transaction::(|| { let txo_details = Txo::get(txo_id_hex, conn)?; let public_key: RistrettoPublic = mc_util_serial::decode(&txo_details.txo.public_key)?; let account = Account::get(account_id, conn)?; let account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; Ok(confirmation.validate(&public_key, account_key.view_private_key())) - })?) + }) } } @@ -1085,7 +1085,7 @@ mod tests { logger.clone(), ); assert_eq!(output_value, 33 * MOB); - assert_eq!(change_value, (966.99 * (MOB as f64)) as i64); + assert_eq!(change_value, 967 * MOB - MINIMUM_FEE as i64); add_block_with_db_txos( &mut ledger_db, @@ -1292,7 +1292,7 @@ mod tests { logger.clone(), ); assert_eq!(output_value, 72 * MOB); - assert_eq!(change_value, (927.98 * (MOB as f64)) as i64); + assert_eq!(change_value, 928 * MOB - (2 * MINIMUM_FEE as i64)); // Add the minted Txos to the ledger add_block_with_db_txos( @@ -1542,7 +1542,7 @@ mod tests { ); assert!(minted_txo_details.received_to_assigned_subaddress.is_none()); - assert_eq!(change_value, (4998.99 * (MOB as f64)) as i64); + assert_eq!(change_value, 4999 * MOB - MINIMUM_FEE as i64); let change_txo_details = Txo::get(&change_txo_id, &wallet_db.get_conn().unwrap()).unwrap(); assert_eq!(change_txo_details.txo.value, change_value); assert_eq!( diff --git a/full-service/src/json_rpc/e2e.rs b/full-service/src/json_rpc/e2e.rs index 2652daec6..9ef095d6b 100644 --- a/full-service/src/json_rpc/e2e.rs +++ b/full-service/src/json_rpc/e2e.rs @@ -23,7 +23,7 @@ mod e2e { use mc_common::logger::{test_with_logger, Logger}; use mc_crypto_rand::rand_core::RngCore; use mc_ledger_db::Ledger; - use mc_transaction_core::ring_signature::KeyImage; + use mc_transaction_core::{constants::MINIMUM_FEE, ring_signature::KeyImage}; use rand::{rngs::StdRng, SeedableRng}; use std::convert::TryFrom; @@ -637,8 +637,7 @@ mod e2e { "value_pmob": "42", } }); - // We will fail because we cannot afford the fee, which is 100000000000 pMOB - // (.01 MOB) + // We will fail because we cannot afford the fee dispatch_expect_error( &client, body, @@ -647,8 +646,8 @@ mod e2e { "code": -32603, "message": "InternalError", "data": json!({ - "server_error": "TransactionBuilder(WalletDb(InsufficientFundsUnderMaxSpendable(\"Max spendable value in wallet: 100, but target value: 10000000042\")))", - "details": "Error building transaction: Wallet DB Error: Insufficient funds from Txos under max_spendable_value: Max spendable value in wallet: 100, but target value: 10000000042", + "server_error": format!("TransactionBuilder(WalletDb(InsufficientFundsUnderMaxSpendable(\"Max spendable value in wallet: 100, but target value: {}\")))", 42 + MINIMUM_FEE), + "details": format!("Error building transaction: Wallet DB Error: Insufficient funds from Txos under max_spendable_value: Max spendable value in wallet: 100, but target value: {}", 42 + MINIMUM_FEE), }) }).to_string(), ); @@ -687,7 +686,7 @@ mod e2e { let fee = tx_proposal.get("fee").unwrap(); // FIXME: WS-9 - Note, minimum fee does not fit into i32 - need to make sure we // are not losing precision with the JsonTxProposal treating Fee as number - assert_eq!(fee, "10000000000"); + assert_eq!(fee, &MINIMUM_FEE.to_string()); assert_eq!(fee, prefix_fee); // Transaction builder attempts to use as many inputs as we have txos @@ -816,7 +815,7 @@ mod e2e { assert_eq!(unspent, "0"); assert_eq!(pending, "100000000000100"); assert_eq!(spent, "0"); - assert_eq!(secreted, "99990000000100"); + assert_eq!(secreted, &(100000000000100 - MINIMUM_FEE).to_string()); assert_eq!(orphaned, "0"); // FIXME: FS-93 Increment ledger manually so tx lands. @@ -852,7 +851,7 @@ mod e2e { transaction_log.get("account_id").unwrap().as_str().unwrap(); assert_eq!( transaction_log.get("fee_pmob").unwrap().as_str().unwrap(), - "10000000000" + &MINIMUM_FEE.to_string() ); assert_eq!( transaction_log.get("status").unwrap().as_str().unwrap(), @@ -1041,7 +1040,7 @@ mod e2e { let fee = tx_proposal.get("fee").unwrap(); // FIXME: WS-9 - Note, minimum fee does not fit into i32 - need to make sure we // are not losing precision with the JsonTxProposal treating Fee as number - assert_eq!(fee, "10000000000"); + assert_eq!(fee, &MINIMUM_FEE.to_string()); assert_eq!(fee, prefix_fee); // Two destinations. @@ -1190,7 +1189,7 @@ mod e2e { .unwrap() .as_str() .unwrap(); - assert_eq!(unspent, "14990000000000"); + assert_eq!(unspent, &(15 * MOB - MINIMUM_FEE as i64).to_string()); let body = json!({ "jsonrpc": "2.0", @@ -1271,7 +1270,7 @@ mod e2e { transaction_log.get("account_id").unwrap().as_str().unwrap(); assert_eq!( transaction_log.get("fee_pmob").unwrap().as_str().unwrap(), - "10000000000" + &MINIMUM_FEE.to_string() ); assert_eq!( transaction_log.get("status").unwrap().as_str().unwrap(), diff --git a/full-service/src/json_rpc/json_rpc_request.rs b/full-service/src/json_rpc/json_rpc_request.rs index 280060563..c905b0f1f 100644 --- a/full-service/src/json_rpc/json_rpc_request.rs +++ b/full-service/src/json_rpc/json_rpc_request.rs @@ -46,7 +46,7 @@ impl TryFrom<&JsonRPCRequest> for JsonCommandRequest { fn try_from(src: &JsonRPCRequest) -> Result { let src_json: serde_json::Value = serde_json::json!(src); - Ok(serde_json::from_value(src_json).map_err(|e| format!("Could not get value {:?}", e))?) + serde_json::from_value(src_json).map_err(|e| format!("Could not get value {:?}", e)) } } diff --git a/full-service/src/json_rpc/txo.rs b/full-service/src/json_rpc/txo.rs index 72ef9aee3..afeb59db7 100644 --- a/full-service/src/json_rpc/txo.rs +++ b/full-service/src/json_rpc/txo.rs @@ -117,7 +117,7 @@ impl From<&TxoDetails> for Txo { object: "txo".to_string(), txo_id_hex: txo_details.txo.txo_id_hex.clone(), value_pmob: (txo_details.txo.value as u64).to_string(), - recipient_address_id: if recipient_address_id == "" { + recipient_address_id: if recipient_address_id.is_empty() { None } else { Some(recipient_address_id) diff --git a/full-service/src/lib.rs b/full-service/src/lib.rs index 8fbb3d1a2..0494313e0 100644 --- a/full-service/src/lib.rs +++ b/full-service/src/lib.rs @@ -3,8 +3,6 @@ //! Full Service Wallet. #![feature(proc_macro_hygiene, decl_macro)] -// Required because hashbrown is at 0.9.1 and has build issues otherwise -#![feature(ptr_offset_from)] pub mod config; mod db; diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index ea26d99d6..abf4f757a 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -269,10 +269,10 @@ where ) -> Result { let conn = self.wallet_db.get_conn()?; - Ok(conn.transaction::(|| { + conn.transaction::(|| { Account::get(&account_id, &conn)?.update_name(name, &conn)?; Ok(Account::get(&account_id, &conn)?) - })?) + }) } fn remove_account(&self, account_id: &AccountID) -> Result { diff --git a/full-service/src/service/address.rs b/full-service/src/service/address.rs index fb575d3e2..e02d33748 100644 --- a/full-service/src/service/address.rs +++ b/full-service/src/service/address.rs @@ -80,18 +80,16 @@ where ) -> Result { let conn = &self.wallet_db.get_conn()?; - Ok( - conn.transaction::(|| { - let (public_address_b58, _subaddress_index) = - AssignedSubaddress::create_next_for_account( - &account_id.to_string(), - metadata.unwrap_or(""), - &conn, - )?; - - Ok(AssignedSubaddress::get(&public_address_b58, &conn)?) - })?, - ) + conn.transaction::(|| { + let (public_address_b58, _subaddress_index) = + AssignedSubaddress::create_next_for_account( + &account_id.to_string(), + metadata.unwrap_or(""), + &conn, + )?; + + Ok(AssignedSubaddress::get(&public_address_b58, &conn)?) + }) } fn get_addresses_for_account( diff --git a/full-service/src/service/balance.rs b/full-service/src/service/balance.rs index 6bbb1a3fe..d21a6add1 100644 --- a/full-service/src/service/balance.rs +++ b/full-service/src/service/balance.rs @@ -172,7 +172,7 @@ where let network_block_index = self.get_network_block_index()? + 1; let local_block_index = self.ledger_db.num_blocks()?; - Ok(conn.transaction::(|| { + conn.transaction::(|| { let txos = Txo::list_for_address(&address.to_string(), &conn)?; let assigned_address = AssignedSubaddress::get(address, &conn)?; @@ -214,7 +214,7 @@ where local_block_index, synced_blocks: account.next_block_index as u64, }) - })?) + }) } fn get_network_status(&self) -> Result { @@ -230,51 +230,49 @@ where let network_block_index = self.get_network_block_index()?; - Ok( - conn.transaction::(|| { - let accounts = Account::list_all(&conn)?; - let mut account_map = HashMap::default(); - - let mut unspent: u128 = 0; - let mut pending: u128 = 0; - let mut spent: u128 = 0; - let mut secreted: u128 = 0; - let mut orphaned: u128 = 0; - - let mut min_synced_block_index = network_block_index; - let mut account_ids = Vec::new(); - for account in accounts { - let account_id = AccountID(account.account_id_hex.clone()); - let balance = Self::get_balance_inner(&account_id.to_string(), &conn)?; - account_map.insert(account_id.clone(), account.clone()); - unspent += balance.0; - pending += balance.1; - spent += balance.2; - secreted += balance.3; - orphaned += balance.4; - - // account.next_block_index is an index in range [0..ledger_db.num_blocks()] - min_synced_block_index = std::cmp::min( - min_synced_block_index, - (account.next_block_index as u64).saturating_sub(1), - ); - account_ids.push(account_id); - } + conn.transaction::(|| { + let accounts = Account::list_all(&conn)?; + let mut account_map = HashMap::default(); + + let mut unspent: u128 = 0; + let mut pending: u128 = 0; + let mut spent: u128 = 0; + let mut secreted: u128 = 0; + let mut orphaned: u128 = 0; + + let mut min_synced_block_index = network_block_index; + let mut account_ids = Vec::new(); + for account in accounts { + let account_id = AccountID(account.account_id_hex.clone()); + let balance = Self::get_balance_inner(&account_id.to_string(), &conn)?; + account_map.insert(account_id.clone(), account.clone()); + unspent += balance.0; + pending += balance.1; + spent += balance.2; + secreted += balance.3; + orphaned += balance.4; + + // account.next_block_index is an index in range [0..ledger_db.num_blocks()] + min_synced_block_index = std::cmp::min( + min_synced_block_index, + (account.next_block_index as u64).saturating_sub(1), + ); + account_ids.push(account_id); + } - Ok(WalletStatus { - unspent, - pending, - spent, - secreted, - orphaned, - network_block_index, - local_block_index: self.ledger_db.num_blocks()? - 1, - min_synced_block_index: min_synced_block_index as u64, - account_ids, - account_map, - }) - })?, - ) + Ok(WalletStatus { + unspent, + pending, + spent, + secreted, + orphaned, + network_block_index, + local_block_index: self.ledger_db.num_blocks()? - 1, + min_synced_block_index: min_synced_block_index as u64, + account_ids, + account_map, + }) + }) } } diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index 3a0a3cb21..753c80f3f 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -559,7 +559,7 @@ where let num_txos = self.ledger_db.num_txos()?; let mut sampled_indices: HashSet = HashSet::default(); while sampled_indices.len() < RING_SIZE - 1 { - let index = rng.gen_range(0, num_txos); + let index = rng.gen_range(0..num_txos); if index == gift_txo_index { continue; } @@ -833,13 +833,13 @@ mod tests { &RistrettoPublic::try_from(&tx_out.public_key).unwrap(), ); let (value, _blinding) = tx_out.amount.get_value(&shared_secret).unwrap(); - assert_eq!(value, 2000000000000); + assert_eq!(value, 2 * MOB as u64); // Verify balance for Alice = original balance - fee - gift_code_value let balance = service .get_balance_for_account(&AccountID(alice.account_id_hex.clone())) .unwrap(); - assert_eq!(balance.unspent, 97990000000000); + assert_eq!(balance.unspent, (98 * MOB - MINIMUM_FEE as i64) as u128); // Verify that we can get the gift_code log::info!(logger, "Getting gift code from database"); @@ -903,7 +903,7 @@ mod tests { let bob_balance = service .get_balance_for_account(&AccountID(bob.account_id_hex)) .unwrap(); - assert_eq!(bob_balance.unspent, 1990000000000) + assert_eq!(bob_balance.unspent, (2 * MOB - MINIMUM_FEE as i64) as u128) } #[test_with_logger] diff --git a/full-service/src/service/receipt.rs b/full-service/src/service/receipt.rs index 385218fe4..4be078489 100644 --- a/full-service/src/service/receipt.rs +++ b/full-service/src/service/receipt.rs @@ -188,78 +188,72 @@ where ) -> Result<(ReceiptTransactionStatus, Option), ReceiptServiceError> { let conn = &self.wallet_db.get_conn()?; - Ok(conn - .transaction::<(ReceiptTransactionStatus, Option), ReceiptServiceError, _>( - || { - let assigned_address = AssignedSubaddress::get(address, &conn)?; - let account_id = AccountID(assigned_address.account_id_hex); - let account = Account::get(&account_id, &conn)?; - // Get the transaction from the database, with status. - let txos_and_statuses = Txo::select_by_public_key( - &account_id, - &[&receiver_receipt.public_key], - &conn, - )?; - - // Return if the Txo from the receipt is not in this wallet yet. - if txos_and_statuses.is_empty() { - return Ok((ReceiptTransactionStatus::TransactionPending, None)); - } - let (txo, status) = &txos_and_statuses[0]; - - // Figure out whether the Txo was minted by us, and has not yet been received by - // us. (For to-self transactions). If the Txo was minted by us, this - // transaction is pending. - if status.txo_type == TXO_TYPE_MINTED - && status.txo_status == TXO_STATUS_SECRETED - { - return Ok((ReceiptTransactionStatus::TransactionPending, None)); - } - let details = Txo::get(&txo.txo_id_hex, &conn)?; - - // Decrypt the amount to get the expected value - let account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; - let public_key: RistrettoPublic = - RistrettoPublic::try_from(&receiver_receipt.public_key)?; - let shared_secret = - get_tx_out_shared_secret(account_key.view_private_key(), &public_key); - let expected_value = match receiver_receipt.amount.get_value(&shared_secret) { - Ok((v, _blinding)) => v, - Err(AmountError::InconsistentCommitment) => { - return Ok(( - ReceiptTransactionStatus::FailedAmountDecryption, - Some(details), - )) - } - }; - // Check that the value of the received Txo matches the expected value. - if (txo.value as u64) != expected_value { + conn.transaction::<(ReceiptTransactionStatus, Option), ReceiptServiceError, _>( + || { + let assigned_address = AssignedSubaddress::get(address, &conn)?; + let account_id = AccountID(assigned_address.account_id_hex); + let account = Account::get(&account_id, &conn)?; + // Get the transaction from the database, with status. + let txos_and_statuses = + Txo::select_by_public_key(&account_id, &[&receiver_receipt.public_key], &conn)?; + + // Return if the Txo from the receipt is not in this wallet yet. + if txos_and_statuses.is_empty() { + return Ok((ReceiptTransactionStatus::TransactionPending, None)); + } + let (txo, status) = &txos_and_statuses[0]; + + // Figure out whether the Txo was minted by us, and has not yet been received by + // us. (For to-self transactions). If the Txo was minted by us, this + // transaction is pending. + if status.txo_type == TXO_TYPE_MINTED && status.txo_status == TXO_STATUS_SECRETED { + return Ok((ReceiptTransactionStatus::TransactionPending, None)); + } + let details = Txo::get(&txo.txo_id_hex, &conn)?; + + // Decrypt the amount to get the expected value + let account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; + let public_key: RistrettoPublic = + RistrettoPublic::try_from(&receiver_receipt.public_key)?; + let shared_secret = + get_tx_out_shared_secret(account_key.view_private_key(), &public_key); + let expected_value = match receiver_receipt.amount.get_value(&shared_secret) { + Ok((v, _blinding)) => v, + Err(AmountError::InconsistentCommitment) => { return Ok(( - ReceiptTransactionStatus::AmountMismatch(format!( - "Expected: {}, Got: {}", - expected_value, txo.value - )), + ReceiptTransactionStatus::FailedAmountDecryption, Some(details), - )); + )) } + }; + // Check that the value of the received Txo matches the expected value. + if (txo.value as u64) != expected_value { + return Ok(( + ReceiptTransactionStatus::AmountMismatch(format!( + "Expected: {}, Got: {}", + expected_value, txo.value + )), + Some(details), + )); + } - // Validate the confirmation number. - let confirmation_hex = - hex::encode(mc_util_serial::encode(&receiver_receipt.confirmation)); - let confirmation: TxOutConfirmationNumber = - mc_util_serial::decode(&hex::decode(confirmation_hex)?)?; - if !Txo::validate_confirmation( - &account_id, - &txo.txo_id_hex.clone(), - &confirmation, - &conn, - )? { - return Ok((ReceiptTransactionStatus::InvalidConfirmation, Some(details))); - } + // Validate the confirmation number. + let confirmation_hex = + hex::encode(mc_util_serial::encode(&receiver_receipt.confirmation)); + let confirmation: TxOutConfirmationNumber = + mc_util_serial::decode(&hex::decode(confirmation_hex)?)?; + if !Txo::validate_confirmation( + &account_id, + &txo.txo_id_hex.clone(), + &confirmation, + &conn, + )? { + return Ok((ReceiptTransactionStatus::InvalidConfirmation, Some(details))); + } - Ok((ReceiptTransactionStatus::TransactionSuccess, Some(details))) - }, - )?) + Ok((ReceiptTransactionStatus::TransactionSuccess, Some(details))) + }, + ) } fn create_receiver_receipts( diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index 1c450b979..f6d259d40 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -299,7 +299,7 @@ mod tests { use mc_account_keys::{AccountKey, PublicAddress}; use mc_common::logger::{test_with_logger, Logger}; use mc_crypto_rand::rand_core::RngCore; - use mc_transaction_core::ring_signature::KeyImage; + use mc_transaction_core::{constants::MINIMUM_FEE, ring_signature::KeyImage}; use rand::{rngs::StdRng, SeedableRng}; // Test sending a transaction from Alice -> Bob, and then from Bob -> Alice @@ -397,7 +397,7 @@ mod tests { .map(|t| Txo::get(&t.txo_id_hex, &service.wallet_db.get_conn().unwrap()).unwrap()) .collect::>(); assert_eq!(change.len(), 1); - assert_eq!(change[0].txo.value, (57.99 * MOB as f64) as i64); + assert_eq!(change[0].txo.value, 58 * MOB - MINIMUM_FEE as i64); let inputs = transaction_txos .inputs @@ -411,7 +411,7 @@ mod tests { let balance = service .get_balance_for_account(&AccountID(alice.account_id_hex.clone())) .unwrap(); - assert_eq!(balance.unspent, 57990000000000); + assert_eq!(balance.unspent, (58 * MOB - MINIMUM_FEE as i64) as u128); // Bob's balance should be = output_txo_value let bob_balance = service @@ -451,13 +451,16 @@ mod tests { let alice_balance = service .get_balance_for_account(&AccountID(alice.account_id_hex)) .unwrap(); - assert_eq!(alice_balance.unspent, 65990000000000); + assert_eq!( + alice_balance.unspent, + (66 * MOB - MINIMUM_FEE as i64) as u128 + ); // Bob's balance should be = output_txo_value let bob_balance = service .get_balance_for_account(&AccountID(bob.account_id_hex)) .unwrap(); - assert_eq!(bob_balance.unspent, 33990000000000); + assert_eq!(bob_balance.unspent, (34 * MOB - MINIMUM_FEE as i64) as u128); } // Building a transaction for an invalid public address should fail. diff --git a/full-service/src/service/transaction_builder.rs b/full-service/src/service/transaction_builder.rs index df5aca2a6..71b27cc51 100644 --- a/full-service/src/service/transaction_builder.rs +++ b/full-service/src/service/transaction_builder.rs @@ -40,7 +40,7 @@ use mc_util_uri::FogUri; use diesel::prelude::*; use rand::Rng; -use std::{convert::TryFrom, iter::FromIterator, str::FromStr, sync::Arc}; +use std::{convert::TryFrom, str::FromStr, sync::Arc}; /// Default number of blocks used for calculating transaction tombstone block /// number. @@ -197,231 +197,231 @@ impl WalletTransactionBuilder { let conn = self.wallet_db.get_conn()?; - Ok( - conn.transaction::(|| { - let account: Account = - Account::get(&AccountID(self.account_id_hex.to_string()), &conn)?; - let from_account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; - - // Collect all required FogUris from public addresses, then pass to resolver - // factory - let fog_resolver = { - let change_address = - from_account_key.subaddress(account.change_subaddress_index as u64); - let fog_uris = core::slice::from_ref(&change_address) - .iter() - .chain(self.outlays.iter().map(|(receiver, _amount)| receiver)) - .filter_map(|x| extract_fog_uri(x).transpose()) - .collect::, _>>()?; - (self.fog_resolver_factory)(&fog_uris) - .map_err(WalletTransactionBuilderError::FogPubkeyResolver)? - }; - - // Create transaction builder. - let mut transaction_builder = TransactionBuilder::new(fog_resolver); - transaction_builder.set_fee(self.fee.unwrap_or(MINIMUM_FEE)); - - // Get membership proofs for our inputs - let indexes = self - .inputs + conn.transaction::(|| { + let account: Account = + Account::get(&AccountID(self.account_id_hex.to_string()), &conn)?; + let from_account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; + + // Collect all required FogUris from public addresses, then pass to resolver + // factory + let fog_resolver = { + let change_address = + from_account_key.subaddress(account.change_subaddress_index as u64); + let fog_uris = core::slice::from_ref(&change_address) .iter() - .map(|utxo| { - let txo: TxOut = mc_util_serial::decode(&utxo.txo)?; - self.ledger_db.get_tx_out_index_by_hash(&txo.hash()) - }) - .collect::, mc_ledger_db::Error>>()?; - let proofs = self.ledger_db.get_tx_out_proof_of_memberships(&indexes)?; - - let inputs_and_proofs: Vec<(Txo, TxOutMembershipProof)> = self - .inputs - .clone() - .into_iter() - .zip(proofs.into_iter()) - .collect(); + .chain(self.outlays.iter().map(|(receiver, _amount)| receiver)) + .filter_map(|x| extract_fog_uri(x).transpose()) + .collect::, _>>()?; + (self.fog_resolver_factory)(&fog_uris) + .map_err(WalletTransactionBuilderError::FogPubkeyResolver)? + }; + + // Create transaction builder. + let mut transaction_builder = TransactionBuilder::new(fog_resolver); + transaction_builder.set_fee(self.fee.unwrap_or(MINIMUM_FEE)); + + // Get membership proofs for our inputs + let indexes = self + .inputs + .iter() + .map(|utxo| { + let txo: TxOut = mc_util_serial::decode(&utxo.txo)?; + self.ledger_db.get_tx_out_index_by_hash(&txo.hash()) + }) + .collect::, mc_ledger_db::Error>>()?; + let proofs = self.ledger_db.get_tx_out_proof_of_memberships(&indexes)?; + + let inputs_and_proofs: Vec<(Txo, TxOutMembershipProof)> = self + .inputs + .clone() + .into_iter() + .zip(proofs.into_iter()) + .collect(); + + let excluded_tx_out_indices: Vec = inputs_and_proofs + .iter() + .map(|(utxo, _membership_proof)| { + let txo: TxOut = mc_util_serial::decode(&utxo.txo)?; + self.ledger_db + .get_tx_out_index_by_hash(&txo.hash()) + .map_err(WalletTransactionBuilderError::LedgerDB) + }) + .collect::, WalletTransactionBuilderError>>()?; - let excluded_tx_out_indices: Vec = inputs_and_proofs - .iter() - .map(|(utxo, _membership_proof)| { - let txo: TxOut = mc_util_serial::decode(&utxo.txo)?; - self.ledger_db - .get_tx_out_index_by_hash(&txo.hash()) - .map_err(WalletTransactionBuilderError::LedgerDB) - }) - .collect::, WalletTransactionBuilderError>>()?; + let rings = self.get_rings(inputs_and_proofs.len(), &excluded_tx_out_indices)?; - let rings = self.get_rings(inputs_and_proofs.len(), &excluded_tx_out_indices)?; + if rings.len() != inputs_and_proofs.len() { + return Err(WalletTransactionBuilderError::RingSizeMismatch); + } - if rings.len() != inputs_and_proofs.len() { - return Err(WalletTransactionBuilderError::RingSizeMismatch); - } + if self.outlays.is_empty() { + return Err(WalletTransactionBuilderError::NoRecipient); + } - if self.outlays.is_empty() { - return Err(WalletTransactionBuilderError::NoRecipient); + // Unzip each vec of tuples into a tuple of vecs. + let mut rings_and_proofs: Vec<(Vec, Vec)> = rings + .into_iter() + .map(|tuples| tuples.into_iter().unzip()) + .collect(); + + // Add inputs to the tx. + for (utxo, proof) in inputs_and_proofs.iter() { + let db_tx_out: TxOut = mc_util_serial::decode(&utxo.txo)?; + let (mut ring, mut membership_proofs) = rings_and_proofs + .pop() + .ok_or(WalletTransactionBuilderError::RingsAndProofsEmpty)?; + if ring.len() != membership_proofs.len() { + return Err(WalletTransactionBuilderError::RingSizeMismatch); } - // Unzip each vec of tuples into a tuple of vecs. - let mut rings_and_proofs: Vec<(Vec, Vec)> = rings - .into_iter() - .map(|tuples| tuples.into_iter().unzip()) - .collect(); - - // Add inputs to the tx. - for (utxo, proof) in inputs_and_proofs.iter() { - let db_tx_out: TxOut = mc_util_serial::decode(&utxo.txo)?; - let (mut ring, mut membership_proofs) = rings_and_proofs - .pop() - .ok_or_else(|| WalletTransactionBuilderError::RingsAndProofsEmpty)?; - if ring.len() != membership_proofs.len() { - return Err(WalletTransactionBuilderError::RingSizeMismatch); + // Add the input to the ring. + let position_opt = ring.iter().position(|txo| *txo == db_tx_out); + let real_key_index = match position_opt { + Some(position) => { + // The input is already present in the ring. + // This could happen if ring elements are sampled randomly from the + // ledger. + position } - - // Add the input to the ring. - let position_opt = ring.iter().position(|txo| *txo == db_tx_out); - let real_key_index = match position_opt { - Some(position) => { - // The input is already present in the ring. - // This could happen if ring elements are sampled randomly from the - // ledger. - position + None => { + // The input is not already in the ring. + if ring.is_empty() { + // Append the input and its proof of membership. + ring.push(db_tx_out.clone()); + membership_proofs.push(proof.clone()); + } else { + // Replace the first element of the ring. + ring[0] = db_tx_out.clone(); + membership_proofs[0] = proof.clone(); } - None => { - // The input is not already in the ring. - if ring.is_empty() { - // Append the input and its proof of membership. - ring.push(db_tx_out.clone()); - membership_proofs.push(proof.clone()); - } else { - // Replace the first element of the ring. - ring[0] = db_tx_out.clone(); - membership_proofs[0] = proof.clone(); - } - // The real input is always the first element. This is safe because - // TransactionBuilder sorts each ring. - 0 - } - }; - - if ring.len() != membership_proofs.len() { - return Err(WalletTransactionBuilderError::RingSizeMismatch); + // The real input is always the first element. This is safe because + // TransactionBuilder sorts each ring. + 0 } + }; - let public_key = RistrettoPublic::try_from(&db_tx_out.public_key).unwrap(); - - let subaddress_index = if let Some(s) = utxo.subaddress_index { - s - } else { - return Err(WalletTransactionBuilderError::NullSubaddress( - utxo.txo_id_hex.to_string(), - )); - }; - - let onetime_private_key = recover_onetime_private_key( - &public_key, - from_account_key.view_private_key(), - &from_account_key.subaddress_spend_private(subaddress_index as u64), - ); - - let key_image = KeyImage::from(&onetime_private_key); - log::debug!( - self.logger, - "Adding input: ring {:?}, utxo index {:?}, key image {:?}, pubkey {:?}", - ring, - real_key_index, - key_image, - public_key - ); - - transaction_builder.add_input(InputCredentials::new( - ring, - membership_proofs, - real_key_index, - onetime_private_key, - *from_account_key.view_private_key(), - )?); + if ring.len() != membership_proofs.len() { + return Err(WalletTransactionBuilderError::RingSizeMismatch); } - // Add outputs to our destinations. - // Note that we make an assumption currently when logging submitted Txos that - // they were built with only one recipient, and one change txo. - let mut total_value = 0; - let mut tx_out_to_outlay_index: HashMap = HashMap::default(); - let mut outlay_confirmation_numbers = Vec::default(); - let mut rng = rand::thread_rng(); - for (i, (recipient, out_value)) in self.outlays.iter().enumerate() { - let (tx_out, confirmation_number) = - transaction_builder.add_output(*out_value as u64, &recipient, &mut rng)?; - - tx_out_to_outlay_index.insert(tx_out, i); - outlay_confirmation_numbers.push(confirmation_number); - - total_value += *out_value; - } + let public_key = RistrettoPublic::try_from(&db_tx_out.public_key).unwrap(); - // Figure out if we have change. - let input_value = inputs_and_proofs - .iter() - .fold(0, |acc, (utxo, _proof)| acc + utxo.value); - if (total_value + transaction_builder.fee) > input_value as u64 { - return Err(WalletTransactionBuilderError::InsufficientInputFunds( - format!( + let subaddress_index = if let Some(s) = utxo.subaddress_index { + s + } else { + return Err(WalletTransactionBuilderError::NullSubaddress( + utxo.txo_id_hex.to_string(), + )); + }; + + let onetime_private_key = recover_onetime_private_key( + &public_key, + from_account_key.view_private_key(), + &from_account_key.subaddress_spend_private(subaddress_index as u64), + ); + + let key_image = KeyImage::from(&onetime_private_key); + log::debug!( + self.logger, + "Adding input: ring {:?}, utxo index {:?}, key image {:?}, pubkey {:?}", + ring, + real_key_index, + key_image, + public_key + ); + + transaction_builder.add_input(InputCredentials::new( + ring, + membership_proofs, + real_key_index, + onetime_private_key, + *from_account_key.view_private_key(), + )?); + } + + // Add outputs to our destinations. + // Note that we make an assumption currently when logging submitted Txos that + // they were built with only one recipient, and one change txo. + let mut total_value = 0; + let mut tx_out_to_outlay_index: HashMap = HashMap::default(); + let mut outlay_confirmation_numbers = Vec::default(); + let mut rng = rand::thread_rng(); + for (i, (recipient, out_value)) in self.outlays.iter().enumerate() { + let (tx_out, confirmation_number) = + transaction_builder.add_output(*out_value as u64, &recipient, &mut rng)?; + + tx_out_to_outlay_index.insert(tx_out, i); + outlay_confirmation_numbers.push(confirmation_number); + + total_value += *out_value; + } + + // Figure out if we have change. + let input_value = inputs_and_proofs + .iter() + .fold(0, |acc, (utxo, _proof)| acc + utxo.value); + if (total_value + transaction_builder.fee) > input_value as u64 { + return Err(WalletTransactionBuilderError::InsufficientInputFunds( + format!( "Total value required to send transaction {:?}, but only {:?} in inputs", total_value + transaction_builder.fee, input_value ), - )); - } - - let change = input_value as u64 - total_value - transaction_builder.fee; + )); + } - // If we do, add an output for that as well. - if change > 0 { - let change_public_address = - from_account_key.subaddress(account.change_subaddress_index as u64); - // FIXME: verify that fog resolver knows to send change with hint encrypted to - // the main public address - transaction_builder.add_output(change, &change_public_address, &mut rng)?; - // FIXME: CBB - map error to indicate error with change - } + let change = input_value as u64 - total_value - transaction_builder.fee; - // Set tombstone block. - transaction_builder.set_tombstone_block(self.tombstone); - - // Build tx. - let tx = transaction_builder.build(&mut rng)?; - - // Map each TxOut in the constructed transaction to its respective outlay. - let outlay_index_to_tx_out_index: HashMap = - HashMap::from_iter(tx.prefix.outputs.iter().enumerate().filter_map( - |(tx_out_index, tx_out)| { - if let Some(outlay_index) = tx_out_to_outlay_index.get(tx_out) { - Some((*outlay_index, tx_out_index)) - } else { - None - } - }, - )); + // If we do, add an output for that as well. + if change > 0 { + let change_public_address = + from_account_key.subaddress(account.change_subaddress_index as u64); + // FIXME: verify that fog resolver knows to send change with hint encrypted to + // the main public address + transaction_builder.add_output(change, &change_public_address, &mut rng)?; + // FIXME: CBB - map error to indicate error with change + } - // Sanity check: All of our outlays should have a unique index in the map. - assert_eq!(outlay_index_to_tx_out_index.len(), self.outlays.len()); - let mut found_tx_out_indices: HashSet<&usize> = HashSet::default(); - for i in 0..self.outlays.len() { - let tx_out_index = outlay_index_to_tx_out_index - .get(&i) - .expect("index not in map"); - if !found_tx_out_indices.insert(tx_out_index) { - panic!("duplicate index {} found in map", tx_out_index); - } + // Set tombstone block. + transaction_builder.set_tombstone_block(self.tombstone); + + // Build tx. + let tx = transaction_builder.build(&mut rng)?; + + // Map each TxOut in the constructed transaction to its respective outlay. + let outlay_index_to_tx_out_index: HashMap = tx + .prefix + .outputs + .iter() + .enumerate() + .filter_map(|(tx_out_index, tx_out)| { + tx_out_to_outlay_index + .get(tx_out) + .map(|outlay_index| (*outlay_index, tx_out_index)) + }) + .collect(); + + // Sanity check: All of our outlays should have a unique index in the map. + assert_eq!(outlay_index_to_tx_out_index.len(), self.outlays.len()); + let mut found_tx_out_indices: HashSet<&usize> = HashSet::default(); + for i in 0..self.outlays.len() { + let tx_out_index = outlay_index_to_tx_out_index + .get(&i) + .expect("index not in map"); + if !found_tx_out_indices.insert(tx_out_index) { + panic!("duplicate index {} found in map", tx_out_index); } + } - // Make the UnspentTxOut for each Txo - // FIXME: WS-27 - I would prefer to provide just the txo_id_hex per txout, but - // this at least preserves some interoperability between - // mobilecoind and wallet-service. However, this is - // pretty clunky and I would rather not expose a storage - // type from mobilecoind just to get around having to write a bunch of - // tedious json conversions. - // Return the TxProposal - let selected_utxos = inputs_and_proofs + // Make the UnspentTxOut for each Txo + // FIXME: WS-27 - I would prefer to provide just the txo_id_hex per txout, but + // this at least preserves some interoperability between + // mobilecoind and wallet-service. However, this is + // pretty clunky and I would rather not expose a storage + // type from mobilecoind just to get around having to write a bunch of + // tedious json conversions. + // Return the TxProposal + let selected_utxos = inputs_and_proofs .iter() .map(|(utxo, _membership_proof)| { let decoded_tx_out = mc_util_serial::decode(&utxo.txo).unwrap(); @@ -438,22 +438,21 @@ impl WalletTransactionBuilder { } }) .collect(); - Ok(TxProposal { - utxos: selected_utxos, - outlays: self - .outlays - .iter() - .map(|(recipient, value)| Outlay { - receiver: recipient.clone(), - value: *value, - }) - .collect::>(), - tx, - outlay_index_to_tx_out_index, - outlay_confirmation_numbers, - }) - })?, - ) + Ok(TxProposal { + utxos: selected_utxos, + outlays: self + .outlays + .iter() + .map(|(recipient, value)| Outlay { + receiver: recipient.clone(), + value: *value, + }) + .collect::>(), + tx, + outlay_index_to_tx_out_index, + outlay_confirmation_numbers, + }) + }) } /// Get rings. @@ -481,7 +480,7 @@ impl WalletTransactionBuilder { let mut rng = rand::thread_rng(); let mut sampled_indices: HashSet = HashSet::default(); while sampled_indices.len() < num_requested { - let index = rng.gen_range(0, num_txos); + let index = rng.gen_range(0..num_txos); if excluded_tx_out_indices.contains(&index) { continue; } diff --git a/full-service/src/service/transaction_log.rs b/full-service/src/service/transaction_log.rs index 58953d9e1..38e7975b7 100644 --- a/full-service/src/service/transaction_log.rs +++ b/full-service/src/service/transaction_log.rs @@ -95,16 +95,12 @@ where ) -> Result<(TransactionLog, AssociatedTxos), TransactionLogServiceError> { let conn = self.wallet_db.get_conn()?; - Ok( - conn.transaction::<(TransactionLog, AssociatedTxos), TransactionLogServiceError, _>( - || { - let transaction_log = TransactionLog::get(transaction_id_hex, &conn)?; - let associated = transaction_log.get_associated_txos(&conn)?; - - Ok((transaction_log, associated)) - }, - )?, - ) + conn.transaction::<(TransactionLog, AssociatedTxos), TransactionLogServiceError, _>(|| { + let transaction_log = TransactionLog::get(transaction_id_hex, &conn)?; + let associated = transaction_log.get_associated_txos(&conn)?; + + Ok((transaction_log, associated)) + }) } fn get_all_transaction_logs_for_block( @@ -113,22 +109,17 @@ where ) -> Result, WalletServiceError> { let conn = self.wallet_db.get_conn()?; - Ok( - conn.transaction::, WalletServiceError, _>( - || { - let transaction_logs = - TransactionLog::get_all_for_block_index(block_index, &conn)?; - let mut res: Vec<(TransactionLog, AssociatedTxos)> = Vec::new(); - for transaction_log in transaction_logs { - res.push(( - transaction_log.clone(), - transaction_log.get_associated_txos(&conn)?, - )); - } - Ok(res) - }, - )?, - ) + conn.transaction::, WalletServiceError, _>(|| { + let transaction_logs = TransactionLog::get_all_for_block_index(block_index, &conn)?; + let mut res: Vec<(TransactionLog, AssociatedTxos)> = Vec::new(); + for transaction_log in transaction_logs { + res.push(( + transaction_log.clone(), + transaction_log.get_associated_txos(&conn)?, + )); + } + Ok(res) + }) } fn get_all_transaction_logs_ordered_by_block( @@ -136,20 +127,16 @@ where ) -> Result, WalletServiceError> { let conn = self.wallet_db.get_conn()?; - Ok( - conn.transaction::, WalletServiceError, _>( - || { - let transaction_logs = TransactionLog::get_all_ordered_by_block_index(&conn)?; - let mut res: Vec<(TransactionLog, AssociatedTxos)> = Vec::new(); - for transaction_log in transaction_logs { - res.push(( - transaction_log.clone(), - transaction_log.get_associated_txos(&conn)?, - )); - } - Ok(res) - }, - )?, - ) + conn.transaction::, WalletServiceError, _>(|| { + let transaction_logs = TransactionLog::get_all_ordered_by_block_index(&conn)?; + let mut res: Vec<(TransactionLog, AssociatedTxos)> = Vec::new(); + for transaction_log in transaction_logs { + res.push(( + transaction_log.clone(), + transaction_log.get_associated_txos(&conn)?, + )); + } + Ok(res) + }) } } diff --git a/full-service/src/service/txo.rs b/full-service/src/service/txo.rs index 9c38e83a7..aa50feb08 100644 --- a/full-service/src/service/txo.rs +++ b/full-service/src/service/txo.rs @@ -126,7 +126,7 @@ where use crate::service::txo::TxoServiceError::{TxoNotSpendable, TxoNotSpendableByAnyAccount}; let conn = self.wallet_db.get_conn()?; - Ok(conn.transaction::(|| { + conn.transaction::(|| { let txo_details = Txo::get(&txo_id.to_string(), &conn)?; let received_to_account_txo_status = match txo_details.received_to_account { Some(account_status) => Ok(account_status), @@ -161,7 +161,7 @@ where tombstone_block, None, )?) - })?) + }) } fn get_all_txos_for_address(&self, address: &str) -> Result, TxoServiceError> { @@ -195,7 +195,7 @@ mod tests { HashSet, }; use mc_crypto_rand::RngCore; - use mc_transaction_core::ring_signature::KeyImage; + use mc_transaction_core::{constants::MINIMUM_FEE, ring_signature::KeyImage}; use rand::{rngs::StdRng, SeedableRng}; use std::iter::FromIterator; @@ -303,17 +303,17 @@ mod tests { TXO_TYPE_MINTED ); let minted_value_set = HashSet::from_iter(minted.iter().map(|m| m.txo.value.clone())); - assert!(minted_value_set.contains(&(57990000000000 as i64))); - assert!(minted_value_set.contains(&(42000000000000 as i64))); + assert!(minted_value_set.contains(&(58 * MOB - MINIMUM_FEE as i64))); + assert!(minted_value_set.contains(&(42 * MOB))); // Our balance should reflect the various statuses of our txos let balance = service .get_balance_for_account(&AccountID(alice.account_id_hex)) .unwrap(); assert_eq!(balance.unspent, 0); - assert_eq!(balance.pending, 100000000000000); + assert_eq!(balance.pending, 100 * MOB as u128); assert_eq!(balance.spent, 0); - assert_eq!(balance.secreted, 99990000000000); + assert_eq!(balance.secreted, (100 * MOB - MINIMUM_FEE as i64) as u128); assert_eq!(balance.orphaned, 0); // FIXME: How to make the transaction actually hit the test ledger? diff --git a/full-service/src/test_utils.rs b/full-service/src/test_utils.rs index 6a8c682c2..4403d7351 100644 --- a/full-service/src/test_utils.rs +++ b/full-service/src/test_utils.rs @@ -66,6 +66,7 @@ impl Default for WalletDbTestContext { thread_rng() .sample_iter(&Alphanumeric) .take(10) + .map(char::from) .collect::() .to_lowercase() ); diff --git a/mobilecoin b/mobilecoin index ae596fb12..864dc1619 160000 --- a/mobilecoin +++ b/mobilecoin @@ -1 +1 @@ -Subproject commit ae596fb127f89814bbf20b3849cddc5714e4eb96 +Subproject commit 864dc1619535ccfb377984bed4c8263b036a388d diff --git a/rust-toolchain b/rust-toolchain index a5fd288b4..1aad10c10 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-07-01 +nightly-2021-03-25 diff --git a/rustfmt.toml b/rustfmt.toml index c6c5b24c1..0471fd688 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,6 @@ -merge_imports = true edition = "2018" wrap_comments = true - +imports_granularity = "Crate" ignore = [ "mobilecoin", # The mobilecoin submodule is a dependency of this project.