mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 13:09:24 +00:00
Correct misc TODOs in monero-serai
This commit is contained in:
1
.github/workflows/coins-tests.yml
vendored
1
.github/workflows/coins-tests.yml
vendored
@@ -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 \
|
||||
|
||||
2
.github/workflows/coordinator-tests.yml
vendored
2
.github/workflows/coordinator-tests.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/full-stack-tests.yml
vendored
2
.github/workflows/full-stack-tests.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/message-queue-tests.yml
vendored
2
.github/workflows/message-queue-tests.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/monero-tests.yaml
vendored
2
.github/workflows/monero-tests.yaml
vendored
@@ -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 '*'
|
||||
|
||||
2
.github/workflows/no-std.yml
vendored
2
.github/workflows/no-std.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/processor-tests.yml
vendored
2
.github/workflows/processor-tests.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/reproducible-runtime.yml
vendored
2
.github/workflows/reproducible-runtime.yml
vendored
@@ -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
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ fn generators(prefix: &'static str, path: &str) {
|
||||
static GENERATORS_CELL: OnceLock<Generators> = 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}
|
||||
],
|
||||
}})
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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<Ed25519> 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(
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
75
coins/monero/rpc/simple-request/tests/tests.rs
Normal file
75
coins/monero/rpc/simple-request/tests/tests.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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<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.
|
||||
///
|
||||
/// 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::<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.
|
||||
@@ -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<Vec<u64>, RpcError> {
|
||||
/// `range` is in terms of block numbers.
|
||||
async fn get_output_distribution(
|
||||
&self,
|
||||
range: impl Send + RangeBounds<usize>,
|
||||
) -> Result<Vec<u64>, RpcError> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Distribution {
|
||||
distribution: Vec<u64>,
|
||||
@@ -667,10 +678,31 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
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(
|
||||
"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<const ADDR_BYTES: u128>(
|
||||
&self,
|
||||
address: &str,
|
||||
address: &Address<ADDR_BYTES>,
|
||||
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::<BlocksResponse>(
|
||||
"generateblocks",
|
||||
Some(json!({
|
||||
"wallet_address": address,
|
||||
"wallet_address": address.to_string(),
|
||||
"amount_of_blocks": block_count
|
||||
})),
|
||||
)
|
||||
|
||||
@@ -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<u64> {
|
||||
pub fn number(&self) -> Option<usize> {
|
||||
match &self.miner_transaction {
|
||||
Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => {
|
||||
match prefix.inputs.first() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -155,8 +155,7 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
||||
|
||||
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
|
||||
|
||||
@@ -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<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 => {
|
||||
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"),
|
||||
})
|
||||
.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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -176,17 +176,17 @@ impl BlockTrait<Monero> 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<Monero> 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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user