Miner querying wallet receiver for coinbase output

With the coinbase receiver daemon in place, when starting a Grin
server in mining mode, the miner will now ask the receiver for a
coinbase output. The output is then used to insert in a block when
successfully mined.
This commit is contained in:
Ignotus Peverell 2017-05-25 17:22:21 -07:00
parent f45cfe97f2
commit 791d2355ee
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
8 changed files with 114 additions and 29 deletions

View file

@ -228,6 +228,7 @@ impl Default for Block {
}
impl Block {
/// Builds a new block from the header of the previous block, a vector of
/// transactions and the private key that will receive the reward. Checks
/// that all transactions are valid and calculates the Merkle tree.
@ -239,12 +240,24 @@ impl Block {
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit);
let (reward_out, reward_proof) = try!(Block::reward_output(reward_key, &secp));
Block::with_reward(prev, txs, reward_out, reward_proof)
}
/// Builds a new block ready to mine from the header of the previous block,
/// a vector of transactions and the reward information. Checks
/// that all transactions are valid and calculates the Merkle tree.
pub fn with_reward(prev: &BlockHeader,
txs: Vec<&mut Transaction>,
reward_out: Output,
reward_kern: TxKernel)
-> Result<Block, secp::Error> {
// note: the following reads easily but may not be the most efficient due to
// repeated iterations, revisit if a problem
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit);
// validate each transaction and gather their kernels
let mut kernels = try_map_vec!(txs, |tx| tx.verify_sig(&secp));
kernels.push(reward_proof);
kernels.push(reward_kern);
// build vectors with all inputs and all outputs, ordering them by hash
// needs to be a fold so we don't end up with a vector of vectors and we
@ -282,7 +295,8 @@ impl Block {
kernels: kernels,
}
.compact())
}
}
/// Blockhash, computed using only the header
pub fn hash(&self) -> Hash {

View file

@ -20,14 +20,19 @@ use std::sync::{Arc, Mutex};
use time;
use adapters::ChainToNetAdapter;
use api;
use core::consensus;
use core::core;
use core::core::hash::{Hash, Hashed};
use core::pow::cuckoo;
use core::ser;
use chain;
use secp;
use types::{MinerConfig, Error};
use util;
pub struct Miner {
config: MinerConfig,
chain_head: Arc<Mutex<chain::Tip>>,
chain_store: Arc<chain::ChainStore>,
/// chain adapter to net
@ -37,11 +42,13 @@ pub struct Miner {
impl Miner {
/// Creates a new Miner. Needs references to the chain state and its
/// storage.
pub fn new(chain_head: Arc<Mutex<chain::Tip>>,
pub fn new(config: MinerConfig,
chain_head: Arc<Mutex<chain::Tip>>,
chain_store: Arc<chain::ChainStore>,
chain_adapter: Arc<ChainToNetAdapter>)
-> Miner {
Miner {
config: config,
chain_head: chain_head,
chain_store: chain_store,
chain_adapter: chain_adapter,
@ -52,6 +59,7 @@ impl Miner {
/// chain anytime required and looking for PoW solution.
pub fn run_loop(&self) {
info!("Starting miner loop.");
let mut coinbase = self.get_coinbase();
loop {
// get the latest chain state and build a block on top of it
let head: core::BlockHeader;
@ -60,7 +68,7 @@ impl Miner {
head = self.chain_store.head_header().unwrap();
latest_hash = self.chain_head.lock().unwrap().last_block_h;
}
let mut b = self.build_block(&head);
let mut b = self.build_block(&head, coinbase.clone());
// look for a pow for at most 2 sec on the same block (to give a chance to new
// transactions) and as long as the head hasn't changed
@ -101,6 +109,7 @@ impl Miner {
} else if let Ok(Some(tip)) = res {
let chain_head = self.chain_head.clone();
let mut head = chain_head.lock().unwrap();
coinbase = self.get_coinbase();
*head = tip;
}
} else {
@ -112,7 +121,7 @@ impl Miner {
/// Builds a new block with the chain head as previous and eligible
/// transactions from the pool.
fn build_block(&self, head: &core::BlockHeader) -> core::Block {
fn build_block(&self, head: &core::BlockHeader, coinbase: (core::Output, core::TxKernel)) -> core::Block {
let mut now_sec = time::get_time().sec;
let head_sec = head.timestamp.to_timespec().sec;
if now_sec == head_sec {
@ -121,17 +130,45 @@ impl Miner {
let (difficulty, cuckoo_len) =
consensus::next_target(now_sec, head_sec, head.difficulty.clone(), head.cuckoo_len);
let mut rng = rand::OsRng::new().unwrap();
let secp_inst = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
// TODO get a new key from the user's wallet or something
let skey = secp::key::SecretKey::new(&secp_inst, &mut rng);
// TODO populate inputs and outputs from pool transactions
let mut b = core::Block::new(head, vec![], skey).unwrap();
let (output, kernel) = coinbase;
let mut b = core::Block::with_reward(head, vec![], output, kernel).unwrap();
let mut rng = rand::OsRng::new().unwrap();
b.header.nonce = rng.gen();
b.header.cuckoo_len = cuckoo_len;
b.header.difficulty = difficulty;
b.header.timestamp = time::at(time::Timespec::new(now_sec, 0));
b
}
fn get_coinbase(&self) -> (core::Output, core::TxKernel) {
if self.config.burn_reward {
let mut rng = rand::OsRng::new().unwrap();
let secp_inst = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
let skey = secp::key::SecretKey::new(&secp_inst, &mut rng);
core::Block::reward_output(skey, &secp_inst).unwrap()
} else {
let url = format!("{}/v1/receive_coinbase", self.config.wallet_receiver_url.as_str());
let res: CbData = api::client::post(url.as_str(), &CbAmount { amount: consensus::REWARD })
.expect("Wallet receiver unreachable, could not claim reward. Is it running?");
let out_bin = util::from_hex(res.output).unwrap();
let kern_bin = util::from_hex(res.kernel).unwrap();
let output = ser::deserialize(&mut &out_bin[..]).unwrap();
let kernel = ser::deserialize(&mut &kern_bin[..]).unwrap();
(output, kernel)
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct CbAmount {
amount: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct CbData {
output: String,
kernel: String,
}

View file

@ -57,10 +57,10 @@ impl Server {
/// Instantiates and starts a new server.
pub fn start(config: ServerConfig) -> Result<Server, Error> {
let mut evtlp = reactor::Core::new().unwrap();
let enable_mining = config.enable_mining;
let mining_config = config.mining_config.clone();
let serv = Server::future(config, &evtlp.handle())?;
if enable_mining {
serv.start_miner();
if mining_config.enable_mining {
serv.start_miner(mining_config);
}
let forever = Timer::default()
@ -133,8 +133,9 @@ impl Server {
/// Start mining for blocks on a separate thread. Relies on a toy miner,
/// mostly for testing.
pub fn start_miner(&self) {
let miner = miner::Miner::new(self.chain_head.clone(),
pub fn start_miner(&self, config: MinerConfig) {
let miner = miner::Miner::new(config,
self.chain_head.clone(),
self.chain_store.clone(),
self.chain_adapter.clone());
thread::spawn(move || {

View file

@ -14,6 +14,7 @@
use std::convert::From;
use api;
use chain;
use p2p;
use store;
@ -27,6 +28,8 @@ pub enum Error {
Chain(chain::Error),
/// Error originating from the peer-to-peer network.
P2P(p2p::Error),
/// Error originating from HTTP API calls
API(api::Error),
}
impl From<chain::Error> for Error {
@ -47,6 +50,12 @@ impl From<store::Error> for Error {
}
}
impl From<api::Error> for Error {
fn from(e: api::Error) -> Error {
Error::API(e)
}
}
/// Type of seeding the server will use to find other peers on the network.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Seeding {
@ -81,8 +90,22 @@ pub struct ServerConfig {
/// Configuration for the peer-to-peer server
pub p2p_config: p2p::P2PConfig,
/// Whethere to start the miner with the server
/// Configuration for the mining daemon
pub mining_config: MinerConfig,
}
/// Mining configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MinerConfig {
/// Whether to start the miner with the server
pub enable_mining: bool,
/// Base address to the HTTP wallet receiver
pub wallet_receiver_url: String,
/// Attributes the reward to a random private key instead of contacting the
/// wallet receiver. Mostly used for tests.
pub burn_reward: bool,
}
impl Default for ServerConfig {
@ -94,7 +117,17 @@ impl Default for ServerConfig {
capabilities: p2p::FULL_NODE,
seeding_type: Seeding::None,
p2p_config: p2p::P2PConfig::default(),
enable_mining: false,
mining_config: MinerConfig::default(),
}
}
}
impl Default for MinerConfig {
fn default() -> MinerConfig {
MinerConfig {
enable_mining: false,
wallet_receiver_url: "http://localhost:13416".to_string(),
burn_reward: false,
}
}
}

View file

@ -141,7 +141,7 @@ fn server_command(server_args: &ArgMatches) {
server_config.p2p_config.port = port.parse().unwrap();
}
if server_args.is_present("mine") {
server_config.enable_mining = true;
server_config.mining_config.enable_mining = true;
}
if let Some(seeds) = server_args.values_of("seed") {
server_config.seeding_type = grin::Seeding::List(seeds.map(|s| s.to_string()).collect());
@ -218,7 +218,6 @@ fn default_config() -> grin::ServerConfig {
grin::ServerConfig {
cuckoo_size: 12,
seeding_type: grin::Seeding::WebStatic,
enable_mining: false,
..Default::default()
}
}

View file

@ -24,7 +24,7 @@ use std::num;
pub fn to_hex(bytes: Vec<u8>) -> String {
let mut s = String::new();
for byte in bytes {
write!(&mut s, "{:X}", byte).expect("Unable to write");
write!(&mut s, "{:02X}", byte).expect("Unable to write");
}
s
}
@ -36,7 +36,7 @@ pub fn from_hex(hex_str: String) -> Result<Vec<u8>, num::ParseIntError> {
} else {
hex_str.clone()
};
split_n(&hex_str.trim()[..], 2).iter()
split_n(&hex_trim.trim()[..], 2).iter()
.map(|b| u8::from_str_radix(b, 16))
.collect::<Result<Vec<u8>, _>>()
}

View file

@ -55,7 +55,7 @@ use secp::key::SecretKey;
use core::core::{Block, Transaction, TxKernel, Output, build};
use core::ser;
use api::*;
use api::{self, ApiEndpoint, Operation, ApiResult};
use extkey::{self, ExtendedKey};
use types::*;
use util;
@ -93,22 +93,22 @@ impl ApiEndpoint for WalletReceiver {
fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> {
debug!("Operation {} with amount {}", op, input.amount);
if input.amount == 0 {
return Err(ApiError::Argument(format!("Zero amount not allowed.")))
return Err(api::Error::Argument(format!("Zero amount not allowed.")))
}
match op.as_str() {
"receive_coinbase" => {
let (out, kern) = receive_coinbase(&self.key, input.amount).
map_err(|e| ApiError::Internal(format!("Error building coinbase: {:?}", e)))?;
map_err(|e| api::Error::Internal(format!("Error building coinbase: {:?}", e)))?;
let out_bin = ser::ser_vec(&out).
map_err(|e| ApiError::Internal(format!("Error serializing output: {:?}", e)))?;
map_err(|e| api::Error::Internal(format!("Error serializing output: {:?}", e)))?;
let kern_bin = ser::ser_vec(&kern).
map_err(|e| ApiError::Internal(format!("Error serializing kernel: {:?}", e)))?;
map_err(|e| api::Error::Internal(format!("Error serializing kernel: {:?}", e)))?;
Ok(CbData {
output: util::to_hex(out_bin),
kernel: util::to_hex(kern_bin),
})
},
_ => Err(ApiError::Argument(format!("Unknown operation: {}", op))),
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))),
}
}
}
@ -131,5 +131,7 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer
});
wallet_data.write()?;
info!("Using child {} for a new coinbase output.", coinbase_key.n_child);
Block::reward_output(ext_key.key, &secp).map_err(&From::from)
}

View file

@ -152,7 +152,6 @@ impl WalletData {
pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 {
let mut max_n = 0;
for out in &self.outputs {
println!("{:?} {:?}", out.fingerprint, fingerprint);
if max_n < out.n_child && out.fingerprint == fingerprint {
max_n = out.n_child;
}