ci: include macos + windows (#52)

* ci: install boost, include macos + windows

* cryptonight: fix `MSVC`

* cryptonight: use `flag_if_supported()`

* fix cryptonight builds

* update randomX

* fix rx builds

* add memwipe

* include memwipe.c in build

* spawn monerod in msys2 for windows

* fix last commit

* install dependencies before spawning monerod

* remove --detach

* try another way of spawning monerod

* add /I

* download and spawn monerod as a part of tests

* add download.rs

* extend time for monerod spawn

* move sleep and show monerod output

* fix clippy

* change stdin to pipped

* #[cfg(unix)] on bytes::Buf

* fix macos capitalisation

* remove tar.bz2 on macos expected dir

* remove zip on windows expected dir

* fix todo

* add docs

* fix a couple typos

---------

Co-authored-by: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com>
Co-authored-by: Boog900 <boog900@tutanota.com>
This commit is contained in:
hinto-janai 2024-02-12 08:39:15 -05:00 committed by GitHub
parent ba0f82c356
commit 630faed263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1224 additions and 227 deletions

View file

@ -1,62 +0,0 @@
# MIT License
#
# Copyright (c) 2022-2023 Luke Parker
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# Initially taken from Serai Dex: https://github.com/serai-dex/serai/blob/b823413c9b7ae6747b9af99e18379cfc49f4271a/.github/actions/monero/action.yml.
name: monero-regtest
description: Spawns a regtest Monero daemon
inputs:
version:
description: "Version to download and run"
required: false
default: v0.18.2.0
runs:
using: "composite"
steps:
- name: Monero Daemon Cache
id: cache-monerod
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
with:
path: monerod
key: monerod-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }}
- name: Download the Monero Daemon
if: steps.cache-monerod.outputs.cache-hit != 'true'
# Calculates OS/ARCH to demonstrate it, yet then locks to linux-x64 due
# to the contained folder not following the same naming scheme and
# requiring further expansion not worth doing right now
shell: bash
run: |
RUNNER_OS=${{ runner.os }}
RUNNER_ARCH=${{ runner.arch }}
RUNNER_OS=${RUNNER_OS,,}
RUNNER_ARCH=${RUNNER_ARCH,,}
RUNNER_OS=linux
RUNNER_ARCH=x64
FILE=monero-$RUNNER_OS-$RUNNER_ARCH-${{ inputs.version }}.tar.bz2
wget https://downloads.getmonero.org/cli/$FILE
tar -xvf $FILE
mv monero-x86_64-linux-gnu-${{ inputs.version }}/monerod monerod
- name: Monero Regtest Daemon
shell: bash
run: ./monerod --regtest --fixed-difficulty=1 --detach --out-peers 0

View file

@ -5,6 +5,7 @@ on:
branches: [ "main" ] branches: [ "main" ]
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
workflow_dispatch:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@ -14,76 +15,85 @@ env:
RUST_MIN_STACK: 8000000 RUST_MIN_STACK: 8000000
jobs: jobs:
# Run format separately.
fmt: fmt:
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
steps: steps:
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Format
run: cargo fmt --all --check
- name: Fmt # All other CI.
run: cargo fmt --all --check ci:
runs-on: ${{ matrix.os }}
build: strategy:
runs-on: ubuntu-latest matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
include:
- os: windows-latest
shell: msys2 {0}
- os: macos-latest
shell: bash
- os: ubuntu-latest
shell: bash
defaults:
run:
shell: ${{ matrix.shell }}
steps: steps:
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/cache@v3 - name: Cache
id: cache uses: actions/cache@v3
with: with:
path: target path: |
key: ${{ runner.os }}-cache target
~/.cargo
~/.rustup
key: ${{ matrix.os }}
- name: Install dependencies # Packages other than `Boost` used by `Monero` are listed here.
run: sudo apt install -y libboost-dev # https://github.com/monero-project/monero/blob/c444a7e002036e834bfb4c68f04a121ce1af5825/.github/workflows/build.yml#L71
- name: Build - name: Install dependencies (Linux)
run: cargo build --all-features --all-targets --workspace if: matrix.os == 'ubuntu-latest'
run: sudo apt install -y libboost-dev
- uses: actions/cache/save@v3 - name: Install dependencies (macOS)
id: cache-save-short-term if: matrix.os == 'macos-latest'
with: run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost
path: target
key: ${{ runner.os }}-cache-${{ github.sha }}
clippy: - name: Install dependencies (Windows)
runs-on: ubuntu-latest if: matrix.os == 'windows-latest'
needs: build uses: msys2/setup-msys2@v2
with:
path-type: inherit
update: true
install: mingw-w64-x86_64-toolchain mingw-w64-x86_64-boost msys2-runtime-devel git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
steps: - name: Switch target (Windows)
- uses: actions/checkout@v3 if: matrix.os == 'windows-latest'
run: rustup toolchain install stable-x86_64-pc-windows-gnu -c clippy && rustup set default-host x86_64-pc-windows-gnu && rustup default stable-x86_64-pc-windows-gnu
- uses: actions/cache/restore@v3 - name: Clippy (fail on warnings)
id: cache run: cargo clippy --workspace --all-targets --all-features -- -D warnings
with:
path: target
key: ${{ runner.os }}-cache-${{ github.sha }}
- name: Install dependencies - name: Test
run: sudo apt install -y libboost-dev run: cargo test --all-features --workspace --all-targets
- name: Clippy # TODO: upload binaries with `actions/upload-artifact@v3`
run: cargo clippy --workspace --all-targets --all-features -- -D warnings - name: Build
run: cargo build --all-features --all-targets --workspace
tests:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- uses: actions/cache/restore@v3
id: cache
with:
path: target
key: ${{ runner.os }}-cache-${{ github.sha }}
- name: Spawn monerod
uses: ./.github/actions/monerod-regtest
- name: Install dependencies
run: sudo apt install -y libboost-dev
- name: Test
run: cargo test --all-features --workspace --all-targets && cargo test --all-features --workspace --doc

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
target/ target/
.vscode .vscode
monerod

