Wallet Restore feature (#338)
* beginning to add wallet restore... api endpoints and basic restore * basic restore working, still missing features * rustfmt * large speed up to output search, should be more or less working * properly mark coinbase status
This commit is contained in:
+85
-17
@@ -25,6 +25,7 @@ use serde_json;
|
||||
|
||||
use chain;
|
||||
use core::core::Transaction;
|
||||
use core::core::hash::Hashed;
|
||||
use core::ser;
|
||||
use pool;
|
||||
use p2p;
|
||||
@@ -51,35 +52,39 @@ impl Handler for IndexHandler {
|
||||
}
|
||||
|
||||
// Supports retrieval of multiple outputs in a single request -
|
||||
// GET /v1/chain/utxos?id=xxx,yyy,zzz
|
||||
// GET /v1/chain/utxos?id=xxx&id=yyy&id=zzz
|
||||
// GET /v1/chain/utxos/byids?id=xxx,yyy,zzz
|
||||
// GET /v1/chain/utxos/byids?id=xxx&id=yyy&id=zzz
|
||||
// GET /v1/chain/utxos/byheight?height=n
|
||||
struct UtxoHandler {
|
||||
chain: Arc<chain::Chain>,
|
||||
}
|
||||
|
||||
impl UtxoHandler {
|
||||
fn get_utxo(&self, id: &str) -> Result<Output, Error> {
|
||||
fn get_utxo(&self, id: &str, include_rp: bool, include_switch: bool) -> Result<Output, Error> {
|
||||
debug!(LOGGER, "getting utxo: {}", id);
|
||||
let c = util::from_hex(String::from(id)).map_err(|_| {
|
||||
Error::Argument(format!("Not a valid commitment: {}", id))
|
||||
})?;
|
||||
let commit = Commitment::from_vec(c);
|
||||
|
||||
let out = self.chain
|
||||
.get_unspent(&commit)
|
||||
.map_err(|_| Error::NotFound)?;
|
||||
let out = self.chain.get_unspent(&commit).map_err(|_| Error::NotFound)?;
|
||||
|
||||
let header = self.chain
|
||||
.get_block_header_by_output_commit(&commit)
|
||||
.map_err(|_| Error::NotFound)?;
|
||||
|
||||
Ok(Output::from_output(&out, &header, false))
|
||||
Ok(Output::from_output(
|
||||
&out,
|
||||
&header,
|
||||
include_rp,
|
||||
include_switch,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for UtxoHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
fn utxos_by_ids(&self, req: &mut Request) -> Vec<Output> {
|
||||
let mut commitments: Vec<&str> = vec![];
|
||||
let mut rp = false;
|
||||
let mut switch = false;
|
||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
||||
if let Some(ids) = params.get("id") {
|
||||
for id in ids {
|
||||
@@ -88,15 +93,75 @@ impl Handler for UtxoHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(_) = params.get("include_rp") {
|
||||
rp = true;
|
||||
}
|
||||
if let Some(_) = params.get("include_switch") {
|
||||
switch = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut utxos: Vec<Output> = vec![];
|
||||
for commit in commitments {
|
||||
if let Ok(out) = self.get_utxo(commit) {
|
||||
if let Ok(out) = self.get_utxo(commit, rp, switch) {
|
||||
utxos.push(out);
|
||||
}
|
||||
}
|
||||
json_response(&utxos)
|
||||
utxos
|
||||
}
|
||||
|
||||
fn utxos_at_height(&self, block_height: u64) -> BlockOutputs {
|
||||
let header = self.chain
|
||||
.clone()
|
||||
.get_header_by_height(block_height)
|
||||
.unwrap();
|
||||
let block = self.chain.clone().get_block(&header.hash()).unwrap();
|
||||
let outputs = block
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|k| OutputSwitch::from_output(k, &header))
|
||||
.collect();
|
||||
BlockOutputs {
|
||||
header: BlockHeaderInfo::from_header(&header),
|
||||
outputs: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
// returns utxos for a specified range of blocks
|
||||
fn utxo_block_batch(&self, req: &mut Request) -> Vec<BlockOutputs> {
|
||||
let mut start_height = 1;
|
||||
let mut end_height = 1;
|
||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
||||
if let Some(heights) = params.get("start_height") {
|
||||
for height in heights {
|
||||
start_height = height.parse().unwrap();
|
||||
}
|
||||
}
|
||||
if let Some(heights) = params.get("end_height") {
|
||||
for height in heights {
|
||||
end_height = height.parse().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut return_vec = vec![];
|
||||
for i in start_height..end_height + 1 {
|
||||
return_vec.push(self.utxos_at_height(i));
|
||||
}
|
||||
return_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for UtxoHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let url = req.url.clone();
|
||||
let mut path_elems = url.path();
|
||||
if *path_elems.last().unwrap() == "" {
|
||||
path_elems.pop();
|
||||
}
|
||||
match *path_elems.last().unwrap() {
|
||||
"byids" => json_response(&self.utxos_by_ids(req)),
|
||||
"atheight" => json_response(&self.utxo_block_batch(req)),
|
||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,15 +307,18 @@ where
|
||||
T: pool::BlockChain + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let wrapper: TxWrapper = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
let wrapper: TxWrapper = serde_json::from_reader(req.body.by_ref()).map_err(|e| {
|
||||
IronError::new(e, status::BadRequest)
|
||||
})?;
|
||||
|
||||
let tx_bin = util::from_hex(wrapper.tx_hex).map_err(|_| {
|
||||
Error::Argument(format!("Invalid hex in transaction wrapper."))
|
||||
})?;
|
||||
|
||||
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
|
||||
Error::Argument("Could not deserialize transaction, invalid format.".to_string())
|
||||
Error::Argument(
|
||||
"Could not deserialize transaction, invalid format.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let source = pool::TxSource {
|
||||
@@ -349,7 +417,7 @@ pub fn start_rest_apis<T>(
|
||||
let router = router!(
|
||||
index: get "/" => index_handler,
|
||||
chain_tip: get "/chain" => chain_tip_handler,
|
||||
chain_utxos: get "/chain/utxos" => utxo_handler,
|
||||
chain_utxos: get "/chain/utxos/*" => utxo_handler,
|
||||
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
|
||||
pool_info: get "/pool" => pool_info_handler,
|
||||
pool_push: post "/pool/push" => pool_push_handler,
|
||||
|
||||
+68
-5
@@ -88,7 +88,7 @@ impl SumTreeNode {
|
||||
.get_block_header_by_output_commit(&elem_output.1.commit)
|
||||
.map_err(|_| Error::NotFound);
|
||||
// Need to call further method to check if output is spent
|
||||
let mut output = OutputPrintable::from_output(&elem_output.1, &header.unwrap());
|
||||
let mut output = OutputPrintable::from_output(&elem_output.1, &header.unwrap(),true);
|
||||
if let Ok(_) = chain.get_unspent(&elem_output.1.commit) {
|
||||
output.spent = false;
|
||||
}
|
||||
@@ -137,6 +137,8 @@ pub struct Output {
|
||||
pub output_type: OutputType,
|
||||
/// The homomorphic commitment representing the output's amount
|
||||
pub commit: pedersen::Commitment,
|
||||
/// switch commit hash
|
||||
pub switch_commit_hash: Option<core::SwitchCommitHash>,
|
||||
/// A proof that the commitment is in the right range
|
||||
pub proof: Option<pedersen::RangeProof>,
|
||||
/// The height of the block creating this output
|
||||
@@ -146,7 +148,8 @@ pub struct Output {
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader, include_proof:bool) -> Output {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader,
|
||||
include_proof:bool, include_switch: bool) -> Output {
|
||||
let (output_type, lock_height) = match output.features {
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
|
||||
OutputType::Coinbase,
|
||||
@@ -158,6 +161,10 @@ impl Output {
|
||||
Output {
|
||||
output_type: output_type,
|
||||
commit: output.commit,
|
||||
switch_commit_hash: match include_switch {
|
||||
true => Some(output.switch_commit_hash),
|
||||
false => None,
|
||||
},
|
||||
proof: match include_proof {
|
||||
true => Some(output.proof),
|
||||
false => None,
|
||||
@@ -176,6 +183,8 @@ pub struct OutputPrintable {
|
||||
/// The homomorphic commitment representing the output's amount (as hex
|
||||
/// string)
|
||||
pub commit: String,
|
||||
/// switch commit hash
|
||||
pub switch_commit_hash: String,
|
||||
/// The height of the block creating this output
|
||||
pub height: u64,
|
||||
/// The lock height (earliest block this output can be spent)
|
||||
@@ -183,11 +192,11 @@ pub struct OutputPrintable {
|
||||
/// Whether the output has been spent
|
||||
pub spent: bool,
|
||||
/// Rangeproof hash (as hex string)
|
||||
pub proof_hash: String,
|
||||
pub proof_hash: Option<String>,
|
||||
}
|
||||
|
||||
impl OutputPrintable {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> OutputPrintable {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader, include_proof_hash:bool) -> OutputPrintable {
|
||||
let (output_type, lock_height) = match output.features {
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
|
||||
OutputType::Coinbase,
|
||||
@@ -198,14 +207,68 @@ impl OutputPrintable {
|
||||
OutputPrintable {
|
||||
output_type: output_type,
|
||||
commit: util::to_hex(output.commit.0.to_vec()),
|
||||
switch_commit_hash: util::to_hex(output.switch_commit_hash.hash.to_vec()),
|
||||
height: block_header.height,
|
||||
lock_height: lock_height,
|
||||
spent: true,
|
||||
proof_hash: util::to_hex(output.proof.hash().to_vec()),
|
||||
proof_hash: match include_proof_hash {
|
||||
true => Some(util::to_hex(output.proof.hash().to_vec())),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As above, except just the info needed for wallet reconstruction
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct OutputSwitch {
|
||||
/// the commit
|
||||
pub commit: String,
|
||||
/// switch commit hash
|
||||
pub switch_commit_hash: [u8; core::SWITCH_COMMIT_HASH_SIZE],
|
||||
/// The height of the block creating this output
|
||||
pub height: u64,
|
||||
}
|
||||
|
||||
impl OutputSwitch {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> OutputSwitch {
|
||||
OutputSwitch {
|
||||
commit: util::to_hex(output.commit.0.to_vec()),
|
||||
switch_commit_hash: output.switch_commit_hash.hash,
|
||||
height: block_header.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Just the information required for wallet reconstruction
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct BlockHeaderInfo {
|
||||
/// Hash
|
||||
pub hash: String,
|
||||
/// Previous block hash
|
||||
pub previous: String,
|
||||
/// Height
|
||||
pub height: u64
|
||||
}
|
||||
|
||||
impl BlockHeaderInfo {
|
||||
pub fn from_header(block_header: &core::BlockHeader) -> BlockHeaderInfo{
|
||||
BlockHeaderInfo {
|
||||
hash: util::to_hex(block_header.hash().to_vec()),
|
||||
previous: util::to_hex(block_header.previous.to_vec()),
|
||||
height: block_header.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
// For wallet reconstruction, include the header info along with the
|
||||
// transactions in the block
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct BlockOutputs {
|
||||
/// The block header
|
||||
pub header: BlockHeaderInfo,
|
||||
/// A printable version of the outputs
|
||||
pub outputs: Vec<OutputSwitch>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PoolInfo {
|
||||
/// Size of the pool
|
||||
|
||||
Reference in New Issue
Block a user