Skip to content

Commit 9c9af5a

Browse files
committed
Merge #9499: Use recent-rejects, orphans, and recently-replaced txn for compact-block-reconstruction
c594580 Add braces around AddToCompactExtraTransactions (Matt Corallo) 1ccfe9b Clarify comment about mempool/extra conflicts (Matt Corallo) fac4c78 Make PartiallyDownloadedBlock::InitData's second param const (Matt Corallo) b55b416 Add extra_count lower bound to compact reconstruction debug print (Matt Corallo) 863edb4 Consider all (<100k memusage) txn for compact-block-extra-txn cache (Matt Corallo) 7f8c8ca Consider all orphan txn for compact-block-extra-txn cache (Matt Corallo) 93380c5 Use replaced transactions in compact block reconstruction (Matt Corallo) 1531652 Keep shared_ptrs to recently-replaced txn for compact blocks (Matt Corallo) edded80 Make ATMP optionally return the CTransactionRefs it replaced (Matt Corallo) c735540 Move ORPHAN constants from validation.h to net_processing.h (Matt Corallo)
2 parents 6012967 + c594580 commit 9c9af5a

11 files changed

+105
-31
lines changed

src/blockencodings.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const uint256& txhash) const {
4747

4848

4949

50-
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock) {
50+
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<uint256, CTransactionRef>>& extra_txn) {
5151
if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty()))
5252
return READ_STATUS_INVALID;
5353
if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_BASE_SIZE / MIN_TRANSACTION_BASE_SIZE)
@@ -104,6 +104,7 @@ ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& c
104104
return READ_STATUS_FAILED; // Short ID collision
105105

106106
std::vector<bool> have_txn(txn_available.size());
107+
{
107108
LOCK(pool->cs);
108109
const std::vector<std::pair<uint256, CTxMemPool::txiter> >& vTxHashes = pool->vTxHashes;
109110
for (size_t i = 0; i < vTxHashes.size(); i++) {
@@ -130,6 +131,38 @@ ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& c
130131
if (mempool_count == shorttxids.size())
131132
break;
132133
}
134+
}
135+
136+
for (size_t i = 0; i < extra_txn.size(); i++) {
137+
uint64_t shortid = cmpctblock.GetShortID(extra_txn[i].first);
138+
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
139+
if (idit != shorttxids.end()) {
140+
if (!have_txn[idit->second]) {
141+
txn_available[idit->second] = extra_txn[i].second;
142+
have_txn[idit->second] = true;
143+
mempool_count++;
144+
extra_count++;
145+
} else {
146+
// If we find two mempool/extra txn that match the short id, just
147+
// request it.
148+
// This should be rare enough that the extra bandwidth doesn't matter,
149+
// but eating a round-trip due to FillBlock failure would be annoying
150+
// Note that we dont want duplication between extra_txn and mempool to
151+
// trigger this case, so we compare witness hashes first
152+
if (txn_available[idit->second] &&
153+
txn_available[idit->second]->GetWitnessHash() != extra_txn[i].second->GetWitnessHash()) {
154+
txn_available[idit->second].reset();
155+
mempool_count--;
156+
extra_count--;
157+
}
158+
}
159+
}
160+
// Though ideally we'd continue scanning for the two-txn-match-shortid case,
161+
// the performance win of an early exit here is too good to pass up and worth
162+
// the extra risk.
163+
if (mempool_count == shorttxids.size())
164+
break;
165+
}
133166

134167
LogPrint("cmpctblock", "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of size %lu\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock, SER_NETWORK, PROTOCOL_VERSION));
135168

@@ -176,7 +209,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
176209
return READ_STATUS_CHECKBLOCK_FAILED;
177210
}
178211

179-
LogPrint("cmpctblock", "Successfully reconstructed block %s with %lu txn prefilled, %lu txn from mempool and %lu txn requested\n", hash.ToString(), prefilled_count, mempool_count, vtx_missing.size());
212+
LogPrint("cmpctblock", "Successfully reconstructed block %s with %lu txn prefilled, %lu txn from mempool (incl at least %lu from extra pool) and %lu txn requested\n", hash.ToString(), prefilled_count, mempool_count, extra_count, vtx_missing.size());
180213
if (vtx_missing.size() < 5) {
181214
for (const auto& tx : vtx_missing)
182215
LogPrint("cmpctblock", "Reconstructed block %s required tx %s\n", hash.ToString(), tx->GetHash().ToString());

src/blockencodings.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,14 @@ class CBlockHeaderAndShortTxIDs {
194194
class PartiallyDownloadedBlock {
195195
protected:
196196
std::vector<CTransactionRef> txn_available;
197-
size_t prefilled_count = 0, mempool_count = 0;
197+
size_t prefilled_count = 0, mempool_count = 0, extra_count = 0;
198198
CTxMemPool* pool;
199199
public:
200200
CBlockHeader header;
201201
PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {}
202202

203-
ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock);
203+
// extra_txn is a list of extra transactions to look at, in <witness hash, reference> form
204+
ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<uint256, CTransactionRef>>& extra_txn);
204205
bool IsTxAvailable(size_t index) const;
205206
ReadStatus FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing);
206207
};

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ std::string HelpMessage(HelpMessageMode mode)
345345
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
346346
strUsage += HelpMessageOpt("-maxmempool=<n>", strprintf(_("Keep the transaction memory pool below <n> megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE));
347347
strUsage += HelpMessageOpt("-mempoolexpiry=<n>", strprintf(_("Do not keep transactions in the mempool longer than <n> hours (default: %u)"), DEFAULT_MEMPOOL_EXPIRY));
348+
strUsage += HelpMessageOpt("-blockreconstructionextratxn=<n>", strprintf(_("Extra transactions to keep in memory for compact block reconstructions (default: %u)"), DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN));
348349
strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"),
349350
-GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS));
350351
#ifndef WIN32

src/net_processing.cpp

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(cs_main);
5959
map<COutPoint, set<map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(cs_main);
6060
void EraseOrphansFor(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
6161

62+
static size_t vExtraTxnForCompactIt = 0;
63+
static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(cs_main);
64+
6265
static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8]
6366

6467
// Internal stuff
@@ -591,6 +594,17 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals)
591594
// mapOrphanTransactions
592595
//
593596

