remove replacementpool
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,4 +1,5 @@
|
||||
#include <cJSON.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#ifndef CRYPTOCONDITIONS_H
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user