From 51aad1873397455645fcd188b2ae406fa44c9646 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 26 Mar 2018 16:21:33 -0300 Subject: [PATCH] remove replacementpool --- qa/cryptoconditions/test_integration.py | 86 +++- qa/cryptoconditions/testsupport.py | 10 +- src/Makefile.am | 9 +- src/Makefile.ktest.include | 16 - src/cryptoconditions/Makefile.am | 16 +- .../include/cryptoconditions.h | 1 + src/komodo_cryptoconditions.cpp | 79 +--- src/komodo_cryptoconditions.h | 7 +- src/main.cpp | 206 +++----- src/main.h | 2 +- src/replacementpool.cpp | 88 ---- src/replacementpool.h | 76 --- src/secp256k1/include/secp256k1.h | 12 + src/secp256k1/src/secp256k1.c | 8 + src/test-komodo/main.cpp | 9 - src/test-komodo/test_replacementpool.cpp | 440 ------------------ 16 files changed, 190 insertions(+), 875 deletions(-) delete mode 100644 src/Makefile.ktest.include delete mode 100644 src/replacementpool.cpp delete mode 100644 src/replacementpool.h delete mode 100644 src/test-komodo/main.cpp delete mode 100644 src/test-komodo/test_replacementpool.cpp diff --git a/qa/cryptoconditions/test_integration.py b/qa/cryptoconditions/test_integration.py index 16b5f93a6..bd72b961d 100644 --- a/qa/cryptoconditions/test_integration.py +++ b/qa/cryptoconditions/test_integration.py @@ -4,7 +4,6 @@ import json import logging import binascii import struct -import base64 from testsupport import * @@ -91,7 +90,91 @@ def test_oversize_fulfillment(inp): assert 'scriptsig-size' in str(e), str(e) +@fanout_input(6) +def test_aux_basic(inp): + aux_cond = { + 'type': 'aux-sha-256', + 'method': 'equals', + 'conditionAux': 'LTE', + 'fulfillmentAux': 'LTE' + } + + # Setup some aux outputs + spend0 = { + 'inputs': [inp], + 'outputs': [ + {'amount': 500, 'script': {'condition': aux_cond}}, + {'amount': 500, 'script': {'condition': aux_cond}} + ] + } + spend0_txid = submit(sign(spend0)) + assert rpc.getrawtransaction(spend0_txid) + + # Test a good fulfillment + spend1 = { + 'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}], + 'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}] + } + spend1_txid = submit(sign(spend1)) + assert rpc.getrawtransaction(spend1_txid) + + # Test a bad fulfillment + aux_cond['fulfillmentAux'] = 'WYW' + spend2 = { + 'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}], + 'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}] + } + try: + assert not submit(sign(spend2)), 'should raise an error' + except RPCError as e: + assert SCRIPT_FALSE in str(e), str(e) + + @fanout_input(7) +def test_aux_complex(inp): + aux_cond = { + 'type': 'aux-sha-256', + 'method': 'inputIsReturn', + 'conditionAux': '', + 'fulfillmentAux': 'AQ' # \1 (tx.vout[1]) + } + + # Setup some aux outputs + spend0 = { + 'inputs': [inp], + 'outputs': [ + {'amount': 500, 'script': {'condition': aux_cond}}, + {'amount': 500, 'script': {'condition': aux_cond}} + ] + } + spend0_txid = submit(sign(spend0)) + assert rpc.getrawtransaction(spend0_txid) + + # Test a good fulfillment + spend1 = { + 'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}], + 'outputs': [ + {'amount': 250, 'script': {'condition': aux_cond}}, + {'amount': 250, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata + ] + } + spend1_txid = submit(sign(spend1)) + assert rpc.getrawtransaction(spend1_txid) + + # Test a bad fulfillment + spend2 = { + 'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}], + 'outputs': [ + {'amount': 500, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata + ] + } + try: + assert not submit(sign(spend2)), 'should raise an error' + except RPCError as e: + assert SCRIPT_FALSE in str(e), str(e) + + +@fanout_input(8) def test_secp256k1_condition(inp): ec_cond = { 'type': 'secp256k1-sha-256', @@ -130,6 +213,7 @@ def test_secp256k1_condition(inp): except RPCError as e: assert SCRIPT_FALSE in str(e), str(e) + if __name__ == '__main__': logging.basicConfig(level=logging.INFO) for name, f in globals().items(): diff --git a/qa/cryptoconditions/testsupport.py b/qa/cryptoconditions/testsupport.py index 055f4cc30..e22520f3f 100644 --- a/qa/cryptoconditions/testsupport.py +++ b/qa/cryptoconditions/testsupport.py @@ -32,7 +32,7 @@ class JsonClient(object): def run_cmd(cmd): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) - assert proc.wait() == 0, cmd + assert proc.wait() == 0 return proc.stdout.read() @@ -68,7 +68,7 @@ def wait_for_block(height): try: return rpc.getblock(str(height)) except RPCError as e: - time.sleep(1) + time.sleep(3) raise Exception('Time out waiting for block at height %s' % height) @@ -97,7 +97,7 @@ def get_fanout_txid(): reward_tx = hoek.decodeTx({'hex': reward_tx_raw}) balance = reward_tx['outputs'][0]['amount'] - n_outs = 40 + n_outs = 16 remainder = balance - n_outs * 1000 fanout = { @@ -109,9 +109,7 @@ def get_fanout_txid(): ] + [{"amount": remainder, 'script': {'address': notary_addr}}]) } - txid = submit(sign(fanout)) - rpc.getrawtransaction(txid) - return txid + return submit(sign(fanout)) def fanout_input(n): diff --git a/src/Makefile.am b/src/Makefile.am index 7c0200623..00242776f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,7 +39,7 @@ LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a LIBSECP256K1=secp256k1/libsecp256k1.la -LIBCRYPTOCONDITIONS=cryptoconditions/libcryptoconditions.la +LIBCRYPTOCONDITIONS=cryptoconditions/cryptoconditions_core.a LIBUNIVALUE=univalue/libunivalue.la LIBZCASH=libzcash.a -lcurl @@ -156,7 +156,6 @@ BITCOIN_CORE_H = \ protocol.h \ pubkey.h \ random.h \ - replacementpool.h \ reverselock.h \ rpcclient.h \ rpcprotocol.h \ @@ -235,7 +234,6 @@ libbitcoin_server_a_SOURCES = \ noui.cpp \ policy/fees.cpp \ pow.cpp \ - replacementpool.cpp \ rest.cpp \ rpcblockchain.cpp \ rpcmining.cpp \ @@ -562,10 +560,9 @@ endif @test -f $(PROTOC) $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(abspath $( +#include #ifndef CRYPTOCONDITIONS_H diff --git a/src/komodo_cryptoconditions.cpp b/src/komodo_cryptoconditions.cpp index e2695af40..4ebbebbeb 100644 --- a/src/komodo_cryptoconditions.cpp +++ b/src/komodo_cryptoconditions.cpp @@ -1,94 +1,21 @@ - -#include "replacementpool.h" #include "komodo_cryptoconditions.h" #include "cryptoconditions/include/cryptoconditions.h" -bool ASSETCHAINS_CC_TEST = false; - - /* * Evaluate the validity of an Eval node */ bool EvalConditionValidity(const CC *cond, const CTransaction *txTo) { - if (ASSETCHAINS_CC_TEST) { - if (strcmp(cond->method, "testEval") == 0) { - return cond->paramsBinLength == 8 && - memcmp(cond->paramsBin, "testEval", 8) == 0; - } - if (strcmp(cond->method, "testReplace") == 0) { - std::vector data; - auto out = txTo->vout[txTo->vout.size()-1]; // Last output is data - return GetOpReturnData(out.scriptPubKey, data) && data.size() == 2; - } + if (strcmp(cond->method, "testEval") == 0) { + return cond->paramsBinLength == 8 && + memcmp(cond->paramsBin, "testEval", 8) == 0; } fprintf(stderr, "no defined behaviour for method: %s\n", cond->method); return 0; } -/* - * Evaluate the priority of an eval node. - * - * This method should set the ->priority and ->replacementWindow (in blocks) - * of the provided replacement pool item. Priority is a 64 bit unsigned int and - * 0 is invalid. - * - * This method does not need to validate, that is done separately. Actually, - * this method will nearly always be called with the same condition and transaction - * in sequence after EvalConditionValidity. If performance became an issue, a very - * small LRU cache could be used to cache a result. - */ -bool EvalConditionPriority(const CC *cond, CTxReplacementPoolItem *rep) -{ - if (ASSETCHAINS_CC_TEST) { - if (strcmp((const char*)cond->method, "testReplace") == 0) { - std::vector data; - auto out = rep->tx.vout[rep->tx.vout.size()-1]; // Last output is data - if (GetOpReturnData(out.scriptPubKey, data)) { - rep->replacementWindow = (int) data[0]; - rep->priority = (uint64_t) data[1]; - return true; - } - } - } - return false; -} - - -int visitConditionPriority(CC *cond, struct CCVisitor visitor) -{ - auto rep = (CTxReplacementPoolItem*)visitor.context; - // visitor protocol is 1 for continue, 0 for stop - return !(cc_typeId(cond) == CC_Eval && EvalConditionPriority(cond, rep)); -} - - -/* - * Try to get replacement parameters from a transaction in &rep. - */ -bool SetReplacementParams(CTxReplacementPoolItem &rep) -{ - // first, see if we have a cryptocondition - const CScript &sig = rep.tx.vin[0].scriptSig; - if (!sig.IsPushOnly()) return false; - CScript::const_iterator pc = sig.begin(); - std::vector data; - opcodetype opcode; - if (!sig.GetOp(pc, opcode, data)) return false; - CC *cond = cc_readFulfillmentBinary((unsigned char*)data.data(), data.size()); - if (!cond) return false; - - // now, see if it has a replacement node - CC *replacementNode = 0; - CCVisitor visitor = {&visitConditionPriority, (const unsigned char*)"", 0, &rep}; - bool out = cc_visit(cond, visitor); - cc_free(cond); - return !out; -} - - bool GetOpReturnData(const CScript &sig, std::vector &data) { auto pc = sig.begin(); diff --git a/src/komodo_cryptoconditions.h b/src/komodo_cryptoconditions.h index de12140ef..4edc2bedc 100644 --- a/src/komodo_cryptoconditions.h +++ b/src/komodo_cryptoconditions.h @@ -1,24 +1,19 @@ #ifndef KOMODO_CRYPTOCONDITIONS_H #define KOMODO_CRYPTOCONDITIONS_H -#include "replacementpool.h" #include "cryptoconditions/include/cryptoconditions.h" +#include "primitives/transaction.h" #include "script/script.h" extern int32_t ASSETCHAINS_CC; -extern bool ASSETCHAINS_CC_TEST; static bool IsCryptoConditionsEnabled() { return 0 != ASSETCHAINS_CC; } -extern CTxReplacementPool replacementPool; - bool EvalConditionValidity(const CC *cond, const CTransaction *tx); -bool SetReplacementParams(CTxReplacementPoolItem &rep); - bool GetOpReturnData(const CScript &sig, std::vector &data); #endif /* KOMODO_CRYPTOCONDITIONS_H */ diff --git a/src/main.cpp b/src/main.cpp index fa366964f..86b52495a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,8 +21,6 @@ #include "pow.h" #include "txdb.h" #include "txmempool.h" -#include "replacementpool.h" -#include "komodo_cryptoconditions.h" #include "ui_interface.h" #include "undo.h" #include "util.h" @@ -55,7 +53,6 @@ extern uint8_t NOTARY_PUBKEY33[33]; BlockMap mapBlockIndex; CChain chainActive; - CBlockIndex *pindexBestHeader = NULL; int64_t nTimeBestReceived = 0; CWaitableCriticalSection csBestBlock; @@ -1110,45 +1107,7 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF } -/* - * This should be called from AcceptToMemoryPool in order - * to perform all validations. - */ -bool AcceptToReplacementPool(const CTransaction &tx, CValidationState &state) -{ - // This is not actually required; if crypto-conditions is disabled, then transactions - // with replaceable outputs will not be accepted as standard. However, just to be a - // bit more explicit. - if (!IsCryptoConditionsEnabled()) return false; - - CTxReplacementPoolItem item(tx, GetHeight()); - if (!SetReplacementParams(item)) return false; - - switch (replacementPool.replace(item)) { - - case RP_Accept: - return true; - - case RP_HaveBetter: - // already have a better one - fprintf(stderr,"accept failure.20\n"); - return state.Invalid(error("AcceptToMemoryPool: Replacement is worse"), - REJECT_HAVEBETTER, "replacement-is-worse"); - - case RP_Invalid: - // Not valid according to replaceability rules - fprintf(stderr,"accept failure.22\n"); - return state.Invalid(error("AcceptToMemoryPool: Replacement has multiple inputs"), - REJECT_INVALID, "replacement-invalid"); - - default: - return false; - } -} - - - -bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee, bool fAcceptReplacement) +bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee) { AssertLockHeld(cs_main); if (pfMissingInputs) @@ -1372,20 +1331,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString()); } + // Store transaction in memory if ( komodo_is_notarytx(tx) == 0 ) KOMODO_ON_DEMAND++; - - if (fAcceptReplacement) - { - if (AcceptToReplacementPool(tx, state)) return true; - if (state.IsInvalid()) return false; - } - - // Store transaction in memory pool.addUnchecked(hash, entry, !IsInitialBlockDownload()); } SyncWithWallets(tx, NULL); + return true; } @@ -1401,12 +1354,6 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock return true; } - // replacementPool lookup is O(n) since there's no index by txid - if (fAllowSlow && replacementPool.lookup(hash, txOut)) - { - return true; - } - if (fTxIndex) { CDiskTxPos postx; if (pblocktree->ReadTxIndex(hash, postx)) { @@ -1456,21 +1403,6 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock return false; } - -void ProcessOrphanTransactions(uint256 initialHash); - -void ProcessReplacementPool(int newHeight) -{ - std::vector pending; - replacementPool.removePending(newHeight, pending); - CValidationState stateDummy; - BOOST_FOREACH(CTransaction tx, pending) { - if (AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, false, false)) - ProcessOrphanTransactions(tx.GetHash()); - // otherwise silently drop. TODO: log - } -} - /*char *komodo_getspendscript(uint256 hash,int32_t n) { CTransaction tx; uint256 hashBlock; @@ -2000,7 +1932,7 @@ bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, cons // as to the correct behavior - we may want to continue // peering with non-upgraded nodes even after a soft-fork // super-majority vote has passed. - return state.DoS(100, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); + return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } } @@ -2770,13 +2702,6 @@ bool static DisconnectTip(CValidationState &state) { // Update cached incremental witnesses //fprintf(stderr,"chaintip false\n"); GetMainSignals().ChainTip(pindexDelete, &block, newTree, false); - - /* if chain tip disconnects, some transactions may return to the replacementPool - * via AcceptToMemoryPool. - * - * No double send conflicts may result as the winning transaction will be picked. - */ - return true; } @@ -2836,8 +2761,6 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * mempool.check(pcoinsTip); // Update chainActive & related variables. UpdateTip(pindexNew); - // Process pending replacements - ProcessReplacementPool(pindexNew->nHeight); // Tell wallet about transactions that went from mempool // to conflicted: BOOST_FOREACH(const CTransaction &tx, txConflicted) { @@ -2847,7 +2770,6 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * BOOST_FOREACH(const CTransaction &tx, pblock->vtx) { SyncWithWallets(tx, pblock); } - // Update cached incremental witnesses //fprintf(stderr,"chaintip true\n"); GetMainSignals().ChainTip(pindexNew, pblock, oldTree, true); @@ -4748,70 +4670,6 @@ void static ProcessGetData(CNode* pfrom) } } - -void ProcessOrphanTransactions(uint256 parentHash) -{ - AssertLockHeld(cs_main); - - vector vWorkQueue, vEraseQueue; - // Recursively process any orphan transactions that depended on this one - vWorkQueue.push_back(parentHash); - set setMisbehaving; - - for (unsigned int i = 0; i < vWorkQueue.size(); i++) - { - map >::iterator itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue[i]); - if (itByPrev == mapOrphanTransactionsByPrev.end()) - continue; - for (set::iterator mi = itByPrev->second.begin(); - mi != itByPrev->second.end(); - ++mi) - { - const uint256& orphanHash = *mi; - const CTransaction& orphanTx = mapOrphanTransactions[orphanHash].tx; - NodeId fromPeer = mapOrphanTransactions[orphanHash].fromPeer; - bool fMissingInputs2 = false; - // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan - // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get - // anyone relaying LegitTxX banned) - CValidationState stateDummy; - - - if (setMisbehaving.count(fromPeer)) - continue; - if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2)) - { - LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString()); - RelayTransaction(orphanTx); - vWorkQueue.push_back(orphanHash); - vEraseQueue.push_back(orphanHash); - } - else if (!fMissingInputs2) - { - int nDos = 0; - if (stateDummy.IsInvalid(nDos) && nDos > 0) - { - // Punish peer that gave us an invalid orphan tx - Misbehaving(fromPeer, nDos); - setMisbehaving.insert(fromPeer); - LogPrint("mempool", " invalid orphan tx %s\n", orphanHash.ToString()); - } - // Has inputs but not accepted to mempool - // Probably non-standard or insufficient fee/priority - LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString()); - vEraseQueue.push_back(orphanHash); - assert(recentRejects); - recentRejects->insert(orphanHash); - } - mempool.check(pcoinsTip); - } - } - - BOOST_FOREACH(uint256 hash, vEraseQueue) - EraseOrphanTx(hash); -} - - bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived) { const CChainParams& chainparams = Params(); @@ -5212,6 +5070,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, else if (strCommand == "tx") { + vector vWorkQueue; + vector vEraseQueue; CTransaction tx; vRecv >> tx; @@ -5230,14 +5090,66 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, { mempool.check(pcoinsTip); RelayTransaction(tx); + vWorkQueue.push_back(inv.hash); LogPrint("mempool", "AcceptToMemoryPool: peer=%d %s: accepted %s (poolsz %u)\n", pfrom->id, pfrom->cleanSubVer, tx.GetHash().ToString(), mempool.mapTx.size()); - ProcessOrphanTransactions(inv.hash); + // Recursively process any orphan transactions that depended on this one + set setMisbehaving; + for (unsigned int i = 0; i < vWorkQueue.size(); i++) + { + map >::iterator itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue[i]); + if (itByPrev == mapOrphanTransactionsByPrev.end()) + continue; + for (set::iterator mi = itByPrev->second.begin(); + mi != itByPrev->second.end(); + ++mi) + { + const uint256& orphanHash = *mi; + const CTransaction& orphanTx = mapOrphanTransactions[orphanHash].tx; + NodeId fromPeer = mapOrphanTransactions[orphanHash].fromPeer; + bool fMissingInputs2 = false; + // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan + // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get + // anyone relaying LegitTxX banned) + CValidationState stateDummy; + + if (setMisbehaving.count(fromPeer)) + continue; + if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2)) + { + LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString()); + RelayTransaction(orphanTx); + vWorkQueue.push_back(orphanHash); + vEraseQueue.push_back(orphanHash); + } + else if (!fMissingInputs2) + { + int nDos = 0; + if (stateDummy.IsInvalid(nDos) && nDos > 0) + { + // Punish peer that gave us an invalid orphan tx + Misbehaving(fromPeer, nDos); + setMisbehaving.insert(fromPeer); + LogPrint("mempool", " invalid orphan tx %s\n", orphanHash.ToString()); + } + // Has inputs but not accepted to mempool + // Probably non-standard or insufficient fee/priority + LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString()); + vEraseQueue.push_back(orphanHash); + assert(recentRejects); + recentRejects->insert(orphanHash); + } + mempool.check(pcoinsTip); + } + } + + BOOST_FOREACH(uint256 hash, vEraseQueue) + EraseOrphanTx(hash); } // TODO: currently, prohibit joinsplits from entering mapOrphans else if (fMissingInputs && tx.vjoinsplit.size() == 0) diff --git a/src/main.h b/src/main.h index 061dcfcc5..bcdd04a5e 100644 --- a/src/main.h +++ b/src/main.h @@ -250,7 +250,7 @@ void PruneAndFlush(); /** (try to) add transaction to memory pool **/ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, - bool* pfMissingInputs, bool fRejectAbsurdFee=false, bool fAcceptReplacement=true); + bool* pfMissingInputs, bool fRejectAbsurdFee=false); struct CNodeStateStats { diff --git a/src/replacementpool.cpp b/src/replacementpool.cpp deleted file mode 100644 index 168c30b6e..000000000 --- a/src/replacementpool.cpp +++ /dev/null @@ -1,88 +0,0 @@ - -#include "main.h" -#include "replacementpool.h" -#include "sync.h" - - -CTxReplacementPool replacementPool; -CCriticalSection cs_replacementPool; - - -/* - * Validate the item - * - * Compare the item with any current replacement candidate - * - * Ensure that the item is not passed the replacement window - * - * Insert the item into the map - */ -CTxReplacementPoolResult CTxReplacementPool::replace(CTxReplacementPoolItem &item) -{ - LOCK(cs_replacementPool); - - // Replaceable transactions with multiple inputs are disabled - // until someone figures out how they would work. - if (item.tx.vin.size() > 1) return RP_Invalid; - - // replacementWindow of 0 goes direct to mempool - if (item.replacementWindow == 0) - { - // But we also need to remove replacement candidates - replaceMap.erase(item.tx.vin[0].prevout); - return RP_NoReplace; - } - - int startBlock = item.startBlock; - - auto it = replaceMap.find(item.tx.vin[0].prevout); - if (it != replaceMap.end()) - { - if (it->second.replacementWindow <= item.replacementWindow && - it->second.priority >= item.priority) { - return RP_HaveBetter; - } - startBlock = it->second.startBlock; - } - - // This transaction has higher priority - replaceMap[item.tx.vin[0].prevout] = item; - replaceMap[item.tx.vin[0].prevout].startBlock = startBlock; - return RP_Accept; -} - - -/* - * Remove and return any spends that have matured - */ -void CTxReplacementPool::removePending(int height, std::vector &txs) -{ - LOCK(cs_replacementPool); - - for (auto it = replaceMap.begin(); it != replaceMap.end(); /**/) { - CTxReplacementPoolItem &rep = it->second; - - if (rep.GetTargetBlock() <= height) { - txs.push_back(rep.tx); - replaceMap.erase(it++); - } else { - ++it; - } - } -} - - -/* - * O(n) lookup of tx by hash - */ -bool CTxReplacementPool::lookup(uint256 txHash, CTransaction &tx) -{ - LOCK(cs_replacementPool); - for (auto it = replaceMap.begin(); it != replaceMap.end(); it++) { - if (it->second.tx.GetHash() == txHash) { - tx = it->second.tx; - return true; - } - } - return false; -} diff --git a/src/replacementpool.h b/src/replacementpool.h deleted file mode 100644 index d6ab2ed28..000000000 --- a/src/replacementpool.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2018 The Komodo Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef KOMODO_REPLACEMENTCACHE_H -#define KOMODO_REPLACEMENTCACHE_H - -#include "primitives/transaction.h" - - -// My kingdom for a proper sum type... -enum CTxReplacementPoolResult { - RP_Accept, - RP_HaveBetter, - RP_Invalid, - RP_NoReplace -}; - - -class CTxReplacementPoolItem -{ -public: - CTransaction tx; - int startBlock; - uint64_t priority; - uint32_t replacementWindow; - - CTxReplacementPoolItem() {} - - CTxReplacementPoolItem(const CTransaction &_tx, int _startBlock) { - tx = _tx; - startBlock = _startBlock; - priority = 0; - replacementWindow = 0; - } - - int GetTargetBlock() { return startBlock + replacementWindow; } -}; - -/** - * CTxReplacementPool stores transactions that are valid but held for - * period of time during which they may be replaced. - * - * Transactions are added when they are found to be valid but not added - * to the mempool until a timeout. - * - * Replacement pool is like another mempool before the main mempool. - * - * Transactions in the replacement pool are indexed by the output - * that they are spending. Once a replaceable transaction tries to - * spend an output, a countdown of blocks begins at the current block - * plus a window that is set by "userland" code. If another, better - * transaction replaces the spend that's already pending, the countdown - * start block remains the same. - */ -class CTxReplacementPool -{ -private: - /* Index of spends that may be replaced */ - std::map replaceMap; -public: - /* Try to replace a transaction in the index */ - CTxReplacementPoolResult replace(CTxReplacementPoolItem &item); - - /* Remove and return all transactions up to a given block height */ - void removePending(int height, std::vector &txs); - - /* Find a transaction in the index by it's hash. */ - bool lookup(uint256 txHash, CTransaction &tx); -}; - - -/* Global instance */ -extern CTxReplacementPool replacementPool; - -#endif // KOMODO_REPLACEMENTCACHE_H diff --git a/src/secp256k1/include/secp256k1.h b/src/secp256k1/include/secp256k1.h index 06afd4c65..9db5cf3a8 100644 --- a/src/secp256k1/include/secp256k1.h +++ b/src/secp256k1/include/secp256k1.h @@ -95,6 +95,18 @@ SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_verify( int pubkeylen ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); +/** Check that signature is in canonical form + * Returns: 1: In canonical form + * 0: Non canonical + * -1: invalid signature + * In: sig: the signature being verified (cannot be NULL) + * siglen: the length of the signature + */ +SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_check_canonical_sig( + const unsigned char *sig, + int siglen +) SECP256K1_ARG_NONNULL(1); + /** A pointer to a function to deterministically generate a nonce. * Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail. * In: msg32: the 32-byte message hash being verified (will not be NULL) diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c index d6192dc4e..48569ad9e 100644 --- a/src/secp256k1/src/secp256k1.c +++ b/src/secp256k1/src/secp256k1.c @@ -85,6 +85,14 @@ int secp256k1_ecdsa_verify(const secp256k1_context_t* ctx, const unsigned char * return ret; } + +int secp256k1_ecdsa_check_canonical_sig(const unsigned char *sig, int siglen) { + secp256k1_ecdsa_sig_t s; + if (!secp256k1_ecdsa_sig_parse(&s, sig, siglen)) return -1; + return !secp256k1_scalar_is_high(&s.s); +} + + static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, unsigned int counter, const void *data) { secp256k1_rfc6979_hmac_sha256_t rng; unsigned int i; diff --git a/src/test-komodo/main.cpp b/src/test-komodo/main.cpp deleted file mode 100644 index 7811d5f9a..000000000 --- a/src/test-komodo/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "gtest/gtest.h" -#include "crypto/common.h" - -int main(int argc, char **argv) { - assert(init_and_check_sodium() != -1); - - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/test-komodo/test_replacementpool.cpp b/src/test-komodo/test_replacementpool.cpp deleted file mode 100644 index b0ccc4278..000000000 --- a/src/test-komodo/test_replacementpool.cpp +++ /dev/null @@ -1,440 +0,0 @@ -#include -#include - -#include "base58.h" -#include "core_io.h" -#include "key.h" -#include "komodo_cryptoconditions.h" -#include "main.h" -#include "miner.h" -#include "random.h" -#include "rpcserver.h" -#include "rpcprotocol.h" -#include "replacementpool.h" -#include "txdb.h" -#include "util.h" -#include "utilstrencodings.h" -#include "utiltime.h" -#include "consensus/validation.h" -#include "primitives/transaction.h" -#include "script/interpreter.h" -#include "cryptoconditions/include/cryptoconditions.h" - - -extern int32_t USE_EXTERNAL_PUBKEY; -extern std::string NOTARY_PUBKEY; -std::string _NOTARY_PUBKEY = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47"; -std::string NOTARY_SECRET = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU"; -CKey notaryKey; - -#define VCH(a,b) std::vector(a, a + b) - - -/* - * We need to have control of clock, - * otherwise block production can fail. - */ -int64_t nMockTime; - - -void testSetup() -{ - SelectParams(CBaseChainParams::REGTEST); - - // enable CC - ASSETCHAINS_CC = 1; - ASSETCHAINS_CC_TEST = true; - - // Settings to get block reward - NOTARY_PUBKEY = _NOTARY_PUBKEY; - USE_EXTERNAL_PUBKEY = 1; - mapArgs["-mineraddress"] = "bogus"; - COINBASE_MATURITY = 1; - // Global mock time - nMockTime = GetTime(); - - // Init blockchain - ClearDatadirCache(); - auto pathTemp = GetTempPath() / strprintf("test_komodo_%li_%i", GetTime(), GetRand(100000)); - boost::filesystem::create_directories(pathTemp); - mapArgs["-datadir"] = pathTemp.string(); - pblocktree = new CBlockTreeDB(1 << 20, true); - CCoinsViewDB *pcoinsdbview = new CCoinsViewDB(1 << 23, true); - pcoinsTip = new CCoinsViewCache(pcoinsdbview); - InitBlockIndex(); - - // Set address - ECC_Start(); - - // Notary key - CBitcoinSecret vchSecret; - // this returns false due to network prefix mismatch but works anyway - vchSecret.SetString(NOTARY_SECRET); - notaryKey = vchSecret.GetKey(); -} - - -void generateBlock(CBlock *block=NULL) -{ - UniValue params; - params.setArray(); - params.push_back(1); - uint256 blockId; - - SetMockTime(nMockTime++); // CreateNewBlock can fail if not enough time passes - - try { - UniValue out = generate(params, false); - blockId.SetHex(out[0].getValStr()); - } catch (const UniValue& e) { - FAIL() << "failed to create block: " << e.write().data(); - } - if (block) ASSERT_TRUE(ReadBlockFromDisk(*block, mapBlockIndex[blockId])); -} - - -void acceptTx(const CTransaction tx) -{ - CValidationState state; - LOCK(cs_main); - if (!AcceptToMemoryPool(mempool, state, tx, false, NULL)) - FAIL() << state.GetRejectReason(); -} - - -static CMutableTransaction spendTx(const CTransaction &txIn, int nOut=0) -{ - CMutableTransaction mtx; - mtx.vin.resize(1); - mtx.vin[0].prevout.hash = txIn.GetHash(); - mtx.vin[0].prevout.n = nOut; - mtx.vout.resize(1); - mtx.vout[0].nValue = txIn.vout[nOut].nValue - 1000; - return mtx; -} - - -/* - * In order to do tests there needs to be inputs to spend. - * This method creates a block and returns a transaction that spends the coinbase. - */ -void getInputTx(CScript scriptPubKey, CTransaction &txIn) -{ - // Get coinbase - CBlock block; - generateBlock(&block); - CTransaction coinbase = block.vtx[0]; - - // Create tx - auto mtx = spendTx(coinbase); - mtx.vout[0].scriptPubKey = scriptPubKey; - uint256 hash = SignatureHash(coinbase.vout[0].scriptPubKey, mtx, 0, SIGHASH_ALL); - std::vector vchSig; - notaryKey.Sign(hash, vchSig); - vchSig.push_back((unsigned char)SIGHASH_ALL); - mtx.vin[0].scriptSig << vchSig; - - // Accept - acceptTx(mtx); - txIn = CTransaction(mtx); -} - - -std::string HexToB64(std::string hexStr) -{ - auto d = ParseHex(hexStr); - return EncodeBase64(d.data(), d.size()); -} - - -CC *getReplaceCond() -{ - const char *condJsonTpl = R"V0G0N( - { "type": "threshold-sha-256", - "threshold": 2, - "subfulfillments": [ - { "type": "secp256k1-sha-256", "publicKey": "%s"}, - { "type": "eval-sha-256", "method": "testReplace", "params": "" } - ] })V0G0N"; - char condJson[1000]; - sprintf(condJson, condJsonTpl, (char*)HexToB64(NOTARY_PUBKEY).data()); - - unsigned char err[1000] = "\0"; - return cc_conditionFromJSONString((const unsigned char*)condJson, err); - // above could fail -} - -CScript condPK(CC *cond) -{ - unsigned char buf[1000]; - size_t bufLen = cc_conditionBinary(cond, buf); - return CScript() << VCH(buf, bufLen) << OP_CHECKCRYPTOCONDITION; -} - -void setFulfillment(CMutableTransaction &mtx, CC *cond, const CScript &spk, int nIn=0) -{ - uint256 hash = SignatureHash(spk, mtx, nIn, SIGHASH_ALL); - int nSigned = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), hash.begin()); - unsigned char buf[1000]; - size_t bufLen = cc_fulfillmentBinary(cond, buf, 1000); - mtx.vin[nIn].scriptSig = CScript() << VCH(buf, bufLen); -} - - -CScript getReplaceOut(unsigned char replacementWindow, unsigned char priority) -{ - std::vector v = {replacementWindow, priority}; - return CScript() << OP_RETURN << v; -} - - -CTransaction _txout; -#define ONLY_REPLACEMENT_POOL(hash) ASSERT_TRUE(replacementPool.lookup(hash, _txout)); \ - ASSERT_FALSE(mempool.lookup(hash, _txout)); -#define ONLY_MEM_POOL(hash) ASSERT_FALSE(replacementPool.lookup(hash, _txout)); \ - ASSERT_TRUE(mempool.lookup(hash, _txout)); - - - -// Setup environment and perform basic spend as test -TEST(replacementpool, 0_setup) -{ - testSetup(); // Only call this method here - - CTransaction txIn; - getInputTx(CScript() << OP_RETURN << VCH("1", 1), txIn); -} - - -// Perform replaceable spend -TEST(replacementpool, basic) -{ - CTransaction txIn; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - - // Spend output to replaceable - auto mtx = spendTx(txIn); - mtx.vout[0].scriptPubKey = getReplaceOut(2, 100); - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - - acceptTx(mtx); - - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - generateBlock(); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - generateBlock(); - ONLY_MEM_POOL(mtx.GetHash()); -} - - -/* - * replacementWindow is 0, transaction should go direct to mempool - */ -TEST(replacementpool, noWindow) -{ - CTransaction txIn; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - - // First set a tx with a 1 block wait. It should stay in the replacement pool. - auto mtx = spendTx(txIn); - mtx.vout[0].scriptPubKey = getReplaceOut(1, 100); - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - - // Now set a transaction with a 0 block wait and higher priority. - // It should go direct to the mem pool. - auto mtx2 = spendTx(txIn); - mtx2.vout[0].scriptPubKey = getReplaceOut(0, 101); - setFulfillment(mtx2, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx2); - ONLY_MEM_POOL(mtx2.GetHash()); - - // Additionally, there should be no replacement remaining for txIn in the mempool - ASSERT_FALSE(replacementPool.lookup(mtx.GetHash(), _txout)); -} - - -/* - * Multiple replaceable transactions dont interfere - */ -TEST(replacementpool, noInterfere) -{ - CTransaction txIn1, txIn2; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn1); - getInputTx(condPK(cond), txIn2); - - // First set a transaction with a low window - auto mtx = spendTx(txIn1); - mtx.vout[0].scriptPubKey = getReplaceOut(1, 100); - setFulfillment(mtx, cond, txIn1.vout[0].scriptPubKey); - acceptTx(mtx); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - - // Now, a different spend with a higher window - auto mtx2 = spendTx(txIn2); - mtx2.vout[0].scriptPubKey = getReplaceOut(10, 100); - setFulfillment(mtx2, cond, txIn2.vout[0].scriptPubKey); - acceptTx(mtx2); - ONLY_REPLACEMENT_POOL(mtx2.GetHash()); - - generateBlock(); - - // mtx has gone to mempool - ONLY_MEM_POOL(mtx.GetHash()); - - // mtx2 still in replacementpool - ONLY_REPLACEMENT_POOL(mtx2.GetHash()); - - // But 9 blocks later... - for (int i=0; i<9; i++) - { - generateBlock(); - ASSERT_EQ(i == 8, mempool.lookup(mtx2.GetHash(), _txout)); - } -} - - -/* - * Multiple inputs is invalid - */ -TEST(replacementpool, invalidMultipleInputs) -{ - LOCK(cs_main); - - CTransaction txIn, txIn2; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - getInputTx(condPK(cond), txIn2); - - CMutableTransaction mtx; - mtx.vout.resize(1); - mtx.vout[0].nValue = txIn.vout[0].nValue * 2 - 1000; - mtx.vout[0].scriptPubKey = getReplaceOut(1, 100); - mtx.vin.resize(2); - mtx.vin[0].prevout.hash = txIn.GetHash(); - mtx.vin[0].prevout.n = 0; - mtx.vin[1].prevout.hash = txIn2.GetHash(); - mtx.vin[1].prevout.n = 0; - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - setFulfillment(mtx, cond, txIn2.vout[0].scriptPubKey, 1); - - CValidationState state; - ASSERT_FALSE(AcceptToMemoryPool(mempool, state, mtx, false, NULL)); - ASSERT_EQ("replacement-invalid", state.GetRejectReason()); -} - - -extern bool AddOrphanTx(const CTransaction& tx, NodeId peer); -extern void ProcessOrphanTransactions(uint256 parentHash); -struct COrphanTx { CTransaction tx; NodeId fromPeer; }; -extern std::map mapOrphanTransactions; - -/* - * Orphans are processed - */ -TEST(replacementpool, orphansAreProcessed) -{ - LOCK(cs_main); - - CTransaction txIn; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - - // Make basic replaceable spend and dont submit - auto mtx = spendTx(txIn); - mtx.vout.resize(2); - mtx.vout[0].nValue = txIn.vout[0].nValue - 1000; - mtx.vout[0].scriptPubKey = condPK(cond); - mtx.vout[1].nValue = 1; - mtx.vout[1].scriptPubKey = getReplaceOut(1, 100); - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - - // Make an orphan and add it - auto orphan = spendTx(mtx); - orphan.vout[0].scriptPubKey = getReplaceOut(0, 100); - setFulfillment(orphan, cond, mtx.vout[0].scriptPubKey); - ASSERT_TRUE(AddOrphanTx(orphan, 1001)); - ASSERT_EQ(1, mapOrphanTransactions.count(orphan.GetHash())); - - // parent goes into replacement pool - acceptTx(mtx); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - - // this should not result in the movement of any orphans - ProcessOrphanTransactions(mtx.GetHash()); - ASSERT_EQ(1, mapOrphanTransactions.count(orphan.GetHash())); - - // Processing of parent transaction also un-orphanises orphan - generateBlock(); - ONLY_MEM_POOL(mtx.GetHash()); - ONLY_MEM_POOL(orphan.GetHash()); - ASSERT_EQ(0, mapOrphanTransactions.count(orphan.GetHash())); -} - - -/* - * Add transaction with lower priority, already have better - */ -TEST(replacementpool, haveBetter) -{ - LOCK(cs_main); - - CTransaction txIn; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - - // A replaceable tx. - auto mtx = spendTx(txIn); - mtx.vout[0].scriptPubKey = getReplaceOut(2, 100); - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - - // Another one, but not as good. - auto mtx2 = spendTx(txIn); - mtx2.vout[0].scriptPubKey = getReplaceOut(2, 99); - setFulfillment(mtx2, cond, txIn.vout[0].scriptPubKey); - CValidationState state; - ASSERT_FALSE(AcceptToMemoryPool(mempool, state, mtx, false, NULL)); - ASSERT_EQ("replacement-is-worse", state.GetRejectReason()); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); -} - - -/* - * Add transaction with lower priority, but shorter replacementWindow - */ -TEST(replacementpool, shorterReplacementWindow) -{ - LOCK(cs_main); - - CTransaction txIn; - CC *cond = getReplaceCond(); - getInputTx(condPK(cond), txIn); - - // A replaceable tx. - auto mtx = spendTx(txIn); - mtx.vout[0].scriptPubKey = getReplaceOut(2, 100); - setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx); - ONLY_REPLACEMENT_POOL(mtx.GetHash()); - - // Another one, lower priority but shorter replacementWindow so wins. - auto mtx2 = spendTx(txIn); - mtx2.vout[0].scriptPubKey = getReplaceOut(1, 99); - setFulfillment(mtx2, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx2); - ONLY_REPLACEMENT_POOL(mtx2.GetHash()); - ASSERT_FALSE(replacementPool.lookup(mtx.GetHash(), _txout)); - - // Shorter still, in fact direct to mem pool - auto mtx3 = spendTx(txIn); - mtx3.vout[0].scriptPubKey = getReplaceOut(0, 98); - setFulfillment(mtx3, cond, txIn.vout[0].scriptPubKey); - acceptTx(mtx3); - ONLY_MEM_POOL(mtx3.GetHash()); -}