Scott Sadler
6 years ago
12 changed files with 392 additions and 83 deletions
@ -1 +1 @@ |
|||
Subproject commit 9ba2d454564c3bb31446a2a2d1db8cb992e6cbe0 |
|||
Subproject commit f606567ce30780c28880094718b5b27c5c426827 |
@ -1,24 +1,93 @@ |
|||
|
|||
#include "replacementpool.h" |
|||
#include "komodo_cryptoconditions.h" |
|||
#include "cryptoconditions/include/cryptoconditions.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
|
|||
if (0 == strcmp((const char*)cond->method, "equals")) { |
|||
return (cond->conditionAuxLength == cond->fulfillmentAuxLength) && |
|||
(0 == memcmp(cond->conditionAux, cond->fulfillmentAux, cond->conditionAuxLength)); |
|||
} |
|||
auto pc = sig.begin(); |
|||
opcodetype opcode; |
|||
if (sig.GetOp(pc, opcode)) |
|||
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")) { |
|||
if (cond->fulfillmentAuxLength != 1) return 0; |
|||
int n = (int) cond->fulfillmentAux[0]; |
|||
if (n >= txTo->vout.size()) return 0; |
|||
uint8_t *ptr = (uint8_t *)txTo->vout[n].scriptPubKey.data(); |
|||
return ptr[0] == OP_RETURN; |
|||
|
|||
bool EvalConditionBool(const CC *cond, const CTransaction *txTo) |
|||
{ |
|||
if (strcmp(cond->method, "testEval") == 0) { |
|||
return cond->paramsBinLength == 8 && |
|||
memcmp(cond->paramsBin, "testEval", 8) == 0; |
|||
} |
|||
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; |
|||
} |
|||
|
|||
|
|||
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 |
|||
#define KOMODO_CRYPTOCONDITIONS_H |
|||
|
|||
#include "coins.h" |
|||
#include "replacementpool.h" |
|||
#include "cryptoconditions/include/cryptoconditions.h" |
|||
|
|||
extern int32_t ASSETCHAINS_CC; |
|||
|
|||
extern CTxReplacementPool replacementPool; |
|||
|
|||
static bool IsCryptoConditionsEnabled() { |
|||
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 */ |
|||
|
@ -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; |
|||
} |
@ -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
|
Loading…
Reference in new issue