mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-28 22:49:42 +00:00
7d2d739042
* Rename the coins folder to networks Ethereum isn't a coin. It's a network. Resolves #357. * More renames of coins -> networks in orchestration * Correct paths in tests/ * cargo fmt
284 lines
9.3 KiB
Rust
284 lines
9.3 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("../README.md")]
|
|
#![deny(missing_docs)]
|
|
|
|
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
|
|
|
use serde::Deserialize;
|
|
use serde_json::json;
|
|
|
|
use monero_serai::{
|
|
io::decompress_point,
|
|
primitives::Commitment,
|
|
ringct::{RctPrunable, bulletproofs::BatchVerifier},
|
|
transaction::{Input, Transaction},
|
|
block::Block,
|
|
};
|
|
|
|
use monero_rpc::{RpcError, Rpc};
|
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
|
|
|
use tokio::task::JoinHandle;
|
|
|
|
async fn check_block(rpc: impl Rpc, block_i: usize) {
|
|
let hash = loop {
|
|
match rpc.get_block_hash(block_i).await {
|
|
Ok(hash) => break hash,
|
|
Err(RpcError::ConnectionError(e)) => {
|
|
println!("get_block_hash ConnectionError: {e}");
|
|
continue;
|
|
}
|
|
Err(e) => panic!("couldn't get block {block_i}'s hash: {e:?}"),
|
|
}
|
|
};
|
|
|
|
// TODO: Grab the JSON to also check it was deserialized correctly
|
|
#[derive(Deserialize, Debug)]
|
|
struct BlockResponse {
|
|
blob: String,
|
|
}
|
|
let res: BlockResponse = loop {
|
|
match rpc.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await {
|
|
Ok(res) => break res,
|
|
Err(RpcError::ConnectionError(e)) => {
|
|
println!("get_block ConnectionError: {e}");
|
|
continue;
|
|
}
|
|
Err(e) => panic!("couldn't get block {block_i} via block.hash(): {e:?}"),
|
|
}
|
|
};
|
|
|
|
let blob = hex::decode(res.blob).expect("node returned non-hex block");
|
|
let block = Block::read(&mut blob.as_slice())
|
|
.unwrap_or_else(|e| panic!("couldn't deserialize block {block_i}: {e}"));
|
|
assert_eq!(block.hash(), hash, "hash differs");
|
|
assert_eq!(block.serialize(), blob, "serialization differs");
|
|
|
|
let txs_len = 1 + block.transactions.len();
|
|
|
|
if !block.transactions.is_empty() {
|
|
// Test getting pruned transactions
|
|
loop {
|
|
match rpc.get_pruned_transactions(&block.transactions).await {
|
|
Ok(_) => break,
|
|
Err(RpcError::ConnectionError(e)) => {
|
|
println!("get_pruned_transactions ConnectionError: {e}");
|
|
continue;
|
|
}
|
|
Err(e) => panic!("couldn't call get_pruned_transactions: {e:?}"),
|
|
}
|
|
}
|
|
|
|
let txs = loop {
|
|
match rpc.get_transactions(&block.transactions).await {
|
|
Ok(txs) => break txs,
|
|
Err(RpcError::ConnectionError(e)) => {
|
|
println!("get_transactions ConnectionError: {e}");
|
|
continue;
|
|
}
|
|
Err(e) => panic!("couldn't call get_transactions: {e:?}"),
|
|
}
|
|
};
|
|
|
|
let mut batch = BatchVerifier::new();
|
|
for tx in txs {
|
|
match tx {
|
|
Transaction::V1 { prefix: _, signatures } => {
|
|
assert!(!signatures.is_empty());
|
|
continue;
|
|
}
|
|
Transaction::V2 { prefix: _, proofs: None } => {
|
|
panic!("proofs were empty in non-miner v2 transaction");
|
|
}
|
|
Transaction::V2 { ref prefix, proofs: Some(ref proofs) } => {
|
|
let sig_hash = tx.signature_hash().expect("no signature hash for TX with proofs");
|
|
// Verify all proofs we support proving for
|
|
// This is due to having debug_asserts calling verify within their proving, and CLSAG
|
|
// multisig explicitly calling verify as part of its signing process
|
|
// Accordingly, making sure our signature_hash algorithm is correct is great, and further
|
|
// making sure the verification functions are valid is appreciated
|
|
match &proofs.prunable {
|
|
RctPrunable::AggregateMlsagBorromean { .. } | RctPrunable::MlsagBorromean { .. } => {}
|
|
RctPrunable::MlsagBulletproofs { bulletproof, .. } |
|
|
RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } => {
|
|
assert!(bulletproof.batch_verify(
|
|
&mut rand_core::OsRng,
|
|
&mut batch,
|
|
&proofs.base.commitments
|
|
));
|
|
}
|
|
RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => {
|
|
assert!(bulletproof.batch_verify(
|
|
&mut rand_core::OsRng,
|
|
&mut batch,
|
|
&proofs.base.commitments
|
|
));
|
|
|
|
for (i, clsag) in clsags.iter().enumerate() {
|
|
let (amount, key_offsets, image) = match &prefix.inputs[i] {
|
|
Input::Gen(_) => panic!("Input::Gen"),
|
|
Input::ToKey { amount, key_offsets, key_image } => {
|
|
(amount, key_offsets, key_image)
|
|
}
|
|
};
|
|
|
|
let mut running_sum = 0;
|
|
let mut actual_indexes = vec![];
|
|
for offset in key_offsets {
|
|
running_sum += offset;
|
|
actual_indexes.push(running_sum);
|
|
}
|
|
|
|
async fn get_outs(
|
|
rpc: &impl Rpc,
|
|
amount: u64,
|
|
indexes: &[u64],
|
|
) -> Vec<[EdwardsPoint; 2]> {
|
|
#[derive(Deserialize, Debug)]
|
|
struct Out {
|
|
key: String,
|
|
mask: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
struct Outs {
|
|
outs: Vec<Out>,
|
|
}
|
|
|
|
let outs: Outs = loop {
|
|
match rpc
|
|
.rpc_call(
|
|
"get_outs",
|
|
Some(json!({
|
|
"get_txid": true,
|
|
"outputs": indexes.iter().map(|o| json!({
|
|
"amount": amount,
|
|
"index": o
|
|
})).collect::<Vec<_>>()
|
|
})),
|
|
)
|
|
.await
|
|
{
|
|
Ok(outs) => break outs,
|
|
Err(RpcError::ConnectionError(e)) => {
|
|
println!("get_outs ConnectionError: {e}");
|
|
continue;
|
|
}
|
|
Err(e) => panic!("couldn't connect to RPC to get outs: {e:?}"),
|
|
}
|
|
};
|
|
|
|
let rpc_point = |point: &str| {
|
|
decompress_point(
|
|
hex::decode(point)
|
|
.expect("invalid hex for ring member")
|
|
.try_into()
|
|
.expect("invalid point len for ring member"),
|
|
)
|
|
.expect("invalid point for ring member")
|
|
};
|
|
|
|
outs
|
|
.outs
|
|
.iter()
|
|
.map(|out| {
|
|
let mask = rpc_point(&out.mask);
|
|
if amount != 0 {
|
|
assert_eq!(mask, Commitment::new(Scalar::from(1u8), amount).calculate());
|
|
}
|
|
[rpc_point(&out.key), mask]
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
clsag
|
|
.verify(
|
|
&get_outs(&rpc, amount.unwrap_or(0), &actual_indexes).await,
|
|
image,
|
|
&pseudo_outs[i],
|
|
&sig_hash,
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert!(batch.verify());
|
|
}
|
|
|
|
println!("Deserialized, hashed, and reserialized {block_i} with {txs_len} TXs");
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let args = std::env::args().collect::<Vec<String>>();
|
|
|
|
// Read start block as the first arg
|
|
let mut block_i =
|
|
args.get(1).expect("no start block specified").parse::<usize>().expect("invalid start block");
|
|
|
|
// How many blocks to work on at once
|
|
let async_parallelism: usize =
|
|
args.get(2).unwrap_or(&"8".to_string()).parse::<usize>().expect("invalid parallelism argument");
|
|
|
|
// Read further args as RPC URLs
|
|
let default_nodes = vec![
|
|
"http://xmr-node-uk.cakewallet.com:18081".to_string(),
|
|
"http://xmr-node-eu.cakewallet.com:18081".to_string(),
|
|
];
|
|
let mut specified_nodes = vec![];
|
|
{
|
|
let mut i = 0;
|
|
loop {
|
|
let Some(node) = args.get(3 + i) else { break };
|
|
specified_nodes.push(node.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
let nodes = if specified_nodes.is_empty() { default_nodes } else { specified_nodes };
|
|
|
|
let rpc = |url: String| async move {
|
|
SimpleRequestRpc::new(url.clone())
|
|
.await
|
|
.unwrap_or_else(|_| panic!("couldn't create SimpleRequestRpc connected to {url}"))
|
|
};
|
|
let main_rpc = rpc(nodes[0].clone()).await;
|
|
let mut rpcs = vec![];
|
|
for i in 0 .. async_parallelism {
|
|
rpcs.push(rpc(nodes[i % nodes.len()].clone()).await);
|
|
}
|
|
|
|
let mut rpc_i = 0;
|
|
let mut handles: Vec<JoinHandle<()>> = vec![];
|
|
let mut height = 0;
|
|
loop {
|
|
let new_height = main_rpc.get_height().await.expect("couldn't call get_height");
|
|
if new_height == height {
|
|
break;
|
|
}
|
|
height = new_height;
|
|
|
|
while block_i < height {
|
|
if handles.len() >= async_parallelism {
|
|
// Guarantee one handle is complete
|
|
handles.swap_remove(0).await.unwrap();
|
|
|
|
// Remove all of the finished handles
|
|
let mut i = 0;
|
|
while i < handles.len() {
|
|
if handles[i].is_finished() {
|
|
handles.swap_remove(i).await.unwrap();
|
|
continue;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
handles.push(tokio::spawn(check_block(rpcs[rpc_i].clone(), block_i)));
|
|
rpc_i = (rpc_i + 1) % rpcs.len();
|
|
block_i += 1;
|
|
}
|
|
}
|
|
}
|