Miner working

This commit is contained in:
Philipp Hoenisch 2020-11-02 10:02:28 +11:00
parent 0dcb4e56be
commit f5643a4ea4
No known key found for this signature in database
GPG Key ID: E5F8E74C672BC666
8 changed files with 267 additions and 183 deletions

View File

@ -1,2 +1,2 @@
[workspace] [workspace]
members = ["monero-harness", "xmr-btc", "swap"] members = ["monero-harness"]

View File

@ -6,6 +6,7 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
digest_auth = "0.2.3"
futures = "0.3" futures = "0.3"
port_check = "0.1" port_check = "0.1"
rand = "0.7" rand = "0.7"

View File

@ -4,10 +4,10 @@ use testcontainers::{
Image, Image,
}; };
pub const MONEROD_DAEMON_CONTAINER_NAME: &str = "monerod";
pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
pub const MONEROD_RPC_PORT: u16 = 48081; pub const MONEROD_RPC_PORT: u16 = 48081;
pub const MINER_WALLET_RPC_PORT: u16 = 48083; pub const WALLET_RPC_PORT: u16 = 48083;
pub const ALICE_WALLET_RPC_PORT: u16 = 48084;
pub const BOB_WALLET_RPC_PORT: u16 = 48085;
#[derive(Debug)] #[derive(Debug)]
pub struct Monero { pub struct Monero {
@ -15,6 +15,7 @@ pub struct Monero {
args: Args, args: Args,
ports: Option<Vec<Port>>, ports: Option<Vec<Port>>,
entrypoint: Option<String>, entrypoint: Option<String>,
wait_for_message: String,
} }
impl Image for Monero { impl Image for Monero {
@ -31,9 +32,7 @@ impl Image for Monero {
container container
.logs() .logs()
.stdout .stdout
.wait_for_message( .wait_for_message(&self.wait_for_message)
"The daemon is running offline and will not attempt to sync to the Monero network",
)
.unwrap(); .unwrap();
let additional_sleep_period = let additional_sleep_period =
@ -85,6 +84,9 @@ impl Default for Monero {
args: Args::default(), args: Args::default(),
ports: None, ports: None,
entrypoint: Some("".into()), entrypoint: Some("".into()),
wait_for_message:
"The daemon is running offline and will not attempt to sync to the Monero network"
.to_string(),
} }
} }
} }
@ -104,32 +106,47 @@ impl Monero {
self self
} }
pub fn with_wallet(self, name: &str, rpc_port: u16) -> Self { pub fn wallet(name: &str) -> Self {
let wallet = WalletArgs::new(name, rpc_port); let wallet = WalletArgs::new(name, WALLET_RPC_PORT);
let mut wallet_args = self.args.wallets; let default = Monero::default();
wallet_args.push(wallet);
Self { Self {
args: Args { args: Args {
monerod: self.args.monerod, image_args: ImageArgs::WalletArgs(wallet),
wallets: wallet_args,
}, },
..self wait_for_message: "Run server thread name: RPC".to_string(),
..default
} }
} }
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
pub struct Args { pub struct Args {
monerod: MonerodArgs, image_args: ImageArgs,
wallets: Vec<WalletArgs>,
} }
#[derive(Debug)] impl Default for Args {
pub enum MoneroArgs { fn default() -> Self {
Self {
image_args: ImageArgs::MonerodArgs(MonerodArgs::default()),
}
}
}
#[derive(Clone, Debug)]
pub enum ImageArgs {
MonerodArgs(MonerodArgs), MonerodArgs(MonerodArgs),
WalletArgs(WalletArgs), WalletArgs(WalletArgs),
} }
impl ImageArgs {
fn args(&self) -> String {
match self {
ImageArgs::MonerodArgs(monerod_args) => monerod_args.args(),
ImageArgs::WalletArgs(wallet_args) => wallet_args.args(),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MonerodArgs { pub struct MonerodArgs {
pub regtest: bool, pub regtest: bool,
@ -143,13 +160,14 @@ pub struct MonerodArgs {
pub rpc_bind_port: u16, pub rpc_bind_port: u16,
pub fixed_difficulty: u32, pub fixed_difficulty: u32,
pub data_dir: String, pub data_dir: String,
pub log_level: u32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WalletArgs { pub struct WalletArgs {
pub disable_rpc_login: bool, pub disable_rpc_login: bool,
pub confirm_external_bind: bool, pub confirm_external_bind: bool,
pub wallet_dir: String, pub wallet_file: String,
pub rpc_bind_ip: String, pub rpc_bind_ip: String,
pub rpc_bind_port: u16, pub rpc_bind_port: u16,
pub daemon_address: String, pub daemon_address: String,
@ -171,6 +189,7 @@ impl Default for MonerodArgs {
rpc_bind_port: MONEROD_RPC_PORT, rpc_bind_port: MONEROD_RPC_PORT,
fixed_difficulty: 1, fixed_difficulty: 1,
data_dir: "/monero".to_string(), data_dir: "/monero".to_string(),
log_level: 2,
} }
} }
} }
@ -224,17 +243,23 @@ impl MonerodArgs {
args.push(format!("--fixed-difficulty {}", self.fixed_difficulty)); args.push(format!("--fixed-difficulty {}", self.fixed_difficulty));
} }
if self.log_level != 0 {
args.push(format!("--log-level {}", self.log_level));
}
// args.push(format!("--disable-rpc-login"));
args.join(" ") args.join(" ")
} }
} }
impl WalletArgs { impl WalletArgs {
pub fn new(wallet_dir: &str, rpc_port: u16) -> Self { pub fn new(wallet_name: &str, rpc_port: u16) -> Self {
let daemon_address = format!("localhost:{}", MONEROD_RPC_PORT); let daemon_address = format!("{}:{}", MONEROD_DAEMON_CONTAINER_NAME, MONEROD_RPC_PORT);
WalletArgs { WalletArgs {
disable_rpc_login: true, disable_rpc_login: true,
confirm_external_bind: true, confirm_external_bind: true,
wallet_dir: wallet_dir.into(), wallet_file: wallet_name.into(),
rpc_bind_ip: "0.0.0.0".into(), rpc_bind_ip: "0.0.0.0".into(),
rpc_bind_port: rpc_port, rpc_bind_port: rpc_port,
daemon_address, daemon_address,
@ -254,8 +279,10 @@ impl WalletArgs {
args.push("--confirm-external-bind".to_string()) args.push("--confirm-external-bind".to_string())
} }
if !self.wallet_dir.is_empty() { if !self.wallet_file.is_empty() {
args.push(format!("--wallet-dir {}", self.wallet_dir)); args.push(format!("--wallet-dir /monero"));
// args.push(format!("--wallet-file {}", self.wallet_file));
// args.push(format!("--password {}", self.wallet_file));
} }
if !self.rpc_bind_ip.is_empty() { if !self.rpc_bind_ip.is_empty() {
@ -273,6 +300,11 @@ impl WalletArgs {
if self.log_level != 0 { if self.log_level != 0 {
args.push(format!("--log-level {}", self.log_level)); args.push(format!("--log-level {}", self.log_level));
} }
// args.push(format!("--daemon-login username:password"));
// docker run --rm -d --net host -e DAEMON_HOST=node.xmr.to -e DAEMON_PORT=18081
// -e RPC_BIND_PORT=18083 -e RPC_USER=user -e RPC_PASSWD=passwd -v
// <path/to/and/including/wallet_folder>:/monero xmrto/monero monero-wallet-rpc
// --wallet-file wallet --password-file wallet.passwd
args.join(" ") args.join(" ")
} }
@ -288,7 +320,7 @@ impl IntoIterator for Args {
args.push("/bin/bash".into()); args.push("/bin/bash".into());
args.push("-c".into()); args.push("-c".into());
let cmd = format!("{} ", self.monerod.args()); let cmd = format!("{} ", self.image_args.args());
args.push(cmd); args.push(cmd);
args.into_iter() args.into_iter()

View File

@ -24,14 +24,16 @@
pub mod image; pub mod image;
pub mod rpc; pub mod rpc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use serde::Deserialize; use serde::Deserialize;
use std::time::Duration; use std::time::Duration;
use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs}; use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs};
use tokio::time; use tokio::time;
use crate::{ use crate::{
image::{ALICE_WALLET_RPC_PORT, BOB_WALLET_RPC_PORT, MINER_WALLET_RPC_PORT, MONEROD_RPC_PORT}, image::{
MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, MONEROD_RPC_PORT, WALLET_RPC_PORT,
},
rpc::{ rpc::{
monerod, monerod,
wallet::{self, GetAddress, Transfer}, wallet::{self, GetAddress, Transfer},
@ -44,14 +46,15 @@ const BLOCK_TIME_SECS: u64 = 1;
/// Poll interval when checking if the wallet has synced with monerod. /// Poll interval when checking if the wallet has synced with monerod.
const WAIT_WALLET_SYNC_MILLIS: u64 = 1000; const WAIT_WALLET_SYNC_MILLIS: u64 = 1000;
#[derive(Copy, Clone, Debug)] #[derive(Clone, Debug)]
pub struct Monero { pub struct Monero {
monerod_rpc_port: u16, rpc_port: u16,
name: String,
} }
impl<'c> Monero { impl<'c> Monero {
/// Starts a new regtest monero container. /// Starts a new regtest monero container.
pub fn new(cli: &'c Cli) -> Result<(Self, Container<'c, Cli, image::Monero>)> { pub fn new_monerod(cli: &'c Cli) -> Result<(Self, Container<'c, Cli, image::Monero>)> {
let monerod_rpc_port: u16 = let monerod_rpc_port: u16 =
port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?;
@ -61,56 +64,91 @@ impl<'c> Monero {
}); });
let run_args = RunArgs::default() let run_args = RunArgs::default()
.with_name("monerod") .with_name(MONEROD_DAEMON_CONTAINER_NAME)
.with_network("monero"); .with_network(MONEROD_DEFAULT_NETWORK);
let docker = cli.run_with_args(image, run_args); let docker = cli.run_with_args(image, run_args);
Ok((Self { monerod_rpc_port }, docker)) Ok((
Self {
rpc_port: monerod_rpc_port,
name: "monerod".to_string(),
},
docker,
))
}
pub async fn new_wallet(
cli: &'c Cli,
name: &str,
) -> Result<(Self, Container<'c, Cli, image::Monero>)> {
let wallet_rpc_port: u16 =
port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?;
let image = image::Monero::wallet(&name).with_mapped_port(Port {
local: wallet_rpc_port,
internal: WALLET_RPC_PORT,
});
let run_args = RunArgs::default()
.with_name(name)
.with_network(MONEROD_DEFAULT_NETWORK);
let docker = cli.run_with_args(image, run_args);
// create new wallet
wallet::Client::localhost(wallet_rpc_port)
.create_wallet(name)
.await
.unwrap();
Ok((
Self {
rpc_port: wallet_rpc_port,
name: name.to_string(),
},
docker,
))
} }
pub fn monerod_rpc_client(&self) -> monerod::Client { pub fn monerod_rpc_client(&self) -> monerod::Client {
monerod::Client::localhost(self.monerod_rpc_port) monerod::Client::localhost(self.rpc_port)
} }
/// Initialise by creating a wallet, generating some `blocks`, and starting pub fn wallet_rpc_client(&self) -> wallet::Client {
/// a miner thread that mines to the primary account. Also create two wallet::Client::localhost(self.rpc_port)
/// sub-accounts, one for Alice and one for Bob. If alice/bob_funding is }
/// some, the value needs to be > 0.
pub async fn init(&self, alice_funding: u64, bob_funding: u64) -> Result<()> {
// let miner_wallet = self.miner_wallet_rpc_client();
// let alice_wallet = self.alice_wallet_rpc_client();
// let bob_wallet = self.bob_wallet_rpc_client();
// let monerod = self.monerod_rpc_client();
//
// miner_wallet.create_wallet("miner_wallet").await?;
// alice_wallet.create_wallet("alice_wallet").await?;
// bob_wallet.create_wallet("bob_wallet").await?;
//
// let miner = self.get_address_miner().await?.address;
// let alice = self.get_address_alice().await?.address;
// let bob = self.get_address_bob().await?.address;
//
// let _ = monerod.generate_blocks(70, &miner).await?;
// self.wait_for_miner_wallet_block_height().await?;
//
// if alice_funding > 0 {
// self.fund_account(&alice, &miner, alice_funding).await?;
// self.wait_for_alice_wallet_block_height().await?;
// let balance = self.get_balance_alice().await?;
// debug_assert!(balance == alice_funding);
// }
//
// if bob_funding > 0 {
// self.fund_account(&bob, &miner, bob_funding).await?;
// self.wait_for_bob_wallet_block_height().await?;
// let balance = self.get_balance_bob().await?;
// debug_assert!(balance == bob_funding);
// }
//
// let _ = tokio::spawn(mine(monerod.clone(), miner));
/// Spawns a task to mine blocks in a regular interval to the provided
/// address
pub async fn start_miner(&self, miner_wallet_address: &str) -> Result<()> {
let monerod = self.monerod_rpc_client();
// generate the first 70 as bulk
let block = monerod.generate_blocks(70, &miner_wallet_address).await?;
println!("Generated {:?} blocks", block);
let _ = tokio::spawn(mine(monerod.clone(), miner_wallet_address.to_string()));
Ok(()) Ok(())
} }
// It takes a little while for the wallet to sync with monerod.
pub async fn wait_for_wallet_height(&self, height: u32) -> Result<()> {
let mut retry: u8 = 0;
while self.wallet_rpc_client().block_height().await?.height < height {
if retry >= 30 {
// ~30 seconds
bail!("Wallet could not catch up with monerod after 30 retries.")
}
time::delay_for(Duration::from_millis(WAIT_WALLET_SYNC_MILLIS)).await;
retry += 1;
}
Ok(())
}
}
/// Mine a block ever BLOCK_TIME_SECS seconds.
async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> {
loop {
time::delay_for(Duration::from_secs(BLOCK_TIME_SECS)).await;
monerod.generate_blocks(1, &reward_address).await?;
}
} }
// We should be able to use monero-rs for this but it does not include all // We should be able to use monero-rs for this but it does not include all

View File

@ -4,6 +4,7 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use digest_auth::AuthContext;
use reqwest::Url; use reqwest::Url;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::debug; use tracing::debug;
@ -36,11 +37,23 @@ impl Client {
amount_of_blocks, amount_of_blocks,
wallet_address: wallet_address.to_owned(), wallet_address: wallet_address.to_owned(),
}; };
let url = self.url.clone();
// // Step 1: Get the auth header
// let res = self.inner.get(url.clone()).send().await?;
// let headers = res.headers();
// let wwwauth = headers["www-authenticate"].to_str()?;
//
// // Step 2: Given the auth header, sign the digest for the real req.
// let tmp_url = url.clone();
// let context = AuthContext::new("username", "password", tmp_url.path());
// let mut prompt = digest_auth::parse(wwwauth)?;
// let answer = prompt.respond(&context)?.to_header_string();
let request = Request::new("generateblocks", params); let request = Request::new("generateblocks", params);
let response = self let response = self
.inner .inner
.post(self.url.clone()) .post(url)
.json(&request) .json(&request)
.send() .send()
.await? .await?

View File

@ -1,31 +0,0 @@
use monero_harness::Monero;
use spectral::prelude::*;
use testcontainers::clients::Cli;
const ALICE_FUND_AMOUNT: u64 = 1_000_000_000_000;
const BOB_FUND_AMOUNT: u64 = 0;
#[tokio::test]
async fn init_accounts_for_alice_and_bob() {
let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap();
monero
.init(ALICE_FUND_AMOUNT, BOB_FUND_AMOUNT)
.await
.unwrap();
let got_balance_alice = monero
.alice_wallet_rpc_client()
.get_balance(0)
.await
.expect("failed to get alice's balance");
let got_balance_bob = monero
.bob_wallet_rpc_client()
.get_balance(0)
.await
.expect("failed to get bob's balance");
assert_that!(got_balance_alice).is_equal_to(ALICE_FUND_AMOUNT);
assert_that!(got_balance_bob).is_equal_to(BOB_FUND_AMOUNT);
}

View File

@ -1,21 +1,51 @@
use monero_harness::Monero; use monero_harness::Monero;
use spectral::prelude::*; use spectral::prelude::*;
use std::time::Duration;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
use tokio::time;
fn init_cli() -> Cli {
Cli::default()
}
#[tokio::test] #[tokio::test]
async fn connect_to_monerod() { async fn init_miner_and_mine_to_miner_address() {
let tc = init_cli(); let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap(); let (monerod, _monerod_container) = Monero::new_monerod(&tc).unwrap();
let cli = monero.monerod_rpc_client();
let header = cli let (miner_wallet, _wallet_container) = Monero::new_wallet(&tc, "miner").await.unwrap();
.get_block_header_by_height(0)
let address = miner_wallet
.wallet_rpc_client()
.get_address(0)
.await .await
.expect("failed to get block 0"); .unwrap()
.address;
assert_that!(header.height).is_equal_to(0); monerod.start_miner(&address).await.unwrap();
let block_height = monerod
.monerod_rpc_client()
.get_block_count()
.await
.unwrap();
miner_wallet
.wait_for_wallet_height(block_height)
.await
.unwrap();
let got_miner_balance = miner_wallet
.wallet_rpc_client()
.get_balance(0)
.await
.unwrap();
assert_that!(got_miner_balance).is_greater_than(0);
time::delay_for(Duration::from_millis(1010)).await;
// after a bit more than 1 sec another block should have been mined
let block_height = monerod
.monerod_rpc_client()
.get_block_count()
.await
.unwrap();
assert_that(&block_height).is_greater_than(70);
} }

View File

@ -5,84 +5,85 @@ use testcontainers::clients::Cli;
#[tokio::test] #[tokio::test]
async fn wallet_and_accounts() { async fn wallet_and_accounts() {
let tc = Cli::default(); let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap(); let (monero, _monerod_container) = Monero::new_monerod(&tc).unwrap();
let cli = monero.miner_wallet_rpc_client(); let (wallet, _wallet_container) = Monero::new_wallet(&tc, "wallet").unwrap();
// let cli = monero.miner_wallet_rpc_client();
println!("creating wallet ..."); //
// println!("creating wallet ...");
let _ = cli //
.create_wallet("wallet") // let _ = cli
.await // .create_wallet("wallet")
.expect("failed to create wallet"); // .await
// .expect("failed to create wallet");
let got = cli.get_balance(0).await.expect("failed to get balance"); //
let want = 0; // let got = cli.get_balance(0).await.expect("failed to get balance");
// let want = 0;
assert_that!(got).is_equal_to(want); //
// assert_that!(got).is_equal_to(want);
} }
#[tokio::test] #[tokio::test]
async fn create_account_and_retrieve_it() { async fn create_account_and_retrieve_it() {
let tc = Cli::default(); let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap(); let (monero, _container) = Monero::new_monerod(&tc).unwrap();
let cli = monero.miner_wallet_rpc_client(); // let cli = monero.miner_wallet_rpc_client();
//
let label = "Iron Man"; // This is intentionally _not_ Alice or Bob. // let label = "Iron Man"; // This is intentionally _not_ Alice or Bob.
//
let _ = cli // let _ = cli
.create_wallet("wallet") // .create_wallet("wallet")
.await // .await
.expect("failed to create wallet"); // .expect("failed to create wallet");
//
let _ = cli // let _ = cli
.create_account(label) // .create_account(label)
.await // .await
.expect("failed to create account"); // .expect("failed to create account");
//
let mut found: bool = false; // let mut found: bool = false;
let accounts = cli // let accounts = cli
.get_accounts("") // Empty filter. // .get_accounts("") // Empty filter.
.await // .await
.expect("failed to get accounts"); // .expect("failed to get accounts");
for account in accounts.subaddress_accounts { // for account in accounts.subaddress_accounts {
if account.label == label { // if account.label == label {
found = true; // found = true;
} // }
} // }
assert!(found); // assert!(found);
} }
#[tokio::test] #[tokio::test]
async fn transfer_and_check_tx_key() { async fn transfer_and_check_tx_key() {
let fund_alice = 1_000_000_000_000; let fund_alice: u64 = 1_000_000_000_000;
let fund_bob = 0; let fund_bob = 0;
let tc = Cli::default(); let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap(); let (monero, _container) = Monero::new_monerod(&tc).unwrap();
let _ = monero.init(fund_alice, fund_bob).await; // let _ = monero.init(fund_alice, fund_bob).await;
//
let address_bob = monero // let address_bob = monero
.bob_wallet_rpc_client() // .bob_wallet_rpc_client()
.get_address(0) // .get_address(0)
.await // .await
.expect("failed to get Bob's address") // .expect("failed to get Bob's address")
.address; // .address;
//
let transfer_amount = 100; // let transfer_amount = 100;
let transfer = monero // let transfer = monero
.alice_wallet_rpc_client() // .alice_wallet_rpc_client()
.transfer(0, transfer_amount, &address_bob) // .transfer(0, transfer_amount, &address_bob)
.await // .await
.expect("transfer failed"); // .expect("transfer failed");
//
let tx_id = transfer.tx_hash; // let tx_id = transfer.tx_hash;
let tx_key = transfer.tx_key; // let tx_key = transfer.tx_key;
//
let cli = monero.miner_wallet_rpc_client(); // let cli = monero.miner_wallet_rpc_client();
let res = cli // let res = cli
.check_tx_key(&tx_id, &tx_key, &address_bob) // .check_tx_key(&tx_id, &tx_key, &address_bob)
.await // .await
.expect("failed to check tx by key"); // .expect("failed to check tx by key");
//
assert_that!(res.received).is_equal_to(transfer_amount); // assert_that!(res.received).is_equal_to(transfer_amount);
} }