From e58891d88088c9d3d08106e4742695e8ae6cdf69 Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 8 Jun 2022 07:43:53 +0100 Subject: [PATCH 1/8] Track token split in account history --- src/flushablestorage.h | 2 +- src/masternodes/accountshistory.cpp | 6 ++- src/masternodes/accountshistory.h | 16 ++++--- src/masternodes/govvariables/attributes.cpp | 17 ++++---- src/masternodes/masternodes.h | 10 +++++ src/masternodes/mn_checks.cpp | 2 + src/masternodes/mn_checks.h | 2 + src/validation.cpp | 45 +++++++++++++------- src/validation.h | 3 ++ test/functional/feature_futures.py | 1 + test/functional/feature_token_split.py | 46 +++++++++++++++++---- 11 files changed, 111 insertions(+), 39 deletions(-) diff --git a/src/flushablestorage.h b/src/flushablestorage.h index c1abd0ef1ab..8bd1b4fbc21 100644 --- a/src/flushablestorage.h +++ b/src/flushablestorage.h @@ -506,7 +506,7 @@ class CStorageView { } } - bool Flush() { return DB().Flush(); } + virtual bool Flush() { return DB().Flush(); } void Discard() { DB().Discard(); } size_t SizeEstimate() const { return DB().SizeEstimate(); } diff --git a/src/masternodes/accountshistory.cpp b/src/masternodes/accountshistory.cpp index 85ee769769d..987d99bf596 100644 --- a/src/masternodes/accountshistory.cpp +++ b/src/masternodes/accountshistory.cpp @@ -75,6 +75,10 @@ bool CAccountsHistoryWriter::Flush() return CCustomCSView::Flush(); } +CAccountHistoryStorage* CAccountsHistoryWriter::GetAccountHistoryStore() { + return writers ? writers->GetAccountHistoryStore() : nullptr; +}; + CAccountsHistoryEraser::CAccountsHistoryEraser(CCustomCSView & storage, uint32_t height, uint32_t txn, CHistoryErasers& erasers) : CStorageView(new CFlushableStorageKV(static_cast(storage.GetStorage()))), height(height), txn(txn), erasers(erasers) { @@ -95,7 +99,7 @@ Res CAccountsHistoryEraser::SubBalance(CScript const & owner, CTokenAmount) bool CAccountsHistoryEraser::Flush() { erasers.Flush(height, txn, vaultID); - return Res::Ok(); // makes sure no changes are applyed to underlaying view + return Res::Ok(); // makes sure no changes are applied to underlying view } CHistoryErasers::CHistoryErasers(CAccountHistoryStorage* historyView, CBurnHistoryStorage* burnView, CVaultHistoryStorage* vaultView) diff --git a/src/masternodes/accountshistory.h b/src/masternodes/accountshistory.h index 9cd1a2c5015..f33b7ab63e5 100644 --- a/src/masternodes/accountshistory.h +++ b/src/masternodes/accountshistory.h @@ -82,19 +82,21 @@ class CBurnHistoryStorage : public CAccountsHistoryView }; class CHistoryWriters { - CAccountHistoryStorage* historyView; - CBurnHistoryStorage* burnView; + CAccountHistoryStorage* historyView{}; + CBurnHistoryStorage* burnView{}; std::map diffs; std::map burnDiffs; std::map> vaultDiffs; public: - CVaultHistoryStorage* vaultView; + CVaultHistoryStorage* vaultView{}; CLoanSchemeCreation globalLoanScheme; std::string schemeID; CHistoryWriters(CAccountHistoryStorage* historyView, CBurnHistoryStorage* burnView, CVaultHistoryStorage* vaultView); + CAccountHistoryStorage* GetAccountHistoryStore() { return historyView; }; + void AddBalance(const CScript& owner, const CTokenAmount amount, const uint256& vaultID); void AddFeeBurn(const CScript& owner, const CAmount amount); void SubBalance(const CScript& owner, const CTokenAmount amount, const uint256& vaultID); @@ -127,7 +129,7 @@ class CAccountsHistoryWriter : public CCustomCSView const uint32_t txn; const uint256 txid; const uint8_t type; - CHistoryWriters* writers; + CHistoryWriters* writers{}; public: uint256 vaultID; @@ -135,7 +137,9 @@ class CAccountsHistoryWriter : public CCustomCSView CAccountsHistoryWriter(CCustomCSView & storage, uint32_t height, uint32_t txn, const uint256& txid, uint8_t type, CHistoryWriters* writers); Res AddBalance(CScript const & owner, CTokenAmount amount) override; Res SubBalance(CScript const & owner, CTokenAmount amount) override; - bool Flush(); + bool Flush() override; + + CAccountHistoryStorage* GetAccountHistoryStore() override; }; class CAccountsHistoryEraser : public CCustomCSView @@ -150,7 +154,7 @@ class CAccountsHistoryEraser : public CCustomCSView CAccountsHistoryEraser(CCustomCSView & storage, uint32_t height, uint32_t txn, CHistoryErasers& erasers); Res AddBalance(CScript const & owner, CTokenAmount amount) override; Res SubBalance(CScript const & owner, CTokenAmount amount) override; - bool Flush(); + bool Flush() override; }; extern std::unique_ptr paccountHistoryDB; diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index 216e88c7b87..5780684e36c 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -6,8 +6,8 @@ #include /// CAccountsHistoryWriter #include /// CCustomCSView -#include /// GetAggregatePrice -#include /// CustomTxType +#include /// GetAggregatePrice / CustomTxType +#include /// GetNextAccPosition #include /// GetDecimaleString #include /// ValueFromAmount @@ -531,22 +531,25 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Current}; auto balances = GetValue(liveKey, CBalances{}); - auto txn = std::numeric_limits::max(); + CAccountHistoryStorage* historyStore{mnview.GetAccountHistoryStore()}; + const auto currentHeight = mnview.GetLastHeight() + 1; for (const auto& [key, value] : userFuturesValues) { mnview.EraseFuturesUserValues(key); - CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter subView(mnview, height, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + CHistoryWriters subWriters{historyStore, nullptr, nullptr}; + CAccountsHistoryWriter subView(mnview, currentHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + auto res = subView.SubBalance(*contractAddressValue, value.source); if (!res) { return res; } subView.Flush(); - CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter addView(mnview, height, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + CHistoryWriters addWriters{historyStore, nullptr, nullptr}; + CAccountsHistoryWriter addView(mnview, currentHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + res = addView.AddBalance(key.owner, value.source); if (!res) { return res; diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 81bace5c3a3..91872176a72 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -29,6 +29,7 @@ #include #include +class CAccountHistoryStorage; class CBlockIndex; class CTransaction; @@ -386,6 +387,7 @@ class CCustomCSView Res PopulateLoansData(CCollateralLoans& result, CVaultId const& vaultId, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); Res PopulateCollateralData(CCollateralLoans& result, CVaultId const& vaultId, CBalances const& collaterals, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); + CAccountHistoryStorage* accHistoryStore{}; public: // Increase version when underlaying tables are changed static constexpr const int DbVersion = 1; @@ -447,6 +449,14 @@ class CCustomCSView return static_cast(DB()); } + virtual CAccountHistoryStorage* GetAccountHistoryStore() { + return accHistoryStore; + } + + void SetAccountHistoryStore(CAccountHistoryStorage* store) { + accHistoryStore = store; + } + struct DbVersion { static constexpr uint8_t prefix() { return 'D'; } }; }; diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 056d4e70678..efa47a81a4a 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -81,6 +81,7 @@ std::string ToString(CustomTxType type) { case CustomTxType::AuctionBid: return "AuctionBid"; case CustomTxType::FutureSwapExecution: return "FutureSwapExecution"; case CustomTxType::FutureSwapRefund: return "FutureSwapRefund"; + case CustomTxType::TokenSplit: return "TokenSplit"; case CustomTxType::Reject: return "Reject"; case CustomTxType::None: return "None"; } @@ -179,6 +180,7 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) { case CustomTxType::AuctionBid: return CAuctionBidMessage{}; case CustomTxType::FutureSwapExecution: return CCustomTxMessageNone{}; case CustomTxType::FutureSwapRefund: return CCustomTxMessageNone{}; + case CustomTxType::TokenSplit: return CCustomTxMessageNone{}; case CustomTxType::Reject: return CCustomTxMessageNone{}; case CustomTxType::None: return CCustomTxMessageNone{}; } diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index a47b702d271..db0645d05ca 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -102,6 +102,7 @@ enum class CustomTxType : uint8_t AuctionBid = 'I', FutureSwapExecution = 'q', FutureSwapRefund = 'w', + TokenSplit = 'P', }; inline CustomTxType CustomTxCodeToType(uint8_t ch) { @@ -159,6 +160,7 @@ inline CustomTxType CustomTxCodeToType(uint8_t ch) { case CustomTxType::AuctionBid: case CustomTxType::FutureSwapExecution: case CustomTxType::FutureSwapRefund: + case CustomTxType::TokenSplit: case CustomTxType::Reject: case CustomTxType::None: return type; diff --git a/src/validation.cpp b/src/validation.cpp index a68d55ece44..a166b803fb4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2189,6 +2189,7 @@ static int64_t nBlocksTotal = 0; // Holds position for burn TXs appended to block in burn history static std::vector::size_type nPhantomBurnTx{0}; +static uint32_t nPhantomAccTx{}; static std::map mapBurnAmounts; @@ -2196,6 +2197,10 @@ static uint32_t GetNextBurnPosition() { return nPhantomBurnTx++; } +uint32_t GetNextAccPosition() { + return nPhantomAccTx--; +} + // Burn non-transaction amounts, that is burns that are not sent directly to the burn address // in a account or UTXO transaction. When parsing TXs via ConnectBlock that result in a burn // from an account in this way call the function below. This will add the burn to the map to @@ -2481,6 +2486,9 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // Reset phanton TX to block TX count nPhantomBurnTx = block.vtx.size(); + // Reset phantom TX to end of block TX count + nPhantomAccTx = std::numeric_limits::max(); + // Wipe burn map, we only want TXs added during ConnectBlock mapBurnAmounts.clear(); @@ -3562,15 +3570,13 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache std::map unpaidContracts; std::set deletionPending; - auto txn = std::numeric_limits::max(); - auto dUsdToTokenSwapsCounter = 0; auto tokenTodUsdSwapsCounter = 0; cache.ForEachFuturesUserValues([&](const CFuturesUserKey& key, const CFuturesUserValue& futuresValues){ CHistoryWriters writers{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter view(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapExecution), &writers); + CAccountsHistoryWriter view(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapExecution), &writers); deletionPending.insert(key); @@ -3636,12 +3642,12 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache for (const auto& [key, value] : unpaidContracts) { CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter subView(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + CAccountsHistoryWriter subView(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); subView.SubBalance(*contractAddressValue, value.source); subView.Flush(); CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter addView(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + CAccountsHistoryWriter addView(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); addView.AddBalance(key.owner, value.source); addView.Flush(); @@ -4403,6 +4409,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } auto view{cache}; + view.SetAccountHistoryStore(paccountHistoryDB.get()); // Refund affected future swaps auto res = attributes->RefundFuturesContracts(view, std::numeric_limits::max(), id); @@ -4496,14 +4503,12 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin continue; } - CAccounts addAccounts; - CAccounts subAccounts; + std::map> balanceUpdates; view.ForEachBalance([&, multiplier = multiplier](CScript const& owner, const CTokenAmount& balance) { if (oldTokenId.v == balance.nTokenId.v) { const auto newBalance = CalculateNewAmount(multiplier, balance.nValue); - addAccounts[owner].Add({newTokenId, newBalance}); - subAccounts[owner].Add(balance); + balanceUpdates.emplace(owner, std::pair{{newTokenId, newBalance}, balance}); totalBalance += newBalance; auto newBalanceStr = CTokenAmount{newTokenId, newBalance}.ToString(); @@ -4515,8 +4520,8 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin }); LogPrintf("Token split info: rebalance " /* Continued */ - "(id: %d, symbol: %s, add-accounts: %d, sub-accounts: %d, val: %d)\n", - id, newToken.symbol, addAccounts.size(), subAccounts.size(), totalBalance); + "(id: %d, symbol: %s, accounts: %d, val: %d)\n", + id, newToken.symbol, balanceUpdates.size(), totalBalance); res = view.AddMintedTokens(newTokenId, totalBalance); if (!res) { @@ -4525,18 +4530,26 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } try { - for (const auto& [owner, balances] : addAccounts) { - res = view.AddBalances(owner, balances); + + for (const auto& [owner, balances] : balanceUpdates) { + + CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter subView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &subWriters); + + res = subView.SubBalance(owner, balances.second); if (!res) { throw std::runtime_error(res.msg); } - } + subView.Flush(); + + CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter addView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &addWriters); - for (const auto& [owner, balances] : subAccounts) { - res = view.SubBalances(owner, balances); + res = addView.AddBalance(owner, balances.first); if (!res) { throw std::runtime_error(res.msg); } + addView.Flush(); } } catch (const std::runtime_error& e) { LogPrintf("Token split failed. %s\n", res.msg); diff --git a/src/validation.h b/src/validation.h index 72c1c2bf6a3..e39c859f9ec 100644 --- a/src/validation.h +++ b/src/validation.h @@ -349,6 +349,9 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai */ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +/** Used to get the end of block TXN for account history entries */ +uint32_t GetNextAccPosition(); + /** * Closure representing one script verification * Note that this stores references to the spending transaction diff --git a/test/functional/feature_futures.py b/test/functional/feature_futures.py index f73348ea738..77565312bb6 100755 --- a/test/functional/feature_futures.py +++ b/test/functional/feature_futures.py @@ -833,6 +833,7 @@ def check_gov_var_change(self): txn_first = 4294967295 result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0, 'txtype':'w'}) result.sort(key = sort_history, reverse = True) + assert_equal(len(result), 4) for result_entry in result: assert_equal(result_entry['blockHeight'], self.nodes[0].getblockcount()) assert_equal(result_entry['type'], 'FutureSwapRefund') diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 4267552ed4e..73ab72d6f23 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -534,11 +534,15 @@ def token_split(self): assert_equal(result[f'v0/token/{self.idGOOGL}/loan_payback_fee_pct/{self.idTSLA}'], '0.25') assert_equal(result[f'v0/token/{self.idTSLA}/loan_payback_fee_pct/{self.idTSLA}'], '0.25') - # Check new balances + # Check new balances and history for [address, amount] in self.funded_addresses: account = self.nodes[0].getaccount(address) new_amount = Decimal(account[0].split('@')[0]) assert_equal(new_amount, amount * 2) + history = self.nodes[0].listaccounthistory(address, {'txtype': 'TokenSplit'}) + #assert_equal(len(history), 2) + #assert_equal(history[0]['amounts'][0], f'{-amount:.8f}' + f'@{self.symbolTSLA}/v1') + #assert_equal(history[1]['amounts'][0], account[0]) # Token split self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{str(self.nodes[0].getblockcount() + 2)}':f'{self.idTSLA}/-3'}}) @@ -756,7 +760,7 @@ def check_govvar_deletion(self): def check_future_swap_refund(self): - # Set all futures attributes but set active to false + # Set all futures attributes self.nodes[0].setgov({"ATTRIBUTES":{ 'v0/params/dfip2203/reward_pct':'0.05', 'v0/params/dfip2203/block_period':'2880' @@ -772,6 +776,9 @@ def check_future_swap_refund(self): address_msft = self.nodes[0].getnewaddress("", "legacy") address_locked = self.nodes[0].getnewaddress("", "legacy") + # Get block to revert to + revert_block = self.nodes[0].getblockcount() + # Fund addresses self.nodes[0].accounttoaccount(self.address, {address_msft: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']}) self.nodes[0].accounttoaccount(self.address, {address_locked: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']}) @@ -811,12 +818,35 @@ def check_future_swap_refund(self): result = self.nodes[0].getaccount(address_msft) assert_equal(result, [f'1.00000000@{self.symbolDUSD}', f'2.00000000@{self.symbolMSFT}']) - # Swap old for new values - self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0] - - # Unlock token - self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idMSFT}':'false'}}) - self.nodes[0].generate(1) + # Check future swap and token split account history + result = self.nodes[0].listaccounthistory(address_msft, {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0}) + assert_equal(result[0]['owner'], address_msft) + assert_equal(result[0]['type'], 'FutureSwapRefund') + assert_equal(result[0]['amounts'], [f'1.00000000@{self.symbolDUSD}']) + assert_equal(result[1]['owner'], address_msft) + assert_equal(result[1]['type'], 'FutureSwapRefund') + assert_equal(result[1]['amounts'], [f'1.00000000@{self.symbolMSFT}/v2']) + assert_equal(result[2]['owner'], address_msft) + assert_equal(result[2]['type'], 'TokenSplit') + assert_equal(result[2]['amounts'], [f'-1.00000000@{self.symbolMSFT}/v2']) + assert_equal(result[3]['owner'], address_msft) + assert_equal(result[3]['type'], 'TokenSplit') + assert_equal(result[3]['amounts'], [f'2.00000000@{self.symbolMSFT}']) + + # Get block to move forward to after revert + future_block = self.nodes[0].getblockcount() + + # Revert chain to before address funding + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(revert_block)) + self.nodes[0].clearmempool() + + # Move forward again without split + self.nodes[0].generate(future_block - self.nodes[0].getblockcount()) + assert_equal(self.nodes[0].getblockcount(), future_block) + + # Account history should now be empty + result = self.nodes[0].listaccounthistory(address_msft, {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0}) + assert_equal(result, []) def migrate_auction_batches(self): From 41d573a7391e53911070d764916840d70b1e1ddf Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 8 Jun 2022 13:03:19 +0100 Subject: [PATCH 2/8] Add acc hist and burn index multi-index height order for simple rollback --- src/init.cpp | 2 + src/masternodes/accountshistory.cpp | 170 +++++++------- src/masternodes/accountshistory.h | 47 +--- src/masternodes/masternodes.cpp | 24 -- src/masternodes/masternodes.h | 2 - src/masternodes/mn_checks.cpp | 312 ------------------------- src/masternodes/mn_checks.h | 1 - src/masternodes/rpc_accounts.cpp | 37 +-- src/masternodes/tokens.cpp | 32 --- src/masternodes/tokens.h | 2 - src/test/storage_tests.cpp | 22 -- src/validation.cpp | 17 +- test/functional/feature_token_split.py | 1 + 13 files changed, 124 insertions(+), 545 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 5c07b2a299d..6f7d4198344 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1691,10 +1691,12 @@ bool AppInitMain(InitInterfaces& interfaces) paccountHistoryDB.reset(); if (gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX)) { paccountHistoryDB = std::make_unique(GetDataDir() / "history", nCustomCacheSize, false, fReset || fReindexChainState); + paccountHistoryDB->CreateMultiIndexIfNeeded(); } pburnHistoryDB.reset(); pburnHistoryDB = std::make_unique(GetDataDir() / "burn", nCustomCacheSize, false, fReset || fReindexChainState); + pburnHistoryDB->CreateMultiIndexIfNeeded(); // Create vault history DB pvaultHistoryDB.reset(); diff --git a/src/masternodes/accountshistory.cpp b/src/masternodes/accountshistory.cpp index 987d99bf596..b6814cd4171 100644 --- a/src/masternodes/accountshistory.cpp +++ b/src/masternodes/accountshistory.cpp @@ -8,15 +8,79 @@ #include #include -void CAccountsHistoryView::ForEachAccountHistory(std::function)> callback, AccountHistoryKey const & start) +struct AccountHistoryKeyNew { + uint32_t blockHeight; + CScript owner; + uint32_t txn; // for order in block + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + if (ser_action.ForRead()) { + READWRITE(WrapBigEndian(blockHeight)); + blockHeight = ~blockHeight; + } else { + uint32_t blockHeight_ = ~blockHeight; + READWRITE(WrapBigEndian(blockHeight_)); + } + + READWRITE(owner); + + if (ser_action.ForRead()) { + READWRITE(WrapBigEndian(txn)); + txn = ~txn; + } else { + uint32_t txn_ = ~txn; + READWRITE(WrapBigEndian(txn_)); + } + } +}; + +static AccountHistoryKeyNew Convert(AccountHistoryKey const & key) { + return {key.blockHeight, key.owner, key.txn}; +} + +static AccountHistoryKey Convert(AccountHistoryKeyNew const & key) { + return {key.owner, key.blockHeight, key.txn}; +} + +void CAccountsHistoryView::CreateMultiIndexIfNeeded() { - ForEach(callback, start); + AccountHistoryKeyNew anyNewKey{~0u, {}, ~0u}; + if (auto it = LowerBound(anyNewKey); it.Valid()) { + return; + } + + LogPrintf("Adding multi index in progress...\n"); + + auto startTime = GetTimeMillis(); + + AccountHistoryKey startKey{{}, ~0u, ~0u}; + auto it = LowerBound(startKey); + for (; it.Valid(); it.Next()) { + WriteBy(Convert(it.Key()), '\0'); + } + + Flush(); + + LogPrint(BCLog::BENCH, " - Multi index took: %dms\n", GetTimeMillis() - startTime); } -Res CAccountsHistoryView::WriteAccountHistory(const AccountHistoryKey& key, const AccountHistoryValue& value) +void CAccountsHistoryView::ForEachAccountHistory(std::function callback, + const CScript & owner, uint32_t height, uint32_t txn) { - WriteBy(key, value); - return Res::Ok(); + if (!owner.empty()) { + ForEach(callback, {owner, height, txn}); + return; + } + + ForEach([&](AccountHistoryKeyNew const & newKey, char) { + auto key = Convert(newKey); + auto value = ReadAccountHistory(key); + assert(value); + return callback(key, *value); + }, {height, owner, txn}); } std::optional CAccountsHistoryView::ReadAccountHistory(AccountHistoryKey const & key) const @@ -24,9 +88,32 @@ std::optional CAccountsHistoryView::ReadAccountHistory(Acco return ReadBy(key); } +Res CAccountsHistoryView::WriteAccountHistory(const AccountHistoryKey& key, const AccountHistoryValue& value) +{ + WriteBy(key, value); + WriteBy(Convert(key), '\0'); + return Res::Ok(); +} + Res CAccountsHistoryView::EraseAccountHistory(const AccountHistoryKey& key) { EraseBy(key); + EraseBy(Convert(key)); + return Res::Ok(); +} + +Res CAccountsHistoryView::EraseAccountHistoryHeight(uint32_t height) +{ + std::vector keysToDelete; + + auto it = LowerBound(AccountHistoryKeyNew{height, {}, ~0u}); + for (; it.Valid() && it.Key().blockHeight == height; it.Next()) { + keysToDelete.push_back(Convert(it.Key())); + } + + for (const auto& key : keysToDelete) { + EraseAccountHistory(key); + } return Res::Ok(); } @@ -79,79 +166,6 @@ CAccountHistoryStorage* CAccountsHistoryWriter::GetAccountHistoryStore() { return writers ? writers->GetAccountHistoryStore() : nullptr; }; -CAccountsHistoryEraser::CAccountsHistoryEraser(CCustomCSView & storage, uint32_t height, uint32_t txn, CHistoryErasers& erasers) - : CStorageView(new CFlushableStorageKV(static_cast(storage.GetStorage()))), height(height), txn(txn), erasers(erasers) -{ -} - -Res CAccountsHistoryEraser::AddBalance(CScript const & owner, CTokenAmount) -{ - erasers.AddBalance(owner, vaultID); - return Res::Ok(); -} - -Res CAccountsHistoryEraser::SubBalance(CScript const & owner, CTokenAmount) -{ - erasers.SubBalance(owner, vaultID); - return Res::Ok(); -} - -bool CAccountsHistoryEraser::Flush() -{ - erasers.Flush(height, txn, vaultID); - return Res::Ok(); // makes sure no changes are applied to underlying view -} - -CHistoryErasers::CHistoryErasers(CAccountHistoryStorage* historyView, CBurnHistoryStorage* burnView, CVaultHistoryStorage* vaultView) - : historyView(historyView), burnView(burnView), vaultView(vaultView) {} - -void CHistoryErasers::AddBalance(const CScript& owner, const uint256& vaultID) -{ - if (historyView) { - accounts.insert(owner); - } - if (burnView && owner == Params().GetConsensus().burnAddress) { - burnAccounts.insert(owner); - } - if (vaultView && !vaultID.IsNull()) { - vaults.insert(vaultID); - } -} - -void CHistoryErasers::SubFeeBurn(const CScript& owner) -{ - if (burnView) { - burnAccounts.insert(owner); - } -} - -void CHistoryErasers::SubBalance(const CScript& owner, const uint256& vaultID) -{ - if (historyView) { - accounts.insert(owner); - } - if (burnView && owner == Params().GetConsensus().burnAddress) { - burnAccounts.insert(owner); - } - if (vaultView && !vaultID.IsNull()) { - vaults.insert(vaultID); - } -} - -void CHistoryErasers::Flush(const uint32_t height, const uint32_t txn, const uint256& vaultID) -{ - if (historyView) { - for (const auto& account : accounts) { - historyView->EraseAccountHistory({account, height, txn}); - } - } - if (burnView) { - for (const auto& account : burnAccounts) { - burnView->EraseAccountHistory({account, height, txn}); - } - } -} - CHistoryWriters::CHistoryWriters(CAccountHistoryStorage* historyView, CBurnHistoryStorage* burnView, CVaultHistoryStorage* vaultView) : historyView(historyView), burnView(burnView), vaultView(vaultView) {} diff --git a/src/masternodes/accountshistory.h b/src/masternodes/accountshistory.h index f33b7ab63e5..6497d1db5dd 100644 --- a/src/masternodes/accountshistory.h +++ b/src/masternodes/accountshistory.h @@ -59,25 +59,31 @@ struct AccountHistoryValue { class CAccountsHistoryView : public virtual CStorageView { public: + void CreateMultiIndexIfNeeded(); + Res EraseAccountHistoryHeight(uint32_t height); + [[nodiscard]] std::optional ReadAccountHistory(AccountHistoryKey const & key) const; Res WriteAccountHistory(AccountHistoryKey const & key, AccountHistoryValue const & value); - std::optional ReadAccountHistory(AccountHistoryKey const & key) const; Res EraseAccountHistory(AccountHistoryKey const & key); - void ForEachAccountHistory(std::function)> callback, AccountHistoryKey const & start = {}); + void ForEachAccountHistory(std::function callback, + const CScript& owner = {}, uint32_t height = std::numeric_limits::max(), uint32_t txn = std::numeric_limits::max()); // tags - struct ByAccountHistoryKey { static constexpr uint8_t prefix() { return 'h'; } }; + struct ByAccountHistoryKey { static constexpr uint8_t prefix() { return 'h'; } }; + struct ByAccountHistoryKeyNew { static constexpr uint8_t prefix() { return 'H'; } }; }; class CAccountHistoryStorage : public CAccountsHistoryView , public CAuctionHistoryView { public: + CAccountHistoryStorage(CAccountHistoryStorage& accountHistory) : CStorageView(&accountHistory.DB()) {} CAccountHistoryStorage(const fs::path& dbName, std::size_t cacheSize, bool fMemory = false, bool fWipe = false); }; class CBurnHistoryStorage : public CAccountsHistoryView { public: + CBurnHistoryStorage(CBurnHistoryStorage& burnHistory) : CStorageView(&burnHistory.DB()) {} CBurnHistoryStorage(const fs::path& dbName, std::size_t cacheSize, bool fMemory = false, bool fWipe = false); }; @@ -103,26 +109,6 @@ class CHistoryWriters { void Flush(const uint32_t height, const uint256& txid, const uint32_t txn, const uint8_t type, const uint256& vaultID); }; -class CHistoryErasers { - CAccountHistoryStorage* historyView; - CBurnHistoryStorage* burnView; - std::set accounts; - std::set burnAccounts; - std::set vaults; - -public: - CVaultHistoryStorage* vaultView; - bool removeLoanScheme{false}; - uint256 schemeCreationTxid; - - CHistoryErasers(CAccountHistoryStorage* historyView, CBurnHistoryStorage* burnView, CVaultHistoryStorage* vaultView); - - void AddBalance(const CScript& owner, const uint256& vaultID); - void SubFeeBurn(const CScript& owner); - void SubBalance(const CScript& owner, const uint256& vaultID); - void Flush(const uint32_t height, const uint32_t txn, const uint256& vaultID); -}; - class CAccountsHistoryWriter : public CCustomCSView { const uint32_t height; @@ -142,21 +128,6 @@ class CAccountsHistoryWriter : public CCustomCSView CAccountHistoryStorage* GetAccountHistoryStore() override; }; -class CAccountsHistoryEraser : public CCustomCSView -{ - const uint32_t height; - const uint32_t txn; - CHistoryErasers& erasers; - -public: - uint256 vaultID; - - CAccountsHistoryEraser(CCustomCSView & storage, uint32_t height, uint32_t txn, CHistoryErasers& erasers); - Res AddBalance(CScript const & owner, CTokenAmount amount) override; - Res SubBalance(CScript const & owner, CTokenAmount amount) override; - bool Flush() override; -}; - extern std::unique_ptr paccountHistoryDB; extern std::unique_ptr pburnHistoryDB; diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index a6a4cf2c938..cfa6ab2f28a 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -490,30 +490,6 @@ void CMasternodesView::EraseSubNodesLastBlockTime(const uint256& nodeId, const u } } -Res CMasternodesView::UnCreateMasternode(const uint256 & nodeId) -{ - auto node = GetMasternode(nodeId); - if (node) { - EraseBy(nodeId); - EraseBy(node->operatorAuthAddress); - EraseBy(node->ownerAuthAddress); - return Res::Ok(); - } - return Res::Err("No such masternode %s", nodeId.GetHex()); -} - -Res CMasternodesView::UnResignMasternode(const uint256 & nodeId, const uint256 & resignTx) -{ - auto node = GetMasternode(nodeId); - if (node && node->resignTx == resignTx) { - node->resignHeight = -1; - node->resignTx = {}; - WriteBy(nodeId, *node); - return Res::Ok(); - } - return Res::Err("No such masternode %s, resignTx: %s", nodeId.GetHex(), resignTx.GetHex()); -} - uint16_t CMasternodesView::GetTimelock(const uint256& nodeId, const CMasternode& node, const uint64_t height) const { auto timelock = ReadBy(nodeId); diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 91872176a72..bbdf43f99f8 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -198,8 +198,6 @@ class CMasternodesView : public virtual CStorageView Res CreateMasternode(uint256 const & nodeId, CMasternode const & node, uint16_t timelock); Res ResignMasternode(uint256 const & nodeId, uint256 const & txid, int height); - Res UnCreateMasternode(uint256 const & nodeId); - Res UnResignMasternode(uint256 const & nodeId, uint256 const & resignTx); Res SetForcedRewardAddress(uint256 const & nodeId, const char rewardAddressType, CKeyID const & rewardAddress, int height); Res RemForcedRewardAddress(uint256 const & nodeId, int height); Res UpdateMasternode(uint256 const & nodeId, char operatorType, const CKeyID& operatorAuthAddress, int height); diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index efa47a81a4a..cd86d8f1f6c 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3449,270 +3449,6 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor } }; -class CCustomTxRevertVisitor : public CCustomTxVisitor -{ - Res EraseHistory(const CScript& owner) const { - // notify account changes, no matter Sub or Add - return mnview.AddBalance(owner, {}); - } - -public: - using CCustomTxVisitor::CCustomTxVisitor; - - template - Res operator()(const T&) const { - return Res::Ok(); - } - - Res operator()(const CCreateMasterNodeMessage& obj) const { - auto res = CheckMasternodeCreationTx(); - return !res ? res : mnview.UnCreateMasternode(tx.GetHash()); - } - - Res operator()(const CResignMasterNodeMessage& obj) const { - auto res = HasCollateralAuth(obj); - return !res ? res : mnview.UnResignMasternode(obj, tx.GetHash()); - } - - Res operator()(const CCreateTokenMessage& obj) const { - auto res = CheckTokenCreationTx(); - return !res ? res : mnview.RevertCreateToken(tx.GetHash()); - } - - Res operator()(const CCreatePoolPairMessage& obj) const { - //check foundation auth - if (!HasFoundationAuth()) { - return Res::Err("tx not from foundation member"); - } - auto pool = mnview.GetPoolPair(obj.poolPair.idTokenA, obj.poolPair.idTokenB); - if (!pool) { - return Res::Err("no such poolPair tokenA %s, tokenB %s", - obj.poolPair.idTokenA.ToString(), - obj.poolPair.idTokenB.ToString()); - } - return mnview.RevertCreateToken(tx.GetHash()); - } - - Res operator()(const CMintTokensMessage& obj) const { - for (const auto& kv : obj.balances) { - DCT_ID tokenId = kv.first; - auto token = mnview.GetToken(tokenId); - if (!token) { - return Res::Err("token %s does not exist!", tokenId.ToString()); - } - auto tokenImpl = static_cast(*token); - const Coin& coin = coins.AccessCoin(COutPoint(tokenImpl.creationTx, 1)); - EraseHistory(coin.out.scriptPubKey); - } - return Res::Ok(); - } - - Res operator()(const CPoolSwapMessage& obj) const { - EraseHistory(obj.to); - return EraseHistory(obj.from); - } - - Res operator()(const CPoolSwapMessageV2& obj) const { - return (*this)(obj.swapInfo); - } - - Res operator()(const CLiquidityMessage& obj) const { - for (const auto& kv : obj.from) { - EraseHistory(kv.first); - } - return EraseHistory(obj.shareAddress); - } - - Res operator()(const CRemoveLiquidityMessage& obj) const { - return EraseHistory(obj.from); - } - - Res operator()(const CUtxosToAccountMessage& obj) const { - for (const auto& account : obj.to) { - EraseHistory(account.first); - } - return Res::Ok(); - } - - Res operator()(const CAccountToUtxosMessage& obj) const { - return EraseHistory(obj.from); - } - - Res operator()(const CAccountToAccountMessage& obj) const { - for (const auto& account : obj.to) { - EraseHistory(account.first); - } - return EraseHistory(obj.from); - } - - Res operator()(const CSmartContractMessage& obj) const { - for (const auto& account : obj.accounts) { - EraseHistory(account.first); - } - return Res::Ok(); - } - - Res operator()(const CFutureSwapMessage& obj) const { - EraseHistory(obj.owner); - return Res::Ok(); - } - - Res operator()(const CAnyAccountsToAccountsMessage& obj) const { - for (const auto& account : obj.to) { - EraseHistory(account.first); - } - for (const auto& account : obj.from) { - EraseHistory(account.first); - } - return Res::Ok(); - } - - Res operator()(const CICXCreateOrderMessage& obj) const { - if (obj.orderType == CICXOrder::TYPE_INTERNAL) { - auto hash = tx.GetHash(); - EraseHistory({hash.begin(), hash.end()}); - EraseHistory(obj.ownerAddress); - } - return Res::Ok(); - } - - Res operator()(const CICXMakeOfferMessage& obj) const { - auto hash = tx.GetHash(); - EraseHistory({hash.begin(), hash.end()}); - return EraseHistory(obj.ownerAddress); - } - - Res operator()(const CICXSubmitDFCHTLCMessage& obj) const { - auto offer = mnview.GetICXMakeOfferByCreationTx(obj.offerTx); - if (!offer) - return Res::Err("offer with creation tx %s does not exists!", obj.offerTx.GetHex()); - - auto order = mnview.GetICXOrderByCreationTx(offer->orderTx); - if (!order) - return Res::Err("order with creation tx %s does not exists!", offer->orderTx.GetHex()); - - EraseHistory(offer->ownerAddress); - if (order->orderType == CICXOrder::TYPE_INTERNAL) { - CScript orderTxidAddr(order->creationTx.begin(), order->creationTx.end()); - CScript offerTxidAddr(offer->creationTx.begin(), offer->creationTx.end()); - EraseHistory(orderTxidAddr); - EraseHistory(offerTxidAddr); - EraseHistory(consensus.burnAddress); - } - auto hash = tx.GetHash(); - return EraseHistory({hash.begin(), hash.end()}); - } - - Res operator()(const CICXSubmitEXTHTLCMessage& obj) const { - auto offer = mnview.GetICXMakeOfferByCreationTx(obj.offerTx); - if (!offer) - return Res::Err("order with creation tx %s does not exists!", obj.offerTx.GetHex()); - - auto order = mnview.GetICXOrderByCreationTx(offer->orderTx); - if (!order) - return Res::Err("order with creation tx %s does not exists!", offer->orderTx.GetHex()); - - if (order->orderType == CICXOrder::TYPE_EXTERNAL) { - CScript offerTxidAddr(offer->creationTx.begin(), offer->creationTx.end()); - EraseHistory(offerTxidAddr); - EraseHistory(offer->ownerAddress); - EraseHistory(consensus.burnAddress); - } - return Res::Ok(); - } - - Res operator()(const CICXClaimDFCHTLCMessage& obj) const { - auto dfchtlc = mnview.GetICXSubmitDFCHTLCByCreationTx(obj.dfchtlcTx); - if (!dfchtlc) - return Res::Err("dfc htlc with creation tx %s does not exists!", obj.dfchtlcTx.GetHex()); - - auto offer = mnview.GetICXMakeOfferByCreationTx(dfchtlc->offerTx); - if (!offer) - return Res::Err("offer with creation tx %s does not exists!", dfchtlc->offerTx.GetHex()); - - auto order = mnview.GetICXOrderByCreationTx(offer->orderTx); - if (!order) - return Res::Err("order with creation tx %s does not exists!", offer->orderTx.GetHex()); - - CScript htlcTxidAddr(dfchtlc->creationTx.begin(), dfchtlc->creationTx.end()); - EraseHistory(htlcTxidAddr); - EraseHistory(order->ownerAddress); - if (order->orderType == CICXOrder::TYPE_INTERNAL) - EraseHistory(offer->ownerAddress); - return Res::Ok(); - } - - Res operator()(const CICXCloseOrderMessage& obj) const { - std::unique_ptr order; - if (!(order = mnview.GetICXOrderByCreationTx(obj.orderTx))) - return Res::Err("order with creation tx %s does not exists!", obj.orderTx.GetHex()); - - if (order->orderType == CICXOrder::TYPE_INTERNAL) { - CScript txidAddr(order->creationTx.begin(), order->creationTx.end()); - EraseHistory(txidAddr); - EraseHistory(order->ownerAddress); - } - return Res::Ok(); - } - - Res operator()(const CICXCloseOfferMessage& obj) const { - std::unique_ptr offer; - if (!(offer = mnview.GetICXMakeOfferByCreationTx(obj.offerTx))) - return Res::Err("offer with creation tx %s does not exists!", obj.offerTx.GetHex()); - - CScript txidAddr(offer->creationTx.begin(), offer->creationTx.end()); - EraseHistory(txidAddr); - return EraseHistory(offer->ownerAddress); - } - - Res operator()(const CDepositToVaultMessage& obj) const { - return EraseHistory(obj.from); - } - - Res operator()(const CCloseVaultMessage& obj) const { - return EraseHistory(obj.to); - } - - Res operator()(const CLoanTakeLoanMessage& obj) const { - const auto vault = mnview.GetVault(obj.vaultId); - if (!vault) - return Res::Err("Vault <%s> not found", obj.vaultId.GetHex()); - - return EraseHistory(!obj.to.empty() ? obj.to : vault->ownerAddress); - } - - Res operator()(const CWithdrawFromVaultMessage& obj) const { - return EraseHistory(obj.to); - } - - Res operator()(const CLoanPaybackLoanMessage& obj) const { - const auto vault = mnview.GetVault(obj.vaultId); - if (!vault) - return Res::Err("Vault <%s> not found", obj.vaultId.GetHex()); - - EraseHistory(obj.from); - EraseHistory(consensus.burnAddress); - return EraseHistory(vault->ownerAddress); - } - - Res operator()(const CLoanPaybackLoanV2Message& obj) const { - const auto vault = mnview.GetVault(obj.vaultId); - if (!vault) - return Res::Err("Vault <%s> not found", obj.vaultId.GetHex()); - - EraseHistory(obj.from); - EraseHistory(consensus.burnAddress); - return EraseHistory(vault->ownerAddress); - } - - Res operator()(const CAuctionBidMessage& obj) const { - if (auto bid = mnview.GetAuctionBid({obj.vaultId, obj.index})) - EraseHistory(bid->first); - - return EraseHistory(obj.from); - } -}; - Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage) { try { return boost::apply_visitor(CCustomMetadataParseVisitor(height, consensus, metadata), txMessage); @@ -3785,16 +3521,6 @@ Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTr } } -Res CustomTxRevert(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage) { - try { - return boost::apply_visitor(CCustomTxRevertVisitor(tx, height, coins, mnview, consensus), txMessage); - } catch (const std::exception& e) { - return Res::Err(e.what()); - } catch (...) { - return Res::Err("unexpected error"); - } -} - bool ShouldReturnNonFatalError(const CTransaction& tx, uint32_t height) { static const std::map skippedTx = { { 471222, uint256S("0ab0b76352e2d865761f4c53037041f33e1200183d55cdf6b09500d6f16b7329") }, @@ -3803,44 +3529,6 @@ bool ShouldReturnNonFatalError(const CTransaction& tx, uint32_t height) { return it != skippedTx.end() && it->second == tx.GetHash(); } -Res RevertCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint32_t txn, CHistoryErasers& erasers) { - if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs - return Res::Ok(); - } - auto res = Res::Ok(); - std::vector metadata; - auto txType = GuessCustomTxType(tx, metadata); - switch(txType) - { - case CustomTxType::CreateMasternode: - case CustomTxType::ResignMasternode: - case CustomTxType::CreateToken: - case CustomTxType::CreatePoolPair: - // Enable these in the future - case CustomTxType::None: - return res; - default: - break; - } - auto txMessage = customTypeToMessage(txType); - CAccountsHistoryEraser view(mnview, height, txn, erasers); - if ((res = CustomMetadataParse(height, consensus, metadata, txMessage))) { - res = CustomTxRevert(view, coins, tx, height, consensus, txMessage); - - // Track burn fee - if (txType == CustomTxType::CreateToken - || txType == CustomTxType::CreateMasternode - || txType == CustomTxType::Vault) { - erasers.SubFeeBurn(tx.vout[0].scriptPubKey); - } - } - if (!res) { - res.msg = strprintf("%sRevertTx: %s", ToString(txType), res.msg); - return res; - } - return (view.Flush(), res); -} - void PopulateVaultHistoryData(CHistoryWriters* writers, CAccountsHistoryWriter& view, const CCustomTxMessage& txMessage, const CustomTxType txType, const uint32_t height, const uint32_t txn, const uint256& txid) { if (txType == CustomTxType::Vault) { auto obj = boost::get(txMessage); diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index db0645d05ca..ba6fa89d11a 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -387,7 +387,6 @@ bool IsMempooledCustomTxCreate(const CTxMemPool& pool, const uint256& txid); Res RpcInfo(const CTransaction& tx, uint32_t height, CustomTxType& type, UniValue& results); Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage); Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time = 0, uint32_t txn = 0, CHistoryWriters* writers = nullptr); -Res RevertCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint32_t txn, CHistoryErasers& erasers); Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage, uint64_t time, uint32_t txn = 0); ResVal ApplyAnchorRewardTx(CCustomCSView& mnview, const CTransaction& tx, int height, const uint256& prevStakeModifier, const std::vector& metadata, const Consensus::Params& consensusParams); ResVal ApplyAnchorRewardTxPlus(CCustomCSView& mnview, const CTransaction& tx, int height, const std::vector& metadata, const Consensus::Params& consensusParams); diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 8144fc7f11e..16b2c198252 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -1096,14 +1096,14 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { auto count = limit; auto lastHeight = maxBlockHeight; - auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool { + auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, AccountHistoryValue value) -> bool { if (!isMatchOwner(key.owner)) { return false; } std::unique_ptr reverter; if (!noRewards) { - reverter = std::make_unique(view, key.owner, valueLazy.get().diff); + reverter = std::make_unique(view, key.owner, value.diff); } bool accountRecord = true; @@ -1123,8 +1123,6 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { return true; } - const auto & value = valueLazy.get(); - if (CustomTxType::None != txType && value.category != uint8_t(txType)) { return true; } @@ -1171,12 +1169,10 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { return count != 0 || isMine; }; - AccountHistoryKey startKey{account, maxBlockHeight, txn}; - if (!noRewards && !account.empty()) { // revert previous tx to restore account balances to maxBlockHeight paccountHistoryDB->ForEachAccountHistory([&](AccountHistoryKey const & key, AccountHistoryValue const & value) { - if (startKey.blockHeight > key.blockHeight) { + if (maxBlockHeight > key.blockHeight) { return false; } if (!isMatchOwner(key.owner)) { @@ -1184,10 +1180,10 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { } CScopeAccountReverter(view, key.owner, value.diff); return true; - }, {account, std::numeric_limits::max(), std::numeric_limits::max()}); + }, account); } - paccountHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, startKey); + paccountHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, account, maxBlockHeight, txn); if (shouldSearchInWallet) { count = limit; @@ -1375,7 +1371,7 @@ UniValue listburnhistory(const JSONRPCRequest& request) { auto count = limit; - auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool + auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, AccountHistoryValue value) -> bool { if (!isMatchOwner(key.owner)) { return false; @@ -1385,8 +1381,6 @@ UniValue listburnhistory(const JSONRPCRequest& request) { return true; } - const auto & value = valueLazy.get(); - if (txTypeSearch && value.category != uint8_t(txType)) { return true; } @@ -1403,8 +1397,7 @@ UniValue listburnhistory(const JSONRPCRequest& request) { return count != 0; }; - AccountHistoryKey startKey{{}, maxBlockHeight, std::numeric_limits::max()}; - pburnHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, startKey); + pburnHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, {}, maxBlockHeight); UniValue slice(UniValue::VARR); for (auto it = ret.cbegin(); limit != 0 && it != ret.cend(); ++it) { @@ -1517,7 +1510,7 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { auto lastHeight = uint32_t(::ChainActive().Height()); const auto currentHeight = lastHeight; - auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool { + auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, AccountHistoryValue value) -> bool { if (!owner.empty() && owner != key.owner) { return false; } @@ -1526,8 +1519,6 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { return true; } - const auto& value = valueLazy.get(); - std::unique_ptr reverter; if (!noRewards) { reverter = std::make_unique(view, key.owner, value.diff); @@ -1564,8 +1555,7 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { return true; }; - AccountHistoryKey startAccountKey{owner, currentHeight, std::numeric_limits::max()}; - paccountHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, startAccountKey); + paccountHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, owner, currentHeight); if (shouldSearchInWallet) { searchInWallet(pwallet, owner, filter, @@ -1828,8 +1818,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { } } - auto calculateBurnAmounts = [&](AccountHistoryKey const& key, CLazySerialize valueLazy) { - const auto & value = valueLazy.get(); + auto calculateBurnAmounts = [&](AccountHistoryKey const& key, AccountHistoryValue value) { // UTXO burn if (value.category == uint8_t(CustomTxType::None)) { for (auto const & diff : value.diff) { @@ -1876,11 +1865,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { return true; }; - AccountHistoryKey startKey{{}, - std::numeric_limits::max(), - std::numeric_limits::max()}; - - burnView->ForEachAccountHistory(calculateBurnAmounts, startKey); + burnView->ForEachAccountHistory(calculateBurnAmounts); UniValue result(UniValue::VOBJ); result.pushKV("address", ScriptToString(burnAddress)); diff --git a/src/masternodes/tokens.cpp b/src/masternodes/tokens.cpp index c2616277f03..e6ac829640c 100644 --- a/src/masternodes/tokens.cpp +++ b/src/masternodes/tokens.cpp @@ -116,25 +116,6 @@ ResVal CTokensView::CreateToken(const CTokensView::CTokenImpl & token, b return {id, Res::Ok()}; } -Res CTokensView::RevertCreateToken(const uint256 & txid) -{ - auto pair = GetTokenByCreationTx(txid); - if (!pair) { - return Res::Err("Token creation revert error: token with creation tx %s does not exist!\n", txid.ToString()); - } - DCT_ID id = pair->first; - auto lastId = ReadLastDctId(); - if (!lastId || (*lastId) != id) { - return Res::Err("Token creation revert error: revert sequence broken! (txid = %s, id = %s, LastDctId = %s)\n", txid.ToString(), id.ToString(), (lastId ? lastId->ToString() : DCT_ID{0}.ToString())); - } - auto const & token = pair->second; - EraseBy(id); - EraseBy(token.symbol); - EraseBy(token.creationTx); - DecrementLastDctId(); - return Res::Ok(); -} - Res CTokensView::UpdateToken(const CTokenImpl& newToken, bool isPreBayfront, const bool tokenSplitUpdate) { auto pair = GetTokenByCreationTx(newToken.creationTx); @@ -272,19 +253,6 @@ DCT_ID CTokensView::IncrementLastDctId() return result; } -DCT_ID CTokensView::DecrementLastDctId() -{ - auto lastDctId = ReadLastDctId(); - if (lastDctId && *lastDctId >= DCT_ID_START) { - --(lastDctId->v); - } else { - LogPrintf("Critical fault: trying to decrement nonexistent DCT_ID or it is lower than DCT_ID_START\n"); - assert (false); - } - assert (Write(LastDctId::prefix(), *lastDctId)); // it is ok if (DCT_ID_START - 1) will be written - return *lastDctId; -} - std::optional CTokensView::ReadLastDctId() const { DCT_ID lastDctId{DCT_ID_START}; diff --git a/src/masternodes/tokens.h b/src/masternodes/tokens.h index b677fcb4942..a2885b2edb0 100644 --- a/src/masternodes/tokens.h +++ b/src/masternodes/tokens.h @@ -150,7 +150,6 @@ class CTokensView : public virtual CStorageView Res CreateDFIToken(); ResVal CreateToken(CTokenImpl const & token, bool isPreBayfront = false); - Res RevertCreateToken(uint256 const & txid); Res UpdateToken(CTokenImpl const & newToken, bool isPreBayfront = false, const bool tokenSplitUpdatea = false); Res BayfrontFlagsCleanup(); @@ -166,7 +165,6 @@ class CTokensView : public virtual CStorageView private: // have to incapsulate "last token id" related methods here DCT_ID IncrementLastDctId(); - DCT_ID DecrementLastDctId(); std::optional ReadLastDctId() const; }; diff --git a/src/test/storage_tests.cpp b/src/test/storage_tests.cpp index 1ac2fbc7b08..6eba25eb673 100644 --- a/src/test/storage_tests.cpp +++ b/src/test/storage_tests.cpp @@ -235,28 +235,6 @@ BOOST_AUTO_TEST_CASE(tokens) BOOST_REQUIRE(pair->second.creationTx == uint256S("0x2222")); } - // revert create token - BOOST_REQUIRE(pcustomcsview->RevertCreateToken(uint256S("0xffff")) == false); - BOOST_REQUIRE(pcustomcsview->RevertCreateToken(uint256S("0x1111")) == false); - BOOST_REQUIRE(pcustomcsview->RevertCreateToken(uint256S("0x2222"))); - BOOST_REQUIRE(GetTokensCount() == 2); - { // search by id - auto token = pcustomcsview->GetToken(DCT_ID{128}); - BOOST_REQUIRE(token); - BOOST_REQUIRE(token->symbol == "DCT1"); - } - - // create again, with same tx and dctid - token1.symbol = "DCT3"; - token1.creationTx = uint256S("0x2222"); // SAME! - BOOST_REQUIRE(pcustomcsview->CreateToken(token1, false).ok); - BOOST_REQUIRE(GetTokensCount() == 3); - { // search by id - auto token = pcustomcsview->GetToken(DCT_ID{129}); - BOOST_REQUIRE(token); - BOOST_REQUIRE(token->symbol == "DCT3"); - } - { // search by id auto token = pcustomcsview->GetToken(DCT_ID{129}); BOOST_REQUIRE(token); diff --git a/src/validation.cpp b/src/validation.cpp index a166b803fb4..eae29723924 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1715,9 +1715,13 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI // special case: possible undo (first) of custom 'complex changes' for the whole block (expired orders and/or prices) mnview.OnUndoTx(uint256(), (uint32_t) pindex->nHeight); // undo for "zero hash" + pburnHistoryDB->EraseAccountHistoryHeight(pindex->nHeight); + if (paccountHistoryDB) { + paccountHistoryDB->EraseAccountHistoryHeight(pindex->nHeight); + } + if (pindex->nHeight >= Params().GetConsensus().FortCanningHeight) { // erase auction fee history - pburnHistoryDB->EraseAccountHistory({Params().GetConsensus().burnAddress, uint32_t(pindex->nHeight), ~0u}); if (paccountHistoryDB) { paccountHistoryDB->EraseAuctionHistoryHeight(pindex->nHeight); } @@ -1832,11 +1836,6 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI // process transactions revert for masternodes mnview.OnUndoTx(tx.GetHash(), (uint32_t) pindex->nHeight); - CHistoryErasers erasers{paccountHistoryDB.get(), pburnHistoryDB.get(), pvaultHistoryDB.get()}; - auto res = RevertCustomTx(mnview, view, tx, Params().GetConsensus(), (uint32_t) pindex->nHeight, i, erasers); - if (!res) { - LogPrintf("%s\n", res.msg); - } } // one time downgrade to revert CInterestRateV2 structure @@ -1853,7 +1852,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI // Make sure to initialize lastTxOut, otherwise it never finds the block and // ends up looping through uninitialized garbage value. uint32_t lastTxOut = 0; - auto shouldContinueToNextAccountHistory = [&lastTxOut, block](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool + auto shouldContinueToNextAccountHistory = [&lastTxOut, block](AccountHistoryKey const & key, AccountHistoryValue const &) -> bool { if (key.owner != Params().GetConsensus().burnAddress) { return false; @@ -1869,7 +1868,9 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI }; AccountHistoryKey startKey({Params().GetConsensus().burnAddress, static_cast(Params().GetConsensus().EunosHeight), std::numeric_limits::max()}); - pburnHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, startKey); + pburnHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, + Params().GetConsensus().burnAddress, + Params().GetConsensus().EunosHeight); for (uint32_t i = block.vtx.size(); i <= lastTxOut; ++i) { pburnHistoryDB->EraseAccountHistory({Params().GetConsensus().burnAddress, static_cast(Params().GetConsensus().EunosHeight), i}); diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 73ab72d6f23..6e0d8c3dc17 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -839,6 +839,7 @@ def check_future_swap_refund(self): # Revert chain to before address funding self.nodes[0].invalidateblock(self.nodes[0].getblockhash(revert_block)) self.nodes[0].clearmempool() + self.nodes[0].generate(1) # Move forward again without split self.nodes[0].generate(future_block - self.nodes[0].getblockcount()) From 0b69a93931c74ab4237f3f6283f68b5c40027b31 Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Thu, 9 Jun 2022 07:36:31 +0100 Subject: [PATCH 3/8] Extend tests to check rollback of tokens and MNs --- test/functional/feature_token_fork.py | 50 +++++++++++++++++---------- test/functional/rpc_mn_basic.py | 25 +++++++++++++- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/test/functional/feature_token_fork.py b/test/functional/feature_token_fork.py index 5ccb848bc67..171f857835a 100755 --- a/test/functional/feature_token_fork.py +++ b/test/functional/feature_token_fork.py @@ -11,21 +11,18 @@ from test_framework.test_framework import DefiTestFramework from test_framework.authproxy import JSONRPCException -from test_framework.util import assert_equal +from test_framework.util import assert_equal, assert_raises_rpc_error class TokensForkTest (DefiTestFramework): def set_test_params(self): - self.num_nodes = 2 + self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-txnotokens=0', '-amkheight=120'], ['-txnotokens=0', '-amkheight=120']] + self.extra_args = [['-txnotokens=0', '-amkheight=120']] def run_test(self): assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI - self.nodes[0].generate(100) - self.sync_blocks() - - self.nodes[0].generate(2) # for 2 matured utxos + self.nodes[0].generate(102) # Try to create token before AMK fork height but will fail: #======================== @@ -63,7 +60,6 @@ def run_test(self): # assert("Token tx before AMK" in errorString) self.nodes[0].generate(17) - self.sync_blocks() # Now at AMK height 120 # Now create again, it should pass @@ -71,14 +67,39 @@ def run_test(self): "symbol": "GOLD", "name": "shiny gold", "collateralAddress": collateralGold - }, []) - self.nodes[0].createtoken({ + }) + self.nodes[0].generate(1) + + txid = self.nodes[0].createtoken({ "symbol": "SILVER", "name": "just silver", "collateralAddress": collateralSilver - }, []) + }) + self.nodes[0].generate(1) + # Get token ID + id_silver = list(self.nodes[0].gettoken('SILVER#129').keys())[0] + + # Check rollback of token + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + self.nodes[0].clearmempool() + + # Make sure token not found + assert_raises_rpc_error(-5, "Token not found", self.nodes[0].gettoken, txid) + assert_raises_rpc_error(-5, "Token not found", self.nodes[0].gettoken, id_silver) + assert_raises_rpc_error(-5, "Token not found", self.nodes[0].gettoken, 'SILVER#129') + + # Create token again + self.nodes[0].createtoken({ + "symbol": "SILVER", + "name": "just silver", + "collateralAddress": collateralSilver + }) self.nodes[0].generate(1) + + # Check the same ID was provided, not an increment of the last one + assert_equal(id_silver, list(self.nodes[0].gettoken('SILVER#129').keys())[0]) + # After fork, create should pass, so now only have 3 kind of tokens tokens = self.nodes[0].listtokens() assert_equal(len(tokens), 3) @@ -93,8 +114,6 @@ def run_test(self): symbolGold = "GOLD#" + idGold symbolSilver = "SILVER#" + idSilver - self.sync_blocks() - # MINT: #======================== # Funding auth addresses @@ -104,12 +123,9 @@ def run_test(self): self.nodes[0].sendmany("", { collateralGold : 1, collateralSilver : 1 }) self.nodes[0].generate(1) - # print(self.nodes[0].listunspent()) - self.nodes[0].minttokens("300@" + symbolGold, []) self.nodes[0].minttokens("3000@" + symbolSilver, []) self.nodes[0].generate(1) - self.sync_blocks() # synthetically check for minting. restart w/o reindex and amk (so, token exists, but minting should fail) self.stop_node(0) @@ -121,7 +137,5 @@ def run_test(self): errorString = e.error['message'] assert("before AMK height" in errorString) - - if __name__ == '__main__': TokensForkTest ().main () diff --git a/test/functional/rpc_mn_basic.py b/test/functional/rpc_mn_basic.py index c0cdd842292..6a13598e1f4 100755 --- a/test/functional/rpc_mn_basic.py +++ b/test/functional/rpc_mn_basic.py @@ -183,6 +183,17 @@ def run_test(self): mnAddress = self.nodes[0].getnewaddress("", "legacy") mnTx = self.nodes[0].createmasternode(mnAddress) self.nodes[0].generate(1) + + # Rollback masternode creation + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + self.nodes[0].clearmempool() + + # Make sure MN not found + assert_raises_rpc_error(-5, "Masternode not found", self.nodes[0].getmasternode, mnTx) + + # Create masternode again + mnTx = self.nodes[0].createmasternode(mnAddress) + self.nodes[0].generate(1) assert_equal(self.nodes[0].listmasternodes({}, False)[mnTx], "PRE_ENABLED") # Try and resign masternode while still in PRE_ENABLED @@ -216,7 +227,19 @@ def run_test(self): self.nodes[0].resignmasternode(mnTx) self.nodes[0].generate(1) assert_equal(self.nodes[0].listmasternodes()[mnTx]['state'], "PRE_RESIGNED") - self.nodes[0].generate(39) + + # Test rollback of MN resign + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + self.nodes[0].clearmempool() + + # Make sure rollback succesful + result = self.nodes[0].getmasternode(mnTx)[mnTx] + assert_equal(result['resignHeight'], -1) + assert_equal(result['resignTx'], '0000000000000000000000000000000000000000000000000000000000000000') + + # Resign again + self.nodes[0].resignmasternode(mnTx) + self.nodes[0].generate(40) assert_equal(self.nodes[0].listmasternodes()[mnTx]['state'], "PRE_RESIGNED") self.nodes[0].generate(1) assert_equal(self.nodes[0].listmasternodes()[mnTx]['state'], "RESIGNED") From 956a19c95620d40c3b9293e273ed6f0d57b09c4c Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Thu, 9 Jun 2022 09:15:25 +0100 Subject: [PATCH 4/8] Track pool split. Do not track on failed split. --- src/masternodes/accountshistory.h | 3 +-- src/validation.cpp | 26 +++++++++++++++++++------- test/functional/feature_token_split.py | 16 +++++++++++++--- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/masternodes/accountshistory.h b/src/masternodes/accountshistory.h index 6497d1db5dd..a9c7cf2d702 100644 --- a/src/masternodes/accountshistory.h +++ b/src/masternodes/accountshistory.h @@ -76,14 +76,13 @@ class CAccountHistoryStorage : public CAccountsHistoryView , public CAuctionHistoryView { public: - CAccountHistoryStorage(CAccountHistoryStorage& accountHistory) : CStorageView(&accountHistory.DB()) {} + CAccountHistoryStorage(CAccountHistoryStorage& accountHistory) : CStorageView(new CFlushableStorageKV(accountHistory.DB())) {} CAccountHistoryStorage(const fs::path& dbName, std::size_t cacheSize, bool fMemory = false, bool fWipe = false); }; class CBurnHistoryStorage : public CAccountsHistoryView { public: - CBurnHistoryStorage(CBurnHistoryStorage& burnHistory) : CStorageView(&burnHistory.DB()) {} CBurnHistoryStorage(const fs::path& dbName, std::size_t cacheSize, bool fMemory = false, bool fWipe = false); }; diff --git a/src/validation.cpp b/src/validation.cpp index eae29723924..e1c143ebe3f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4042,10 +4042,14 @@ static Res PoolSplits(CCustomCSView& view, CAmount& totalBalance, ATTRIBUTES& at for (auto& [owner, amount] : balancesToMigrate) { if (owner != Params().GetConsensus().burnAddress) { - res = view.SubBalance(owner, CTokenAmount{oldPoolId, amount}); + CHistoryWriters subWriters{view.GetAccountHistoryStore(), nullptr, nullptr}; + CAccountsHistoryWriter subView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &subWriters); + + res = subView.SubBalance(owner, CTokenAmount{oldPoolId, amount}); if (!res.ok) { throw std::runtime_error(strprintf("SubBalance failed: %s", res.msg)); } + subView.Flush(); } if (oldPoolPair->totalLiquidity < CPoolPair::MINIMUM_LIQUIDITY) { @@ -4075,9 +4079,13 @@ static Res PoolSplits(CCustomCSView& view, CAmount& totalBalance, ATTRIBUTES& at totalBalance += amountB; } + CHistoryWriters addWriters{view.GetAccountHistoryStore(), nullptr, nullptr}; + CAccountsHistoryWriter addView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &addWriters); + auto refundBalances = [&, owner = owner]() { - view.AddBalance(owner, {newPoolPair.idTokenA, amountA}); - view.AddBalance(owner, {newPoolPair.idTokenB, amountB}); + addView.AddBalance(owner, {newPoolPair.idTokenA, amountA}); + addView.AddBalance(owner, {newPoolPair.idTokenB, amountB}); + addView.Flush(); }; if (amountA <= 0 || amountB <= 0 || owner == Params().GetConsensus().burnAddress) { @@ -4118,11 +4126,13 @@ static Res PoolSplits(CCustomCSView& view, CAmount& totalBalance, ATTRIBUTES& at continue; } - res = view.AddBalance(owner, {newPoolId, liquidity}); + res = addView.AddBalance(owner, {newPoolId, liquidity}); if (!res) { + addView.Discard(); refundBalances(); continue; } + addView.Flush(); auto oldPoolLogStr = CTokenAmount{oldPoolId, amount}.ToString(); auto newPoolLogStr = CTokenAmount{newPoolId, liquidity}.ToString(); @@ -4410,7 +4420,8 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } auto view{cache}; - view.SetAccountHistoryStore(paccountHistoryDB.get()); + auto historyCache{*paccountHistoryDB}; + view.SetAccountHistoryStore(&historyCache); // Refund affected future swaps auto res = attributes->RefundFuturesContracts(view, std::numeric_limits::max(), id); @@ -4534,7 +4545,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin for (const auto& [owner, balances] : balanceUpdates) { - CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CHistoryWriters subWriters{&historyCache, nullptr, nullptr}; CAccountsHistoryWriter subView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &subWriters); res = subView.SubBalance(owner, balances.second); @@ -4543,7 +4554,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } subView.Flush(); - CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CHistoryWriters addWriters{&historyCache, nullptr, nullptr}; CAccountsHistoryWriter addView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &addWriters); res = addView.AddBalance(owner, balances.first); @@ -4589,6 +4600,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } view.SetVariable(*attributes); view.Flush(); + historyCache.Flush(); LogPrintf("Token split completed: (id: %d, mul: %d, time: %dms)\n", id, multiplier, GetTimeMillis() - time); } } diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 6e0d8c3dc17..58ae49a7a54 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -540,9 +540,9 @@ def token_split(self): new_amount = Decimal(account[0].split('@')[0]) assert_equal(new_amount, amount * 2) history = self.nodes[0].listaccounthistory(address, {'txtype': 'TokenSplit'}) - #assert_equal(len(history), 2) - #assert_equal(history[0]['amounts'][0], f'{-amount:.8f}' + f'@{self.symbolTSLA}/v1') - #assert_equal(history[1]['amounts'][0], account[0]) + assert_equal(len(history), 2) + assert_equal(history[0]['amounts'][0], f'{-amount:.8f}' + f'@{self.symbolTSLA}/v1') + assert_equal(history[1]['amounts'][0], account[0]) # Token split self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{str(self.nodes[0].getblockcount() + 2)}':f'{self.idTSLA}/-3'}}) @@ -602,6 +602,16 @@ def pool_split(self): self.idGOOGL = list(self.nodes[0].gettoken(self.symbolGOOGL).keys())[0] self.idGD = list(self.nodes[0].gettoken(self.symbolGD).keys())[0] + # Check token split records in account history + balances = self.nodes[0].gettokenbalances({'start': int(self.idGD), 'including_start': True, 'limit': 1}, False, True) + result = self.nodes[0].listaccounthistory(self.address, {"maxBlockHeight":self.nodes[0].getblockcount(), 'txtype': 'TokenSplit', 'depth':0}) + assert_equal(result[0]['owner'], self.address) + assert_equal(result[0]['type'], 'TokenSplit') + assert_equal(result[0]['amounts'], [f'-{self.poolGDTotal - Decimal("0.00001")}@{self.symbolGD}/v1']) + assert_equal(result[1]['owner'], self.address) + assert_equal(result[1]['type'], 'TokenSplit') + assert_equal(result[1]['amounts'], balances) + # Token split self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{str(self.nodes[0].getblockcount() + 2)}':f'{self.idGOOGL}/-3'}}) self.nodes[0].generate(2) From fed42b362bda3f671d9c4fb4cdce9bba98951b0d Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Thu, 9 Jun 2022 10:56:28 +0100 Subject: [PATCH 5/8] lint: circular deps --- test/lint/lint-circular-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 68111416af6..d9a1fb0f690 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -37,8 +37,8 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "masternodes/anchors -> validation -> masternodes/anchors" "masternodes/govvariables/attributes -> masternodes/gv -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> masternodes/masternodes -> masternodes/govvariables/attributes" - "masternodes/govvariables/attributes -> masternodes/masternodes -> validation -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> masternodes/mn_checks -> masternodes/govvariables/attributes" + "masternodes/govvariables/attributes -> validation -> masternodes/govvariables/attributes" "masternodes/govvariables/icx_takerfee_per_btc -> masternodes/gv -> masternodes/govvariables/icx_takerfee_per_btc" "masternodes/govvariables/loan_daily_reward -> masternodes/gv -> masternodes/govvariables/loan_daily_reward" "masternodes/govvariables/loan_daily_reward -> masternodes/masternodes -> validation -> masternodes/govvariables/loan_daily_reward" From 64cb62089817d4ed629f259588eb13d2b54502ff Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Fri, 10 Jun 2022 11:19:04 +0100 Subject: [PATCH 6/8] Track vault during token split --- src/masternodes/masternodes.cpp | 44 +++++++++++++++++++++++++ src/masternodes/masternodes.h | 35 +++++++------------- src/masternodes/vaulthistory.h | 1 + src/validation.cpp | 27 ++++++++++++--- test/functional/feature_token_split.py | 26 ++++++++++++--- test/lint/lint-circular-dependencies.sh | 3 +- 6 files changed, 103 insertions(+), 33 deletions(-) diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index cfa6ab2f28a..1b9f1defdb6 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -3,9 +3,11 @@ // file LICENSE or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include +#include #include #include @@ -703,6 +705,26 @@ std::vector CAnchorConfirmsView::GetAnchorConfirmData() /* * CCustomCSView */ +CCustomCSView::CCustomCSView() +{ + CheckPrefixes(); +} + +CCustomCSView::~CCustomCSView() = default; + +CCustomCSView::CCustomCSView(CStorageKV & st) + : CStorageView(new CFlushableStorageKV(st)) +{ + CheckPrefixes(); +} + +// cache-upon-a-cache (not a copy!) constructor +CCustomCSView::CCustomCSView(CCustomCSView & other) + : CStorageView(new CFlushableStorageKV(other.DB())) +{ + CheckPrefixes(); +} + int CCustomCSView::GetDbVersion() const { int version; @@ -1174,3 +1196,25 @@ std::optional CCustomCSView::GetCollater return {}; } + +CAccountHistoryStorage* CCustomCSView::GetAccountHistoryStore() { + return accHistoryStore.get(); +} + +CVaultHistoryStorage* CCustomCSView::GetVaultHistoryStore() { + return vauHistoryStore.get(); +} + +void CCustomCSView::SetAccountHistoryStore() { + if (paccountHistoryDB) { + accHistoryStore.reset(); + accHistoryStore = std::make_unique(*paccountHistoryDB); + } +} + +void CCustomCSView::SetVaultHistoryStore() { + if (pvaultHistoryDB) { + vauHistoryStore.reset(); + vauHistoryStore = std::make_unique(*pvaultHistoryDB); + } +} diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index bbdf43f99f8..f92beb73b65 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -32,6 +32,7 @@ class CAccountHistoryStorage; class CBlockIndex; class CTransaction; +class CVaultHistoryStorage; // Works instead of constants cause 'regtest' differs (don't want to overcharge chainparams) int GetMnActivationDelay(int height); @@ -385,28 +386,19 @@ class CCustomCSView Res PopulateLoansData(CCollateralLoans& result, CVaultId const& vaultId, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); Res PopulateCollateralData(CCollateralLoans& result, CVaultId const& vaultId, CBalances const& collaterals, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); - CAccountHistoryStorage* accHistoryStore{}; + std::unique_ptr accHistoryStore; + std::unique_ptr vauHistoryStore; public: // Increase version when underlaying tables are changed static constexpr const int DbVersion = 1; - CCustomCSView() - { - CheckPrefixes(); - } - - CCustomCSView(CStorageKV & st) - : CStorageView(new CFlushableStorageKV(st)) - { - CheckPrefixes(); - } + CCustomCSView(); + explicit CCustomCSView(CStorageKV & st); // cache-upon-a-cache (not a copy!) constructor - CCustomCSView(CCustomCSView & other) - : CStorageView(new CFlushableStorageKV(other.DB())) - { - CheckPrefixes(); - } + CCustomCSView(CCustomCSView & other); + + ~CCustomCSView(); // cause depends on current mns: CTeamView::CTeam CalcNextTeam(int height, uint256 const & stakeModifier); @@ -447,13 +439,10 @@ class CCustomCSView return static_cast(DB()); } - virtual CAccountHistoryStorage* GetAccountHistoryStore() { - return accHistoryStore; - } - - void SetAccountHistoryStore(CAccountHistoryStorage* store) { - accHistoryStore = store; - } + virtual CAccountHistoryStorage* GetAccountHistoryStore(); + CVaultHistoryStorage* GetVaultHistoryStore(); + void SetAccountHistoryStore(); + void SetVaultHistoryStore(); struct DbVersion { static constexpr uint8_t prefix() { return 'D'; } }; }; diff --git a/src/masternodes/vaulthistory.h b/src/masternodes/vaulthistory.h index 07751e5f755..e6afa58f210 100644 --- a/src/masternodes/vaulthistory.h +++ b/src/masternodes/vaulthistory.h @@ -183,6 +183,7 @@ class CVaultHistoryView : public virtual CStorageView class CVaultHistoryStorage : public CVaultHistoryView { public: + CVaultHistoryStorage(CVaultHistoryStorage& vaultHistory) : CStorageView(new CFlushableStorageKV(vaultHistory.DB())) {} CVaultHistoryStorage(const fs::path& dbName, std::size_t cacheSize, bool fMemory = false, bool fWipe = false); }; diff --git a/src/validation.cpp b/src/validation.cpp index e1c143ebe3f..0c215b9fa80 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4272,6 +4272,18 @@ static Res VaultSplits(CCustomCSView& view, ATTRIBUTES& attributes, const DCT_ID if (!res) { return res; } + + if (view.GetVaultHistoryStore()) { + if (const auto vault = view.GetVault(vaultId)) { + VaultHistoryKey subKey{static_cast(height), vaultId, GetNextAccPosition(), vault->ownerAddress}; + VaultHistoryValue subValue{uint256{}, static_cast(CustomTxType::TokenSplit), {{oldTokenId, -amount}}}; + view.GetVaultHistoryStore()->WriteVaultHistory(subKey, subValue); + + VaultHistoryKey addKey{static_cast(height), vaultId, GetNextAccPosition(), vault->ownerAddress}; + VaultHistoryValue addValue{uint256{}, static_cast(CustomTxType::TokenSplit), {{newTokenId, newAmount}}}; + view.GetVaultHistoryStore()->WriteVaultHistory(addKey, addValue); + } + } } const auto loanToken = view.GetLoanTokenByID(newTokenId); @@ -4420,8 +4432,8 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } auto view{cache}; - auto historyCache{*paccountHistoryDB}; - view.SetAccountHistoryStore(&historyCache); + view.SetAccountHistoryStore(); + view.SetVaultHistoryStore(); // Refund affected future swaps auto res = attributes->RefundFuturesContracts(view, std::numeric_limits::max(), id); @@ -4545,7 +4557,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin for (const auto& [owner, balances] : balanceUpdates) { - CHistoryWriters subWriters{&historyCache, nullptr, nullptr}; + CHistoryWriters subWriters{view.GetAccountHistoryStore(), nullptr, nullptr}; CAccountsHistoryWriter subView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &subWriters); res = subView.SubBalance(owner, balances.second); @@ -4554,7 +4566,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } subView.Flush(); - CHistoryWriters addWriters{&historyCache, nullptr, nullptr}; + CHistoryWriters addWriters{view.GetAccountHistoryStore(), nullptr, nullptr}; CAccountsHistoryWriter addView(view, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::TokenSplit), &addWriters); res = addView.AddBalance(owner, balances.first); @@ -4600,7 +4612,12 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } view.SetVariable(*attributes); view.Flush(); - historyCache.Flush(); + if (auto accountHistory = view.GetAccountHistoryStore()) { + accountHistory->Flush(); + } + if (auto vaultHistory = view.GetVaultHistoryStore()) { + vaultHistory->Flush(); + } LogPrintf("Token split completed: (id: %d, mul: %d, time: %dms)\n", id, multiplier, GetTimeMillis() - time); } } diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 58ae49a7a54..e3e96b0bb3f 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -21,7 +21,7 @@ def set_test_params(self): self.setup_clean_chain = True self.fort_canning_crunch = 600 self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', f'-fortcanningcrunchheight={self.fort_canning_crunch}', '-subsidytest=1']] + ['-vaultindex=1', '-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', f'-fortcanningcrunchheight={self.fort_canning_crunch}', '-subsidytest=1']] def run_test(self): self.setup_test_tokens() @@ -295,26 +295,35 @@ def setup_test_vaults(self): self.nodes[0].utxostoaccount({self.address: f'30000@{self.symbolDFI}'}) self.nodes[0].generate(1) - for _ in range(100): + for vault in range(100): # Create vault vault_id = self.nodes[0].createvault(self.address, '') self.nodes[0].generate(1) + # Store single vault to check later + if vault == 0: + self.vault_id = vault_id + self.vault_loan = 0 + # Take 1 to 3 loans for _ in range(1, 4): # Deposit random collateral collateral = round(random.uniform(1, 100), 8) loan = truncate(str(collateral / 3), 8) - self.nodes[0].deposittovault(vault_id, self.address, f'{str(collateral)}@{self.symbolDFI}') + self.nodes[0].deposittovault(vault_id, self.address, f'{collateral}@{self.symbolDFI}') self.nodes[0].generate(1) # Take loan self.nodes[0].takeloan({ 'vaultId': vault_id, - 'amounts': f"{str(loan)}@{self.symbolNVDA}" + 'amounts': f"{loan}@{self.symbolNVDA}" }) self.nodes[0].generate(1) + # Store loan amounts + if vault == 0: + self.vault_loan += Decimal(loan) + def check_token_split(self, token_id, token_symbol, token_suffix, multiplier, minted, loan, collateral): # Check old token @@ -723,6 +732,15 @@ def vault_split(self): # Multiplier 2 self.execute_vault_split(self.idNVDA, self.symbolNVDA, 2, '/v1') + # Check split history + result = self.nodes[0].listvaulthistory(self.vault_id, {'maxBlockHeight': self.nodes[0].getblockcount(), 'depth':1}) + assert_equal(result[0]['address'], self.address) + assert_equal(result[0]['type'], 'TokenSplit') + assert_equal(result[0]['amounts'], [f'-{self.vault_loan}@{self.symbolNVDA}/v1']) + assert_equal(result[1]['address'], self.address) + assert_equal(result[1]['type'], 'TokenSplit') + assert_equal(result[1]['amounts'], [f'{self.vault_loan * 2}@{self.symbolNVDA}']) + # Swap old for new values self.idNVDA = list(self.nodes[0].gettoken(self.symbolNVDA).keys())[0] diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index d9a1fb0f690..1dd30532ce5 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -28,6 +28,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "consensus/tx_verify -> masternodes/masternodes -> validation -> consensus/tx_verify" "consensus/tx_verify -> masternodes/mn_checks -> txmempool -> consensus/tx_verify" "index/txindex -> validation -> index/txindex" + "masternodes/accountshistory -> masternodes/masternodes -> masternodes/accountshistory" "masternodes/accountshistory -> masternodes/masternodes -> masternodes/mn_checks -> masternodes/accountshistory" "masternodes/accountshistory -> masternodes/masternodes -> validation -> masternodes/accountshistory" "masternodes/anchors -> masternodes/masternodes -> masternodes/anchors" @@ -54,8 +55,8 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "masternodes/govvariables/oracle_deviation -> masternodes/gv -> masternodes/govvariables/oracle_deviation" "masternodes/loan -> masternodes/masternodes -> masternodes/loan" "masternodes/masternodes -> masternodes/mn_checks -> masternodes/masternodes" - "masternodes/masternodes -> masternodes/mn_checks -> masternodes/vaulthistory -> masternodes/masternodes" "masternodes/masternodes -> masternodes/oracles -> masternodes/masternodes" + "masternodes/masternodes -> masternodes/vaulthistory -> masternodes/masternodes" "masternodes/masternodes -> net_processing -> masternodes/masternodes" "masternodes/masternodes -> validation -> masternodes/masternodes" "masternodes/masternodes -> wallet/wallet -> masternodes/masternodes" From 540a46f53afe7a85e2601f645c1d88d7643d77ef Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Fri, 17 Jun 2022 08:02:33 +0100 Subject: [PATCH 7/8] Update burn test --- test/functional/feature_burn_address.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/functional/feature_burn_address.py b/test/functional/feature_burn_address.py index bb2e45b48db..3d68f1ebd34 100755 --- a/test/functional/feature_burn_address.py +++ b/test/functional/feature_burn_address.py @@ -214,14 +214,25 @@ def run_test(self): assert_equal(len(result), 1) assert_equal(result[0]['type'], 'CreateToken') - # Revert all TXs and check that burn history is empty + # Get current block + current_block = self.nodes[0].getblockcount() + + # Revert all TXs self.nodes[0].invalidateblock(self.nodes[0].getblockhash(101)) + self.nodes[0].clearmempool() + + # Move the chain forward to the previous height + self.nodes[0].generate(current_block - self.nodes[0].getblockcount()) + + # Check that burn history is empty result = self.nodes[0].listburnhistory() assert_equal(len(result), 0) + # Check burn info reset result = self.nodes[0].getburninfo() - assert_equal(result['amount'], Decimal('0.0')) - assert_equal(len(result['tokens']), 0) + assert_equal(result['amount'], Decimal('0')) + assert_equal(result['feeburn'], Decimal('0')) + assert_equal(result['tokens'], []) if __name__ == '__main__': BurnAddressTest().main() From cc4389021e2370d926a7a3eda9ddbb58f93e901e Mon Sep 17 00:00:00 2001 From: Peter Bushnell Date: Wed, 22 Jun 2022 16:18:55 +0100 Subject: [PATCH 8/8] Update circular deps --- test/lint/lint-circular-dependencies.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 04259474f6c..0bb6a07c3a3 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -39,7 +39,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "masternodes/govvariables/attributes -> masternodes/gv -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> masternodes/masternodes -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> masternodes/masternodes -> masternodes/poolpairs -> masternodes/govvariables/attributes" - "masternodes/govvariables/attributes -> masternodes/masternodes -> validation -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> masternodes/mn_checks -> masternodes/govvariables/attributes" "masternodes/govvariables/attributes -> validation -> masternodes/govvariables/attributes" "masternodes/govvariables/icx_takerfee_per_btc -> masternodes/gv -> masternodes/govvariables/icx_takerfee_per_btc"