792
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -43,16 +43,15 @@ chrono = { version = "0.4.31", default-features = false }
crypto-bigint = { version = "0.5.5", default-features = false } crypto-bigint = { version = "0.5.5", default-features = false }
crossbeam = { version = "0.8.4", default-features = false } crossbeam = { version = "0.8.4", default-features = false }
curve25519-dalek = { version = "4.1.1", default-features = false } curve25519-dalek = { version = "4.1.1", default-features = false }
dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "a59966b", default-features = false } dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "f3429ec1ef386da1458f4ed244402f38e3e12930", default-features = false }
dirs = { version = "5.0.1", default-features = false } dirs = { version = "5.0.1", default-features = false }
futures = { version = "0.3.29", default-features = false } futures = { version = "0.3.29", default-features = false }
hex = { version = "0.4.3", default-features = false } hex = { version = "0.4.3", default-features = false }
hex-literal = { version = "0.4", default-features = false } hex-literal = { version = "0.4", default-features = false }
monero-epee-bin-serde = { git = "https://github.com/monero-rs/monero-epee-bin-serde.git", rev = "fae7a23", default-features = false } monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "f3429ec1ef386da1458f4ed244402f38e3e12930", default-features = false }
monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "a59966b", default-features = false } multiexp = { git = "https://github.com/Cuprate/serai.git", rev = "f3429ec1ef386da1458f4ed244402f38e3e12930", default-features = false }
multiexp = { git = "https://github.com/Cuprate/serai.git", rev = "a59966b", default-features = false }
pin-project = { version = "1.1.3", default-features = false } pin-project = { version = "1.1.3", default-features = false }
randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "6496a61", default-features = false } randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false }
rand = { version = "0.8.5", default-features = false } rand = { version = "0.8.5", default-features = false }
rayon = { version = "1.8.0", default-features = false } rayon = { version = "1.8.0", default-features = false }
serde_bytes = { version = "0.11.12", default-features = false } serde_bytes = { version = "0.11.12", default-features = false }
@ -68,8 +67,9 @@ tracing-subscriber = { version = "0.3.17", default-features = false }
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1.40", default-features = false }
## workspace.dev-dependencies ## workspace.dev-dependencies
proptest = { version = "1" } reqwest = { version = "0.11.24" }
proptest-derive = { version = "0.4.0" } proptest = { version = "1" }
proptest-derive = { version = "0.4.0" }
## TODO: ## TODO:

View file

