Pass pubkey for coinbase (#159)

* store wallet output data in hashmap
* cleanup up commented out code
* pass pubkey/identifier and not derivation around to miner etc.
* fix failing tests
This commit is contained in:
AntiochP 2017-10-07 13:38:41 -04:00 committed by Ignotus Peverell
parent c7f1ce965e
commit 3dd1dde00b
14 changed files with 249 additions and 69 deletions

View file

@ -80,7 +80,7 @@ fn mine_empty_chain() {
for n in 1..4 {
let prev = chain.head_header().unwrap();
let pk = keychain.derive_pubkey(n as u32).unwrap();
let mut b = core::core::Block::new(&prev, vec![], &keychain, pk).unwrap();
let mut b = core::core::Block::new(&prev, vec![], &keychain, &pk).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -251,7 +251,7 @@ fn prepare_block_nosum(prev: &BlockHeader, diff: u64) -> Block {
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let mut b = core::core::Block::new(prev, vec![], &keychain, pubkey).unwrap();
let mut b = core::core::Block::new(prev, vec![], &keychain, &pubkey).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
b.header.total_difficulty = Difficulty::from_num(diff);
b

View file

@ -39,7 +39,7 @@ fn test_various_store_indices() {
let chain_store = &chain::store::ChainKVStore::new(".grin".to_string()).unwrap() as &ChainStore;
let block = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap();
let block = Block::new(&BlockHeader::default(), vec![], &keychain, &pubkey).unwrap();
let commit = block.outputs[0].commitment();
let block_hash = block.hash();

View file

@ -76,7 +76,7 @@ fn test_coinbase_maturity() {
let pk3 = keychain.derive_pubkey(3).unwrap();
let pk4 = keychain.derive_pubkey(4).unwrap();
let mut block = core::core::Block::new(&prev, vec![], &keychain, pk1.clone()).unwrap();
let mut block = core::core::Block::new(&prev, vec![], &keychain, &pk1).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -109,8 +109,7 @@ fn test_coinbase_maturity() {
&keychain,
).unwrap();
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, pk3.clone())
.unwrap();
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, &pk3).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -138,7 +137,7 @@ fn test_coinbase_maturity() {
let keychain = Keychain::from_random_seed().unwrap();
let pk = keychain.derive_pubkey(1).unwrap();
let mut block = core::core::Block::new(&prev, vec![], &keychain, pk).unwrap();
let mut block = core::core::Block::new(&prev, vec![], &keychain, &pk).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -157,7 +156,7 @@ fn test_coinbase_maturity() {
let prev = chain.head_header().unwrap();
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, pk4).unwrap();
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, &pk4).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);

View file

@ -266,7 +266,7 @@ impl Block {
prev: &BlockHeader,
txs: Vec<&Transaction>,
keychain: &keychain::Keychain,
pubkey: keychain::Identifier,
pubkey: &keychain::Identifier,
) -> Result<Block, keychain::Error> {
let fees = txs.iter().map(|tx| tx.fee).sum();
@ -469,11 +469,18 @@ impl Block {
// * That the sum of blinding factors for all coinbase-marked outputs match
// the coinbase-marked kernels.
fn verify_coinbase(&self, secp: &Secp256k1) -> Result<(), Error> {
let cb_outs = filter_map_vec!(self.outputs, |out| {
if out.features.contains(COINBASE_OUTPUT) { Some(out.commitment()) } else { None }
let cb_outs = filter_map_vec!(self.outputs, |out| if out.features.contains(
COINBASE_OUTPUT,
)
{
Some(out.commitment())
} else {
None
});
let cb_kerns = filter_map_vec!(self.kernels, |k| {
if k.features.contains(COINBASE_KERNEL) { Some(k.excess) } else { None }
let cb_kerns = filter_map_vec!(self.kernels, |k| if k.features.contains(COINBASE_KERNEL) {
Some(k.excess)
} else {
None
});
let over_commit = secp.commit_value(reward(self.total_fees()))?;
@ -490,16 +497,15 @@ impl Block {
/// reward.
pub fn reward_output(
keychain: &keychain::Keychain,
pubkey: keychain::Identifier,
pubkey: &keychain::Identifier,
fees: u64,
) -> Result<(Output, TxKernel), keychain::Error> {
let secp = keychain.secp();
let commit = keychain.commit(reward(fees), &pubkey)?;
let commit = keychain.commit(reward(fees), pubkey)?;
// let switch_commit = keychain.switch_commit(pubkey)?;
let msg = secp::pedersen::ProofMessage::empty();
let rproof = keychain.range_proof(reward(fees), &pubkey, commit, msg)?;
let rproof = keychain.range_proof(reward(fees), pubkey, commit, msg)?;
let output = Output {
features: COINBASE_OUTPUT,
@ -540,7 +546,7 @@ mod test {
// header
fn new_block(txs: Vec<&Transaction>, keychain: &Keychain) -> Block {
let pubkey = keychain.derive_pubkey(1).unwrap();
Block::new(&BlockHeader::default(), txs, keychain, pubkey).unwrap()
Block::new(&BlockHeader::default(), txs, keychain, &pubkey).unwrap()
}
// utility producing a transaction that spends an output with the provided

View file

@ -329,7 +329,7 @@ mod test {
let keychain = new_keychain();
let pubkey = keychain.derive_pubkey(1).unwrap();
let b = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap();
let b = Block::new(&BlockHeader::default(), vec![], &keychain, &pubkey).unwrap();
b.compact().validate(&keychain.secp()).unwrap();
}
@ -345,7 +345,7 @@ mod test {
let mut tx1 = tx2i1o();
tx1.verify_sig(keychain.secp()).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1], &keychain, pubkey).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1], &keychain, &pubkey).unwrap();
b.compact().validate(keychain.secp()).unwrap();
}
@ -360,7 +360,7 @@ mod test {
let mut tx2 = tx1i1o();
tx2.verify_sig(keychain.secp()).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], &keychain, pubkey).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], &keychain, &pubkey).unwrap();
b.validate(keychain.secp()).unwrap();
}

View file

@ -22,6 +22,7 @@
use std::{error, fmt, cmp};
use std::io::{self, Write, Read};
use byteorder::{ByteOrder, ReadBytesExt, BigEndian};
use keychain::Identifier;
use secp::pedersen::Commitment;
use secp::pedersen::RangeProof;
use secp::constants::PEDERSEN_COMMITMENT_SIZE;
@ -285,6 +286,19 @@ impl Writeable for Commitment {
}
}
impl Writeable for Identifier {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
writer.write_fixed_bytes(self)
}
}
impl Readable for Identifier {
fn read(reader: &mut Reader) -> Result<Identifier, Error> {
let bytes = reader.read_fixed_bytes(20)?;
Ok(Identifier::from_bytes(&bytes))
}
}
impl Writeable for RangeProof {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
writer.write_fixed_bytes(self)
@ -511,3 +525,8 @@ impl AsFixedBytes for ::secp::pedersen::Commitment {
return PEDERSEN_COMMITMENT_SIZE;
}
}
impl AsFixedBytes for ::keychain::Identifier {
fn len(&self) -> usize {
return 20;
}
}

View file

@ -42,7 +42,7 @@ use chain;
use secp;
use pool;
use util;
use keychain::Keychain;
use keychain::{Identifier, Keychain};
use wallet::{BlockFees, WalletReceiveRequest, CbData};
use pow::plugin::PluginMiner;
@ -431,13 +431,15 @@ impl Miner {
// to prevent the wallet from generating a new HD key derivation for each
// iteration, we keep the returned derivation to provide it back when
// nothing has changed
let mut derivation = 0;
let mut pubkey = None;
loop {
debug!("in miner loop...");
// get the latest chain state and build a block on top of it
let head = self.chain.head_header().unwrap();
let mut latest_hash = self.chain.head().unwrap().last_block_h;
let (mut b, deriv) = self.build_block(&head, derivation);
let (mut b, block_fees) = self.build_block(&head, pubkey);
let mut sol = None;
let mut use_async = false;
@ -500,16 +502,22 @@ impl Miner {
e
);
}
derivation = 0;
debug!("resetting pubkey in miner to None");
pubkey = None;
} else {
derivation = deriv;
debug!("setting pubkey in miner to pubkey from block_fees - {:?}", block_fees);
pubkey = block_fees.pubkey();
}
}
}
/// Builds a new block with the chain head as previous and eligible
/// transactions from the pool.
fn build_block(&self, head: &core::BlockHeader, deriv: u32) -> (core::Block, u32) {
fn build_block(
&self,
head: &core::BlockHeader,
pubkey: Option<Identifier>,
) -> (core::Block, BlockFees) {
// prepare the block header timestamp
let mut now_sec = time::get_time().sec;
let head_sec = head.timestamp.to_timespec().sec;
@ -529,7 +537,12 @@ impl Miner {
// build the coinbase and the block itself
let fees = txs.iter().map(|tx| tx.fee).sum();
let (output, kernel, deriv) = self.get_coinbase(fees, deriv);
let block_fees = BlockFees {
fees: fees,
pubkey: pubkey,
};
let (output, kernel, block_fees) = self.get_coinbase(block_fees);
let mut b = core::Block::with_reward(head, txs, output, kernel).unwrap();
debug!(
"(Server ID: {}) Built new block with {} inputs and {} outputs, difficulty: {}",
@ -550,21 +563,28 @@ impl Miner {
self.chain.set_sumtree_roots(&mut b).expect(
"Error setting sum tree roots",
);
(b, deriv)
(b, block_fees)
}
fn get_coinbase(&self, fees: u64, derivation: u32) -> (core::Output, core::TxKernel, u32) {
fn get_coinbase(
&self,
block_fees: BlockFees,
) -> (core::Output, core::TxKernel, BlockFees) {
if self.config.burn_reward {
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let (out, kern) = core::Block::reward_output(&keychain, pubkey, fees).unwrap();
(out, kern, 0)
let (out, kern) = core::Block::reward_output(
&keychain,
&pubkey,
block_fees.fees
).unwrap();
(out, kern, block_fees)
} else {
let url = format!(
"{}/v1/receive/coinbase",
self.config.wallet_receiver_url.as_str()
);
let request = WalletReceiveRequest::Coinbase(BlockFees { fees: fees, derivation: derivation });
let request = WalletReceiveRequest::Coinbase(block_fees.clone());
let res: CbData = api::client::post(url.as_str(), &request).expect(
format!(
"(Server ID: {}) Wallet receiver unreachable, could not claim reward. Is it running?",
@ -574,10 +594,18 @@ impl Miner {
);
let out_bin = util::from_hex(res.output).unwrap();
let kern_bin = util::from_hex(res.kernel).unwrap();
let pubkey_bin = util::from_hex(res.pubkey).unwrap();
let output = ser::deserialize(&mut &out_bin[..]).unwrap();
let kernel = ser::deserialize(&mut &kern_bin[..]).unwrap();
let pubkey = ser::deserialize(&mut &pubkey_bin[..]).unwrap();
let block_fees = BlockFees {
pubkey: Some(pubkey),
.. block_fees
};
(output, kernel, res.derivation)
debug!("block_fees here: {:?}", block_fees);
(output, kernel, block_fees)
}
}
}

View file

@ -4,10 +4,11 @@ version = "0.1.0"
authors = ["Antioch Peverell"]
[dependencies]
byteorder = "1"
byteorder = "~1"
blake2-rfc = "~0.2.17"
rand = "~0.3"
serde = "~1.0.8"
serde_derive = "~1.0.8"
serde_json = "~1.0.3"
grin_util = { path = "../util" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

View file

@ -15,6 +15,8 @@
use std::{error, fmt};
use std::cmp::min;
use serde::{de, ser};
use byteorder::{ByteOrder, BigEndian};
use blake2::blake2b::blake2b;
use secp::Secp256k1;
@ -75,25 +77,83 @@ impl fmt::Display for Fingerprint {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Identifier(String);
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Identifier([u8; 20]);
impl ser::Serialize for Identifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> de::Deserialize<'de> for Identifier {
fn deserialize<D>(deserializer: D) -> Result<Identifier, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_u64(IdentifierVisitor)
}
}
struct IdentifierVisitor;
impl<'de> de::Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an identifier")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
// TODO - error handling here
let identifier = Identifier::from_hex(s).unwrap();
Ok(identifier)
}
}
impl Identifier {
fn from_bytes(bytes: &[u8]) -> Identifier {
pub fn from_bytes(bytes: &[u8]) -> Identifier {
let mut identifier = [0; 20];
for i in 0..min(20, bytes.len()) {
identifier[i] = bytes[i];
}
Identifier(util::to_hex(identifier.to_vec()))
Identifier(identifier)
}
fn from_hex(hex: &str) -> Result<Identifier, Error> {
// TODO - error handling, don't unwrap here
let bytes = util::from_hex(hex.to_string()).unwrap();
Ok(Identifier::from_bytes(&bytes))
}
pub fn to_hex(&self) -> String {
self.0.clone()
util::to_hex(self.0.to_vec())
}
pub fn fingerprint(&self) -> Fingerprint {
let hex = &self.0[0..8];
Fingerprint(String::from(hex))
Fingerprint::from_bytes(&self.0)
}
}
impl AsRef<[u8]> for Identifier {
fn as_ref(&self) -> &[u8] {
&self.0.as_ref()
}
}
impl ::std::fmt::Debug for Identifier {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
try!(write!(f, "{}(", stringify!(Identifier)));
for i in self.0.iter().cloned() {
try!(write!(f, "{:02x}", i));
}
write!(f, ")")
}
}
@ -208,6 +268,8 @@ impl ExtendedKey {
#[cfg(test)]
mod test {
use serde_json;
use secp::Secp256k1;
use secp::key::SecretKey;
use super::{ExtendedKey, Fingerprint, Identifier};
@ -217,6 +279,29 @@ mod test {
util::from_hex(hex_str.to_string()).unwrap()
}
#[test]
fn test_identifier_json_ser_deser() {
let hex = "942b6c0bd43bdcb24f3edfe7fadbc77054ecc4f2";
let identifier = Identifier::from_hex(hex).unwrap();
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct HasAnIdentifier {
identifier: Identifier,
}
let has_an_identifier = HasAnIdentifier { identifier };
let json = serde_json::to_string(&has_an_identifier).unwrap();
assert_eq!(
json,
"{\"identifier\":\"942b6c0bd43bdcb24f3edfe7fadbc77054ecc4f2\"}"
);
let deserialized: HasAnIdentifier = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, has_an_identifier);
}
#[test]
fn extkey_from_seed() {
// TODO More test vectors

View file

@ -51,7 +51,7 @@ pub struct Keychain {
}
impl Keychain {
pub fn fingerprint(self) -> Fingerprint {
pub fn fingerprint(&self) -> Fingerprint {
self.extkey.fingerprint.clone()
}
@ -87,7 +87,19 @@ impl Keychain {
return Ok(extkey.key);
}
}
Err(Error::KeyDerivation("cannot find one...".to_string()))
Err(Error::KeyDerivation(format!("cannot find extkey for {}", pubkey.fingerprint())))
}
// TODO - clean this and derived_key up, rename them?
// TODO - maybe wallet deals exclusively with pubkeys and not derivations - this leaks?
pub fn derivation_from_pubkey(&self, pubkey: &Identifier) -> Result<u32, Error> {
for i in 1..10000 {
let extkey = self.extkey.derive(&self.secp, i)?;
if extkey.identifier() == *pubkey {
return Ok(extkey.n_child);
}
}
Err(Error::KeyDerivation(format!("cannot find extkey for {}", pubkey.fingerprint())))
}
pub fn commit(&self, amount: u64, pubkey: &Identifier) -> Result<Commitment, Error> {

View file

@ -22,6 +22,7 @@ extern crate grin_util as util;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
mod blind;
mod extkey;

View file

@ -908,7 +908,7 @@ mod tests {
&block::BlockHeader::default(),
block_transactions,
&keychain,
pubkey,
&pubkey,
).unwrap();
chain_ref.apply_block(&block);
@ -1007,7 +1007,7 @@ mod tests {
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
block = block::Block::new(&block::BlockHeader::default(), tx_refs, &keychain, pubkey)
block = block::Block::new(&block::BlockHeader::default(), tx_refs, &keychain, &pubkey)
.unwrap();
}

View file

@ -108,27 +108,35 @@ impl ApiEndpoint for WalletReceiver {
match input {
WalletReceiveRequest::Coinbase(cb_fees) => {
debug!("Operation {} with fees {:?}", op, cb_fees);
let (out, kern, derivation) =
let (out, kern, block_fees) =
receive_coinbase(
&self.config,
&self.keychain,
cb_fees.fees,
cb_fees.derivation,
&cb_fees,
).map_err(|e| {
api::Error::Internal(format!("Error building coinbase: {:?}", e))
})?;
let out_bin =
ser::ser_vec(&out).map_err(|e| {
api::Error::Internal(format!("Error serializing output: {:?}", e))
})?;
api::Error::Internal(format!("Error serializing output: {:?}", e))
})?;
let kern_bin =
ser::ser_vec(&kern).map_err(|e| {
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
})?;
let pubkey_bin = match block_fees.pubkey {
Some(pubkey) => {
ser::ser_vec(&pubkey).map_err(|e| {
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
})?;
})?
},
None => vec![],
};
Ok(CbData {
output: util::to_hex(out_bin),
kernel: util::to_hex(kern_bin),
derivation: derivation,
pubkey: util::to_hex(pubkey_bin),
})
}
_ => Err(api::Error::Argument(
@ -148,7 +156,7 @@ impl ApiEndpoint for WalletReceiver {
Ok(CbData {
output: String::from(""),
kernel: String::from(""),
derivation: 0,
pubkey: String::from(""),
})
}
_ => Err(api::Error::Argument(
@ -165,33 +173,48 @@ impl ApiEndpoint for WalletReceiver {
fn receive_coinbase(
config: &WalletConfig,
keychain: &Keychain,
fees: u64,
mut derivation: u32,
) -> Result<(Output, TxKernel, u32), Error> {
let fingerprint = keychain.clone().fingerprint();
block_fees: &BlockFees,
) -> Result<(Output, TxKernel, BlockFees), Error> {
let fingerprint = keychain.fingerprint();
// operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
if derivation == 0 {
derivation = wallet_data.next_child(fingerprint.clone());
}
let pubkey = keychain.derive_pubkey(derivation)?;
let pubkey = block_fees.pubkey();
let (pubkey, derivation) = match pubkey {
Some(pubkey) => {
let derivation = keychain.derivation_from_pubkey(&pubkey)?;
(pubkey.clone(), derivation)
},
None => {
let derivation = wallet_data.next_child(fingerprint.clone());
let pubkey = keychain.derive_pubkey(derivation)?;
(pubkey, derivation)
}
};
// track the new output and return the stuff needed for reward
wallet_data.add_output(OutputData {
fingerprint: fingerprint.clone(),
identifier: pubkey.clone(),
n_child: derivation,
value: reward(fees),
value: reward(block_fees.fees),
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
});
debug!("Received coinbase and built output - {}, {}, {}",
debug!("Received coinbase and built candidate output - {}, {}, {}",
fingerprint.clone(), pubkey.fingerprint(), derivation);
let (out, kern) = Block::reward_output(&keychain, pubkey, fees)?;
Ok((out, kern, derivation))
debug!("block_fees - {:?}", block_fees);
let mut block_fees = block_fees.clone();
block_fees.pubkey = Some(pubkey.clone());
debug!("block_fees updated - {:?}", block_fees);
let (out, kern) = Block::reward_output(&keychain, &pubkey, block_fees.fees)?;
Ok((out, kern, block_fees))
})?
}

View file

@ -264,7 +264,7 @@ impl WalletData {
Error::WalletData(format!("Could not create {}: {}", data_file_path, e))
})?;
let res_json = serde_json::to_vec_pretty(self).map_err(|e| {
Error::WalletData(format!("Error serializing wallet data."))
Error::WalletData(format!("Error serializing wallet data: {}", e))
})?;
data_file.write_all(res_json.as_slice()).map_err(|e| {
Error::WalletData(format!("Error writing {}: {}", data_file_path, e))
@ -385,7 +385,13 @@ pub enum WalletReceiveRequest {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlockFees {
pub fees: u64,
pub derivation: u32,
pub pubkey: Option<keychain::Identifier>,
}
impl BlockFees {
pub fn pubkey(&self) -> Option<keychain::Identifier> {
self.pubkey.clone()
}
}
/// Response to build a coinbase output.
@ -393,5 +399,5 @@ pub struct BlockFees {
pub struct CbData {
pub output: String,
pub kernel: String,
pub derivation: u32,
pub pubkey: String,
}