From 29b1f21c7792927b640a57d1b871549005178ac0 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Tue, 18 Jul 2017 17:41:30 -0400 Subject: [PATCH 1/4] Free relay reserved entirely for withdrawlock spends, not rate limited --- src/validation.cpp | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index f046e309919..2b6a9e767ec 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1210,8 +1210,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount inChainInputValue; double dPriority = view.GetPriority(tx, chainActive.Height(), inChainInputValue); + bool fIsWithdrawLockSpender = false; + // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. + // Also track withdraw lock spends to allow them through free relay bool fSpendsCoinbase = false; BOOST_FOREACH(const CTxIn &txin, tx.vin) { const CCoins *coins = view.AccessCoins(txin.prevout.hash); @@ -1219,6 +1222,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C fSpendsCoinbase = true; break; } + if (coins->vout[txin.prevout.n].scriptPubKey.IsWithdrawLock()) { + fIsWithdrawLockSpender = true; + } } CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, dPriority, chainActive.Height(), @@ -1237,32 +1243,12 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount mempoolRejectFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize); if (mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) { return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nFees, mempoolRejectFee)); - } else if (GetBoolArg("-relaypriority", DEFAULT_RELAYPRIORITY) && nModifiedFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(entry.GetPriority(chainActive.Height() + 1))) { - // Require that free transactions have sufficient priority to be mined in the next block. - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient priority"); } - // Continuously rate-limit free (really, very-low-fee) transactions - // This mitigates 'penny-flooding' -- sending thousands of free transactions just to - // be annoying or make others' transactions take longer to confirm. - if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) + // No transactions are allowed below minRelayTxFee except from disconnected blocks and withdraw lock spends + if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nSize) && !fIsWithdrawLockSpender) { - static CCriticalSection csFreeLimiter; - static double dFreeCount; - static int64_t nLastTime; - int64_t nNow = GetTime(); - - LOCK(csFreeLimiter); - - // Use an exponentially decaying ~10-minute window: - dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime)); - nLastTime = nNow; - // -limitfreerelay unit is thousand-bytes-per-minute - // At default rate it would take over a month to fill 1GB - if (dFreeCount + nSize >= GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) * 10 * 1000) - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "rate limited free transaction"); - LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); - dFreeCount += nSize; + return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met"); } if (nAbsurdFee && nFees > nAbsurdFee) From 9823c8889c29def885eaa0f3ed93514fd79cc963 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Tue, 18 Jul 2017 17:41:59 -0400 Subject: [PATCH 2/4] Allow mempool withdrawlocks to be grabbed by clients during pegin --- src/validation.cpp | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 2b6a9e767ec..43a9f7c6971 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1591,8 +1591,10 @@ bool GetLockedOutputs(const uint256 &genesisHash, const CAmount &nAmount, std::v std::vector > locksCreated; bool locksChanged = false; - if (!pblocktree->ReadLocksCreated(genesisHash, locksCreated)) - return false; + // If this fails, we should still try to grab from mempool. + if (!pblocktree->ReadLocksCreated(genesisHash, locksCreated)) { + locksCreated.clear(); + } //For faster random esasure std::list > locksList; @@ -1632,8 +1634,9 @@ bool GetLockedOutputs(const uint256 &genesisHash, const CAmount &nAmount, std::v pblocktree->ReWriteLocksCreated(genesisHash, vChangedLocks); } //Found single lock large enough - if (res.size()) + if (res.size()) { return true; + } CAmount nTotal = 0; //Gather up smaller locked outputs for aggregation. @@ -1646,8 +1649,9 @@ bool GetLockedOutputs(const uint256 &genesisHash, const CAmount &nAmount, std::v continue; } - if (mempool.mapNextTx.count(COutPoint(it->first.hash, it->first.n))) + if (mempool.mapNextTx.count(COutPoint(it->first.hash, it->first.n))) { continue; + } assert(coins.vout[it->first.n].nValue.IsExplicit() && coins.vout[it->first.n].nValue.GetAmount() == it->second); res.push_back(*it); @@ -1675,20 +1679,28 @@ bool GetLockedOutputs(const uint256 &genesisHash, const CAmount &nAmount, std::v const CTransaction& tx = *ptx; for (unsigned int j = 0; j < tx.vout.size() && nTotal < nAmount; j++) { CTxOut txout = tx.vout[j]; - if (!txout.scriptPubKey.IsWithdrawLock()) + if (!txout.scriptPubKey.IsWithdrawLock()) { continue; + } uint256 withdrawGenHash = txout.scriptPubKey.GetWithdrawLockGenesisHash(); - if (genesisHash != withdrawGenHash) + if (genesisHash != withdrawGenHash) { continue; - if (mempool.mapWithdrawsSpentToTxid.count(std::make_pair(withdrawGenHash, COutPoint(tx.GetHash(), j)))) + } + + // Only written locks are filtered previously for invalid type + if (txout.nValue.IsCommitment() || txout.nAsset.IsCommitment() || txout.nAsset.GetAsset() != BITCOINID) { continue; + } + + if (mempool.mapNextTx.count(COutPoint(tx.GetHash(), j))) { + continue; + } - if (txout.scriptPubKey.IsWithdrawLock() && txout.nValue.IsExplicit()) { - res.push_back(std::make_pair(COutPoint(tx.GetHash(), j), txout.nValue.GetAmount())); - nTotal += txout.nValue.GetAmount(); - if (nTotal >= nAmount) - return true; + res.push_back(std::make_pair(COutPoint(tx.GetHash(), j), txout.nValue.GetAmount())); + nTotal += txout.nValue.GetAmount(); + if (nTotal >= nAmount) { + return true; } } } @@ -1698,8 +1710,9 @@ bool GetLockedOutputs(const uint256 &genesisHash, const CAmount &nAmount, std::v //res list is already randomized nTotal = 0; size_t i = 0; - for (; i < res.size() && nTotal < nAmount; i++) + for (; i < res.size() && nTotal < nAmount; i++) { nTotal += res[i].second; + } res.resize(i); return true; From 90ee152c93c73159f45847b149fa4422312f0585 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Wed, 19 Jul 2017 10:26:58 -0400 Subject: [PATCH 3/4] withdrawlocks do not need any maturing, test mempool access --- qa/rpc-tests/pegging.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/qa/rpc-tests/pegging.py b/qa/rpc-tests/pegging.py index 38e6ad37a7b..275f492abff 100755 --- a/qa/rpc-tests/pegging.py +++ b/qa/rpc-tests/pegging.py @@ -114,13 +114,17 @@ # Lockup some funds to unlock later sidechain.sendtomainchain(addr, 50) - sidechain.generate(101) + # Tests withdrawlock tracking in database + sidechain.generate(1) + # Tests withdrawlock in mempool + sidechain.sendtomainchain(addr, 50) addrs = sidechain.getpeginaddress() - txid = bitcoin.sendtoaddress(addrs["mainchain_address"], 49) + txid1 = bitcoin.sendtoaddress(addrs["mainchain_address"], 24) + txid2 = bitcoin.sendtoaddress(addrs["mainchain_address"], 24) bitcoin.generate(10) - proof = bitcoin.gettxoutproof([txid]) - raw = bitcoin.getrawtransaction(txid) + proof = bitcoin.gettxoutproof([txid1]) + raw = bitcoin.getrawtransaction(txid1) print("Attempting peg-in") @@ -132,8 +136,13 @@ pass timeout = 20 - # Should succeed via wallet lookup for address match - pegtxid = sidechain.claimpegin(raw, proof) + # Both should succeed via wallet lookup for address match, and when given + pegtxid1 = sidechain.claimpegin(raw, proof) + + proof = bitcoin.gettxoutproof([txid2]) + raw = bitcoin.getrawtransaction(txid2) + pegtxid2 = sidechain.claimpegin(raw, proof, addrs["sidechain_address"]) + while len(sidechain.getrawmempool()) != len(sidechain2.getrawmempool()): time.sleep(1) timeout -= 1 @@ -147,9 +156,10 @@ raise Exception("Blocks are not propagating.") - tx = sidechain.gettransaction(pegtxid) + tx1 = sidechain.gettransaction(pegtxid1) + tx2 = sidechain.gettransaction(pegtxid2) - if "confirmations" in tx and tx["confirmations"] > 0: + if "confirmations" in tx1 and tx1["confirmations"] > 0 and "confirmations" in tx2 and tx2["confirmations"] > 0: print("Peg-in is confirmed: Success!") else: raise Exception("Peg-in confirmation has failed.") From 793e92bd08949d639798e0c04e66461488bfea26 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Tue, 25 Jul 2017 19:10:39 -0400 Subject: [PATCH 4/4] add mempool claimpegin flood testing --- qa/rpc-tests/pegging.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/pegging.py b/qa/rpc-tests/pegging.py index 275f492abff..7aabc3db87f 100755 --- a/qa/rpc-tests/pegging.py +++ b/qa/rpc-tests/pegging.py @@ -100,7 +100,7 @@ subprocess.Popen(sidechain2start.split(), stdout=subprocess.PIPE) print("Daemons started") - time.sleep(2) + time.sleep(3) bitcoin = AuthServiceProxy("http://bitcoinrpc:"+bitcoin_pass+"@127.0.0.1:"+str(bitcoin_port)) sidechain = AuthServiceProxy("http://sidechainrpc:"+sidechain_pass+"@127.0.0.1:"+str(sidechain_port)) @@ -164,6 +164,34 @@ else: raise Exception("Peg-in confirmation has failed.") + # Make a few large locks, then do many claims in mempool + n_locks = 10 + n_claims = 30 + + print("Flooding mempool with many small claims") + pegtxs = [] + for i in range(n_locks): + # Lockup some funds to unlock later + sidechain.sendtomainchain(addr, 50) + sidechain.generate(1) + sidechain.generate(101) + + for i in range(n_claims): + addrs = sidechain.getpeginaddress() + txid = bitcoin.sendtoaddress(addrs["mainchain_address"], 1) + bitcoin.generate(10) + proof = bitcoin.gettxoutproof([txid]) + raw = bitcoin.getrawtransaction(txid) + pegtxs += [sidechain.claimpegin(raw, proof)] + + sidechain.generate(1) + for pegtxid in pegtxs: + tx = sidechain.gettransaction(pegtxid) + if "confirmations" not in tx or tx["confirmations"] == 0: + raise Exception("Peg-in confirmation has failed.") + + print("Success!") + except JSONRPCException as e: print("Pegging testing failed, aborting:") print(e.error)