rework WalletData and OutputData serialization so we serialize block hashes cleanly in wallet.dat (#625)

This commit is contained in:
AntiochP 2018-01-17 11:25:34 -05:00 committed by GitHub
parent cbd3b2ff87
commit bbdd4a91ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 26 deletions

View file

@ -74,7 +74,7 @@ impl error::Error for Error {
} }
} }
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Ord, Hash, PartialOrd)]
pub struct Identifier([u8; IDENTIFIER_SIZE]); pub struct Identifier([u8; IDENTIFIER_SIZE]);
impl ser::Serialize for Identifier { impl ser::Serialize for Identifier {

View file

@ -64,7 +64,7 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R
.values() .values()
.filter(|x| { .filter(|x| {
x.root_key_id == keychain.root_key_id() && x.root_key_id == keychain.root_key_id() &&
x.block_hash == Hash::zero() && x.block.hash() == Hash::zero() &&
x.status == OutputStatus::Unspent x.status == OutputStatus::Unspent
}) })
{ {
@ -139,7 +139,7 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) { if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
if let Some(b) = api_blocks.get(&commit) { if let Some(b) = api_blocks.get(&commit) {
let output = output.get_mut(); let output = output.get_mut();
output.block_hash = Hash::from_hex(&b.hash).unwrap(); output.block = BlockIdentifier::from_str(&b.hash).unwrap();
output.height = b.height; output.height = b.height;
} }
} }

View file

@ -25,7 +25,6 @@ use serde_json;
use api; use api;
use core::consensus::reward; use core::consensus::reward;
use core::core::{build, Block, Output, Transaction, TxKernel, amount_to_hr_string}; use core::core::{build, Block, Output, Transaction, TxKernel, amount_to_hr_string};
use core::core::hash::Hash;
use core::{global, ser}; use core::{global, ser};
use keychain::{Identifier, Keychain}; use keychain::{Identifier, Keychain};
use types::*; use types::*;
@ -97,7 +96,7 @@ fn handle_sender_initiation(
height: 0, height: 0,
lock_height: 0, lock_height: 0,
is_coinbase: false, is_coinbase: false,
block_hash: Hash::zero(), block: BlockIdentifier::zero(),
}); });
key_id key_id
@ -282,7 +281,7 @@ pub fn receive_coinbase(
height: height, height: height,
lock_height: lock_height, lock_height: lock_height,
is_coinbase: true, is_coinbase: true,
block_hash: Hash::zero(), block: BlockIdentifier::zero(),
}); });
(key_id, derivation) (key_id, derivation)
@ -363,7 +362,7 @@ fn build_final_transaction(
height: 0, height: 0,
lock_height: 0, lock_height: 0,
is_coinbase: false, is_coinbase: false,
block_hash: Hash::zero(), block: BlockIdentifier::zero(),
}); });
(key_id, derivation) (key_id, derivation)

View file

@ -18,9 +18,8 @@ use util::secp::pedersen;
use api; use api;
use core::global; use core::global;
use core::core::{Output, SwitchCommitHash}; use core::core::{Output, SwitchCommitHash};
use core::core::hash::Hash;
use core::core::transaction::{COINBASE_OUTPUT, DEFAULT_OUTPUT}; use core::core::transaction::{COINBASE_OUTPUT, DEFAULT_OUTPUT};
use types::{WalletConfig, WalletData, OutputData, OutputStatus, Error}; use types::{BlockIdentifier, WalletConfig, WalletData, OutputData, OutputStatus, Error};
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
@ -311,7 +310,7 @@ pub fn restore(
height: output.4, height: output.4,
lock_height: output.5, lock_height: output.5,
is_coinbase: output.6, is_coinbase: output.6,
block_hash: Hash::zero(), block: BlockIdentifier::zero(),
}); });
}; };
} }

View file

@ -16,7 +16,6 @@ use api;
use client; use client;
use checker; use checker;
use core::core::{build, Transaction, amount_to_hr_string}; use core::core::{build, Transaction, amount_to_hr_string};
use core::core::hash::Hash;
use core::ser; use core::ser;
use keychain::{BlindingFactor, Identifier, Keychain}; use keychain::{BlindingFactor, Identifier, Keychain};
use receiver::TxWrapper; use receiver::TxWrapper;
@ -267,9 +266,9 @@ fn inputs_and_change(
for coin in coins { for coin in coins {
let key_id = keychain.derive_key_id(coin.n_child)?; let key_id = keychain.derive_key_id(coin.n_child)?;
if coin.is_coinbase { if coin.is_coinbase {
parts.push(build::coinbase_input(coin.value, coin.block_hash, key_id)); parts.push(build::coinbase_input(coin.value, coin.block.hash(), key_id));
} else { } else {
parts.push(build::input(coin.value, coin.block_hash, key_id)); parts.push(build::input(coin.value, coin.block.hash(), key_id));
} }
} }
@ -288,7 +287,7 @@ fn inputs_and_change(
height: 0, height: 0,
lock_height: 0, lock_height: 0,
is_coinbase: false, is_coinbase: false,
block_hash: Hash::zero(), block: BlockIdentifier::zero(),
}); });
change_key change_key

View file

