mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-13 22:49:25 +00:00
Compare commits
4 Commits
90880cc9c8
...
b2c962cd3e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c962cd3e | ||
|
|
788c4fc0a7 | ||
|
|
04df229df1 | ||
|
|
1f5e5fc7ac |
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",
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ impl Decoys {
|
||||
pub fn positions(&self) -> Vec<u64> {
|
||||
let mut res = Vec::with_capacity(self.len());
|
||||
res.push(self.offsets[0]);
|
||||
for m in 1 .. res.len() {
|
||||
for m in 1 .. self.len() {
|
||||
res.push(res[m - 1] + self.offsets[m]);
|
||||
}
|
||||
res
|
||||
|
||||
@@ -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.
|
||||
@@ -741,7 +788,7 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
Vec::new()
|
||||
vec![]
|
||||
};
|
||||
|
||||
// TODO: https://github.com/serai-dex/serai/issues/104
|
||||
@@ -763,6 +810,8 @@ 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?
|
||||
// TODO: This doesn't check the default timelock has been passed
|
||||
Timelock::Block(height) >= txs[i].prefix().additional_timelock
|
||||
} else {
|
||||
out.unlocked
|
||||
@@ -864,10 +913,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 +928,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 {
|
||||
|
||||
@@ -12,30 +12,31 @@ use crate::{
|
||||
primitives::{keccak256, Commitment},
|
||||
ringct::EncryptedAmount,
|
||||
SharedKeyDerivations,
|
||||
send::{InternalPayment, SignableTransaction},
|
||||
send::{InternalPayment, SignableTransaction, key_image_sort},
|
||||
};
|
||||
|
||||
fn seeded_rng(
|
||||
dst: &'static [u8],
|
||||
outgoing_view_key: &Zeroizing<[u8; 32]>,
|
||||
output_keys: impl Iterator<Item = EdwardsPoint>,
|
||||
) -> ChaCha20Rng {
|
||||
// Apply the DST
|
||||
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
||||
transcript.extend(dst);
|
||||
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
||||
transcript.extend(outgoing_view_key.as_slice());
|
||||
// Ensure uniqueness across transactions by binding to a use-once object
|
||||
// The output key is also binding to the output's key image, making this use-once
|
||||
for key in output_keys {
|
||||
transcript.extend(key.compress().to_bytes());
|
||||
}
|
||||
ChaCha20Rng::from_seed(keccak256(&transcript))
|
||||
}
|
||||
|
||||
impl SignableTransaction {
|
||||
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
|
||||
seeded_rng(dst, &self.outgoing_view_key, self.inputs.iter().map(|(input, _)| input.key()))
|
||||
// Apply the DST
|
||||
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
||||
transcript.extend(dst);
|
||||
|
||||
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
||||
transcript.extend(self.outgoing_view_key.as_slice());
|
||||
|
||||
// Ensure uniqueness across transactions by binding to a use-once object
|
||||
// The keys for the inputs is binding to their key images, making them use-once
|
||||
let mut input_keys = self.inputs.iter().map(|(input, _)| input.key()).collect::<Vec<_>>();
|
||||
// We sort the inputs mid-way through TX construction, so apply our own sort to ensure a
|
||||
// consistent order
|
||||
// We use the key image sort as it's applicable and well-defined, not because these are key
|
||||
// images
|
||||
input_keys.sort_by(key_image_sort);
|
||||
for key in input_keys {
|
||||
transcript.extend(key.compress().to_bytes());
|
||||
}
|
||||
|
||||
ChaCha20Rng::from_seed(keccak256(&transcript))
|
||||
}
|
||||
|
||||
fn has_payments_to_subaddresses(&self) -> bool {
|
||||
|
||||
@@ -45,20 +45,18 @@ test!(
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
},
|
||||
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move {
|
||||
|rpc, _, tx: Transaction, _: Scanner, state: (_, _)| async move {
|
||||
use rand_core::OsRng;
|
||||
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let height = rpc.get_height().await.unwrap();
|
||||
|
||||
let output_tx1 =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output_tx1.transaction(), tx.hash());
|
||||
let most_recent_o_index = rpc.get_o_indexes(tx.hash()).await.unwrap().pop().unwrap();
|
||||
|
||||
// Make sure output from tx1 is in the block in which it unlocks
|
||||
let out_tx1: OutputResponse =
|
||||
rpc.get_outs(&[output_tx1.index_on_blockchain()]).await.unwrap().swap_remove(0);
|
||||
rpc.get_outs(&[most_recent_o_index]).await.unwrap().swap_remove(0);
|
||||
assert_eq!(out_tx1.height, height - DEFAULT_LOCK_WINDOW);
|
||||
assert!(out_tx1.unlocked);
|
||||
|
||||
@@ -78,7 +76,7 @@ test!(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
selected_fresh_decoy = decoys[0].positions().contains(&output_tx1.index_on_blockchain());
|
||||
selected_fresh_decoy = decoys[0].positions().contains(&most_recent_o_index);
|
||||
attempts -= 1;
|
||||
}
|
||||
|
||||
@@ -101,6 +99,7 @@ test!(
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 2000000000000);
|
||||
output
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -125,20 +124,18 @@ test!(
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
},
|
||||
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move {
|
||||
|rpc, _, tx: Transaction, _: Scanner, state: (_, _)| async move {
|
||||
use rand_core::OsRng;
|
||||
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let height = rpc.get_height().await.unwrap();
|
||||
|
||||
let output_tx1 =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output_tx1.transaction(), tx.hash());
|
||||
let most_recent_o_index = rpc.get_o_indexes(tx.hash()).await.unwrap().pop().unwrap();
|
||||
|
||||
// Make sure output from tx1 is in the block in which it unlocks
|
||||
let out_tx1: OutputResponse =
|
||||
rpc.get_outs(&[output_tx1.index_on_blockchain()]).await.unwrap().swap_remove(0);
|
||||
rpc.get_outs(&[most_recent_o_index]).await.unwrap().swap_remove(0);
|
||||
assert_eq!(out_tx1.height, height - DEFAULT_LOCK_WINDOW);
|
||||
assert!(out_tx1.unlocked);
|
||||
|
||||
@@ -158,7 +155,7 @@ test!(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
selected_fresh_decoy = decoys[0].positions().contains(&output_tx1.index_on_blockchain());
|
||||
selected_fresh_decoy = decoys[0].positions().contains(&most_recent_o_index);
|
||||
attempts -= 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use monero_serai::transaction::Transaction;
|
||||
use monero_wallet::{
|
||||
rpc::Rpc,
|
||||
address::{AddressType, MoneroAddress},
|
||||
send::Eventuality,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
|
||||
@@ -15,7 +15,7 @@ use monero_wallet::{
|
||||
block::Block,
|
||||
rpc::{Rpc, FeeRate},
|
||||
address::{Network, AddressType, MoneroAddress},
|
||||
ViewPair, GuaranteedViewPair, WalletOutput, Scanner,
|
||||
DEFAULT_LOCK_WINDOW, ViewPair, GuaranteedViewPair, WalletOutput, Scanner,
|
||||
};
|
||||
|
||||
mod builder;
|
||||
@@ -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;
|
||||
@@ -84,15 +88,8 @@ pub async fn mine_until_unlocked(rpc: &SimpleRequestRpc, addr: &str, tx_hash: [u
|
||||
}
|
||||
|
||||
// Mine until tx's outputs are unlocked
|
||||
let o_indexes: Vec<u64> = rpc.get_o_indexes(tx_hash).await.unwrap();
|
||||
while rpc
|
||||
.get_unlocked_outputs(&o_indexes, height, false)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.any(|output| output.is_none())
|
||||
{
|
||||
height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1;
|
||||
for _ in 0 .. (DEFAULT_LOCK_WINDOW - 1) {
|
||||
rpc.generate_blocks(addr, 1).await.unwrap();
|
||||
}
|
||||
|
||||
block.unwrap()
|
||||
@@ -105,7 +102,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 +135,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();
|
||||
@@ -207,7 +203,7 @@ macro_rules! test {
|
||||
ViewPair,
|
||||
DecoySelection,
|
||||
Scanner,
|
||||
send::{Change, SignableTransaction},
|
||||
send::{Change, SignableTransaction, Eventuality},
|
||||
};
|
||||
|
||||
use runner::{
|
||||
@@ -271,23 +267,29 @@ macro_rules! test {
|
||||
let spend = spend.clone();
|
||||
#[cfg(feature = "multisig")]
|
||||
let keys = keys.clone();
|
||||
async move {
|
||||
if !multisig {
|
||||
tx.sign(&mut OsRng, &spend).unwrap()
|
||||
} else {
|
||||
#[cfg(not(feature = "multisig"))]
|
||||
panic!("Multisig branch called without the multisig feature");
|
||||
#[cfg(feature = "multisig")]
|
||||
{
|
||||
let mut machines = HashMap::new();
|
||||
for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) {
|
||||
machines.insert(i, tx.clone().multisig(&keys[&i]).unwrap());
|
||||
}
|
||||
|
||||
frost::tests::sign_without_caching(&mut OsRng, machines, &[])
|
||||
let eventuality = Eventuality::from(tx.clone());
|
||||
|
||||
let tx = if !multisig {
|
||||
tx.sign(&mut OsRng, &spend).unwrap()
|
||||
} else {
|
||||
#[cfg(not(feature = "multisig"))]
|
||||
panic!("multisig branch called without the multisig feature");
|
||||
#[cfg(feature = "multisig")]
|
||||
{
|
||||
let mut machines = HashMap::new();
|
||||
for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) {
|
||||
machines.insert(i, tx.clone().multisig(&keys[&i]).unwrap());
|
||||
}
|
||||
|
||||
frost::tests::sign_without_caching(&mut OsRng, machines, &[])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(&eventuality.extra(), &tx.prefix().extra, "eventuality extra was distinct");
|
||||
assert!(eventuality.matches(&tx), "eventuality didn't match");
|
||||
|
||||
tx
|
||||
};
|
||||
|
||||
// TODO: Generate a distinct wallet for each transaction to prevent overlap
|
||||
@@ -309,10 +311,10 @@ macro_rules! test {
|
||||
|
||||
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
||||
let fee_rate = tx.fee_rate().clone();
|
||||
let signed = sign(tx).await;
|
||||
let signed = sign(tx);
|
||||
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());
|
||||
@@ -330,10 +332,10 @@ macro_rules! test {
|
||||
*carried_state.downcast().unwrap()
|
||||
).await;
|
||||
let fee_rate = tx.fee_rate().clone();
|
||||
let signed = sign(tx).await;
|
||||
let signed = sign(tx);
|
||||
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,
|
||||
|
||||
@@ -72,21 +72,17 @@ test!(
|
||||
scan_guaranteed,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let subaddress = SubaddressIndex::new(0, 2).unwrap();
|
||||
|
||||
let view = runner::random_guaranteed_address().1;
|
||||
let mut scanner = GuaranteedScanner::new(view.clone());
|
||||
scanner.register_subaddress(subaddress);
|
||||
|
||||
let scanner = GuaranteedScanner::new(view.clone());
|
||||
builder.add_payment(view.address(Network::Mainnet, None, None), 5);
|
||||
(builder.build().unwrap(), (scanner, subaddress))
|
||||
(builder.build().unwrap(), scanner)
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (GuaranteedScanner, SubaddressIndex)| async move {
|
||||
|rpc, block, tx: Transaction, _, mut scanner: GuaranteedScanner| async move {
|
||||
let output =
|
||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.subaddress(), Some(state.1));
|
||||
assert_eq!(output.subaddress(), None);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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