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]
members = ["monero-harness", "xmr-btc", "swap"]
members = ["monero-harness"]

View File

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

View File

@ -4,10 +4,10 @@ use testcontainers::{
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 MINER_WALLET_RPC_PORT: u16 = 48083;
pub const ALICE_WALLET_RPC_PORT: u16 = 48084;
pub const BOB_WALLET_RPC_PORT: u16 = 48085;
pub const WALLET_RPC_PORT: u16 = 48083;
#[derive(Debug)]
pub struct Monero {
@ -15,6 +15,7 @@ pub struct Monero {
args: Args,
ports: Option<Vec<Port>>,
entrypoint: Option<String>,
wait_for_message: String,
}
impl Image for Monero {
@ -31,9 +32,7 @@ impl Image for Monero {
container
.logs()
.stdout
.wait_for_message(
"The daemon is running offline and will not attempt to sync to the Monero network",
)
.wait_for_message(&self.wait_for_message)
.unwrap();
let additional_sleep_period =
@ -85,6 +84,9 @@ impl Default for Monero {
args: Args::default(),
ports: None,
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
}
pub fn with_wallet(self, name: &str, rpc_port: u16) -> Self {
let wallet = WalletArgs::new(name, rpc_port);
let mut wallet_args = self.args.wallets;
wallet_args.push(wallet);
pub fn wallet(name: &str) -> Self {
let wallet = WalletArgs::new(name, WALLET_RPC_PORT);
let default = Monero::default();
Self {
args: Args {
monerod: self.args.monerod,
wallets: wallet_args,
image_args: ImageArgs::WalletArgs(wallet),
},
..self
wait_for_message: "Run server thread name: RPC".to_string(),
..default
}
}
}
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct Args {
monerod: MonerodArgs,
wallets: Vec<WalletArgs>,
image_args: ImageArgs,
}
#[derive(Debug)]
pub enum MoneroArgs {
impl Default for Args {
fn default() -> Self {
Self {
image_args: ImageArgs::MonerodArgs(MonerodArgs::default()),
}
}
}
#[derive(Clone, Debug)]
pub enum ImageArgs {
MonerodArgs(MonerodArgs),
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)]
pub struct MonerodArgs {
pub regtest: bool,
@ -143,13 +160,14 @@ pub struct MonerodArgs {
pub rpc_bind_port: u16,
pub fixed_difficulty: u32,
pub data_dir: String,
pub log_level: u32,
}
#[derive(Debug, Clone)]
pub struct WalletArgs {
pub disable_rpc_login: bool,
pub confirm_external_bind: bool,
pub wallet_dir: String,
pub wallet_file: String,
pub rpc_bind_ip: String,
pub rpc_bind_port: u16,
pub daemon_address: String,
@ -171,6 +189,7 @@ impl Default for MonerodArgs {
rpc_bind_port: MONEROD_RPC_PORT,
fixed_difficulty: 1,
data_dir: "/monero".to_string(),
log_level: 2,
}
}
}
@ -224,17 +243,23 @@ impl MonerodArgs {
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(" ")
}
}
impl WalletArgs {
pub fn new(wallet_dir: &str, rpc_port: u16) -> Self {
let daemon_address = format!("localhost:{}", MONEROD_RPC_PORT);
pub fn new(wallet_name: &str, rpc_port: u16) -> Self {
let daemon_address = format!("{}:{}", MONEROD_DAEMON_CONTAINER_NAME, MONEROD_RPC_PORT);
WalletArgs {
disable_rpc_login: 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_port: rpc_port,
daemon_address,
@ -254,8 +279,10 @@ impl WalletArgs {
args.push("--confirm-external-bind".to_string())
}
if !self.wallet_dir.is_empty() {
args.push(format!("--wallet-dir {}", self.wallet_dir));
if !self.wallet_file.is_empty() {
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() {
@ -273,6 +300,11 @@ impl WalletArgs {
if self.log_level != 0 {
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(" ")
}
@ -288,7 +320,7 @@ impl IntoIterator for Args {
args.push("/bin/bash".into());
args.push("-c".into());
let cmd = format!("{} ", self.monerod.args());
let cmd = format!("{} ", self.image_args.args());
args.push(cmd);
args.into_iter()

View File

@ -24,14 +24,16 @@
pub mod image;
pub mod rpc;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use serde::Deserialize;
use std::time::Duration;
use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs};
use tokio::time;
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::{
monerod,
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.
const WAIT_WALLET_SYNC_MILLIS: u64 = 1000;
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct Monero {
monerod_rpc_port: u16,
rpc_port: u16,
name: String,
}
impl<'c> Monero {
/// 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 =
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()
.with_name("monerod")
.with_network("monero");
.with_name(MONEROD_DAEMON_CONTAINER_NAME)
.with_network(MONEROD_DEFAULT_NETWORK);
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 {
monerod::Client::localhost(self.monerod_rpc_port)
monerod::Client::localhost(self.rpc_port)
}
/// Initialise by creating a wallet, generating some `blocks`, and starting
/// a miner thread that mines to the primary account. Also create two
/// 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));
pub fn wallet_rpc_client(&self) -> wallet::Client {
wallet::Client::localhost(self.rpc_port)
}
/// 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(())
}
// 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

View File

@ -4,6 +4,7 @@ use crate::{
};
use anyhow::Result;
use digest_auth::AuthContext;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use tracing::debug;
@ -36,11 +37,23 @@ impl Client {
amount_of_blocks,
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 response = self
.inner
.post(self.url.clone())
.post(url)
.json(&request)
.send()
.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 spectral::prelude::*;
use std::time::Duration;
use testcontainers::clients::Cli;
fn init_cli() -> Cli {
Cli::default()
}
use tokio::time;
#[tokio::test]
async fn connect_to_monerod() {
let tc = init_cli();
let (monero, _container) = Monero::new(&tc).unwrap();
let cli = monero.monerod_rpc_client();
async fn init_miner_and_mine_to_miner_address() {
let tc = Cli::default();
let (monerod, _monerod_container) = Monero::new_monerod(&tc).unwrap();
let header = cli
.get_block_header_by_height(0)
let (miner_wallet, _wallet_container) = Monero::new_wallet(&tc, "miner").await.unwrap();
let address = miner_wallet
.wallet_rpc_client()
.get_address(0)
.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]
async fn wallet_and_accounts() {
let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap();
let cli = monero.miner_wallet_rpc_client();
println!("creating wallet ...");
let _ = cli
.create_wallet("wallet")
.await
.expect("failed to create wallet");
let got = cli.get_balance(0).await.expect("failed to get balance");
let want = 0;
assert_that!(got).is_equal_to(want);
let (monero, _monerod_container) = Monero::new_monerod(&tc).unwrap();
let (wallet, _wallet_container) = Monero::new_wallet(&tc, "wallet").unwrap();
// let cli = monero.miner_wallet_rpc_client();
//
// println!("creating wallet ...");
//
// let _ = cli
// .create_wallet("wallet")
// .await
// .expect("failed to create wallet");
//
// let got = cli.get_balance(0).await.expect("failed to get balance");
// let want = 0;
//
// assert_that!(got).is_equal_to(want);
}
#[tokio::test]
async fn create_account_and_retrieve_it() {
let tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap();
let cli = monero.miner_wallet_rpc_client();
let label = "Iron Man"; // This is intentionally _not_ Alice or Bob.
let _ = cli
.create_wallet("wallet")
.await
.expect("failed to create wallet");
let _ = cli
.create_account(label)
.await
.expect("failed to create account");
let mut found: bool = false;
let accounts = cli
.get_accounts("") // Empty filter.
.await
.expect("failed to get accounts");
for account in accounts.subaddress_accounts {
if account.label == label {
found = true;
}
}
assert!(found);
let (monero, _container) = Monero::new_monerod(&tc).unwrap();
// let cli = monero.miner_wallet_rpc_client();
//
// let label = "Iron Man"; // This is intentionally _not_ Alice or Bob.
//
// let _ = cli
// .create_wallet("wallet")
// .await
// .expect("failed to create wallet");
//
// let _ = cli
// .create_account(label)
// .await
// .expect("failed to create account");
//
// let mut found: bool = false;
// let accounts = cli
// .get_accounts("") // Empty filter.
// .await
// .expect("failed to get accounts");
// for account in accounts.subaddress_accounts {
// if account.label == label {
// found = true;
// }
// }
// assert!(found);
}
#[tokio::test]
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 tc = Cli::default();
let (monero, _container) = Monero::new(&tc).unwrap();
let _ = monero.init(fund_alice, fund_bob).await;
let address_bob = monero
.bob_wallet_rpc_client()
.get_address(0)
.await
.expect("failed to get Bob's address")
.address;
let transfer_amount = 100;
let transfer = monero
.alice_wallet_rpc_client()
.transfer(0, transfer_amount, &address_bob)
.await
.expect("transfer failed");
let tx_id = transfer.tx_hash;
let tx_key = transfer.tx_key;
let cli = monero.miner_wallet_rpc_client();
let res = cli
.check_tx_key(&tx_id, &tx_key, &address_bob)
.await
.expect("failed to check tx by key");
assert_that!(res.received).is_equal_to(transfer_amount);
let (monero, _container) = Monero::new_monerod(&tc).unwrap();
// let _ = monero.init(fund_alice, fund_bob).await;
//
// let address_bob = monero
// .bob_wallet_rpc_client()
// .get_address(0)
// .await
// .expect("failed to get Bob's address")
// .address;
//
// let transfer_amount = 100;
// let transfer = monero
// .alice_wallet_rpc_client()
// .transfer(0, transfer_amount, &address_bob)
// .await
// .expect("transfer failed");
//
// let tx_id = transfer.tx_hash;
// let tx_key = transfer.tx_key;
//
// let cli = monero.miner_wallet_rpc_client();
// let res = cli
// .check_tx_key(&tx_id, &tx_key, &address_bob)
// .await
// .expect("failed to check tx by key");
//
// assert_that!(res.received).is_equal_to(transfer_amount);
}