basic implementation of transaction replacement. requires cleanup and testing
This commit is contained in:
@@ -4,6 +4,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import binascii
|
import binascii
|
||||||
import struct
|
import struct
|
||||||
|
import base64
|
||||||
from testsupport import *
|
from testsupport import *
|
||||||
|
|
||||||
|
|
||||||
@@ -91,20 +92,19 @@ def test_oversize_fulfillment(inp):
|
|||||||
|
|
||||||
|
|
||||||
@fanout_input(6)
|
@fanout_input(6)
|
||||||
def test_aux_basic(inp):
|
def test_eval_basic(inp):
|
||||||
aux_cond = {
|
eval_cond = {
|
||||||
'type': 'aux-sha-256',
|
'type': 'eval-sha-256',
|
||||||
'method': 'equals',
|
'method': 'testEval',
|
||||||
'conditionAux': 'LTE',
|
'params': encode_base64('testEval')
|
||||||
'fulfillmentAux': 'LTE'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setup some aux outputs
|
# Setup some eval outputs
|
||||||
spend0 = {
|
spend0 = {
|
||||||
'inputs': [inp],
|
'inputs': [inp],
|
||||||
'outputs': [
|
'outputs': [
|
||||||
{'amount': 500, 'script': {'condition': aux_cond}},
|
{'amount': 500, 'script': {'condition': eval_cond}},
|
||||||
{'amount': 500, 'script': {'condition': aux_cond}}
|
{'amount': 500, 'script': {'condition': eval_cond}}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
spend0_txid = submit(sign(spend0))
|
spend0_txid = submit(sign(spend0))
|
||||||
@@ -112,17 +112,17 @@ def test_aux_basic(inp):
|
|||||||
|
|
||||||
# Test a good fulfillment
|
# Test a good fulfillment
|
||||||
spend1 = {
|
spend1 = {
|
||||||
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}],
|
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': eval_cond}}],
|
||||||
'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}]
|
'outputs': [{'amount': 500, 'script': {'condition': eval_cond}}]
|
||||||
}
|
}
|
||||||
spend1_txid = submit(sign(spend1))
|
spend1_txid = submit(sign(spend1))
|
||||||
assert rpc.getrawtransaction(spend1_txid)
|
assert rpc.getrawtransaction(spend1_txid)
|
||||||
|
|
||||||
# Test a bad fulfillment
|
# Test a bad fulfillment
|
||||||
aux_cond['fulfillmentAux'] = 'WYW'
|
eval_cond['params'] = ''
|
||||||
spend2 = {
|
spend2 = {
|
||||||
'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}],
|
'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': eval_cond}}],
|
||||||
'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}]
|
'outputs': [{'amount': 500, 'script': {'condition': eval_cond}}]
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
assert not submit(sign(spend2)), 'should raise an error'
|
assert not submit(sign(spend2)), 'should raise an error'
|
||||||
@@ -131,50 +131,6 @@ def test_aux_basic(inp):
|
|||||||
|
|
||||||
|
|
||||||
@fanout_input(7)
|
@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):
|
def test_secp256k1_condition(inp):
|
||||||
ec_cond = {
|
ec_cond = {
|
||||||
'type': 'secp256k1-sha-256',
|
'type': 'secp256k1-sha-256',
|
||||||
@@ -214,6 +170,60 @@ def test_secp256k1_condition(inp):
|
|||||||
assert SCRIPT_FALSE in str(e), str(e)
|
assert SCRIPT_FALSE in str(e), str(e)
|
||||||
|
|
||||||
|
|
||||||
|
@fanout_input(20)
|
||||||
|
def test_eval_replacement(inp):
|
||||||
|
eval_cond = {
|
||||||
|
'type': 'eval-sha-256',
|
||||||
|
'method': 'testReplace',
|
||||||
|
'params': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup replaceable output
|
||||||
|
spend0 = {
|
||||||
|
'inputs': [inp],
|
||||||
|
'outputs': [
|
||||||
|
{'amount': 1000, 'script': {'condition': eval_cond}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
spend0_txid = submit(sign(spend0))
|
||||||
|
assert rpc.getrawtransaction(spend0_txid)
|
||||||
|
|
||||||
|
b64_1 = 'AQ=='
|
||||||
|
spend1 = {
|
||||||
|
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': eval_cond}}],
|
||||||
|
'outputs': [{'amount': 1000, 'script': {'op_return': b64_1}}]
|
||||||
|
}
|
||||||
|
|
||||||
|
b64_2 = 'Ag=='
|
||||||
|
spend2 = {
|
||||||
|
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': eval_cond}}],
|
||||||
|
'outputs': [{'amount': 1000, 'script': {'op_return': b64_2}}]
|
||||||
|
}
|
||||||
|
|
||||||
|
# If spend2 is already registered, return true, as this test has already been performed
|
||||||
|
spend2_txid = hoek.encodeTx(sign(spend2))['txid']
|
||||||
|
try:
|
||||||
|
rpc.getrawtransaction(spend2_txid)
|
||||||
|
return
|
||||||
|
except RPCError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Send replaceable
|
||||||
|
spend1_txid = submit(sign(spend1))
|
||||||
|
assert rpc.getrawtransaction(spend1_txid)
|
||||||
|
|
||||||
|
|
||||||
|
# Send replacement (higher OP_RETURN data)
|
||||||
|
spend2_txid = submit(sign(spend2))
|
||||||
|
assert rpc.getrawtransaction(spend2_txid)
|
||||||
|
|
||||||
|
# Now the first transaction has gone
|
||||||
|
try:
|
||||||
|
assert not rpc.getrawtransaction(spend1_txid), "should raise an error"
|
||||||
|
except RPCError as e:
|
||||||
|
assert 'No information available about transaction' in str(e), str(e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
for name, f in globals().items():
|
for name, f in globals().items():
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ def get_fanout_txid():
|
|||||||
reward_tx = hoek.decodeTx({'hex': reward_tx_raw})
|
reward_tx = hoek.decodeTx({'hex': reward_tx_raw})
|
||||||
balance = reward_tx['outputs'][0]['amount']
|
balance = reward_tx['outputs'][0]['amount']
|
||||||
|
|
||||||
n_outs = 16
|
n_outs = 40
|
||||||
remainder = balance - n_outs * 1000
|
remainder = balance - n_outs * 1000
|
||||||
|
|
||||||
fanout = {
|
fanout = {
|
||||||
@@ -109,7 +109,9 @@ def get_fanout_txid():
|
|||||||
] + [{"amount": remainder, 'script': {'address': notary_addr}}])
|
] + [{"amount": remainder, 'script': {'address': notary_addr}}])
|
||||||
}
|
}
|
||||||
|
|
||||||
return submit(sign(fanout))
|
txid = submit(sign(fanout))
|
||||||
|
rpc.getrawtransaction(txid)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
|
||||||
def fanout_input(n):
|
def fanout_input(n):
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ BITCOIN_CORE_H = \
|
|||||||
init.h \
|
init.h \
|
||||||
key.h \
|
key.h \
|
||||||
keystore.h \
|
keystore.h \
|
||||||
|
komodo_cryptoconditions.h \
|
||||||
leveldbwrapper.h \
|
leveldbwrapper.h \
|
||||||
limitedmap.h \
|
limitedmap.h \
|
||||||
main.h \
|
main.h \
|
||||||
@@ -155,6 +156,7 @@ BITCOIN_CORE_H = \
|
|||||||
protocol.h \
|
protocol.h \
|
||||||
pubkey.h \
|
pubkey.h \
|
||||||
random.h \
|
random.h \
|
||||||
|
replacementpool.h \
|
||||||
reverselock.h \
|
reverselock.h \
|
||||||
rpcclient.h \
|
rpcclient.h \
|
||||||
rpcprotocol.h \
|
rpcprotocol.h \
|
||||||
@@ -223,6 +225,7 @@ libbitcoin_server_a_SOURCES = \
|
|||||||
httprpc.cpp \
|
httprpc.cpp \
|
||||||
httpserver.cpp \
|
httpserver.cpp \
|
||||||
init.cpp \
|
init.cpp \
|
||||||
|
komodo_cryptoconditions.cpp \
|
||||||
leveldbwrapper.cpp \
|
leveldbwrapper.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
merkleblock.cpp \
|
merkleblock.cpp \
|
||||||
@@ -232,6 +235,7 @@ libbitcoin_server_a_SOURCES = \
|
|||||||
noui.cpp \
|
noui.cpp \
|
||||||
policy/fees.cpp \
|
policy/fees.cpp \
|
||||||
pow.cpp \
|
pow.cpp \
|
||||||
|
replacementpool.cpp \
|
||||||
rest.cpp \
|
rest.cpp \
|
||||||
rpcblockchain.cpp \
|
rpcblockchain.cpp \
|
||||||
rpcmining.cpp \
|
rpcmining.cpp \
|
||||||
@@ -325,6 +329,7 @@ libbitcoin_common_a_SOURCES = \
|
|||||||
hash.cpp \
|
hash.cpp \
|
||||||
key.cpp \
|
key.cpp \
|
||||||
keystore.cpp \
|
keystore.cpp \
|
||||||
|
komodo_cryptoconditions.cpp \
|
||||||
netbase.cpp \
|
netbase.cpp \
|
||||||
primitives/block.cpp \
|
primitives/block.cpp \
|
||||||
primitives/transaction.cpp \
|
primitives/transaction.cpp \
|
||||||
@@ -336,7 +341,6 @@ libbitcoin_common_a_SOURCES = \
|
|||||||
script/script_error.cpp \
|
script/script_error.cpp \
|
||||||
script/sign.cpp \
|
script/sign.cpp \
|
||||||
script/standard.cpp \
|
script/standard.cpp \
|
||||||
komodo_cryptoconditions.cpp \
|
|
||||||
$(BITCOIN_CORE_H) \
|
$(BITCOIN_CORE_H) \
|
||||||
$(LIBZCASH_H)
|
$(LIBZCASH_H)
|
||||||
|
|
||||||
@@ -506,7 +510,6 @@ libzcashconsensus_la_SOURCES = \
|
|||||||
script/zcashconsensus.cpp \
|
script/zcashconsensus.cpp \
|
||||||
script/interpreter.cpp \
|
script/interpreter.cpp \
|
||||||
script/script.cpp \
|
script/script.cpp \
|
||||||
komodo_cryptoconditions.cpp \
|
|
||||||
uint256.cpp \
|
uint256.cpp \
|
||||||
utilstrencodings.cpp
|
utilstrencodings.cpp
|
||||||
|
|
||||||
|
|||||||
Submodule src/cryptoconditions updated: 9ba2d45456...f606567ce3
@@ -1,24 +1,93 @@
|
|||||||
|
|
||||||
|
#include "replacementpool.h"
|
||||||
|
#include "komodo_cryptoconditions.h"
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include "cryptoconditions/include/cryptoconditions.h"
|
||||||
#include "script/interpreter.h"
|
#include "script/interpreter.h"
|
||||||
|
#include "coins.h"
|
||||||
|
|
||||||
|
|
||||||
int TransactionSignatureChecker::CheckAuxCondition(const CC *cond) const
|
#define REPLACEMENT_WINDOW_BLOCKS 2
|
||||||
|
|
||||||
|
|
||||||
|
bool GetOpReturnData(const CScript &sig, std::vector<unsigned char> &data)
|
||||||
{
|
{
|
||||||
// Check that condition is equal to fulfillment
|
auto pc = sig.begin();
|
||||||
if (0 == strcmp((const char*)cond->method, "equals")) {
|
opcodetype opcode;
|
||||||
return (cond->conditionAuxLength == cond->fulfillmentAuxLength) &&
|
if (sig.GetOp(pc, opcode))
|
||||||
(0 == memcmp(cond->conditionAux, cond->fulfillmentAux, cond->conditionAuxLength));
|
if (opcode == OP_RETURN)
|
||||||
}
|
if (sig.GetOp(pc, opcode, data))
|
||||||
|
return opcode > OP_0 && opcode <= OP_PUSHDATA4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check that pubKeyScript specified in fulfillment is OP_RETURN
|
|
||||||
if (0 == strcmp((const char*)cond->method, "inputIsReturn")) {
|
bool EvalConditionBool(const CC *cond, const CTransaction *txTo)
|
||||||
if (cond->fulfillmentAuxLength != 1) return 0;
|
{
|
||||||
int n = (int) cond->fulfillmentAux[0];
|
if (strcmp(cond->method, "testEval") == 0) {
|
||||||
if (n >= txTo->vout.size()) return 0;
|
return cond->paramsBinLength == 8 &&
|
||||||
uint8_t *ptr = (uint8_t *)txTo->vout[n].scriptPubKey.data();
|
memcmp(cond->paramsBin, "testEval", 8) == 0;
|
||||||
return ptr[0] == OP_RETURN;
|
|
||||||
}
|
}
|
||||||
printf("no defined behaviour for method:%s\n", cond->method);
|
if (strcmp(cond->method, "testReplace") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "no defined behaviour for method: %s\n", cond->method);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GetConditionPriority(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TransactionSignatureChecker::CheckEvalCondition(const CC *cond) const
|
||||||
|
{
|
||||||
|
return EvalConditionBool(cond, txTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
int visitConditionPriority(CC *cond, struct CCVisitor visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int visitConditionPriority(CC *cond, struct CCVisitor visitor)
|
||||||
|
{
|
||||||
|
if (cc_typeId(cond) == CC_Eval) {
|
||||||
|
if (GetConditionPriority(cond, (CTxReplacementPoolItem*)visitor.context)) {
|
||||||
|
return 0; // stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1; // continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
#ifndef KOMODO_CRYPTOCONDITIONS_H
|
#ifndef KOMODO_CRYPTOCONDITIONS_H
|
||||||
#define KOMODO_CRYPTOCONDITIONS_H
|
#define KOMODO_CRYPTOCONDITIONS_H
|
||||||
|
|
||||||
|
#include "coins.h"
|
||||||
|
#include "replacementpool.h"
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include "cryptoconditions/include/cryptoconditions.h"
|
||||||
|
|
||||||
extern int32_t ASSETCHAINS_CC;
|
extern int32_t ASSETCHAINS_CC;
|
||||||
|
|
||||||
|
extern CTxReplacementPool replacementPool;
|
||||||
|
|
||||||
static bool IsCryptoConditionsEnabled() {
|
static bool IsCryptoConditionsEnabled() {
|
||||||
return 0 != ASSETCHAINS_CC;
|
return 0 != ASSETCHAINS_CC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EvalConditionBool(const CC *cond, const CTransaction *tx);
|
||||||
|
//uint64_t EvalConditionPriority(const CC *cond, const CTransaction *tx);
|
||||||
|
bool SetReplacementParams(CTxReplacementPoolItem &rep);
|
||||||
#endif /* KOMODO_CRYPTOCONDITIONS_H */
|
#endif /* KOMODO_CRYPTOCONDITIONS_H */
|
||||||
|
|||||||
62
src/main.cpp
62
src/main.cpp
@@ -21,6 +21,8 @@
|
|||||||
#include "pow.h"
|
#include "pow.h"
|
||||||
#include "txdb.h"
|
#include "txdb.h"
|
||||||
#include "txmempool.h"
|
#include "txmempool.h"
|
||||||
|
#include "replacementpool.h"
|
||||||
|
#include "komodo_cryptoconditions.h"
|
||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
#include "undo.h"
|
#include "undo.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@@ -53,6 +55,7 @@ extern uint8_t NOTARY_PUBKEY33[33];
|
|||||||
|
|
||||||
BlockMap mapBlockIndex;
|
BlockMap mapBlockIndex;
|
||||||
CChain chainActive;
|
CChain chainActive;
|
||||||
|
|
||||||
CBlockIndex *pindexBestHeader = NULL;
|
CBlockIndex *pindexBestHeader = NULL;
|
||||||
int64_t nTimeBestReceived = 0;
|
int64_t nTimeBestReceived = 0;
|
||||||
CWaitableCriticalSection csBestBlock;
|
CWaitableCriticalSection csBestBlock;
|
||||||
@@ -1107,7 +1110,7 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee)
|
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee, bool fAcceptReplacement)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
if (pfMissingInputs)
|
if (pfMissingInputs)
|
||||||
@@ -1178,6 +1181,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CTxReplacementPoolResult rpr = RP_NotReplace;
|
||||||
|
|
||||||
{
|
{
|
||||||
CCoinsView dummy;
|
CCoinsView dummy;
|
||||||
CCoinsViewCache view(&dummy);
|
CCoinsViewCache view(&dummy);
|
||||||
@@ -1331,13 +1336,35 @@ 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());
|
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 )
|
if ( komodo_is_notarytx(tx) == 0 )
|
||||||
KOMODO_ON_DEMAND++;
|
KOMODO_ON_DEMAND++;
|
||||||
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
|
|
||||||
|
if (fAcceptReplacement) {
|
||||||
|
CTxReplacementPoolItem item(tx, GetHeight());
|
||||||
|
if (SetReplacementParams(item)) {
|
||||||
|
rpr = replacementPool.replace(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rpr == RP_Invalid) {
|
||||||
|
// already have a better one
|
||||||
|
// TODO: Shouldn't neccesary log this as invalid, not sure if punishing peers is the best idea
|
||||||
|
fprintf(stderr,"accept failure.20\n");
|
||||||
|
return error("AcceptToMemoryPool: Replacement is worse %s", hash.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no replacement action happening...
|
||||||
|
if (rpr == RP_NotReplace) {
|
||||||
|
// Store transaction in memory
|
||||||
|
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncWithWallets(tx, NULL);
|
// in order for replaceable transactions to sync with wallet, replacementpool should advise
|
||||||
|
// wallet of transaction eviction
|
||||||
|
if (rpr == RP_NotReplace) {
|
||||||
|
SyncWithWallets(tx, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1354,6 +1381,12 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replacementPool lookup is O(n) since there's no index by txid
|
||||||
|
if (fAllowSlow && replacementPool.lookup(hash, txOut))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (fTxIndex) {
|
if (fTxIndex) {
|
||||||
CDiskTxPos postx;
|
CDiskTxPos postx;
|
||||||
if (pblocktree->ReadTxIndex(hash, postx)) {
|
if (pblocktree->ReadTxIndex(hash, postx)) {
|
||||||
@@ -1403,6 +1436,17 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ProcessReplacementPool(int newHeight)
|
||||||
|
{
|
||||||
|
std::vector<CTransaction> pending;
|
||||||
|
replacementPool.removePending(newHeight, pending);
|
||||||
|
CValidationState stateDummy;
|
||||||
|
BOOST_FOREACH(CTransaction tx, pending) {
|
||||||
|
AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*char *komodo_getspendscript(uint256 hash,int32_t n)
|
/*char *komodo_getspendscript(uint256 hash,int32_t n)
|
||||||
{
|
{
|
||||||
CTransaction tx; uint256 hashBlock;
|
CTransaction tx; uint256 hashBlock;
|
||||||
@@ -2702,6 +2746,13 @@ bool static DisconnectTip(CValidationState &state) {
|
|||||||
// Update cached incremental witnesses
|
// Update cached incremental witnesses
|
||||||
//fprintf(stderr,"chaintip false\n");
|
//fprintf(stderr,"chaintip false\n");
|
||||||
GetMainSignals().ChainTip(pindexDelete, &block, newTree, false);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2761,6 +2812,8 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
|
|||||||
mempool.check(pcoinsTip);
|
mempool.check(pcoinsTip);
|
||||||
// Update chainActive & related variables.
|
// Update chainActive & related variables.
|
||||||
UpdateTip(pindexNew);
|
UpdateTip(pindexNew);
|
||||||
|
// Process pending replacements
|
||||||
|
ProcessReplacementPool(pindexNew->nHeight-1);
|
||||||
// Tell wallet about transactions that went from mempool
|
// Tell wallet about transactions that went from mempool
|
||||||
// to conflicted:
|
// to conflicted:
|
||||||
BOOST_FOREACH(const CTransaction &tx, txConflicted) {
|
BOOST_FOREACH(const CTransaction &tx, txConflicted) {
|
||||||
@@ -2770,6 +2823,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
|
|||||||
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
|
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
|
||||||
SyncWithWallets(tx, pblock);
|
SyncWithWallets(tx, pblock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cached incremental witnesses
|
// Update cached incremental witnesses
|
||||||
//fprintf(stderr,"chaintip true\n");
|
//fprintf(stderr,"chaintip true\n");
|
||||||
GetMainSignals().ChainTip(pindexNew, pblock, oldTree, true);
|
GetMainSignals().ChainTip(pindexNew, pblock, oldTree, true);
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ void PruneAndFlush();
|
|||||||
|
|
||||||
/** (try to) add transaction to memory pool **/
|
/** (try to) add transaction to memory pool **/
|
||||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
||||||
bool* pfMissingInputs, bool fRejectAbsurdFee=false);
|
bool* pfMissingInputs, bool fRejectAbsurdFee=false, bool fAcceptReplacement=true);
|
||||||
|
|
||||||
|
|
||||||
struct CNodeStateStats {
|
struct CNodeStateStats {
|
||||||
|
|||||||
96
src/replacementpool.cpp
Normal file
96
src/replacementpool.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
#include "coins.h"
|
||||||
|
#include "replacementpool.h"
|
||||||
|
|
||||||
|
|
||||||
|
CTxReplacementPool replacementPool;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a transaction to the pool, with a priority.
|
||||||
|
* Return true if valid, false if not valid. */
|
||||||
|
bool ValidateReplacementPoolItem(CTxReplacementPoolItem item)
|
||||||
|
{
|
||||||
|
// Perform some validations.
|
||||||
|
if (item.tx.vin.size() > 1) {
|
||||||
|
// Replaceable transactions with multiple inputs are disabled for now. It's not yet clear
|
||||||
|
// what edge cases may arise. It is speculated that it will "just work", since if
|
||||||
|
// replaceable transactions spend multiple outputs using the replacement protocol,
|
||||||
|
// they will never conflict in the replaceMap data structure. But for now, to be prudent, disable.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A transaction with 0 priority is not valid.
|
||||||
|
if (item.priority == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
if (!ValidateReplacementPoolItem(item)) {
|
||||||
|
return RP_Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = replaceMap.find(item.tx.vin[0].prevout);
|
||||||
|
|
||||||
|
if (it != replaceMap.end()) {
|
||||||
|
if (it->second.priority >= item.priority) {
|
||||||
|
// Already have a transaction with equal or greater priority; this is not valid
|
||||||
|
return RP_Invalid;
|
||||||
|
}
|
||||||
|
// copy the previous starting block over
|
||||||
|
item.startBlock = it->second.startBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This transaction has higher priority
|
||||||
|
replaceMap[item.tx.vin[0].prevout] = item;
|
||||||
|
|
||||||
|
return RP_Valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CTxReplacementPool::removePending(int height, std::vector<CTransaction> &txs)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
for (auto it = replaceMap.begin(); it != replaceMap.end(); it++) {
|
||||||
|
if (it->second.tx.GetHash() == txHash) {
|
||||||
|
tx = it->second.tx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
68
src/replacementpool.h
Normal file
68
src/replacementpool.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// 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 "coins.h"
|
||||||
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum CTxReplacementPoolResult { RP_NotReplace, RP_Valid, RP_Invalid };
|
||||||
|
|
||||||
|
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 valid-according-to-the-current-best-chain (??? do we need to do this?)
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
class CTxReplacementPool
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// A potential replacement is first stored here, not in the replaceMap.
|
||||||
|
// This is in case some other checks fail, during AcceptToMemoryPool.
|
||||||
|
// Later on, if all checks pass, processReplacement() is called.
|
||||||
|
|
||||||
|
/* Index of spends that may be replaced */
|
||||||
|
std::map<COutPoint, CTxReplacementPoolItem> replaceMap;
|
||||||
|
public:
|
||||||
|
CTxReplacementPoolResult replace(CTxReplacementPoolItem &item);
|
||||||
|
|
||||||
|
// Remove and return all transactions up to a given block height.
|
||||||
|
void removePending(int height, std::vector<CTransaction> &txs);
|
||||||
|
|
||||||
|
bool lookup(uint256 txHash, CTransaction &tx);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern CTxReplacementPool replacementPool;
|
||||||
|
|
||||||
|
#endif // KOMODO_REPLACEMENTCACHE_H
|
||||||
@@ -1154,18 +1154,18 @@ bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
static int komodoCCAux(CC *cond, void *transactionSignatureChecker);
|
static int komodoCCEval(CC *cond, void *transactionSignatureChecker);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int komodoCCAux(CC *cond, void *checker) {
|
static int komodoCCEval(CC *cond, void *checker) {
|
||||||
return ((TransactionSignatureChecker*)checker)->CheckAuxCondition(cond);
|
return ((TransactionSignatureChecker*)checker)->CheckEvalCondition(cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TransactionSignatureChecker::CheckCryptoCondition(const CC *cond, const std::vector<unsigned char>& condBin, const CScript& scriptCode) const
|
bool TransactionSignatureChecker::CheckCryptoCondition(const CC *cond, const std::vector<unsigned char>& condBin, const CScript& scriptCode) const
|
||||||
{
|
{
|
||||||
uint256 message = SignatureHash(scriptCode, *txTo, nIn, SIGHASH_ALL);
|
uint256 message = SignatureHash(scriptCode, *txTo, nIn, SIGHASH_ALL);
|
||||||
return cc_verify(cond, (const unsigned char*)&message, 32, 0,
|
return cc_verify(cond, (const unsigned char*)&message, 32, 0,
|
||||||
condBin.data(), condBin.size(), komodoCCAux, (void*)this);
|
condBin.data(), condBin.size(), komodoCCEval, (void*)this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const
|
bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public:
|
|||||||
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode) const;
|
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode) const;
|
||||||
bool CheckLockTime(const CScriptNum& nLockTime) const;
|
bool CheckLockTime(const CScriptNum& nLockTime) const;
|
||||||
bool CheckCryptoCondition(const CC *cond, const std::vector<unsigned char>& condBin, const CScript& scriptCode) const;
|
bool CheckCryptoCondition(const CC *cond, const std::vector<unsigned char>& condBin, const CScript& scriptCode) const;
|
||||||
int CheckAuxCondition(const CC *cond) const;
|
bool CheckEvalCondition(const CC *cond) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MutableTransactionSignatureChecker : public TransactionSignatureChecker
|
class MutableTransactionSignatureChecker : public TransactionSignatureChecker
|
||||||
|
|||||||
Reference in New Issue
Block a user