Browse Source

remove replacementpool

pull/4/head
Scott Sadler 6 years ago
parent
commit
51aad18733
  1. 86
      qa/cryptoconditions/test_integration.py
  2. 10
      qa/cryptoconditions/testsupport.py
  3. 9
      src/Makefile.am
  4. 16
      src/Makefile.ktest.include
  5. 16
      src/cryptoconditions/Makefile.am
  6. 1
      src/cryptoconditions/include/cryptoconditions.h
  7. 79
      src/komodo_cryptoconditions.cpp
  8. 7
      src/komodo_cryptoconditions.h
  9. 206
      src/main.cpp
  10. 2
      src/main.h
  11. 88
      src/replacementpool.cpp
  12. 76
      src/replacementpool.h
  13. 12
      src/secp256k1/include/secp256k1.h
  14. 8
      src/secp256k1/src/secp256k1.c
  15. 9
      src/test-komodo/main.cpp
  16. 440
      src/test-komodo/test_replacementpool.cpp

86
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():

10
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):

9
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 $(<D) $<)
if ENABLE_TESTS
include Makefile.ktest.include
#if ENABLE_TESTS
#include Makefile.test.include
#include Makefile.gtest.include
endif
#endif
include Makefile.zcash.include

16
src/Makefile.ktest.include

@ -1,16 +0,0 @@
TESTS += komodo-test
bin_PROGRAMS += komodo-test
# tool for generating our public parameters
komodo_test_SOURCES = \
test-komodo/main.cpp \
test-komodo/test_replacementpool.cpp
komodo_test_CPPFLAGS = $(komodod_CPPFLAGS)
komodo_test_LDADD = -lgtest -lgmock $(komodod_LDADD)
komodo_test_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static

16
src/cryptoconditions/Makefile.am

@ -1,16 +1,26 @@
lib_LTLIBRARIES=libcryptoconditions.la
noinst_LTLIBRARIES=cryptoconditions_core.a
SUBDIRS = src/include/secp256k1
# Have a separate build target for cryptoconditions that does not contain secp256k1
libcryptoconditions_la_SOURCES =
libcryptoconditions_la_LIBADD = $(CRYPTOCONDITIONS_CORE) $(LIBSECP256K1)
AM_CFLAGS = -I$(top_srcdir)/src/asn -I$(top_srcdir)/include -I$(top_srcdir)/src/include \
-Wall -Wno-pointer-sign -Wno-discarded-qualifiers
LIBSECP256K1=src/include/secp256k1/libsecp256k1.la
$(LIBSECP256K1): $(wildcard src/secp256k1/*)
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
libcryptoconditions_la_LIBADD = $(LIBSECP256K1)
libcryptoconditions_la_SOURCES = \
CRYPTOCONDITIONS_CORE=cryptoconditions_core.a
# libcryptoconditions_la_SOURCES = \
cryptoconditions_core_a_CFLAGS = -I$(top_srcdir)/src/asn -I$(top_srcdir)/include -I$(top_srcdir)/src/include \
-Wall -Wno-pointer-sign -Wno-discarded-qualifiers
cryptoconditions_core_a_SOURCES = \
src/cryptoconditions.c \
src/include/cJSON.c \
src/include/sha256.c \

1
src/cryptoconditions/include/cryptoconditions.h

@ -1,4 +1,5 @@
#include <cJSON.h>
#include <stdint.h>
#ifndef CRYPTOCONDITIONS_H

79
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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> &data)
{
auto pc = sig.begin();

7
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<unsigned char> &data);
#endif /* KOMODO_CRYPTOCONDITIONS_H */

206
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<CTransaction> 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<uint256> vWorkQueue, vEraseQueue;
// Recursively process any orphan transactions that depended on this one
vWorkQueue.push_back(parentHash);
set<NodeId> setMisbehaving;
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{
map<uint256, set<uint256> >::iterator itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue[i]);
if (itByPrev == mapOrphanTransactionsByPrev.end())
continue;
for (set<uint256>::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<uint256> vWorkQueue;
vector<uint256> 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<NodeId> setMisbehaving;
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{
map<uint256, set<uint256> >::iterator itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue[i]);
if (itByPrev == mapOrphanTransactionsByPrev.end())
continue;
for (set<uint256>::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)

2
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 {

88
src/replacementpool.cpp

@ -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<CTransaction> &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;
}

76
src/replacementpool.h

@ -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<COutPoint, CTxReplacementPoolItem> 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<CTransaction> &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

12
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)

8
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;

9
src/test-komodo/main.cpp

@ -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();
}

440
src/test-komodo/test_replacementpool.cpp

@ -1,440 +0,0 @@
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#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<unsigned char>(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<unsigned char> 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<unsigned char> 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<uint256, COrphanTx> 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());
}
Loading…
Cancel
Save