Browse Source

test suite for replacementPool

metaverse
Scott Sadler 6 years ago
parent
commit
a8acafb354
  1. 1
      .gitignore
  2. 5
      src/Makefile.am
  3. 16
      src/Makefile.ktest.include
  4. 2
      src/cryptoconditions
  5. 41
      src/komodo_cryptoconditions.cpp
  6. 1
      src/komodo_cryptoconditions.h
  7. 80
      src/main.cpp
  8. 44
      src/replacementpool.cpp
  9. 9
      src/replacementpool.h
  10. 5
      src/rpcmining.cpp
  11. 9
      src/test-komodo/main.cpp
  12. 461
      src/test-komodo/test_replacementpool.cpp

1
.gitignore

@ -119,3 +119,4 @@ src/rpcmisc~.cpp
src/komodo-cli
src/komodod
src/komodo-tx
src/komodo-test

5
src/Makefile.am

@ -562,9 +562,10 @@ endif
@test -f $(PROTOC)
$(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(abspath $(<D) $<)
#if ENABLE_TESTS
if ENABLE_TESTS
include Makefile.ktest.include
#include Makefile.test.include
#include Makefile.gtest.include
#endif
endif
include Makefile.zcash.include

16
src/Makefile.ktest.include

@ -0,0 +1,16 @@
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

2
src/cryptoconditions

@ -1 +1 @@
Subproject commit f606567ce30780c28880094718b5b27c5c426827
Subproject commit 3200bc78e82fe7af54c5b3bc89029070349ed430

41
src/komodo_cryptoconditions.cpp

@ -4,7 +4,7 @@
#include "cryptoconditions/include/cryptoconditions.h"
#define REPLACEMENT_WINDOW_BLOCKS 2
bool ASSETCHAINS_CC_TEST = false;
/*
@ -12,12 +12,16 @@
*/
bool EvalConditionValidity(const CC *cond, const CTransaction *txTo)
{
if (strcmp(cond->method, "testEval") == 0) {
return cond->paramsBinLength == 8 &&
memcmp(cond->paramsBin, "testEval", 8) == 0;
}
if (strcmp(cond->method, "testReplace") == 0) {
return true;
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;
}
}
fprintf(stderr, "no defined behaviour for method: %s\n", cond->method);
return 0;
@ -31,16 +35,22 @@ bool EvalConditionValidity(const CC *cond, const CTransaction *txTo)
* 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.
* 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 (strcmp((const char*)cond->method, "testReplace") == 0) {
std::vector<unsigned char> data;
if (GetOpReturnData(rep->tx.vout[0].scriptPubKey, data)) {
rep->priority = (uint64_t) data[0];
rep->replacementWindow = REPLACEMENT_WINDOW_BLOCKS;
return true;
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;
@ -58,7 +68,7 @@ int visitConditionPriority(CC *cond, struct CCVisitor visitor)
/*
* Try to get replacement parameters from a transaction in &rep.
*/
bool PutReplacementParams(CTxReplacementPoolItem &rep)
bool SetReplacementParams(CTxReplacementPoolItem &rep)
{
// first, see if we have a cryptocondition
const CScript &sig = rep.tx.vin[0].scriptSig;
@ -68,7 +78,6 @@ bool PutReplacementParams(CTxReplacementPoolItem &rep)
opcodetype opcode;
if (!sig.GetOp(pc, opcode, data)) return false;
CC *cond = cc_readFulfillmentBinary((unsigned char*)data.data(), data.size());
auto wat = {1, ""};
if (!cond) return false;
// now, see if it has a replacement node

1
src/komodo_cryptoconditions.h

@ -7,6 +7,7 @@
extern int32_t ASSETCHAINS_CC;
extern bool ASSETCHAINS_CC_TEST;
static bool IsCryptoConditionsEnabled() {
return 0 != ASSETCHAINS_CC;

80
src/main.cpp

@ -1110,6 +1110,45 @@ 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)
{
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_InvalidZeroPriority:
// Not valid according to replaceability rules
fprintf(stderr,"accept failure.21\n");
return state.Invalid(error("AcceptToMemoryPool: Replacement has 0 priority"),
REJECT_INVALID, "replacement-invalid-zero-priority");
case RP_InvalidStructure:
// Not valid according to replaceability rules
fprintf(stderr,"accept failure.22\n");
return state.Invalid(error("AcceptToMemoryPool: Replacement has multiple inputs"),
REJECT_INVALID, "replacement-has-invalid-structure");
default:
return false;
}
}
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee, bool fAcceptReplacement)
{
AssertLockHeld(cs_main);
@ -1181,8 +1220,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
}
}
CTxReplacementPoolResult rpr = RP_NotReplaceable;
{
CCoinsView dummy;
CCoinsViewCache view(&dummy);
@ -1341,40 +1378,15 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (fAcceptReplacement)
{
CTxReplacementPoolItem item(tx, GetHeight());
if (SetReplacementParams(item)) {
rpr = replacementPool.replace(item);
}
if (AcceptToReplacementPool(tx, state)) return true;
if (state.IsInvalid()) return false;
}
if (rpr == RP_HaveBetter)
{
// already have a better one
fprintf(stderr,"accept failure.20\n");
return state.DoS(0, error("AcceptToMemoryPool: Replacement is worse"), REJECT_HAVEBETTER, "replacement-is-worse");
}
if (rpr == RP_Invalid)
{
// Not valid according to replaceability rules
fprintf(stderr,"accept failure.21\n");
return state.DoS(0, error("AcceptToMemoryPool: Replacement is invalid"), REJECT_INVALID, "replacement-is-worse");
}
// If there is no replacement action happening...
if (rpr == RP_NotReplaceable)
{
// Store transaction in memory
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
}
}
// in order for replaceable transactions to sync with wallet, replacementpool should advise
// wallet of transaction eviction
if (rpr == RP_NotReplaceable) {
SyncWithWallets(tx, NULL);
// Store transaction in memory
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
}
SyncWithWallets(tx, NULL);
return true;
}
@ -1989,7 +2001,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())));
}
}
}
@ -2826,7 +2838,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
// Update chainActive & related variables.
UpdateTip(pindexNew);
// Process pending replacements
ProcessReplacementPool(pindexNew->nHeight-1);
ProcessReplacementPool(pindexNew->nHeight);
// Tell wallet about transactions that went from mempool
// to conflicted:
BOOST_FOREACH(const CTransaction &tx, txConflicted) {

44
src/replacementpool.cpp

@ -1,9 +1,11 @@
#include "main.h"
#include "replacementpool.h"
#include "sync.h"
CTxReplacementPool replacementPool;
CCriticalSection cs_replacementPool;
/*
@ -17,33 +19,39 @@ CTxReplacementPool replacementPool;
*/
CTxReplacementPoolResult CTxReplacementPool::replace(CTxReplacementPoolItem &item)
{
AssertLockHeld(cs_main);
LOCK(cs_replacementPool);
// Perform some validations.
if (item.tx.vin.size() > 1) {
// Replaceable transactions with multiple inputs are disabled.
// It seems like quite alot of additional complexity.
return RP_Invalid;
}
// Replaceable transactions with multiple inputs are disabled
// until someone figures out how they would work.
if (item.tx.vin.size() > 1) return RP_InvalidStructure;
// A transaction with 0 priority is not valid.
if (item.priority == 0) {
return RP_Invalid;
if (item.priority == 0) return RP_InvalidZeroPriority;
// 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;
}
auto it = replaceMap.find(item.tx.vin[0].prevout);
int startBlock = item.startBlock;
if (it != replaceMap.end()) {
if (it->second.priority >= item.priority) {
return RP_HaveBetter; // (ThanksThough)
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 = it->second.startBlock;
return RP_Accepted;
replaceMap[item.tx.vin[0].prevout].startBlock = startBlock;
return RP_Accept;
}
@ -52,10 +60,11 @@ CTxReplacementPoolResult CTxReplacementPool::replace(CTxReplacementPoolItem &ite
*/
void CTxReplacementPool::removePending(int height, std::vector<CTransaction> &txs)
{
AssertLockHeld(cs_main);
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++);
@ -71,6 +80,7 @@ void CTxReplacementPool::removePending(int height, std::vector<CTransaction> &tx
*/
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;

9
src/replacementpool.h

@ -8,7 +8,14 @@
#include "primitives/transaction.h"
enum CTxReplacementPoolResult { RP_Accepted, RP_HaveBetter, RP_Invalid, RP_NotReplaceable };
// My kingdom for a proper sum type...
enum CTxReplacementPoolResult {
RP_Accept,
RP_HaveBetter,
RP_InvalidZeroPriority,
RP_InvalidStructure,
RP_NoReplace
};
class CTxReplacementPoolItem

5
src/rpcmining.cpp

@ -206,8 +206,13 @@ UniValue generate(const UniValue& params, bool fHelp)
UniValue blockHashes(UniValue::VARR);
unsigned int n = Params().EquihashN();
unsigned int k = Params().EquihashK();
uint64_t lastTime = 0;
while (nHeight < nHeightEnd)
{
// Validation may fail if block generation is too fast
if (GetTime() == lastTime) MilliSleep(1001);
lastTime = GetTime();
#ifdef ENABLE_WALLET
std::unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey));
#else

9
src/test-komodo/main.cpp

@ -0,0 +1,9 @@
#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();
}

461
src/test-komodo/test_replacementpool.cpp

@ -0,0 +1,461 @@
#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 ASSERT_REPLACEMENT_POOL(hash) ASSERT_TRUE(replacementPool.lookup(hash, _txout)); \
ASSERT_FALSE(mempool.lookup(hash, _txout));
#define ASSERT_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, 1_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);
ASSERT_REPLACEMENT_POOL(mtx.GetHash());
generateBlock();
ASSERT_REPLACEMENT_POOL(mtx.GetHash());
generateBlock();
ASSERT_MEM_POOL(mtx.GetHash());
}
/*
* replacementWindow is 0, transaction should go direct to mempool
*/
TEST(replacementpool, 2_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);
ASSERT_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);
ASSERT_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, 3_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);
ASSERT_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);
ASSERT_REPLACEMENT_POOL(mtx2.GetHash());
generateBlock();
// mtx has gone to mempool
ASSERT_MEM_POOL(mtx.GetHash());
// mtx2 still in replacementpool
ASSERT_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));
}
}
/*
* 0 priority is invalid
*/
TEST(replacementpool, 4_invalidZeroPriority)
{
LOCK(cs_main);
CTransaction txIn;
CC *cond = getReplaceCond();
getInputTx(condPK(cond), txIn);
auto mtx = spendTx(txIn);
mtx.vout[0].scriptPubKey = getReplaceOut(1, 0);
setFulfillment(mtx, cond, txIn.vout[0].scriptPubKey);
CValidationState state;
ASSERT_FALSE(AcceptToMemoryPool(mempool, state, mtx, false, NULL));
ASSERT_EQ("replacement-invalid-zero-priority", state.GetRejectReason());
}
/*
* Multiple inputs is invalid
*/
TEST(replacementpool, 5_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-has-invalid-structure", 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, 6_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);
ASSERT_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();
ASSERT_MEM_POOL(mtx.GetHash());
ASSERT_MEM_POOL(orphan.GetHash());
ASSERT_EQ(0, mapOrphanTransactions.count(orphan.GetHash()));
}
/*
* Add transaction with lower priority, already have better
*/
TEST(replacementpool, 7_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);
ASSERT_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());
ASSERT_REPLACEMENT_POOL(mtx.GetHash());
}
/*
* Add transaction with lower priority, but shorter replacementWindow
*/
TEST(replacementpool, 8_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);
ASSERT_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);
ASSERT_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);
ASSERT_MEM_POOL(mtx3.GetHash());
}
Loading…
Cancel
Save