@ -24,6 +24,7 @@ use std::collections::HashMap;
use std::cmp::min; use std::cmp::min;
use hyper; use hyper;
use serde;
use serde_json; use serde_json;
use tokio_core::reactor; use tokio_core::reactor;
use tokio_retry::Retry; use tokio_retry::Retry;
@ -205,7 +206,7 @@ impl WalletConfig {
/// unconfirmed, spent, unspent, or locked (when it's been used to generate /// unconfirmed, spent, unspent, or locked (when it's been used to generate
/// a transaction but we don't have confirmation that the transaction was /// a transaction but we don't have confirmation that the transaction was
/// broadcasted or mined). /// broadcasted or mined).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum OutputStatus { pub enum OutputStatus {
Unconfirmed, Unconfirmed,
Unspent, Unspent,
@ -224,10 +225,64 @@ impl fmt::Display for OutputStatus {
} }
} }
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct BlockIdentifier(Hash);
impl BlockIdentifier {
pub fn hash(&self) -> Hash {
self.0
}
pub fn from_str(hex: &str) -> Result<BlockIdentifier, Error> {
let hash = Hash::from_hex(hex)?;
Ok(BlockIdentifier(hash))
}
pub fn zero() -> BlockIdentifier {
BlockIdentifier(Hash::zero())
}
}
impl serde::ser::Serialize for BlockIdentifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for BlockIdentifier {
fn deserialize<D>(deserializer: D) -> Result<BlockIdentifier, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(BlockIdentifierVisitor)
}
}
struct BlockIdentifierVisitor;
impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor {
type Value = BlockIdentifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a block hash")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let block_hash = Hash::from_hex(s).unwrap();
Ok(BlockIdentifier(block_hash))
}
}
/// Information about an output that's being tracked by the wallet. Must be /// Information about an output that's being tracked by the wallet. Must be
/// enough to reconstruct the commitment associated with the ouput when the /// enough to reconstruct the commitment associated with the ouput when the
/// root private key is known. /// root private key is known.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct OutputData { pub struct OutputData {
/// Root key_id that the key for this output is derived from /// Root key_id that the key for this output is derived from
pub root_key_id: keychain::Identifier, pub root_key_id: keychain::Identifier,
@ -246,7 +301,7 @@ pub struct OutputData {
/// Is this a coinbase output? Is it subject to coinbase locktime? /// Is this a coinbase output? Is it subject to coinbase locktime?
pub is_coinbase: bool, pub is_coinbase: bool,
/// Hash of the block this output originated from. /// Hash of the block this output originated from.
pub block_hash: Hash, pub block: BlockIdentifier,
} }
impl OutputData { impl OutputData {
@ -381,12 +436,6 @@ impl WalletSeed {
/// Wallet information tracking all our outputs. Based on HD derivation and /// Wallet information tracking all our outputs. Based on HD derivation and
/// avoids storing any key data, only storing output amounts and child index. /// avoids storing any key data, only storing output amounts and child index.
/// This data structure is directly based on the JSON representation stored
/// on disk, so selection algorithms are fairly primitive and non optimized.
///
/// TODO optimization so everything isn't O(n) or even O(n^2)
/// TODO account for fees
/// TODO write locks so files don't get overwritten
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletData { pub struct WalletData {
pub outputs: HashMap<String, OutputData>, pub outputs: HashMap<String, OutputData>,
@ -480,8 +529,8 @@ impl WalletData {
} }
} }
/// Read the wallet data from disk. /// Read output_data vec from disk.
fn read(data_file_path: &str) -> Result<WalletData, Error> { fn read_outputs(data_file_path: &str) -> Result<Vec<OutputData>, Error> {
let data_file = File::open(data_file_path).map_err(|e| { let data_file = File::open(data_file_path).map_err(|e| {
Error::WalletData(format!("Could not open {}: {}", data_file_path, e)) Error::WalletData(format!("Could not open {}: {}", data_file_path, e))
})?; })?;
@ -490,12 +539,26 @@ impl WalletData {
}) })
} }
/// Populate wallet_data with output_data from disk.
fn read(data_file_path: &str) -> Result<WalletData, Error> {
let outputs = WalletData::read_outputs(data_file_path)?;
let mut wallet_data = WalletData {
outputs: HashMap::new(),
};
for out in outputs {
wallet_data.add_output(out);
}
Ok(wallet_data)
}
/// Write the wallet data to disk. /// Write the wallet data to disk.
fn write(&self, data_file_path: &str) -> Result<(), Error> { fn write(&self, data_file_path: &str) -> Result<(), Error> {
let mut data_file = File::create(data_file_path).map_err(|e| { let mut data_file = File::create(data_file_path).map_err(|e| {
Error::WalletData(format!("Could not create {}: {}", data_file_path, e)) Error::WalletData(format!("Could not create {}: {}", data_file_path, e))
})?; })?;
let res_json = serde_json::to_vec_pretty(self).map_err(|e| { let mut outputs = self.outputs.values().collect::<Vec<_>>();
outputs.sort();
let res_json = serde_json::to_vec_pretty(&outputs).map_err(|e| {
Error::WalletData(format!("Error serializing wallet data: {}", e)) Error::WalletData(format!("Error serializing wallet data: {}", e))
})?; })?;
data_file.write_all(res_json.as_slice()).map_err(|e| { data_file.write_all(res_json.as_slice()).map_err(|e| {