Correct misc TODOs in monero-serai

This commit is contained in:
Luke Parker
2024-07-05 23:30:02 -04:00
parent 90880cc9c8
commit 1f5e5fc7ac
27 changed files with 266 additions and 111 deletions

View File

@@ -32,6 +32,7 @@ jobs:
-p bitcoin-serai \ -p bitcoin-serai \
-p alloy-simple-request-transport \ -p alloy-simple-request-transport \
-p ethereum-serai \ -p ethereum-serai \
-p serai-ethereum-relayer \
-p monero-io \ -p monero-io \
-p monero-generators \ -p monero-generators \
-p monero-primitives \ -p monero-primitives \

View File

@@ -37,4 +37,4 @@ jobs:
uses: ./.github/actions/build-dependencies uses: ./.github/actions/build-dependencies
- name: Run coordinator Docker tests - 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

View File

@@ -19,4 +19,4 @@ jobs:
uses: ./.github/actions/build-dependencies uses: ./.github/actions/build-dependencies
- name: Run Full Stack Docker tests - 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

View File

@@ -33,4 +33,4 @@ jobs:
uses: ./.github/actions/build-dependencies uses: ./.github/actions/build-dependencies
- name: Run message-queue Docker tests - 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

View File

@@ -64,6 +64,7 @@ jobs:
- name: Run Integration Tests Without Features - name: Run Integration Tests Without Features
run: | run: |
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*' 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 --test '*'
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --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 '*' 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' }} if: ${{ matrix.version != 'v0.18.2.0' }}
run: | 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-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 --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-wallet-util --all-features --test '*'
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai-verify-chain --test '*' GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai-verify-chain --test '*'

View File

@@ -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 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 - 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

View File

@@ -37,4 +37,4 @@ jobs:
uses: ./.github/actions/build-dependencies uses: ./.github/actions/build-dependencies
- name: Run processor Docker tests - 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

View File

@@ -33,4 +33,4 @@ jobs:
uses: ./.github/actions/build-dependencies uses: ./.github/actions/build-dependencies
- name: Run Reproducible Runtime tests - 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

2
Cargo.lock generated
View File