@ -19,10 +19,10 @@ fn main() {
.file("c/keccak.c") .file("c/keccak.c")
.file("c/oaes_lib.c") .file("c/oaes_lib.c")
.file("c/skein.c") .file("c/skein.c")
.file("c/memwipe.c")
.file("c/slow-hash.c") .file("c/slow-hash.c")
.file("c/CryptonightR_JIT.c") .file("c/CryptonightR_JIT.c")
.flag("-O3") .flag_if_supported("-fexceptions")
.flag("-fexceptions")
// c/oaes_lib.c: In function oaes_get_seed: // c/oaes_lib.c: In function oaes_get_seed:
// c/oaes_lib.c:515:9: warning: ftime is deprecated: Use gettimeofday or clock_gettime instead [-Wdeprecated-declarations] // c/oaes_lib.c:515:9: warning: ftime is deprecated: Use gettimeofday or clock_gettime instead [-Wdeprecated-declarations]
// 515 | ftime (&timer); // 515 | ftime (&timer);
@ -31,13 +31,18 @@ fn main() {
// /usr/include/sys/timeb.h:29:12: note: declared here // /usr/include/sys/timeb.h:29:12: note: declared here
// 29 | extern int ftime (struct timeb *__timebuf) // 29 | extern int ftime (struct timeb *__timebuf)
// | ^~~~~ // | ^~~~~
.flag("-Wno-deprecated-declarations"); // This flag doesn't work on MSVC and breaks CI.
.flag_if_supported("-Wno-deprecated-declarations");
// Optimization flags are automatically added.
// https://docs.rs/cc/latest/cc/struct.Build.html#method.opt_level
let target = env::var("TARGET").unwrap(); let target = env::var("TARGET").unwrap();
if target.contains("x86_64") { if target.contains("x86_64") {
// FIXME: what are the equivalent flags for MSVC?
cfg.file("c/CryptonightR_template.S") cfg.file("c/CryptonightR_template.S")
.flag("-maes") .flag_if_supported("-maes")
.flag("-msse2"); .flag_if_supported("-msse2");
} }
cfg.compile("cryptonight") cfg.compile("cryptonight")

View file

@ -40,7 +40,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <memwipe.h> #include "memwipe.h"
#include "blake256.h" #include "blake256.h"
#define U8TO32(p) \ #define U8TO32(p) \

115
cryptonight/c/memwipe.c Normal file
View file

@ -0,0 +1,115 @@
// Copyright (c) 2017-2023, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file Copyright (c) 2009-2015 The Bitcoin Core developers
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#ifdef HAVE_EXPLICIT_BZERO
#include <strings.h>
#endif
#include "memwipe.h"
#if defined(_MSC_VER)
#define SCARECROW \
__asm;
#else
#define SCARECROW \
__asm__ __volatile__("" : : "r"(ptr) : "memory");
#endif
#ifdef HAVE_MEMSET_S
void *memwipe(void *ptr, size_t n)
{
if (n > 0 && memset_s(ptr, n, 0, n))
{
#ifdef NDEBUG
fprintf(stderr, "Error: memset_s failed\n");
_exit(1);
#else
abort();
#endif
}
SCARECROW // might as well...
return ptr;
}
#elif defined HAVE_EXPLICIT_BZERO
void *memwipe(void *ptr, size_t n)
{
if (n > 0)
explicit_bzero(ptr, n);
SCARECROW
return ptr;
}
#else
/* The memory_cleanse implementation is taken from Bitcoin */
/* Compilers have a bad habit of removing "superfluous" memset calls that
* are trying to zero memory. For example, when memset()ing a buffer and
* then free()ing it, the compiler might decide that the memset is
* unobservable and thus can be removed.
*
* Previously we used OpenSSL which tried to stop this by a) implementing
* memset in assembly on x86 and b) putting the function in its own file
* for other platforms.
*
* This change removes those tricks in favour of using asm directives to
* scare the compiler away. As best as our compiler folks can tell, this is
* sufficient and will continue to be so.
*
* Adam Langley <agl@google.com>
* Commit: ad1907fe73334d6c696c8539646c21b11178f20f
* BoringSSL (LICENSE: ISC)
*/
static void memory_cleanse(void *ptr, size_t len)
{
memset(ptr, 0, len);
/* As best as we can tell, this is sufficient to break any optimisations that
might try to eliminate "superfluous" memsets. If there's an easy way to
detect memset_s, it would be better to use that. */
SCARECROW
}
void *memwipe(void *ptr, size_t n)
{
if (n > 0)
memory_cleanse(ptr, n);
SCARECROW
return ptr;
}
#endif

View file

@ -80,7 +80,9 @@ pub fn low_priority_thread() {
// SAFETY: calling C. // SAFETY: calling C.
// We are _lowering_ our priority, not increasing, so this function should never fail. // We are _lowering_ our priority, not increasing, so this function should never fail.
unsafe { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE) }; unsafe {
let _ = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
}
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@ -91,7 +93,9 @@ pub fn low_priority_thread() {
// SAFETY: calling C. // SAFETY: calling C.
// We are _lowering_ our priority, not increasing, so this function should never fail. // We are _lowering_ our priority, not increasing, so this function should never fail.
unsafe { libc::nice(NICE_MAX) }; unsafe {
let _ = libc::nice(NICE_MAX);
}
} }
} }

