Skip to content

Commit cab298a

Browse files
PastaPastaPastaknst
authored andcommitted
Merge dashpay#6658: perf: cache block data for CreditPool for asset unlock limits calculation
72cfb93 feat: bail out if GetTxPayload failed (Konstantin Akimov) 095b2d8 refactor: use if statement feature (Konstantin Akimov) 9c5e451 fix: typo in error message for GetDataFromUnlockTx (Konstantin Akimov) 52ae3aa refactor: use CreditPoolPeriodBlocks for block_data_cache (Konstantin Akimov) 34e06ec fmt: apply clang-format suggestions (Konstantin Akimov) 1706270 perf: cache block data for credit pool for calculation asset unlock limits (Konstantin Akimov) e3d3783 refactor: combine GetDataFromUnlockTxes and GetBlockForCreditPool to GetCreditDataFromBlock (Konstantin Akimov) afd7f84 perf: use GetAncestor() to jump blocks back for CreditPool (Konstantin Akimov) f7749db refactor: use helper GetDataFromUnlockTxes to get credit pool amount (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented For each new block CreditPool read blocks twice from disk: for last connected block and for distant block (576 block ago) which is used to calculate sliding window limit for withdrawals. ## What was done? Added mini cache with block data (locked amount, indexes) to avoid reading block with ReadBlockFromDisk. [It is possible](https://github.com/knst/dash/tree/perf-cp-cache-2) to avoid 2nd block reading too (for tip), but benchmark doesn't show clear improvement. It will go to the separate PR later. ## How Has This Been Tested? develop: <img width="613" alt="image" src="https://github.com/user-attachments/assets/b29382cf-2de3-4223-a85e-2623982ff86a" /> ``` 2025-05-04T18:36:29Z [bench] - ProcessSpecialTxsInBlock: 52.94ms [84.66s (14.46ms/blk)] 2025-05-04T18:36:29Z [bench] - CheckCreditPoolDiffForBlock: 0.21ms [2.11s (0.36ms/blk)] 2025-05-04T18:36:29Z [bench] - Connect total: 54.40ms [94.48s (16.14ms/blk)] ``` RP: <img width="613" alt="image" src="https://github.com/user-attachments/assets/40166b85-cc37-4bf3-a618-2931c3d0fdca" /> ``` 2025-05-05T10:11:47Z [bench] - ProcessSpecialTxsInBlock: 52.62ms [83.01s (14.18ms/blk)] 2025-05-05T10:11:47Z [bench] - CheckCreditPoolDiffForBlock: 0.21ms [2.09s (0.36ms/blk)] 2025-05-05T10:11:47Z [bench] - Connect total: 53.46ms [90.66s (15.49ms/blk)] ``` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: PastaPastaPasta: utACK 72cfb93 UdjinM6: utACK 72cfb93 Tree-SHA512: fd67770d42ed5c8b8d9a9fd542ae632d7ef425e8530902fa033310bc0da18b824e1c35b39c73daa99a480d83aef13ff709b19794c7aa7aa9f2f09d88f6b9ca6c
1 parent c596267 commit cab298a

File tree

1 file changed

+56
-53
lines changed

1 file changed

+56
-53
lines changed

src/evo/creditpool.cpp

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#include <deploymentstatus.h>
1616
#include <logging.h>
1717
#include <node/blockstorage.h>
18-
#include <util/irange.h>
1918
#include <validation.h>
2019

2120
#include <algorithm>
@@ -47,29 +46,63 @@ static bool GetDataFromUnlockTx(const CTransaction& tx, CAmount& toUnlock, uint6
4746
}
4847

4948
namespace {
50-
struct UnlockDataPerBlock {
51-
CAmount unlocked{0};
52-
std::unordered_set<uint64_t> indexes;
53-
};
49+
struct CreditPoolDataPerBlock {
50+
CAmount credit_pool{0};
51+
CAmount unlocked{0};
52+
std::unordered_set<uint64_t> indexes;
53+
};
5454
} // anonymous namespace
5555

5656
// it throws exception if anything went wrong
57-
static UnlockDataPerBlock GetDataFromUnlockTxes(const std::vector<CTransactionRef>& vtx)
57+
static std::optional<CreditPoolDataPerBlock> GetCreditDataFromBlock(const gsl::not_null<const CBlockIndex*> block_index,
58+
const Consensus::Params& consensusParams)
5859
{
59-
UnlockDataPerBlock blockData;
60+
// There's no CbTx before DIP0003 activation
61+
if (!DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
62+
return std::nullopt;
63+
}
64+
65+
CreditPoolDataPerBlock blockData;
66+
67+
static Mutex cache_mutex;
68+
static unordered_lru_cache<uint256, CreditPoolDataPerBlock, StaticSaltedHasher> block_data_cache GUARDED_BY(
69+
cache_mutex){static_cast<size_t>(Params().CreditPoolPeriodBlocks()) * 2};
70+
if (LOCK(cache_mutex); block_data_cache.get(block_index->GetBlockHash(), blockData)) {
71+
return blockData;
72+
}
73+
74+
CBlock block;
75+
if (!ReadBlockFromDisk(block, block_index, consensusParams)) {
76+
throw std::runtime_error("failed-getcbforblock-read");
77+
}
6078

61-
for (CTransactionRef tx : vtx) {
79+
if (block.vtx.empty() || block.vtx[0]->vExtraPayload.empty() || !block.vtx[0]->IsSpecialTxVersion()) {
80+
LogPrintf("%s: ERROR: empty CbTx for CreditPool at height=%d\n", __func__, block_index->nHeight);
81+
return std::nullopt;
82+
}
83+
84+
85+
if (const auto opt_cbTx = GetTxPayload<CCbTx>(block.vtx[0]->vExtraPayload); opt_cbTx) {
86+
blockData.credit_pool = opt_cbTx->creditPoolBalance;
87+
} else {
88+
LogPrintf("%s: WARNING: No valid CbTx at height=%d\n", __func__, block_index->nHeight);
89+
return std::nullopt;
90+
}
91+
for (CTransactionRef tx : block.vtx) {
6292
if (!tx->IsSpecialTxVersion() || tx->nType != TRANSACTION_ASSET_UNLOCK) continue;
6393

6494
CAmount unlocked{0};
6595
TxValidationState tx_state;
6696
uint64_t index{0};
6797
if (!GetDataFromUnlockTx(*tx, unlocked, index, tx_state)) {
68-
throw std::runtime_error(strprintf("%s: CCreditPoolManager::GetDataFromUnlockTxes failed: %s", __func__, tx_state.ToString()));
98+
throw std::runtime_error(strprintf("%s: GetDataFromUnlockTx failed: %s", __func__, tx_state.ToString()));
6999
}
70100
blockData.unlocked += unlocked;
71101
blockData.indexes.insert(index);
72102
}
103+
104+
LOCK(cache_mutex);
105+
block_data_cache.insert(block_index->GetBlockHash(), blockData);
73106
return blockData;
74107
}
75108

@@ -112,32 +145,11 @@ void CCreditPoolManager::AddToCache(const uint256& block_hash, int height, const
112145
}
113146
}
114147

115-
static std::optional<CBlock> GetBlockForCreditPool(const gsl::not_null<const CBlockIndex*> block_index,
116-
const Consensus::Params& consensusParams)
117-
{
118-
// There's no CbTx before DIP0003 activation
119-
if (!DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
120-
return std::nullopt;
121-
}
122-
123-
CBlock block;
124-
if (!ReadBlockFromDisk(block, block_index, consensusParams)) {
125-
throw std::runtime_error("failed-getcbforblock-read");
126-
}
127-
128-
if (block.vtx.empty() || block.vtx[0]->vExtraPayload.empty() || !block.vtx[0]->IsSpecialTxVersion()) {
129-
LogPrintf("%s: ERROR: empty CbTx for CreditPool at height=%d\n", __func__, block_index->nHeight);
130-
return std::nullopt;
131-
}
132-
133-
return block;
134-
}
135-
136148
CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_null<const CBlockIndex*> block_index,
137149
CCreditPool prev, const Consensus::Params& consensusParams)
138150
{
139-
std::optional<CBlock> block = GetBlockForCreditPool(block_index, consensusParams);
140-
if (!block) {
151+
std::optional<CreditPoolDataPerBlock> opt_block_data = GetCreditDataFromBlock(block_index, consensusParams);
152+
if (!opt_block_data) {
141153
// If reading of previous block is not successfully, but
142154
// prev contains credit pool related data, something strange happened
143155
if (prev.locked != 0) {
@@ -151,63 +163,54 @@ CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_null<const CB
151163
AddToCache(block_index->GetBlockHash(), block_index->nHeight, emptyPool);
152164
return emptyPool;
153165
}
154-
CAmount locked = [&, func=__func__]() {
155-
const auto opt_cbTx = GetTxPayload<CCbTx>(block->vtx[0]->vExtraPayload);
156-
if (!opt_cbTx) {
157-
throw std::runtime_error(strprintf("%s: failed-getcreditpool-cbtx-payload", func));
158-
}
159-
return opt_cbTx->creditPoolBalance;
160-
}();
166+
const CreditPoolDataPerBlock& blockData{*opt_block_data};
161167

162168
// We use here sliding window with Params().CreditPoolPeriodBlocks to determine
163169
// current limits for asset unlock transactions.
164170
// Indexes should not be duplicated since genesis block, but the Unlock Amount
165171
// of withdrawal transaction is limited only by this window
166-
UnlockDataPerBlock blockData = GetDataFromUnlockTxes(block->vtx);
167172
CRangesSet indexes{std::move(prev.indexes)};
168173
if (std::any_of(blockData.indexes.begin(), blockData.indexes.end(), [&](const uint64_t index) { return !indexes.Add(index); })) {
169174
throw std::runtime_error(strprintf("%s: failed-getcreditpool-index-duplicated", __func__));
170175
}
171176

172-
const CBlockIndex* distant_block_index = block_index;
173-
for ([[maybe_unused]] auto _ : irange::range(Params().CreditPoolPeriodBlocks())) {
174-
distant_block_index = distant_block_index->pprev;
175-
if (distant_block_index == nullptr) break;
176-
}
177+
const CBlockIndex* distant_block_index{
178+
block_index->GetAncestor(block_index->nHeight - Params().CreditPoolPeriodBlocks())};
177179
CAmount distantUnlocked{0};
178180
if (distant_block_index) {
179-
if (std::optional<CBlock> distant_block = GetBlockForCreditPool(distant_block_index, consensusParams); distant_block) {
180-
distantUnlocked = GetDataFromUnlockTxes(distant_block->vtx).unlocked;
181+
if (std::optional<CreditPoolDataPerBlock> distant_block{GetCreditDataFromBlock(distant_block_index, consensusParams)};
182+
distant_block) {
183+
distantUnlocked = distant_block->unlocked;
181184
}
182185
}
183186

184-
CAmount currentLimit = locked;
187+
CAmount currentLimit = blockData.credit_pool;
185188
const CAmount latelyUnlocked = prev.latelyUnlocked + blockData.unlocked - distantUnlocked;
186189
if (DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_WITHDRAWALS)) {
187190
currentLimit = std::min(currentLimit, LimitAmountV22);
188191
} else {
189192
// Unlock limits in pre-v22 are max(100, min(.10 * assetlockpool, 1000)) inside window
190193
if (currentLimit + latelyUnlocked > LimitAmountLow) {
191-
currentLimit = std::max(LimitAmountLow, locked / 10) - latelyUnlocked;
194+
currentLimit = std::max(LimitAmountLow, blockData.credit_pool / 10) - latelyUnlocked;
192195
if (currentLimit < 0) currentLimit = 0;
193196
}
194197
currentLimit = std::min(currentLimit, LimitAmountHigh - latelyUnlocked);
195198
}
196199

197-
if (currentLimit != 0 || latelyUnlocked > 0 || locked > 0) {
200+
if (currentLimit != 0 || latelyUnlocked > 0 || blockData.credit_pool > 0) {
198201
LogPrint(BCLog::CREDITPOOL, /* Continued */
199202
"CCreditPoolManager: asset unlock limits on height: %d locked: %d.%08d limit: %d.%08d "
200203
"unlocked-in-window: %d.%08d\n",
201-
block_index->nHeight, locked / COIN, locked % COIN, currentLimit / COIN, currentLimit % COIN,
202-
latelyUnlocked / COIN, latelyUnlocked % COIN);
204+
block_index->nHeight, blockData.credit_pool / COIN, blockData.credit_pool % COIN, currentLimit / COIN,
205+
currentLimit % COIN, latelyUnlocked / COIN, latelyUnlocked % COIN);
203206
}
204207

205208
if (currentLimit < 0) {
206209
throw std::runtime_error(
207210
strprintf("Negative limit for CreditPool: %d.%08d\n", currentLimit / COIN, currentLimit % COIN));
208211
}
209212

210-
CCreditPool pool{locked, currentLimit, latelyUnlocked, indexes};
213+
CCreditPool pool{blockData.credit_pool, currentLimit, latelyUnlocked, indexes};
211214
AddToCache(block_index->GetBlockHash(), block_index->nHeight, pool);
212215
return pool;
213216

0 commit comments

Comments
 (0)