@@ -4861,6 +4861,7 @@ dependencies = [
"async-trait", "async-trait",
"curve25519-dalek", "curve25519-dalek",
"hex", "hex",
"monero-address",
"monero-serai", "monero-serai",
"serde", "serde",
"serde_json", "serde_json",
@@ -4921,6 +4922,7 @@ dependencies = [
"async-trait", "async-trait",
"digest_auth", "digest_auth",
"hex", "hex",
"monero-address",
"monero-rpc", "monero-rpc",
"simple-request", "simple-request",
"tokio", "tokio",

View File

@@ -74,12 +74,14 @@ members = [
"substrate/coins/primitives", "substrate/coins/primitives",
"substrate/coins/pallet", "substrate/coins/pallet",
"substrate/in-instructions/primitives", "substrate/dex/pallet",
"substrate/in-instructions/pallet",
"substrate/validator-sets/primitives", "substrate/validator-sets/primitives",
"substrate/validator-sets/pallet", "substrate/validator-sets/pallet",
"substrate/in-instructions/primitives",
"substrate/in-instructions/pallet",
"substrate/signals/primitives", "substrate/signals/primitives",
"substrate/signals/pallet", "substrate/signals/pallet",

View File

@@ -43,10 +43,10 @@ fn generators(prefix: &'static str, path: &str) {
static GENERATORS_CELL: OnceLock<Generators> = OnceLock::new(); static GENERATORS_CELL: OnceLock<Generators> = OnceLock::new();
pub(crate) fn GENERATORS() -> &'static Generators {{ pub(crate) fn GENERATORS() -> &'static Generators {{
GENERATORS_CELL.get_or_init(|| Generators {{ GENERATORS_CELL.get_or_init(|| Generators {{
G: vec![ G: std_shims::vec![
{G_str} {G_str}
], ],
H: vec![ H: std_shims::vec![
{H_str} {H_str}
], ],
}}) }})

View File

@@ -1,6 +1,6 @@
#![allow(non_snake_case)] #![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}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar, edwards::EdwardsPoint};

View File

@@ -113,6 +113,11 @@ struct Interim {
} }
/// FROST-inspired algorithm for producing a CLSAG signature. /// 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)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ClsagMultisig { pub struct ClsagMultisig {
@@ -133,8 +138,6 @@ pub struct ClsagMultisig {
impl ClsagMultisig { impl ClsagMultisig {
/// Construct a new instance of multisignature CLSAG signing. /// 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( pub fn new(
transcript: RecommendedTranscript, transcript: RecommendedTranscript,
context: ClsagContext, context: ClsagContext,
@@ -261,7 +264,6 @@ impl Algorithm<Ed25519> for ClsagMultisig {
// opening of the commitment being re-randomized (and what it's re-randomized to) // 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")); 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")); self.msg = Some(msg.try_into().expect("CLSAG message should be 32-bytes"));
let sign_core = Clsag::sign_core( let sign_core = Clsag::sign_core(

View File

@@ -29,6 +29,7 @@ serde_json = { version = "1", default-features = false, features = ["alloc"] }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
monero-serai = { path = "..", default-features = false } monero-serai = { path = "..", default-features = false }
monero-address = { path = "../wallet/address", default-features = false }
[features] [features]
std = [ std = [
@@ -42,5 +43,6 @@ std = [
"serde_json/std", "serde_json/std",
"monero-serai/std", "monero-serai/std",
"monero-address/std",
] ]
default = ["std"] default = ["std"]

View File

@@ -24,3 +24,8 @@ simple-request = { path = "../../../../common/request", version = "0.1", default
tokio = { version = "1", default-features = false } tokio = { version = "1", default-features = false }
monero-rpc = { path = "..", default-features = false, features = ["std"] } 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"] }

View File

@@ -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();
}
}

View File

@@ -3,7 +3,10 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
use core::fmt::Debug; use core::{
fmt::Debug,
ops::{Bound, RangeBounds},
};
use std_shims::{ use std_shims::{
alloc::{boxed::Box, format}, alloc::{boxed::Box, format},
vec, vec,
@@ -26,6 +29,7 @@ use monero_serai::{
transaction::{Input, Timelock, Transaction}, transaction::{Input, Timelock, Transaction},
block::Block, block::Block,
}; };
use monero_address::Address;
// Number of blocks the fee estimate will be valid for // Number of blocks the fee estimate will be valid for
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/ // https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
@@ -70,9 +74,9 @@ pub enum RpcError {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct FeeRate { pub struct FeeRate {
/// The fee per-weight of the transaction. /// The fee per-weight of the transaction.
pub per_weight: u64, per_weight: u64,
/// The mask to round with. /// The mask to round with.
pub mask: u64, mask: u64,
} }
impl FeeRate { impl FeeRate {
@@ -108,22 +112,22 @@ impl FeeRate {
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
/// defined serialization. /// defined serialization.
pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> { pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> {
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. /// 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 { pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
let fee = self.per_weight * u64::try_from(weight).unwrap(); 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"); debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee");
fee fee
} }
/// Calculate the weight from the 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 { pub fn calculate_weight_from_fee(&self, fee: u64) -> usize {
usize::try_from(fee / self.per_weight).unwrap() usize::try_from(fee / self.per_weight).unwrap()
} }
@@ -323,7 +327,11 @@ pub trait Rpc: Sync + Clone + Debug {
struct HeightResponse { struct HeightResponse {
height: usize, height: usize,
} }
Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height) let res = self.rpc_call::<Option<()>, 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. /// Get the specified transactions.
@@ -460,7 +468,7 @@ pub trait Rpc: Sync + Clone + Debug {
// Make sure this is actually the block for this number // Make sure this is actually the block for this number
match block.miner_transaction.prefix().inputs.first() { match block.miner_transaction.prefix().inputs.first() {
Some(Input::Gen(actual)) => { Some(Input::Gen(actual)) => {
if usize::try_from(*actual) == Ok(number) { if *actual == number {
Ok(block) Ok(block)
} else { } else {
Err(RpcError::InvalidNode("different block than requested (number)".to_string())) Err(RpcError::InvalidNode("different block than requested (number)".to_string()))
@@ -658,8 +666,11 @@ pub trait Rpc: Sync + Clone + Debug {
/// Get the output distribution. /// Get the output distribution.
/// ///
/// `from` and `to` are heights, not block numbers, and inclusive. /// `range` is in terms of block numbers.
async fn get_output_distribution(&self, from: usize, to: usize) -> Result<Vec<u64>, RpcError> { async fn get_output_distribution(
&self,
range: impl Send + RangeBounds<usize>,
) -> Result<Vec<u64>, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct Distribution { struct Distribution {
distribution: Vec<u64>, distribution: Vec<u64>,
@@ -667,10 +678,31 @@ pub trait Rpc: Sync + Clone + Debug {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct Distributions { struct Distributions {
distributions: Vec<Distribution>, 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( .json_rpc_call(
"get_output_distribution", "get_output_distribution",
Some(json!({ Some(json!({
@@ -678,12 +710,27 @@ pub trait Rpc: Sync + Clone + Debug {
"amounts": [0], "amounts": [0],
"cumulative": true, "cumulative": true,
"from_height": from, "from_height": from,
"to_height": to, "to_height": if zero_zero_case { 1 } else { to },
})), })),
) )
.await?; .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. /// 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(|_| { Ok(Some([key, rpc_point(&out.mask)?]).filter(|_| {
if fingerprintable_canonical { if fingerprintable_canonical {
// TODO: Are timelock blocks by height or number?
Timelock::Block(height) >= txs[i].prefix().additional_timelock Timelock::Block(height) >= txs[i].prefix().additional_timelock
} else { } else {
out.unlocked out.unlocked
@@ -864,10 +912,9 @@ pub trait Rpc: Sync + Clone + Debug {
/// Generate blocks, with the specified address receiving the block reward. /// Generate blocks, with the specified address receiving the block reward.
/// ///
/// Returns the hashes of the generated blocks and the last block's number. /// Returns the hashes of the generated blocks and the last block's number.
// TODO: Take &Address, not &str? async fn generate_blocks<const ADDR_BYTES: u128>(
async fn generate_blocks(
&self, &self,
address: &str, address: &Address<ADDR_BYTES>,
block_count: usize, block_count: usize,
) -> Result<(Vec<[u8; 32]>, usize), RpcError> { ) -> Result<(Vec<[u8; 32]>, usize), RpcError> {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -880,7 +927,7 @@ pub trait Rpc: Sync + Clone + Debug {
.json_rpc_call::<BlocksResponse>( .json_rpc_call::<BlocksResponse>(
"generateblocks", "generateblocks",
Some(json!({ Some(json!({
"wallet_address": address, "wallet_address": address.to_string(),
"amount_of_blocks": block_count "amount_of_blocks": block_count
})), })),
) )

View File

@@ -83,7 +83,7 @@ impl Block {
/// ///
/// This information comes from the Block's miner transaction. If the miner transaction isn't /// This information comes from the Block's miner transaction. If the miner transaction isn't
/// structed as expected, this will return None. /// structed as expected, this will return None.
pub fn number(&self) -> Option<u64> { pub fn number(&self) -> Option<usize> {
match &self.miner_transaction { match &self.miner_transaction {
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => { Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => {
match prefix.inputs.first() { match prefix.inputs.first() {

View File

@@ -20,7 +20,7 @@ use crate::{
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum Input { pub enum Input {
/// An input for a miner transaction, which is generating new coins. /// An input for a miner transaction, which is generating new coins.
Gen(u64), Gen(usize),
/// An input spending an output on-chain. /// An input spending an output on-chain.
ToKey { ToKey {
/// The pool this input spends an output of. /// The pool this input spends an output of.

View File

@@ -155,8 +155,7 @@ async fn select_decoys<R: RngCore + CryptoRng>(
if distribution.len() < height { if distribution.len() < height {
// TODO: verify distribution elems are strictly increasing // TODO: verify distribution elems are strictly increasing
let extension = let extension = rpc.get_output_distribution(distribution.len() .. height).await?;
rpc.get_output_distribution(distribution.len(), height.saturating_sub(1)).await?;
distribution.extend(extension); distribution.extend(extension);
} }
// If asked to use an older height than previously asked, truncate to ensure accuracy // If asked to use an older height than previously asked, truncate to ensure accuracy

View File

@@ -1,15 +1,12 @@
use std_shims::{vec, vec::Vec}; use std_shims::{vec, vec::Vec};
use rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
use curve25519_dalek::{ use curve25519_dalek::{
constants::{ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE}, constants::{ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE},
Scalar, EdwardsPoint, Scalar, EdwardsPoint,
}; };
use crate::{ use crate::{
io::varint_len, io::{varint_len, write_varint},
primitives::Commitment, primitives::Commitment,
ringct::{ ringct::{
clsag::Clsag, bulletproofs::Bulletproof, EncryptedAmount, RctType, RctBase, RctPrunable, clsag::Clsag, bulletproofs::Bulletproof, EncryptedAmount, RctType, RctBase, RctPrunable,
@@ -138,17 +135,69 @@ impl SignableTransaction {
bp_commitments.push(Commitment::zero()); bp_commitments.push(Commitment::zero());
commitments.push(ED25519_BASEPOINT_POINT); 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 => { 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<u8>| {
bp.push(1);
bp.extend([0; 31]);
};
let push_scalar = |bp: &mut Vec<u8>| 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 => { 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<u8>| {
bp.push(1);
bp.extend([0; 31]);
};
let push_scalar = |bp: &mut Vec<u8>| 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"), _ => 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 // `- 1` to remove the one byte for the 0 fee
Transaction::V2 { Transaction::V2 {

View File

@@ -64,7 +64,11 @@ pub fn random_guaranteed_address() -> (Scalar, GuaranteedViewPair, MoneroAddress
// TODO: Support transactions already on-chain // TODO: Support transactions already on-chain
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions // 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 // mine until tx is in a block
let mut height = rpc.get_height().await.unwrap(); let mut height = rpc.get_height().await.unwrap();
let mut found = false; 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 // Mine 60 blocks to unlock a miner TX
let start = rpc.get_height().await.unwrap(); 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(); let block = rpc.get_block_by_number(start).await.unwrap();
scanner.scan(rpc, &block).await.unwrap().ignore_additional_timelock().swap_remove(0) scanner.scan(rpc, &block).await.unwrap().ignore_additional_timelock().swap_remove(0)
@@ -138,8 +142,7 @@ pub async fn rpc() -> SimpleRequestRpc {
AddressType::Legacy, AddressType::Legacy,
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
) );
.to_string();
// Mine 40 blocks to ensure decoy availability // Mine 40 blocks to ensure decoy availability
rpc.generate_blocks(&addr, 40).await.unwrap(); rpc.generate_blocks(&addr, 40).await.unwrap();
@@ -312,7 +315,7 @@ macro_rules! test {
let signed = sign(tx).await; let signed = sign(tx).await;
rpc.publish_transaction(&signed).await.unwrap(); rpc.publish_transaction(&signed).await.unwrap();
let block = 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(); let tx = rpc.get_transaction(signed.hash()).await.unwrap();
check_weight_and_fee(&tx, fee_rate); check_weight_and_fee(&tx, fee_rate);
let scanner = Scanner::new(view.clone()); let scanner = Scanner::new(view.clone());
@@ -333,7 +336,7 @@ macro_rules! test {
let signed = sign(tx).await; let signed = sign(tx).await;
rpc.publish_transaction(&signed).await.unwrap(); rpc.publish_transaction(&signed).await.unwrap();
let block = 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(); let tx = rpc.get_transaction(signed.hash()).await.unwrap();
if stringify!($name) != "spend_one_input_to_two_outputs_no_change" { 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, // Skip weight and fee check for the above test because when there is no change,

View File

@@ -41,7 +41,7 @@ async fn make_integrated_address(rpc: &SimpleRequestRpc, payment_id: [u8; 8]) ->
res.integrated_address 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 wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap();
let daemon_rpc = runner::rpc().await; 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(); wallet_rpc.json_rpc_call("get_address", Some(json!({ "account_index": 0 }))).await.unwrap();
// Fund the new wallet // 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) { async fn from_wallet_rpc_to_self(spec: AddressSpec) {
@@ -184,8 +185,7 @@ test!(
let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await; let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await;
// add destination // add destination
builder builder.add_payment(wallet_rpc_addr, 1000000);
.add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000);
(builder.build().unwrap(), wallet_rpc) (builder.build().unwrap(), wallet_rpc)
}, },
|_, _, tx: Transaction, _, data: SimpleRequestRpc| async move { |_, _, tx: Transaction, _, data: SimpleRequestRpc| async move {
@@ -342,8 +342,7 @@ test!(
let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await; let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await;
// add destination // add destination
builder builder.add_payment(wallet_rpc_addr, 1000000);
.add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000);
// Make 2 data that is the full 255 bytes // Make 2 data that is the full 255 bytes
for _ in 0 .. 2 { for _ in 0 .. 2 {

View File

@@ -176,17 +176,17 @@ impl BlockTrait<Monero> for Block {
async fn time(&self, rpc: &Monero) -> u64 { async fn time(&self, rpc: &Monero) -> u64 {
// Constant from Monero // 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 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 { if (self.number().unwrap() + 1) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
// Use the block number as the time // 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 timestamps = vec![self.header.timestamp];
let mut parent = self.parent(); 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; let mut parent_block;
while { while {
parent_block = rpc.rpc.get_block(parent).await; parent_block = rpc.rpc.get_block(parent).await;
@@ -217,7 +217,7 @@ impl BlockTrait<Monero> for Block {
// Monero also solely requires the block's time not be less than the median, it doesn't ensure // Monero also solely requires the block's time not be less than the median, it doesn't ensure
// it advances the median forward // it advances the median forward
// Ensure monotonicity despite both these issues by adding the block number to the median time // 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) { if eventuality.matches(&tx) {
res.insert( res.insert(
eventualities.map.remove(&tx.prefix().extra).unwrap().0, 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; 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 for block_num in (eventualities.block_number + 1) .. block.number().unwrap() {
(eventualities.block_number + 1) .. usize::try_from(block.number().unwrap()).unwrap()
{
let block = { let block = {
let mut block; let mut block;
while { while {
@@ -583,7 +581,7 @@ impl Network for Monero {
// Also check the current block // Also check the current block
check_block(self, eventualities, block, &mut res).await; 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 res
} }
@@ -664,7 +662,7 @@ impl Network for Monero {
#[cfg(test)] #[cfg(test)]
async fn get_block_number(&self, id: &[u8; 32]) -> usize { 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)] #[cfg(test)]
@@ -696,23 +694,7 @@ impl Network for Monero {
async fn mine_block(&self) { async fn mine_block(&self) {
// https://github.com/serai-dex/serai/issues/198 // https://github.com/serai-dex/serai/issues/198
sleep(std::time::Duration::from_millis(100)).await; sleep(std::time::Duration::from_millis(100)).await;
self.rpc.generate_blocks(&Self::test_address().into(), 1).await.unwrap();
#[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();
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -90,8 +90,7 @@ async fn mint_and_burn_test() {
use monero_wallet::{rpc::Rpc, ViewPair, address::Network}; use monero_wallet::{rpc::Rpc, ViewPair, address::Network};
let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)) let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
.legacy_address(Network::Mainnet) .legacy_address(Network::Mainnet);
.to_string();
let rpc = producer_handles.monero(ops).await; let rpc = producer_handles.monero(ops).await;
let mut res = Vec::with_capacity(count); let mut res = Vec::with_capacity(count);

View File

@@ -408,16 +408,11 @@ impl Coordinator {
use monero_wallet::{rpc::Rpc, address::Network, ViewPair}; use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC"); let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
let _: EmptyResponse = rpc rpc
.json_rpc_call( .generate_blocks(
"generateblocks", &ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
Some(serde_json::json!({ .legacy_address(Network::Mainnet),
"wallet_address": ViewPair::new( 1,
ED25519_BASEPOINT_POINT,
Zeroizing::new(Scalar::ONE),
).legacy_address(Network::Mainnet).to_string(),
"amount_of_blocks": 1,
})),
) )
.await .await
.unwrap(); .unwrap();

View File

@@ -200,16 +200,7 @@ impl Wallet {
let height = rpc.get_height().await.unwrap(); let height = rpc.get_height().await.unwrap();
// Mines 200 blocks so sufficient decoys exist, as only 60 is needed for maturity // Mines 200 blocks so sufficient decoys exist, as only 60 is needed for maturity
let _: EmptyResponse = rpc rpc.generate_blocks(&view_pair.legacy_address(Network::Mainnet), 200).await.unwrap();
.json_rpc_call(
"generateblocks",
Some(serde_json::json!({
"wallet_address": view_pair.legacy_address(Network::Mainnet).to_string(),
"amount_of_blocks": 200,
})),
)
.await
.unwrap();
let block = rpc.get_block(rpc.get_block_hash(height).await.unwrap()).await.unwrap(); let block = rpc.get_block(rpc.get_block_hash(height).await.unwrap()).await.unwrap();
Wallet::Monero { Wallet::Monero {