aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/blockchain_db.cpp570
-rw-r--r--src/blockchain_db/blockchain_db.h35
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp16
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h4
-rw-r--r--src/blockchain_db/testdb.h2
-rw-r--r--src/cryptonote_basic/account.cpp11
-rw-r--r--src/cryptonote_basic/account.h1
-rw-r--r--src/cryptonote_config.h2
-rw-r--r--src/device/log.hpp2
-rw-r--r--src/multisig/multisig_account.h45
-rw-r--r--src/multisig/multisig_account_kex_impl.cpp175
-rw-r--r--src/simplewallet/simplewallet.cpp207
-rw-r--r--src/simplewallet/simplewallet.h1
-rw-r--r--src/wallet/api/wallet.cpp229
-rw-r--r--src/wallet/api/wallet.h13
-rw-r--r--src/wallet/api/wallet2_api.h51
-rw-r--r--src/wallet/wallet2.cpp1239
-rw-r--r--src/wallet/wallet2.h181
-rw-r--r--src/wallet/wallet_errors.h39
-rw-r--r--src/wallet/wallet_rpc_server.cpp203
-rw-r--r--src/wallet/wallet_rpc_server.h8
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h93
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h2
23 files changed, 2283 insertions, 846 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp
index ddfb22ac2..894eb15c7 100644
--- a/src/blockchain_db/blockchain_db.cpp
+++ b/src/blockchain_db/blockchain_db.cpp
@@ -449,579 +449,33 @@ void BlockchainDB::fixup()
// There was a bug that would cause key images for transactions without
// any outputs to not be added to the spent key image set. There are two
- // instances of such transactions, in blocks 202612 and 685498.
- // The key images below are those from the inputs in those transactions.
+ // instances of such transactions on mainnet, in blocks 202612 and 685498.
// On testnet, there are no such transactions
// See commit 533acc30eda7792c802ea8b6417917fa99b8bc2b for the fix
+ // Since its been 8 years since the fix was implemented, we can safely force
+ // nodes to re-sync if the bug is present in their chain.
static const char * const mainnet_genesis_hex = "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3";
crypto::hash mainnet_genesis_hash;
epee::string_tools::hex_to_pod(mainnet_genesis_hex, mainnet_genesis_hash );
- set_batch_transactions(true);
- batch_start();
if (get_block_hash_from_height(0) == mainnet_genesis_hash)
{
- // block 202612 (511 key images in 511 transactions)
- static const char * const key_images_202612[] =
- {
- "51fc647fb27439fbb3672197d2068e4110391edf80d822f58607bd5757cba7f3",
- "d8cf1c1bd41f13c4553186e130e6e2c1cd80135ddb418f350088926997a95ca9",
- "95d2556c8acd1457dce7bfd9c83b1d82b821a55a9c9588b04b7b5cf562a65949",
- "4b5d987fee1bb563a162d23e41741ad73560c003e26a09b6655f09496538daac",
- "1d25ea86323d1578579d3894a54b99ea1c3e2dca547c6726c44aef67db958b02",
- "92e46fb70be5d9df39ca83c4fc6ae26c594118314bb75502a9c9752a781d0b33",
- "924d0cb9060d429be7e59d164a0f80a4dabc3607d44401b26fb93e7182ab435d",
- "f63e4a23fec860fd4c3734623891330ac1ff5af251e83a0e6247287818b8a72f",
- "5b14c5ef13738d015619b61dacefc2ade3660d25b35ef96330a8f4e2afc26526",
- "d5016b012a2fb6ca23fd56ece544d847962264b4aee15efe1465805fd824a8fb",
- "0a7f3da1d9dd341cd96829e484b07163099763ac7bd60603b7ee14f7dbcb278d",
- "d716c03d7447d2b693f6f61b6ad36bd57344033fc1a11feaf60d569f40014530",
- "23154a812e99ce226f6a87087e0812f419aed51289f1c0c359b0b61303b53a36",
- "03940341e1a99d5b0c68eacfdf5a20df90d7d0a3d6089d39709cdd964490755c",
- "ef09648814cfe071f5d8e9cfde57247ad09409265c4b6c84697bbb046809bd7e",
- "8843ec52b0496ca4e895813cfe00bb18ea777d3618e9bd2e200287e888e2f5c7",
- "8558bf39baf3df62b5d33cdf97163a365e6c44f4d6deef920730b4982b66449f",
- "504d9380ce581de0af97d5800d5ca9e61d78df368907151ab1e567eb6445332a",
- "673797763593c23b3ee07b43bd8760365e2c251a8a60a275528ff34a477110cc",
- "25178c95e4d402c58d79c160d2c52dd3c45db2c78e6aaa8d24d35c64f19d4957",
- "407c3a05dcc8bdcb0446b5d562cf05b4536fc7337344765215130d5f1e7ee906",
- "4e7fa771a5455d8ee8295f01181a360cdc6467cc185c2834c7daf9fbf85b6f1d",
- "6beb64cb024f9c5c988f942177fc9c1ec5ecfa85b7db0f13a17f9f98e8e46fe7",
- "6406bfc4e486e64c889ea15577d66e5835c65c6a39ec081af8ac5acdc153b4b5",
- "1b1da638709f9f85898af70ffaa5b88d5a4c9f2663ca92113c400ab25caf553c",
- "da49407a9e1ed27abd28076a647177157c42517e2542e9b6a4921fdadf4e8742",
- "3c3fdba2a792fddaeb033605163991a09933e8f05c6c934d718e50a613b64d69",
- "82c60429171173739fa67c4807cab359620299e6ed2a9da80139b5b1e23c5456",
- "0a19e5767e1381ac16f57cfa5aabd8463551d19f069f7f3465c11a8583253f3e",
- "d0fae6ffdd4818399eae6224978f170f696150eaf699f2f218abc00c68530f96",
- "0937889aeb3af5c64608d9a9f88229a068e53417448f00f9aa5e28c570cca8f8",
- "d6072d269753020912524961ce8c6930cf35abe5c4b2bdc7fd678338d20a68fb",
- "0e8bc9b06fcc842bdaa7df029bfd1f252d5defc014d58a006c10ab678ecf543f",
- "9d42f90520f02c9258be53b123d69ddbce5f33f92a903d3fb4cf3358ff0e07b5",
- "1cc05416b12cbe719617158773c3e6173435fc64e1ee44310dc696baecaeaa95",
- "266b15222913c11ef6403ee15dc42c8c0e16bc5fa2f49110447802236e045797",
- "791b123af3b71ac9497a010610f72207ff8ec642969b5cf0d2891b21e7eee562",
- "946d4d7b084dc32495f22b35fc30394144c8e0ba04f3ad6e2c2bfb0173a2266d",
- "2c015cb990c1583228d08b2d5de9227b950c3f57364fc1033bca5c0fbfd08c58",
- "13fdc41862fd85507f579c69accb9cc6a40f5971bfa41e3caff598a3dcffd2fc",
- "64b06d9874a83917c583c9439d1c736083377d67fda2961b623f7124663134c3",
- "2fa49cd19e0aa02989991a4c3760f44be800fe8fb4d58b23aca382e10dc0d2d6",
- "377628f265f799772e9fb6065be8b6eee200c329f729fe36c25ee179e4d20df9",
- "ba94fa79134ce383b6a98b04dc6ad3d1b950e410d50a292bc770f9685e59fe91",
- "875c924329f0733e31fe8d8aed70dc1906335b8a9984932b6368ea24edb39765",
- "f31f4abb3f5ee42a5aae86d70b3bd9a9c1934933641893864dd333f89719d608",
- "2bcd629e125514a780f568d3c2e8b12a2e7fbbee06e652bbeed3e7825508e31c",
- "918b43581163ca1963de21bb9ac401756e75c3f00ac8dcfafc139f1ad5d7d998",
- "5730dd57fa52749a0d6502b11c9d802ac495875542431310c674a65655b7c2a3",
- "03f84b990683e569e2f6143bb963a2a8de411e7c4b7923117b94c7afcb4b43ea",
- "b298c8510d35bd2be0ff0753ad7d98d480f4c6490bb67fb93cd4632ea726e8a7",
- "0a771afbf9be104c01b89eaeb57073297d35ac8fbbcc0816820fdb9a29d26625",
- "713d90d6ca1be1a4e05a5f8441dc528c699caa09eda49c09072f5f8291354c2e",
- "988827f45c19330d9404309f63d536a447803cca7cb182ef005b074def09ab7d",
- "9dcaa105b4def895f3faee704c250bdc924316f153cb972f3fb565beec0b7942",
- "1c06c30afe65b59e9e22d6bb454e4209a03efe53cdbf27b3945d5d75b1b90427",
- "49e08c13d1da209ec1aea7b7fbe0daa648e30febeb2aa5ffbaaabdd71a278ac2",
- "e1c2e49ab7b829854c46a64772ded35459908e0f563edbcf5c612913b7767046",
- "e08bb7d133490ca85a6325d46807170cd07618b6a5f6e1d069e44890cc366fab",
- "5c73ca0691cde2f35b7933d6db33f0b642ec70d0bd3f2b0ebbd97754ca67e248",
- "6404399151872a521dae767311712225dba65d810ba2feba209204221b5d772d",
- "4a0c3aa6cef36f44edf08ad8fb1533d7e1186e317da8a3afb3d81af072043233",
- "104b3e1af37cf10b663a7ec8452ea882082018c4d5be4cd49e7f532e2fea64e5",
- "e723a46bf9684b4476c3005eb5c26511c58b7eb3c708ddf7470ee30a40834b32",
- "18e6f0fa3aa779a73ceefabea27bff3202003fd2c558ec5f5d07920528947d57",
- "c97e73eb593ff39e63307220796cc64974c0c8adac860a2559ab47c49bc0c860",
- "13c363a962955b00db6d5a68b8307cd900ae9202d9b2deb357b8d433545244ac",
- "76a488865151fab977d3639bac6cba4ba9b52aa17d28ac3580775ed0bff393e4",
- "a14de587c9f4cd50bb470ecffd10026de97b9b5e327168a0a8906891d39d4319",
- "b1d38ee1c4ca8ae2754a719706e6f71865e8c512310061b8d26438fedf78707e",
- "772bb8a3f74be96fa84be5fa8f9a8ef355e2df54869c2e8ae6ad2bf54ed5057e",
- "3083a7011da36be63e3f7cacd70ab52e364dd58783302f1cb07535a66b5735f5",
- "2b1d892e3002aa3201deb4ffe28c0c43b75b8f30c96b5d43f6d5829049ecbd94",
- "cb738aabe44c6fb17ade284bf27db0169e309bf8cf9c91c4e4e62856619a4c64",
- "1707e04b792f4953f460f217b9bb94c84cef60736a749fb01277cfe0eaaa48c7",
- "ab8b6bac9b8a4f00b78acb4bd50ed2758e0fa100964b6f298d2a943eb2af2b30",
- "dd317193fef72490f3be01293b29e9c2f94eda10824a76ca74bf39dd7cb40ab2",
- "4fb3d995b087af7517fcb79e71f43bac0c4fbda64d89417a40ca4a708f2e8bc1",
- "549ba38c31bf926b2cb7e8f7f15d15df6388dce477a3aff0245caa44606849fc",
- "7585c14ab9abbffb89d0fa9f00c78ecae9f7c9062e5d4f1fae8453b3951fc60b",
- "953f855323f72461b7167e3df0f4fd746a06f5a7f98aa42acdce2eef822a0b2f",
- "0931932d57dde94dcfb017179a5a0954b7d671422149738260a365ca44f50eb8",
- "a3d179d16a4a275a3bb0f260cee9284db243abad637a9dbe92d02940f1c7ee8c",
- "959843f1e76ff0785dafe312c2ea66380fdc32b9d6180920f05f874c74599a80",
- "fbc36b3e1718fe6c338968b04caa01a7adb315d206abc63e56768d69e008a65d",
- "f054de7eac5e2ea48072e7fb4db93594c5f5d2dfa0afe8266042b6adc80dfdca",
- "39dfc68dc6ba8c457b2995562c867cef2f2cf994e8d6776a6b20869e25053f70",
- "19ad7ca7629758c22ac83643605c8a32a6665bae8e35dbc9b4ad90343756ebb3",
- "e89e80ea5c64cf7840f614f26e35a12c9c3091fa873e63b298835d9eda31a9ea",
- "572c1b9a83c947f62331b83009cc2ec9e62eab7260b49929388e6500c45cd917",
- "df0b21f679e6c0bf97f7b874e9f07c93c3467b092f3d9e1484e5646fda6eca5f",
- "8f3b7c0f4b403af62fe83d3cfac3f1e2572af8afa4cea3f3e2e04291efe84cf6",
- "aae8b8db243009d021d8c9897d52ee8125a17212f0a8b85f681ad8950ae45f0e",
- "3d45a4957d27447dea83d9ae2ef392a3a86619bfcf8dda2db405a7b304997797",
- "a5b0a619a8e3030b691bdba1ed951cd54e4bc2063602eae26d9791fb18e60301",
- "14650df217dd64a2905cd058114e761502dff37d40e80789200bc53af29b265f",
- "fd6a245ab5e4e6e18d7ba9b37478ce38248f0ab864e5511d2208ae3d25017e5f",
- "fbe0be6dd42a11feb5db5ae56fcbbac41041ab04a35f1df075580e960c8eeab0",
- "72f3f1213d9bec92ba9705b447d99cd0a6a446e37a3c1c50bb8ece1090bfe56e",
- "20df836554e1534f62b2a6df9ce58e11c1b9b4746ce8ee3c462300a8c01f5e76",
- "5c3d2a81b7331c86420ad32b6e9a212b73b1c3413724a0f91bf073eba18e2f1f",
- "63264ddfb29cd36fc18f3ee6614c4101ba7229bc5ac375f912590d3f0df982f4",
- "5ec4eb637761c1c9dbc6aa6649d4410508ef8d25d61ad6caa40c6ee3236d5515",
- "270c70940536017915e1cdbc003de7279ec1c94cba1ef6130f4236f7e306e4f0",
- "c1d1d57a7c03f6ddeeab5230a4910db8355e2143f473dea6e1d57c2f8d882b76",
- "218c030a7fdc9917e9f87e2921e258d34d7740a68b5bee48a392b8a2acf1f347",
- "ac47861c01c89ea64abee14cf6e1f317859ed56b69ae66377dc63e6575b7b1eb",
- "23bf549c8a03f9870983c8098e974308ec362354b0dcf636c242a88f24fc2718",
- "a3ce8b817e5212c851c6b95e693849a396c79b0d04b2a554de9b78933fbea2b7",
- "7310120c1cc1961b0d3fce13743c8a7075ae426fe6cccaf83600f24cee106236",
- "8fa0630f193777dcc4f5eccd1ad9ceacf80acdf65e52e4e01bf3a2b2fdd0dac6",
- "4a5f5c87f67d573d0673f01abaebc26eaa62e6d04627588549cc9e6c142dc994",
- "78971cccacc645116f9d380c167f955d54b386a22af112233f7de004fc0c8316",
- "badc67216868e1de1bbe044bf0e6070e6ee0353d05c13fa0c43b1897db5219a2",
- "c45b2a168bc51cbb615a79f97432cc4bb6b104da9cdc1fc640c930657452f71b",
- "c17eda13541d14554c5db542155b08b6bf9cb403d425745b662ebc2b2b9b3a3b",
- "313210cd9d2efc1603f07859bae7bd5fb5914f4a631b943f2f6ff5927a4e681a",
- "6ee94ec8af4e6828f9b46c590ea55da640ef50e810a247d3e8cdf4b91c42d2c2",
- "505b7a4d9f1ba6577aa2a941843f86c205b23b1ea21035925e587022e2f0aeed",
- "98e6a7cd687e8192e300a8202999ec31ad57bc34f656f2ae90d148607ff6d29f",
- "1be5db002c0a446cc2c1da363e5d08ae045cd8f5e76c8cccd65d5166393c0bdf",
- "17c02ac6d390c5c735e1e873c40294220e89071fca08a5da396a131fa1ba8670",
- "2540507c39ae6fdcd90de826077f0ca390da126166a25c15c048a60606a27367",
- "5ab9328e525c7a017ef4f591d995ad4595d74cbf8ff4112af33b08c70661a304",
- "9c105587a96c51d81422f64e46016564d22329760648c95dcac7218f3733f915",
- "525afb1b94a75f1edc2b55c700071e14a2166acda003370047c30dba8ea80278",
- "745d4a5d9f95ca4efa6261b6bcd4ecacd504b5b901a2ce1353c522a5c0c15dcc",
- "5a5a568cd87ba34252ba254e6a320e1a7f52f13e7451bb887efb34ff881785f2",
- "1ec50a80198cd830b51f4f7a0222015a268d9b40f04e7f838c7b8dc7abf63b01",
- "68836b662d79349cb42f5cef54e6a066157d398cc87d3b13f29fc04e5cf364a5",
- "658db317f355a9cbd86f673775cac0c308fe14967428fd283a36e300a6a53b2f",
- "677d79a8c467dd9db38f0ef45c2787dd368f701a6b47bf7a5f06112c38da643e",
- "2baa455d4066f5d628f9ecd315cb57deca71069db5d0d112ae0aa18a84b6f0d7",
- "5e7b0889f351560081360ac2b1429b48b2f7d886227f144e3b198e2f1fa56ed9",
- "c3d317fbf26e15add8b4f8f93df9de9b22297b8e4945ebab9ee249d4f72f4e45",
- "3c0b705a5c1e31abc7e46d8ff3c148a033f6875454cfb67f8d2a2b9a57a5ba7e",
- "a0ab74663561af2adc2d38be3569fbe7aa2454346416ac96e5eb26b1e01b1e2f",
- "53526cffdb74327670566c1dacacffb7d30a43a7f1862ff8bab87737bfa5edb6",
- "24c5d36ab98d88f87b2c71afb4ea8562e05c7aa0b50f3bc0f9ed50a4cd52989b",
- "c3ce4de5f94dff65d11e33a865855a4404259cf45263914c884f79db4f35169d",
- "f1009b6dcf30030cff872d636fb96ed233eb6ecb8ffed003c7da64e4f5a02f4c",
- "e3729f58614d3b42450d1599d863983ab7e3e5c29fb57aad7958c8923a2627c4",
- "31cf4792f7b5ce01b217ec80184edd2a7c49c0b21701f5494ee2c9bac67c28ca",
- "b42a5c9c92a656c5bb2b759ce160fdfd245243aeb1786338faea63b62e9a60ce",
- "a1efc8d5d0855933d5ac8fe5960c7acacb92fcb09bfbc929e5002f168251e648",
- "c4322c7f3682ec94b0dcb42f13b498c36cf575d505aacc8ec8bf67a6a2abf4c9",
- "684ee5aa3c98357aeaddcc30c6620939b52aeef729e24b4a46ccafc44f24d831",
- "36180f2ae11d105e0efbfbddb94e6b45b08609a383e4e1a8fa3b06d7a8051de9",
- "96c2d183eacc87581a0b17b8d07878bc10d436728510715109a7565d9972f8b5",
- "3068c9d04d561c7e29e3f621280b61a61885de0e9ec35a66a3116ca7a9e09627",
- "2eb94b9673ad6f8f88288fddfceae4baaeccb37bed88a35611d826ba06a5363b",
- "fc8cd5fae8b81121001f7767dcd5f185c0fdcc88cce1fbb184ddbcfad697ba54",
- "51521da1ecedea6d588d774eb155d936b32a14913c2f11d989bcc5116e65bf41",
- "3d23542e597a83dd6307700d79058b920f281c65f832333734d8a0adec510495",
- "11d2e01913ff0d4bd21970d709d88e63289492c0bbad7bff99c0d36858a841ca",
- "de674f1eee3068d2bc8c2f2897d8556e5deb872869652f7d3a4e5dbc6f1063c8",
- "e722d7f728526110c0921791b417afde4af1e87ae48ccc01911786197843104b",
- "aaba3a4e2a7d20ab76edfbcccefc27acfd509db3638582c28230e73ffd71d340",
- "1385a7209dafb9622dd4274179832e40c7fae19445383c34ef79adb0e4de0c98",
- "108408531fca288d74de4a2c596eab8569e355d9ab2f8380f4d24073a6b3fa95",
- "294476a86fcd39351ae452cdb8af9584067ec4501ec6182d0062bb154559fed3",
- "e64b175e0284c5cb69c8db46344ed43b5ced8abfe3cbf0c197103cfd116944cd",
- "cdd73a0f1fa7c14ed2177ae2163035333718847e49dd5cca6775bd20fc7553ad",
- "d423d2a374bc66a4587a5e3affa327ca75b8116051320759a3b88a868a7b80d4",
- "f13ad1e5b1315557d5497b58516eb3b0759d909725ddd0eb8a0dee439c6c0a48",
- "3a600b547a6061186a54e491344fd50cc7d4f0566a386a40aba6545254773728",
- "37a6f3f221fe17cc04a65aa544d5135e8297ecaf0853ba784dffacb74feb481b",
- "0ca42d67d0f84b28861d63e919e6ce5ad527447fdc53a03d8497a0241bee9376",
- "c7dbda42459e6fadb92c416eaef3e04674fc57915a93f3be4e656634c9654075",
- "0e34d728ae4fe347a5afecdf886fbd4d48a65c5d0dfab807da6ae95b6b2d7a3a",
- "f1bc69257ed510db5b2ed370010b037f452a29c49e36337264b3011ce2516216",
- "33f98f6b8a8e202463955998fba3b790199daa893e5471554826cfd9daa5c02f",
- "f8a0a37a2c9ebd7022d7cded1ee0318fd363020070b4cdaea800e44dcc1300d2",
- "6862714daedb908a4d86a3a3f1e65ec2c29ae61501b4ddcaf184243dd095d71b",
- "555cd19a6d21941c1174129b8bbcc70edcf0d6874262ce9e1e542351990d523d",
- "2cd6b44326828f23a2aa33699754bfa072c4883f39d53616f6a6b74149b664b6",
- "127f45d2eacb565c21c1030fe8054fd0a3a75705bc368924712aa145d414fa47",
- "19225e2dae6e1166f21cdab1290194470ded09df9b66f3faad3c1cc9ebcf340f",
- "b7b3f53f0539b2b4837b8bb9dae0ccbd200c8d36126d9f50199d68a4293a46d3",
- "6b6323be01f27d6d759d9670825e8ebb9c4cd8016351702328df91cef36cfec8",
- "020c31cfdfc5b22b10235745b89b311d271cf82f2ba16d03fdf7a8bc8538694b",
- "62573218530182b79e40d0113b7d281dace6da33bfcd0f9318558de5e5c76f08",
- "37d928416b15982f5bb8be40c5b62fae0b664e412c25891f8860c4242927e610",
- "b07ad11134a5c0542d2b418ef3863e8ea8477de68d9310681818ddd40825fdb0",
- "4af77cb76bab845b56470c95ce7b8cd84ce49a095984c1f3eed67b0ee344316e",
- "e3fdd4668d8726ba6adc401ac662c0cf6b5c1872082c488ed7da966d425fb1c0",
- "3dec71c81c7e78e879abc8da8b30e2446edbe98eeb8df9dafe9201ebb4c6a834",
- "7105467d9c5e855f1362fbddf820ed5e757997862efc9000317d3830a2f60ef3",
- "2821df94b021d3e77e8d9c0f3972340210f5ea2c0935cbf125cfc578d4d6722f",
- "114e5807accc337a22598bded30ebf3e0cfd75877e239f10cb043f829c315ab5",
- "d658a1c0354628cd7312593ab25d5b9083de8f0def6e8425f188101d256cd136",
- "4818d3be9b2a38fcc8c85d6c46f69b502943f79cf2462dfb0b6499e761bcc836",
- "92b8c943cb017b5f2d39264640c069f1ecced1d3ce9b3fd755d6df2fddb99458",
- "6edbd0fdf064fcbccd4a9e7a8ea520b87cb7faf867f7fe8a5f53625beb575119",
- "bf3b49c477dafb06af65bf09851c0fbef9dbc3152a7268d31b55a8e7a9a95860",
- "0e234dbadfda1393be2f068182615dbb83736f84f87710b5c7965bdce9f4a26a",
- "df5ceae34429e47b92bbd5505ba27666552e0eb619997f359c55699c3252b1ff",
- "08c1c7d940d699a91a83249bd578772e8958ffe23179e6150f07b96f1b47ce1e",
- "6f919a429270da0022d70062537bdc1b21c43a8abc552d8e366647e5b719d310",
- "63c66e5fd5d27f6fda87912ce46fa91a5e5b3634ed147fa2986330fc2696d3fa",
- "bde070b75296bca3aa494e7f549cd2bd1ff003776712bc98a3164b139b2054ab",
- "66694196dac5b60cf5e0ae05db8f3894fe04d65910686806551f471a0a0472e9",
- "0d2e97524b7ce4cf30b54e61b2689df036d099c53d42e2977b1671834bac39e7",
- "e081af76e923455f408127862be5c9baf7de6b19b952aa2a1da997d4dfe594c0",
- "121bf6ae1596983b703d62fecf60ea7dd3c3909acf1e0911652e7dadb420ed12",
- "a25e7b17464df71cd84ad08b17c5268520923bc33fe78c21b756f17353ea39a0",
- "e985f078fb44dbfdf3f4f34388f0f233a4e413e02297ee9a7dcc3fcceacd44f9",
- "b9184cf45e6e6b112cd863b1719de1bcab2137eb957e8028edca3a204a9ebb96",
- "157d177d5e4bcce0040eb4bddb681eacf9e2942e1c542a57ce851b4742a9cc4f",
- "0823e06635c9a1a69fd8833d1e48df98d711c1c47c06f27bb384932db1bbe9ee",
- "8beeec1fd1bcdecba235b449cc49abca69b6486ed1c0861a2bfb6a43c970b86f",
- "349f61a1cfc9112e537522858a0edae732a2f8434cf4780d3d2ec1e96f581cca",
- "587cdf72b5914d364f7e214a70481cf1131ee4a09e6b43e52428d2e56b000c13",
- "a6aa0c179316534c7b9ffb5f42a2af98d1d3a166bfb413199579f259c7b5e6df",
- "f9f3bb1ba8da5899b79186008ecfbd416b49f3b86d94045b91e34a40e41d5cff",
- "0cdda65a60b7b4d94e794c9397e01f69fb29309ce4fac83e7392dbba6bc497f9",
- "8fa5fce5ad09d43af7218ea5724cff2c4849a59ff73caf3bbca466e3c8538ba8",
- "8874ef46008753fcc0b77eb7a4a8560e35966bf5a12bcad4203ad2b4c1f8bfbe",
- "a8ee9a3aa2d0c08a951439ffb0e6d10315fc4776997b275de1ec19663e88c2c2",
- "9c184cbbff464ab4d5f6bfa78c39bf0880fb93b1574139306a97acb940d415c9",
- "5493a38c255c91ca49b958ed417f6c57e5bc444779d8f536f290596a31bc63d3",
- "3e1e82b96cc599d9fc55ae74330692ccbfb538a4cc923975fd8876efe4b81552",
- "16aaaf820c24c2726e349b0e49bbab72ca6eef7b3a2835de88d0cececa4da684",
- "7fa52ba349f7203c3dbc2249f9881101a3318d21869dd59f17abf953d234db65",
- "713d8018bb9ba3ab55c3a110120b9d7593514111075ef05f0fdb233ff2ceddc8",
- "56063afb495759a0942f1c33f28a4fb8320c6d376cb3c9513249453e45f24a04",
- "f9a6bacd9e055749b45174ecf3c3db18b78f3474761948a68adf601f54e59660",
- "7ddd7c6d41572f93fe07c0300c34e455b6d1f4372204933bf45035241c8b060c",
- "f81021b893a36b201de71330a2ea050b59dbf7560c62fa9cbea9261ab47a0ba2",
- "a01fbe4114c18fd534ae1621404d25c08e3b6775a2631ff40279bafd8c9304f4",
- "350fad8ebc938c6eb508a1483f385f577794a002bc1229db70a1b0131d174b9d",
- "570cb8bce87f532c5051d8c4c864012408e672a7d492669e660251fb1e066bec",
- "8cb6efbb129c84eba26d894c4293b476a6e9a1fe969c6ad18b554d2a57885f36",
- "f384a98467bf7f084ca31bea121a4ec76e530f523d3225c21ed25a18544a9916",
- "da127ab58ce557c2c95c20d6a291c2e5d880fff09dc28927b7bdfec97b995d72",
- "a4d95b4f74366ec920d0a0c5d81265688cc18733ffc444cac9b01ae2431568aa",
- "5ae2a71470570733422468bb733d53c85b1c8a6e7e6df5c05941556bcf342d1a",
- "65a2d161ff0e095d3afe37584dbbe649f1e9cd440755b5a3c5b2a252d5c0b8bc",
- "25ef70a8e41bb422ed7996a41160294e33238d6af17a532232f0a50b123431a2",
- "f1f0f76ee901664a65b97104296babb9c7422370e99bb677ae07c2ee420e7f40",
- "c3c66dda180b0330e75c8139b9f315a8c6b937f55d87d7be42e172bbac60d71e",
- "5881786695a9e58e19d79f790c7d9243a847c0269c5770bdd01f5149c2a62a88",
- "f2f816d3c8ebc7df06ab68d39223181aacc7be04364d1d4e69a56c33949bb983",
- "80a1c3b6b2778d4846ad9fe0bb2dd5afd99aa897f8231bfaac45fde43d602d9f",
- "72ad67cb043aa5df0c3dcc2464953a66893259d81c9cc7778c12bca3447fbd58",
- "ad72420a7963b8d4536d8eba00b4b989992247cd8c01660e242a8e71edaf0e19",
- "999d603d1cf6068e3bb6abe1bca976fa0ab84c4660b29ea8973de8b5cf5fd283",
- "e137a5910f02a764c3a3d8f1579ac0c7e3cc34e58933216868efe010332c1e6e",
- "10e0fa2362f59317626ae989bd1f962c583339d8d74d76c3e585243d152b91e8",
- "1951c652704962f5c6e33a4d4aadfee5d53ce2253644d1ed542da3e278524a07",
- "c938bccb7ba6c0217d8ba35ed91502aee051c8ae5bff05e88aab3b322aec936f",
- "4d6386c689785edd5beb55911a3a9fc8914838c8192184199589beef8b6ddf9f",
- "26f6f45a6468bc4b1f085fd28d63c264ee17564f9e247fc03ee179a0b579dcda",
- "235b7bb82b72c61acd5979ca5f2ca740aee805a780ba22e11aae5cd34f6ec760",
- "c027ffb585a1e4844b4907490b621b08c7e40a5e6f93e97bd4bb1b615bba9908",
- "aa77fc8053d139b998577319de29457b78b1cc8b35a5f3526c0621eaa42ce6e8",
- "afd0af9a11c5ae2a7c4a4571ce39ad57d8df70ef146ed83ad8eaff97b2387fb8",
- "a1f8fee9f1da9a2b306489d00edf754187b55e97f4fe6f249432fe6c7f44d6be",
- "4f12e8a123465a862060efb656299e6bef732e5954b02194308817b243e84d32",
- "6a1ca62f7d6952ad2eba1c64035260319baf03deabf856ca860744fc886b3d3a",
- "c72dd1fe890d6e4c1f7325a4f224e85aef6cdca8bf9441d228efaf126e02ba63",
- "2f6ddcea18d891ef4252e657989de68adcc43c2175d49c0c059c5e49b9dd5aed",
- "24efac0f240ed183c30398ee40307623f52113598f66c5864c90fc62643a2aec",
- "6ba3ebc935e7cf7fbb446e7f5c12b19c4455e6894412b0eedee4fc945e429e9a",
- "3519d6e5bc9649f97d07a07ef5471a553ffce35c7619f4f63e91a2ba38cbb729",
- "65e073df352fa9917e5c2475167e6c523b68c1406e1b6e81109e2d4cc87c740d",
- "d73bf816c3648a7d53d34be938c454e515efb0c974d5a225f33635540b2db90d",
- "bce167790fc86a273db011757d09e2d1148521ce242c2ded58f26cc49993aacb",
- "2d4286ed4039916f29602e86f47ea4c5b05998c0198509ca7799fcadfb337e8d",
- "9837c495b1af4f76b09177514a0f3e1dceb509c52b91712f3c7d29dc1b91d09b",
- "5c848b8291f39759903ce77f151acf40f3ab5afa2d4a45af62b320371c29a697",
- "b92df5016ee947ce6a21365d3361977f7f2f6c14025a983c44e13d3e8cc72152",
- "71d2f57222a39b1a7ed2df5e6fb23a964439b5a8e7d49b49d87e5cd5354baa75",
- "88b44d0198fb15b0c20a97f87e021c744606bfd35eae2874f35c447aa4ac3cd4",
- "29bb4c2557714119cd684da2867e689e37e3ca9c912db83ab84746816f6092ab",
- "b1836d98a288752675b133b9018fa1edf174f311921d01926c1e1a5900c21029",
- "a00645e090c7d96f3155ffbcfc41e526a763b0f53a98151ac4a7d4a5b14066b6",
- "78aab09919d17773b0d62799b37bd2e2970f72f5d1eb6472489c367b6135374f",
- "eb6123aeb28608f1c97b2bf62ef69f977cd0658a0ab491deebb1e972caa938c5",
- "8dd7ef1650b1b30cdf7814ae4d61a237eb0acc3ec3ce0f72b1c25780329c2d7d",
- "b1998419be3172858b990eea84fe10bb24b76c219cde277cb4305955fc7e0b65",
- "1b10560016c4bc506eef9056dedc2943a17179081e6eaf85b48d37dc20eac3cc",
- "1fb1d9d4d408a6734234910f554d272739a0d6fa401918d79b57be62c3f23ba2",
- "dec878f54ce36788289b61d32de0d9539032aba22cd15522752f729659c7cc5c",
- "fdbfd0773f5a66637b093dabf812197940d1134619a7e60a931b19915b7dab0a",
- "21bd2c9aae052a1c187947d2964f2be4afa7b30248409c41a75522e88a4e7977",
- "59326adab03416ec1d36699c18e5e4fa13ca1f2212d48c23bfdecb0be7263616",
- "bcf263d39457c0aef8ef42fd98f8131198ec4fb203155dd5bcd759e499a9ca5c",
- "f1ad083bcd8c7630eef93336d8a971ae8ae37166a6a00ac39684420c5f9afef8",
- "d82ee2ac41b36e3c1787a01835bf054070486dc20f6565efedbbc37cd3bf5fa5",
- "eba91a0dcbd3986299b0a3e66c116f77bd3421829291fd769522f01da26f107b",
- "11016558b7e8c6386c6a3e862691dcba47e33e257f0e7df38901ea7c0eba894c",
- "04f02795e34a0030e5186c8e700da8a46b1aa6bc0abed6b35c9c1cd6a73776b9",
- "2628dc8ad7fb731d59456b2755a99c6701467125fa69816c21bfccabc31edf6b",
- "9b7ca249ee5b45cd264492f30df09f708a7d9caed7beb9a5c6292f22e4c31f85",
- "5c42e7caedf382092faaf392174792b3cf5f2fe29cb586387ee55611af0617c9",
- "373f2fd5940a01feb79659c8b9099477f6d3e7b570ebb749c2ac336ea4be583d",
- "fea22887147adc3a659a14902080b03e86b4b8b16985fdf0bbacaed00d812422",
- "6a3e51a1443cff62af9fa12fafc8ea78ae74dac7792c9ae0f436f570ab33eb71",
- "796be21e213d6d0cd6fbe2de1555fb73d1cf9edc041a9f1ff1ad583c4ca92460",
- "03fcbcb31d3fd17f0eedb45ac5a51678c7c8b2b8498225d05f74e2892f017f72",
- "d28da07c6c22daf9ae987b3033c33d3140e5a56fa1ffd7dc5c7853d55a45bcc7",
- "fbb0ce02f50018741a12fc08eea80a18067d7bb0fcd96153d40bb8c808473aae",
- "2bf7c05a0209b4ea31314f04bd754cd01c58102d7cde8c496c655b6494924354",
- "1968a9e6e14ae86a1e02e6078fc4631851fce5dbac6aa34f860defd1ccfd0ded",
- "d886181329c9e06462a1407f547d77b38ff2c868b86d8976aa478e1cbb3d66d4",
- "0d465e02ff2f8eb0b5fb2fa9a38579c5d66337d4a16b58f8ed28d2d89fc02392",
- "3196419015289807880ef24b6781734822d654dc260c0560d00bac65eacd5219",
- "fa08390ddc333a2a12248d5ec3e51fff9b782227069fe5a0afbd8eba967ae8d1",
- "49ae36a791cb84516688d59a1ed3e5112851d65f265078aa2d433b45fa984c8a",
- "35daa428e12c59da6730760979aca3444d8b31149c6febd99fbfefa4b2372082",
- "5ef1d697beba612ff31d1dc139817c313a4e2ad3977775943b635c141ef0f2a1",
- "674256037ec00edb66b9355fb1d33a30a47a5d1f4bce2dd5475d89f1ea6502db",
- "7b6f017bc550933af91eec32a79464f540c5e0c208703e175843ee4e9ffc0a50",
- "bf0eb91da1d18dbb18fd9ff36c837387887ba142961176a44889718b2becb9dc",
- "3e5ac43a05164b074a2ff6353e9882917c5a3dbe168c2695de895f7decf1a56c",
- "35e8f004965347c2b18a000a83dd3a8084b8a4bf00522061ed1179aa1107c413",
- "fccb0fff3a24e555ec96f702ec11d420338910d148fc7b039d67568ad3a0e032",
- "5cab231048032dbf921b4fafa1951dd2da93bc3740667f31d5a1d5665b141261",
- "ffedb24be73441fbcd069f7785ebb865870e0f3ed228190732e4ffd5962bb82d",
- "a4fcfec18adf92f4ed822f64d2da9f5ae630885a1bfa626530f641db99aa7a30",
- "f98bcee41b0e3deafa1efaa1863750dbfd9bd7430b82529b670867d852230b5d",
- "8ab8d5fca047a52364a737c1af57bf656c9ad5049f08ef4c5aa252e61aa72123",
- "91318b39ad94c1d58143586b6d90dd6092a9d7487e321f4976967b6ac445ff43",
- "fabfbd4569ab018e12d5ffa9b1a40ca8eb2ca60a685817351d90eaa770d5eccb",
- "bbc5ef34428d980e2401942ceecfe07cdf21bfb1acae0596ea1d43fccf504f69",
- "26943e4201ea407a5667103fe07ca6e08ef76940f274349b0e2e776bcfb0acb6",
- "e3b305ffe33e72841f8e2a8688cc5cc27d42aee7624b33b7b6399b42db392437",
- "17c5a763dd57e6bcc7c4cf2db0eb5cf3e97116b67fe0dd519c97e4a4d55d5a62",
- "bbd260216879ce86af8318ffcf73c9e063ca76dd8bc35d3b6be45b2b4184888b",
- "41285591d0595bc42ab663051b410d51af39fe1720592e27acb1a8af72360a76",
- "f29acd6068ce494d0c0fe294cad91bb8968e3fff3f595a113227ab545c3ca3e6",
- "ec9013c6394528e7dd788ce7cc085ca79fcdfbb37565999d5b4b5a4e39452ecc",
- "27829bd7f1a8fcddcad0cc34a3b3fc67d62a2f3e09f8e75d35035c2281e83afd",
- "666bea9db4e15087204d076294d221d4cf5864f5d94de38f29132b1934a17ace",
- "a3a30924cad3dbda3446e5a6324e0a1390c70f795d5ecfe17ee5c70b14f7d87d",
- "19567fe5fdb10711d60aa4d9843e1c49c2a6d2fd1b5cf662e2105606bb7815d3",
- "b139f1c3a2f15596b9320334e37e4184d5d584c4a81e72d821a7edcad3aa62d5",
- "08f1531e0e3e8f8bae313b2c60a72d5601bf8b60d7a4d2f60e8481650340d250",
- "c5895669e1ff182bf1dd6c00dc955265e08ded0952b8ca62a1c63ba11c99f4ca",
- "84d1c28153f66c1a4eb5fa0df695e936d83294df31a08d8d8e2d4798d19d8ce0",
- "b8699f6af853fdbe897848feb46a05d733363f304eac4c8c1932e6ea4bc062cb",
- "10eb3f6c1d0661468d9ed83593e5e9c0b43c6feec6a5405a498194905ea6ed48",
- "509e215a600d9cadcbf5d62632ba321d7314764218db00ce8c907e425fccc445",
- "e62119b7be84c8eaad41ba7f4a35da403f4ed96b967a3134e89ee8b78f8792c2",
- "f790754a95d59ea5ffe6b9b5cc386c600a9e19e8bec997c192764365f1d05376",
- "990121b5aa4d6badfb7154db4cdbb4512124bc2f442bebac71ea90b5cc86f203",
- "b6983dedaa891eb14c964d84461e5cd37ed27b61771c64978ba83e3ecea194fa",
- "00fba1ceaa6aa1e378cd5b22a771d6070250ac37f4e17d7bf1a70b3139e9a860",
- "429854e7738abf2ecf46909454039e2fc5a080eb9a3c0c5ea13b07426dac3ad9",
- "ceb3e017944b0dd787be219d8629930b3f2e20e22b12dc88fd838488ebb896f3",
- "eb9e5d14424c63e675fe771b73ca865f7d38cf334d65e2426e12a0b88c1a2236",
- "556ee713449e6e59ac4b8b2e8310180c8f6280484e9db23456526cceb9216168",
- "bc89c3aa889e0144ac526a1f861227430dde7e439cc6a7e9b25c9a049c3ca7b3",
- "56d070c62ea99be66fff741a8e45fafda5f9ff783e84d5395b251f356ce4e16f",
- "ace15859c399e5ecd13b1420d3c3703c6a88dfb4a084f7225e7ba40a4b444fc8",
- "f03f1261ab6eb879fe9c5b0028cd400b3ffdfac4344e4c75f6cde3c05ded1f26",
- "955b2fda8d0068226f89270028b316b5adac871f1c1c85435479aba14a381b0f",
- "422509a98d7461a6b8ec87cbb173b2486577b59ea9b269e01c20567b38b3b3b2",
- "007d4de62ad89a4f5985f0cd9b76a7293acf383b4e9e734e813b9df1d57f986f",
- "13a04e32948225b7e22aa0137341ebbb811e0743032fac16be9d691a652db2eb",
- "8244b11d880a52f9f9e1830a822e6eeeaf0b12fc759f8261bc2f490cb0794f3b",
- "27d3415f8f8fd3048a1ee0d9487256dd1b0f9e31be663778efa5b3add63868ec",
- "0053f888db916a8905320e253fe2f0666355e6fb6de36f897231920a3edfe39f",
- "0bc5c0a2ea47fa3bb8be107e3b9d6b7226b1c8bd16ca1bab8f51b8e1de08aa8b",
- "4ca13aaa161c79025b5cd6c9a8ac33554f5ceb479fe293d9a342c865cd9c9948",
- "333afbe82e2a3df38bd1ef998f39c5feef2554697aa21b5410c0e95ef9156249",
- "587c4fcabd18ff890064171fce3d5be0c4aa1bba46893fb6a827a85ab54d20f3",
- "964328e4d51d67c4e2f1fd102a66b861d98199f81d18d660b1b4b52504cd772a",
- "196aad5594651efd679d30b9feb0f0d172cf977b4f89aa670ec741a8bf21e669",
- "9137bfd66bbf47bfa0bfcbb9f6e71b6eb3fd9776530e9fd30d3dab19e182f35d",
- "8217392c4ed2313188f295d94148a027a7f43824a5f5fba33a0b8c1045d509b4",
- "be9e12761519a4836e73015544163254877e1c4912fcea67a10e7026421dde75",
- "7b5220421a520b876cc6cdba0d3895104d7fac914dca5b93f9fe8b226566b71e",
- "5c83fccfeb4bf0eb8a94d43ebc84a81639a32f26c7ef77d0a2b626b7de7befdb",
- "132fd6c92cf176f975efdb5ded53470b462a48a2815c6f54a93ce4f935784cc7",
- "46a3dba364022d11aa616a2bc20e3be5c4390f38b9446edfa464d90d9af5d48f",
- "34b3f3fd8a83905a37762060f51d0b116377b4820b031b8e668e16f46c5b0285",
- "f0e397e033dabec859a4b9a9043c5f1fb0dba504764d6bcf2fe9bf2ffd318474",
- "85ecf59c7dd3b24ad17f591bc4737f32f1384c370a7a6f2add06a811dc824b6c",
- "4d03cdb1e6ad8e066a83654626d8c221433e8d4fd901c671300af37e000177f2",
- "61cb9c651893e6401b25f2bdf79c9f3ddc9ffe69cf6c791c420462bd73e347e1",
- "85f2686a42158cd5ad327781ecccd1bdcd346941dd4b4edc45f717de6a011800",
- "92de2ab82cac528e6d4ccd61e5b7c79591dcad9909c8ad3c8276ece6d48c0f08",
- "23a878a06bb94bff33083349149f3c860f2b00bc3fb28f04cbaf717c08af19a3",
- "1b1cce18ff0323566b192885d7ced30f9a9531a2580240f2c593a7d5b8580974",
- "08fcdec7ea1376d84f3b13a47a4b73c7781c9c7890bb28f712b58af4fd3f24fe",
- "03cc08fc4ece807c6495272c412be23b045622cc6b786ed8d5c94156ae678a0e",
- "c4d8a61dc3f5dcf4b83f27a90cbc37e816cf4754e12309626ec5679c99087c46",
- "b29d00681e29001cdd63c4bc50e5e25715faef692aeebb678c8050e1c095e888",
- "ac154617e93f2bb1afa232675f2135437a9cc9700c14c51c40084946596ba11a",
- "ce9549de8e68ae89f424dd9e1cde8a4eea2069da667cfcfbe837691d37366668",
- "426c45a98e2af35cc9708149f6c086ff5a3972e77d62c627d5e20de5d731cad8",
- "7e21bfe240a3d9b77a129c734a1d428dbc890379fbaf862853f48b2f7470b2b0",
- "fa090a71f77223a7210de6db18d9aa809e89fb15253aea28131df6c5a7639140",
- "7094ad044c5ab025e088b43aa0b947601fabe58ed700a412fd96e4b917ced0c8",
- "936d5cdc4f081b6fe36c356af4378d472cd7990303f2ea44da645afd7d5d7f9c",
- "05342037d3b69349dce7b95529d4b2a63ceb9d9393217a68f7cc8c958a96c3ea",
- "ff9e1c414ef27b1178b1de296526f50520b7ddb06286bf9c47792bfb449e40b6",
- "2f2b7bedb34d2854b17ccb702cddd8bc0157e39721d58be0b2ad54ee291fc9f1",
- "0d8db1f34140bbf7eb809137018a74af08cb3345b8a3e368cdda8521dab45791",
- "b109e4bfabcfe4a1c38be1156d9ca851c75e6aa2e57c0869e40cd9056f571e07",
- "5cb363547ca077c806fc69bc8c2006831ab89e72fd778ac1a48fa810934e350e",
- "85ee928bb110fd64eae54a91fc8548883e7fc4c60a3c61b505c31cea2d295c86",
- "1ec3df7d10ee6fd5f0532ad4fe771e6befc28b0bed0250bf523695d6d49a8246",
- "de9db2fc07c866bd7b885fb41522b63d550d0ce2e8ac5e14464a41733c2319e6",
- "9a27136422a8f56768db29ba172a7ba26c3c7aa910324e78e5ab3a3268ac3674",
- "60213c315119bf9005cb533d1a5b403b4a13c59982fe7773d30fdd8f519f4205",
- "40eb61ef1812eb8a4d389599bf449fc86653b2c4986061b952f46fc049de53f4",
- "658ef0d8140162b5f04591be13b47456245f531208bbca3260b857ca09b803c5",
- "02270fa66255048d724894e2206b4e773cc6a7b6d17ca090cdc25f317d5f53b9",
- "2ec6a0147f419161f7198d05be5f93152d9ccc10672db0ea47ff1687c0f0dd15",
- "4be1d8ceb96eb80ef7ce30079ded31163272aeccff5c18fe3aaa32ea2f5bed9d",
- "04ecaf48f44de87243b17b4c71ebff00020738639336010fa57435a54b623798",
- "e313a9feb7cfd1d56ec87b1f1062ff9a80da498f7b761af4bef0cecd1b4c385b",
- "ede3748f971f22341f7f5844dc60fc03cdb30c7cc720ebe13ae588c17a78aa94",
- "d90c0faa70e39b7c0a8c55457ed6e6478a4e4bf3707b08104326a1ee8377c3ab",
- "c79ffd0bbc8d004cc542e212990df6498abddd3deb50fd00ed00a2ff690974d6",
- "35c37d88cf73a89c4124b0ee537347c37fdb47156c8b0ecc509efc58236ed3f0",
- "a99182f343ccf05e557ffa6df71f03688b2afcf314c59daa774fe78db6f47add",
- "01115397a78af8a4ae2727ca7a01843235b626bd3db80888d3dfd0020d4135e2",
- "4a55aced578470d2f7280096d7fe8095f294095fba4778d1977d6db9270472f0",
- "4624adf8a5633f65b213b8ca46b55cb0ee36c41495f39b1ae70cbd545779b1a0",
- "d72bcd5b57a9c47e7bd5e9a1103657d10beb7b6c6d41f2b2985bc3bd3cc74860",
- "48baadb9a46293c92f29e7617846171356a42c3b5d18d49a05a7e173993785f7",
- "3da927737af8cb0e1c77097e35c54158d18aabfb3051c45bcc7ddfe00b157b1f",
- "b4a24bfdb2cb802c8d48a3a18fbfe18622a767fe7eddfca57d4555550ccd1643",
- "c58f82ac7c49dcba1721a88358f07636c9df60d3fd383e5789b808dc57a1dc9d",
- "5e1f756eff5155df073d30f4452bdafc4adaf4f35960771bf2c1e30137fd7a79",
- "be4a332f289338d67bd4834eae3128c488a61d255e972da484b6252b67a46b89",
- "d496e4a36238d03a83d8b45cf33d9388aa7568a279b034d1cdd87b457356cc5b",
- "a1c5212730ccda34de393210e276bbd44720dda777bcfd602315a3eee582f7dc",
- "08914ec63f6ef7fe1d678937dc0f6178883440b26b4aca29fce79068947e8397",
- "49e2cd2bd9b974074d9814f93eed371620bd4ea5fbf97a625065704e8fb382d7",
- "047c194111818b48ce93a4b006e4a09b9a2650757a87357111796e11e847bf23",
- "5955b0baa8265341f35a6f24fdc79066ba3ca9c5354c69b6b37a9ef3a26be556",
- "d7c962f3ea1938c5267cca4072548acf3afcce4d438ed62027caa211a5f98e8b",
- "c8cfba67cb4ce7e291b35154a50476d9a5c6bbb5d6cbaaa5d2408547fea7b02b",
- "de8a940d8a69a64ce264d2ab7320662aef2e391c587cb2aae22a86718d5dffbb",
- "94176f1310b26e54d4de48f87b74aa0b60532f184a2268508dece86dd7f85d36",
- "9ce6ee3fca56c9256a69df404782301300a6e5e7f5a25a1f6d68c0e9e42584c9",
- "7c423c4a220c6ff43ab6432f92b166323c58ee77f8c096ba0b00d52d7bd507e8",
- "0b62b9c1ae4d4988720e8d41d980b334458189de0a3dd01699338d7b07c3894e",
- "64f45f6f75110624506c53716f2fc1d5fe5f88f82a5bc6a7459ce70eae56dbb3",
- "12ffbeb8e52fed161af4d8a015d1a5c45dcb8240e5c8933ce3a88ba2c58f97e8",
- "2ee6b7b96043c8ded9fb52f87cdd0d0580ca6f8cef183c8a656394a11c0aa393",
- "aaef26b1f5726258bf9ce305a3e54bca65cf68779f90f9d24287245c27362e27",
- "ef59588dce57c35d010bea4d209f44c62f0b7c7e65bc0226c0e4971934da9435",
- "0e606c2f6f8dcd579faf4739312bd7327ac7796fa44a81780fe0d66fc7761fb9",
- "2f307198afdbde5f95989a17e06ce1bb9ff36c441cf3b2248431534fe13bb9fe",
- "51418e6df23d450aaacc74ef2df53a6b1693727b70bda9ebc43acfc23d8fb5ea",
- "6e9e3ac46705ed80520695b924435b00d2b3079598bf7faca7fd1524be777e5e",
- "1e96241e2876aad29ce64f5d7e7fbb8db7265449df816c0d30a96633778c5cb6",
- "81788f00eb72696d811f946e65d2c96528c45590874a1defdd46651e9b79a3a1",
- "d9aa5a9f1df50e933d7105a5d72b5fe96bfbb9fd4b5b0eaa5e80af12e72d497a",
- "e1b6976a732d27fc5d6a96b6d3d0d1d5eaa6ec46bae4665f17e7a43aefc75280",
- "9151c75edcec1cc90aa2d2c240ff657b0eef3f5f1ec37418c8854b2493114f1d",
- "3e7c12d0132421f08ea0a390cfa325e422a6b35120fb2eb650f108a165237934",
- "1ee0e85c7d8a91089c03f37318cdc9127026bf789e3ce4b75046eaa3eebd3458",
- "4ae64a3ef66cad847409ce175bd5365c9097fd21647a05730ac6b45841add3c8",
- "f0ea0f334cf1d64678a6dab08c07e2f94f339e8389bd17ebc882b5c8b736cbf2",
- "da904db96060546ff69e28993ce8183766da9402ac10fae9fc1f1d67ebd83c90",
- "db11820615f7b5e47778c45d2e083e77f49b608b587dc09ec26f077aba07a242",
- "ff5c726a83bd785484de75bb03b421f9e8e382bf2740120a2fcf72326aa01c75",
- "f8643a7efd6304980db323303ab73a6fd4f4ee1047520d39d571580395b97f21",
- "8facf9737d07838aedf6030593bfb247d8c29fe8d9b18b2913408627a4424d7e",
- "f0672964aa6e4c7dd4768e18827023787386927f4db89fd661444979afb43c18",
- "6fd3649c8401f2704ed2be18518b870eb6bf2b9a6689d1b336f05bd8b49017f6",
- "ed172dac7de827493e0c0fdd8d3299333acb678e72ed499e0224b389cc1e0fba",
- "7f9a8a8cc8e34add11934a1a5882be5978a6d28405cb0a053ec5699a502b1cef",
- "6ca829ebd2a0a40994f68c1db7978ec274b45c46e9b351df869a2bfe0630bbd4",
- "0bbb017c437573a55db88258a9d9a01188bdd23bb6b26903b137814871661f47",
- "9a6358d2541f46b6d05b80fe25a2cb025fbe9e4b227a6275908d5ee31c948569",
- "a75d26c6d4ce944024f10e6d23e8b5b888d680120e15dc0e4fee8d8833ee0c6a",
- "1d43d33556699b42c124b46e41243abf48727fe488428056fdd174a3861c1e3c",
- "7b5bd3fecbaa093f005c4f806ea67846c0df6b04df7729925cb14724f6a8b582",
- "6bdf2b54f2f5ab90191261d33dee80fede75896994016422b28db8ba62327d82",
- "1a16181b250085a91ccecc118473fa2ab98515e894a7b63b347c24b5be560c7a",
- "22d24891755910b48ad632358c26245bdcc375abc41f7e2c9fb3c7773dbf4e22",
- "5b70c5d4a373d541619c944fcc3b61259550b0e9fba3eca16f0879e5845b43b9",
- "b78f9098c9d76987b7409e63426a8d49972bb4e75289576c680cf96513d44b6d",
- "916b53b8e85eb7e0a2a76d6fc8d2163430e7183ccb103d6705f54af4bb070907",
- "dc3d78f43110d2aa9df83c5485ec33663ad5452b8cdeb1aeeac9d6b1487fb781",
- "3975539ed5402cb9f5ab503584524dd141cc4296b666ec66d807f94f62b1c026",
- "bd1f97fd89183643423073f22733880616456ca41960699f18e868cb9ac35508",
- "90b468ff0f83460c3dd8cfe778c39d32c6bb1eeff9ac5de7804a4050d3b8073e",
- "0e886d49d88b82c9f8dbdd2f38a535992f35ab16629724d746394db3898235fa",
- "76c22e965242d1ca5614e829d9028dcad9c4b09393bcbdd318b0365557335fb9",
- "59b168488ec8629f820a1efd8fc5a0c2adec4253e61d6a2945a68a9a43be9035",
- "ff172b42854eaf2865caa985d2fb6283c5ab19574126623ffd615a761a5bce72",
- "cb46ac9ccc024ee74c96e3cf1c13a6949a432e855dfa881b6a307c0e6daac59e",
- "9971574924c0f413bf4c0f96bb9c2fbdfea8f475e33a8fb6f15fa40903b63444",
- "1a95567deb0f45a8941e2248f33286485984a5e9d86d16c37d42169ad864dc36",
- "205fc7f7ec7a83f0bc22d5269c91762cd00adc7428456d799be5a0cd76f08b0f",
- "849dd41ef59a722901b7a0deb2c1fd3c110a91a726120a0a119cb7a15cf98438",
- "a32880917c714612101af95e5c8d8eb5fb046fdcc68bae76c05b829b3fa73c2e",
- "70b38d6d510d13b359dffa910329952c620a4bce4ee7a8552b9bb3a14572394d",
- "ef257ab2f4226faa6ba288a6793f026609068effb866c18496a847e8b60b102d",
- "e5196ab42ff53c8352288bde6b6b7312cd6f39f7d21b556b0db178d8470d5790",
- "ca98f128bf085f2b718f2b3c12da7c4d98887cc94251a2b1705b637611bd83bb",
- "79508f0b93a49ec19c5cb05906ca1ba3d3db8ed4f9c6884873d0d7e3e985ea51",
- "9088be3f47f9debc63e928739f7163182b49eab044518b151f0b89f6b6aefdd0",
- "46b2782fd669b6288a4d7348cf6671360277ba4864cc69bce3497369ac2ec31e",
- "0fa5131557db67b430d516530be939ff25882adf68a076602f3dfad8c77c963a",
- "3404302cc097d5457244453a4c9990804201ee8161188df811bcb32404998c71",
- "856939710dbb90a8eeda875a31f9a52af759bd932b88e7b08df35414c54d4721",
- "72569573b9b41d0ac5ce17764a139c6b8b36ef3ca6d92cec625dbcdae758ba22",
- "9746da344e435a008d6acb4847211bb676376ecc76c825b5d44a28b89ceeb40e",
- "3eafded1595516f032e33ec975f4c9c3a1055d13aa5575cf8a801d6103fdbeb4",
- "e88a6d2daa863c0787cc523a2cab45c546fad788951b10d75e2b0954db24cca7",
- "38f531e67f88f66de44d3357c8e8f2db456160ca31dd2024c9562f6afd260278",
- };
- // block 685498 (13 key images in one transaction)
- static const char * const key_images_685498[] =
- {
- "749b7277aa21c70c417f255fb181c3a30b44277edf657eaaebf28a2709dd2a90",
- "5a9b3e1a87332d735cedaa2b5623a6a5e99d99f5a2887c8fc84293577e8bf25c",
- "bea438768445eb3650cf619bf6758e94035abfe0ccda91d4a58c582df143d835",
- "376e237ff4da5e5cbd6e1bba4b01412fa751f2959c0c57006589f382413df429",
- "14ac2f2e044f8635a3a42ecb46c575424073c1f6e5ed5e905f516d57f63184b5",
- "2d1d2ecb77b69a2901de00897d0509c1b7855a3b2f8eb1afe419008fc03cd15a",
- "ea01658f0972b77ae9112d525ec073e3ec5c3b98d5ad912d95ab2636354b70b6",
- "d3934864a46101d8c242415282e7fc9ee73ad16cd40355535d226ab45ecdb61a",
- "ee379b05c5d02432330ebd4ea9c4f1c87d14c388568d526a0f8a22649a14e453",
- "aeb7b842b410b13ca4af7a5ffd5ae6caddc8bfec653df1b945e478839a2e0057",
- "451806929d9f5c3a7f365472703871abadc25b2a5a2d75472a45e86cd76c610b",
- "272d9b9fcc9e253c08da9caf8233471150019582eaefef461c1f9ceff7e2c337",
- "633cdedeb3b96ec4f234c670254c6f721e0b368d00b48c6b26759db7d62cf52d",
- };
+ // From tx 17ce4c8feeb82a6d6adaa8a89724b32bf4456f6909c7f84c8ce3ee9ebba19163
+ static const char * const first_missing_key_image_202612 =
+ "121bf6ae1596983b703d62fecf60ea7dd3c3909acf1e0911652e7dadb420ed12";
if (height() > 202612)
{
- for (const auto &kis: key_images_202612)
- {
- crypto::key_image ki;
- epee::string_tools::hex_to_pod(kis, ki);
- if (!has_key_image(ki))
- {
- LOG_PRINT_L1("Fixup: adding missing spent key " << ki);
- add_spent_key(ki);
- }
- }
- }
- if (height() > 685498)
- {
- for (const auto &kis: key_images_685498)
+ crypto::key_image ki;
+ epee::string_tools::hex_to_pod(first_missing_key_image_202612, ki);
+ if (!has_key_image(ki))
{
- crypto::key_image ki;
- epee::string_tools::hex_to_pod(kis, ki);
- if (!has_key_image(ki))
- {
- LOG_PRINT_L1("Fixup: adding missing spent key " << ki);
- add_spent_key(ki);
- }
+ LOG_PRINT_L1("Fixup: detected missing key images from block 202612! Popping blocks...");
+ while (height() > 202612)
+ pop_block();
}
}
}
- batch_stop();
}
bool BlockchainDB::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category)
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index a9dfcf24c..3e953da30 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -718,41 +718,6 @@ public:
*/
virtual std::string get_db_name() const = 0;
-
- // FIXME: these are just for functionality mocking, need to implement
- // RAII-friendly and multi-read one-write friendly locking mechanism
- //
- // acquire db lock
- /**
- * @brief acquires the BlockchainDB lock
- *
- * This function is a stub until such a time as locking is implemented at
- * this level.
- *
- * The subclass implementation should return true unless implementing a
- * locking scheme of some sort, in which case it should return true upon
- * acquisition of the lock and block until then.
- *
- * If any of this cannot be done, the subclass should throw the corresponding
- * subclass of DB_EXCEPTION
- *
- * @return true, unless at a future time false makes sense (timeout, etc)
- */
- virtual bool lock() = 0;
-
- // release db lock
- /**
- * @brief This function releases the BlockchainDB lock
- *
- * The subclass, should it have implemented lock(), will release any lock
- * held by the calling thread. In the case of recursive locking, it should
- * release one instance of a lock.
- *
- * If any of this cannot be done, the subclass should throw the corresponding
- * subclass of DB_EXCEPTION
- */
- virtual void unlock() = 0;
-
/**
* @brief tells the BlockchainDB to start a new "batch" of blocks
*
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 61d76bda5..d01119249 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1688,22 +1688,6 @@ std::string BlockchainLMDB::get_db_name() const
return std::string("lmdb");
}
-// TODO: this?
-bool BlockchainLMDB::lock()
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- check_open();
- return false;
-}
-
-// TODO: this?
-void BlockchainLMDB::unlock()
-{
- LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- check_open();
-}
-
-
// The below two macros are for DB access within block add/remove, whether
// regular batch txn is in use or not. m_write_txn is used as a batch txn, even
// if it's only within block add/remove.
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index b528a69ac..6eeb942dc 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -202,10 +202,6 @@ public:
virtual std::string get_db_name() const;
- virtual bool lock();
-
- virtual void unlock();
-
virtual bool block_exists(const crypto::hash& h, uint64_t *height = NULL) const;
virtual uint64_t get_block_height(const crypto::hash& h) const;
diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h
index c374c9603..308bdd4c2 100644
--- a/src/blockchain_db/testdb.h
+++ b/src/blockchain_db/testdb.h
@@ -50,8 +50,6 @@ public:
virtual std::vector<std::string> get_filenames() const override { return std::vector<std::string>(); }
virtual bool remove_data_file(const std::string& folder) const override { return true; }
virtual std::string get_db_name() const override { return std::string(); }
- virtual bool lock() override { return true; }
- virtual void unlock() override { }
virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override { return true; }
virtual void batch_stop() override {}
virtual void batch_abort() override {}
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index 37bb9fe47..686796ce4 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -152,6 +152,17 @@ DISABLE_VS_WARNINGS(4244 4345)
m_keys.m_multisig_keys.clear();
}
//-----------------------------------------------------------------
+ void account_base::set_spend_key(const crypto::secret_key& spend_secret_key)
+ {
+ // make sure derived spend public key matches saved public spend key
+ crypto::public_key spend_public_key;
+ crypto::secret_key_to_public_key(spend_secret_key, spend_public_key);
+ CHECK_AND_ASSERT_THROW_MES(m_keys.m_account_address.m_spend_public_key == spend_public_key,
+ "Unexpected derived public spend key");
+
+ m_keys.m_spend_secret_key = spend_secret_key;
+ }
+ //-----------------------------------------------------------------
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random)
{
crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover);
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
index 002a6aedd..de5912032 100644
--- a/src/cryptonote_basic/account.h
+++ b/src/cryptonote_basic/account.h
@@ -95,6 +95,7 @@ namespace cryptonote
bool store(const std::string& file_path);
void forget_spend_key();
+ void set_spend_key(const crypto::secret_key& spend_secret_key);
const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; }
void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); }
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 575b3666c..88c8e0c20 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -241,6 +241,8 @@ namespace config
const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d;
const unsigned char HASH_KEY_WALLET = 0x8c;
const unsigned char HASH_KEY_WALLET_CACHE = 0x8d;
+ const unsigned char HASH_KEY_BACKGROUND_CACHE = 0x8e;
+ const unsigned char HASH_KEY_BACKGROUND_KEYS_FILE = 0x8f;
const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58;
const unsigned char HASH_KEY_MEMORY = 'k';
const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
diff --git a/src/device/log.hpp b/src/device/log.hpp
index 20a5caaf3..d8276e577 100644
--- a/src/device/log.hpp
+++ b/src/device/log.hpp
@@ -48,7 +48,7 @@ namespace hw {
* - All computation done by device are checked by default device.
* Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working
* #define IODUMMYCRYPT_HWDEVICE 1
- * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value
+ * - It assumes sensitive data encryption is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value
* #define IONOCRYPT_HWDEVICE 1
* - It assumes sensitive data encryption is off on device side.
*/
diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h
index 468a6e985..917f5c87a 100644
--- a/src/multisig/multisig_account.h
+++ b/src/multisig/multisig_account.h
@@ -103,7 +103,8 @@ namespace multisig
*
* - prepares a kex msg for the first round of multisig key construction.
* - the local account's kex msgs are signed with the base_privkey
- * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey
+ * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's
+ * common_privkey
*/
multisig_account(const crypto::secret_key &base_privkey,
const crypto::secret_key &base_common_privkey);
@@ -190,24 +191,48 @@ namespace multisig
* - If force updating with maliciously-crafted messages, the resulting account will be invalid (either unable
* to complete signatures, or a 'hostage' to the malicious signer [i.e. can't sign without his participation]).
*/
- void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
- const bool force_update_use_with_caution = false);
+ void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, const bool force_update_use_with_caution = false);
+ /**
+ * brief: get_multisig_kex_round_booster - Create a multisig kex msg for the kex round that follows the kex round this
+ * account is currently working on, in order to 'boost' another participant's kex setup.
+ * - A booster message is for the round after the in-progress round because get_next_kex_round_msg() provides access
+ * to the in-progress round's message.
+ * - Useful for 'jumpstarting' the following kex round when you don't have messages from all other signers to complete
+ * the current round.
+ * - Sanitizes input messages and produces a new kex msg for round 'num_completed_rounds + 2'.
+ *
+ * - For example, in 2-of-3 escrowed purchasing, the [vendor, arbitrator] pair can boost the second round
+ * of key exchange by calling this function with the 'round 1' messages of each other.
+ * Then the [buyer] can use the resulting boost messages, in combination with [vender, arbitrator] round 1 messages,
+ * to complete the address in one step. In other words, call initialize_kex() on the round 1 messages,
+ * then call kex_update() on the round 2 booster messages to finish the multisig key.
+ *
+ * - Note: The 'threshold' and 'num_signers' are inputs here in case kex has not been initialized yet.
+ * param: threshold - threshold for multisig (M in M-of-N)
+ * param: num_signers - number of participants in multisig (N)
+ * param: expanded_msgs - set of multisig kex messages to process
+ * return: multisig kex message for next round
+ */
+ multisig_kex_msg get_multisig_kex_round_booster(const std::uint32_t threshold,
+ const std::uint32_t num_signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs) const;
private:
// implementation of kex_update() (non-transactional)
void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, const bool incomplete_signer_set);
/**
- * brief: initialize_kex_update - Helper for kex_update_impl()
- * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key
- * if appropriate.
+ * brief: get_kex_exclude_pubkeys - collect the local signer's shared keys to ignore in incoming messages
+ * return: keys held by the local account corresponding to the 'in-progress round'
+ * - If 'in-progress round' is the final round, these are the local account's shares of the final aggregate key.
+ */
+ std::vector<crypto::public_key> get_kex_exclude_pubkeys() const;
+ /**
+ * brief: initialize_kex_update - initialize the multisig account for the first kex round
* param: expanded_msgs - set of multisig kex messages to process
* param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
- * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round'
- * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key.
*/
void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
- const std::uint32_t kex_rounds_required,
- std::vector<crypto::public_key> &exclude_pubkeys_out);
+ const std::uint32_t kex_rounds_required);
/**
* brief: finalize_kex_update - Helper for kex_update_impl()
* param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp
index c88f20768..5d2f362e5 100644
--- a/src/multisig/multisig_account_kex_impl.cpp
+++ b/src/multisig/multisig_account_kex_impl.cpp
@@ -395,7 +395,7 @@ namespace multisig
* - Sanitizes input msgs.
* - Require uniqueness in: 'exclude_pubkeys'.
* - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers.
- * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be
+ * - For a final multisig key to be truly 'M-of-N', each of the private key's components must be
* shared by (N - M + 1) signers.
* - Requires that msgs are signed by only keys in 'signers'.
* - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys.
@@ -567,7 +567,7 @@ namespace multisig
// note: do NOT remove the local signer from the pubkey origins map, since the post-kex round can be force-updated with
// just the local signer's post-kex message (if the local signer were removed, then the post-kex message's pubkeys
- // would be completely lost)
+ // would be completely deleted)
// evaluate pubkeys collected
@@ -608,7 +608,7 @@ namespace multisig
* INTERNAL
*
* brief: multisig_kex_process_round_msgs - Process kex messages for the active kex round.
- * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg().
+ * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_round_keys().
* - In other words, evaluate the input messages and try to make a message for the next round.
* - Note: Must be called on the final round's msgs to evaluate the final key components
* recommended by other participants.
@@ -623,7 +623,7 @@ namespace multisig
* outparam: keys_to_origins_map_out - map between round keys and identity keys
* - If in the final round, these are key shares recommended by other signers for the final aggregate key.
* - Otherwise, these are the local account's DH derivations for the next round.
- * - See multisig_kex_make_next_msg() for an explanation.
+ * - See multisig_kex_make_round_keys() for an explanation.
* return: multisig kex message for next round, or empty message if 'current_round' is the final round
*/
//----------------------------------------------------------------------------------------------------------------------
@@ -684,59 +684,67 @@ namespace multisig
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
//----------------------------------------------------------------------------------------------------------------------
- void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
- const std::uint32_t kex_rounds_required,
- std::vector<crypto::public_key> &exclude_pubkeys_out)
+ std::vector<crypto::public_key> multisig_account::get_kex_exclude_pubkeys() const
{
+ // exclude all keys the local account recommends
+ std::vector<crypto::public_key> exclude_pubkeys;
+
if (m_kex_rounds_complete == 0)
{
- // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys
+ // in the first round, only the local pubkey is recommended by the local signer
+ exclude_pubkeys.emplace_back(m_base_pubkey);
+ }
+ else
+ {
+ // in other rounds, kex msgs will contain participants' shared keys, so ignore shared keys the account helped
+ // create for this round
+ for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map)
+ exclude_pubkeys.emplace_back(shared_key_with_origins.first);
+ }
- // collect participants' base common privkey shares
- // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
- // will be blocked by duplicate-signer errors after this function is called
- std::vector<crypto::secret_key> participant_base_common_privkeys;
- participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
+ return exclude_pubkeys;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::uint32_t kex_rounds_required)
+ {
+ // initialization is only needed during the first round
+ if (m_kex_rounds_complete > 0)
+ return;
- // add local ancillary base privkey
- participant_base_common_privkeys.emplace_back(m_base_common_privkey);
+ // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys, so we prepare
+ // them here
- // add other signers' base common privkeys
- for (const auto &expanded_msg : expanded_msgs)
- {
- if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
- {
- participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
- }
- }
+ // collect participants' base common privkey shares
+ // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
+ // will be blocked by duplicate-signer errors after this function is called
+ std::vector<crypto::secret_key> participant_base_common_privkeys;
+ participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
- // make common privkey
- make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
+ // add local ancillary base privkey
+ participant_base_common_privkeys.emplace_back(m_base_common_privkey);
- // set common pubkey
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
- "Failed to derive public key");
+ // add other signers' base common privkeys
+ for (const multisig_kex_msg &expanded_msg : expanded_msgs)
+ {
+ if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
+ participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
+ }
- // if N-of-N, then the base privkey will be used directly to make the account's share of the final key
- if (kex_rounds_required == 1)
- {
- m_multisig_privkeys.clear();
- m_multisig_privkeys.emplace_back(m_base_privkey);
- }
+ // make common privkey
+ make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
- // exclude all keys the local account recommends
- // - in the first round, only the local pubkey is recommended by the local signer
- exclude_pubkeys_out.emplace_back(m_base_pubkey);
- }
- else
- {
- // in other rounds, kex msgs will contain participants' shared keys
+ // set common pubkey
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
+ "Failed to derive public key");
- // ignore shared keys the account helped create for this round
- for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map)
- {
- exclude_pubkeys_out.emplace_back(shared_key_with_origins.first);
- }
+ // if N-of-N, then the base privkey will be used directly to make the account's share of the final key
+ if (kex_rounds_required == 1)
+ {
+ m_multisig_privkeys.clear();
+ m_multisig_privkeys.emplace_back(m_base_privkey);
}
}
//----------------------------------------------------------------------------------------------------------------------
@@ -771,9 +779,7 @@ namespace multisig
result_keys.reserve(result_keys_to_origins_map.size());
for (const auto &result_key_and_origins : result_keys_to_origins_map)
- {
result_keys.emplace_back(result_key_and_origins.first);
- }
// compute final aggregate key, update local multisig privkeys with aggregation coefficients applied
m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys);
@@ -811,7 +817,8 @@ namespace multisig
// derived pubkey = multisig_key * G
crypto::public_key_memsafe derived_pubkey;
m_multisig_privkeys.push_back(
- calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey));
+ calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)
+ );
// save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key]
m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second);
@@ -863,8 +870,7 @@ namespace multisig
"Multisig kex has already completed all required rounds (including post-kex verification).");
// initialize account update
- std::vector<crypto::public_key> exclude_pubkeys;
- initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys);
+ this->initialize_kex_update(expanded_msgs, kex_rounds_required);
// process messages into a [pubkey : {origins}] map
multisig_keyset_map_memsafe_t result_keys_to_origins_map;
@@ -875,12 +881,75 @@ namespace multisig
m_threshold,
m_signers,
expanded_msgs,
- exclude_pubkeys,
+ this->get_kex_exclude_pubkeys(),
incomplete_signer_set,
result_keys_to_origins_map);
// finish account update
- finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map));
+ this->finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map));
+ }
+ //-----------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //-----------------------------------------------------------------
+ multisig_kex_msg multisig_account::get_multisig_kex_round_booster(const std::uint32_t threshold,
+ const std::uint32_t num_signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs) const
+ {
+ // the messages passed in should be required for the next kex round of this account (the round it is currently
+ // working on)
+ const std::uint32_t expected_msgs_round{m_kex_rounds_complete + 1};
+ const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(num_signers, threshold)};
+
+ CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer.");
+ CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, "Too many multisig signers specified.");
+ CHECK_AND_ASSERT_THROW_MES(expected_msgs_round < kex_rounds_required,
+ "Multisig kex booster: this account has already completed all intermediate kex rounds so it can't make a kex "
+ "booster (there is no round available to boost).");
+ CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input kex message expected.");
+
+ // sanitize pubkeys from input msgs
+ multisig_keyset_map_memsafe_t pubkey_origins_map;
+ const std::uint32_t msgs_round{
+ multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, this->get_kex_exclude_pubkeys(), pubkey_origins_map)
+ };
+ CHECK_AND_ASSERT_THROW_MES(msgs_round == expected_msgs_round, "Kex messages were not for expected round.");
+
+ // remove the local signer from sanitized messages
+ remove_key_from_mapped_sets(m_base_pubkey, pubkey_origins_map);
+
+ // make DH derivations for booster message
+ multisig_keyset_map_memsafe_t derivation_to_origins_map;
+ multisig_kex_make_round_keys(m_base_privkey, std::move(pubkey_origins_map), derivation_to_origins_map);
+
+ // collect keys for booster message
+ std::vector<crypto::public_key> next_msg_keys;
+ next_msg_keys.reserve(derivation_to_origins_map.size());
+
+ if (msgs_round + 1 == kex_rounds_required)
+ {
+ // final kex round: send DH derivation pubkeys in the message
+ for (const auto &derivation_and_origins : derivation_to_origins_map)
+ {
+ // multisig_privkey = H(derivation)
+ // derived pubkey = multisig_key * G
+ crypto::public_key_memsafe derived_pubkey;
+ calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey);
+
+ // save keys that should be recommended to other signers
+ // - The keys multisig_key*G are sent to other participants in the message, so they can be used to produce the final
+ // multisig key via generate_multisig_spend_public_key().
+ next_msg_keys.push_back(derived_pubkey);
+ }
+ }
+ else //(msgs_round + 1 < kex_rounds_required)
+ {
+ // intermediate kex round: send DH derivations directly in the message
+ for (const auto &derivation_and_origins : derivation_to_origins_map)
+ next_msg_keys.push_back(derivation_and_origins.first);
+ }
+
+ // produce a kex message for the round after the round this account is currently working on
+ return multisig_kex_msg{msgs_round + 1, m_base_privkey, std::move(next_msg_keys)}.get_msg();
}
//----------------------------------------------------------------------------------------------------------------------
} //namespace multisig
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 875f1fe1b..153d5f2ac 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -148,6 +148,17 @@ typedef cryptonote::simple_wallet sw;
} \
} while(0)
+#define CHECK_IF_BACKGROUND_SYNCING(msg) \
+ do \
+ { \
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) \
+ { \
+ std::string type = m_wallet->is_background_wallet() ? "background wallet" : "background syncing wallet"; \
+ fail_msg_writer() << boost::format(tr("%s %s")) % type % msg; \
+ return false; \
+ } \
+ } while (0)
+
static std::string get_human_readable_timespan(std::chrono::seconds seconds);
static std::string get_human_readable_timespan(uint64_t seconds);
@@ -314,7 +325,7 @@ namespace
auto pwd_container = tools::password_container::prompt(verify, prompt);
if (!pwd_container)
{
- tools::fail_msg_writer() << sw::tr("failed to read wallet password");
+ tools::fail_msg_writer() << sw::tr("failed to read password");
}
return pwd_container;
}
@@ -324,6 +335,11 @@ namespace
return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify);
}
+ boost::optional<tools::password_container> background_sync_cache_password_prompter(bool verify)
+ {
+ return password_prompter(verify ? sw::tr("Enter a custom password for the background sync cache") : sw::tr("Background sync cache password"), verify);
+ }
+
inline std::string interpret_rpc_response(bool ok, const std::string& status)
{
std::string err;
@@ -441,6 +457,41 @@ namespace
return "invalid";
}
+ const struct
+ {
+ const char *name;
+ tools::wallet2::BackgroundSyncType background_sync_type;
+ } background_sync_type_names[] =
+ {
+ { "off", tools::wallet2::BackgroundSyncOff },
+ { "reuse-wallet-password", tools::wallet2::BackgroundSyncReusePassword },
+ { "custom-background-password", tools::wallet2::BackgroundSyncCustomPassword },
+ };
+
+ bool parse_background_sync_type(const std::string &s, tools::wallet2::BackgroundSyncType &background_sync_type)
+ {
+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n)
+ {
+ if (s == background_sync_type_names[n].name)
+ {
+ background_sync_type = background_sync_type_names[n].background_sync_type;
+ return true;
+ }
+ }
+ fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse background sync type");
+ return false;
+ }
+
+ std::string get_background_sync_type_name(tools::wallet2::BackgroundSyncType type)
+ {
+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n)
+ {
+ if (type == background_sync_type_names[n].background_sync_type)
+ return background_sync_type_names[n].name;
+ }
+ return "invalid";
+ }
+
std::string get_version_string(uint32_t version)
{
return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff);
@@ -793,6 +844,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
fail_msg_writer() << tr("wallet is watch-only and has no spend key");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("has no spend key");
// don't log
PAUSE_READLINE();
if (m_wallet->key_on_device()) {
@@ -823,6 +875,7 @@ bool simple_wallet::print_seed(bool encrypted)
fail_msg_writer() << tr("wallet is watch-only and has no seed");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("has no seed");
const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()};
if (ms_status.multisig_is_active)
@@ -900,6 +953,7 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s
fail_msg_writer() << tr("wallet is watch-only and has no seed");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("has no seed");
epee::wipeable_string password;
{
@@ -1046,6 +1100,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig");
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot be made multisig");
if(m_wallet->get_num_transfer_details())
{
@@ -2105,6 +2160,7 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args)
bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot freeze/thaw");
if (args.empty())
{
fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw");
@@ -2144,6 +2200,7 @@ bool simple_wallet::thaw(const std::vector<std::string> &args)
bool simple_wallet::frozen(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot see frozen key images");
if (args.empty())
{
size_t ntd = m_wallet->get_num_transfer_details();
@@ -2794,6 +2851,57 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std:
return true;
}
+bool simple_wallet::setup_background_sync(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ if (m_wallet->get_multisig_status().multisig_is_active)
+ {
+ fail_msg_writer() << tr("background sync not implemented for multisig wallet");
+ return true;
+ }
+ if (m_wallet->watch_only())
+ {
+ fail_msg_writer() << tr("background sync not implemented for watch only wallet");
+ return true;
+ }
+ if (m_wallet->key_on_device())
+ {
+ fail_msg_writer() << tr("command not supported by HW wallet");
+ return true;
+ }
+
+ tools::wallet2::BackgroundSyncType background_sync_type;
+ if (!parse_background_sync_type(args[1], background_sync_type))
+ {
+ fail_msg_writer() << tr("invalid option");
+ return true;
+ }
+
+ const auto pwd_container = get_and_verify_password();
+ if (!pwd_container)
+ return true;
+
+ try
+ {
+ boost::optional<epee::wipeable_string> background_cache_password = boost::none;
+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword)
+ {
+ const auto background_pwd_container = background_sync_cache_password_prompter(true);
+ if (!background_pwd_container)
+ return true;
+ background_cache_password = background_pwd_container->password();
+ }
+
+ LOCK_IDLE_SCOPE();
+ m_wallet->setup_background_sync(background_sync_type, pwd_container->password(), background_cache_password);
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Error setting background sync type: ") << e.what();
+ }
+
+ return true;
+}
+
bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
@@ -3026,6 +3134,7 @@ bool simple_wallet::apropos(const std::vector<std::string> &args)
bool simple_wallet::scan_tx(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot scan tx");
if (args.empty())
{
PRINT_USAGE(USAGE_SCAN_TX);
@@ -3243,6 +3352,8 @@ simple_wallet::simple_wallet()
" Ignore outputs of amount below this threshold when spending.\n "
"track-uses <1|0>\n "
" Whether to keep track of owned outputs uses.\n "
+ "background-sync <off|reuse-wallet-password|custom-background-password>\n "
+ " Set this to enable scanning in the background with just the view key while the wallet is locked.\n "
"setup-background-mining <1|0>\n "
" Whether to enable background mining. Set this to support the network and to get a chance to receive new monero.\n "
"device-name <device_name[:device_spec]>\n "
@@ -3645,6 +3756,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above());
success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below());
success_msg_writer() << "track-uses = " << m_wallet->track_uses();
+ success_msg_writer() << "background-sync = " << get_background_sync_type_name(m_wallet->background_sync_type());
success_msg_writer() << "setup-background-mining = " << setup_background_mining_string;
success_msg_writer() << "device-name = " << m_wallet->device_name();
success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary");
@@ -3660,6 +3772,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot change wallet settings");
#define CHECK_SIMPLE_VARIABLE(name, f, help) do \
if (args[0] == name) { \
@@ -3713,6 +3826,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount"));
CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount"));
CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("background-sync", setup_background_sync, tr("off (default); reuse-wallet-password (reuse the wallet password to encrypt the background cache); custom-background-password (use a custom background password to encrypt the background cache)"));
CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0"));
CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)"));
CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no"));
@@ -4653,7 +4767,10 @@ std::string simple_wallet::get_mnemonic_language()
//----------------------------------------------------------------------------------------------------
boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const
{
- auto pwd_container = default_password_prompter(m_wallet_file.empty());
+ const bool verify = m_wallet_file.empty();
+ auto pwd_container = (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword)
+ ? background_sync_cache_password_prompter(verify)
+ : default_password_prompter(verify);
if (!pwd_container)
return boost::none;
@@ -4956,6 +5073,8 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p
prefix = tr("Opened watch-only wallet");
else if (ms_status.multisig_is_active)
prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % ms_status.threshold % ms_status.total % (ms_status.is_ready ? "" : " (not yet finalized)")).str();
+ else if (m_wallet->is_background_wallet())
+ prefix = tr("Opened background wallet");
else
prefix = tr("Opened wallet");
message_writer(console_color_white, true) <<
@@ -5163,6 +5282,10 @@ void simple_wallet::stop_background_mining()
//----------------------------------------------------------------------------------------------------
void simple_wallet::check_background_mining(const epee::wipeable_string &password)
{
+ // Background mining can be toggled from the main wallet
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing())
+ return;
+
tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining();
if (setup == tools::wallet2::BackgroundMiningNo)
{
@@ -5978,6 +6101,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan spent");
if (!m_wallet->is_trusted_daemon())
{
fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
@@ -6233,10 +6357,27 @@ void simple_wallet::check_for_inactivity_lock(bool user)
" || ||" << std::endl <<
"" << std::endl;
}
+
+ bool started_background_sync = false;
+ if (!m_wallet->is_background_wallet() &&
+ m_wallet->background_sync_type() != tools::wallet2::BackgroundSyncOff)
+ {
+ LOCK_IDLE_SCOPE();
+ m_wallet->start_background_sync();
+ started_background_sync = true;
+ }
+
while (1)
{
const char *inactivity_msg = user ? "" : tr("Locked due to inactivity.");
- tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console.");
+ tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << (
+ (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword)
+ ? tr("The background password is required to unlock the console.")
+ : tr("The wallet password is required to unlock the console.")
+ );
+
+ if (m_wallet->is_background_syncing())
+ tools::msg_writer() << tr("\nSyncing in the background while locked...") << std::endl;
const bool show_wallet_name = m_wallet->show_wallet_name_when_locked();
if (show_wallet_name)
@@ -6249,8 +6390,16 @@ void simple_wallet::check_for_inactivity_lock(bool user)
}
try
{
- if (get_and_verify_password())
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ if (started_background_sync)
+ {
+ LOCK_IDLE_SCOPE();
+ m_wallet->stop_background_sync(pwd_container->password());
+ }
break;
+ }
}
catch (...) { /* do nothing, just let the loop loop */ }
}
@@ -6277,6 +6426,7 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std:
bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool called_by_mms)
{
// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"
+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
if (!try_connect_to_daemon())
return false;
@@ -6690,6 +6840,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer");
if (args_.size() < 1)
{
PRINT_USAGE(USAGE_TRANSFER);
@@ -6702,6 +6853,7 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
if (!try_connect_to_daemon())
return true;
@@ -6809,6 +6961,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
auto print_usage = [this, account, below]()
{
if (below)
@@ -7090,6 +7243,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vect
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
if (!try_connect_to_daemon())
return true;
@@ -7328,12 +7482,14 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
sweep_main(m_current_subaddress_account, 0, args_);
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
auto local_args = args_;
if (local_args.empty())
{
@@ -7354,6 +7510,7 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep");
uint64_t below = 0;
if (args_.size() < 1)
{
@@ -7372,6 +7529,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::donate(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot donate");
std::vector<std::string> local_args = args_;
if(local_args.empty() || local_args.size() > 5)
{
@@ -7433,6 +7591,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot load tx");
// gather info to ask the user
uint64_t amount = 0, amount_to_dests = 0, change = 0;
size_t min_ring_size = ~0;
@@ -7613,6 +7772,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
fail_msg_writer() << tr("This is a watch only wallet");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer");
bool export_raw = false;
std::string unsigned_filename = "unsigned_monero_tx";
@@ -7720,6 +7880,8 @@ std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::sec
bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx key");
+
std::vector<std::string> local_args = args_;
if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
@@ -7760,6 +7922,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx key");
+
std::vector<std::string> local_args = args_;
if(local_args.size() != 2 && local_args.size() != 3) {
@@ -7836,6 +8000,8 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx proof");
+
if (args.size() != 2 && args.size() != 3)
{
PRINT_USAGE(USAGE_GET_TX_PROOF);
@@ -8042,6 +8208,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get spend proof");
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -8126,6 +8293,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get reserve proof");
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -8721,6 +8889,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
uint64_t max_height = 0;
uint64_t found_min_amount = std::numeric_limits<uint64_t>::max();
uint64_t found_max_amount = 0;
+ uint64_t found_sum_amount = 0;
uint64_t count = 0;
for (const auto& td : transfers)
{
@@ -8732,6 +8901,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
if (max_height < td.m_block_height) max_height = td.m_block_height;
if (found_min_amount > amount) found_min_amount = amount;
if (found_max_amount < amount) found_max_amount = amount;
+ found_sum_amount += amount;
++count;
}
if (amount_to_tds.empty())
@@ -8756,6 +8926,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
<< tr("\nMax block height: ") << max_height
<< tr("\nMin amount found: ") << print_money(found_min_amount)
<< tr("\nMax amount found: ") << print_money(found_max_amount)
+ << tr("\nSum amount found: ") << print_money(found_sum_amount)
<< tr("\nTotal count: ") << count;
const size_t histogram_height = 10;
const size_t histogram_width = 50;
@@ -8812,6 +8983,8 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan");
+
uint64_t start_height = 0;
ResetType reset_type = ResetSoft;
@@ -9036,6 +9209,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
if (command == "new")
{
// create a new account and switch to it
+ CHECK_IF_BACKGROUND_SYNCING("cannot create new account");
std::string label = boost::join(local_args, " ");
if (label.empty())
label = tr("(Untitled account)");
@@ -9066,6 +9240,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
else if (command == "label" && local_args.size() >= 1)
{
// set label of the specified account
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
uint32_t index_major;
if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0]))
{
@@ -9087,6 +9262,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
}
else if (command == "tag" && local_args.size() >= 2)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
const std::string tag = local_args[0];
std::set<uint32_t> account_indices;
for (size_t i = 1; i < local_args.size(); ++i)
@@ -9111,6 +9287,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
}
else if (command == "untag" && local_args.size() >= 1)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
std::set<uint32_t> account_indices;
for (size_t i = 0; i < local_args.size(); ++i)
{
@@ -9134,6 +9311,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
}
else if (command == "tag_description" && local_args.size() >= 1)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account");
const std::string tag = local_args[0];
std::string description;
if (local_args.size() > 1)
@@ -9251,6 +9429,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
}
else if (local_args[0] == "new")
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot add address");
local_args.erase(local_args.begin());
std::string label;
if (local_args.size() > 0)
@@ -9263,6 +9442,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
}
else if (local_args[0] == "mnew")
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot add addresses");
local_args.erase(local_args.begin());
if (local_args.size() != 1)
{
@@ -9288,6 +9468,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
}
else if (local_args[0] == "one-off")
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot add address");
local_args.erase(local_args.begin());
std::string label;
if (local_args.size() != 2)
@@ -9306,6 +9487,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
}
else if (local_args.size() >= 2 && local_args[0] == "label")
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot modify address");
if (!epee::string_tools::get_xtype_from_string(index, local_args[1]))
{
fail_msg_writer() << tr("failed to parse index: ") << local_args[1];
@@ -9452,6 +9634,8 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg
//----------------------------------------------------------------------------------------------------
bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get address book");
+
if (args.size() == 0)
{
}
@@ -9512,6 +9696,8 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_tx_note(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx note");
+
if (args.size() == 0)
{
PRINT_USAGE(USAGE_SET_TX_NOTE);
@@ -9540,6 +9726,8 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_tx_note(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx note");
+
if (args.size() != 1)
{
PRINT_USAGE(USAGE_GET_TX_NOTE);
@@ -9565,6 +9753,8 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_description(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot set description");
+
// 0 arguments allowed, for setting the description to empty string
std::string description = "";
@@ -9581,6 +9771,8 @@ bool simple_wallet::set_description(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_description(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot get description");
+
if (args.size() != 0)
{
PRINT_USAGE(USAGE_GET_DESCRIPTION);
@@ -9639,6 +9831,8 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args)
type = tr("Watch only");
else if (ms_status.multisig_is_active)
type = (boost::format(tr("%u/%u multisig%s")) % ms_status.threshold % ms_status.total % (ms_status.is_ready ? "" : " (not yet finalized)")).str();
+ else if (m_wallet->is_background_wallet())
+ type = tr("Background wallet");
else
type = tr("Normal");
message_writer() << tr("Type: ") << type;
@@ -9650,6 +9844,7 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sign(const std::vector<std::string> &args)
{
+ CHECK_IF_BACKGROUND_SYNCING("cannot sign");
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -9757,6 +9952,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args_)
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot export key images");
auto args = args_;
if (m_wallet->watch_only())
@@ -9810,6 +10006,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args)
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot import key images");
if (!m_wallet->is_trusted_daemon())
{
fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
@@ -9918,6 +10115,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args_)
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot export outputs");
auto args = args_;
bool all = false;
@@ -9967,6 +10165,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+ CHECK_IF_BACKGROUND_SYNCING("cannot import outputs");
if (args.size() != 1)
{
PRINT_USAGE(USAGE_IMPORT_OUTPUTS);
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index fd93a3eff..76ece1b38 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -147,6 +147,7 @@ namespace cryptonote
bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>());
bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>());
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
+ bool setup_background_sync(const std::vector<std::string> &args = std::vector<std::string>());
bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>());
bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>());
bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>());
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index bfb872c63..c8257919d 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -58,6 +58,40 @@ using namespace cryptonote;
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI"
+#define LOCK_REFRESH() \
+ bool refresh_enabled = m_refreshEnabled; \
+ m_refreshEnabled = false; \
+ m_wallet->stop(); \
+ m_refreshCV.notify_one(); \
+ boost::mutex::scoped_lock lock(m_refreshMutex); \
+ boost::mutex::scoped_lock lock2(m_refreshMutex2); \
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \
+ /* m_refreshMutex's still locked here */ \
+ if (refresh_enabled) \
+ startRefresh(); \
+ })
+
+#define PRE_VALIDATE_BACKGROUND_SYNC() \
+ do \
+ { \
+ clearStatus(); \
+ if (m_wallet->key_on_device()) \
+ { \
+ setStatusError(tr("HW wallet cannot use background sync")); \
+ return false; \
+ } \
+ if (m_wallet->watch_only()) \
+ { \
+ setStatusError(tr("View only wallet cannot use background sync")); \
+ return false; \
+ } \
+ if (m_wallet->get_multisig_status().multisig_is_active) \
+ { \
+ setStatusError(tr("Multisig wallet cannot use background sync")); \
+ return false; \
+ } \
+ } while (0)
+
namespace Monero {
namespace {
@@ -766,6 +800,8 @@ bool WalletImpl::close(bool store)
std::string WalletImpl::seed(const std::string& seed_offset) const
{
+ if (checkBackgroundSync("cannot get seed"))
+ return std::string();
epee::wipeable_string seed;
if (m_wallet)
m_wallet->get_seed(seed, seed_offset);
@@ -779,6 +815,8 @@ std::string WalletImpl::getSeedLanguage() const
void WalletImpl::setSeedLanguage(const std::string &arg)
{
+ if (checkBackgroundSync("cannot set seed language"))
+ return;
m_wallet->set_seed_language(arg);
}
@@ -802,6 +840,8 @@ void WalletImpl::statusWithErrorString(int& status, std::string& errorString) co
bool WalletImpl::setPassword(const std::string &password)
{
+ if (checkBackgroundSync("cannot change password"))
+ return false;
clearStatus();
try {
m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password);
@@ -931,6 +971,8 @@ bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transact
void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height)
{
+ if (checkBackgroundSync("cannot change refresh height"))
+ return;
m_wallet->set_refresh_from_block_height(refresh_from_block_height);
}
@@ -1039,6 +1081,8 @@ void WalletImpl::refreshAsync()
bool WalletImpl::rescanBlockchain()
{
+ if (checkBackgroundSync("cannot rescan blockchain"))
+ return false;
clearStatus();
m_refreshShouldRescan = true;
doRefresh();
@@ -1047,6 +1091,8 @@ bool WalletImpl::rescanBlockchain()
void WalletImpl::rescanBlockchainAsync()
{
+ if (checkBackgroundSync("cannot rescan blockchain"))
+ return;
m_refreshShouldRescan = true;
refreshAsync();
}
@@ -1070,7 +1116,7 @@ int WalletImpl::autoRefreshInterval() const
UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) {
clearStatus();
UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this);
- if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
+ if (checkBackgroundSync("cannot load tx") || !m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
setStatusError(tr("Failed to load unsigned transactions"));
transaction->m_status = UnsignedTransaction::Status::Status_Error;
transaction->m_errorString = errorString();
@@ -1090,6 +1136,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file
bool WalletImpl::submitTransaction(const string &fileName) {
clearStatus();
+ if (checkBackgroundSync("cannot submit tx"))
+ return false;
std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this));
bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx);
@@ -1113,6 +1161,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all)
setStatusError(tr("Wallet is view only"));
return false;
}
+ if (checkBackgroundSync("cannot export key images"))
+ return false;
try
{
@@ -1133,6 +1183,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all)
bool WalletImpl::importKeyImages(const string &filename)
{
+ if (checkBackgroundSync("cannot import key images"))
+ return false;
if (!trustedDaemon()) {
setStatusError(tr("Key images can only be imported with a trusted daemon"));
return false;
@@ -1156,6 +1208,8 @@ bool WalletImpl::importKeyImages(const string &filename)
bool WalletImpl::exportOutputs(const string &filename, bool all)
{
+ if (checkBackgroundSync("cannot export outputs"))
+ return false;
if (m_wallet->key_on_device())
{
setStatusError(string(tr("Not supported on HW wallets.")) + filename);
@@ -1186,6 +1240,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all)
bool WalletImpl::importOutputs(const string &filename)
{
+ if (checkBackgroundSync("cannot import outputs"))
+ return false;
if (m_wallet->key_on_device())
{
setStatusError(string(tr("Not supported on HW wallets.")) + filename);
@@ -1218,6 +1274,8 @@ bool WalletImpl::importOutputs(const string &filename)
bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
{
+ if (checkBackgroundSync("cannot scan transactions"))
+ return false;
if (txids.empty())
{
setStatusError(string(tr("Failed to scan transactions: no transaction ids provided.")));
@@ -1256,8 +1314,86 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
return true;
}
+bool WalletImpl::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password)
+{
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+
+ tools::wallet2::BackgroundSyncType bgs_type;
+ switch (background_sync_type)
+ {
+ case Wallet::BackgroundSync_Off: bgs_type = tools::wallet2::BackgroundSyncOff; break;
+ case Wallet::BackgroundSync_ReusePassword: bgs_type = tools::wallet2::BackgroundSyncReusePassword; break;
+ case Wallet::BackgroundSync_CustomPassword: bgs_type = tools::wallet2::BackgroundSyncCustomPassword; break;
+ default: setStatusError(tr("Unknown background sync type")); return false;
+ }
+
+ boost::optional<epee::wipeable_string> bgc_password = background_cache_password
+ ? boost::optional<epee::wipeable_string>(*background_cache_password)
+ : boost::none;
+
+ LOCK_REFRESH();
+ m_wallet->setup_background_sync(bgs_type, wallet_password, bgc_password);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Failed to setup background sync: " << e.what());
+ setStatusError(string(tr("Failed to setup background sync: ")) + e.what());
+ return false;
+ }
+ return true;
+}
+
+Wallet::BackgroundSyncType WalletImpl::getBackgroundSyncType() const
+{
+ switch (m_wallet->background_sync_type())
+ {
+ case tools::wallet2::BackgroundSyncOff: return Wallet::BackgroundSync_Off;
+ case tools::wallet2::BackgroundSyncReusePassword: return Wallet::BackgroundSync_ReusePassword;
+ case tools::wallet2::BackgroundSyncCustomPassword: return Wallet::BackgroundSync_CustomPassword;
+ default: setStatusError(tr("Unknown background sync type")); return Wallet::BackgroundSync_Off;
+ }
+}
+
+bool WalletImpl::startBackgroundSync()
+{
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+ LOCK_REFRESH();
+ m_wallet->start_background_sync();
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Failed to start background sync: " << e.what());
+ setStatusError(string(tr("Failed to start background sync: ")) + e.what());
+ return false;
+ }
+ return true;
+}
+
+bool WalletImpl::stopBackgroundSync(const std::string &wallet_password)
+{
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+ LOCK_REFRESH();
+ m_wallet->stop_background_sync(epee::wipeable_string(wallet_password));
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Failed to stop background sync: " << e.what());
+ setStatusError(string(tr("Failed to stop background sync: ")) + e.what());
+ return false;
+ }
+ return true;
+}
+
void WalletImpl::addSubaddressAccount(const std::string& label)
{
+ if (checkBackgroundSync("cannot add account"))
+ return;
m_wallet->add_subaddress_account(label);
}
size_t WalletImpl::numSubaddressAccounts() const
@@ -1270,10 +1406,14 @@ size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const
}
void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label)
{
+ if (checkBackgroundSync("cannot add subbaddress"))
+ return;
m_wallet->add_subaddress(accountIndex, label);
}
std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const
{
+ if (checkBackgroundSync("cannot get subbaddress label"))
+ return "";
try
{
return m_wallet->get_subaddress_label({accountIndex, addressIndex});
@@ -1287,6 +1427,8 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre
}
void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label)
{
+ if (checkBackgroundSync("cannot set subbaddress label"))
+ return;
try
{
return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label);
@@ -1300,6 +1442,9 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex
MultisigState WalletImpl::multisig() const {
MultisigState state;
+ if (checkBackgroundSync("cannot use multisig"))
+ return state;
+
const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()};
state.isMultisig = ms_status.multisig_is_active;
@@ -1312,6 +1457,8 @@ MultisigState WalletImpl::multisig() const {
}
string WalletImpl::getMultisigInfo() const {
+ if (checkBackgroundSync("cannot use multisig"))
+ return string();
try {
clearStatus();
return m_wallet->get_multisig_first_kex_msg();
@@ -1324,6 +1471,8 @@ string WalletImpl::getMultisigInfo() const {
}
string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) {
+ if (checkBackgroundSync("cannot make multisig"))
+ return string();
try {
clearStatus();
@@ -1354,6 +1503,21 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf
return string();
}
+std::string WalletImpl::getMultisigKeyExchangeBooster(const std::vector<std::string> &info,
+ const std::uint32_t threshold,
+ const std::uint32_t num_signers) {
+ try {
+ clearStatus();
+
+ return m_wallet->get_multisig_key_exchange_booster(epee::wipeable_string(m_password), info, threshold, num_signers);
+ } catch (const exception& e) {
+ LOG_ERROR("Error on boosting multisig key exchange: " << e.what());
+ setStatusError(string(tr("Failed to boost multisig key exchange: ")) + e.what());
+ }
+
+ return string();
+}
+
bool WalletImpl::exportMultisigImages(string& images) {
try {
clearStatus();
@@ -1464,6 +1628,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
do {
+ if (checkBackgroundSync("cannot create transactions"))
+ break;
+
std::vector<uint8_t> extra;
std::string extra_nonce;
vector<cryptonote::tx_destination_entry> dsts;
@@ -1630,6 +1797,9 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
do {
+ if (checkBackgroundSync("cannot sweep"))
+ break;
+
try {
transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions();
pendingTxPostProcess(transaction);
@@ -1763,11 +1933,15 @@ uint32_t WalletImpl::defaultMixin() const
void WalletImpl::setDefaultMixin(uint32_t arg)
{
+ if (checkBackgroundSync("cannot set default mixin"))
+ return;
m_wallet->default_mixin(arg);
}
bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val)
{
+ if (checkBackgroundSync("cannot set cache attribute"))
+ return false;
m_wallet->set_attribute(key, val);
return true;
}
@@ -1781,6 +1955,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const
bool WalletImpl::setUserNote(const std::string &txid, const std::string &note)
{
+ if (checkBackgroundSync("cannot set user note"))
+ return false;
cryptonote::blobdata txid_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
return false;
@@ -1792,6 +1968,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string &note)
std::string WalletImpl::getUserNote(const std::string &txid) const
{
+ if (checkBackgroundSync("cannot get user note"))
+ return "";
cryptonote::blobdata txid_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
return "";
@@ -1802,6 +1980,9 @@ std::string WalletImpl::getUserNote(const std::string &txid) const
std::string WalletImpl::getTxKey(const std::string &txid_str) const
{
+ if (checkBackgroundSync("cannot get tx key"))
+ return "";
+
crypto::hash txid;
if(!epee::string_tools::hex_to_pod(txid_str, txid))
{
@@ -1886,6 +2067,9 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str,
std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const
{
+ if (checkBackgroundSync("cannot get tx proof"))
+ return "";
+
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(txid_str, txid))
{
@@ -1942,6 +2126,9 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad
}
std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const {
+ if (checkBackgroundSync("cannot get spend proof"))
+ return "";
+
crypto::hash txid;
if(!epee::string_tools::hex_to_pod(txid_str, txid))
{
@@ -1984,6 +2171,9 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string
}
std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const {
+ if (checkBackgroundSync("cannot get reserve proof"))
+ return "";
+
try
{
clearStatus();
@@ -2030,6 +2220,9 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string
std::string WalletImpl::signMessage(const std::string &message, const std::string &address)
{
+ if (checkBackgroundSync("cannot sign message"))
+ return "";
+
if (address.empty()) {
return m_wallet->sign(message, tools::wallet2::sign_with_spend_key);
}
@@ -2156,6 +2349,16 @@ bool WalletImpl::isDeterministic() const
return m_wallet->is_deterministic();
}
+bool WalletImpl::isBackgroundSyncing() const
+{
+ return m_wallet->is_background_syncing();
+}
+
+bool WalletImpl::isBackgroundWallet() const
+{
+ return m_wallet->is_background_wallet();
+}
+
void WalletImpl::clearStatus() const
{
boost::lock_guard<boost::mutex> l(m_statusMutex);
@@ -2224,9 +2427,7 @@ void WalletImpl::doRefresh()
if(rescan)
m_wallet->rescan_blockchain(false);
m_wallet->refresh(trustedDaemon());
- if (!m_synchronized) {
- m_synchronized = true;
- }
+ m_synchronized = m_wallet->is_synced();
// assuming if we have empty history, it wasn't initialized yet
// for further history changes client need to update history in
// "on_money_received" and "on_money_sent" callbacks
@@ -2329,6 +2530,24 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a
return true;
}
+bool WalletImpl::checkBackgroundSync(const std::string &message) const
+{
+ clearStatus();
+ if (m_wallet->is_background_wallet())
+ {
+ LOG_ERROR("Background wallets " + message);
+ setStatusError(tr("Background wallets ") + message);
+ return true;
+ }
+ if (m_wallet->is_background_syncing())
+ {
+ LOG_ERROR(message + " while background syncing");
+ setStatusError(message + tr(" while background syncing. Stop background syncing first."));
+ return true;
+ }
+ return false;
+}
+
bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error)
{
return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error);
@@ -2347,6 +2566,8 @@ std::string WalletImpl::getDefaultDataDir() const
bool WalletImpl::rescanSpent()
{
clearStatus();
+ if (checkBackgroundSync("cannot rescan spent"))
+ return false;
if (!trustedDaemon()) {
setStatusError(tr("Rescan spent can only be used with a trusted daemon"));
return false;
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 131ca418c..d48d7f130 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -148,6 +148,7 @@ public:
std::string getMultisigInfo() const override;
std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override;
+ std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) override;
bool exportMultisigImages(std::string& images) override;
size_t importMultisigImages(const std::vector<std::string>& images) override;
bool hasMultisigPartialKeyImages() const override;
@@ -172,6 +173,13 @@ public:
bool importOutputs(const std::string &filename) override;
bool scanTransactions(const std::vector<std::string> &txids) override;
+ bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password = optional<std::string>()) override;
+ BackgroundSyncType getBackgroundSyncType() const override;
+ bool startBackgroundSync() override;
+ bool stopBackgroundSync(const std::string &wallet_password) override;
+ bool isBackgroundSyncing() const override;
+ bool isBackgroundWallet() const override;
+
virtual void disposeTransaction(PendingTransaction * t) override;
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
PendingTransaction::Priority priority) const override;
@@ -238,6 +246,7 @@ private:
bool isNewWallet() const;
void pendingTxPostProcess(PendingTransactionImpl * pending);
bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
+ bool checkBackgroundSync(const std::string &message) const;
private:
friend class PendingTransactionImpl;
@@ -253,6 +262,10 @@ private:
mutable boost::mutex m_statusMutex;
mutable int m_status;
mutable std::string m_errorString;
+ // TODO: harden password handling in the wallet API, see relevant discussion
+ // https://github.com/monero-project/monero-gui/issues/1537
+ // https://github.com/feather-wallet/feather/issues/72#issuecomment-1405602142
+ // https://github.com/monero-project/monero/pull/8619#issuecomment-1632951461
std::string m_password;
std::unique_ptr<TransactionHistoryImpl> m_history;
std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback;
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index c27683e66..c374d1574 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -446,6 +446,12 @@ struct Wallet
ConnectionStatus_WrongVersion
};
+ enum BackgroundSyncType {
+ BackgroundSync_Off = 0,
+ BackgroundSync_ReusePassword = 1,
+ BackgroundSync_CustomPassword = 2
+ };
+
virtual ~Wallet() = 0;
virtual std::string seed(const std::string& seed_offset = "") const = 0;
virtual std::string getSeedLanguage() const = 0;
@@ -803,6 +809,15 @@ struct Wallet
*/
virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0;
/**
+ * @brief getMultisigKeyExchangeBooster - obtain partial information for the key exchange round after the in-progress round,
+ * to speed up another signer's key exchange process
+ * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
+ * @param threshold - number of required signers to make valid transaction. Must be <= number of participants.
+ * @param num_signers - total number of multisig participants.
+ * @return new info string if more rounds required or exception if no more rounds (i.e. no rounds to boost)
+ */
+ virtual std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) = 0;
+ /**
* @brief exportMultisigImages - exports transfers' key images
* @param images - output paramter for hex encoded array of images
* @return true if success
@@ -937,6 +952,42 @@ struct Wallet
*/
virtual bool scanTransactions(const std::vector<std::string> &txids) = 0;
+ /*!
+ * \brief setupBackgroundSync - setup background sync mode with just a view key
+ * \param background_sync_type - the mode the wallet background syncs in
+ * \param wallet_password
+ * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type
+ * \return - true on success
+ */
+ virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) = 0;
+
+ /*!
+ * \brief getBackgroundSyncType - get mode the wallet background syncs in
+ * \return - the type, or off if type is unknown
+ */
+ virtual BackgroundSyncType getBackgroundSyncType() const = 0;
+
+ /**
+ * @brief startBackgroundSync - sync the chain in the background with just view key
+ */
+ virtual bool startBackgroundSync() = 0;
+
+ /**
+ * @brief stopBackgroundSync - bring back spend key and process background synced txs
+ * \param wallet_password
+ */
+ virtual bool stopBackgroundSync(const std::string &wallet_password) = 0;
+
+ /**
+ * @brief isBackgroundSyncing - returns true if the wallet is background syncing
+ */
+ virtual bool isBackgroundSyncing() const = 0;
+
+ /**
+ * @brief isBackgroundWallet - returns true if the wallet is a background wallet
+ */
+ virtual bool isBackgroundWallet() const = 0;
+
virtual TransactionHistory * history() = 0;
virtual AddressBook * addressBook() = 0;
virtual Subaddress * subaddress() = 0;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 02e6e80a3..93ac1a938 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -156,6 +156,8 @@ static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1";
+static const std::string BACKGROUND_WALLET_SUFFIX = ".background";
+
boost::mutex tools::wallet2::default_daemon_address_lock;
std::string tools::wallet2::default_daemon_address = "";
@@ -1008,14 +1010,14 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total)
* @param keys_data_key the chacha key that encrypts wallet keys files
* @return crypto::chacha_key the chacha key that encrypts the wallet cache files
*/
-crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key)
+crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key, const unsigned char domain_separator)
{
static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
crypto::chacha_key cache_key;
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE);
- cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
+ cache_key_data[HASH_SIZE] = domain_separator;
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key);
return cache_key;
@@ -1103,7 +1105,7 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<too
boost::lock_guard<boost::mutex> lock(lockers_lock);
if (lockers++ > 0)
locked = false;
- if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only())
+ if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only() || w.is_background_syncing())
{
locked = false;
return;
@@ -1219,6 +1221,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_ignore_outputs_above(MONEY_SUPPLY),
m_ignore_outputs_below(0),
m_track_uses(false),
+ m_is_background_wallet(false),
+ m_background_sync_type(BackgroundSyncOff),
+ m_background_syncing(false),
+ m_processing_background_cache(false),
+ m_custom_background_key(boost::none),
m_show_wallet_name_when_locked(false),
m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT),
m_setup_background_mining(BackgroundMiningMaybe),
@@ -1456,7 +1463,7 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
return false;
}
- const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(threshold, total);
+ const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(ms_status.threshold, ms_status.total);
crypto::secret_key skey;
crypto::public_key pkey;
@@ -1854,6 +1861,9 @@ bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std
//----------------------------------------------------------------------------------------------------
void wallet2::scan_tx(const std::unordered_set<crypto::hash> &txids)
{
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
+ "cannot scan tx from background wallet");
+
// Get the transactions from daemon in batches sorted lowest height to highest
tx_entry_data txs_to_scan = get_tx_entries(txids);
if (txs_to_scan.tx_entries.empty())
@@ -2161,11 +2171,11 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
// if keys are encrypted, ask for password
- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k)
+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k && !m_background_syncing)
{
static critical_section password_lock;
CRITICAL_REGION_LOCAL(password_lock);
- if (!m_encrypt_keys_after_refresh)
+ if (!m_encrypt_keys_after_refresh && !m_processing_background_cache)
{
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
@@ -2177,7 +2187,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
crypto::public_key output_public_key;
THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key");
- if (m_multisig)
+ if (m_multisig || m_background_syncing/*no spend key*/)
{
tx_scan_info.in_ephemeral.pub = output_public_key;
tx_scan_info.in_ephemeral.sec = crypto::null_skey;
@@ -2434,6 +2444,22 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error,
"transactions outputs size=" + std::to_string(tx.vout.size()) +
" not match with daemon response size=" + std::to_string(o_indices.size()));
+
+ // we're going to re-process this receive when background sync is disabled
+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
+ {
+ size_t bgs_idx = m_background_sync_data.txs.size();
+ background_synced_tx_t bgs_tx = {
+ .index_in_background_sync_data = bgs_idx,
+ .tx = tx,
+ .output_indices = o_indices,
+ .height = height,
+ .block_timestamp = ts,
+ .double_spend_seen = double_spend_seen
+ };
+ LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
+ }
}
for(size_t o: outs)
@@ -2459,7 +2485,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
- td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_known = !m_watch_only && !m_multisig && !m_background_syncing;
if (!td.m_key_image_known)
{
// we might have cold signed, and have a mapping to key images
@@ -2649,10 +2675,25 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
set_spent(it->second, height);
if (!ignore_callbacks && 0 != m_callback)
m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
+
+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
+ {
+ size_t bgs_idx = m_background_sync_data.txs.size();
+ background_synced_tx_t bgs_tx = {
+ .index_in_background_sync_data = bgs_idx,
+ .tx = tx,
+ .output_indices = o_indices,
+ .height = height,
+ .block_timestamp = ts,
+ .double_spend_seen = double_spend_seen
+ };
+ LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
+ }
}
}
- if (!pool && m_track_uses)
+ if (!pool && (m_track_uses || (m_background_syncing && it == m_key_images.end())))
{
PERF_TIMER(track_uses);
const uint64_t amount = in_to_key.amount;
@@ -2666,7 +2707,27 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
{
size_t idx = i->second;
THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range");
- m_transfers[idx].m_uses.push_back(std::make_pair(height, txid));
+
+ if (m_track_uses)
+ m_transfers[idx].m_uses.push_back(std::make_pair(height, txid));
+
+ // We'll re-process all txs which *might* be spends when we disable
+ // background sync and retrieve the spend key. We don't know if an
+ // output is a spend in this tx if we don't know its key image.
+ if (m_background_syncing && !m_transfers[idx].m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
+ {
+ size_t bgs_idx = m_background_sync_data.txs.size();
+ background_synced_tx_t bgs_tx = {
+ .index_in_background_sync_data = bgs_idx,
+ .tx = tx,
+ .output_indices = o_indices,
+ .height = height,
+ .block_timestamp = ts,
+ .double_spend_seen = double_spend_seen
+ };
+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
+ }
}
}
}
@@ -2676,7 +2737,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
continue;
for (uint64_t offset: offsets)
if (offset == td.m_global_output_index)
- td.m_uses.push_back(std::make_pair(height, txid));
+ {
+ if (m_track_uses)
+ td.m_uses.push_back(std::make_pair(height, txid));
+ if (m_background_syncing && !td.m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end())
+ {
+ size_t bgs_idx = m_background_sync_data.txs.size();
+ background_synced_tx_t bgs_tx = {
+ .index_in_background_sync_data = bgs_idx,
+ .tx = tx,
+ .output_indices = o_indices,
+ .height = height,
+ .block_timestamp = ts,
+ .double_spend_seen = double_spend_seen
+ };
+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")");
+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)});
+ }
+ }
}
}
}
@@ -3049,8 +3127,8 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh
req.start_height = start_height;
req.no_miner_tx = m_refresh_type == RefreshNoCoinbase;
- req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY;
- if (try_incremental)
+ req.requested_info = (first && !m_background_syncing) ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY;
+ if (try_incremental && !m_background_syncing)
req.pool_info_since = m_pool_info_query_time;
{
@@ -3073,7 +3151,7 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh
<< ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height
<< ", pool info " << static_cast<unsigned int>(res.pool_info_extent));
- if (first)
+ if (first && !m_background_syncing)
{
if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE)
{
@@ -3587,6 +3665,9 @@ void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash
// incremental update anymore, because with that we might miss some txs altogether.
void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental)
{
+ process_txs.clear();
+ if (m_background_syncing)
+ return;
bool updated = false;
if (m_pool_info_query_time != 0 && try_incremental)
{
@@ -4121,6 +4202,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
}
m_first_refresh_done = true;
+ if (m_background_syncing || m_is_background_wallet)
+ m_background_sync_data.first_refresh_done = true;
LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false)));
}
@@ -4203,6 +4286,14 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st
td.m_uses.pop_back();
}
+ for (auto it = m_background_sync_data.txs.begin(); it != m_background_sync_data.txs.end(); )
+ {
+ if(height <= it->second.height)
+ it = m_background_sync_data.txs.erase(it);
+ else
+ ++it;
+ }
+
if (output_tracker_cache)
output_tracker_cache->clear();
@@ -4277,8 +4368,12 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_
// C
THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(),
error::wallet_internal_error, "Daemon claims reorg below last checkpoint");
+
detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache);
+ if (m_background_syncing && height < m_background_sync_data.start_height)
+ m_background_sync_data.start_height = height;
+
if (m_callback)
m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size());
}
@@ -4288,6 +4383,7 @@ bool wallet2::deinit()
if(m_is_initialized) {
m_is_initialized = false;
unlock_keys_file();
+ unlock_background_keys_file();
m_account.deinit();
}
return true;
@@ -4314,6 +4410,7 @@ bool wallet2::clear()
m_device_last_key_image_sync = 0;
m_pool_info_query_time = 0;
m_skip_to_height = 0;
+ m_background_sync_data = background_sync_data_t{};
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -4332,13 +4429,30 @@ void wallet2::clear_soft(bool keep_key_images)
m_scanned_pool_txs[1].clear();
m_pool_info_query_time = 0;
m_skip_to_height = 0;
+ m_background_sync_data = background_sync_data_t{};
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
}
-
+//----------------------------------------------------------------------------------------------------
+void wallet2::clear_user_data()
+{
+ for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i)
+ i->second.m_dests.clear();
+ for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i)
+ i->second.m_dests.clear();
+ for (auto i = m_transfers.begin(); i != m_transfers.end(); ++i)
+ i->m_frozen = false;
+ m_tx_keys.clear();
+ m_tx_notes.clear();
+ m_address_book.clear();
+ m_subaddress_labels.clear();
+ m_attributes.clear();
+ m_account_tags = std::pair<std::map<std::string, std::string>, std::vector<std::string>>();
+}
+//----------------------------------------------------------------------------------------------------
/*!
* \brief Stores wallet information to wallet file.
* \param keys_file_name Name of wallet file
@@ -4350,16 +4464,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
{
boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(password, watch_only);
CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data");
-
+ return store_keys_file_data(keys_file_name, keys_file_data.get());
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only, bool background_keys_file)
+{
+ boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(key, watch_only, background_keys_file);
+ CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data");
+ return store_keys_file_data(keys_file_name, keys_file_data.get(), background_keys_file);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file)
+{
std::string tmp_file_name = keys_file_name + ".new";
std::string buf;
- bool r = ::serialization::dump_binary(keys_file_data.get(), buf);
+ bool r = ::serialization::dump_binary(keys_file_data, buf);
r = r && save_to_file(tmp_file_name, buf);
CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
- unlock_keys_file();
+ if (!background_keys_file)
+ unlock_keys_file();
+ else
+ unlock_background_keys_file();
+
std::error_code e = tools::replace_file(tmp_file_name, keys_file_name);
- lock_keys_file();
+
+ if (!background_keys_file)
+ lock_keys_file();
+ else
+ lock_background_keys_file(keys_file_name);
if (e) {
boost::filesystem::remove(tmp_file_name);
@@ -4372,25 +4505,26 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
//----------------------------------------------------------------------------------------------------
boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only)
{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+ verify_password_with_cached_key(key);
+ return get_keys_file_data(key, watch_only);
+}
+//----------------------------------------------------------------------------------------------------
+boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypto::chacha_key& key, bool watch_only, bool background_keys_file)
+{
epee::byte_slice account_data;
std::string multisig_signers;
std::string multisig_derivations;
cryptonote::account_base account = m_account;
- crypto::chacha_key key;
- crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
-
- // We use m_cache_key as a deterministic test to see if given key corresponds to original password
- const crypto::chacha_key cache_key = derive_cache_key(key);
- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
-
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
{
account.encrypt_viewkey(key);
account.decrypt_keys(key);
}
- if (watch_only)
+ if (watch_only || background_keys_file)
account.forget_spend_key();
account.encrypt_keys(key);
@@ -4525,6 +4659,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetInt(m_track_uses ? 1 : 0);
json.AddMember("track_uses", value2, json.GetAllocator());
+ value2.SetInt(m_background_sync_type);
+ json.AddMember("background_sync_type", value2, json.GetAllocator());
+
value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0);
json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator());
@@ -4585,6 +4722,12 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetInt(m_enable_multisig ? 1 : 0);
json.AddMember("enable_multisig", value2, json.GetAllocator());
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key)
+ {
+ value.SetString(reinterpret_cast<const char*>(m_custom_background_key.get().data()), m_custom_background_key.get().size());
+ json.AddMember("custom_background_key", value, json.GetAllocator());
+ }
+
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -4611,13 +4754,81 @@ void wallet2::setup_keys(const epee::wipeable_string &password)
m_account.decrypt_viewkey(key);
}
- m_cache_key = derive_cache_key(key);
+ m_cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE);
get_ringdb_key();
}
//----------------------------------------------------------------------------------------------------
+void validate_background_cache_password_usage(const tools::wallet2::BackgroundSyncType background_sync_type, const boost::optional<epee::wipeable_string> &background_cache_password, const bool multisig, const bool watch_only, const bool key_on_device)
+{
+ THROW_WALLET_EXCEPTION_IF(multisig || watch_only || key_on_device, error::wallet_internal_error, multisig
+ ? "Background sync not implemented for multisig wallets" : watch_only
+ ? "Background sync not implemented for view only wallets"
+ : "Background sync not implemented for HW wallets");
+
+ switch (background_sync_type)
+ {
+ case tools::wallet2::BackgroundSyncOff:
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "background sync is not enabled");
+ break;
+ }
+ case tools::wallet2::BackgroundSyncReusePassword:
+ {
+ THROW_WALLET_EXCEPTION_IF(background_cache_password, error::wallet_internal_error,
+ "unexpected custom background cache password");
+ break;
+ }
+ case tools::wallet2::BackgroundSyncCustomPassword:
+ {
+ THROW_WALLET_EXCEPTION_IF(!background_cache_password, error::wallet_internal_error,
+ "expected custom background cache password");
+ break;
+ }
+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void get_custom_background_key(const epee::wipeable_string &password, crypto::chacha_key &custom_background_key, const uint64_t kdf_rounds)
+{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds);
+ custom_background_key = derive_cache_key(key, config::HASH_KEY_BACKGROUND_KEYS_FILE);
+}
+//----------------------------------------------------------------------------------------------------
+const crypto::chacha_key wallet2::get_cache_key()
+{
+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing)
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
+ // Domain separate keys used to encrypt background keys file and cache
+ return derive_cache_key(m_custom_background_key.get(), config::HASH_KEY_BACKGROUND_CACHE);
+ }
+ else
+ {
+ return m_cache_key;
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::verify_password_with_cached_key(const epee::wipeable_string &password)
+{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+ verify_password_with_cached_key(key);
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::verify_password_with_cached_key(const crypto::chacha_key &key)
+{
+ // We use m_cache_key as a deterministic test to see if given key corresponds to original password
+ const crypto::chacha_key cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE);
+ THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password)
{
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
+ "cannot change password from background wallet");
+
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
decrypt_keys(original_password);
setup_keys(new_password);
@@ -4676,8 +4887,24 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
std::string account_data;
account_data.resize(keys_file_data.account_data.size());
crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject();
+ if (try_v0_format)
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+
+ // Check if it's a background keys file if both of the above formats fail
+ {
+ m_is_background_wallet = false;
+ m_background_syncing = false;
+ cryptonote::account_base account_data_check;
+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data))
+ {
+ get_custom_background_key(password, key, m_kdf_rounds);
+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+ m_is_background_wallet = !json.Parse(account_data.c_str()).HasParseError() && json.IsObject();
+ m_background_syncing = m_is_background_wallet; // start a background wallet background syncing
+ }
+ }
+
// The contents should be JSON if the wallet follows the new format.
if (json.Parse(account_data.c_str()).HasParseError())
{
@@ -4714,6 +4941,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_ignore_outputs_above = MONEY_SUPPLY;
m_ignore_outputs_below = 0;
m_track_uses = false;
+ m_background_sync_type = BackgroundSyncOff;
m_show_wallet_name_when_locked = false;
m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT;
m_setup_background_mining = BackgroundMiningMaybe;
@@ -4728,6 +4956,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
encrypted_secret_keys = false;
m_enable_multisig = false;
m_allow_mismatched_daemon_version = false;
+ m_custom_background_key = boost::none;
}
else if(json.IsObject())
{
@@ -4955,6 +5184,39 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false);
m_enable_multisig = field_enable_multisig;
+
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff);
+ m_background_sync_type = field_background_sync_type;
+
+ // Load encryption key used to encrypt background cache
+ crypto::chacha_key custom_background_key;
+ m_custom_background_key = boost::none;
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_is_background_wallet)
+ {
+ if (!json.HasMember("custom_background_key"))
+ {
+ LOG_ERROR("Field custom_background_key not found in JSON");
+ return false;
+ }
+ else if (!json["custom_background_key"].IsString())
+ {
+ LOG_ERROR("Field custom_background_key found in JSON, but not String");
+ return false;
+ }
+ else if (json["custom_background_key"].GetStringLength() != sizeof(crypto::chacha_key))
+ {
+ LOG_ERROR("Field custom_background_key found in JSON, but not correct length");
+ return false;
+ }
+ const char *field_custom_background_key = json["custom_background_key"].GetString();
+ memcpy(custom_background_key.data(), field_custom_background_key, sizeof(crypto::chacha_key));
+ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
+ LOG_PRINT_L1("Loaded custom background key derived from custom password");
+ }
+ else if (json.HasMember("custom_background_key"))
+ {
+ LOG_ERROR("Unexpected field custom_background_key found in JSON");
+ }
}
else
{
@@ -5018,12 +5280,17 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
- if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
+ if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
if (r)
- setup_keys(password);
+ {
+ if (!m_is_background_wallet)
+ setup_keys(password);
+ else
+ m_custom_background_key = boost::optional<crypto::chacha_key>(key);
+ }
return true;
}
@@ -5038,11 +5305,12 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
*
*/
-bool wallet2::verify_password(const epee::wipeable_string& password)
+bool wallet2::verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out)
{
// this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded).
unlock_keys_file();
- bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
+ const bool no_spend_key = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet;
+ bool r = verify_password(m_keys_file, password, no_spend_key, m_account.get_device(), m_kdf_rounds, spend_key_out);
lock_keys_file();
return r;
}
@@ -5060,7 +5328,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password)
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
*
*/
-bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds)
+bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out)
{
rapidjson::Document json;
wallet2::keys_file_data keys_file_data;
@@ -5077,9 +5345,22 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
std::string account_data;
account_data.resize(keys_file_data.account_data.size());
crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject();
+ if (try_v0_format)
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+ // Check if it's a background keys file if both of the above formats fail
+ {
+ cryptonote::account_base account_data_check;
+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data))
+ {
+ get_custom_background_key(password, key, kdf_rounds);
+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+ const bool is_background_wallet = json.Parse(account_data.c_str()).HasParseError() && json.IsObject();
+ no_spend_key = no_spend_key || is_background_wallet;
+ }
+ }
+
// The contents should be JSON if the wallet follows the new format.
if (json.Parse(account_data.c_str()).HasParseError())
{
@@ -5104,6 +5385,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
if(!no_spend_key)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
+ spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey;
return r;
}
@@ -5115,9 +5397,7 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key)
void wallet2::decrypt_keys(const crypto::chacha_key &key)
{
- // We use m_cache_key as a deterministic test to see if given key corresponds to original password
- const crypto::chacha_key cache_key = derive_cache_key(key);
- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
+ verify_password_with_cached_key(key);
m_account.encrypt_viewkey(key);
m_account.decrypt_keys(key);
@@ -5509,32 +5789,77 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
}
}
//----------------------------------------------------------------------------------------------------
-std::string wallet2::make_multisig(const epee::wipeable_string &password,
- const std::vector<std::string> &initial_kex_msgs,
- const std::uint32_t threshold)
+epee::misc_utils::auto_scope_leave_caller wallet2::decrypt_account_for_multisig(const epee::wipeable_string &password)
{
// decrypt account keys
+ // note: this conditional's clauses are old and undocumented
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
{
crypto::chacha_key chacha_key;
crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
- m_account.encrypt_viewkey(chacha_key);
- m_account.decrypt_keys(chacha_key);
+ this->decrypt_keys(chacha_key);
keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
- [&, this, chacha_key]()
+ [this, chacha_key]()
{
- m_account.encrypt_keys(chacha_key);
- m_account.decrypt_viewkey(chacha_key);
+ this->encrypt_keys(chacha_key);
}
);
}
- // create multisig account
- multisig::multisig_account multisig_account{
- multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
- multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
+ return keys_reencryptor;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::get_uninitialized_multisig_account(multisig::multisig_account &account_out) const
+{
+ // create uninitialized multisig account
+ account_out = multisig::multisig_account{
+ // k_base = H(normal private spend key)
+ multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_spend_secret_key),
+ // k_view = H(normal private view key)
+ multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_view_secret_key)
};
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::get_reconstructed_multisig_account(multisig::multisig_account &account_out) const
+{
+ const multisig::multisig_account_status ms_status{this->get_multisig_status()};
+ CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active,
+ "The wallet is not multisig, so the multisig account couldn't be reconstructed");
+
+ // reconstruct multisig account
+ crypto::public_key common_pubkey;
+ crypto::secret_key_to_public_key(this->get_account().get_keys().m_view_secret_key, common_pubkey);
+
+ multisig::multisig_keyset_map_memsafe_t kex_origins_map;
+ for (const auto &derivation : m_multisig_derivations)
+ kex_origins_map[derivation];
+
+ account_out = multisig::multisig_account{
+ m_multisig_threshold,
+ m_multisig_signers,
+ this->get_account().get_keys().m_spend_secret_key,
+ this->get_account().get_keys().m_view_secret_key,
+ this->get_account().get_keys().m_multisig_keys,
+ this->get_account().get_keys().m_view_secret_key,
+ m_account_public_address.m_spend_public_key,
+ common_pubkey,
+ m_multisig_rounds_passed,
+ std::move(kex_origins_map),
+ ""
+ };
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::make_multisig(const epee::wipeable_string &password,
+ const std::vector<std::string> &initial_kex_msgs,
+ const std::uint32_t threshold)
+{
+ // decrypt account keys
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
+
+ // create multisig account
+ multisig::multisig_account multisig_account;
+ this->get_uninitialized_multisig_account(multisig_account);
// open initial kex messages, validate them, extract signers
std::vector<multisig::multisig_kex_msg> expanded_msgs;
@@ -5542,7 +5867,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
expanded_msgs.reserve(initial_kex_msgs.size());
signers.reserve(initial_kex_msgs.size() + 1);
- for (const auto &msg : initial_kex_msgs)
+ for (const std::string &msg : initial_kex_msgs)
{
expanded_msgs.emplace_back(msg);
@@ -5551,17 +5876,19 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1,
"Trying to make multisig with message that has invalid multisig kex round (should be '1').");
- // 2. duplicate signers not allowed
+ // 2. duplicate signers not allowed (the number of signers is implied by the number of initial kex messages passed
+ // in, so we can't just ignore duplicates here)
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(),
"Duplicate signers not allowed when converting a wallet to multisig.");
- // add signer (skip self for now)
- if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey())
- signers.push_back(expanded_msgs.back().get_signing_pubkey());
+ // add signer
+ signers.push_back(expanded_msgs.back().get_signing_pubkey());
}
- // add self to signers
- signers.push_back(multisig_account.get_base_pubkey());
+ // expect that self is in the input list (this guarantees that the input list size always equals the number of intended
+ // signers for the account [when combined with duplicate checking])
+ CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), multisig_account.get_base_pubkey()) != signers.end(),
+ "The local account's signer key was not found in initial multisig kex messages when converting a wallet to multisig.");
// intialize key exchange
multisig_account.initialize_kex(threshold, signers, expanded_msgs);
@@ -5572,12 +5899,13 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
{
// Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages
// (making a wallet multisig overwrites those keys, see account_base::make_multisig)
- m_original_address = get_account().get_keys().m_account_address;
- m_original_view_secret_key = get_account().get_keys().m_view_secret_key;
+ m_original_address = this->get_account().get_keys().m_account_address;
+ m_original_view_secret_key = this->get_account().get_keys().m_view_secret_key;
m_original_keys_available = true;
}
- clear();
+ // clear wallet caches
+ this->clear();
// account base
MINFO("Creating multisig address...");
@@ -5587,14 +5915,14 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
multisig_account.get_multisig_privkeys()),
"Failed to create multisig wallet account due to bad keys");
- init_type(hw::device::device_type::SOFTWARE);
+ this->init_type(hw::device::device_type::SOFTWARE);
m_original_keys_available = true;
m_multisig = true;
m_multisig_threshold = threshold;
m_multisig_signers = signers;
m_multisig_rounds_passed = 1;
- // derivations stored (should be empty in last round)
+ // derivations stored (note: should be empty in last kex round)
m_multisig_derivations.clear();
m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
@@ -5608,12 +5936,12 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
if (!m_wallet_file.empty())
- create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
+ this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
- setup_new_blockchain();
+ this->setup_new_blockchain();
if (!m_wallet_file.empty())
- store();
+ this->store();
return multisig_account.get_next_kex_round_msg();
}
@@ -5622,45 +5950,15 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
const std::vector<std::string> &kex_messages,
const bool force_update_use_with_caution /*= false*/)
{
- const multisig::multisig_account_status ms_status{get_multisig_status()};
+ const multisig::multisig_account_status ms_status{this->get_multisig_status()};
CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active, "The wallet is not multisig");
// decrypt account keys
- epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
- {
- crypto::chacha_key chacha_key;
- crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
- m_account.encrypt_viewkey(chacha_key);
- m_account.decrypt_keys(chacha_key);
- keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
- [&, this, chacha_key]()
- {
- m_account.encrypt_keys(chacha_key);
- m_account.decrypt_viewkey(chacha_key);
- }
- );
- }
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
// reconstruct multisig account
- multisig::multisig_keyset_map_memsafe_t kex_origins_map;
-
- for (const auto &derivation : m_multisig_derivations)
- kex_origins_map[derivation];
-
- multisig::multisig_account multisig_account{
- m_multisig_threshold,
- m_multisig_signers,
- get_account().get_keys().m_spend_secret_key,
- crypto::null_skey, //base common privkey: not used
- get_account().get_keys().m_multisig_keys,
- get_account().get_keys().m_view_secret_key,
- m_account_public_address.m_spend_public_key,
- m_account_public_address.m_view_public_key,
- m_multisig_rounds_passed,
- std::move(kex_origins_map),
- ""
- };
+ multisig::multisig_account multisig_account;
+ this->get_reconstructed_multisig_account(multisig_account);
// KLUDGE: early return if there are no kex messages and main kex is complete (will return the post-kex verification round
// message) (it's a kludge because this behavior would be more appropriate for a standalone wallet method)
@@ -5676,7 +5974,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
std::vector<multisig::multisig_kex_msg> expanded_msgs;
expanded_msgs.reserve(kex_messages.size());
- for (const auto &msg : kex_messages)
+ for (const std::string &msg : kex_messages)
expanded_msgs.emplace_back(msg);
// update multisig kex
@@ -5712,27 +6010,27 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
if (!m_wallet_file.empty())
{
- bool r = store_keys(m_keys_file, password, false);
+ bool r = this->store_keys(m_keys_file, password, false);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
if (boost::filesystem::exists(m_wallet_file + ".address.txt"))
{
- r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
+ r = this->save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
if(!r) MERROR("String with address text not saved");
}
}
m_subaddresses.clear();
m_subaddress_labels.clear();
- add_subaddress_account(tr("Primary account"));
+ this->add_subaddress_account(tr("Primary account"));
if (!m_wallet_file.empty())
- store();
+ this->store();
}
// wallet/file relationship
if (!m_wallet_file.empty())
- create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
+ this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
return multisig_account.get_next_kex_round_msg();
}
@@ -5740,16 +6038,63 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
std::string wallet2::get_multisig_first_kex_msg() const
{
// create multisig account
- multisig::multisig_account multisig_account{
- // k_base = H(normal private spend key)
- multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
- // k_view = H(normal private view key)
- multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
- };
+ multisig::multisig_account multisig_account;
+ this->get_uninitialized_multisig_account(multisig_account);
return multisig_account.get_next_kex_round_msg();
}
//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_multisig_key_exchange_booster(const epee::wipeable_string &password,
+ const std::vector<std::string> &kex_messages,
+ const std::uint32_t threshold,
+ const std::uint32_t num_signers)
+{
+ CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in.");
+
+ // decrypt account keys
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)};
+
+ // prepare multisig account
+ multisig::multisig_account multisig_account;
+
+ const multisig::multisig_account_status ms_status{this->get_multisig_status()};
+ CHECK_AND_ASSERT_THROW_MES(!ms_status.is_ready, "Multisig wallet creation process has already been finished.");
+
+ if (ms_status.multisig_is_active)
+ {
+ // case: this wallet is in the middle of multisig key exchange
+ // - boost the round that comes after the in-progress round
+
+ CHECK_AND_ASSERT_THROW_MES(threshold == m_multisig_threshold,
+ "Expected threshold does not match multisig wallet setting.");
+ CHECK_AND_ASSERT_THROW_MES(num_signers == m_multisig_signers.size(),
+ "Expected number of signers does not match multisig wallet setting.");
+
+ // reconstruct multisig account
+ this->get_reconstructed_multisig_account(multisig_account);
+ }
+ else
+ {
+ // case: make_multisig() has not been called
+ // DANGER: If 'num_signers - threshold > 1', but this wallet's future multisig settings
+ // will be 'num_signers - threshold == 1', then the booster message WILL leak the
+ // future multisig wallet's private keys in this case where the wallet2 multisig wallet is uninitialized.
+
+ this->get_uninitialized_multisig_account(multisig_account);
+ }
+
+ // open kex messages
+ std::vector<multisig::multisig_kex_msg> expanded_msgs;
+ expanded_msgs.reserve(kex_messages.size());
+
+ for (const std::string &msg : kex_messages)
+ expanded_msgs.emplace_back(msg);
+
+ // get kex booster message
+ // note: booster does not change wallet state other than decrypting/reencrypting account keys
+ return multisig_account.get_multisig_kex_round_booster(threshold, num_signers, expanded_msgs).get_msg();
+}
+//----------------------------------------------------------------------------------------------------
multisig::multisig_account_status wallet2::get_multisig_status() const
{
multisig::multisig_account_status ret;
@@ -5803,11 +6148,30 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin
{
if (wallet_name.empty())
return;
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
+ "cannot change wallet settings from background wallet");
prepare_file_names(wallet_name);
boost::system::error_code ignored_ec;
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
bool r = store_keys(m_keys_file, password, m_watch_only);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+
+ // Update the background keys file when we rewrite the main wallet keys file
+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_custom_background_key)
+ {
+ const std::string background_keys_filename = make_background_keys_file_name(wallet_name);
+ if (!lock_background_keys_file(background_keys_filename))
+ {
+ LOG_ERROR("Background keys file " << background_keys_filename << " is opened by another wallet program and cannot be rewritten");
+ return; // not fatal, background keys file will just have different wallet settings
+ }
+ store_background_keys(m_custom_background_key.get());
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
+ }
+ else if (m_background_sync_type == BackgroundSyncReusePassword)
+ {
+ reset_background_sync_data(m_background_sync_data);
+ }
}
/*!
* \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there)
@@ -5841,6 +6205,16 @@ bool wallet2::wallet_valid_path_format(const std::string& file_path)
return !file_path.empty();
}
//----------------------------------------------------------------------------------------------------
+std::string wallet2::make_background_wallet_file_name(const std::string &wallet_file)
+{
+ return wallet_file + BACKGROUND_WALLET_SUFFIX;
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::make_background_keys_file_name(const std::string &wallet_file)
+{
+ return make_background_wallet_file_name(wallet_file) + ".keys";
+}
+//----------------------------------------------------------------------------------------------------
bool wallet2::parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id)
{
cryptonote::blobdata payment_id_data;
@@ -6066,10 +6440,78 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer");
}
- wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password);
+ wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_is_background_wallet, password);
//keys loaded ok!
//try to load wallet cache. but even if we failed, it is not big problem
+ load_wallet_cache(use_fs, cache_buf);
+
+ // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks.
+ // Here we erase these multisig keys if they're zero'd out to free up space.
+ for (auto &td : m_transfers)
+ {
+ auto mk_it = td.m_multisig_k.begin();
+ while (mk_it != td.m_multisig_k.end())
+ {
+ if (*mk_it == rct::zero())
+ mk_it = td.m_multisig_k.erase(mk_it);
+ else
+ ++mk_it;
+ }
+ }
+
+ cryptonote::block genesis;
+ generate_genesis(genesis);
+ crypto::hash genesis_hash = get_block_hash(genesis);
+
+ if (m_blockchain.empty())
+ {
+ m_blockchain.push_back(genesis_hash);
+ m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
+ }
+ else
+ {
+ check_genesis(genesis_hash);
+ }
+
+ trim_hashchain();
+
+ if (get_num_subaddress_accounts() == 0)
+ add_subaddress_account(tr("Primary account"));
+
+ try
+ {
+ find_and_save_rings(false);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to save rings, will try again next time");
+ }
+
+ try
+ {
+ if (use_fs)
+ m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to initialize MMS, it will be unusable");
+ }
+
+ try
+ {
+ if (use_fs)
+ process_background_cache_on_open();
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to process background cache on open: " << e.what());
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf)
+{
+ boost::system::error_code e;
bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty();
if (cache_missing)
{
@@ -6083,7 +6525,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
bool r = true;
if (use_fs)
{
- load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max());
+ r = load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max());
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file);
}
@@ -6096,7 +6538,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"');
std::string cache_data;
cache_data.resize(cache_file_data.cache_data.size());
- crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]);
+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), get_cache_key(), cache_file_data.iv, &cache_data[0]);
try {
bool loaded = false;
@@ -6186,57 +6628,76 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key,
error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
}
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::process_background_cache_on_open()
+{
+ if (m_wallet_file.empty())
+ return;
+ if (m_background_syncing || m_is_background_wallet)
+ return;
+ if (m_background_sync_type == BackgroundSyncOff)
+ return;
- // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks.
- // Here we erase these multisig keys if they're zero'd out to free up space.
- for (auto &td : m_transfers)
+ if (m_background_sync_type == BackgroundSyncReusePassword)
{
- auto mk_it = td.m_multisig_k.begin();
- while (mk_it != td.m_multisig_k.end())
+ const background_sync_data_t background_sync_data = m_background_sync_data;
+ const hashchain blockchain = m_blockchain;
+ process_background_cache(background_sync_data, blockchain, m_last_block_reward);
+
+ // Reset the background cache after processing
+ reset_background_sync_data(m_background_sync_data);
+ }
+ else if (m_background_sync_type == BackgroundSyncCustomPassword)
+ {
+ // If the background wallet files don't exist, recreate them
+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file);
+ const std::string background_wallet_file = make_background_wallet_file_name(m_wallet_file);
+ const bool background_keys_file_exists = boost::filesystem::exists(background_keys_file);
+ const bool background_wallet_exists = boost::filesystem::exists(background_wallet_file);
+
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_keys_file), error::background_wallet_already_open, background_wallet_file);
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
+
+ if (!background_keys_file_exists)
{
- if (*mk_it == rct::zero())
- mk_it = td.m_multisig_k.erase(mk_it);
- else
- ++mk_it;
+ MDEBUG("Background keys file not found, restoring");
+ store_background_keys(m_custom_background_key.get());
}
- }
- cryptonote::block genesis;
- generate_genesis(genesis);
- crypto::hash genesis_hash = get_block_hash(genesis);
+ if (!background_wallet_exists)
+ {
+ MDEBUG("Background cache not found, restoring");
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
+ return;
+ }
- if (m_blockchain.empty())
- {
- m_blockchain.push_back(genesis_hash);
- m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
- }
- else
- {
- check_genesis(genesis_hash);
- }
+ MDEBUG("Loading background cache");
- trim_hashchain();
+ // Set up a minimal background wallet2 instance
+ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype));
+ background_w2->m_is_background_wallet = true;
+ background_w2->m_background_syncing = true;
+ background_w2->m_background_sync_type = m_background_sync_type;
+ background_w2->m_custom_background_key = m_custom_background_key;
- if (get_num_subaddress_accounts() == 0)
- add_subaddress_account(tr("Primary account"));
+ cryptonote::account_base account = m_account;
+ account.forget_spend_key();
+ background_w2->m_account = account;
- try
- {
- find_and_save_rings(false);
- }
- catch (const std::exception &e)
- {
- MERROR("Failed to save rings, will try again next time");
- }
-
- try
- {
- if (use_fs)
- m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats);
+ // Load background cache from file
+ background_w2->clear();
+ background_w2->prepare_file_names(background_wallet_file);
+ background_w2->load_wallet_cache(true/*use_fs*/);
+
+ process_background_cache(background_w2->m_background_sync_data, background_w2->m_blockchain, background_w2->m_last_block_reward);
+
+ // Reset the background cache after processing
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
}
- catch (const std::exception &e)
+ else
{
- MERROR("Failed to initialize MMS, it will be unusable");
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
}
}
//----------------------------------------------------------------------------------------------------
@@ -6318,6 +6779,8 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
same_file = canonical_old_path == canonical_new_path;
}
+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet && !same_file, error::wallet_internal_error,
+ "Cannot save background wallet files to a different location");
if (!same_file)
{
@@ -6334,6 +6797,21 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
}
}
}
+ else if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing && !m_is_background_wallet)
+ {
+ // We're background syncing, so store the wallet cache as a background cache
+ // keeping the background sync data
+ try
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
+ store_background_cache(m_custom_background_key.get(), false/*do_reset_background_sync_data*/);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to store background cache while background syncing: " << e.what());
+ }
+ return;
+ }
// get wallet cache data
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data();
@@ -6427,6 +6905,22 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
// store should only exist if the MMS is really active
m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file);
}
+
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_background_syncing && !m_is_background_wallet)
+ {
+ // Update the background wallet cache when we store the main wallet cache
+ // Note: if background syncing when this is called, it means the background
+ // wallet is open and was already stored above
+ try
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set");
+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to update background cache: " << e.what());
+ }
+ }
}
//----------------------------------------------------------------------------------------------------
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
@@ -6444,7 +6938,7 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
std::string cipher;
cipher.resize(cache_file_data.get().cache_data.size());
cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>();
- crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]);
+ crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), get_cache_key(), cache_file_data.get().iv, &cipher[0]);
cache_file_data.get().cache_data = cipher;
return cache_file_data;
}
@@ -8495,6 +8989,34 @@ bool wallet2::is_keys_file_locked() const
return m_keys_file_locker->locked();
}
+bool wallet2::lock_background_keys_file(const std::string &background_keys_file)
+{
+ if (background_keys_file.empty() || !boost::filesystem::exists(background_keys_file))
+ return true;
+ if (m_background_keys_file_locker && m_background_keys_file_locker->locked())
+ return true;
+ m_background_keys_file_locker.reset(new tools::file_locker(background_keys_file));
+ return m_background_keys_file_locker->locked();
+}
+
+bool wallet2::unlock_background_keys_file()
+{
+ if (!m_background_keys_file_locker)
+ {
+ MDEBUG("background keys file locker is not set");
+ return false;
+ }
+ m_background_keys_file_locker.reset();
+ return true;
+}
+
+bool wallet2::is_background_keys_file_locked() const
+{
+ if (!m_background_keys_file_locker)
+ return false;
+ return m_background_keys_file_locker->locked();
+}
+
bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const
{
if (!unlocked) // don't add locked outs
@@ -13176,6 +13698,413 @@ bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool o
return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none);
}
+/*
+ In background sync mode, we use just the view key when the wallet is scanning
+ to identify all txs where:
+
+ 1. We received an output.
+ 2. We spent an output.
+ 3. We *may* have spent a received output but we didn't know for sure because
+ the spend key was not loaded while background sync was enabled.
+
+ When the user is ready to use the spend key again, we call this function to
+ process all those background synced transactions with the spend key loaded,
+ so that we can properly generate key images for the transactions which we
+ we were not able to do so for while background sync was enabled. This allows
+ us to determine *all* receives and spends the user completed while the wallet
+ had background sync enabled. Once this function completes, we can continue
+ scanning from where the background sync left off.
+
+ Txs of type 3 (txs which we *may* have spent received output(s)) are txs where
+ 1+ rings contain an output that the user received and the wallet does not know
+ the associated key image for that output. We don't know if the user spent in
+ this type of tx or not. This function will generate key images for all outputs
+ we don't know key images for, and then check if those outputs were spent in
+ the txs of type 3.
+
+ By storing this type of "plausible spend tx" when scanning in background sync
+ mode, we avoid the need to query the daemon with key images when background
+ sync mode is disabled to see if those key images were spent. This would
+ reveal key images to 3rd party nodes for users who don't run their own.
+ Although this is not a perfect solution to avoid revealing key images to a 3rd
+ party node (since tx submission trivially reveals key images to a node), it's
+ probably better than revealing *unused* key images to a 3rd party node, which
+ would enable the 3rd party to deduce that a tx is spending an output at least
+ X old when the key image is included in the chain.
+*/
+void wallet2::process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_synced_chain, uint64_t last_block_reward)
+{
+ // We expect the spend key to be in a decrypted state while
+ // m_processing_background_cache is true
+ m_processing_background_cache = true;
+ auto done_processing = epee::misc_utils::create_scope_leave_handler([&, this]() {
+ m_processing_background_cache = false;
+ });
+
+ if (m_background_syncing || m_multisig || m_watch_only || key_on_device())
+ return;
+
+ if (!background_sync_data.first_refresh_done)
+ {
+ MDEBUG("Skipping processing background cache, background cache has not synced yet");
+ return;
+ }
+
+ // Skip processing if wallet cache is synced higher than background cache
+ const uint64_t current_height = m_blockchain.size();
+ const uint64_t background_height = background_synced_chain.size();
+ MDEBUG("Background cache height " << background_height << " , wallet height " << current_height);
+ if (current_height > background_height)
+ {
+ MWARNING("Skipping processing background cache, synced height is higher than background cache");
+ return;
+ }
+
+ if (m_refresh_from_block_height < background_sync_data.wallet_refresh_from_block_height ||
+ m_subaddress_lookahead_major > background_sync_data.subaddress_lookahead_major ||
+ m_subaddress_lookahead_minor > background_sync_data.subaddress_lookahead_minor ||
+ m_refresh_type < background_sync_data.wallet_refresh_type)
+ {
+ MWARNING("Skipping processing background cache, background wallet sync settings did not match main wallet's");
+ MDEBUG("Wallet settings: " <<
+ ", m_refresh_from_block_height: " << m_refresh_from_block_height << " vs " << background_sync_data.wallet_refresh_from_block_height <<
+ ", m_subaddress_lookahead_major: " << m_subaddress_lookahead_major << " vs " << background_sync_data.subaddress_lookahead_major <<
+ ", m_subaddress_lookahead_minor: " << m_subaddress_lookahead_minor << " vs " << background_sync_data.subaddress_lookahead_minor <<
+ ", m_refresh_type: " << m_refresh_type << " vs " << background_sync_data.wallet_refresh_type);
+ return;
+ }
+
+ // Sort background synced txs in the order they appeared in the cache so that
+ // we process them in the order they appeared in the chain. Thus if tx2 spends
+ // from tx1, we will know because tx1 is processed before tx2.
+ std::vector<std::pair<crypto::hash, background_synced_tx_t>> sorted_bgs_cache(background_sync_data.txs.begin(), background_sync_data.txs.end());
+ std::sort(sorted_bgs_cache.begin(), sorted_bgs_cache.end(),
+ [](const std::pair<crypto::hash, background_synced_tx_t>& l, const std::pair<crypto::hash, background_synced_tx_t>& r)
+ {
+ uint64_t left_index = l.second.index_in_background_sync_data;
+ uint64_t right_index = r.second.index_in_background_sync_data;
+ THROW_WALLET_EXCEPTION_IF(
+ (left_index < right_index && l.second.height > r.second.height) ||
+ (left_index > right_index && l.second.height < r.second.height),
+ error::wallet_internal_error, "Unexpected background sync data order");
+ return left_index < right_index;
+ });
+
+ // All txs in the background cache should have height >= sync start height,
+ // but not fatal if not
+ if (!sorted_bgs_cache.empty() && sorted_bgs_cache[0].second.height < background_sync_data.start_height)
+ MWARNING("First tx in background cache has height (" << sorted_bgs_cache[0].second.height << ") lower than sync start height (" << background_sync_data.start_height << ")");
+
+ // We want to process all background synced txs in order to make sure
+ // the wallet state updates correctly. First we remove all txs from the wallet
+ // from before the background sync start height, then re-process them in
+ // chronological order. The background cache should contain a superset of
+ // *all* the wallet's txs from after the background sync start height.
+ MDEBUG("Processing " << background_sync_data.txs.size() << " background synced txs starting from height " << background_sync_data.start_height);
+ detached_blockchain_data dbd = detach_blockchain(background_sync_data.start_height);
+
+ for (const auto &bgs_tx : sorted_bgs_cache)
+ {
+ MDEBUG("Processing background synced tx " << bgs_tx.first);
+
+ process_new_transaction(bgs_tx.first, bgs_tx.second.tx, bgs_tx.second.output_indices, bgs_tx.second.height, 0, bgs_tx.second.block_timestamp,
+ cryptonote::is_coinbase(bgs_tx.second.tx), false/*pool*/, bgs_tx.second.double_spend_seen, {}, {}, true/*ignore_callbacks*/);
+
+ // Re-set destination addresses if they were previously set
+ if (m_confirmed_txs.find(bgs_tx.first) != m_confirmed_txs.end() &&
+ dbd.detached_confirmed_txs_dests.find(bgs_tx.first) != dbd.detached_confirmed_txs_dests.end())
+ {
+ m_confirmed_txs[bgs_tx.first].m_dests = std::move(dbd.detached_confirmed_txs_dests[bgs_tx.first]);
+ }
+ }
+
+ m_blockchain = background_synced_chain;
+ m_last_block_reward = last_block_reward;
+
+ MDEBUG("Finished processing background sync data");
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::reset_background_sync_data(background_sync_data_t &background_sync_data)
+{
+ background_sync_data.first_refresh_done = false;
+ background_sync_data.start_height = get_blockchain_current_height();
+ background_sync_data.txs.clear();
+
+ background_sync_data.wallet_refresh_from_block_height = m_refresh_from_block_height;
+ background_sync_data.subaddress_lookahead_major = m_subaddress_lookahead_major;
+ background_sync_data.subaddress_lookahead_minor = m_subaddress_lookahead_minor;
+ background_sync_data.wallet_refresh_type = m_refresh_type;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data)
+{
+ MDEBUG("Storing background cache (do_reset_background_sync_data=" << do_reset_background_sync_data << ")");
+
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error,
+ "Can only write a background cache when using a custom background password");
+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error,
+ "No wallet file known, can't store background cache");
+
+ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype));
+ background_w2->prepare_file_names(make_background_wallet_file_name(m_wallet_file));
+
+ // Make sure background wallet is opened by this wallet
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_w2->m_keys_file),
+ error::background_wallet_already_open, background_w2->m_wallet_file);
+
+ // Load a background wallet2 instance using this wallet2 instance
+ std::string this_wallet2;
+ bool r = ::serialization::dump_binary(*this, this_wallet2);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to serialize wallet cache");
+
+ background_w2->clear();
+ r = ::serialization::parse_binary(this_wallet2, *background_w2);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to deserialize wallet cache");
+
+ // Clear sensitive data from background cache not needed to sync
+ background_w2->clear_user_data();
+
+ background_w2->m_is_background_wallet = true;
+ if (do_reset_background_sync_data)
+ reset_background_sync_data(background_w2->m_background_sync_data);
+ else
+ background_w2->m_background_sync_data = m_background_sync_data;
+ background_w2->m_background_syncing = true;
+
+ background_w2->m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
+ background_w2->m_background_sync_type = m_background_sync_type;
+ background_w2->store();
+
+ MDEBUG("Background cache stored (" << background_w2->m_transfers.size() << " transfers, " << background_w2->m_background_sync_data.txs.size() << " background synced txs)");
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::store_background_keys(const crypto::chacha_key &custom_background_key)
+{
+ MDEBUG("Storing background keys");
+
+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error,
+ "No wallet file known, can't store background keys");
+
+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file);
+ bool r = store_keys(background_keys_file, custom_background_key, false/*watch_only*/, true/*background_keys_file*/);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, background_keys_file);
+ THROW_WALLET_EXCEPTION_IF(!is_background_keys_file_locked(), error::wallet_internal_error, background_keys_file + "\" should be locked");
+
+ // GUI uses the address file to differentiate non-mainnet wallets in the UI
+ const std::string background_address_file = make_background_wallet_file_name(m_wallet_file) + ".address.txt";
+ if (m_nettype != MAINNET && !boost::filesystem::exists(background_address_file))
+ {
+ r = save_to_file(background_address_file, m_account.get_public_address_str(m_nettype), true);
+ if (!r) MERROR("String with address text not saved");
+ }
+
+ MDEBUG("Background keys stored");
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password)
+{
+ MDEBUG("Storing background sync wallet");
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error,
+ "Can only write a background sync wallet when using a custom background password");
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
+ "Can't write background sync wallet from an existing background cache");
+ THROW_WALLET_EXCEPTION_IF(wallet_password == background_cache_password,
+ error::background_custom_password_same_as_wallet_password);
+
+ // Set the background encryption key
+ crypto::chacha_key custom_background_key;
+ get_custom_background_key(background_cache_password, custom_background_key, m_kdf_rounds);
+
+ // Keep the background encryption key in memory so the main wallet can update
+ // the background cache when it stores the main wallet cache
+ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key);
+
+ if (m_wallet_file.empty() || m_keys_file.empty())
+ return;
+
+ // Save background keys file, then background cache, then update main wallet settings
+ store_background_keys(custom_background_key);
+ store_background_cache(custom_background_key, true/*do_reset_background_sync_data*/);
+ bool r = store_keys(m_keys_file, wallet_password, false/*watch_only*/);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+
+ MDEBUG("Background sync wallet saved successfully");
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password)
+{
+ MDEBUG("Setting background sync to type " << background_sync_type);
+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error,
+ "Can't set background sync type from an existing background cache");
+ verify_password_with_cached_key(wallet_password);
+
+ if (background_sync_type != BackgroundSyncOff)
+ validate_background_cache_password_usage(background_sync_type, background_cache_password, m_multisig, m_watch_only, key_on_device());
+
+ THROW_WALLET_EXCEPTION_IF(background_sync_type == BackgroundSyncCustomPassword && wallet_password == background_cache_password,
+ error::background_custom_password_same_as_wallet_password);
+
+ if (m_background_sync_type == background_sync_type && background_sync_type != BackgroundSyncCustomPassword)
+ return; // No need to make any changes
+
+ if (!m_wallet_file.empty())
+ {
+ // Delete existing background files if they already exist
+ const std::string old_background_wallet_file = make_background_wallet_file_name(m_wallet_file);
+ const std::string old_background_keys_file = make_background_keys_file_name(m_wallet_file);
+ const std::string old_background_address_file = old_background_wallet_file + ".address.txt";
+
+ // Make sure no other program is using the background wallet
+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(old_background_keys_file),
+ error::background_wallet_already_open, old_background_wallet_file);
+
+ if (boost::filesystem::exists(old_background_wallet_file))
+ if (!boost::filesystem::remove(old_background_wallet_file))
+ LOG_ERROR("Error deleting background wallet file: " << old_background_wallet_file);
+
+ if (boost::filesystem::exists(old_background_keys_file))
+ if (!boost::filesystem::remove(old_background_keys_file))
+ LOG_ERROR("Error deleting background keys file: " << old_background_keys_file);
+
+ if (boost::filesystem::exists(old_background_address_file))
+ if (!boost::filesystem::remove(old_background_address_file))
+ LOG_ERROR("Error deleting background address file: " << old_background_address_file);
+ }
+
+ m_background_sync_type = background_sync_type;
+ m_custom_background_key = boost::none;
+
+ // Write the new files
+ switch (background_sync_type)
+ {
+ case BackgroundSyncOff:
+ case BackgroundSyncReusePassword: rewrite(m_wallet_file, wallet_password); break;
+ case BackgroundSyncCustomPassword: write_background_sync_wallet(wallet_password, background_cache_password.get()); break;
+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type");
+ }
+
+ MDEBUG("Done setting background sync type");
+}
+//----------------------------------------------------------------------------------------------------
+/*
+ When background syncing, the wallet scans using just the view key, without
+ keeping the spend key in decrypted state. When a user returns to the wallet
+ and decrypts the spend key, the wallet processes the background synced txs,
+ then the wallet picks up scanning normally right where the background sync
+ left off.
+*/
+void wallet2::start_background_sync()
+{
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error,
+ "must setup background sync first before using background sync");
+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error,
+ "Can't start background syncing from a background wallet (it is always background syncing)");
+
+ MDEBUG("Starting background sync");
+
+ if (m_background_syncing)
+ {
+ MDEBUG("Already background syncing");
+ return;
+ }
+
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty())
+ {
+ // Save the current state of the wallet cache. Only necessary when using a
+ // custom background password which uses distinct background wallet to sync.
+ // When reusing wallet password to sync we reuse the main wallet cache.
+ store();
+
+ // Wipe user data from the background wallet cache not needed to sync.
+ // Only wipe user data from background cache if wallet cache is stored
+ // on disk; otherwise we could lose the data.
+ clear_user_data();
+
+ // Wipe m_cache_key since it can be used to decrypt main wallet cache
+ m_cache_key.scrub();
+ }
+
+ reset_background_sync_data(m_background_sync_data);
+ m_background_syncing = true;
+
+ // Wipe the spend key from memory
+ m_account.forget_spend_key();
+
+ MDEBUG("Background sync started at height " << m_background_sync_data.start_height);
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key)
+{
+ MDEBUG("Stopping background sync");
+
+ // Verify provided password and spend secret key. If no spend secret key is
+ // provided, recover it from the wallet keys file
+ crypto::secret_key recovered_spend_key = crypto::null_skey;
+ if (!m_wallet_file.empty())
+ {
+ THROW_WALLET_EXCEPTION_IF(!verify_password(wallet_password, recovered_spend_key), error::invalid_password);
+ }
+ else
+ {
+ verify_password_with_cached_key(wallet_password);
+ }
+
+ if (spend_secret_key != crypto::null_skey)
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_wallet_file.empty() && spend_secret_key != recovered_spend_key,
+ error::invalid_spend_key);
+ MDEBUG("Setting spend secret key with the provided key");
+ recovered_spend_key = spend_secret_key;
+ }
+
+ // Verify private spend key derives to wallet's public spend key
+ const auto verify_spend_key = [this](crypto::secret_key &recovered_spend_key) -> bool
+ {
+ crypto::public_key spend_public_key;
+ return recovered_spend_key != crypto::null_skey &&
+ crypto::secret_key_to_public_key(recovered_spend_key, spend_public_key) &&
+ m_account.get_keys().m_account_address.m_spend_public_key == spend_public_key;
+ };
+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key);
+
+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error,
+ "must setup background sync first before using background sync");
+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error,
+ "Can't stop background syncing from a background wallet");
+
+ if (!m_background_syncing)
+ return;
+
+ // Copy background cache, we're about to overwrite it
+ const background_sync_data_t background_sync_data = m_background_sync_data;
+ const hashchain background_synced_chain = m_blockchain;
+ const uint64_t last_block_reward = m_last_block_reward;
+
+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty())
+ {
+ // Reload the wallet from disk
+ load(m_wallet_file, wallet_password);
+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key);
+ }
+ m_background_syncing = false;
+
+ // Set the plaintext spend key
+ m_account.set_spend_key(recovered_spend_key);
+
+ // Encrypt the spend key when done if needed
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]{encrypt_keys(wallet_password);});
+
+ // Now we can use the decrypted spend key to process background cache
+ process_background_cache(background_sync_data, background_synced_chain, last_block_reward);
+
+ // Reset the background cache after processing
+ reset_background_sync_data(m_background_sync_data);
+
+ MDEBUG("Background sync stopped");
+}
+//----------------------------------------------------------------------------------------------------
wallet2::payment_container wallet2::export_payments() const
{
payment_container payments;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index b3f8a6bea..35dffbc36 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -86,6 +86,7 @@
class Serialization_portability_wallet_Test;
class wallet_accessor_test;
+namespace multisig { class multisig_account; }
namespace tools
{
@@ -249,6 +250,20 @@ private:
BackgroundMiningNo = 2,
};
+ enum BackgroundSyncType {
+ BackgroundSyncOff = 0,
+ BackgroundSyncReusePassword = 1,
+ BackgroundSyncCustomPassword = 2,
+ };
+
+ static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str)
+ {
+ if (background_sync_type_str == "off") return BackgroundSyncOff;
+ if (background_sync_type_str == "reuse-wallet-password") return BackgroundSyncReusePassword;
+ if (background_sync_type_str == "custom-background-password") return BackgroundSyncCustomPassword;
+ throw std::logic_error("Unknown background sync type");
+ };
+
enum ExportFormat {
Binary = 0,
Ascii,
@@ -275,7 +290,12 @@ private:
//! Just parses variables.
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
- static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds);
+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds)
+ {
+ crypto::secret_key spend_key = crypto::null_skey;
+ return verify_password(keys_file_name, password, no_spend_key, hwdev, kdf_rounds, spend_key);
+ };
+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out);
static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1);
wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_client_factory>(new net::http::client_factory()));
@@ -785,6 +805,54 @@ private:
END_SERIALIZE()
};
+ struct background_synced_tx_t
+ {
+ uint64_t index_in_background_sync_data;
+ cryptonote::transaction tx;
+ std::vector<uint64_t> output_indices;
+ uint64_t height;
+ uint64_t block_timestamp;
+ bool double_spend_seen;
+
+ BEGIN_SERIALIZE_OBJECT()
+ VERSION_FIELD(0)
+ VARINT_FIELD(index_in_background_sync_data)
+
+ // prune tx; don't need to keep signature data
+ if (!tx.serialize_base(ar))
+ return false;
+
+ FIELD(output_indices)
+ VARINT_FIELD(height)
+ VARINT_FIELD(block_timestamp)
+ FIELD(double_spend_seen)
+ END_SERIALIZE()
+ };
+
+ struct background_sync_data_t
+ {
+ bool first_refresh_done = false;
+ uint64_t start_height = 0;
+ std::unordered_map<crypto::hash, background_synced_tx_t> txs;
+
+ // Relevant wallet settings
+ uint64_t wallet_refresh_from_block_height;
+ size_t subaddress_lookahead_major;
+ size_t subaddress_lookahead_minor;
+ RefreshType wallet_refresh_type;
+
+ BEGIN_SERIALIZE_OBJECT()
+ VERSION_FIELD(0)
+ FIELD(first_refresh_done)
+ FIELD(start_height)
+ FIELD(txs)
+ FIELD(wallet_refresh_from_block_height)
+ VARINT_FIELD(subaddress_lookahead_major)
+ VARINT_FIELD(subaddress_lookahead_minor)
+ VARINT_FIELD(wallet_refresh_type)
+ END_SERIALIZE()
+ };
+
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
struct parsed_block
@@ -892,6 +960,23 @@ private:
*/
void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file = false);
+ private:
+ /*!
+ * \brief Decrypts the account keys
+ * \return an RAII reencryptor for the account keys
+ */
+ epee::misc_utils::auto_scope_leave_caller decrypt_account_for_multisig(const epee::wipeable_string &password);
+ /*!
+ * \brief Creates an uninitialized multisig account
+ * \outparam: the uninitialized multisig account
+ */
+ void get_uninitialized_multisig_account(multisig::multisig_account &account_out) const;
+ /*!
+ * \brief Reconstructs a multisig account from wallet2 state
+ * \outparam: the reconstructed multisig account
+ */
+ void get_reconstructed_multisig_account(multisig::multisig_account &account_out) const;
+ public:
/*!
* \brief Creates a multisig wallet
* \return empty if done, non empty if we need to send another string
@@ -914,6 +999,13 @@ private:
*/
std::string get_multisig_first_kex_msg() const;
/*!
+ * \brief Use multisig kex messages for an in-progress kex round to 'boost' the following round for another group member
+ */
+ std::string get_multisig_key_exchange_booster(const epee::wipeable_string &password,
+ const std::vector<std::string> &kex_messages,
+ const std::uint32_t threshold,
+ const std::uint32_t num_signers);
+ /*!
* Export multisig info
* This will generate and remember new k values
*/
@@ -973,7 +1065,8 @@ private:
/*!
* \brief verifies given password is correct for default wallet keys file
*/
- bool verify_password(const epee::wipeable_string& password);
+ bool verify_password(const epee::wipeable_string& password) {crypto::secret_key key = crypto::null_skey; return verify_password(password, key);};
+ bool verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out);
cryptonote::account_base& get_account(){return m_account;}
const cryptonote::account_base& get_account()const{return m_account;}
@@ -1060,6 +1153,7 @@ private:
cryptonote::network_type nettype() const { return m_nettype; }
bool watch_only() const { return m_watch_only; }
+ bool is_background_wallet() const { return m_is_background_wallet; }
multisig::multisig_account_status get_multisig_status() const;
bool has_multisig_partial_key_images() const;
bool has_unknown_key_images() const;
@@ -1269,11 +1363,17 @@ private:
return;
}
a & m_has_ever_refreshed_from_node;
+ if(ver < 31)
+ {
+ m_background_sync_data = background_sync_data_t{};
+ return;
+ }
+ a & m_background_sync_data;
}
BEGIN_SERIALIZE_OBJECT()
MAGIC_FIELD("monero wallet cache")
- VERSION_FIELD(1)
+ VERSION_FIELD(2)
FIELD(m_blockchain)
FIELD(m_transfers)
FIELD(m_account_public_address)
@@ -1306,6 +1406,12 @@ private:
return true;
}
FIELD(m_has_ever_refreshed_from_node)
+ if (version < 2)
+ {
+ m_background_sync_data = background_sync_data_t{};
+ return true;
+ }
+ FIELD(m_background_sync_data)
END_SERIALIZE()
/*!
@@ -1321,6 +1427,8 @@ private:
* \return Whether path is valid format
*/
static bool wallet_valid_path_format(const std::string& file_path);
+ static std::string make_background_wallet_file_name(const std::string &wallet_file);
+ static std::string make_background_keys_file_name(const std::string &wallet_file);
static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id);
static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id);
static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id);
@@ -1367,6 +1475,9 @@ private:
void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; }
bool track_uses() const { return m_track_uses; }
void track_uses(bool value) { m_track_uses = value; }
+ BackgroundSyncType background_sync_type() const { return m_background_sync_type; }
+ void setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password);
+ bool is_background_syncing() const { return m_background_syncing; }
bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; }
void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; }
BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; }
@@ -1641,6 +1752,9 @@ private:
uint64_t get_bytes_sent() const;
uint64_t get_bytes_received() const;
+ void start_background_sync();
+ void stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key = crypto::null_skey);
+
// MMS -------------------------------------------------------------------------------------------------
mms::message_store& get_message_store() { return m_message_store; };
const mms::message_store& get_message_store() const { return m_message_store; };
@@ -1673,6 +1787,9 @@ private:
* \return Whether it was successful.
*/
bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false);
+ bool store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false);
+ boost::optional<wallet2::keys_file_data> get_keys_file_data(const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false);
+ bool store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file = false);
/*!
* \brief Load wallet keys information from wallet file.
* \param keys_file_name Name of wallet file
@@ -1686,6 +1803,7 @@ private:
*/
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password);
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt);
+ void load_wallet_cache(const bool use_fs, const std::string& cache_buf = "");
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false);
bool should_skip_block(const cryptonote::block &b, uint64_t height) const;
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
@@ -1694,6 +1812,15 @@ private:
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
bool clear();
void clear_soft(bool keep_key_images=false);
+ /*
+ * clear_user_data clears data created by the user, which is mostly data
+ * that a view key cannot identify on chain. This function was initially
+ * added to ensure that a "background" wallet (a wallet that syncs with just
+ * a view key hot in memory) does not have any sensitive data loaded that it
+ * does not need in order to sync. Future devs should take care to ensure
+ * that this function deletes data that is not useful for background syncing
+ */
+ void clear_user_data();
void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t &current_height, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
@@ -1745,10 +1872,23 @@ private:
bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
crypto::chacha_key get_ringdb_key();
void setup_keys(const epee::wipeable_string &password);
+ const crypto::chacha_key get_cache_key();
+ void verify_password_with_cached_key(const epee::wipeable_string &password);
+ void verify_password_with_cached_key(const crypto::chacha_key &key);
size_t get_transfer_details(const crypto::key_image &ki) const;
tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids);
void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries);
void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd);
+ void write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password);
+ void process_background_cache_on_open();
+ void process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_chain, uint64_t last_block_reward);
+ void reset_background_sync_data(background_sync_data_t &background_sync_data);
+ void store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data = true);
+ void store_background_keys(const crypto::chacha_key &custom_background_key);
+
+ bool lock_background_keys_file(const std::string &background_keys_file);
+ bool unlock_background_keys_file();
+ bool is_background_keys_file_locked() const;
void register_devices();
hw::device& lookup_device(const std::string & device_descriptor);
@@ -1860,6 +2000,8 @@ private:
uint64_t m_ignore_outputs_above;
uint64_t m_ignore_outputs_below;
bool m_track_uses;
+ bool m_is_background_wallet;
+ BackgroundSyncType m_background_sync_type;
bool m_show_wallet_name_when_locked;
uint32_t m_inactivity_lock_timeout;
BackgroundMiningSetupType m_setup_background_mining;
@@ -1887,6 +2029,7 @@ private:
uint64_t m_last_block_reward;
std::unique_ptr<tools::file_locker> m_keys_file_locker;
+ std::unique_ptr<tools::file_locker> m_background_keys_file_locker;
mms::message_store m_message_store;
bool m_original_keys_available;
@@ -1894,6 +2037,7 @@ private:
crypto::secret_key m_original_view_secret_key;
crypto::chacha_key m_cache_key;
+ boost::optional<crypto::chacha_key> m_custom_background_key = boost::none;
std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh;
bool m_unattended;
@@ -1909,9 +2053,13 @@ private:
static boost::mutex default_daemon_address_lock;
static std::string default_daemon_address;
+
+ bool m_background_syncing;
+ bool m_processing_background_cache;
+ background_sync_data_t m_background_sync_data;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 30)
+BOOST_CLASS_VERSION(tools::wallet2, 31)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
@@ -1927,6 +2075,8 @@ BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1)
+BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx_t, 0)
+BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0)
namespace boost
{
@@ -2425,6 +2575,29 @@ namespace boost
return;
a & x.multisig_sigs;
}
+
+ template <class Archive>
+ inline void serialize(Archive& a, tools::wallet2::background_synced_tx_t &x, const boost::serialization::version_type ver)
+ {
+ a & x.index_in_background_sync_data;
+ a & x.tx;
+ a & x.output_indices;
+ a & x.height;
+ a & x.block_timestamp;
+ a & x.double_spend_seen;
+ }
+
+ template <class Archive>
+ inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver)
+ {
+ a & x.first_refresh_done;
+ a & x.start_height;
+ a & x.txs;
+ a & x.wallet_refresh_from_block_height;
+ a & x.subaddress_lookahead_major;
+ a & x.subaddress_lookahead_minor;
+ a & x.wallet_refresh_type;
+ }
}
}
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 5341f43b9..4bbf05008 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -63,6 +63,7 @@ namespace tools
// invalid_password
// invalid_priority
// invalid_multisig_seed
+ // invalid_spend_key
// refresh_error *
// acc_outs_lookup_error
// block_parse_error
@@ -97,6 +98,9 @@ namespace tools
// wallet_files_doesnt_correspond
// scan_tx_error *
// wont_reprocess_recent_txs_via_untrusted_daemon
+ // background_sync_error *
+ // background_wallet_already_open
+ // background_custom_password_same_as_wallet_password
//
// * - class with protected ctor
@@ -304,6 +308,16 @@ namespace tools
std::string to_string() const { return wallet_logic_error::to_string(); }
};
+ struct invalid_spend_key : public wallet_logic_error
+ {
+ explicit invalid_spend_key(std::string&& loc)
+ : wallet_logic_error(std::move(loc), "invalid spend key")
+ {
+ }
+
+ std::string to_string() const { return wallet_logic_error::to_string(); }
+ };
+
//----------------------------------------------------------------------------------------------------
struct invalid_pregenerated_random : public wallet_logic_error
{
@@ -948,6 +962,31 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct background_sync_error : public wallet_logic_error
+ {
+ protected:
+ explicit background_sync_error(std::string&& loc, const std::string& message)
+ : wallet_logic_error(std::move(loc), message)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
+ struct background_wallet_already_open : public background_sync_error
+ {
+ explicit background_wallet_already_open(std::string&& loc, const std::string& background_wallet_file)
+ : background_sync_error(std::move(loc), "background wallet " + background_wallet_file + " is already opened by another wallet program")
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
+ struct background_custom_password_same_as_wallet_password : public background_sync_error
+ {
+ explicit background_custom_password_same_as_wallet_password(std::string&& loc)
+ : background_sync_error(std::move(loc), "custom background password must be different than wallet password")
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
#if !defined(_MSC_VER)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index fc7df5310..bb65304d4 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -73,6 +73,54 @@ using namespace epee;
} \
} while(0)
+#define CHECK_IF_BACKGROUND_SYNCING() \
+ do \
+ { \
+ if (!m_wallet) { return not_open(er); } \
+ if (m_wallet->is_background_wallet()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \
+ er.message = "This command is disabled for background wallets."; \
+ return false; \
+ } \
+ if (m_wallet->is_background_syncing()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \
+ er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \
+ return false; \
+ } \
+ } while(0)
+
+#define PRE_VALIDATE_BACKGROUND_SYNC() \
+ do \
+ { \
+ if (!m_wallet) { return not_open(er); } \
+ if (m_restricted) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_DENIED; \
+ er.message = "Command unavailable in restricted mode."; \
+ return false; \
+ } \
+ if (m_wallet->key_on_device()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
+ er.message = "Command not supported by HW wallet"; \
+ return false; \
+ } \
+ if (m_wallet->get_multisig_status().multisig_is_active) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
+ er.message = "Multisig wallet cannot enable background sync"; \
+ return false; \
+ } \
+ if (m_wallet->watch_only()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \
+ er.message = "Watch-only wallet cannot enable background sync"; \
+ return false; \
+ } \
+ } while (0)
+
namespace
{
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
@@ -291,6 +339,9 @@ namespace tools
{
if (!m_wallet)
return;
+ // Background mining can be toggled from the main wallet
+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing())
+ return;
tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining();
if (setup == tools::wallet2::BackgroundMiningNo)
@@ -581,6 +632,7 @@ namespace tools
bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.count < 1 || req.count > 65536) {
@@ -618,6 +670,7 @@ namespace tools
bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_subaddress_label(req.index, req.label);
@@ -680,6 +733,7 @@ namespace tools
bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->add_subaddress_account(req.label);
@@ -697,6 +751,7 @@ namespace tools
bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_subaddress_label({req.account_index, 0}, req.label);
@@ -712,6 +767,7 @@ namespace tools
bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags();
for (const std::pair<const std::string, std::string>& p : account_tags.first)
{
@@ -731,6 +787,7 @@ namespace tools
bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag(req.accounts, req.tag);
@@ -746,6 +803,7 @@ namespace tools
bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag(req.accounts, "");
@@ -761,6 +819,7 @@ namespace tools
bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag_description(req.tag, req.description);
@@ -791,6 +850,7 @@ namespace tools
bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@@ -819,6 +879,7 @@ namespace tools
bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@@ -847,6 +908,7 @@ namespace tools
bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@@ -874,6 +936,8 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er)
{
+ CHECK_IF_BACKGROUND_SYNCING();
+
crypto::hash8 integrated_payment_id = crypto::null_hash8;
std::string extra_nonce;
for (auto it = destinations.begin(); it != destinations.end(); it++)
@@ -1203,6 +1267,7 @@ namespace tools
}
CHECK_MULTISIG_ENABLED();
+ CHECK_IF_BACKGROUND_SYNCING();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
@@ -1284,6 +1349,7 @@ namespace tools
er.message = "command not supported by watch-only wallet";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
if(req.unsigned_txset.empty() && req.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@@ -1553,6 +1619,7 @@ namespace tools
}
CHECK_MULTISIG_ENABLED();
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
@@ -2115,6 +2182,7 @@ namespace tools
er.message = "The wallet is watch-only. Cannot retrieve seed.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
if (!m_wallet->is_deterministic())
{
er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC;
@@ -2143,6 +2211,7 @@ namespace tools
er.message = "The wallet is watch-only. Cannot retrieve spend key.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key);
res.key = std::string(key.data(), key.size());
}
@@ -2164,6 +2233,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
@@ -2177,6 +2247,79 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+ const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type);
+ boost::optional<epee::wipeable_string> background_cache_password = boost::none;
+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword)
+ background_cache_password = boost::optional<epee::wipeable_string>(req.background_cache_password);
+ m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password);
+ }
+ catch (...)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+ m_wallet->start_background_sync();
+ }
+ catch (...)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ try
+ {
+ PRE_VALIDATE_BACKGROUND_SYNC();
+ crypto::secret_key spend_secret_key = crypto::null_skey;
+
+ // Load the spend key from seed if seed is provided
+ if (!req.seed.empty())
+ {
+ crypto::secret_key recovery_key;
+ std::string language;
+
+ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Electrum-style word list failed verification";
+ return false;
+ }
+
+ if (!req.seed_offset.empty())
+ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
+
+ // generate spend key
+ cryptonote::account_base account;
+ account.generate(recovery_key, true, false);
+ spend_secret_key = account.get_keys().m_spend_secret_key;
+ }
+
+ m_wallet->stop_background_sync(req.wallet_password, spend_secret_key);
+ }
+ catch (...)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
@@ -2186,6 +2329,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key;
if (req.signature_type == "spend" || req.signature_type == "")
@@ -2278,6 +2422,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
if (req.txids.size() != req.notes.size())
{
@@ -2350,6 +2495,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
m_wallet->set_attribute(req.key, req.value);
@@ -2377,6 +2523,7 @@ namespace tools
bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(req.txid, txid))
@@ -2468,6 +2615,7 @@ namespace tools
bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(req.txid, txid))
@@ -2584,6 +2732,7 @@ namespace tools
bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
if (!req.all)
@@ -2826,6 +2975,7 @@ namespace tools
er.message = "command not supported by HW wallet";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
@@ -2855,6 +3005,7 @@ namespace tools
er.message = "command not supported by HW wallet";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob))
@@ -2880,6 +3031,7 @@ namespace tools
bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all);
@@ -2916,6 +3068,7 @@ namespace tools
er.message = "This command requires a trusted daemon.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
@@ -2984,6 +3137,7 @@ namespace tools
bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
+ CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.entries.empty())
{
@@ -3029,6 +3183,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
cryptonote::address_parse_info info;
er.message = "";
@@ -3071,6 +3226,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.index >= ab.size())
@@ -3133,6 +3289,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.index >= ab.size())
@@ -3203,6 +3360,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
std::unordered_set<crypto::hash> txids;
std::list<std::string>::const_iterator i = req.txids.begin();
@@ -3242,6 +3400,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->rescan_spent();
@@ -3506,6 +3665,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
if (m_wallet->verify_password(req.old_password))
{
try
@@ -4039,6 +4199,7 @@ namespace tools
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
res.multisig_info = m_wallet->get_multisig_first_kex_msg();
return true;
@@ -4066,6 +4227,7 @@ namespace tools
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
+ CHECK_IF_BACKGROUND_SYNCING();
try
{
@@ -4248,6 +4410,47 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_restricted)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()};
+
+ if (ms_status.is_ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
+ er.message = "This wallet is multisig, and already finalized";
+ return false;
+ }
+
+ if (req.multisig_info.size() == 0)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
+ er.message = "Needs multisig info from more participants";
+ return false;
+ }
+
+ try
+ {
+ res.multisig_info = m_wallet->get_multisig_key_exchange_booster(req.password,
+ req.multisig_info,
+ req.threshold,
+ req.num_signers);
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = std::string("Error calling exchange_multisig_info_booster: ") + e.what();
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index d083c71d8..f118f581a 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -151,6 +151,7 @@ namespace tools
MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
MAP_JON_RPC_WE("exchange_multisig_keys", on_exchange_multisig_keys, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS)
+ MAP_JON_RPC_WE("get_multisig_key_exchange_booster", on_get_multisig_key_exchange_booster, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER)
MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
MAP_JON_RPC_WE("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS)
@@ -159,6 +160,9 @@ namespace tools
MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES)
MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT)
MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION)
+ MAP_JON_RPC_WE("setup_background_sync", on_setup_background_sync, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC)
+ MAP_JON_RPC_WE("start_background_sync", on_start_background_sync, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC)
+ MAP_JON_RPC_WE("stop_background_sync", on_stop_background_sync, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC)
END_JSON_RPC_MAP()
END_URI_MAP2()
@@ -242,6 +246,7 @@ namespace tools
bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
@@ -250,6 +255,9 @@ namespace tools
bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
//json rpc v2
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 164b87870..ab7898299 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -2481,6 +2481,35 @@ namespace wallet_rpc
typedef epee::misc_utils::struct_init<response_t> response;
};
+ struct COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER
+ {
+ struct request_t
+ {
+ std::string password;
+ std::vector<std::string> multisig_info;
+ uint32_t threshold;
+ uint32_t num_signers;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(password)
+ KV_SERIALIZE(multisig_info)
+ KV_SERIALIZE(threshold)
+ KV_SERIALIZE(num_signers)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ std::string multisig_info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(multisig_info)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
struct COMMAND_RPC_SIGN_MULTISIG
{
struct request_t
@@ -2700,5 +2729,69 @@ namespace wallet_rpc
typedef epee::misc_utils::struct_init<response_t> response;
};
+ struct COMMAND_RPC_SETUP_BACKGROUND_SYNC
+ {
+ struct request_t
+ {
+ std::string background_sync_type;
+ std::string wallet_password;
+ std::string background_cache_password;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(background_sync_type)
+ KV_SERIALIZE(wallet_password)
+ KV_SERIALIZE_OPT(background_cache_password, (std::string)"")
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
+ struct COMMAND_RPC_START_BACKGROUND_SYNC
+ {
+ struct request_t
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
+ struct COMMAND_RPC_STOP_BACKGROUND_SYNC
+ {
+ struct request_t
+ {
+ std::string wallet_password;
+ std::string seed;
+ std::string seed_offset;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(wallet_password)
+ KV_SERIALIZE_OPT(seed, (std::string)"")
+ KV_SERIALIZE_OPT(seed_offset, (std::string)"")
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
}
}
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index 2985a8280..97f9e81a3 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -81,3 +81,5 @@
#define WALLET_RPC_ERROR_CODE_DISABLED -48
#define WALLET_RPC_ERROR_CODE_PROXY_ALREADY_DEFINED -49
#define WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME -50
+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -51
+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -52