2017-06-01 01:52:43 +03:00
|
|
|
// Copyright 2016 The Grin Developers
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! Utilities to check the status of all the outputs we have stored in
|
|
|
|
//! the wallet storage and update them.
|
|
|
|
|
2017-10-25 20:57:48 +03:00
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2017-06-01 01:52:43 +03:00
|
|
|
use api;
|
2017-09-22 19:44:12 +03:00
|
|
|
use types::*;
|
2017-10-25 20:57:48 +03:00
|
|
|
use keychain::{Identifier, Keychain};
|
2017-11-01 02:20:55 +03:00
|
|
|
use util::secp::pedersen;
|
2017-06-01 01:52:43 +03:00
|
|
|
use util;
|
2017-10-26 00:09:34 +03:00
|
|
|
use util::LOGGER;
|
2017-06-01 01:52:43 +03:00
|
|
|
|
2017-10-25 20:57:48 +03:00
|
|
|
// Transitions a local wallet output from Unconfirmed -> Unspent.
|
|
|
|
// Also updates the height and lock_height based on latest from the api.
|
|
|
|
fn refresh_output(out: &mut OutputData, api_out: &api::Output) {
|
|
|
|
out.height = api_out.height;
|
|
|
|
out.lock_height = api_out.lock_height;
|
2017-09-22 19:44:12 +03:00
|
|
|
|
2017-10-26 00:09:34 +03:00
|
|
|
match out.status {
|
|
|
|
OutputStatus::Unconfirmed => {
|
|
|
|
out.status = OutputStatus::Unspent;
|
2017-11-01 02:32:33 +03:00
|
|
|
}
|
2017-10-26 00:09:34 +03:00
|
|
|
_ => (),
|
2017-10-25 20:57:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
// Transitions a local wallet output (based on it not being in the node utxo
|
|
|
|
// set) -
|
2017-10-25 20:57:48 +03:00
|
|
|
// Unspent -> Spent
|
|
|
|
// Locked -> Spent
|
|
|
|
fn mark_spent_output(out: &mut OutputData) {
|
2017-10-26 00:09:34 +03:00
|
|
|
match out.status {
|
2017-11-01 02:32:33 +03:00
|
|
|
OutputStatus::Unspent | OutputStatus::Locked => out.status = OutputStatus::Spent,
|
2017-10-26 00:09:34 +03:00
|
|
|
_ => (),
|
2017-09-22 19:44:12 +03:00
|
|
|
}
|
|
|
|
}
|
2017-06-01 01:52:43 +03:00
|
|
|
|
2018-01-06 00:03:53 +03:00
|
|
|
/// Builds multiple api queries to retrieve the latest output data from the node.
|
2017-10-25 20:57:48 +03:00
|
|
|
/// So we can refresh the local wallet outputs.
|
2017-11-01 02:32:33 +03:00
|
|
|
pub fn refresh_outputs(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
2017-10-26 00:09:34 +03:00
|
|
|
debug!(LOGGER, "Refreshing wallet outputs");
|
|
|
|
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
|
|
|
let mut commits: Vec<pedersen::Commitment> = vec![];
|
2017-10-25 20:57:48 +03:00
|
|
|
|
2017-10-26 00:09:34 +03:00
|
|
|
// build a local map of wallet outputs by commits
|
2017-11-10 18:12:15 +03:00
|
|
|
// and a list of outputs we want to query the node for
|
2017-10-26 00:09:34 +03:00
|
|
|
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
2017-11-01 02:32:33 +03:00
|
|
|
for out in wallet_data
|
|
|
|
.outputs
|
2017-10-26 00:09:34 +03:00
|
|
|
.values()
|
2017-10-25 20:57:48 +03:00
|
|
|
.filter(|out| out.root_key_id == keychain.root_key_id())
|
2017-10-26 00:09:34 +03:00
|
|
|
.filter(|out| out.status != OutputStatus::Spent)
|
|
|
|
{
|
2018-01-06 00:03:53 +03:00
|
|
|
let commit = keychain
|
|
|
|
.commit_with_key_index(out.value, out.n_child)
|
|
|
|
.unwrap();
|
2017-10-26 00:09:34 +03:00
|
|
|
commits.push(commit);
|
|
|
|
wallet_outputs.insert(commit, out.key_id.clone());
|
|
|
|
}
|
|
|
|
});
|
2017-10-25 20:57:48 +03:00
|
|
|
|
2017-10-26 00:09:34 +03:00
|
|
|
// build the necessary query params -
|
2018-01-06 00:03:53 +03:00
|
|
|
// ?id=xxx&id=yyy&id=zzz
|
2017-10-26 00:09:34 +03:00
|
|
|
let query_params: Vec<String> = commits
|
|
|
|
.iter()
|
|
|
|
.map(|commit| {
|
|
|
|
let id = util::to_hex(commit.as_ref().to_vec());
|
|
|
|
format!("id={}", id)
|
|
|
|
})
|
|
|
|
.collect();
|
2017-10-25 20:57:48 +03:00
|
|
|
|
2017-10-26 00:09:34 +03:00
|
|
|
// build a map of api outputs by commit so we can look them up efficiently
|
|
|
|
let mut api_outputs: HashMap<pedersen::Commitment, api::Output> = HashMap::new();
|
2018-01-06 00:03:53 +03:00
|
|
|
|
|
|
|
// size of the batch size for the utxo query
|
|
|
|
let batch_query_size = 500;
|
|
|
|
|
|
|
|
let mut index_id = 0;
|
|
|
|
while index_id < query_params.len() {
|
|
|
|
let batch_query: Vec<String>;
|
|
|
|
if index_id + batch_query_size > query_params.len() {
|
|
|
|
batch_query = query_params[index_id..query_params.len()].to_vec();
|
|
|
|
index_id = query_params.len();
|
|
|
|
} else {
|
|
|
|
batch_query = query_params[index_id..index_id + batch_query_size].to_vec();
|
|
|
|
index_id = index_id + batch_query_size;
|
2017-11-08 03:44:20 +03:00
|
|
|
}
|
2018-01-06 00:03:53 +03:00
|
|
|
|
|
|
|
let query_string = batch_query.join("&");
|
|
|
|
|
|
|
|
let url = format!(
|
|
|
|
"{}/v1/chain/utxos/byids?{}",
|
|
|
|
config.check_node_api_http_addr, query_string,
|
|
|
|
);
|
|
|
|
|
|
|
|
match api::client::get::<Vec<api::Output>>(url.as_str()) {
|
|
|
|
Ok(outputs) => for out in outputs {
|
|
|
|
api_outputs.insert(out.commit, out);
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
// if we got anything other than 200 back from server, don't attempt to refresh
|
|
|
|
// the wallet data after
|
|
|
|
return Err(Error::Node(e));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-10-25 20:57:48 +03:00
|
|
|
|
2017-10-26 00:09:34 +03:00
|
|
|
// now for each commit, find the output in the wallet and
|
2018-01-06 00:03:53 +03:00
|
|
|
// the corresponding api output (if it exists)
|
|
|
|
// and refresh it in-place in the wallet.
|
|
|
|
// Note: minimizing the time we spend holding the wallet lock.
|
|
|
|
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
|
|
|
for commit in commits {
|
|
|
|
let id = wallet_outputs.get(&commit).unwrap();
|
|
|
|
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
|
|
|
|
match api_outputs.get(&commit) {
|
|
|
|
Some(api_output) => refresh_output(&mut output.get_mut(), api_output),
|
|
|
|
None => mark_spent_output(&mut output.get_mut()),
|
|
|
|
};
|
|
|
|
}
|
2017-06-01 01:52:43 +03:00
|
|
|
}
|
2017-09-22 19:44:12 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-10-11 21:12:01 +03:00
|
|
|
pub fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {
|
2017-11-01 02:32:33 +03:00
|
|
|
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
2017-09-29 21:44:25 +03:00
|
|
|
api::client::get::<api::Tip>(url.as_str()).map_err(|e| Error::Node(e))
|
2017-06-01 01:52:43 +03:00
|
|
|
}
|