From 9177db0ae0f87c02c765a9f106ae5d08e8fa4fd1 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Fri, 13 Jan 2023 12:31:54 +0800 Subject: [PATCH 1/9] Update getblock help dialogue (#1685) --- src/rpc/blockchain.cpp | 44 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b9f521235a..f0b0e8c1c5 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -888,29 +888,33 @@ static UniValue getblock(const JSONRPCRequest& request) }, RPCResult{"for verbosity = 1", "{\n" - " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" - " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" - " \"size\" : n, (numeric) The block size\n" - " \"strippedsize\" : n, (numeric) The block size excluding witness data\n" - " \"weight\" : n (numeric) The block weight as defined in BIP 141\n" - " \"height\" : n, (numeric) The block height or index\n" - " \"version\" : n, (numeric) The block version\n" - " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n" - " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" - " \"nonutxo\" : [, (array of string) Non-UTXO coinbase rewards\n" - " \"type\" n.nnnnnnnn (numeric) Reward type and amount\n" + " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" + " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" + " \"size\" : n, (numeric) The block size\n" + " \"strippedsize\" : n, (numeric) The block size excluding witness data\n" + " \"weight\" : n (numeric) The block weight as defined in BIP 141\n" + " \"height\" : n, (numeric) The block height or index\n" + " \"masternode\" : \"hex\", (string) Masternode ID of the block minter\n" + " \"minter\" : \"address\", (string) Operator address of block minter\n" + " \"mintedBlocks\" : n, (numeric) Total number of blocks minted by block minter\n" + " \"stakeModifier\" : \"hex\", (string) The block stake modifier\n" + " \"version\" : n, (numeric) The block version\n" + " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n" + " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" + " \"nonutxo\" : [, (array of string) Non-UTXO coinbase rewards\n" + " \"type\" n.nnnnnnnn (numeric) Reward type and amount\n" " ],\n" - " \"tx\" : [ (array of string) The transaction ids\n" - " \"transactionid\" (string) The transaction id\n" + " \"tx\" : [ (array of string) The transaction ids\n" + " \"transactionid\" (string) The transaction id\n" " ,...\n" " ],\n" - " \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" - " \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n" - " \"nonce\" : n, (numeric) The nonce\n" - " \"bits\" : \"1d00ffff\", (string) The bits\n" - " \"difficulty\" : x.xxx, (numeric) The difficulty\n" - " \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n" - " \"nTx\" : n, (numeric) The number of transactions in the block.\n" + " \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"nonce\" : n, (numeric) The nonce\n" + " \"bits\" : \"1d00ffff\", (string) The bits\n" + " \"difficulty\" : x.xxx, (numeric) The difficulty\n" + " \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n" + " \"nTx\" : n, (numeric) The number of transactions in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" "}\n" From a0fbacd921a34a4067c467fa9bb8a2a223667baa Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Fri, 13 Jan 2023 05:37:34 +0100 Subject: [PATCH 2/9] Add workflow_dispatch event trigger to full sync CI (#1651) * Trigger workflow via workflow_dispatch and conditionally pass block ranges * Use aria2 to download snapshot * Improve workflow_dispatch description * Rename server-2 to builder * Restore full sync on ci/sync label * Restore if order --- .github/workflows/fullsync-tests.yml | 35 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/fullsync-tests.yml b/.github/workflows/fullsync-tests.yml index c7c33e0a09..23e5008bb8 100644 --- a/.github/workflows/fullsync-tests.yml +++ b/.github/workflows/fullsync-tests.yml @@ -5,12 +5,16 @@ on: - master - epic/* types: [labeled, opened, reopened, synchronize] + workflow_dispatch: + inputs: + block_ranges: + description: 'Optionally restrict to specific block ranges. Should be formatted as multiple start block, such as "350000 50000 750000". Defaults to all block ranges' + required: false jobs: build-binaries: - if: contains(github.event.pull_request.labels.*.name, 'ci/sync') - runs-on: [self-hosted, linux, x64, server-2] - # runs-on: ubuntu-18.04 + if: contains(github.event.pull_request.labels.*.name, 'ci/sync') || github.event_name == 'workflow_dispatch' + runs-on: [self-hosted, linux, x64, builder] steps: - uses: actions/checkout@v3 with: @@ -43,7 +47,7 @@ jobs: ci/parallel_sync/sync_then_diff.sh generate-matrix: - if: contains(github.event.pull_request.labels.*.name, 'ci/sync') + if: contains(github.event.pull_request.labels.*.name, 'ci/sync') || github.event_name == 'workflow_dispatch' runs-on: [self-hosted, linux, x64] # Add "id-token" with the intended permissions. @@ -68,16 +72,20 @@ jobs: uses: 'google-github-actions/setup-gcloud@v0' - id: set-matrix + name: 'Set matrix output' run: | - SNAPSHOTS=$(gsutil ls gs://team-drop/${{github.base_ref}}-datadir) - BLOCKS=$(echo "$SNAPSHOTS" | sed -e 's/.*\-\(.*\)\.tar.*/\1/' | grep -v gs | sort -n | head -n -1) - JSON=$(jq -n -c -M --arg blocks "$BLOCKS" '{blocks: ($blocks | split("\n") | .[] |= tonumber | to_entries | map({start: .value , stop: (.value + 50000)}))}') - echo "::set-output name=matrix::$JSON" + if [ -n "${{ inputs.block_ranges }}" ]; then + BLOCKS=$(echo ${{ inputs.block_ranges }} | tr ' ' '\n') + else + SNAPSHOTS=$(gsutil ls gs://team-drop/master-datadir) + BLOCKS=$(echo "$SNAPSHOTS" | sed -e 's/.*\-\(.*\)\.tar.*/\1/' | grep -v gs | sort -n | head -n -1) + fi + MATRIX_JSON=$(jq -n -c -M --arg blocks "$BLOCKS" '{blocks: ($blocks | split("\n") | .[] |= tonumber | to_entries | map({start: .value , stop: (.value + 50000)}))}') + echo "MATRIX=$MATRIX_JSON" >> $GITHUB_OUTPUT sync: runs-on: [self-hosted, linux, x64] needs: [build-binaries, generate-matrix] - if: contains(github.event.pull_request.labels.*.name, 'ci/sync') strategy: matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}} @@ -89,18 +97,15 @@ jobs: START_BLOCK: ${{matrix.blocks.start}} DEFID_BIN: ./defid DEFI_CLI_BIN: ./defi-cli - REF_LOG_DIR: ${{github.base_ref}}-datadir/log - BASE_REF: ${{github.base_ref}} + REF_LOG_DIR: master-datadir/log + BASE_REF: master timeout-minutes: 4320 steps: - uses: actions/checkout@v3 - - name: Install Dependencies - run: sudo apt-get update && sudo apt-get install -y wget - - name: Download Snapshot - run: wget https://storage.googleapis.com/team-drop/${{github.base_ref}}-datadir/datadir-${{matrix.blocks.start}}.tar.gz + run: aria2c -x16 -s16 https://storage.googleapis.com/team-drop/master-datadir/datadir-${{matrix.blocks.start}}.tar.gz - name: Create datadir run: mkdir $DATADIR && tar -C $DATADIR -xvf datadir-${{matrix.blocks.start}}.tar.gz From 2f295798b7da5ac9c4459d7e35f21f51b13315f6 Mon Sep 17 00:00:00 2001 From: Mihailo Milenkovic Date: Fri, 13 Jan 2023 09:26:45 +0100 Subject: [PATCH 3/9] Fix start pagination for listgovproposals (#1689) * Fix pagination in listgovproposals * Fix lint * Fix to filter after filtering cycle rounds --- src/masternodes/rpc_proposals.cpp | 104 +++++++++--------- .../functional/feature_on_chain_government.py | 61 ++++++++-- ...ature_on_chain_government_govvar_update.py | 8 +- 3 files changed, 103 insertions(+), 70 deletions(-) mode change 100644 => 100755 test/functional/feature_on_chain_government.py diff --git a/src/masternodes/rpc_proposals.cpp b/src/masternodes/rpc_proposals.cpp index 01ecca5159..704ff840ce 100644 --- a/src/masternodes/rpc_proposals.cpp +++ b/src/masternodes/rpc_proposals.cpp @@ -786,6 +786,30 @@ UniValue getgovproposal(const JSONRPCRequest &request) { return proposalToJSON(propId, *prop, view, info); } +template +void iterateProps(const T& list, UniValue& ret, const CPropId& start, bool including_start, size_t limit, const uint8_t type, const uint8_t status) +{ + for (const auto &prop : list) { + if (status && status != prop.second.status) { + continue; + } + if (type && type != prop.second.type) { + continue; + } + if (start != CPropId{} && prop.first != start) + continue; + if (!including_start) { + including_start = true; + continue; + } + + limit--; + ret.push_back(proposalToJSON(prop.first, prop.second, *pcustomcsview, std::nullopt)); + if (!limit) + break; + } +} + UniValue listgovproposals(const JSONRPCRequest &request) { RPCHelpMan{ "listgovproposals", @@ -801,9 +825,9 @@ UniValue listgovproposals(const JSONRPCRequest &request) { "", { {"start", - RPCArg::Type::NUM, + RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, - "Vote index to iterate from." + "Proposal id to iterate from." "Typically it's set to last ID from previous request."}, {"including_start", RPCArg::Type::BOOL, @@ -937,71 +961,47 @@ UniValue listgovproposals(const JSONRPCRequest &request) { using IdPropPair = std::pair; using CycleEndHeightInt = int; - using PropBatchesMap = std::map>; + using CyclePropsMap = std::map>; - PropBatchesMap propBatches; + std::map props; + CyclePropsMap cycleProps; + + view.ForEachProp( + [&](const CPropId &propId, const CPropObject &prop) { + props.insert({propId, prop}); + return true; + },static_cast(0)); if (cycle != 0) { // populate map - view.ForEachProp( - [&](const CPropId &propId, const CPropObject &prop) { - auto batch = propBatches.find(prop.cycleEndHeight); - auto propPair = std::make_pair(propId, prop); - // if batch is not found create it - if (batch == propBatches.end()) { - propBatches.insert({prop.cycleEndHeight, std::vector{propPair}}); - } else { // else insert to prop vector - batch->second.push_back(propPair); - } - return true; - }, - static_cast(0), - start); + for (const auto &[propId, prop] : props) { + auto batch = cycleProps.find(prop.cycleEndHeight); + auto propPair = std::make_pair(propId, prop); + // if batch is not found create it + if (batch == cycleProps.end()) { + cycleProps.insert({prop.cycleEndHeight, std::vector{propPair}}); + } else { // else insert to prop vector + batch->second.push_back(propPair); + } + } - auto batch = propBatches.rbegin(); + auto batch = cycleProps.rbegin(); if (cycle != -1) { - if (static_cast(cycle) > propBatches.size()) + if (static_cast(cycle) > cycleProps.size()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find cycle"); - for (unsigned int i = 1; i <= (propBatches.size() - cycle); i++) { + for (unsigned int i = 1; i <= (cycleProps.size() - cycle); i++) { batch++; } } else { batch++; } - // Filter batch - for (const auto &prop : batch->second) { - if (status && status != prop.second.status) { - continue; - } - if (type && type != prop.second.type) { - continue; - } - limit--; - ret.push_back(proposalToJSON(prop.first, prop.second, view, std::nullopt)); - if (!limit) - break; - } + + iterateProps(batch->second, ret, start, including_start, limit, type, status); + return ret; } - view.ForEachProp( - [&](const CPropId &propId, const CPropObject &prop) { - if (!including_start) { - including_start = true; - return (true); - } - if (status && status != prop.status) { - return false; - } - if (type && type != prop.type) { - return true; - } - limit--; - ret.push_back(proposalToJSON(propId, prop, view, std::nullopt)); - return limit != 0; - }, - static_cast(status), - start); + iterateProps(props, ret, start, including_start, limit, type, status); return ret; } diff --git a/test/functional/feature_on_chain_government.py b/test/functional/feature_on_chain_government.py old mode 100644 new mode 100755 index c4b4c60a82..8f09187276 --- a/test/functional/feature_on_chain_government.py +++ b/test/functional/feature_on_chain_government.py @@ -123,7 +123,7 @@ def run_test(self): self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/feature/gov-payout':'true'}}) # Create CFP - tx = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": 100, "cycles": 2, "payoutAddress": address}) + cfp1 = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": 100, "cycles": 2, "payoutAddress": address}) # Fund addresses self.nodes[0].sendtoaddress(address1, Decimal("1.0")) self.nodes[0].sendtoaddress(address2, Decimal("1.0")) @@ -141,19 +141,19 @@ def run_test(self): assert_equal(self.nodes[0].getburninfo()['feeburn'], Decimal('5.00000000')) # cannot vote by non owning masternode - assert_raises_rpc_error(-5, "Incorrect authorization", self.nodes[0].votegov, tx, mn1, "yes") + assert_raises_rpc_error(-5, "Incorrect authorization", self.nodes[0].votegov, cfp1, mn1, "yes") # Vote on proposal - self.nodes[0].votegov(tx, mn0, "yes") + self.nodes[0].votegov(cfp1, mn0, "yes") self.nodes[0].generate(1) - self.nodes[1].votegov(tx, mn1, "no") + self.nodes[1].votegov(cfp1, mn1, "no") self.nodes[1].generate(1) - self.nodes[2].votegov(tx, mn2, "yes") + self.nodes[2].votegov(cfp1, mn2, "yes") self.nodes[2].generate(1) self.sync_blocks() # Try and vote with non-staked MN - assert_raises_rpc_error(None, "does not mine at least one block", self.nodes[3].votegov, tx, mn3, "neutral") + assert_raises_rpc_error(None, "does not mine at least one block", self.nodes[3].votegov, cfp1, mn3, "neutral") # voting period votingPeriod = 70 @@ -165,7 +165,7 @@ def run_test(self): # Check proposal and votes result = self.nodes[0].listgovproposals() assert_equal(len(result), 1) - assert_equal(result[0]["proposalId"], tx) + assert_equal(result[0]["proposalId"], cfp1) assert_equal(result[0]["creationHeight"], creationHeight) assert_equal(result[0]["title"], title) assert_equal(result[0]["context"], context) @@ -184,23 +184,23 @@ def run_test(self): assert_equal(result[0]["fee"], Decimal("10")) # Check individual MN votes - results = self.nodes[1].listgovproposalvotes(tx, mn0) + results = self.nodes[1].listgovproposalvotes(cfp1, mn0) assert_equal(len(results), 1) result = results[0] assert_equal(result['vote'], 'YES') - results = self.nodes[1].listgovproposalvotes(tx, mn1) + results = self.nodes[1].listgovproposalvotes(cfp1, mn1) assert_equal(len(results), 1) result = results[0] assert_equal(result['vote'], 'NO') - results = self.nodes[1].listgovproposalvotes(tx, mn2) + results = self.nodes[1].listgovproposalvotes(cfp1, mn2) assert_equal(len(results), 1) result = results[0] assert_equal(result['vote'], 'YES') # Check total votes - result = self.nodes[1].listgovproposalvotes(tx, "all") + result = self.nodes[1].listgovproposalvotes(cfp1, "all") assert_equal(len(result), 3) # Move to just before cycle payout @@ -468,7 +468,7 @@ def run_test(self): assert_equal(self.nodes[0].listcommunitybalances()['CommunityDevelopmentFunds'], bal + Decimal("19.23346268")) # not votes on 2nd cycle makes proposal to rejected - result = self.nodes[0].listgovproposals()[0] + result = self.nodes[0].listgovproposals({"pagination":{"start": propId, "including_start":True}})[0] assert_equal(result["status"], "Rejected") # No proposals pending @@ -625,5 +625,42 @@ def run_test(self): # test non-object RPC arguments assert_equal(len(self.nodes[0].listgovproposalvotes(propId, 'all', -1, {"limit": 2})), 2) + tx1 = self.nodes[0].creategovcfp({"title": "1111", + "context": context, + "amount": 50, + "cycles": 1, + "payoutAddress": address}) + self.nodes[0].generate(1) + self.sync_blocks() + + tx2 = self.nodes[0].creategovcfp({"title": "2222", + "context": context, + "amount": 50, + "cycles": 1, + "payoutAddress": address}) + self.nodes[0].generate(1) + self.sync_blocks() + + tx3 = self.nodes[0].creategovcfp({"title": "3333", + "context": context, + "amount": 50, + "cycles": 1, + "payoutAddress": address}) + self.nodes[0].generate(1) + self.sync_blocks() + + assert_equal(self.nodes[0].listgovproposals({"cycle":1, "pagination": {"start": cfp1, "including_start": True, "limit": 1}})[0]["proposalId"], cfp1) + assert_equal(len(self.nodes[0].listgovproposals({"cycle":5})), 3) + assert_equal(self.nodes[0].listgovproposals({"cycle":5, "pagination": {"start": tx2, "including_start": True, "limit": 1}})[0]["proposalId"], tx2) + assert_equal(self.nodes[0].listgovproposals({"cycle":5, "pagination": {"start": tx3, "including_start": True, "limit": 1}})[0]["proposalId"], tx3) + + assert_equal(len(self.nodes[0].listgovproposals({"type": "cfp"})), 5) + assert_equal(self.nodes[0].listgovproposals({"type": "cfp", "pagination": {"start": cfp1, "including_start": True, "limit": 1}})[0]["proposalId"], cfp1) + assert_equal(self.nodes[0].listgovproposals({"type": "cfp", "pagination": {"start": tx2, "including_start": True, "limit": 1}})[0]["proposalId"], tx2) + + assert_equal(len(self.nodes[0].listgovproposals({"status": "voting"})), 3) + assert_equal(self.nodes[0].listgovproposals({"status": "voting", "pagination": {"start": tx1, "including_start": True, "limit": 1}})[0]["proposalId"], tx1) + assert_equal(self.nodes[0].listgovproposals({"status": "voting", "pagination": {"start": tx3, "including_start": True, "limit": 1}})[0]["proposalId"], tx3) + if __name__ == '__main__': OnChainGovernanceTest().main () diff --git a/test/functional/feature_on_chain_government_govvar_update.py b/test/functional/feature_on_chain_government_govvar_update.py index 864286d606..e4c3ac9803 100755 --- a/test/functional/feature_on_chain_government_govvar_update.py +++ b/test/functional/feature_on_chain_government_govvar_update.py @@ -414,12 +414,10 @@ def test_cfp_update_voc_emergency_period(self): height = self.nodes[0].getblockcount() # Create address for CFP - address = self.nodes[0].getnewaddress() context = "" title = "Create test community fund request proposal without automatic payout" - amount = 100 # Create CFP - propId = self.nodes[0].creategovvoc({"title": title, "context": context, "amount": amount, "payoutAddress": address, "emergency": True}) + propId = self.nodes[0].creategovvoc({"title": title, "context": context, "emergency": True}) self.nodes[0].generate(1) self.sync_blocks(timeout=120) @@ -441,12 +439,10 @@ def test_cfp_update_voc_emergency_fee(self): height = self.nodes[0].getblockcount() # Create address for CFP - address = self.nodes[0].getnewaddress() context = "" title = "Create test community fund request proposal without automatic payout" - amount = 100 # Create CFP - propId = self.nodes[0].creategovvoc({"title": title, "context": context, "amount": amount, "payoutAddress": address}) + propId = self.nodes[0].creategovvoc({"title": title, "context": context}) # Fund addresses self.nodes[0].sendtoaddress(self.address1, Decimal("1.0")) From aa0c423e9976d2bdd707da01b54873b0d254bff4 Mon Sep 17 00:00:00 2001 From: Mambisi Zempare Date: Fri, 13 Jan 2023 08:30:27 +0000 Subject: [PATCH 4/9] Feature: Auto Backup Wallet (#1331) * add backup schedule `MaybeBackupWallet` * refactor: better naming * variable backup time * add logs for errors * add `-backupwallet` to `DummyWalletInit::AddWalletOptions` * Lint * Default to no wallet backup Co-authored-by: Jouzo <15011228+Jouzo@users.noreply.github.com> Co-authored-by: jouzo --- src/dummywallet.cpp | 1 + src/wallet/init.cpp | 1 + src/wallet/load.cpp | 7 ++ src/wallet/walletdb.cpp | 31 +++++ src/wallet/walletdb.h | 160 ++++++++++++-------------- test/functional/wallet_multiwallet.py | 3 +- 6 files changed, 115 insertions(+), 88 deletions(-) diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 71f0566b0d..94b99a946f 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -49,6 +49,7 @@ void DummyWalletInit::AddWalletOptions() const "-flushwallet", "-privdb", "-walletrejectlongchains", + "-backupwallet", }); } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index abfcd58684..b7a216295c 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -67,6 +67,7 @@ void WalletInit::AddWalletOptions() const gArgs.AddArg("-dblogsize=", strprintf("Flush wallet database activity from memory to disk log every megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-backupwallet=", strprintf("Backup wallet database every minutes (default: %d), to disable <= 0", DEFAULT_WALLET_BACKUP_PERIOD), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); gArgs.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); } diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index f2c2bd7e4c..622bc1de8e 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -86,6 +87,12 @@ void StartWallets(CScheduler& scheduler) // Schedule periodic wallet flushes and tx rebroadcasts scheduler.scheduleEvery(MaybeCompactWalletDB, 2000); scheduler.scheduleEvery(MaybeResendWalletTxs, 1000); + // Schedule periodic backup for wallets + auto walletBackupPeriodMinutes = gArgs.GetArg("-backupwallet", DEFAULT_WALLET_BACKUP_PERIOD); + if (walletBackupPeriodMinutes > 0) { + auto walletBackupPeriodMs = std::chrono::duration_cast(std::chrono::minutes(walletBackupPeriodMinutes)); + scheduler.scheduleEvery(AutoBackupWallet, walletBackupPeriodMs.count()); + } } void FlushWallets() diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index eb0b5d2242..c282346df4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -677,6 +677,37 @@ void MaybeCompactWalletDB() fOneThread = false; } +void AutoBackupWallet() { + for (const std::shared_ptr &pwallet: GetWallets()) { + auto env = pwallet->GetDBHandle().env; + std::string walletName = pwallet->GetName().empty() ? "default" : pwallet->GetName(); + fs::path prevBackup = env->Directory() / strprintf("auto.backup.%s.bak1", walletName); + fs::path currentBackup = env->Directory() / strprintf("auto.backup.%s.bak2", walletName); + if (fs::exists(prevBackup) && !fs::exists(currentBackup)) { + pwallet->BackupWallet(currentBackup.string()); + } else if (fs::exists(prevBackup) && fs::exists(currentBackup)) { + fs::remove(prevBackup); + try { + fs::rename(currentBackup, prevBackup); + } catch (const fs::filesystem_error &) { + LogPrintf("failed rename %s to %s\n", prevBackup.string(), currentBackup.string()); + } + pwallet->BackupWallet(currentBackup.string()); + + } else if (!fs::exists(prevBackup) && fs::exists(currentBackup)) { + try { + fs::rename(currentBackup, prevBackup); + } catch (const fs::filesystem_error &) { + LogPrintf("failed rename %s to %s\n", prevBackup.string(), currentBackup.string()); + } + pwallet->BackupWallet(currentBackup.string()); + } else { + pwallet->BackupWallet(prevBackup.string()); + } + + } +} + // // Try to (very carefully!) recover wallet file if there is a problem. // diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 69041acc22..4844f95a4d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -7,13 +7,13 @@ #define DEFI_WALLET_WALLETDB_H #include +#include #include #include