Browse Source

Merge pull request 'Merge dev into master' (#25) from dev into master

Reviewed-on: https://git.hush.is/hush/silentdragonlite-cli/pulls/25
pull/4/head
duke 5 months ago
parent
commit
f55038ab0f
  1. 3
      .gitignore
  2. 2058
      Cargo.lock
  3. 7
      Makefile
  4. 15
      README.md
  5. 9
      cli/src/lib.rs
  6. 2
      cli/src/version.rs
  7. 8
      lib/proto/service.proto
  8. 21
      lib/src/commands.rs
  9. 35
      lib/src/grpcconnector.rs
  10. 73
      lib/src/lightclient.rs
  11. 150
      lib/src/lightclient/checkpoints.rs
  12. 631
      lib/src/lightwallet.rs
  13. 50
      lib/src/lightwallet/data.rs
  14. 50
      util/build.sh

3
.gitignore

@ -3,4 +3,5 @@ target/
history.txt
/.idea/
tarpaulin-report.html
/log*
/log*
silentdragonlite-cli

2058
Cargo.lock

File diff suppressed because it is too large

7
Makefile

@ -11,10 +11,13 @@ help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
about: ## Display release info
printf "Hush Silentdragonlite-cli Makefile by jahway603"
printf "Hush Silentdragonlite-cli Makefile by jahway603\n"
build: ## Build the release
cargo build --release
./util/build.sh
cp `pwd`/target/release/$(PROJECT_NAME) .
printf "Hush silentdragonlite-cli is ready for you\n"
clean: ## Clean the repo
cargo clean
rm $(PROJECT_NAME)

15
README.md

@ -30,16 +30,31 @@ silentdragonlite does automatic note and utxo management, which means it doesn't
#### Pre-requisites
* You need Rust and how you install it will depend on your version of Linux. Below are well known rust versions tested on common Linux distributions.
| Linux Version | Rust Version Tested | Command to install |
|---------------|--------|---------------------------|
| Ubuntu 18.04 | 1.47.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) |
| Ubuntu 20.04 | 1.57.0 | sudo apt install rust-all |
| Debian 11 | 1.50.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) |
| Arch Linux | 1.56.0 | pacman -S rustc cargo |
* Debian 11 comes with a much older rust compiler (1.48.0) and so you want to use rustup with Debian and install at least 1.50.0.
* If you're using another Linux distro, then consult their package manager for rustc and cargo, but if it's tool old then you want to [use Rustup](https://www.rust-lang.org/tools/install) to install at least 1.50.0.
* The build will fail if you do not have `rustfmt` binary, which is included when you use `rustup` but may not be included in via operating system packages. Using `rustup` is recommended
To securely install rustup by compiling it yourself:
```
git clone https://github.com/rust-lang/rustup
cd rustup
cargo run --release
```
The above avoids piping the output of curl to bash (bad idea) and avoids using binaries. It will take a few minutes longer but is the better solution.
#### The compilation

9
cli/src/lib.rs

@ -201,13 +201,20 @@ pub fn start_interactive(command_tx: Sender<(String, Vec<String>)>, resp_rx: Rec
}
}
pub fn command_loop(lightclient: Arc<LightClient>) -> (Sender<(String, Vec<String>)>, Receiver<String>) {
let (command_tx, command_rx) = channel::<(String, Vec<String>)>();
let (resp_tx, resp_rx) = channel::<String>();
let lc = lightclient.clone();
std::thread::spawn(move || {
//start mempool_monitor
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {},
Err(e) => {
error!("Error starting mempool: {:?}", e);
}
}
loop {
match command_rx.recv_timeout(std::time::Duration::from_secs(5 * 60)) {
Ok((cmd, args)) => {

2
cli/src/version.rs

@ -1 +1 @@
pub const VERSION:&str = "1.1.1";
pub const VERSION:&str = "1.1.2";

8
lib/proto/service.proto

@ -75,6 +75,10 @@ message TransparentAddressBlockFilter {
BlockRange range = 2;
}
message Exclude {
repeated bytes txid = 1;
}
service CompactTxStreamer {
// Compact Blocks
rpc GetLatestBlock(ChainSpec) returns (BlockID) {}
@ -91,4 +95,8 @@ service CompactTxStreamer {
// Misc
rpc GetLightdInfo(Empty) returns (LightdInfo) {}
rpc GetCoinsupply(Empty) returns (Coinsupply) {}
//Mempool
rpc GetMempoolTx(Exclude) returns (stream CompactTx) {}
rpc GetMempoolStream(Empty) returns (stream RawTransaction) {}
}

21
lib/src/commands.rs

@ -32,10 +32,11 @@ impl Command for SyncCommand {
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
match lightclient.do_sync(true) {
Ok(j) => j.pretty(2),
Err(e) => e
Ok(j) => j.pretty(2),
Err(e) => e.to_string()
}
}
}
struct EncryptionStatusCommand {}
@ -112,7 +113,6 @@ impl Command for RescanCommand {
}
}
struct ClearCommand {}
impl Command for ClearCommand {
fn help(&self) -> String {
@ -250,7 +250,6 @@ impl Command for BalanceCommand {
}
}
struct AddressCommand {}
impl Command for AddressCommand {
fn help(&self) -> String {
@ -385,7 +384,6 @@ impl Command for DecryptCommand {
}
}
struct UnlockCommand {}
impl Command for UnlockCommand {
fn help(&self) -> String {
@ -425,7 +423,6 @@ impl Command for UnlockCommand {
}
}
struct LockCommand {}
impl Command for LockCommand {
fn help(&self) -> String {
@ -466,7 +463,6 @@ impl Command for LockCommand {
}
}
struct SendCommand {}
impl Command for SendCommand {
fn help(&self) -> String {
@ -770,19 +766,17 @@ impl Command for HeightCommand {
}
}
struct NewAddressCommand {}
impl Command for NewAddressCommand {
fn help(&self) -> String {
let mut h = vec![];
h.push("Create a new address in this wallet");
h.push("Usage:");
h.push("new [z | t]");
h.push("new [z | r]");
h.push("");
h.push("Example:");
h.push("To create a new z address:");
h.push("new z");
h.push("To create a new zs address:");
h.push("new zs");
h.join("\n")
}
@ -939,9 +933,6 @@ pub fn do_user_command(cmd: &str, args: &Vec<&str>, lightclient: &LightClient) -
}
}
#[cfg(test)]
pub mod tests {
use lazy_static::lazy_static;

35
lib/src/grpcconnector.rs

@ -1,6 +1,6 @@
// Copyright The Hush Developers 2019-2022
// Released under the GPLv3
use log::{error};
use log::{info,error};
use std::sync::Arc;
use zcash_primitives::transaction::{TxId};
@ -189,6 +189,39 @@ async fn get_address_txids<F : 'static + std::marker::Send>(uri: &http::Uri, add
Ok(())
}
// function to monitor mempool transactions
pub async fn monitor_mempool<F: 'static + std::marker::Send>(
uri: &http::Uri,
no_cert: bool,
mut c: F
) -> Result<(), Box<dyn std::error::Error>>
where
F: FnMut(RawTransaction) -> Result<(), Box<dyn std::error::Error>>,
{
let mut client = get_client(uri, no_cert)
.await
.map_err(|e| format!("Error getting client: {:?}", e))?;
let request = Request::new(Empty {});
let mut response = client
.get_mempool_stream(request)
.await
.map_err(|e| format!("{}", e))?
.into_inner();
while let Ok(Some(rtx)) = response.message().await {
if let Err(e) = c(rtx) {
info!("Error processing RawTransaction: {:?}", e);
}
}
Ok(())
}
pub fn fetch_transparent_txids<F : 'static + std::marker::Send>(uri: &http::Uri, address: String,
start_height: u64, end_height: u64, no_cert: bool, c: F) -> Result<(), String>

73
lib/src/lightclient.rs

@ -11,6 +11,10 @@ use std::cmp::{max, min};
use std::io;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter, Error, ErrorKind};
use tokio::runtime::Runtime;
use tokio::time::{Duration};
use crate::grpc_client::RawTransaction;
use protobuf::parse_from_bytes;
@ -629,6 +633,7 @@ impl LightClient {
object!{
"address" => zaddress.clone(),
"zbalance" => wallet.zbalance(Some(zaddress.clone())),
"unconfirmed" => wallet.unconfirmed_zbalance(Some(zaddress.clone())),
"verified_zbalance" => wallet.verified_zbalance(Some(zaddress.clone())),
"spendable_zbalance" => wallet.spendable_zbalance(Some(zaddress.clone()))
}
@ -647,6 +652,7 @@ impl LightClient {
object!{
"zbalance" => wallet.zbalance(None),
"unconfirmed" => wallet.unconfirmed_zbalance(None),
"verified_zbalance" => wallet.verified_zbalance(None),
"spendable_zbalance" => wallet.spendable_zbalance(None),
"tbalance" => wallet.tbalance(None),
@ -940,7 +946,30 @@ impl LightClient {
txns
})
.collect::<Vec<JsonValue>>();
// Add the incoming Mempool - incoming_mempool flag is atm useless, but we can use that in future maybe
tx_list.extend(
wallet.incoming_mempool_txs.read().unwrap().iter().flat_map(|(_, wtxs)| {
wtxs.iter().flat_map(|wtx| {
wtx.incoming_metadata.iter()
.enumerate()
.map(move |(_i, om)|
object! {
"block_height" => wtx.block.clone(),
"datetime" => wtx.datetime.clone(),
"position" => om.position,
"txid" => format!("{}", wtx.txid),
"amount" => om.value as i64,
"address" => om.address.clone(),
"memo" => LightWallet::memo_str(&Some(om.memo.clone())),
"unconfirmed" => true,
"incoming_mempool" => true,
}
)
})
})
);
// Add in all mempool txns
tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| {
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
@ -956,6 +985,7 @@ impl LightClient {
"address" => om.address.clone(),
"value" => om.value,
"memo" => LightWallet::memo_str(&Some(om.memo.clone())),
}).collect::<Vec<JsonValue>>();
object! {
@ -1037,6 +1067,47 @@ impl LightClient {
Ok(array![new_address])
}
// Start Mempool-Monitor
pub fn start_mempool_monitor(lc: Arc<LightClient>) -> Result<(), String> {
let config = lc.config.clone();
let uri = config.server.clone();
let (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::<RawTransaction>();
// Thread for reveive transactions
std::thread::spawn(move || {
while let Ok(rtx) = incoming_mempool_rx.recv() {
if let Ok(tx) = Transaction::read(
&rtx.data[..])
{
let light_wallet_clone = lc.wallet.clone();
light_wallet_clone.read().unwrap().scan_full_mempool_tx(&tx, rtx.height as i32, 0, true);
}
}
});
// Thread mempool monitor
std::thread::spawn(move || {
let mut rt = Runtime::new().unwrap();
rt.block_on(async {
loop {
let incoming_mempool_tx_clone = incoming_mempool_tx.clone();
let send_closure = move |rtx: RawTransaction| {
incoming_mempool_tx_clone.send(rtx).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
};
match grpcconnector::monitor_mempool(&uri.clone(), true, send_closure).await {
Ok(_) => info!("Mempool monitor loop successful"),
Err(e) => warn!("Mempool monitor returned {:?}, will restart listening", e),
}
std::thread::sleep(Duration::from_secs(10));
}
});
});
Ok(())
}
/// Convinence function to determine what type of key this is and import it
pub fn do_import_key(&self, key: String, birthday: u64) -> Result<JsonValue, String> {
if key.starts_with(self.config.hrp_sapling_private_key()) {

150
lib/src/lightclient/checkpoints.rs

@ -339,6 +339,156 @@ fn get_main_checkpoint(height: u64) -> Option<(u64, &'static str, &'static str)
(1120000,"00000006b40bb5d87feefa917c203eaa024285f894a7784e7c28822f10dfad14",
"01cd4ed0a0591e49aeffd02bdc16ff4471c2b67d3da70692ac35d3102d61c8fe2e0190f46ff89ac6f92f89f8d5f4e3e463f323e6145f8c78e12cbe0e82d6a712b822150140571e6721e4bb43f43dda2f124823a284cf5573c1a5cbbaaaa7f7755ec9c0090001104354a15f5dbb98ac573df32bda616620d399b943309c677bbca9a3d286d843000171430a040c56b3330fdb37e161e4f2ff4df9a77c7e1ed10138d02b658f8886490000014aa7470ae275590e0eaaba244644f7f2f3beb1257faba0e77081c54cf780701f0001fc266a88e73d766a964fcdec99eb7f08bc866484015f2f344d2d748b006b17180000000152d760747aee5ba88475ec19b1b4a38c60ba7049c364c993ba1123ebbd2c3c2b01b7ba57706a60c3d48d581563d835fdcb09934fc1190aacf45d43c0027d74c91501e3b12c54ba54412ac8ac92c84f2841009646b4a0a92e725dba7ccb33f829244e0196c5875a8320c6bbfe67198a103379dcda2de6036def7c2a7b4df50a9814201b011c29798adb3d52e01dae88cf3814a2ec15a1eb3e2738506bf0e93b5e439213660189bd7de7b740521900ca316b7031e8f14fde273224125baca70e76c1c5d0e1660001d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1130000,"000000076f4faeb53342aac9204affdbacb1709885a4a245e64740343d665326",
"01fcf026d46abccb7ebd2f2241941867982097b4deff4fff84d16072a82c146e680104ae5d933c5c4e70797136f366577320ffa629e6ca5c2613e7c8972a3679d10c150001e5a61fb53c56787a9d40d0737a15f39b87b5ad095d19b223d14e3b3a402ad66001e023a629ce9f7025675cb8397a84984a7f281d44a34fdd4a8ea1498489c909120000000001ece103c27d5fed0e55e3e979c406e267cd3257cf90e6c53b61a8db13c7a83a4001783ce2fad4389aa5616653612975dce321cd494ad5957f2c134ad9084d63172c0000000001c440f1b5b833f6deb11b34941b204ec820cf861cea8a584ab2383448e2858e3d01770aed5b9f90e96df6c1df9c4d3ab57f270075231f123e127bd00596937e731a0000000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1140000,"00000008265a1b52f46556220ef184bd924a47ca13dea30a2e01ae91c614d521",
"0179200e234e3270bc2b9d965c33af98be9b4dd6dd920f1019a31552fc0b9e511601883613531f9b6049048fff599f79c1164960f9741e7430db8af5fdfa909cf91515011b23fe9b4a6c8ce8f66810b4dd61d0787294a548010e3846b2acb60f4496cb3a000001adf2f2fe681da30b93d13cf7561973120582697d32550ec416d5243594e67a250001e8855b6534f0bf443d9ba67030cabd099c3ee3f068e89ea92b2a954ee6cc9e72019dfa6b155cdeb7879ae4bf4292f9822663518cbc66e13a9409414503587b0d2b01c7554d9c3cdb5db2dfe959a0de6a42a295691c2d7f029651bf57ad22c1e28a5201907d464ba947d4aa88a63a1c03ec33f593a37ccbe7b03c080546a7821065283c0000016ffa7e15c1923b15486684f23d7d589c5616f156a8e41841e63e03a7cce7686e000001c8001a8d640c0c4205421a04192b0ac34e8f82185837d1c33086527d74c4014d015a8656db4dafe1f6974b7099ca121cb27cd5e62138ba9a680823560eaf1cfb1100000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1150000,"0000000650e627bd7da6868f14070aff8fdbd31ef7125fe77851976ed3adfc54",
"012e058162e4e6bc9553c413134b66e5e89cd63c330fc557060878c623fdedc63d01533dbef6ad6b226c7138bef1e1961ca170510a91fdb1ff5972e1cd79c347401c150168979af907639a39b427d83d4602fd22867faffb0942cbb11955dac680aab85901d8d396d94feb0cc78cbec422c6fd09834c4031a1ffdcbef04178827add5193160102e9b3fecaf39d40e5e63ef3253d1ac5aee4141bdf26763de74033d6016f5955019f59fdd4a570ad22980428caea7b5fa61f55b52e2e6fbb29600eee53d31ed35f017c5ad297c1e83430ce9d3768fd38e74bd06757b67e2917b34d0f3f763803f32600000000015c1016fb7c68d85099ce8423d6446c2ea3d77a63b5ab6044f6eb0c024ddb0d5a01627b5eae7588998ef2645fe8be1ec3227d560828956b7df00632b26784c4f80a0000012d8bdb15bce00ab0c8bd332355a100d9db356ac05fb97412b479214dcefea331000001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1160000,"000000006904fea1620eb53ad7f912197881e3d47a8a6683d943efc0ff43c94e",
"01569b6b693b7fc56715b01e81a3d07dcfd723f54dc8f31cdb465509f39304755800150001dcf14961a27da1444097a9618a6a3d4a6a198b4afc8c9c2960e30da42c5790210001f95f7e52338af7b69ae511d4ae5c1fbaa394380e92fbb51cddfdc4b8b0cfef0601cd00c0bca2308a408b94327da9505d55a241aa05ae730884ee38c8d553d218290000000001d637d869f0247b4dc198156dee890fa12f44ad83c1ecd9f73bb89ab578ff31260001cebd13b9f31ab7c442c5a89562817d5b590b51ac75d735041d2389ceabd3cf7101e3a962887edb0c646e6390f87eb64ab4b12753338b8b72f3afcbca0b499b1f3a01447d8106fe66cea724f27e0f0310821d7e5c536b02f4540ce14f6359ec30650a014431c3e5e81d9dec6f8c51f83ad971de208c6a7d990f727f2b203af910f760410001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1170000,"00000002adc92872d2031c7f142d7c3cb85ffa90b3a656d2b2061e997e7b8770",
"01e3c9d37067b3aa1cd33ba2a3e51cbe1690042769f2ac915cb3e9d61bae65823501ea153766cc73c36fed019670212ab7b1aa8f4dd8df9610fa3145dc4e76b90c3f1500010fb2c7d5d6bc641af0827e396a066c985d9518fd6ef592e35f7ec18d0b74e64001d9241347efc57296ba0f95e934af314f2e6bc5cf57fc2bd5f05ff1fb2056a03900000129fd868317570e7f0da3cd0b61a3119f98f55c11686999a440f26400c55c2d650156fd80b1527e6908121a5bb7aaba95286350517370cb14820e87dc4b1be8091c01894020fc3e84860215434dced0c574e3c3b73bb4a130f55f187cff880148cc1200012f00562149cd5f5e63082732758f2af3639a78ed6559c8fc905d81ba57c3cc5e000175e1fd5f99946a2f3f92f8db241ff15741475e3ca145ee08bed8d708561ed8430000000152bc335d5563bacbe46b23053e3d68222c9b4c5b682ae9bce63df2fcbc125c2101a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1180000,"00000007747404722bfbab50f0a42b8c950c636d332263f2dc189f72b8832df4",
"01dc075635b61101c6fe78d040974c5d6ebf9ce9ac1b0a7bf77f9f38e236d05555001501dfe7916b94e0cec2aa1d749f9daca5e458a8ffa0a8b106b850e126ff9299d0530000019f441cf317780ffbc8c224d3d69c24fa732298334c070cb5e3d49c07ebbae00e0104ff5d6aa8c7b47476867f27e8eeeb6e4c0d73f3e1fcff1a90e9d31b0c2a4b210000000166d9f9e0de4e319ded5dc53ca266b6ad0209ebbde6a6b0d36b72dc9d5d22e86a000184a39a4968ee16bc6f879adf3269a98cabdaf7e04de248aac7097d8de97f952f0001e73b4bbc68881cf09b5e2c3cff9606c1ec1871c144c4c30fc15631c1314a165f00000152bc335d5563bacbe46b23053e3d68222c9b4c5b682ae9bce63df2fcbc125c2101a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1190000,"000000012c06d63527fc546d26d426fc0a1ca861d7261b87b899b52603bd6476",
"01434c2af8db16c6cd91c2ba5ded326c44b2e7998820eebe2141521bb947e9d532016bf26dd49f962640078b1434ac9192a1adab384b710009af0c5a11cf12d4216415000163b0432357068fc5afd67a83965ef5df02f01678307f13e00075d07b0896f965019828ecfed000d499541872ad236d30210072c28a59f614777252bc02e0318f540001ec25a79d0cf42c4322f4c8c372759aeef1df291c7647edb18486e3299c25784001476405f45415b1aa4a8a2b8c48322451480621fa4d8bfb65fcf1fe30293353600001dcc2f98d6b8daa166c493515bcf2fc721386eee17d09c42e8f50ddb553eabc03000000000001fad5104d0ac0048d720b5877cfcd3eb0c292d41c44f32524859c54176ca52651000152bc335d5563bacbe46b23053e3d68222c9b4c5b682ae9bce63df2fcbc125c2101a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1200000,"00000008b51ed341723143be685bfa5dc7d0623a7892958fab0e91f1a0b067bd",
"0167b318d15dab94f059a2797cc085f78bda644291b6e296be93f4b099edc8422001a9880f62e08675d40655bd1d89c311d784dd1c61ccd07ebec50ad556ffde6837150001953a6db39dc8cef59839b9089476d5a875dc21033f68f6b09f2f38b1c20368110000000000000141fd390d5af6a8319cfddfda858143a3bb8a53fba7ce1840734f3838a2f4f24400012a6a7779a560d3f6a7423803a41049c246ff3055c9ca534eb365a57afb004e7000014304189c82cd8cd49858bf3b8b19348c174e5693a8f4cca031b05a1df3273f29000122182ec759126e167f2abf99fe7eee7832dcc67947f8f6b55a175eae864222280152bc335d5563bacbe46b23053e3d68222c9b4c5b682ae9bce63df2fcbc125c2101a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1210000,"000000001466676d37c27e82694b03318fda2e81a363d16ff8d7fbe8a76d8693",
"01887efea331b25ed747289966066169d7600056b22d273aed830df72883fc293001139b8f11c7f4de8d69dce6218d79a11752be6b2480a24faf3e911f08e72b111c1501da3882662ae3353d906b61978c94c77e5f0049f73470020c029621f62fcb93100000014c08ee6fdc7de4dd99aa763e3211dd1194bcb0a7f6404e94dda3497cb8334d6a00000001d056da4fb56dd531de45388b12df2427e8a1157f5d11d9f9a2e645279bbd897001a9c7fb2f4c58d7e3d1744840ab14549adec3936d2542e105c91562eb7e9d9d6600014451d7a2971b32677e631ae23a1ac031049a44dbcc681af42f39c0db9b64136c000189f7b3b1a77a49f6888a3b3018d61b2268e27121f3537890b28b7c8f9295011e0000000001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1220000,"00000006e1c2ff124328e47c93b36f491d02ea6b3f73a5035d448123977afa3a",
"01de949b946356dc9f891ac5d0fce14c3f98ee93c8a2b3fc523a078d7dd636e83501b2e275ac90ff68d854a813f3430d774ebc178b1b0592cc3d3ef6d46bdd62d522150000000140a4644623a3704f854e9e6817da24ea35602818a48809709b02b66cd5e31026000001c4e0072587fe1f327b36cbe21f7436f75b2c78772b35d30d3bf28767408f4d00000001c03ba44a0bea63ef51bd9d261dfa64338c4b8f59f0f1ae4a795848b0cc082e0b01b7ece589560429856511ea8e292395c2a7c965794b2345f9289b5ae1abb2af5c00000001ba2c2617e4b338d6d1e7b45444675762a735838905238dcd272a26a9359fb940000001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1230000,"00000004ce24d10589f23cc53ac76df507dff75495431c279fb8ebb771791073",
"01044c5e5aa33db90d91d6892de5aec78715c3772ea65f4061e265a71d7d5ca7240118340f662b641bfa9007d3cde760b51349cfa2b3b836b18e265f3514981ca512150000019dfe8abeda3ff7c8d0a92ac465688049195229e99e833a1fcd46fd2960674f5f0001aaddf43f00e18a49839f70690b6de157dfd172874b4a170be93755775f98e06c0001ebbd52830dfbc8b35d98d10ed5d40a7ee2dc0d36c04ca009484197fed257ca65017803e5e41850251341995593f0c4d0e2f4c868c4e88a790acd39aece257ccf6201fb55e2c74a72dd35cac7cb085230a0c4dff4c4179845a856889b4fc80737451701e11023b34202c7ff23cb30ba202d79c6e67cce3b3d38eabc769f9619285b8734018c0e2f045c699b56b2684911780b17f4122120732eb9a5a28a1e0215bf31cf48014f92bffb8f6b9159666ef3fb84fa7f4c558b8262c8ac0752bfbd50dab0664f0b01e4ca50a2dd81680ffef3f69128675e9be5bf2986979a6f97b688afd248aee2030001ba2c2617e4b338d6d1e7b45444675762a735838905238dcd272a26a9359fb940000001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1240000,"000000042929e3117f22829289d73ed8c33032ecd813e65da9d25ee48139c5de",
"01031589171a3564c6913eb928e46d0c15cdde79e96176e52ece9ef288e05da7250106ab253ddcb2ff0559b5c8bd2d304b3d396dba44d6d43d36d5d9f7fb24083d6115019e122067ba264543782f6b853247c8ce0ec8aba7fb6edf9d3a6757ca7b16e53c013dbd5fc364ab7495a8fefd21cd6e0f65c54978fe773c48d6ff193b549fd9ce2300012e4c580046cb273f9dae176286308bdd767c25ade59ad798831faae821a54f2101226962aeb6bd6e749ec7315e9a8fb836574a1cea5d6acce25162276f34379101000001e50718f237f689f56e9d2f85d0fbb666ce88f789af42a09c886aea4c909fea6f000001779a41366fc6918c8544d2eaaca9bc85ca2793a5e9c61c92b6940581f0ac7f3700000000014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1250000,"000000033226eef40d094c8aa88d03bbc9146856c52248760e28c45426787352",
"0141ba41ee39d242893430eafea3da36c8f145988bc5b5197955b8319024ef78370187b3fb06889c42785ed0d58e42091896d96937d176ca6bf38859c2e85f39790115000100684449fa49a1d054027bd848ffdb926ef763d7eec2d89a41ec8e000abbb06c00000001f0feba3b7a13f2d3e391e814feb8166643d7a83e0a171d927189eb69ccc9a83d010293fe4407dd8b8210b6895efeb25fc9b04f7f170ee886f43684c326f513b36a0000000000000148decc2ce592128f159127e47efbe86945cc753ad561a7c9c2f1cc08aaf0426600014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1260000,"00000000b7fc90789fca4e53b5966707642d1d9ffcccc99c28fd194135d08618",
"016c56aca9eb431f561ff0076de88125a2a8496fa43a208bcb56e205138f066e480170ed150eead6d280ca06ee78ebe0a1e497fe626b5c1d0fd27f9ac011a0e3fa471500000156d5c46488b0e090a5f9e3dca3972b1361b4a035543662bf976cf36433679a010179de564d31701fcd2ead3ed7ad15c474fa6d3c3953d58a84de3265f4b42be62601348192d6c386c1cbe064ab3775d88e02b19317f287c8484a55f92521ea72d7370174dbd601820a8a446fd50ad50efb280936a18ea72ef862703dc29378466a376a010ba60f0f93ee5039e7041c1eede618fdf670bbfede4df885015628806a768f1d00015d52e07f708eb16da7d7e66fc1a32621c978171adef97e353c8babda575fef0b0195da1a9dd2c7dec13ba6b796f010dd64e9f3cdc40b132aad0c0997787ff8a925000168230f898166cc02b4b2f96958dedd24ebcf87d102e7dd28f08f1998b9d06f3701b4658fe046bba8df072bbd368969e3b2c5c387769ab74a9462374b15060463620001df509699f1009b4996aa329153ecd50c0c131e4c74cc78691c6492c1c362c019014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1270000,"00000000041c198471a04f305344aeae7650140175c97d62765861d6416a4ed1",
"01aa4f0d662a02d6205630b6becc2520dd67e290784a79017e54bec50a818a7e6701284a633aeec3f98db932323908febea122743f401ffd0b9b3b5254ff6689f768150000000001d4797cd87b99cac8b7c5da05852a527421cca77a529753fb1fac42bc6924a10501ffaaad4f376ed41b5f109e953e319293b2d193983f623cbcbbda28d11299911201a1d4adaba3301f2760811862f0d83d0fe227d9696de57d3fdb397d441f8c953001a15e2f347cc58507bb5cfd34d295c8bffe94ad92d5d195b51a7cd448a5919a4e0130a340676af3449263564cfa7c9a2e322b1e6d2de66173c168a0bce54a3bf934000001c3edce0df21c9bb91730c9f84e4021d9531b5d86b2e4bf95e5a7c022fc222d5400016244c4119437c4569ed468ba7777fa8169e9515fdfd3246c2c2c7b8a49e2ea4a01df509699f1009b4996aa329153ecd50c0c131e4c74cc78691c6492c1c362c019014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1280000,"0000000172d327c94631b30e851ada193ad0d4b05a659db7e92cdaf4a5859841",
"012f189b8e7e8d4ed31e63f26477e4944bcf24ebe2b1bc87e7ba4ddfead53df5490132633b0452e6ee907f69ee12d93b357590a1d4f89b0cff14ecb6db3c0efb973315000140bf0832101b85af9a3634fbb8fdd62d20638eda071859382e7a0dca1455633f00000000017a2ae902e520a3612ca41c729de5620bc7a4bb2254c4d45ecfa33587d54b3c640154c27323fb6b37e1c36d3851872ff0164c3b4996350a60a6d6afea0167f965680000000001cd1eda1387aea1a8eaba300f06bd3ce823febc156e7a551c9ffb355dba061d60016244c4119437c4569ed468ba7777fa8169e9515fdfd3246c2c2c7b8a49e2ea4a01df509699f1009b4996aa329153ecd50c0c131e4c74cc78691c6492c1c362c019014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1290000,"0000000305db71205f1c622693cc55fee7c3b7c26400e13e82b3855a13c40df7",
"01ff51396359add84d9ee4f1bbc6ba7fef7427a4b023d9732f5413b6202ab3c76200150001030dd4c88f0b2ba4764475181f612603b056f11f4df30b8302448a971bb937170197e3ba47ecdabe3705552a0027ddd622e6f8f6eb5e6d02766b1736b55fa0ee410173da9642855d9310b5e613818b63aeb471fd609cada15670fcd6692655aa212b0101b7b566cb6d82f4c744e743b963745e8f174dc59520eb1130388afdb14721690000012a40d9243f9e1a6422dbeae97d81acf783443bf9e1a204a3240e6a456b8d915f000000018ea6abb4d839e41177c01382db3c69e491450b898c260f0c6afd4e1906007a6201cd1eda1387aea1a8eaba300f06bd3ce823febc156e7a551c9ffb355dba061d60016244c4119437c4569ed468ba7777fa8169e9515fdfd3246c2c2c7b8a49e2ea4a01df509699f1009b4996aa329153ecd50c0c131e4c74cc78691c6492c1c362c019014aa507e6d089d8dc9a374085fd757952589b1f6625c2ad01540c7762cce7d73d0001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1300000,"00000003f498d56accc70c7257bfd708a22128dda0a038499f0c984f3e9ba2c0",
"0154d0ab0e747f55ce06b8d0a26190b55de89ac316afcae9125a5b35bb7add602d01ccae3e5bee18904063043ed9f182353a60373436700f8736c622ee1295c5d82e1500014294547ff33b9dd396eba07cc89a37240e3604458855fe79472f1eb71bb4ca3301024861538b380cf1f62b4f0efa84373e10c0bfac5eec5bb4b738a43ba6c13347000001b772befd90630de2d8576e4efdd4d3aab1134d64225c2f16ca354499ae83073a01b88d5eddc3bc917b11258b918cafab58597eee40ef9c021f9ec11b5a8c12e1050001e89579da4919280b3117af80fb1f07fc0e01b50b0635f3d8e108e365a82a08410142fd322541f3cea3dde5cbf43a64e72950704a63cea10ad7781eff61121ed62b0000000000000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1310000,"00000000cd97fe334d3be6f3fda419589ebf643bffab3194d5cdeb7d324d123c",
"01b5ba71ffa3543856777b9752e69e365f2befc5f510d802ca732d62bbf734e9440015012246d0dad83200d00542c57cfed8c6402f5156ebdc30a40d1e23f1e6237bcb5d01c8b1c4ee99fcbb76e8ab80abb973b383983f9a9815235de57f579524a49f6a38000001b8d2e3c93ef0c11df0efede3779d6e70acc4b1df797a86fb48fe2a6aba90415e019a150780d96ddd53388e455f6bc596bc2c444095d33a1a10d969ed8af166e526013239200560b5212ee84b72ae22b6f608f47b93622c04f7e4446cc49164bccb1501866f8ab942f35aad856bb406757b3aa155661b62250d45d10f82b03dacd2d21e01f292b74288d1c82e1708a43be0d4d114148f2a3bdfe43f886131e69fd9fa645600017f94a5993c238eb6f37c4eac4fe1ce2af093fed021849f75144606adde6fd420000185a8cbd76cd8fa8760152032e411fb884d2465617202efb2db7f65633481f26f0000000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1320000,"00000000995d4bafd5b3e9bcc709cf0623090008aa34c9ee6b44c815ad08bc48",
"01e997fecc5d718084b3cecaa69e45754309ec519c1aff83fcd4dac7c2bc58086401990844102e4c958ed8180f0d6dd1033539b565685f76b5cc93c2e9f5a8c6471d15000001b55b1efadc76e5d3eeeb4e291651ffd0d9d438535bafd00b6e0e6e80d5e9424801af5cdbde66d2d4473633d728f9822fb66e13ae846e278f19d48604acffae4a440152123d7723a7fdd2d15e11137e2a4f79e80a168a8a28fd186e9c62e94242b85d0001dc7d8470d225e1b31800a8e97f2f7b5ae753ceee6fe6b7d4718799929b28755901386aa30ef79e865a02a70db7924083b97ff554041c5d2dc86ba5016ee800ef1d00000153b1bbc4f08ec454d8921327035b9af1c5e349e6c894807eaf391318797b0a6e0000016b7fb5c9643c91102aad82dcbfe22fcb1d2c8af31057fdbc1cf4adaf9901d91100000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1330000,"000000004bfdeafb2dcdbb7b0a214c62fc4f03583f3f6f27bbe2dd78c083bc6a",
"01366fd1a0d525398df283778fedb0c51927cab0824a0edb62f12659012199e15401c3bfa3a862a42c887785197f584e05123ce8aab1fada69cbd38cb4134054e4101501cf0183ac26ad552ec4b176f08605ea83cd36160856bb9fdcda4ff29a0ece6b3a0001a8ffbd594b0cb08a2f63579a67ac28c859f64dfc60c8dc27a48427013107fb6a0001a43595332a65522bfc47de716855a27e3a07513d80c7488068ea956f923c204c01601417cacf850d64d5577f13b9a1dcbed20dd5b251211d6073f6bc60c777a125000000016bcc0413d0ceb351aacf269fb8d7f3ef58361b8496840ae5111966925888965c0001eeac840024fe032da0a4a645953622829733e01e35733f80ce36cd83450cf10600016b7fb5c9643c91102aad82dcbfe22fcb1d2c8af31057fdbc1cf4adaf9901d91100000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1340000,"0000000296642fe481256d81da12afbbfc867a6e56f7e88ee575376171a2eb2b",
"01e2aca02502cf0123f0a733d76f58ce64ef01ef4c2150f2e31eba0efbd1e8a16901f0c93c79f34e3f25bd376961995bdd37e79e9f8123fd577214cb0d546e255446150000000148d92a66c03debffd1352c77039a946958e7b72c2144d169d745043361d76b1c01bdbfa492c8d003f2f8f936df439469bb33c8a63c40ce007bdb3388ac0f3cb6420175ce5ef66e41ceb8d808a9d65ef52b64a7579993fb21aa7ebe35fdf3d6c95c30017a7e335b52c39b2afe58f898ecd9a1391a6bd072e487b729923641029b992e720001699dbd4984b09871c56e2c37298de74c8b5a0dcf0426ae4a9628cd828073de0701fa09a4ff3fc9d3dc9bf509f8c9309e3836ee980ca989c993e16e576d7e756662000001bb9d23de1629656a5002c1a7dde44452904d3f1b0153acdb96ad5a30d0bb6035016b7fb5c9643c91102aad82dcbfe22fcb1d2c8af31057fdbc1cf4adaf9901d91100000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1350000,"0000000040bdf5b1900a2d20f6fb7824f7f0ad2117a357ac212388bbe5238f40",
"018c9e81606764e5e4ddcb394208f7af43bf1f058d9968d01fc5d4bed68db9211200150001287f89fb5c0426800f3a78961ec0583cd5a9afc34ec2bf9e94c6abd04491c55d0001e672659839d5f31a8e74ae637335464110ffd20578f98c33152dd62f3773d91700000000015966a87175a3cb4d0a0813193bd6a206a0d592bd6e2cb8f97d45d85ded3a556d00000181d05915c012f68cf2d32295294532ffdc1db5c2a14bc1c2447b048577531a2301bb9d23de1629656a5002c1a7dde44452904d3f1b0153acdb96ad5a30d0bb6035016b7fb5c9643c91102aad82dcbfe22fcb1d2c8af31057fdbc1cf4adaf9901d91100000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1360000,"00000002e0a212bc285eef4232f6805f95ef054582a43fd4b926ed23e75ec378",
"01df84b3b74f8f5d043572a7546bf93f220e079e5955e761ad98a19207a992df2e016b34211be559838c98b236e6bbe0d1fe6f9c9adcd25cb59fb5b62f23ad301d56150000000001a019247df5c95eab49e0f622553411d353e465820bb997606298bdde09912f680000000120febd109cd5aa72a39af81773540f3e84965df0e40c6bef2e1dd619845cf84801069525c7bd728b6254afbc8359657d03b60726036746d9a82747a67d8f2f366d00000000017445b082f98141aa0d940882707945e8339d57737e36e0f1a3359d44456ec734000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1370000,"000000033c971fbcf50d26531667597acd4abf989473c10a7ecd23cd4546dc05",
"01166ad90b061ad3bf6381b46134a1709123567fdfac278433be1698f533197505001500017553b832182c217296601739a5b491ac9bd5b5f9c2f5d8a070bb99281fd8de64011a123843b680c316cea597a381f434c2d7cf73164eae586de4c0fc47ccabf1090127e88210bbb662d8688ab4405c57fcdb3991d40fbe51b722ed63350a720714340125544441d1bde725d8f06ec9b89ea8d09b034a1a3441e4822a6df93dad93223e00010c7d993dd51b9ab2440e46b83c11fbc7ffa026ce533ad4b58c3b2f82840dc445000192d0ce94b4f07d865e53886803d458b79f6a8eae70f81a83c5fb1efb758df25100017affa0d302edcbb379677626143dfab2c094b1e25cf37a968081f38c6dc0514a012e8289df4f4015b0f4b05bc17faa1a036a2fb0088506cfb2b83342893725e5120000017445b082f98141aa0d940882707945e8339d57737e36e0f1a3359d44456ec734000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1380000,"000000023a6ce85edc04e5af620930477bea1aaf31ee262678968976717d176e",
"019421a811b3d7625a1c74ef3e17d727b1ca6c4700e6ac76bcf8090dc08926d71b019ca3647c062831e3ac802970e5e06c7cc7a747e7601522c77649348d685fe3301501b8fdcd7126fbebf0f2fc4d7dff65e74aacf2c24a7403fe0d17a807cd273a48410001d4faf8b561fa81c230f72af05f4208932ab7b70a64374e215a17154feee0764900000001107f45e4a2e04748518785e094daf793f9ef968b530273c80f254b3ce86d9428015fd044ccf3cf5580280e25bb1b8921e24017dcbaa94b6cba698afdec24fd32720000016663995573a5bab4b0cd940b9148dfe246dd2401f5dc8931b0ebed85a34f622a0147d72c8e17fec6c90ee0d4f8fb78a8a4aa7360e85ccb8ea800e67f4afbadce33015a5860ff18b2a7419a85f4861aae38ab0e64ae842c91a4ca86a0b5711e5a5b6900017445b082f98141aa0d940882707945e8339d57737e36e0f1a3359d44456ec734000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1390000,"00000001f4f78ddb8d92d00fea207be21bed937c632a70681a522f2b21257f30",
"0156dc6263d4a5b9aed7df092b7c4ff500a1f46af115c7400ff5b2d174c4ccbd620015013c91b3983dcaa0350e973886eee5314f5636c33ef2bf0714635699825b13db1b017118d3862d56b8d655e104dc627cd5d29d845d5a879588b6cc565b5c76e1c364014f39855c43f73dfba9781dc4d3f475730a152a8219b66023c12220c7b67c8a22017c2d633d9dfde81c8545d467947c4d6f44c2d10db4cf324ead3181d66db5916000017a5ef3dcd15d96cf6ea7bdebd6b08adeddcf75ea1af299b147c455e626b3344c0001c6e587c76d2e56acfee61a8af9b89c6f535b9a0de496bbbf8a8b1c27142de02e0129090f67a827c807057e339d94ebe5d26921e233e8e7088a7f442ef104bde92c000168cbbe72c1c4e3fe7b014a9dcd211dbc2e51e7e5437b5de7797817cb0a63175d00000185f8542f77e6e01fcb4c9cde5c390c063ca80e89d39ac03817142086e43ccb2a017445b082f98141aa0d940882707945e8339d57737e36e0f1a3359d44456ec734000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1400000,"00000008d0941b9a5b394d19cff188a665cb8a57b89de8ad06f58fd08c6fcb8e",
"012dbf4ef46f0d00feb75e6e34e14ce72d9bcb774dcd890eab6328adbd9fabae15018f389e9d27d0a9877c13c27a57e88774f444dfac119792868fa7f5c262a8c95a150171a050ef6e54462a4de14ea53c92c93b3f5be4892967b23bd6dd1456cb3daf3d00014a77b1932dab168421459890f24207b348123423958b5baa6654ab159a961349000181ee8f53a09f2c1f3e567e3e70c7abe98aeda5a5d0a683f66c5e8409e635cf3b0000000000015cb8bfc55ef9a0dc25b1fd2a33187d0f77c25564e60b73eaa83691a38fbf0d02000121df2eaf715ace77ead0bcb7ab0cceb97c228ea57897e514c00fc5f234e976300185f8542f77e6e01fcb4c9cde5c390c063ca80e89d39ac03817142086e43ccb2a017445b082f98141aa0d940882707945e8339d57737e36e0f1a3359d44456ec734000129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1410000,"000000071ffa758ba71f243c8b4106cdabaaa5ec0df9247af03f5dc9221b58b2",
"01e13fc6cb9faa72c7e78b5aa6cb39369a032fcf6408920c4da7e2413dc72bcb160015000137944c1824fa144097df000fa9efaf320f8642ba251a5e095734bf6a2aaecb730182a852dc9d1163dc7fb76ea4acf5d3bc8036a4f44a8d8b07a2cf88d821e291110001883c92e9477e185a7e9f3232221be53874453154d5c6328c27720ad8a18a4d64000001750002abd239906761f437d5292df3a3aecfac1975283ba8936dba2e1afa7c3f00000000000000012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1420000,"0000000720e4e42612867d768dea333e255b9c660401072e1db5c2c0a927cb5e",
"016f0c18073b9f0e8e5cea84f9f56cdb3b2d8e6a18922eb1d7036b79a529ae6c6f010563681f9f9a91df3ff572b00ca3c90cce8fdb31e872ee127b2015323a26c33f15000001bb732c934241dc83e49ecd1675883c5f85391f408c65767f3b6374e85fd29d0501b3e1a844b72c6f09b0c7dc418b368ae05033a2b986fdb811b67c03999c5822600132693a5aa386d2d277b2a9ee9a3c817203ff326e280b9c4f197610ff09c1ae24015a0038e662df338af6c605d540c11bce9feeb243cfe0618fc203f0c139df4715000001434be856a8ae56ba6a17e2ce52c9029a12f73264d63966a38e68665e507cec1b0145310efd354420a6b267a0ac1fcb2eb5dff102d87f051dc4254705578fb6752001397dbab204e89d8ec263ea7cec16b0ec5c0a13bc61abb103895003b72781720001159199c0176b5cae220eaadf50eacf92bd17ade3ba2d6c247a2359b9019eb659000000012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1430000,"000000066375bf5edfb65bec45cfa47042b47e3aea87da9236da5b9f4ca70f84",
"0175dcd1343c9d5e07f3195043b605626f81b88071c31f6a5fc0ae900f170ed205014359d75dc4c94f4048bf44a8a182a18768aa4c60167a1312aae55ea95ead8d56150001c424e72313147674d468d308382167f9812f682b5e9abeed33a21f6911dd2f7301ccde0679ebf8f4f20d9bb2e41f96fc394e106118267757dcf7d8f3336ffcc72b013065dc4f2f9786453772f66c3e71aeea04f38a9773259fb6ffaa71d9e7646802016f4efb90f63de4413a27b774b216cf614bf8b27e4fd041473bde68445d575b3b01ee5b354371c5b9cada16df0c120907ee2928681e9237384d1b49de85cd66b9390126c696517f1dba2a81a0e74099b1f8d3fa2a65ee991344e8fe94d4f53a624c56000001a2fc4b1843af353e9287fe23854906267e892a053f714d1eb472b7f1d3dafe010153aa5aafa9c11464a123d59badb7743bb67fc2686550452df004897abc2ef66401fcc633dd7d6fb7f00d1b2bd8c0a99db4be5c8c6393b84fd36d6b2562db3c60480134f45736d1336cb1f55432e553afb2843f58fbd53a1424f546bbb68f4926ce460000012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1440000,"00000001025150e823cedbe98c30f09385017b229bc247420d43854ddb1c4ff0",
"01c96fa25dd7e8d53195f97031006d2cb1f2758c4b12fef11b588de57c69784455001501d3c916889978629148d469916a9d9b5770ac23657d69989e3e4fde867ff6c04b016c46a62678cea4266cc095d7f287bb3f5bb5dcc0d408a921d210843cab43227201dc162e6835b6b9e5c930ae8bab60ecf12fe735ae20355e5681d6fb0d78a4254f0001a708465a549e5798486fe87a2dfb0732e35974248cf14ec7840cda4bf5424f6201935fcb60099ad7fdf6b06fe286c5d95e25210e4826ecfe432af130f7eb6da472000001c4954e9df77cc98c76c6ee8c266ddf7bb193e4284cd39f3f9288f55e72cd920300014b8ed9563d4cbd1fd3e278bc201509a95645ff5414ba8b5dc3734cd5dcd966210111921d0b1f05672fb165599bed630e0cf8267e832b4c4ec4e6154e880e100f3f00019309b9363ebf08e38aa5ba8a8937532af004fb2d0c77f0b306b1e928516f702800012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1450000,"00000001ba8b62f62c313bad5f3e5e5b1068060e7b52855a703575e8b999e980",
"01062fdf962638e40cdf292711483f13aacc7adb20970e698451554eb39f0c96550015018b2a0192536c7b078ebafba23cadc4553ce910882b1d533a2f393440bbea120a015e5702d657da65aae04535f90ba42480be7b9a80b1d407ea55d536e8b7de79150117eb1a826ddd4705151442d3b2d9e148b771ee3bcab6c71e6e2f12572556460900000001b470a160ac7c4cf0474f54d05b4759d67dfc1d53239f7325fc10b3b8e0a70e5800000001f0df59b96f31f8753d8c82b30652e05a95b887ccd20939be6721195307a9647301739a817c8780e31798dab9a36e7a234df3799f85ac127b0191d8033b92c1f005012f3bb696570f0ac464a701f0ba745eb269c66f99026909b1c596234950b2d919019309b9363ebf08e38aa5ba8a8937532af004fb2d0c77f0b306b1e928516f702800012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1460000,"00000005153ae6e0e3f6dabd9ae9c3e02cbb2cdd61fc9cc697f94a943d3e7c31",
"01d4bf8b5ab1566fbf2c641008a65776866da18d3a90ee79521fac2177c26ca43e01e03d8a0d3b4092d7406f72833dd0e0bafe98920301164c5e7b5c300e01dc4443150001437e540964f73c898e5405a1af51ae34367076bcef86503aae34d92ce289e064000001e238cd66315e1f88b7fca6049bc0c40ec8c5e31ac2d603f79e1358be5aa6e41a00000000000162890d418ca91fc1dce31a115ba58a3d543d3a5ce293549ad698e23e58ed1e6101db4441db6eaac29cd078df0685d7d7124d5d2889664550f0b9cca3c2731df958000001faf6cdafacc60ed1b3b10777af659d02de872c23760ab9284486f92b4670af5a012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1470000,"00000001be9faf4c236b5925b9d82a843c98fdecb0db32a7b971c7f0e06ae686",
"013fc66f1aaf5428876bcba3c13eaf73c600bcbabdf0c6876f815f547cdafb585c01555aa955ed996be9e1acbdfc1c1c90c66157148baec627d5b1854796c911633c1501ae5f94529f588c2a468e68376c11b671369638bed86f182c787eff05935c276b000001f9efa221186444b30c09adb18f50049cd2e75a55a8e5644669be0ff084d5470501cdeadb4714448e7102061689fa3dcda163270c0fd6e712ef8c0b70d5899cef37014431821f9f977b204a710d621173ea98a612cf1e3bc1d3574af9e0638ec09e5900000152622e13f673b2776139957e1d5ad6cfedfff470f6a35dcace7c2b37076da64c014e096a16bed1a93215733b76a130aaffb9fc0a47d1a6fd497f9b89b64189071e000129121619d51717862f03cacfb05221cebf0118cd359558e20067c5ecccc172500176334579d1b7828cac70c63961ef5ce2c2e057b76812cdaa377e9990f568bc3f0001faf6cdafacc60ed1b3b10777af659d02de872c23760ab9284486f92b4670af5a012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1480000,"000000061fc1275ef78b285aec0a73edc82861a0d390503c3b4c5455d8242acc",
"01e2c7c111a10f50427bc4bc897e4ec3663d8c8f35aa588e92e7ea044c53b8a7150015000000019f147addb25fa3b22db6d2629ec21d569f97e9cb99e72604d8d2fdb1e0d88c0f01bfcd48547222892d0a02ddb9d5c993ae28595d01e28daea9d75df186ee611e6d000173b557332d245327c0b1472cf4a28e058527f4781a16844931bce099d0a94c4e010c50783bdf7fe4edd6e8676b8ae3ad6770e67601d4b934395d3efe32efee9e5e000001548aea4e8877dfce4d1cf9146c1d995e01365effe75c59793db2eeaec04c112a0191bfe1bc84ec45224de2151bd3ff73c758afd19aba2710a1d5d5c68f9846af670001048eff1e2456fde7d076e192449cf565729879f1ad37393b5b0583c3597b636e01faf6cdafacc60ed1b3b10777af659d02de872c23760ab9284486f92b4670af5a012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1490000,"00000002271c1f284ad47cfc27332ea820045602f62381dd10cdfe77cbf73cce",
"01ee724ea0ccc38da9b996e7d4324ebc8fa96cf02584226835ed68ecb2734d9d240015000000000000000001731883b430616e3b2f973be6189d6555f14d3a4f4d48576b072bf635390cee210108037602f447ed3d39a212fdbd967e7e2b5a79ecd2f241d571cca1273308a72601acb845b8a7c6a288ec4534bfd55dec73eeb20b3b6b926482ed1bcdf8f205a2240152cbaf618ef392310700621cbd0158fc48718fd6b7933781c4c914f1709b135201a050d10929e616bdb7599d670fc08d44065d2ba307434443bacdfb3a467e391b01048eff1e2456fde7d076e192449cf565729879f1ad37393b5b0583c3597b636e01faf6cdafacc60ed1b3b10777af659d02de872c23760ab9284486f92b4670af5a012a5cc87bee766002799c55ceb4811e7f7393a1e6a4cbec47deffc5f5545c0e630129e917c1084ce9790872926426d49c072d65efba4028dc1d29c4ff163f2f794001007357498cf229fb2aa34a72ecff748d7aa6f1a4e486f3eecf8a57673ad0ce340001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1500000,"0000000015757461b6c1169980a8be062898c1aca4861e45290bb2f07edac0fa",
"01ca0b0297a0cb17ca41bfee5396e1ef252485c9d07087b684ff52922a1fca6c230127ad4a6c7f09cbc7b26cb93a4d0be56bf9db0fd11f71e2984507a9928bf6135a150001bb5b561b4c49473ba301f931e1bfb70e71926e416906f1841d2a7769f23c07100001eab4cdc55c3ad81a08c4c0e7a8ed79f754881de4b44718ed4230fa20b3f33c4101c1ccf91e6f5272f8dcabb246b65faae325473a7cca1c6860a358b4c5852b227000013b7051ca25271585b515cb34b0c32a5d5c6957aba18b05a83fe24da5aee9fc1f0000011f91bb48e98e17dd128559e09c5725df99d204258274a7b716a2c421ef59d107000001b80be3abe749b80354f89195cad85c45d89e5b3ccffff88ca10e6eb4a0c37238000000000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1510000,"00000003a10174289817f24b4c8f60378e2f075e6bba3fce06e766e5f6eb90f1",
"01971f1e5c04ad6f515c265fb7ca287cd3e325ce679e99bf665688eb036972e4430015000001687da9f56edd4364dc7e7ee3e6887a2e7933e8111c80888e3c089b3974364e12000165b3b9949855b0caeca9057520d8d1ea313f85d880371ab9adc1a72f782b242f00010acf40cff2e4749c17f58352b9ebbfc93f3093320cdf16d6b1e052dd34012c35017cd2a09bb7b82061cf0a2f0aface1b1fadbef1bfc0118d2d74062cbf06406e2401ec221b063215234d84074ed3bce108f0067a39f0583da2c02944734e0f00597201ad177ae3fd3637c38a38b30b0909b5437dc54edbd7bfb29a3d3d86d12726e96e0155a16a02a55e3e59aec6e2b40b292636610c515a08f432e25e2a96b6d0ae757101447d962c014e0aa51c2939c259ca2bd31b7458f28c76d51073fa9d0505e5f01501b80be3abe749b80354f89195cad85c45d89e5b3ccffff88ca10e6eb4a0c37238000000000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1520000,"00000007fba88eea7c09076fa7693b7ef39feb161bd7d96299deff6f16887ce7",
"016da64c5c9b5db696dc386c9b230e60e3572f1baab569af0e3ae3800d920c3a34015a90e174da6cd71e7b9505d4efa1ed7a91e00904c931210b80eb95fa5dc19431150001461b3858cf8585506809f331942869ee7bc88cd01ea4fd3d3ddb6298f9ee882801f9f5dd3908aeb65b36afa6df68ed369dc3bb354f3f7a116f6c7c55cc02868c0c01fb4b292d6a79bd9b8a0d067986ee1ecfe82763b8c7d590bfc71ee21508778a26000001bce18fa0af931af2f6b448202692ad31d197de42e322b871b2d21eb36b3d4e1101770741860cf8ba2a06a6f36601463c51fd8a117f60a020c7acab47e3fd7ce84d000000016f50ed4e26245944913c521adfc3e08b600f4e14c120b38afe2a78b1dbd471560001637dc44b8bb875ee3dc2796619ab3bdab974206e4dbc44435cb6bd734c47f31f0000000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1530000,"000000027efbba859044503a52fd35ea1a1fac64e6b159dbdcebcf861f521396",
"01d700df59367140aeee819c9264ec44182a544bf1d61a44f5ae9801bc940523020015012145e4feee12794f1d91327ac04aaa7a5cf154c527265c60d12b31e86f31fc4201aa1fab9261488d0d5d12982fcf10ddadd4a13fca190729d3fb9ed4ad4afe902201c4a760b9cf362345ce4251c5058be602fc324f8e2ec7e81f30a617c5aebb3a01000000000000000000011f07cccbe7288652b08cbca3184e3a4f7963e5c090597806e5b5bd156c84132001637dc44b8bb875ee3dc2796619ab3bdab974206e4dbc44435cb6bd734c47f31f0000000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1540000,"000000093b7b4861ec7cf9c1262ac89815b515160e383819383ae2bce4d9d15a",
"011342c13e4934fee3cf4ea3e2e40e7cf6430a0174f2715804085535f68ad7d90c00150000000001cc346113164aae165662668adf9cc53a91610dd04bd2a6b15a105d5b9d63a90f012061331d786c5f9f2e65b28dc34c87f66077aaf183189d261a739878891b0d42000001af41255e72b7762008ff731b1704c886dc54d587d6771411d4d60499e2b6176c0000013f9474168eaca54b4d736885e9169c891d9eba9bf22693dcfc7a87d29f2f206d011f07cccbe7288652b08cbca3184e3a4f7963e5c090597806e5b5bd156c84132001637dc44b8bb875ee3dc2796619ab3bdab974206e4dbc44435cb6bd734c47f31f0000000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1550000,"00000000b6414f92b836b1a6400976ea986216b28ab041ea038844d3c56683b7",
"012959a80b2361c3caf552ac681ac8ee53d4f7d7918d7efecf241b955cddffae06001501f854b0728fdb8b83e14988ae5c6f7a152bb8815e3b56b2ac44c07df8644f512e000000000001c5211557d874cad6c86442b2d6ab582ad40b504f50839684939d06fe0542793d0138f66e9b6eb361417a9004c6d7fff767873e697d12a1e16ffb5cd347327e650d015bf7b25e2d937ca6903efcb779bfb0a523bbf5d6498be86d07e80ef6207e331200000000000125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1560000,"0000000b6295fcf534ba222c7d68b5ee14445e2519a62c512cfd3010778d044a",
"0117edf3b6b6db9a9f728c1b084078006a7cddce36dbaff33e92ff2dac65576028001500000001acc1dbfa656f36048482a56c369676f2a3140e814d695d5d038bcd4b4c1138290119c41be4c3cc878a1c2f3c07f068b7950bed5b7b955275bb524c61778d42d326000113ff65779be1fa31c731c366a3cdaadf24d170d3c62fada99ed928ba923f0c4101613bbb7d09e97f0e05d5bb51d73a8738a9bc1d3661af063c8be2e9f67997f71c000123cbd4c96011968bd75db9dc8d5e14c0b4f603c96ea5e21f3e61474c63cc3c3b00013ecb9ab98402ef99e0b98cb4418dfaf5cb5fc1ededa798473166fd644888283d00000125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1570000,"000000069f9bdf236df00a42975f1ec785fb915e900e044352043c6ecb1cb34d",
"01f3261dc0617e334b65ca331389fed5133c038a0d202797d66f4c102d9c83d6650160e0aad042372518cad7c7188f11c40ae814b342c9bec000ebd3380f91a931631501b31de24776c40363ac58eba6ea6fcdbbc5a770686ef60224b1728a2210b7211d0001a52e639c975f9a6d08fed8bf695a1f63dd9a9f4531acd4623e3f1dc5463ff46c000000019060e603e69002f2d257840561b4590be7249a30f84acb2f8adebc5f5e7f3308000197e088d7bec23e30ef0df831c8cc1de48f7321332aa94fabe5e21f3ce9c95e4700000001143588a059963f5858e40572537e922ef3135194315e178b790d7cc163478f04000125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1580000,"0000000897f1f891389334c3cc14841378246bd7666232b52a1a755ac092f8a5",
"01558f9f250df9da58711bd03e0ab2062ca3762bf636d71ee75f58dd2bf6ff8b72010ebc561a6797e26de46d73ed389ae6250cd5909931658f048069c5321444a2411501cf113ab9087777a20b32e5893f9028688c9240529b8b3682e50990dd2979231e01bccf5a5f334a91d864b068ced192e6ba95b73449b0fc7f62b25a5dd451732325015329fb72c027eb29673788d24f576208d044c1d58abba320467d0cafc4c93a6f019a69d1b1ba2b5cd37e13b95455ee87bca4e597ac4504ce0edee56303a76db61c01b8ddfa8020797eaaf70800f906e41795b487cd28d8456a3b8f718510a507ef1101ed830c798394c87515d79a50e3e8beb1ba59582cd3ade27fe524803702b3b2620140f65f7ad86f0922fd84fb91f52f29c4b3ddbf2df719df160cff9a85ec8320010001669ff43d86a7362a42f7e848ec9c65ba43f37db16a97aba07947668e904f733701545a7f49f7e7d639d26b565cc102c766a09c48a35d99dc1dc0d81dbcb489244401298669f9aa43c8014d707636f2eb5b4c1f6ac7a7c0829264cb7f70bf2026a4100001143588a059963f5858e40572537e922ef3135194315e178b790d7cc163478f04000125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1590000,"0000000124680145c0bef5178b36aab191b7e59a456d7df83c4696eaaec17beb",
"013f1bfd7e56bf3101fb403f12025e5e13c3af5858ac85b243ccd535d344bb916401224ec528e8535ad9686cf4571d1ca110b709b9e8d607658c596daa687103d9691501c8bbb0f7d30c63d0731cefb57fbcc7cd3b39f5a5ff1356b139e674975a0d4a2801f25c13991e52dda8eb5bf251863e8c8bcd545e731e9a259de18ef46bfb50192c0152de9695535114914a9a6a0ac971c0bf15317fd669fdd3df002f4df87b179b4601e2791babe7064b61e73983b307e167e00fa4bb63a395717a294153c1f462223f00010d6dd5b68eb1774243ce7432c2ffaeb0d6cfce5a2ad4f0b8568585ec0b1bef4100016c9d83cefcaf3d27937ecb51187e6a59fb9f9aab956185be34e3df2efd4b5b1000013f77095217f3d96cded8612ffbbec3fca13ff07b415e3923e511bdb5c6cb3111017e3a84ece7c82421ec87669f0689ddcbb3eeb7f568c1f9ab9da210c437df562b000001fdb08d995349a8ef46813684149318c56e4eb13b8d45091b97615f19ab75b22e0125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1600000,"0000000e4dfb00523f8535e5bbc66401dc0aed33ffcba721c275ca0a35f2477b",
"0137a6ca552cb484e82240915c6ba673c54021f54167e3a70b11e58769cd5a464601257c3ec2a62d68f9acdcd7e879c8582e837882d6484f6cfe0785080a8afba04515016f657f1b16a1d111d877db63da5b90221dfbfa7c542fceb5a864c200483e9f0a01bda7c5a7d87ba7b6422f1e736d08f2b8281641f890e934e562d0ea2f6d34662b011502d741feb4ff5b96f43ec817d07d7e016be617ff7a7263631e106d4fd0dd2b000001c119905221927860a0d0397c5d0f9cf81c443d91c9ec47292ed651b4ab8fc004000001ca290b18e9523cc028abd61060174156bf559003c60ab42771f63f7f026a114101d792a7073b1c2189cd654259bdedc49002fb3b332347f40c6137a460e18ba60b0000019feca10203b8cf204fa30d003c3bdc58ceaa16acec7ea186965f72bc3f12636401fdb08d995349a8ef46813684149318c56e4eb13b8d45091b97615f19ab75b22e0125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1610000,"00000000318ea4aea52e334b9b3f8bf91e63fe485468936bba68485ae1095a46",
"0107fa598f71ceb0c4f0c27352c39ccb0d2f3db75177df7e2fb430789a8c61e52d001501fb18ccdf825ec2ee3c1b1e2faa627f2e2d5cddd951ef52180ae9cd665ae41a72015a1802b4732638b38b39be844f6bef3a3c2db76594a70f95983696401c1b72570000000001ecc07ae0469c4c027f81532b62e69b781d01d96cd14b0dc9b4bb815710598526000000013b3adf8da7f712d30195b7874e2d92fe9980f898fcf2c8e171023f6ce529ce3b01913b76ea8c0876d59e936d5247363a54dbf4e03bf4a9ac9f7cd2faabaf12b207019feca10203b8cf204fa30d003c3bdc58ceaa16acec7ea186965f72bc3f12636401fdb08d995349a8ef46813684149318c56e4eb13b8d45091b97615f19ab75b22e0125c334cd519e383870df442ed17c7472f684244fffd16f3b98210b6166dfde4400000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
(1620000,"000000048c6667a8724512cbd999bc491ec8522b1f3817001c7ba485dec46d10",
"01eb8a3b067a71d2e6686e825734082de9d83a0e95c9c263246addc16873547c68013e06723bb1d16eb6ec9877e1ad9cffcffc73699e9a2a9f88ad4f4b85ac3f0a681500014f6d4feac67da46c00c726a59ab226cfe1641b0dd6d19a87849f1b82d6838925000001bfaecc149cb1bb9d26b59b32c92bd73710003bfaeecee3da9c32c757954e0f38016a717ab8ceb7d7aa433073b2015bc8842f267aa66e2ccf620ae02b672a4eeb3201868aced3b0eeec75b2d06b6f97f035abeb83c8fb4657cea908d1e24707f89253000101990cb33ebc55fc786e97b85f0137bee6fc961f6c57f298cf6ed7c886269a5801e7178f4dde7b917f3ddd408bb11204ab309114a7d25c41530e93f328c98fcd5001afc9b45263c748c61fcf9b74c33073073a31a8fa20aad6215471d948344c50650000000001c2b480770a4ed4aa312595eba91cb25a5e4cb4dd368b51f0d639f866a23fe12e000001491139c6c100cdd9176607c63fa695709727d919634d2983a9412c3010ed6d3d01708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
),
];
find_checkpoint(height, checkpoints)

631
lib/src/lightwallet.rs

@ -53,9 +53,6 @@ use zcash_primitives::{
};
use crate::lightclient::{LightClientConfig};
mod data;
@ -65,7 +62,7 @@ mod address;
mod prover;
mod walletzkey;
use data::{BlockData, WalletTx, Utxo, SaplingNoteData, SpendableNote, OutgoingTxMetadata};
use data::{BlockData, WalletTx, Utxo, SaplingNoteData, SpendableNote, OutgoingTxMetadata, IncomingTxMetadata};
use extended_key::{KeyIndex, ExtendedPrivKey};
use walletzkey::{WalletZKey, WalletTKey, WalletZKeyType};
@ -102,7 +99,7 @@ impl ToBase58Check for [u8] {
payload.extend_from_slice(self);
payload.extend_from_slice(suffix);
let mut checksum = double_sha256(&payload);
let checksum = double_sha256(&payload);
payload.append(&mut checksum[..4].to_vec());
payload.to_base58()
}
@ -135,6 +132,7 @@ pub struct LightWallet {
// Transactions that are only in the mempool, but haven't been confirmed yet.
// This is not stored to disk.
pub mempool_txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
pub incoming_mempool_txs: Arc<RwLock<HashMap<TxId, Vec<WalletTx>>>>,
// The block at which this wallet was born. Rescans
// will start from here.
@ -199,7 +197,7 @@ impl LightWallet {
let zdustextfvk = ExtendedFullViewingKey::from(&zdustextsk);
let zdustaddress = zdustextfvk.default_address().unwrap().1;
(zdustaddress)
zdustaddress
}
pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool {
@ -258,6 +256,7 @@ impl LightWallet {
blocks: Arc::new(RwLock::new(vec![])),
txs: Arc::new(RwLock::new(HashMap::new())),
mempool_txs: Arc::new(RwLock::new(HashMap::new())),
incoming_mempool_txs: Arc::new(RwLock::new(HashMap::new())),
config: config.clone(),
birthday: latest_block,
total_scan_duration: Arc::new(RwLock::new(vec![Duration::new(0, 0)])),
@ -427,6 +426,7 @@ impl LightWallet {
blocks: Arc::new(RwLock::new(blocks)),
txs: Arc::new(RwLock::new(txs)),
mempool_txs: Arc::new(RwLock::new(HashMap::new())),
incoming_mempool_txs: Arc::new(RwLock::new(HashMap::new())),
config: config.clone(),
birthday,
total_scan_duration: Arc::new(RwLock::new(vec![Duration::new(0, 0)])),
@ -749,6 +749,7 @@ impl LightWallet {
self.blocks.write().unwrap().clear();
self.txs.write().unwrap().clear();
self.mempool_txs.write().unwrap().clear();
self.incoming_mempool_txs.write().unwrap().clear();
}
pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool {
@ -1048,27 +1049,36 @@ impl LightWallet {
}
pub fn zbalance(&self, addr: Option<String>) -> u64 {
self.txs.read().unwrap()
let unconfirmed_balance = self.unconfirmed_zbalance(addr.clone());
let confirmed_balance = self.txs.read().unwrap()
.values()
.map (|tx| {
.map(|tx| {
tx.notes.iter()
.filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr.clone() {
Some(a) => a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk
.into_payment_address(nd.diversifier, &JUBJUB).unwrap()
),
None => true
.filter(|nd| {
match addr.as_ref() {
Some(a) => *a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk
.into_payment_address(nd.diversifier, &JUBJUB).unwrap()
),
None => true
}
})
.map(|nd| {
if nd.spent.is_none() && nd.unconfirmed_spent.is_none() {
nd.note.value
} else {
0
}
})
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 })
.sum::<u64>()
})
.sum::<u64>() as u64
.sum::<u64>();
confirmed_balance + unconfirmed_balance
}
// Get all (unspent) utxos. Unconfirmed spent utxos are included
pub fn get_utxos(&self) -> Vec<Utxo> {
let txs = self.txs.read().unwrap();
@ -1109,8 +1119,8 @@ impl LightWallet {
.iter()
.filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none())
.filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr.clone() {
Some(a) => a == encode_payment_address(
match addr.as_ref() {
Some(a) => *a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk
.into_payment_address(nd.diversifier, &JUBJUB).unwrap()
@ -1144,8 +1154,8 @@ impl LightWallet {
self.have_spendingkey_for_extfvk(&nd.extfvk)
})
.filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr.clone() {
Some(a) => a == encode_payment_address(
match addr.as_ref() {
Some(a) => *a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk
.into_payment_address(nd.diversifier, &JUBJUB).unwrap()
@ -1162,6 +1172,30 @@ impl LightWallet {
.sum::<u64>() as u64
}
pub fn unconfirmed_zbalance(&self, addr: Option<String>) -> u64 {
self.incoming_mempool_txs.read().unwrap()
.values()
.flat_map(|txs| txs.iter())
.map(|tx| {
tx.incoming_metadata.iter()
.filter(|meta| {
match addr.as_ref() {
Some(a) => {
a == &meta.address
},
None => true
}
})
.map(|meta| {
meta.value
})
.sum::<u64>()
})
.sum::<u64>()
}
pub fn have_spendingkey_for_extfvk(&self, extfvk: &ExtendedFullViewingKey) -> bool {
match self.zkeys.read().unwrap().iter().find(|zk| zk.extfvk == *extfvk) {
None => false,
@ -1275,224 +1309,365 @@ impl LightWallet {
}
}
// Scan the full Tx and update memos for incoming shielded transactions.
pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) {
let mut total_transparent_spend: u64 = 0;
// Scan all the inputs to see if we spent any transparent funds in this tx
for vin in tx.vin.iter() {
// Find the txid in the list of utxos that we have.
let txid = TxId {0: vin.prevout.hash};
match self.txs.write().unwrap().get_mut(&txid) {
Some(wtx) => {
//println!("Looking for {}, {}", txid, vin.prevout.n);
// One of the tx outputs is a match
let spent_utxo = wtx.utxos.iter_mut()
.find(|u| u.txid == txid && u.output_index == (vin.prevout.n as u64));
match spent_utxo {
Some(su) => {
info!("Spent utxo from {} was spent in {}", txid, tx.txid());
su.spent = Some(tx.txid().clone());
su.unconfirmed_spent = None;
total_transparent_spend += su.value;
},
_ => {}
// Scan the full Tx and update memos for incoming shielded transactions.
pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) {
let mut total_transparent_spend: u64 = 0;
// Scan all the inputs to see if we spent any transparent funds in this tx
for vin in tx.vin.iter() {
// Find the txid in the list of utxos that we have.
let txid = TxId {0: vin.prevout.hash};
match self.txs.write().unwrap().get_mut(&txid) {
Some(wtx) => {
//println!("Looking for {}, {}", txid, vin.prevout.n);
// One of the tx outputs is a match
let spent_utxo = wtx.utxos.iter_mut()
.find(|u| u.txid == txid && u.output_index == (vin.prevout.n as u64));
match spent_utxo {
Some(su) => {
info!("Spent utxo from {} was spent in {}", txid, tx.txid());
su.spent = Some(tx.txid().clone());
su.unconfirmed_spent = None;
total_transparent_spend += su.value;
},
_ => {}
}
},
_ => {}
};
}
if total_transparent_spend > 0 {
// Update the WalletTx. Do it in a short scope because of the write lock.
let mut txs = self.txs.write().unwrap();
if !txs.contains_key(&tx.txid()) {
let tx_entry = WalletTx::new(height, datetime, &tx.txid());
txs.insert(tx.txid().clone(), tx_entry);
}
txs.get_mut(&tx.txid()).unwrap()
.total_transparent_value_spent = total_transparent_spend;
}
// Scan for t outputs
let all_taddresses = self.tkeys.read().unwrap().iter()
.map(|wtx| wtx.address.clone())
.map(|a| a.clone())
.collect::<Vec<_>>();
for address in all_taddresses {
for (n, vout) in tx.vout.iter().enumerate() {
match vout.script_pubkey.address() {
Some(TransparentAddress::PublicKey(hash)) => {
if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) {
// This is our address. Add this as an output to the txid
self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64);
// Ensure that we add any new HD addresses
self.ensure_hd_taddresses(&address);
}
},
_ => {}
};
}
}
if total_transparent_spend > 0 {
// Update the WalletTx. Do it in a short scope because of the write lock.
let mut txs = self.txs.write().unwrap();
if !txs.contains_key(&tx.txid()) {
let tx_entry = WalletTx::new(height, datetime, &tx.txid());
txs.insert(tx.txid().clone(), tx_entry);
}
{
let total_shielded_value_spent = self.txs.read().unwrap().get(&tx.txid()).map_or(0, |wtx| wtx.total_shielded_value_spent);
if total_transparent_spend + total_shielded_value_spent > 0 {
// We spent money in this Tx, so grab all the transparent outputs (except ours) and add them to the
// outgoing metadata
// Collect our t-addresses
let wallet_taddrs = self.tkeys.read().unwrap().iter()
.map(|wtx| wtx.address.clone())
.map(|a| a.clone())
.collect::<HashSet<String>>();
for vout in tx.vout.iter() {
let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address());
if taddr.is_some() && !wallet_taddrs.contains(&taddr.clone().unwrap()) {
let taddr = taddr.unwrap();
// Add it to outgoing metadata
let mut txs = self.txs.write().unwrap();
if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter()
.find(|om|
om.address == taddr && Amount::from_u64(om.value).unwrap() == vout.value)
.is_some() {
warn!("Duplicate outgoing metadata");
continue;
}
// Write the outgoing metadata
txs.get_mut(&tx.txid()).unwrap()
.outgoing_metadata
.push(OutgoingTxMetadata{
address: taddr,
value: vout.value.into(),
memo: Memo::default(),
});
}
}
txs.get_mut(&tx.txid()).unwrap()
.total_transparent_value_spent = total_transparent_spend;
}
// Scan for t outputs
let all_taddresses = self.tkeys.read().unwrap().iter()
.map(|wtx| wtx.address.clone())
.map(|a| a.clone())
.collect::<Vec<_>>();
for address in all_taddresses {
for (n, vout) in tx.vout.iter().enumerate() {
match vout.script_pubkey.address() {
Some(TransparentAddress::PublicKey(hash)) => {
if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) {
// This is our address. Add this as an output to the txid
self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64);
// Ensure that we add any new HD addresses
self.ensure_hd_taddresses(&address);
}
}
}
// Scan shielded sapling outputs to see if anyone of them is us, and if it is, extract the memo
for output in tx.shielded_outputs.iter() {
let ivks: Vec<_> = self.zkeys.read().unwrap().iter()
.map(|zk| zk.extfvk.fvk.vk.ivk()
).collect();
let cmu = output.cmu;
let ct = output.enc_ciphertext;
// Search all of our keys
for ivk in ivks {
let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap();
let (note, _to, memo) = match try_sapling_note_decryption(&ivk, &epk_prime, &cmu, &ct) {
Some(ret) => ret,
None => continue,
};
if memo.to_utf8().is_some() {
// info!("A sapling note was sent to wallet in {} that had a memo", tx.txid());
// Do it in a short scope because of the write lock.
let mut txs = self.txs.write().unwrap();
// Update memo if we have this Tx.
match txs.get_mut(&tx.txid())
.and_then(|t| {
t.notes.iter_mut().find(|nd| nd.note == note)
}) {
None => {
info!("No txid matched for incoming sapling funds while updating memo");
()
},
_ => {}
}
Some(nd) => {
nd.memo = Some(memo)
}
}
}
}
{
let total_shielded_value_spent = self.txs.read().unwrap().get(&tx.txid()).map_or(0, |wtx| wtx.total_shielded_value_spent);
if total_transparent_spend + total_shielded_value_spent > 0 {
// We spent money in this Tx, so grab all the transparent outputs (except ours) and add them to the
// outgoing metadata
// Collect our t-addresses
let wallet_taddrs = self.tkeys.read().unwrap().iter()
.map(|wtx| wtx.address.clone())
.map(|a| a.clone())
.collect::<HashSet<String>>();
for vout in tx.vout.iter() {
let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address());
if taddr.is_some() && !wallet_taddrs.contains(&taddr.clone().unwrap()) {
let taddr = taddr.unwrap();
// Add it to outgoing metadata
let mut txs = self.txs.write().unwrap();
if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter()
.find(|om|
om.address == taddr && Amount::from_u64(om.value).unwrap() == vout.value)
.is_some() {
warn!("Duplicate outgoing metadata");
// Also scan the output to see if it can be decoded with our OutgoingViewKey
// If it can, then we sent this transaction, so we should be able to get
// the memo and value for our records
// First, collect all our z addresses, to check for change
// Collect z addresses
let z_addresses = self.zkeys.read().unwrap().iter().map( |zk| {
encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress)
}).collect::<HashSet<String>>();
// Search all ovks that we have
let ovks: Vec<_> = self.zkeys.read().unwrap().iter()
.map(|zk| zk.extfvk.fvk.ovk.clone())
.collect();
for ovk in ovks {
match try_sapling_output_recovery(
&ovk,
&output.cv,
&output.cmu,
&output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(),
&output.enc_ciphertext,
&output.out_ciphertext) {
Some((note, payment_address, memo)) => {
let address = encode_payment_address(self.config.hrp_sapling_address(),
&payment_address);
// Check if this is change, and if it also doesn't have a memo, don't add
// to the outgoing metadata.
// If this is change (i.e., funds sent to ourself) AND has a memo, then
// presumably the users is writing a memo to themself, so we will add it to
// the outgoing metadata, even though it might be confusing in the UI, but hopefully
// the user can make sense of it.
if z_addresses.contains(&address) && memo.to_utf8().is_none() {
continue;
}
// Write the outgoing metadata
txs.get_mut(&tx.txid()).unwrap()
.outgoing_metadata
.push(OutgoingTxMetadata{
address: taddr,
value: vout.value.into(),
memo: Memo::default(),
});
}
}
}
// Update the WalletTx
// Do it in a short scope because of the write lock.
{
info!("A sapling output was sent in {}", tx.txid());
let mut txs = self.txs.write().unwrap();
if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter()
.find(|om| om.address == address && om.value == note.value && om.memo == memo)
.is_some() {
warn!("Duplicate outgoing metadata");
continue;
}
// Write the outgoing metadata
txs.get_mut(&tx.txid()).unwrap()
.outgoing_metadata
.push(OutgoingTxMetadata{
address, value: note.value, memo,
});
}
},
None => {}
};
}
}
// Mark this Tx as scanned
{
let mut txs = self.txs.write().unwrap();
match txs.get_mut(&tx.txid()) {
Some(wtx) => wtx.full_tx_scanned = true,
None => {},
};
}
}
// Scan shielded sapling outputs to see if anyone of them is us, and if it is, extract the memo
for output in tx.shielded_outputs.iter() {
let ivks: Vec<_> = self.zkeys.read().unwrap().iter()
.map(|zk| zk.extfvk.fvk.vk.ivk()
).collect();
let cmu = output.cmu;
let ct = output.enc_ciphertext;
pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64, mempool_transaction: bool) {
if tx.shielded_outputs.is_empty() {
error!("Something went wrong, there are no shielded outputs");
return;
}
// Search all of our keys
for ivk in ivks {
let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap();
for output in tx.shielded_outputs.iter() {
let ivks: Vec<_> = self.zkeys.read().unwrap().iter()
.map(|zk| zk.extfvk.fvk.vk.ivk())
.collect();
let cmu = output.cmu;
let ct = output.enc_ciphertext;
// Search all of our keys
for ivk in ivks {
let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap();
let (note, _to, memo) = match try_sapling_note_decryption(&ivk, &epk_prime, &cmu, &ct) {
Some(ret) => ret,
None => continue,
};
let (note, _to, memo) = match try_sapling_note_decryption(&ivk, &epk_prime, &cmu, &ct) {
Some(ret) => ret,
None => continue,
if mempool_transaction {
let mut incoming_mempool_txs = match self.incoming_mempool_txs.write() {
Ok(txs) => txs,
Err(e) => {
error!("Error acquiring write lock: {}", e);
return;
}
};
if memo.to_utf8().is_some() {
// info!("A sapling note was sent to wallet in {} that had a memo", tx.txid());
let addr = encode_payment_address(self.config.hrp_sapling_address(), &_to);
let amt = note.value;
let mut wtx = WalletTx::new(height, now() as u64, &tx.txid());
let formatted_memo = LightWallet::memo_str(&Some(memo.clone()));
let existing_txs = incoming_mempool_txs.entry(tx.txid())
.or_insert_with(Vec::new);
if formatted_memo.as_ref().map_or(false, |m| !m.is_empty()) {
// Check if a transaction with the exact same memo already exists
if existing_txs.iter().any(|tx| tx.incoming_metadata.iter().any(|meta| LightWallet::memo_str(&Some(meta.memo.clone())) == formatted_memo.as_ref().cloned())) {
// Transaction with this memo already exists, do nothing
return;
}
// Do it in a short scope because of the write lock.
let mut txs = self.txs.write().unwrap();
// Update memo if we have this Tx.
match txs.get_mut(&tx.txid())
.and_then(|t| {
t.notes.iter_mut().find(|nd| nd.note == note)
}) {
None => {
info!("No txid matched for incoming sapling funds while updating memo");
()
},
Some(nd) => {
nd.memo = Some(memo)
}
}
}
}
let position = if formatted_memo.as_ref().map_or(false, |m| m.starts_with('{')) {
1
} else {
existing_txs.iter()
.filter(|tx| !LightWallet::memo_str(&Some(tx.incoming_metadata.iter().last().unwrap().memo.clone())).as_ref().map_or(false, |m| m.starts_with('{')))
.count() as u64 + 2
};
let incoming_metadata = IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
};
wtx.incoming_metadata.push(incoming_metadata);
existing_txs.push(wtx);
let mut txs = match self.txs.write() {
Ok(t) => t,
Err(e) => {
error!("Error acquiring write lock: {}", e);
return;
}
};
if let Some(wtx) = txs.get_mut(&tx.txid()) {
wtx.incoming_metadata.push(IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
});
} else {
let mut new_wtx = WalletTx::new(height, now() as u64, &tx.txid());
new_wtx.incoming_metadata.push(IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
});
txs.insert(tx.txid(), new_wtx);
}
// Also scan the output to see if it can be decoded with our OutgoingViewKey
// If it can, then we sent this transaction, so we should be able to get
// the memo and value for our records
info!("Successfully added txid with memo");
} else {
let position = 0;
// Check if txid already exists in the hashmap
let txid_exists = match self.txs.read() {
Ok(t) => t.contains_key(&tx.txid()),
Err(e) => {
error!("Error acquiring read lock: {}", e);
return;
}
};
// First, collect all our z addresses, to check for change
// Collect z addresses
let z_addresses = self.zkeys.read().unwrap().iter().map( |zk| {
encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress)
}).collect::<HashSet<String>>();
if txid_exists {
// If txid already exists, do not process further
info!("Txid already exists, not adding");
return;
}
// Search all ovks that we have
let ovks: Vec<_> = self.zkeys.read().unwrap().iter()
.map(|zk| zk.extfvk.fvk.ovk.clone())
.collect();
let incoming_metadata = IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
};
wtx.incoming_metadata.push(incoming_metadata);
existing_txs.push(wtx);
let mut txs = match self.txs.write() {
Ok(t) => t,
Err(e) => {
error!("Error acquiring write lock: {}", e);
return;
}
};
if let Some(wtx) = txs.get_mut(&tx.txid()) {
wtx.incoming_metadata.push(IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
});
} else {
let mut new_wtx = WalletTx::new(height, now() as u64, &tx.txid());
new_wtx.incoming_metadata.push(IncomingTxMetadata {
address: addr.clone(),
value: amt,
memo: memo.clone(),
incoming_mempool: true,
position: position,
});
txs.insert(tx.txid(), new_wtx);
}
for ovk in ovks {
match try_sapling_output_recovery(
&ovk,
&output.cv,
&output.cmu,
&output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(),
&output.enc_ciphertext,
&output.out_ciphertext) {
Some((note, payment_address, memo)) => {
let address = encode_payment_address(self.config.hrp_sapling_address(),
&payment_address);
// Check if this is change, and if it also doesn't have a memo, don't add
// to the outgoing metadata.
// If this is change (i.e., funds sent to ourself) AND has a memo, then
// presumably the users is writing a memo to themself, so we will add it to
// the outgoing metadata, even though it might be confusing in the UI, but hopefully
// the user can make sense of it.
if z_addresses.contains(&address) && memo.to_utf8().is_none() {
continue;
}
info!("Successfully added txid");
}
} else {
info!("Not a mempool transaction");
}
// Update the WalletTx
// Do it in a short scope because of the write lock.
{
info!("A sapling output was sent in {}", tx.txid());
let mut txs = self.txs.write().unwrap();
if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter()
.find(|om| om.address == address && om.value == note.value && om.memo == memo)
.is_some() {
warn!("Duplicate outgoing metadata");
continue;
}
// Write the outgoing metadata
txs.get_mut(&tx.txid()).unwrap()
.outgoing_metadata
.push(OutgoingTxMetadata{
address, value: note.value, memo,
});
}
},
None => {}
// Mark this Tx as scanned
{
let mut txs = self.txs.write().unwrap();
match txs.get_mut(&tx.txid()) {
Some(wtx) => wtx.full_tx_scanned = true,
None => {},
};
}
}
// Mark this Tx as scanned
{
let mut txs = self.txs.write().unwrap();
match txs.get_mut(&tx.txid()) {
Some(wtx) => wtx.full_tx_scanned = true,
None => {},
};
}
}
}
// Invalidate all blocks including and after "at_height".
// Returns the number of blocks invalidated
@ -1981,6 +2156,7 @@ impl LightWallet {
{
// Cleanup mempool tx after adding a block, to remove all txns that got mined
self.cleanup_mempool();
self.cleanup_incoming_mempool();
}
// Print info about the block every 10,000 blocks
@ -1999,7 +2175,7 @@ impl LightWallet {
consensus_branch_id: u32,
spend_params: &[u8],
output_params: &[u8],
transparent_only: bool,
_transparent_only: bool,
tos: Vec<(&str, u64, Option<String>)>,
broadcast_fn: F
) -> Result<(String, Vec<u8>), String>
@ -2311,6 +2487,27 @@ impl LightWallet {
});
}
}
pub fn cleanup_incoming_mempool(&self) {
const DEFAULT_TX_EXPIRY_DELTA: i32 = 20;
let current_height = self.blocks.read().unwrap().last().map(|b| b.height).unwrap_or(0);
{
// Remove all expired Txns
self.incoming_mempool_txs.write().unwrap().retain(|_, wtxs| {
wtxs.retain(|wtx| current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA));
!wtxs.is_empty() // Behalte den Eintrag nur, wenn nicht alle Transaktionen abgelaufen sind
});
}
{
// Remove all txns where the txid is added to the wallet directly
self.incoming_mempool_txs.write().unwrap().retain(|txid, _| {
self.txs.read().unwrap().get(txid).is_none()
});
}
}
}
#[cfg(test)]

50
lib/src/lightwallet/data.rs

@ -349,6 +349,49 @@ impl OutgoingTxMetadata {
memo,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
// Strings are written as len + utf8
writer.write_u64::<LittleEndian>(self.address.as_bytes().len() as u64)?;
writer.write_all(self.address.as_bytes())?;
writer.write_u64::<LittleEndian>(self.value)?;
writer.write_all(self.memo.as_bytes())
}
}
#[derive(Debug)]
pub struct IncomingTxMetadata {
pub address: String,
pub value : u64,
pub memo : Memo,
pub incoming_mempool: bool,
pub position: u64,
}
impl IncomingTxMetadata {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let address_len = reader.read_u64::<LittleEndian>()?;
let mut address_bytes = vec![0; address_len as usize];
reader.read_exact(&mut address_bytes)?;
let address = String::from_utf8(address_bytes).unwrap();
let value = reader.read_u64::<LittleEndian>()?;
let incoming_mempool = true;
let position = 0;
let mut memo_bytes = [0u8; 512];
reader.read_exact(&mut memo_bytes)?;
let memo = Memo::from_bytes(&memo_bytes).unwrap();
Ok(IncomingTxMetadata{
address,
value,
memo,
incoming_mempool,
position,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
// Strings are written as len + utf8
@ -389,6 +432,8 @@ pub struct WalletTx {
// All outgoing sapling sends to addresses outside this wallet
pub outgoing_metadata: Vec<OutgoingTxMetadata>,
pub incoming_metadata: Vec<IncomingTxMetadata>,
// Whether this TxID was downloaded from the server and scanned for Memos
pub full_tx_scanned: bool,
}
@ -408,6 +453,7 @@ impl WalletTx {
total_shielded_value_spent: 0,
total_transparent_value_spent: 0,
outgoing_metadata: vec![],
incoming_metadata: vec![],
full_tx_scanned: false,
}
}
@ -438,6 +484,8 @@ impl WalletTx {
// Outgoing metadata was only added in version 2
let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?;
let incoming_metadata = Vector::read(&mut reader, |r| IncomingTxMetadata::read(r))?;
let full_tx_scanned = reader.read_u8()? > 0;
Ok(WalletTx{
@ -449,6 +497,7 @@ impl WalletTx {
total_shielded_value_spent,
total_transparent_value_spent,
outgoing_metadata,
incoming_metadata,
full_tx_scanned
})
}
@ -470,6 +519,7 @@ impl WalletTx {
// Write the outgoing metadata
Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?;
Vector::write(&mut writer, &self.incoming_metadata, |w, om| om.write(w))?;
writer.write_u8(if self.full_tx_scanned {1} else {0})?;

50
util/build.sh

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Copyright 2021-2022 The Hush Developers
# Distributed under the GPLv3 software license, see the accompanying
# file LICENSE or https://www.gnu.org/licenses/gpl-3.0.en.html
# Purpose: Script to build Hush silentdragonlite on x86 64-bit arch
## Usage: ./util/build.sh
# Check if rustc is installed on system and exits if it is not
if ! [ -x "$(command -v rustc)" ]; then
echo 'Error: rustc is not installed. Install it and try again.' >&2
exit 1
fi
# Check if cargo is installed on system and exits if it is not
if ! [ -x "$(command -v cargo)" ]; then
echo 'Error: cargo is not installed. Install it and try again.' >&2
exit 1
fi
# Check if rustfmt is installed on system and exits if it is not
if ! [ -x "$(command -v rustfmt)" ]; then
echo 'Error: rustfmt is not installed. Install it and try again.' >&2
exit 1
fi
echo ""
echo "Welcome to the Hush magic folks..."
echo ""
echo " #### ##### # #### # # ##### # # # # ##### ##### # # # ###### "
echo "# # # # # # # # # # # # # # # # # # ## ## # "
echo " #### # # # ##### # # # ##### # # # # # # # # # ## # ##### "
echo " # # # # # # # # # # # # # # # # # # # # "
echo "# # # # # # # # # # # # # # # # # # # # # # "
echo " #### ##### ###### #### ###### # ##### #### # ###### ##### # # # # ###### "
# now to compiling...
echo ""
echo "You have the requirements installed, so let's build!"
cargo build --release
# check if compile was success
if [ $? -ne 0 ]; then
echo ""
echo 'Error: Something went wrong and it did not build successfully... Please reach out if you need support.' >&2
exit 1
fi
echo ""
echo "Hush silentdragonlite-cli is now compiled for you. Enjoy and reach out if you need support."
echo "For options, run ./silentdragonlite --help"
Loading…
Cancel
Save