|
17 | 17 | #include <random.h>
|
18 | 18 | #include <reverse_iterator.h>
|
19 | 19 | #include <util/check.h>
|
| 20 | +#include <util/feefrac.h> |
20 | 21 | #include <util/moneystr.h>
|
21 | 22 | #include <util/overflow.h>
|
22 | 23 | #include <util/result.h>
|
@@ -1238,3 +1239,131 @@ std::vector<CTxMemPool::txiter> CTxMemPool::GatherClusters(const std::vector<uin
|
1238 | 1239 | }
|
1239 | 1240 | return clustered_txs;
|
1240 | 1241 | }
|
| 1242 | + |
| 1243 | +std::optional<std::string> CTxMemPool::CheckConflictTopology(const setEntries& direct_conflicts) |
| 1244 | +{ |
| 1245 | + for (const auto& direct_conflict : direct_conflicts) { |
| 1246 | + // Ancestor and descendant counts are inclusive of the tx itself. |
| 1247 | + const auto ancestor_count{direct_conflict->GetCountWithAncestors()}; |
| 1248 | + const auto descendant_count{direct_conflict->GetCountWithDescendants()}; |
| 1249 | + const bool has_ancestor{ancestor_count > 1}; |
| 1250 | + const bool has_descendant{descendant_count > 1}; |
| 1251 | + const auto& txid_string{direct_conflict->GetSharedTx()->GetHash().ToString()}; |
| 1252 | + // The only allowed configurations are: |
| 1253 | + // 1 ancestor and 0 descendant |
| 1254 | + // 0 ancestor and 1 descendant |
| 1255 | + // 0 ancestor and 0 descendant |
| 1256 | + if (ancestor_count > 2) { |
| 1257 | + return strprintf("%s has %u ancestors, max 1 allowed", txid_string, ancestor_count - 1); |
| 1258 | + } else if (descendant_count > 2) { |
| 1259 | + return strprintf("%s has %u descendants, max 1 allowed", txid_string, descendant_count - 1); |
| 1260 | + } else if (has_ancestor && has_descendant) { |
| 1261 | + return strprintf("%s has both ancestor and descendant, exceeding cluster limit of 2", txid_string); |
| 1262 | + } |
| 1263 | + // Additionally enforce that: |
| 1264 | + // If we have a child, we are its only parent. |
| 1265 | + // If we have a parent, we are its only child. |
| 1266 | + if (has_descendant) { |
| 1267 | + const auto& our_child = direct_conflict->GetMemPoolChildrenConst().begin(); |
| 1268 | + if (our_child->get().GetCountWithAncestors() > 2) { |
| 1269 | + return strprintf("%s is not the only parent of child %s", |
| 1270 | + txid_string, our_child->get().GetSharedTx()->GetHash().ToString()); |
| 1271 | + } |
| 1272 | + } else if (has_ancestor) { |
| 1273 | + const auto& our_parent = direct_conflict->GetMemPoolParentsConst().begin(); |
| 1274 | + if (our_parent->get().GetCountWithDescendants() > 2) { |
| 1275 | + return strprintf("%s is not the only child of parent %s", |
| 1276 | + txid_string, our_parent->get().GetSharedTx()->GetHash().ToString()); |
| 1277 | + } |
| 1278 | + } |
| 1279 | + } |
| 1280 | + return std::nullopt; |
| 1281 | +} |
| 1282 | + |
| 1283 | +util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::CalculateFeerateDiagramsForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts) |
| 1284 | +{ |
| 1285 | + Assume(replacement_vsize > 0); |
| 1286 | + |
| 1287 | + auto err_string{CheckConflictTopology(direct_conflicts)}; |
| 1288 | + if (err_string.has_value()) { |
| 1289 | + // Unsupported topology for calculating a feerate diagram |
| 1290 | + return util::Error{Untranslated(err_string.value())}; |
| 1291 | + } |
| 1292 | + |
| 1293 | + // new diagram will have chunks that consist of each ancestor of |
| 1294 | + // direct_conflicts that is at its own fee/size, along with the replacement |
| 1295 | + // tx/package at its own fee/size |
| 1296 | + |
| 1297 | + // old diagram will consist of each element of all_conflicts either at |
| 1298 | + // its own feerate (followed by any descendant at its own feerate) or as a |
| 1299 | + // single chunk at its descendant's ancestor feerate. |
| 1300 | + |
| 1301 | + std::vector<FeeFrac> old_chunks; |
| 1302 | + // Step 1: build the old diagram. |
| 1303 | + |
| 1304 | + // The above clusters are all trivially linearized; |
| 1305 | + // they have a strict topology of 1 or two connected transactions. |
| 1306 | + |
| 1307 | + // OLD: Compute existing chunks from all affected clusters |
| 1308 | + for (auto txiter : all_conflicts) { |
| 1309 | + // Does this transaction have descendants? |
| 1310 | + if (txiter->GetCountWithDescendants() > 1) { |
| 1311 | + // Consider this tx when we consider the descendant. |
| 1312 | + continue; |
| 1313 | + } |
| 1314 | + // Does this transaction have ancestors? |
| 1315 | + FeeFrac individual{txiter->GetModifiedFee(), txiter->GetTxSize()}; |
| 1316 | + if (txiter->GetCountWithAncestors() > 1) { |
| 1317 | + // We'll add chunks for either the ancestor by itself and this tx |
| 1318 | + // by itself, or for a combined package. |
| 1319 | + FeeFrac package{txiter->GetModFeesWithAncestors(), static_cast<int32_t>(txiter->GetSizeWithAncestors())}; |
| 1320 | + if (individual > package) { |
| 1321 | + // The individual feerate is higher than the package, and |
| 1322 | + // therefore higher than the parent's fee. Chunk these |
| 1323 | + // together. |
| 1324 | + old_chunks.emplace_back(package); |
| 1325 | + } else { |
| 1326 | + // Add two points, one for the parent and one for this child. |
| 1327 | + old_chunks.emplace_back(package - individual); |
| 1328 | + old_chunks.emplace_back(individual); |
| 1329 | + } |
| 1330 | + } else { |
| 1331 | + old_chunks.emplace_back(individual); |
| 1332 | + } |
| 1333 | + } |
| 1334 | + |
| 1335 | + // No topology restrictions post-chunking; sort |
| 1336 | + std::sort(old_chunks.begin(), old_chunks.end(), std::greater()); |
| 1337 | + std::vector<FeeFrac> old_diagram = BuildDiagramFromChunks(old_chunks); |
| 1338 | + |
| 1339 | + std::vector<FeeFrac> new_chunks; |
| 1340 | + |
| 1341 | + /* Step 2: build the NEW diagram |
| 1342 | + * CON = Conflicts of proposed chunk |
| 1343 | + * CNK = Proposed chunk |
| 1344 | + * NEW = OLD - CON + CNK: New diagram includes all chunks in OLD, minus |
| 1345 | + * the conflicts, plus the proposed chunk |
| 1346 | + */ |
| 1347 | + |
| 1348 | + // OLD - CON: Add any parents of direct conflicts that are not conflicted themselves |
| 1349 | + for (auto direct_conflict : direct_conflicts) { |
| 1350 | + // If a direct conflict has an ancestor that is not in all_conflicts, |
| 1351 | + // it can be affected by the replacement of the child. |
| 1352 | + if (direct_conflict->GetMemPoolParentsConst().size() > 0) { |
| 1353 | + // Grab the parent. |
| 1354 | + const CTxMemPoolEntry& parent = direct_conflict->GetMemPoolParentsConst().begin()->get(); |
| 1355 | + if (!all_conflicts.count(mapTx.iterator_to(parent))) { |
| 1356 | + // This transaction would be left over, so add to the NEW |
| 1357 | + // diagram. |
| 1358 | + new_chunks.emplace_back(parent.GetModifiedFee(), parent.GetTxSize()); |
| 1359 | + } |
| 1360 | + } |
| 1361 | + } |
| 1362 | + // + CNK: Add the proposed chunk itself |
| 1363 | + new_chunks.emplace_back(replacement_fees, int32_t(replacement_vsize)); |
| 1364 | + |
| 1365 | + // No topology restrictions post-chunking; sort |
| 1366 | + std::sort(new_chunks.begin(), new_chunks.end(), std::greater()); |
| 1367 | + std::vector<FeeFrac> new_diagram = BuildDiagramFromChunks(new_chunks); |
| 1368 | + return std::make_pair(old_diagram, new_diagram); |
| 1369 | +} |
0 commit comments