diff --git a/.github/workflows/coins-tests.yml b/.github/workflows/coins-tests.yml index e4a3e75b..65d2b6dc 100644 --- a/.github/workflows/coins-tests.yml +++ b/.github/workflows/coins-tests.yml @@ -32,6 +32,7 @@ jobs: -p bitcoin-serai \ -p alloy-simple-request-transport \ -p ethereum-serai \ + -p serai-ethereum-relayer \ -p monero-io \ -p monero-generators \ -p monero-primitives \ diff --git a/.github/workflows/coordinator-tests.yml b/.github/workflows/coordinator-tests.yml index 138fd106..1ef27704 100644 --- a/.github/workflows/coordinator-tests.yml +++ b/.github/workflows/coordinator-tests.yml @@ -37,4 +37,4 @@ jobs: uses: ./.github/actions/build-dependencies - name: Run coordinator Docker tests - run: cd tests/coordinator && GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features + run: GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-coordinator-tests diff --git a/.github/workflows/full-stack-tests.yml b/.github/workflows/full-stack-tests.yml index baacf774..7bcce866 100644 --- a/.github/workflows/full-stack-tests.yml +++ b/.github/workflows/full-stack-tests.yml @@ -19,4 +19,4 @@ jobs: uses: ./.github/actions/build-dependencies - name: Run Full Stack Docker tests - run: cd tests/full-stack && GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features + run: GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-full-stack-tests diff --git a/.github/workflows/message-queue-tests.yml b/.github/workflows/message-queue-tests.yml index 7894549c..aa6f9328 100644 --- a/.github/workflows/message-queue-tests.yml +++ b/.github/workflows/message-queue-tests.yml @@ -33,4 +33,4 @@ jobs: uses: ./.github/actions/build-dependencies - name: Run message-queue Docker tests - run: cd tests/message-queue && GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features + run: GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-message-queue-tests diff --git a/.github/workflows/monero-tests.yaml b/.github/workflows/monero-tests.yaml index f258d232..96c175f1 100644 --- a/.github/workflows/monero-tests.yaml +++ b/.github/workflows/monero-tests.yaml @@ -64,6 +64,7 @@ jobs: - name: Run Integration Tests Without Features run: | GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*' + GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai-verify-chain --test '*' @@ -73,6 +74,7 @@ jobs: if: ${{ matrix.version != 'v0.18.2.0' }} run: | GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --all-features --test '*' + GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --all-features --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --all-features --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai-verify-chain --test '*' diff --git a/.github/workflows/no-std.yml b/.github/workflows/no-std.yml index 79ea5767..4e446daf 100644 --- a/.github/workflows/no-std.yml +++ b/.github/workflows/no-std.yml @@ -32,4 +32,4 @@ jobs: run: sudo apt update && sudo apt install -y gcc-riscv64-unknown-elf gcc-multilib && rustup target add riscv32imac-unknown-none-elf - name: Verify no-std builds - run: cd tests/no-std && CFLAGS=-I/usr/include cargo build --target riscv32imac-unknown-none-elf + run: CFLAGS=-I/usr/include cargo build --target riscv32imac-unknown-none-elf -p serai-no-std-tests diff --git a/.github/workflows/processor-tests.yml b/.github/workflows/processor-tests.yml index 0b5ecbbe..c5b02321 100644 --- a/.github/workflows/processor-tests.yml +++ b/.github/workflows/processor-tests.yml @@ -37,4 +37,4 @@ jobs: uses: ./.github/actions/build-dependencies - name: Run processor Docker tests - run: cd tests/processor && GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features + run: GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-processor-tests diff --git a/.github/workflows/reproducible-runtime.yml b/.github/workflows/reproducible-runtime.yml index d34e5ca5..2c418bd5 100644 --- a/.github/workflows/reproducible-runtime.yml +++ b/.github/workflows/reproducible-runtime.yml @@ -33,4 +33,4 @@ jobs: uses: ./.github/actions/build-dependencies - name: Run Reproducible Runtime tests - run: cd tests/reproducible-runtime && GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features + run: GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-reproducible-runtime-tests diff --git a/Cargo.lock b/Cargo.lock index 28bbfbd8..4bc741da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4861,6 +4861,7 @@ dependencies = [ "async-trait", "curve25519-dalek", "hex", + "monero-address", "monero-serai", "serde", "serde_json", @@ -4921,6 +4922,7 @@ dependencies = [ "async-trait", "digest_auth", "hex", + "monero-address", "monero-rpc", "simple-request", "tokio", diff --git a/Cargo.toml b/Cargo.toml index aad93ca0..7f63f626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,12 +74,14 @@ members = [ "substrate/coins/primitives", "substrate/coins/pallet", - "substrate/in-instructions/primitives", - "substrate/in-instructions/pallet", + "substrate/dex/pallet", "substrate/validator-sets/primitives", "substrate/validator-sets/pallet", + "substrate/in-instructions/primitives", + "substrate/in-instructions/pallet", + "substrate/signals/primitives", "substrate/signals/pallet", diff --git a/coins/monero/ringct/bulletproofs/build.rs b/coins/monero/ringct/bulletproofs/build.rs index 4c3565c9..6ef1bb54 100644 --- a/coins/monero/ringct/bulletproofs/build.rs +++ b/coins/monero/ringct/bulletproofs/build.rs @@ -43,10 +43,10 @@ fn generators(prefix: &'static str, path: &str) { static GENERATORS_CELL: OnceLock = OnceLock::new(); pub(crate) fn GENERATORS() -> &'static Generators {{ GENERATORS_CELL.get_or_init(|| Generators {{ - G: vec![ + G: std_shims::vec![ {G_str} ], - H: vec![ + H: std_shims::vec![ {H_str} ], }}) diff --git a/coins/monero/ringct/bulletproofs/src/plus/mod.rs b/coins/monero/ringct/bulletproofs/src/plus/mod.rs index 015242fb..ec7ca6a7 100644 --- a/coins/monero/ringct/bulletproofs/src/plus/mod.rs +++ b/coins/monero/ringct/bulletproofs/src/plus/mod.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use std_shims::{sync::OnceLock, vec}; +use std_shims::sync::OnceLock; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar, edwards::EdwardsPoint}; diff --git a/coins/monero/ringct/clsag/src/multisig.rs b/coins/monero/ringct/clsag/src/multisig.rs index f7265f85..bfbb8fc5 100644 --- a/coins/monero/ringct/clsag/src/multisig.rs +++ b/coins/monero/ringct/clsag/src/multisig.rs @@ -113,6 +113,11 @@ struct Interim { } /// FROST-inspired algorithm for producing a CLSAG signature. +/// +/// Before this has its `process_addendum` called, a mask must be set. Else this will panic. +/// +/// The message signed is expected to be a 32-byte value. Per Monero, it's the keccak256 hash of +/// the transaction data which is signed. This will panic if the message is not a 32-byte value. #[allow(non_snake_case)] #[derive(Clone, Debug)] pub struct ClsagMultisig { @@ -133,8 +138,6 @@ pub struct ClsagMultisig { impl ClsagMultisig { /// Construct a new instance of multisignature CLSAG signing. - /// - /// Before this has its `process_addendum` called, a mask must be set. Else this will panic. pub fn new( transcript: RecommendedTranscript, context: ClsagContext, @@ -261,7 +264,6 @@ impl Algorithm for ClsagMultisig { // opening of the commitment being re-randomized (and what it's re-randomized to) let mut rng = ChaCha20Rng::from_seed(self.transcript.rng_seed(b"decoy_responses")); - // TODO: Accept the message preimage and remove this panic self.msg = Some(msg.try_into().expect("CLSAG message should be 32-bytes")); let sign_core = Clsag::sign_core( diff --git a/coins/monero/rpc/Cargo.toml b/coins/monero/rpc/Cargo.toml index 88e3cdaf..ffe50379 100644 --- a/coins/monero/rpc/Cargo.toml +++ b/coins/monero/rpc/Cargo.toml @@ -29,6 +29,7 @@ serde_json = { version = "1", default-features = false, features = ["alloc"] } curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } monero-serai = { path = "..", default-features = false } +monero-address = { path = "../wallet/address", default-features = false } [features] std = [ @@ -42,5 +43,6 @@ std = [ "serde_json/std", "monero-serai/std", + "monero-address/std", ] default = ["std"] diff --git a/coins/monero/rpc/simple-request/Cargo.toml b/coins/monero/rpc/simple-request/Cargo.toml index 3d0b8bd9..b0c53fcb 100644 --- a/coins/monero/rpc/simple-request/Cargo.toml +++ b/coins/monero/rpc/simple-request/Cargo.toml @@ -24,3 +24,8 @@ simple-request = { path = "../../../../common/request", version = "0.1", default tokio = { version = "1", default-features = false } monero-rpc = { path = "..", default-features = false, features = ["std"] } + +[dev-dependencies] +monero-address = { path = "../../wallet/address", default-features = false, features = ["std"] } + +tokio = { version = "1", default-features = false, features = ["macros"] } diff --git a/coins/monero/rpc/simple-request/tests/tests.rs b/coins/monero/rpc/simple-request/tests/tests.rs new file mode 100644 index 00000000..c787387d --- /dev/null +++ b/coins/monero/rpc/simple-request/tests/tests.rs @@ -0,0 +1,75 @@ +use monero_address::{Network, MoneroAddress}; + +// monero-rpc doesn't include a transport +// We can't include the simple-request crate there as then we'd have a cyclical dependency +// Accordingly, we test monero-rpc here (implicitly testing the simple-request transport) +use monero_rpc::*; +use monero_simple_request_rpc::*; + +const ADDRESS: &str = + "4B33mFPMq6mKi7Eiyd5XuyKRVMGVZz1Rqb9ZTyGApXW5d1aT7UBDZ89ewmnWFkzJ5wPd2SFbn313vCT8a4E2Qf4KQH4pNey"; + +#[tokio::test] +async fn test_rpc() { + let rpc = + SimpleRequestRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap(); + + { + // Test get_height + let height = rpc.get_height().await.unwrap(); + // The height should be the amount of blocks on chain + // The number of a block should be its zero-indexed position + // Accordingly, there should be no block whose number is the height + assert!(rpc.get_block_by_number(height).await.is_err()); + let block_number = height - 1; + // There should be a block just prior + let block = rpc.get_block_by_number(block_number).await.unwrap(); + + // Also test the block RPC routes are consistent + assert_eq!(block.number().unwrap(), block_number); + assert_eq!(rpc.get_block(block.hash()).await.unwrap(), block); + assert_eq!(rpc.get_block_hash(block_number).await.unwrap(), block.hash()); + + // And finally the hardfork version route + assert_eq!(rpc.get_hardfork_version().await.unwrap(), block.header.hardfork_version); + } + + // Test generate_blocks + for amount_of_blocks in [1, 5] { + let (blocks, number) = rpc + .generate_blocks( + &MoneroAddress::from_str(Network::Mainnet, ADDRESS).unwrap(), + amount_of_blocks, + ) + .await + .unwrap(); + let height = rpc.get_height().await.unwrap(); + assert_eq!(number, height - 1); + + let mut actual_blocks = Vec::with_capacity(amount_of_blocks); + for i in (height - amount_of_blocks) .. height { + actual_blocks.push(rpc.get_block_by_number(i).await.unwrap().hash()); + } + assert_eq!(blocks, actual_blocks); + } + + // Test get_output_distribution + // It's documented to take two inclusive block numbers + { + let height = rpc.get_height().await.unwrap(); + + rpc.get_output_distribution(0 ..= height).await.unwrap_err(); + assert_eq!(rpc.get_output_distribution(0 .. height).await.unwrap().len(), height); + + assert_eq!(rpc.get_output_distribution(0 .. (height - 1)).await.unwrap().len(), height - 1); + assert_eq!(rpc.get_output_distribution(1 .. height).await.unwrap().len(), height - 1); + + assert_eq!(rpc.get_output_distribution(0 ..= 0).await.unwrap().len(), 1); + assert_eq!(rpc.get_output_distribution(0 ..= 1).await.unwrap().len(), 2); + assert_eq!(rpc.get_output_distribution(1 ..= 1).await.unwrap().len(), 1); + + rpc.get_output_distribution(0 .. 0).await.unwrap_err(); + #[allow(clippy::reversed_empty_ranges)] + rpc.get_output_distribution(1 .. 0).await.unwrap_err(); + } +} diff --git a/coins/monero/rpc/src/lib.rs b/coins/monero/rpc/src/lib.rs index 8962e776..48e59445 100644 --- a/coins/monero/rpc/src/lib.rs +++ b/coins/monero/rpc/src/lib.rs @@ -3,7 +3,10 @@ #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use core::fmt::Debug; +use core::{ + fmt::Debug, + ops::{Bound, RangeBounds}, +}; use std_shims::{ alloc::{boxed::Box, format}, vec, @@ -26,6 +29,7 @@ use monero_serai::{ transaction::{Input, Timelock, Transaction}, block::Block, }; +use monero_address::Address; // Number of blocks the fee estimate will be valid for // https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/ @@ -70,9 +74,9 @@ pub enum RpcError { #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] pub struct FeeRate { /// The fee per-weight of the transaction. - pub per_weight: u64, + per_weight: u64, /// The mask to round with. - pub mask: u64, + mask: u64, } impl FeeRate { @@ -108,22 +112,22 @@ impl FeeRate { /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol /// defined serialization. pub fn read(r: &mut impl io::Read) -> io::Result { - Ok(FeeRate { per_weight: read_u64(r)?, mask: read_u64(r)? }) + let per_weight = read_u64(r)?; + let mask = read_u64(r)?; + FeeRate::new(per_weight, mask).map_err(io::Error::other) } /// Calculate the fee to use from the weight. /// - /// This function may panic if any of the `FeeRate`'s fields are zero. + /// This function may panic upon overflow. pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 { let fee = self.per_weight * u64::try_from(weight).unwrap(); - let fee = ((fee + self.mask - 1) / self.mask) * self.mask; + let fee = fee.div_ceil(self.mask) * self.mask; debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee"); fee } /// Calculate the weight from the fee. - /// - /// This function may panic if any of the `FeeRate`'s fields are zero. pub fn calculate_weight_from_fee(&self, fee: u64) -> usize { usize::try_from(fee / self.per_weight).unwrap() } @@ -323,7 +327,11 @@ pub trait Rpc: Sync + Clone + Debug { struct HeightResponse { height: usize, } - Ok(self.rpc_call::, HeightResponse>("get_height", None).await?.height) + let res = self.rpc_call::, HeightResponse>("get_height", None).await?.height; + if res == 0 { + Err(RpcError::InvalidNode("node responded with 0 for the height".to_string()))?; + } + Ok(res) } /// Get the specified transactions. @@ -460,7 +468,7 @@ pub trait Rpc: Sync + Clone + Debug { // Make sure this is actually the block for this number match block.miner_transaction.prefix().inputs.first() { Some(Input::Gen(actual)) => { - if usize::try_from(*actual) == Ok(number) { + if *actual == number { Ok(block) } else { Err(RpcError::InvalidNode("different block than requested (number)".to_string())) @@ -658,8 +666,11 @@ pub trait Rpc: Sync + Clone + Debug { /// Get the output distribution. /// - /// `from` and `to` are heights, not block numbers, and inclusive. - async fn get_output_distribution(&self, from: usize, to: usize) -> Result, RpcError> { + /// `range` is in terms of block numbers. + async fn get_output_distribution( + &self, + range: impl Send + RangeBounds, + ) -> Result, RpcError> { #[derive(Deserialize, Debug)] struct Distribution { distribution: Vec, @@ -667,10 +678,31 @@ pub trait Rpc: Sync + Clone + Debug { #[derive(Deserialize, Debug)] struct Distributions { - distributions: Vec, + distributions: [Distribution; 1], } - let mut distributions: Distributions = self + let from = match range.start_bound() { + Bound::Included(from) => *from, + Bound::Excluded(from) => from + .checked_add(1) + .ok_or_else(|| RpcError::InternalError("range's from wasn't representable".to_string()))?, + Bound::Unbounded => 0, + }; + let to = match range.end_bound() { + Bound::Included(to) => *to, + Bound::Excluded(to) => to + .checked_sub(1) + .ok_or_else(|| RpcError::InternalError("range's to wasn't representable".to_string()))?, + Bound::Unbounded => self.get_height().await? - 1, + }; + if from > to { + Err(RpcError::InternalError(format!( + "malformed range: inclusive start {from}, inclusive end {to}" + )))?; + } + + let zero_zero_case = (from == 0) && (to == 0); + let distributions: Distributions = self .json_rpc_call( "get_output_distribution", Some(json!({ @@ -678,12 +710,27 @@ pub trait Rpc: Sync + Clone + Debug { "amounts": [0], "cumulative": true, "from_height": from, - "to_height": to, + "to_height": if zero_zero_case { 1 } else { to }, })), ) .await?; + let mut distributions = distributions.distributions; + let mut distribution = core::mem::take(&mut distributions[0].distribution); - Ok(distributions.distributions.swap_remove(0).distribution) + let expected_len = if zero_zero_case { 2 } else { (to - from) + 1 }; + if expected_len != distribution.len() { + Err(RpcError::InvalidNode(format!( + "distribution length ({}) wasn't of the requested length ({})", + distribution.len(), + expected_len + )))?; + } + // Requesting 0, 0 returns the distribution for the entire chain + // We work-around this by requesting 0, 1 (yielding two blocks), then popping the second block + if zero_zero_case { + distribution.pop(); + } + Ok(distribution) } /// Get the specified outputs from the RingCT (zero-amount) pool. @@ -763,6 +810,7 @@ pub trait Rpc: Sync + Clone + Debug { }; Ok(Some([key, rpc_point(&out.mask)?]).filter(|_| { if fingerprintable_canonical { + // TODO: Are timelock blocks by height or number? Timelock::Block(height) >= txs[i].prefix().additional_timelock } else { out.unlocked @@ -864,10 +912,9 @@ pub trait Rpc: Sync + Clone + Debug { /// Generate blocks, with the specified address receiving the block reward. /// /// Returns the hashes of the generated blocks and the last block's number. - // TODO: Take &Address, not &str? - async fn generate_blocks( + async fn generate_blocks( &self, - address: &str, + address: &Address, block_count: usize, ) -> Result<(Vec<[u8; 32]>, usize), RpcError> { #[derive(Debug, Deserialize)] @@ -880,7 +927,7 @@ pub trait Rpc: Sync + Clone + Debug { .json_rpc_call::( "generateblocks", Some(json!({ - "wallet_address": address, + "wallet_address": address.to_string(), "amount_of_blocks": block_count })), ) diff --git a/coins/monero/src/block.rs b/coins/monero/src/block.rs index c9668227..62a77f8b 100644 --- a/coins/monero/src/block.rs +++ b/coins/monero/src/block.rs @@ -83,7 +83,7 @@ impl Block { /// /// This information comes from the Block's miner transaction. If the miner transaction isn't /// structed as expected, this will return None. - pub fn number(&self) -> Option { + pub fn number(&self) -> Option { match &self.miner_transaction { Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => { match prefix.inputs.first() { diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 16061b46..6fe2a251 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -20,7 +20,7 @@ use crate::{ #[derive(Clone, PartialEq, Eq, Debug)] pub enum Input { /// An input for a miner transaction, which is generating new coins. - Gen(u64), + Gen(usize), /// An input spending an output on-chain. ToKey { /// The pool this input spends an output of. diff --git a/coins/monero/wallet/src/decoys.rs b/coins/monero/wallet/src/decoys.rs index d98d62e9..01424598 100644 --- a/coins/monero/wallet/src/decoys.rs +++ b/coins/monero/wallet/src/decoys.rs @@ -155,8 +155,7 @@ async fn select_decoys( if distribution.len() < height { // TODO: verify distribution elems are strictly increasing - let extension = - rpc.get_output_distribution(distribution.len(), height.saturating_sub(1)).await?; + let extension = rpc.get_output_distribution(distribution.len() .. height).await?; distribution.extend(extension); } // If asked to use an older height than previously asked, truncate to ensure accuracy diff --git a/coins/monero/wallet/src/send/tx.rs b/coins/monero/wallet/src/send/tx.rs index 299c17b6..e46cc909 100644 --- a/coins/monero/wallet/src/send/tx.rs +++ b/coins/monero/wallet/src/send/tx.rs @@ -1,15 +1,12 @@ use std_shims::{vec, vec::Vec}; -use rand_core::SeedableRng; -use rand_chacha::ChaCha20Rng; - use curve25519_dalek::{ constants::{ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE}, Scalar, EdwardsPoint, }; use crate::{ - io::varint_len, + io::{varint_len, write_varint}, primitives::Commitment, ringct::{ clsag::Clsag, bulletproofs::Bulletproof, EncryptedAmount, RctType, RctBase, RctPrunable, @@ -138,17 +135,69 @@ impl SignableTransaction { bp_commitments.push(Commitment::zero()); commitments.push(ED25519_BASEPOINT_POINT); } - // TODO: Remove this. Deserialize an empty BP? - let bulletproof = (match self.rct_type { + + let padded_log2 = { + let mut log2_find = 0; + while (1 << log2_find) < self.payments.len() { + log2_find += 1; + } + log2_find + }; + // This is log2 the padded amount of IPA rows + // We have 64 rows per commitment, so we need 64 * c IPA rows + // We rewrite this as 2**6 * c + // By finding the padded log2 of c, we get 2**6 * 2**p + // This declares the log2 to be 6 + p + let lr_len = 6 + padded_log2; + + let bulletproof = match self.rct_type { RctType::ClsagBulletproof => { - Bulletproof::prove(&mut ChaCha20Rng::from_seed([0; 32]), &bp_commitments) + let mut bp = Vec::with_capacity(((9 + (2 * lr_len)) * 32) + 2); + let push_point = |bp: &mut Vec| { + bp.push(1); + bp.extend([0; 31]); + }; + let push_scalar = |bp: &mut Vec| bp.extend([0; 32]); + for _ in 0 .. 4 { + push_point(&mut bp); + } + for _ in 0 .. 2 { + push_scalar(&mut bp); + } + for _ in 0 .. 2 { + write_varint(&lr_len, &mut bp).unwrap(); + for _ in 0 .. lr_len { + push_point(&mut bp); + } + } + for _ in 0 .. 3 { + push_scalar(&mut bp); + } + Bulletproof::read(&mut bp.as_slice()).expect("made an invalid dummy BP") } RctType::ClsagBulletproofPlus => { - Bulletproof::prove_plus(&mut ChaCha20Rng::from_seed([0; 32]), bp_commitments) + let mut bp = Vec::with_capacity(((6 + (2 * lr_len)) * 32) + 2); + let push_point = |bp: &mut Vec| { + bp.push(1); + bp.extend([0; 31]); + }; + let push_scalar = |bp: &mut Vec| bp.extend([0; 32]); + for _ in 0 .. 3 { + push_point(&mut bp); + } + for _ in 0 .. 3 { + push_scalar(&mut bp); + } + for _ in 0 .. 2 { + write_varint(&lr_len, &mut bp).unwrap(); + for _ in 0 .. lr_len { + push_point(&mut bp); + } + } + Bulletproof::read_plus(&mut bp.as_slice()).expect("made an invalid dummy BP+") } _ => panic!("unsupported RctType"), - }) - .expect("couldn't prove BP(+)s for this many payments despite checking in constructor?"); + }; // `- 1` to remove the one byte for the 0 fee Transaction::V2 { diff --git a/coins/monero/wallet/tests/runner/mod.rs b/coins/monero/wallet/tests/runner/mod.rs index abb90402..2469a145 100644 --- a/coins/monero/wallet/tests/runner/mod.rs +++ b/coins/monero/wallet/tests/runner/mod.rs @@ -64,7 +64,11 @@ pub fn random_guaranteed_address() -> (Scalar, GuaranteedViewPair, MoneroAddress // TODO: Support transactions already on-chain // TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions -pub async fn mine_until_unlocked(rpc: &SimpleRequestRpc, addr: &str, tx_hash: [u8; 32]) -> Block { +pub async fn mine_until_unlocked( + rpc: &SimpleRequestRpc, + addr: &MoneroAddress, + tx_hash: [u8; 32], +) -> Block { // mine until tx is in a block let mut height = rpc.get_height().await.unwrap(); let mut found = false; @@ -105,7 +109,7 @@ pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> Wal // Mine 60 blocks to unlock a miner TX let start = rpc.get_height().await.unwrap(); - rpc.generate_blocks(&view.legacy_address(Network::Mainnet).to_string(), 60).await.unwrap(); + rpc.generate_blocks(&view.legacy_address(Network::Mainnet), 60).await.unwrap(); let block = rpc.get_block_by_number(start).await.unwrap(); scanner.scan(rpc, &block).await.unwrap().ignore_additional_timelock().swap_remove(0) @@ -138,8 +142,7 @@ pub async fn rpc() -> SimpleRequestRpc { AddressType::Legacy, &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, - ) - .to_string(); + ); // Mine 40 blocks to ensure decoy availability rpc.generate_blocks(&addr, 40).await.unwrap(); @@ -312,7 +315,7 @@ macro_rules! test { let signed = sign(tx).await; rpc.publish_transaction(&signed).await.unwrap(); let block = - mine_until_unlocked(&rpc, &random_address().2.to_string(), signed.hash()).await; + mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await; let tx = rpc.get_transaction(signed.hash()).await.unwrap(); check_weight_and_fee(&tx, fee_rate); let scanner = Scanner::new(view.clone()); @@ -333,7 +336,7 @@ macro_rules! test { let signed = sign(tx).await; rpc.publish_transaction(&signed).await.unwrap(); let block = - mine_until_unlocked(&rpc, &random_address().2.to_string(), signed.hash()).await; + mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await; let tx = rpc.get_transaction(signed.hash()).await.unwrap(); if stringify!($name) != "spend_one_input_to_two_outputs_no_change" { // Skip weight and fee check for the above test because when there is no change, diff --git a/coins/monero/wallet/tests/wallet2_compatibility.rs b/coins/monero/wallet/tests/wallet2_compatibility.rs index 5348e243..e7815d70 100644 --- a/coins/monero/wallet/tests/wallet2_compatibility.rs +++ b/coins/monero/wallet/tests/wallet2_compatibility.rs @@ -41,7 +41,7 @@ async fn make_integrated_address(rpc: &SimpleRequestRpc, payment_id: [u8; 8]) -> res.integrated_address } -async fn initialize_rpcs() -> (SimpleRequestRpc, SimpleRequestRpc, String) { +async fn initialize_rpcs() -> (SimpleRequestRpc, SimpleRequestRpc, MoneroAddress) { let wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap(); let daemon_rpc = runner::rpc().await; @@ -64,9 +64,10 @@ async fn initialize_rpcs() -> (SimpleRequestRpc, SimpleRequestRpc, String) { wallet_rpc.json_rpc_call("get_address", Some(json!({ "account_index": 0 }))).await.unwrap(); // Fund the new wallet - daemon_rpc.generate_blocks(&address.address, 70).await.unwrap(); + let address = MoneroAddress::from_str(Network::Mainnet, &address.address).unwrap(); + daemon_rpc.generate_blocks(&address, 70).await.unwrap(); - (wallet_rpc, daemon_rpc, address.address) + (wallet_rpc, daemon_rpc, address) } async fn from_wallet_rpc_to_self(spec: AddressSpec) { @@ -184,8 +185,7 @@ test!( let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await; // add destination - builder - .add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000); + builder.add_payment(wallet_rpc_addr, 1000000); (builder.build().unwrap(), wallet_rpc) }, |_, _, tx: Transaction, _, data: SimpleRequestRpc| async move { @@ -342,8 +342,7 @@ test!( let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await; // add destination - builder - .add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000); + builder.add_payment(wallet_rpc_addr, 1000000); // Make 2 data that is the full 255 bytes for _ in 0 .. 2 { diff --git a/processor/src/networks/monero.rs b/processor/src/networks/monero.rs index 60363bbc..43613829 100644 --- a/processor/src/networks/monero.rs +++ b/processor/src/networks/monero.rs @@ -176,17 +176,17 @@ impl BlockTrait for Block { async fn time(&self, rpc: &Monero) -> u64 { // Constant from Monero - const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60; + const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60; // If Monero doesn't have enough blocks to build a window, it doesn't define a network time if (self.number().unwrap() + 1) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { // Use the block number as the time - return self.number().unwrap(); + return u64::try_from(self.number().unwrap()).unwrap(); } let mut timestamps = vec![self.header.timestamp]; let mut parent = self.parent(); - while u64::try_from(timestamps.len()).unwrap() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { + while timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { let mut parent_block; while { parent_block = rpc.rpc.get_block(parent).await; @@ -217,7 +217,7 @@ impl BlockTrait for Block { // Monero also solely requires the block's time not be less than the median, it doesn't ensure // it advances the median forward // Ensure monotonicity despite both these issues by adding the block number to the median time - res + self.number().unwrap() + res + u64::try_from(self.number().unwrap()).unwrap() } } @@ -553,19 +553,17 @@ impl Network for Monero { if eventuality.matches(&tx) { res.insert( eventualities.map.remove(&tx.prefix().extra).unwrap().0, - (usize::try_from(block.number().unwrap()).unwrap(), tx.id(), tx), + (block.number().unwrap(), tx.id(), tx), ); } } } eventualities.block_number += 1; - assert_eq!(eventualities.block_number, usize::try_from(block.number().unwrap()).unwrap()); + assert_eq!(eventualities.block_number, block.number().unwrap()); } - for block_num in - (eventualities.block_number + 1) .. usize::try_from(block.number().unwrap()).unwrap() - { + for block_num in (eventualities.block_number + 1) .. block.number().unwrap() { let block = { let mut block; while { @@ -583,7 +581,7 @@ impl Network for Monero { // Also check the current block check_block(self, eventualities, block, &mut res).await; - assert_eq!(eventualities.block_number, usize::try_from(block.number().unwrap()).unwrap()); + assert_eq!(eventualities.block_number, block.number().unwrap()); res } @@ -664,7 +662,7 @@ impl Network for Monero { #[cfg(test)] async fn get_block_number(&self, id: &[u8; 32]) -> usize { - self.rpc.get_block(*id).await.unwrap().number().unwrap().try_into().unwrap() + self.rpc.get_block(*id).await.unwrap().number().unwrap() } #[cfg(test)] @@ -696,23 +694,7 @@ impl Network for Monero { async fn mine_block(&self) { // https://github.com/serai-dex/serai/issues/198 sleep(std::time::Duration::from_millis(100)).await; - - #[derive(Debug, serde::Deserialize)] - struct EmptyResponse {} - let _: EmptyResponse = self - .rpc - .rpc_call( - "json_rpc", - Some(serde_json::json!({ - "method": "generateblocks", - "params": { - "wallet_address": Self::test_address().to_string(), - "amount_of_blocks": 1 - }, - })), - ) - .await - .unwrap(); + self.rpc.generate_blocks(&Self::test_address().into(), 1).await.unwrap(); } #[cfg(test)] diff --git a/tests/full-stack/src/tests/mint_and_burn.rs b/tests/full-stack/src/tests/mint_and_burn.rs index 0a9da011..5ccd11ad 100644 --- a/tests/full-stack/src/tests/mint_and_burn.rs +++ b/tests/full-stack/src/tests/mint_and_burn.rs @@ -90,8 +90,7 @@ async fn mint_and_burn_test() { use monero_wallet::{rpc::Rpc, ViewPair, address::Network}; let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)) - .legacy_address(Network::Mainnet) - .to_string(); + .legacy_address(Network::Mainnet); let rpc = producer_handles.monero(ops).await; let mut res = Vec::with_capacity(count); diff --git a/tests/processor/src/lib.rs b/tests/processor/src/lib.rs index ec5675aa..58e8ba8f 100644 --- a/tests/processor/src/lib.rs +++ b/tests/processor/src/lib.rs @@ -408,16 +408,11 @@ impl Coordinator { use monero_wallet::{rpc::Rpc, address::Network, ViewPair}; let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC"); - let _: EmptyResponse = rpc - .json_rpc_call( - "generateblocks", - Some(serde_json::json!({ - "wallet_address": ViewPair::new( - ED25519_BASEPOINT_POINT, - Zeroizing::new(Scalar::ONE), - ).legacy_address(Network::Mainnet).to_string(), - "amount_of_blocks": 1, - })), + rpc + .generate_blocks( + &ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)) + .legacy_address(Network::Mainnet), + 1, ) .await .unwrap(); diff --git a/tests/processor/src/networks.rs b/tests/processor/src/networks.rs index 3902042f..29375949 100644 --- a/tests/processor/src/networks.rs +++ b/tests/processor/src/networks.rs @@ -200,16 +200,7 @@ impl Wallet { let height = rpc.get_height().await.unwrap(); // Mines 200 blocks so sufficient decoys exist, as only 60 is needed for maturity - let _: EmptyResponse = rpc - .json_rpc_call( - "generateblocks", - Some(serde_json::json!({ - "wallet_address": view_pair.legacy_address(Network::Mainnet).to_string(), - "amount_of_blocks": 200, - })), - ) - .await - .unwrap(); + rpc.generate_blocks(&view_pair.legacy_address(Network::Mainnet), 200).await.unwrap(); let block = rpc.get_block(rpc.get_block_hash(height).await.unwrap()).await.unwrap(); Wallet::Monero {