597+
void AddToCompactExtraTransactions(const CTransactionRef& tx)
598+
{
599+
size_t max_extra_txn = GetArg("-blockreconstructionextratxn", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN);
600+
if (max_extra_txn <= 0)
601+
return;
602+
if (!vExtraTxnForCompact.size())
603+
vExtraTxnForCompact.resize(max_extra_txn);
604+
vExtraTxnForCompact[vExtraTxnForCompactIt] = std::make_pair(tx->GetWitnessHash(), tx);
605+
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn;
606+
}
607+
594608
bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
595609
{
596610
const uint256& hash = tx->GetHash();
@@ -617,6 +631,8 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE
617631
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first);
618632
}
619633

634+
AddToCompactExtraTransactions(tx);
635+
620636
LogPrint("mempool", "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(),
621637
mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size());
622638
return true;
@@ -1722,7 +1738,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
17221738
pfrom->setAskFor.erase(inv.hash);
17231739
mapAlreadyAskedFor.erase(inv.hash);
17241740

1725-
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) {
1741+
std::list<CTransactionRef> lRemovedTxn;
1742+
1743+
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs, &lRemovedTxn)) {
17261744
mempool.check(pcoinsTip);
17271745
RelayTransaction(tx, connman);
17281746
for (unsigned int i = 0; i < tx.vout.size(); i++) {
@@ -1760,7 +1778,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
17601778

17611779
if (setMisbehaving.count(fromPeer))
17621780
continue;
1763-
if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, true, &fMissingInputs2)) {
1781+
if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, true, &fMissingInputs2, &lRemovedTxn)) {
17641782
LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString());
17651783
RelayTransaction(orphanTx, connman);
17661784
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
@@ -1833,6 +1851,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
18331851
// See https://github.com/bitcoin/bitcoin/issues/8279 for details.
18341852
assert(recentRejects);
18351853
recentRejects->insert(tx.GetHash());
1854+
if (RecursiveDynamicUsage(*ptx) < 100000) {
1855+
AddToCompactExtraTransactions(ptx);
1856+
}
1857+
} else if (tx.HasWitness() && RecursiveDynamicUsage(*ptx) < 100000) {
1858+
AddToCompactExtraTransactions(ptx);
18361859
}
18371860

18381861
if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
@@ -1853,6 +1876,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
18531876
}
18541877
}
18551878
}
1879+
1880+
for (const CTransactionRef& tx : lRemovedTxn)
1881+
AddToCompactExtraTransactions(tx);
1882+
18561883
int nDoS = 0;
18571884
if (state.IsInvalid(nDoS))
18581885
{
@@ -1969,7 +1996,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
19691996
}
19701997