View file

@ -84,7 +84,7 @@ epee_object!(
); );
/// The status field of an okay ping response /// The status field of an okay ping response
pub const PING_OK_RESPONSE_STATUS_TEXT: Bytes = Bytes::from_static("OK".as_bytes()); pub static PING_OK_RESPONSE_STATUS_TEXT: Bytes = Bytes::from_static("OK".as_bytes());
/// A Ping Response /// A Ping Response
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -1,5 +1,4 @@
use std::sync::Arc; use std::sync::Arc;
use std::{net::SocketAddr, str::FromStr};
use futures::{channel::mpsc, StreamExt}; use futures::{channel::mpsc, StreamExt};
use tokio::sync::{broadcast, Semaphore}; use tokio::sync::{broadcast, Semaphore};
@ -14,7 +13,10 @@ use monero_p2p::{
ConnectionDirection, ConnectionDirection,
}; };
use cuprate_test_utils::test_netzone::{TestNetZone, TestNetZoneAddr}; use cuprate_test_utils::{
monerod::monerod,
test_netzone::{TestNetZone, TestNetZoneAddr},
};
use monero_p2p::client::InternalPeerID; use monero_p2p::client::InternalPeerID;
mod utils; mod utils;
@ -104,12 +106,16 @@ async fn handshake_cuprate_to_cuprate() {
} }
#[tokio::test] #[tokio::test]
async fn handshake() { async fn handshake_cuprate_to_monerod() {
let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test. let (broadcast_tx, _) = broadcast::channel(1); // this isn't actually used in this test.
let semaphore = Arc::new(Semaphore::new(10)); let semaphore = Arc::new(Semaphore::new(10));
let permit = semaphore.acquire_owned().await.unwrap(); let permit = semaphore.acquire_owned().await.unwrap();
let addr = "127.0.0.1:18080"; let (monerod, _) = monerod(
vec!["--fixed-difficulty=1".into(), "--out-peers=0".into()],
false,
)
.await;
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 0, my_port: 0,
@ -135,7 +141,7 @@ async fn handshake() {
.await .await
.unwrap() .unwrap()
.call(ConnectRequest { .call(ConnectRequest {
addr: SocketAddr::from_str(addr).unwrap(), addr: monerod,
permit, permit,
}) })
.await .await

View file

@ -9,5 +9,17 @@ monero-p2p = {path = "../p2p/monero-p2p", features = ["borsh"] }
futures = { workspace = true, features = ["std"] } futures = { workspace = true, features = ["std"] }
async-trait = { workspace = true } async-trait = { workspace = true }
tokio = { workspace = true, features = ["full"] }
reqwest = { workspace = true }
bytes = { workspace = true, features = ["std"] }
borsh = { workspace = true, features = ["derive"]} borsh = { workspace = true, features = ["derive"]}
rand = { workspace = true, features = ["std", "std_rng"] }
[target.'cfg(unix)'.dependencies]
tar = "0.4.40"
bzip2 = "0.4.4"
[target.'cfg(windows)'.dependencies]
zip = "0.6"

7
test-utils/README.MD Normal file
View file

@ -0,0 +1,7 @@
# Test Utils
This crate contains code that can be shared across multiple Cuprate crates tests, this crate should not be included in any
Cuprate crate, only in tests.
It currently contains code to spawn monerod instances and a testing network zone.

View file

@ -1 +1,2 @@
pub mod monerod;
pub mod test_netzone; pub mod test_netzone;

172
test-utils/src/monerod.rs Normal file
View file

@ -0,0 +1,172 @@
//! Monerod Module
//!
//! This module contains a function [`monerod`] to start `monerod` - the core Monero node. Cuprate can then use
//! this to test compatibility with monerod.
//!
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
process::Stdio,
sync::OnceLock,
time::Duration,
};
use rand::Rng;
use tokio::{
process::{Child, Command},
sync::{mpsc, oneshot},
};
mod download;
const LOCAL_HOST: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
const MONEROD_VERSION: &str = "v0.18.3.1";
#[allow(clippy::type_complexity)]
static MONEROD_HANDLER_CHANNEL: OnceLock<
mpsc::Sender<(MoneroDRequest, oneshot::Sender<(SocketAddr, SocketAddr)>)>,
> = OnceLock::new();
/// Spawns monerod and returns the p2p address and rpc address.
///
/// When spawning monerod, this module will try to use an already spawned instance to reduce the amount
/// of instances that need to be spawned.
///
/// This function will set `regtest` and the P2P/ RPC ports so these can't be included in the flags.
pub async fn monerod(flags: Vec<String>, mutable: bool) -> (SocketAddr, SocketAddr) {
// TODO: sort flags so the same flags in a different order will give the same monerod?
// We only actually need these channels on first run so this might be wasteful
let (tx, rx) = mpsc::channel(3);
let mut should_spwan = false;
let monero_handler_tx = MONEROD_HANDLER_CHANNEL.get_or_init(|| {
should_spwan = true;
tx
});
if should_spwan {
// If this call was the first call to start a monerod instance then start the handler.
let manager = MoneroDManager::new().await;
tokio::task::spawn(manager.run(rx));
}
let (tx, rx) = oneshot::channel();
monero_handler_tx
.send((MoneroDRequest { mutable, flags }, tx))
.await
.unwrap();
// Give monerod some time to start
tokio::time::sleep(Duration::from_secs(5)).await;
rx.await.unwrap()
}
/// A request sent to get an address to a monerod instance.
struct MoneroDRequest {
/// Whether we plan to change the state of the spawned monerod's blockchain.
mutable: bool,
/// Start flags to start monerod with.
flags: Vec<String>,
}
/// A struct representing a spawned monerod.
struct SpwanedMoneroD {
/// A marker for if the test that spawned this monerod is going to mutate it.
mutable: bool,
/// A handle to the monerod process, monerod will be stopped when this is dropped.
#[allow(dead_code)]
process: Child,
/// The RPC port of the monerod instance.
rpc_port: u16,
/// The P2P port of the monerod instance.
p2p_port: u16,
}
/// A manger of spawned monerods.
struct MoneroDManager {
/// A map of start flags to monerods.
monerods: HashMap<Vec<String>, Vec<SpwanedMoneroD>>,
/// The path to the monerod binary.
path_to_monerod: PathBuf,
}
impl MoneroDManager {
pub async fn new() -> Self {
let path_to_monerod = download::check_download_monerod().await.unwrap();
Self {
monerods: Default::default(),
path_to_monerod,
}
}
pub async fn run(
mut self,
mut rx: mpsc::Receiver<(MoneroDRequest, oneshot::Sender<(SocketAddr, SocketAddr)>)>,
) {
while let Some((req, tx)) = rx.recv().await {
let (p2p_port, rpc_port) = self.get_monerod_with_flags(req.flags, req.mutable);
let _ = tx.send((
SocketAddr::new(LOCAL_HOST, p2p_port),
SocketAddr::new(LOCAL_HOST, rpc_port),
));
}
}
/// Trys to get a current monerod instance or spans one if there is not an appropriate one to use.
/// Returns the p2p port and then the RPC port of the spawned monerd.
fn get_monerod_with_flags(&mut self, flags: Vec<String>, mutable: bool) -> (u16, u16) {
// If we need to mutate monerod's blockchain then we can't reuse one.
if !mutable {
if let Some(monerods) = &self.monerods.get(&flags) {
for monerod in monerods.iter() {
if !monerod.mutable {
return (monerod.p2p_port, monerod.rpc_port);
}
}
}
}
let mut rng = rand::thread_rng();
// Use random ports and *hope* we don't get a collision (TODO: just keep a counter and increment?)
let rpc_port: u16 = rng.gen_range(1500..u16::MAX);
let p2p_port: u16 = rng.gen_range(1500..u16::MAX);
// TODO: set a different DB location per node
let monerod = Command::new(&self.path_to_monerod)
.stdout(Stdio::null())
.stdin(Stdio::piped())
.args(&flags)
.arg("--regtest")
.arg(format!("--p2p-bind-port={}", p2p_port))
.arg(format!("--rpc-bind-port={}", rpc_port))
.kill_on_drop(true)
.spawn()
.unwrap();
let spawned_monerd = SpwanedMoneroD {
mutable,
process: monerod,
rpc_port,
p2p_port,
};
self.monerods
.entry(flags.clone())
.or_default()
.push(spawned_monerd);
let Some(monerods) = self.monerods.get(&flags) else {
unreachable!()
};
for monerod in monerods {
if !monerod.mutable {
return (monerod.p2p_port, monerod.rpc_port);
}
}
unreachable!()
}
}

View file

@ -0,0 +1,97 @@
//! Downloading Monerod Module
//!
//! This module handles finding the right monerod file to download, downloading it and extracting it.
//!
use std::{
env::{
consts::{ARCH, OS},
current_dir,
},
fs::read_dir,
path::{Path, PathBuf},
};
#[cfg(unix)]
use bytes::Buf;
use reqwest::{get, Error as ReqError};
use super::MONEROD_VERSION;
/// Returns the file name to download and the expected extracted folder name.
fn file_name(version: &str) -> (String, String) {
let download_file = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => format!("monero-win-x64-{}.zip", version),
("windows", "x86") => format!("monero-win-x86-{}.zip", version),
("linux", "x64") | ("linux", "x86_64") => format!("monero-linux-x64-{}.tar.bz2", version),
("linux", "x86") => format!("monero-linux-x86-{}.tar.bz2", version),
("macos", "x64") | ("macos", "x86_64") => format!("monero-mac-x64-{}.tar.bz2", version),
_ => panic!("Can't get monerod for {OS}, {ARCH}."),
};
let extracted_dir = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => {
format!("monero-x86_64-w64-mingw32-{}", version)
}
("windows", "x86") => format!("monero-i686-w64-mingw32-{}", version),
("linux", "x64") | ("linux", "x86_64") => format!("monero-x86_64-linux-gnu-{}", version),
("linux", "x86") => format!("monero-i686-linux-gnu-{}", version),
("macos", "x64") | ("macos", "x86_64") => {
format!("monero-x86_64-apple-darwin11-{}", version)
}
_ => panic!("Can't get monerod for {OS}, {ARCH}."),
};
(download_file, extracted_dir)
}
/// Downloads the monerod file provided, extracts it and puts the extracted folder into `path_to_store`.
async fn download_monerod(file_name: &str, path_to_store: &Path) -> Result<(), ReqError> {
let res = get(format!("https://downloads.getmonero.org/cli/{}", file_name)).await?;
let monerod_archive = res.bytes().await.unwrap();
#[cfg(unix)]
{
let bzip_decomp = bzip2::read::BzDecoder::new(monerod_archive.reader());
let mut tar_archive = tar::Archive::new(bzip_decomp);
tar_archive.unpack(path_to_store).unwrap();
}
#[cfg(windows)]
{
let mut zip = zip::ZipArchive::new(std::io::Cursor::new(monerod_archive.as_ref())).unwrap();
zip.extract(path_to_store).unwrap();
}
Ok(())
}
/// Finds the `target` directory, this will work up from the current directory until
/// it finds a `target` directory.
fn find_target() -> PathBuf {
let mut current_dir = current_dir().unwrap();
loop {
let potential_target = current_dir.join("target");
if read_dir(current_dir.join("target")).is_ok() {
return potential_target;
} else if !current_dir.pop() {
panic!("Could not find ./target");
}
}
}
/// Checks if we have monerod or downloads it if we don't and then returns the path to it.
pub async fn check_download_monerod() -> Result<PathBuf, ReqError> {
let path_to_store = find_target();
let (file_name, dir_name) = file_name(MONEROD_VERSION);
let path_to_monerod = path_to_store.join(dir_name);
// Check if we already have monerod
if read_dir(&path_to_monerod).is_ok() {
return Ok(path_to_monerod.join("monerod"));
}
download_monerod(&file_name, &path_to_store).await?;
Ok(path_to_monerod.join("monerod"))
}

View file

@ -1,3 +1,8 @@
//! Test NetZone
//!
//! This module contains a test network zone, this network zone use channels as the network layer to simulate p2p
//! communication.
//!
use std::{ use std::{
fmt::Formatter, fmt::Formatter,
io::Error, io::Error,
@ -16,6 +21,7 @@ use monero_wire::{
use monero_p2p::{NetZoneAddress, NetworkZone}; use monero_p2p::{NetZoneAddress, NetworkZone};
/// An address on the test network
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize)] #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct TestNetZoneAddr(pub u32); pub struct TestNetZoneAddr(pub u32);
@ -56,6 +62,7 @@ impl TryFrom<NetworkAddress> for TestNetZoneAddr {
} }
} }
/// A wrapper around [`futures::channel::mpsc::Sender`] that changes the error to [`BucketError`].
pub struct Sender { pub struct Sender {
inner: InnerSender<Message>, inner: InnerSender<Message>,
} }