19711998
PartiallyDownloadedBlock& partialBlock = *(*queuedBlockIt)->partialBlock;
1972-
ReadStatus status = partialBlock.InitData(cmpctblock);
1999+
ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact);
19732000
if (status == READ_STATUS_INVALID) {
19742001
MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case of whitelist
19752002
Misbehaving(pfrom->GetId(), 100);
@@ -2005,7 +2032,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
20052032
// Optimistically try to reconstruct anyway since we might be
20062033
// able to without any round trips.
20072034
PartiallyDownloadedBlock tempBlock(&mempool);
2008-
ReadStatus status = tempBlock.InitData(cmpctblock);
2035+
ReadStatus status = tempBlock.InitData(cmpctblock, vExtraTxnForCompact);
20092036
if (status != READ_STATUS_OK) {
20102037
// TODO: don't ignore failures
20112038
return true;

src/net_processing.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
#include "net.h"
1010
#include "validationinterface.h"
1111

12+
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
13+
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
14+
/** Expiration time for orphan transactions in seconds */
15+
static const int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
16+
/** Minimum time between orphan transactions expire time checks in seconds */
17+
static const int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
18+
/** Default number of orphan+recently-replaced txn to keep around for block reconstruction */
19+
static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100;
20+
1221
/** Register with a network node to receive its signals */
1322
void RegisterNodeSignals(CNodeSignals& nodeSignals);
1423
/** Unregister a network node */

src/rpc/rawtransaction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
903903
// push to local node and sync with wallets
904904
CValidationState state;
905905
bool fMissingInputs;
906-
if (!AcceptToMemoryPool(mempool, state, std::move(tx), fLimitFree, &fMissingInputs, false, nMaxRawTxFee)) {
906+
if (!AcceptToMemoryPool(mempool, state, std::move(tx), fLimitFree, &fMissingInputs, NULL, false, nMaxRawTxFee)) {
907907
if (state.IsInvalid()) {
908908
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
909909
} else {

src/test/blockencodings_tests.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include <boost/test/unit_test.hpp>
1313

14+
std::vector<std::pair<uint256, CTransactionRef>> extra_txn;
15+
1416
struct RegtestingSetup : public TestingSetup {
1517
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
1618
};
@@ -73,7 +75,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
7375
stream >> shortIDs2;
7476

7577
PartiallyDownloadedBlock partialBlock(&pool);
76-
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK);
78+
BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
7779
BOOST_CHECK( partialBlock.IsTxAvailable(0));
7880
BOOST_CHECK(!partialBlock.IsTxAvailable(1));
7981
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -179,7 +181,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
179181
stream >> shortIDs2;
180182

181183
PartiallyDownloadedBlock partialBlock(&pool);
182-
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK);
184+
BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
183185
BOOST_CHECK(!partialBlock.IsTxAvailable(0));
184186
BOOST_CHECK( partialBlock.IsTxAvailable(1));
185187
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -245,7 +247,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
245247
stream >> shortIDs2;
246248

247249
PartiallyDownloadedBlock partialBlock(&pool);
248-
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK);
250+
BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
249251
BOOST_CHECK( partialBlock.IsTxAvailable(0));
250252
BOOST_CHECK( partialBlock.IsTxAvailable(1));
251253
BOOST_CHECK( partialBlock.IsTxAvailable(2));
@@ -300,7 +302,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
300302
stream >> shortIDs2;
301303

302304
PartiallyDownloadedBlock partialBlock(&pool);
303-
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK);
305+
BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
304306
BOOST_CHECK(partialBlock.IsTxAvailable(0));
305307

306308
CBlock block2;

src/test/txvalidationcache_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ ToMemPool(CMutableTransaction& tx)
2323
LOCK(cs_main);
2424

2525
CValidationState state;
26-
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), false, NULL, true, 0);
26+
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), false, NULL, NULL, true, 0);
2727
}
2828

2929
BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)

src/validation.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ static bool IsCurrentForFeeEstimation()
540540
}
541541

542542
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree,
543-
bool* pfMissingInputs, int64_t nAcceptTime, bool fOverrideMempoolLimit, const CAmount& nAbsurdFee,
544-
std::vector<uint256>& vHashTxnToUncache)
543+
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
544+
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<uint256>& vHashTxnToUncache)
545545
{
546546
const CTransaction& tx = *ptx;
547547
const uint256 hash = tx.GetHash();
@@ -952,6 +952,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
952952
hash.ToString(),
953953
FormatMoney(nModifiedFees - nConflictingFees),
954954
(int)nSize - (int)nConflictingSize);
955+
if (plTxnReplaced)
956+
plTxnReplaced->push_back(it->GetSharedTx());
955957
}
956958
pool.RemoveStaged(allConflicting, false);
957959

@@ -977,10 +979,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
977979
}
978980

979981
bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
980-
bool* pfMissingInputs, int64_t nAcceptTime, bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
982+
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
983+
bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
981984
{
982985
std::vector<uint256> vHashTxToUncache;
983-
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
986+
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
984987
if (!res) {
985988
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
986989
pcoinsTip->Uncache(hashTx);
@@ -992,9 +995,10 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
992995
}
993996

994997
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
995-
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
998+
bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced,
999+
bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
9961000
{
997-
return AcceptToMemoryPoolWithTime(pool, state, tx, fLimitFree, pfMissingInputs, GetTime(), fOverrideMempoolLimit, nAbsurdFee);
1001+
return AcceptToMemoryPoolWithTime(pool, state, tx, fLimitFree, pfMissingInputs, GetTime(), plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee);
9981002
}
9991003

10001004
/** Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock */
@@ -2160,7 +2164,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
21602164
const CTransaction& tx = *it;
21612165
// ignore validation errors in resurrected transactions
21622166
CValidationState stateDummy;
2163-
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, it, false, NULL, true)) {
2167+
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, it, false, NULL, NULL, true)) {
21642168
mempool.removeRecursive(tx);
21652169
} else if (mempool.exists(tx.GetHash())) {
21662170
vHashUpdate.push_back(tx.GetHash());

0 commit comments

Comments
 (0)