This commit is contained in:
Scott Sadler
2018-04-16 22:57:35 -05:00
parent 3205a566d4
commit 0cb91a8d20
30 changed files with 1002 additions and 289 deletions

61
migratecoin.md Normal file
View File

@@ -0,0 +1,61 @@
# MigrateCoin protocol
## ExportCoins tx:
```
vin:
[ any ]
vout:
- amount: {burnAmount}
script: OP_RETURN "send to ledger {id} {voutsHash}"
```
* ExportCoin is a standard tx which burns coins in an OP_RETURN
## ImportCoins tx:
```
vin:
- txid: 0000000000000000000000000000000000000000000000000000000000000000
idx: 0
script: CC_EVAL(EVAL_IMPORTCOINS, {momoProof},{exportCoin}) OP_CHECKCRYPTOCONDITION_UNILATERAL
vout:
- [ vouts matching voutsHash in exportCoin ]
```
* ImportCoin transaction has no signature
* ImportCoin is non malleable
* ImportCoin satisfies tx.IsCoinBase()
* ImportCoin uses a new opcode which allows a one sided check (no scriptPubKey)
* ImportCoin must contain CC opcode EVAL_IMPORTCOINS
* ImportCoin fees are equal to the difference between burnAmount in exportCoins and the sum of outputs.

View File

@@ -256,8 +256,7 @@ libbitcoin_server_a_SOURCES = \
asyncrpcqueue.cpp \
bloom.cpp \
cc/eval.cpp \
cc/importpayout.cpp \
cc/disputepayout.cpp \
cc/importcoin.cpp \
cc/betprotocol.cpp \
chain.cpp \
checkpoints.cpp \
@@ -605,7 +604,7 @@ endif
libzcashconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS)
libzcashconsensus_la_LIBADD = $(LIBSECP256K1)
libzcashconsensus_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL
libzcashconsensus_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -I$(srcdir)/cryptoconditions/include -DBUILD_BITCOIN_INTERNAL
libzcashconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
endif

View File

@@ -5,7 +5,9 @@ bin_PROGRAMS += komodo-test
# tool for generating our public parameters
komodo_test_SOURCES = \
test-komodo/main.cpp \
test-komodo/testutils.cpp \
test-komodo/test_cryptoconditions.cpp \
test-komodo/test_coinimport.cpp \
test-komodo/test_eval_bet.cpp \
test-komodo/test_eval_notarisation.cpp

View File

@@ -1,5 +1,8 @@
#include <cryptoconditions.h>
#include "hash.h"
#include "main.h"
#include "chain.h"
#include "streams.h"
#include "script/cc.h"
#include "cc/eval.h"
@@ -137,3 +140,146 @@ bool GetOpReturnHash(CScript script, uint256 &hash)
hash = uint256(vHash);
return true;
}
/*
* Crypto-Condition EVAL method that verifies a payout against a transaction
* notarised on another chain.
*
* IN: params - condition params
* IN: importTx - Payout transaction on value chain (KMD)
* IN: nIn - index of input of stake
*
* importTx: Spends stakeTx with payouts from asset chain
*
* in 0: Spends Stake TX and contains ImportPayout CC
* out 0: OP_RETURN MomProof, disputeTx
* out 1-: arbitrary payouts
*
* disputeTx: Spends sessionTx.0 (opener on asset chain)
*
* in 0: spends sessionTx.0
* in 1-: anything
* out 0: OP_RETURN hash of payouts
* out 1-: anything
*/
bool Eval::ImportPayout(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// load data from vout[0]
MoMProof proof;
CTransaction disputeTx;
{
std::vector<unsigned char> vopret;
GetOpReturnData(importTx.vout[0].scriptPubKey, vopret);
if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx))
return Invalid("invalid-payload");
}
// Check disputeTx.0 shows correct payouts
{
uint256 givenPayoutsHash;
GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash);
std::vector<CTxOut> payouts(importTx.vout.begin() + 1, importTx.vout.end());
if (givenPayoutsHash != SerializeHash(payouts))
return Invalid("wrong-payouts");
}
// Check disputeTx spends sessionTx.0
// condition ImportPayout params is session ID from other chain
{
uint256 sessionHash;
if (!E_UNMARSHAL(params, ss >> sessionHash))
return Invalid("malformed-params");
if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0))
return Invalid("wrong-session");
}
// Check disputeTx solves momproof from vout[0]
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHash, data))
return Invalid("coudnt-load-mom");
if (data.MoM != proof.branch.Exec(disputeTx.GetHash()))
return Invalid("mom-check-fail");
}
return Valid();
}
/*
* Crypto-Condition EVAL method that resolves a dispute of a session
*
* IN: vm - AppVM virtual machine to verify states
* IN: params - condition params
* IN: disputeTx - transaction attempting to resolve dispute
* IN: nIn - index of input of dispute tx
*
* disputeTx: attempt to resolve a dispute
*
* in 0: Spends Session TX first output, reveals DisputeHeader
* out 0: OP_RETURN hash of payouts
*/
bool Eval::DisputePayout(AppVM &vm, std::vector<uint8_t> params, const CTransaction &disputeTx, unsigned int nIn)
{
if (disputeTx.vout.size() == 0) return Invalid("no-vouts");
// get payouts hash
uint256 payoutHash;
if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash))
return Invalid("invalid-payout-hash");
// load params
uint16_t waitBlocks;
std::vector<uint8_t> vmParams;
if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams))
return Invalid("malformed-params");
// ensure that enough time has passed
{
CTransaction sessionTx;
CBlockIndex sessionBlock;
// if unconformed its too soon
if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock))
return Error("couldnt-get-parent");
if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks)
return Invalid("dispute-too-soon"); // Not yet
}
// get spends
std::vector<CTransaction> spends;
if (!GetSpendsConfirmed(disputeTx.vin[0].prevout.hash, spends))
return Error("couldnt-get-spends");
// verify result from VM
int maxLength = -1;
uint256 bestPayout;
for (int i=1; i<spends.size(); i++)
{
std::vector<unsigned char> vmState;
if (spends[i].vout.size() == 0) continue;
if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue;
auto out = vm.evaluate(vmParams, vmState);
uint256 resultHash = SerializeHash(out.second);
if (out.first > maxLength) {
maxLength = out.first;
bestPayout = resultHash;
}
// The below means that if for any reason there is a draw, the first dispute wins
else if (out.first == maxLength) {
if (bestPayout != payoutHash) {
fprintf(stderr, "WARNING: VM has multiple solutions of same length\n");
bestPayout = resultHash;
}
}
}
if (maxLength == -1) return Invalid("no-evidence");
return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout");
}

View File

@@ -3,7 +3,6 @@
#include "cc/eval.h"
#include "pubkey.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "cryptoconditions/include/cryptoconditions.h"
@@ -11,25 +10,18 @@
class MoMProof
{
public:
int nIndex;
std::vector<uint256> branch;
MerkleBranch branch;
uint256 notarisationHash;
MoMProof() {}
MoMProof(int i, std::vector<uint256> b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {}
uint256 Exec(uint256 hash) const { return CBlock::CheckMerkleBranch(hash, branch, nIndex); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(VARINT(nIndex));
READWRITE(branch);
READWRITE(notarisationHash);
}
};
class BetProtocol
{
protected:

View File

@@ -1,84 +0,0 @@
#include <cryptoconditions.h>
#include "hash.h"
#include "chain.h"
#include "version.h"
#include "script/cc.h"
#include "cc/eval.h"
#include "cc/betprotocol.h"
#include "primitives/transaction.h"
/*
* Crypto-Condition EVAL method that resolves a dispute of a session
*
* IN: vm - AppVM virtual machine to verify states
* IN: params - condition params
* IN: disputeTx - transaction attempting to resolve dispute
* IN: nIn - index of input of dispute tx
*
* disputeTx: attempt to resolve a dispute
*
* in 0: Spends Session TX first output, reveals DisputeHeader
* out 0: OP_RETURN hash of payouts
*/
bool Eval::DisputePayout(AppVM &vm, std::vector<uint8_t> params, const CTransaction &disputeTx, unsigned int nIn)
{
if (disputeTx.vout.size() == 0) return Invalid("no-vouts");
// get payouts hash
uint256 payoutHash;
if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash))
return Invalid("invalid-payout-hash");
// load params
uint16_t waitBlocks;
std::vector<uint8_t> vmParams;
if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams))
return Invalid("malformed-params");
// ensure that enough time has passed
{
CTransaction sessionTx;
CBlockIndex sessionBlock;
// if unconformed its too soon
if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock))
return Error("couldnt-get-parent");
if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks)
return Invalid("dispute-too-soon"); // Not yet
}
// get spends
std::vector<CTransaction> spends;
if (!GetSpendsConfirmed(disputeTx.vin[0].prevout.hash, spends))
return Error("couldnt-get-spends");
// verify result from VM
int maxLength = -1;
uint256 bestPayout;
for (int i=1; i<spends.size(); i++)
{
std::vector<unsigned char> vmState;
if (spends[i].vout.size() == 0) continue;
if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue;
auto out = vm.evaluate(vmParams, vmState);
uint256 resultHash = SerializeHash(out.second);
if (out.first > maxLength) {
maxLength = out.first;
bestPayout = resultHash;
}
// The below means that if for any reason there is a draw, the first dispute wins
else if (out.first == maxLength) {
if (bestPayout != payoutHash) {
fprintf(stderr, "WARNING: VM has multiple solutions of same length\n");
bestPayout = resultHash;
}
}
}
if (maxLength == -1) return Invalid("no-evidence");
return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout");
}

View File

@@ -49,6 +49,10 @@ bool Eval::Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
return ImportPayout(vparams, txTo, nIn);
}
if (ecode == EVAL_IMPORTCOIN) {
return ImportCoin(vparams, txTo, nIn);
}
return Invalid("invalid-code");
}
@@ -143,6 +147,12 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t
}
uint32_t Eval::GetCurrentLedgerID() const
{
return -1; // TODO
}
/*
* Get MoM from a notarisation tx hash
*/
@@ -157,6 +167,11 @@ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data)
return true;
}
bool Eval::GetNotarisationData(int notarisationHeight, NotarisationData &data, bool verifyCanonical) const
{
return false;
}
/*
* Notarisation data, ie, OP_RETURN payload in notarisation transactions
@@ -202,4 +217,26 @@ std::string EvalToStr(EvalCode c)
char s[10];
sprintf(s, "0x%x", c);
return std::string(s);
}
uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex)
{
if (nIndex == -1)
return uint256();
for (auto it(vMerkleBranch.begin()); it != vMerkleBranch.end(); ++it)
{
if (nIndex & 1) {
if (*it == hash) {
// non canonical. hash may be equal to node but never on the right.
return uint256();
}
hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash));
}
else
hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it));
nIndex >>= 1;
}
return hash;
}

View File

@@ -20,8 +20,10 @@
* a possible code is EVAL_BITCOIN_SCRIPT, where the entire binary
* after the code is interpreted as a bitcoin script.
*/
#define FOREACH_EVAL(EVAL) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1)
#define FOREACH_EVAL(EVAL) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1) \
EVAL(EVAL_IMPORTCOIN, 0xe2)
typedef uint8_t EvalCode;
@@ -54,6 +56,11 @@ public:
*/
bool ImportPayout(std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn);
/*
* Import coin from another chain with same symbol
*/
bool ImportCoin(std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn);
/*
* IO functions
*/
@@ -64,7 +71,10 @@ public:
virtual bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const;
virtual int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const;
virtual bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const;
virtual bool GetNotarisationData(int notarisationHeight, NotarisationData &data,
bool verifyCanonical) const;
virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const;
virtual uint32_t GetCurrentLedgerID() const;
};
@@ -87,7 +97,6 @@ public:
evaluate(std::vector<unsigned char> header, std::vector<unsigned char> body) = 0;
};
/*
* Data from notarisation OP_RETURN
*/
@@ -99,6 +108,8 @@ public:
char symbol[64];
uint256 MoM;
uint32_t MoMDepth;
uint256 MoMoM;
uint32_t MoMoMDepth;
bool Parse(CScript scriptPubKey);
};
@@ -140,4 +151,30 @@ bool DeserializeF(const std::vector<unsigned char> vIn, T f)
}
/*
* Merkle stuff
*/
uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
class MerkleBranch
{
public:
int nIndex;
std::vector<uint256> branch;
MerkleBranch() {}
MerkleBranch(int i, std::vector<uint256> b) : nIndex(i), branch(b) {}
uint256 Exec(uint256 hash) const { return SafeCheckMerkleBranch(hash, branch, nIndex); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(VARINT(nIndex));
READWRITE(branch);
}
};
#endif /* CC_EVAL_H */

147
src/cc/importcoin.cpp Normal file
View File

@@ -0,0 +1,147 @@
#include "cc/importcoin.h"
#include "hash.h"
#include "script/cc.h"
#include "primitives/transaction.h"
/*
* Generate ImportCoin transaction.
*
* Contains an empty OP_RETURN as first output; this is critical for preventing a double
* import. If it doesn't contain this it's invalid. The empty OP_RETURN will hang around
* in the UTXO set and the transaction will be detected as a duplicate.
*/
CTransaction MakeImportCoinTransaction(const MomoProof proof, const CTransaction burnTx, const std::vector<CTxOut> payouts)
{
std::vector<uint8_t> payload =
E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx);
CMutableTransaction mtx;
mtx.vin.resize(1);
mtx.vin[0].scriptSig << payload;
mtx.vin[0].prevout.n = 10e8;
mtx.vout = payouts;
return CTransaction(mtx);
}
CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector<CTxOut> payouts)
{
std::vector<uint8_t> opret = E_MARSHAL(ss << VARINT(targetChain); ss << SerializeHash(payouts));
return CTxOut(value, CScript() << OP_RETURN << opret);
}
/*
* CC Eval method for import coin.
*
* This method has to control *every* parameter of the ImportCoin transaction, so that the legal
* importTx for a valid burnTx is 1:1. There can be no two legal importTx transactions for a burnTx
* on another chain.
*/
bool Eval::ImportCoin(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// params
MomoProof proof;
CTransaction burnTx;
if (!E_UNMARSHAL(params, ss >> proof; ss >> burnTx))
return Invalid("invalid-params");
// Control all aspects of this transaction
// It must not be at all malleable
if (MakeImportCoinTransaction(proof, burnTx, importTx.vout).GetHash() != importTx.GetHash())
return Invalid("non-canonical");
// burn params
uint32_t chain; // todo
uint256 payoutsHash;
std::vector<uint8_t> burnOpret;
if (burnTx.vout.size() == 0) return Invalid("invalid-burn-outputs");
GetOpReturnData(burnTx.vout[0].scriptPubKey, burnOpret);
if (!E_UNMARSHAL(burnOpret, ss >> VARINT(chain); ss >> payoutsHash))
return Invalid("invalid-burn-params");
// check chain
if (chain != GetCurrentLedgerID())
return Invalid("importcoin-wrong-chain");
// check burn amount
{
uint64_t burnAmount = burnTx.vout[0].nValue;
if (burnAmount == 0)
return Invalid("invalid-burn-amount");
uint64_t totalOut = 0;
for (int i=0; i<importTx.vout.size(); i++)
totalOut += importTx.vout[i].nValue;
if (totalOut > burnAmount)
return Invalid("payout-too-high");
}
// Check burntx shows correct outputs hash
if (payoutsHash != SerializeHash(importTx.vout))
return Invalid("wrong-payouts");
// Check proof confirms existance of burnTx
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHeight, data, true))
return Invalid("coudnt-load-momom");
if (data.MoMoM != proof.branch.Exec(burnTx.GetHash()))
return Invalid("momom-check-fail");
}
return Valid();
}
/*
* Required by main
*/
CAmount GetCoinImportValue(const CTransaction &tx)
{
CScript scriptSig = tx.vin[0].scriptSig;
auto pc = scriptSig.begin();
opcodetype opcode;
std::vector<uint8_t> evalScript;
if (!scriptSig.GetOp(pc, opcode, evalScript))
return false;
if (pc != scriptSig.end())
return false;
EvalCode code;
MomoProof proof;
CTransaction burnTx;
if (!E_UNMARSHAL(evalScript, ss >> proof; ss >> burnTx))
return 0;
return burnTx.vout.size() ? burnTx.vout[0].nValue : 0;
}
/*
* CoinImport is different enough from normal script execution that it's not worth
* making all the mods neccesary in the interpreter to do the dispatch correctly.
*/
bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state)
{
auto pc = scriptSig.begin();
opcodetype opcode;
std::vector<uint8_t> evalScript;
auto f = [&] () {
if (!scriptSig.GetOp(pc, opcode, evalScript))
return false;
if (pc != scriptSig.end())
return false;
if (evalScript.size() == 0)
return false;
if (evalScript.begin()[0] != EVAL_IMPORTCOIN)
return false;
// Ok, all looks good so far...
CC *cond = CCNewEval(evalScript);
bool out = checker.CheckEvalCondition(cond);
cc_free(cond);
return out;
};
return f() ? true : state.Invalid(false, 0, "invalid-coin-import");
}

33
src/cc/importcoin.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef CC_IMPORTCOIN_H
#define CC_IMPORTCOIN_H
#include "cc/eval.h"
#include "primitives/transaction.h"
#include "script/interpreter.h"
#include <cryptoconditions.h>
class MomoProof
{
public:
MerkleBranch branch;
int notarisationHeight;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(branch);
READWRITE(notarisationHeight);
}
};
CAmount GetCoinImportValue(const CTransaction &tx);
CTransaction MakeImportCoinTransaction(const MomoProof proof,
const CTransaction burnTx, const std::vector<CTxOut> payouts);
CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector<CTxOut> payouts);
bool VerifyCoinImport(const CScript& scriptSig,
TransactionSignatureChecker& checker, CValidationState &state);
#endif /* CC_IMPORTCOIN_H */

View File

@@ -1,76 +0,0 @@
#include <cryptoconditions.h>
#include "main.h"
#include "chain.h"
#include "streams.h"
#include "cc/eval.h"
#include "cc/betprotocol.h"
#include "primitives/transaction.h"
/*
* Crypto-Condition EVAL method that verifies a payout against a transaction
* notarised on another chain.
*
* IN: params - condition params
* IN: importTx - Payout transaction on value chain (KMD)
* IN: nIn - index of input of stake
*
* importTx: Spends stakeTx with payouts from asset chain
*
* in 0: Spends Stake TX and contains ImportPayout CC
* out 0: OP_RETURN MomProof, disputeTx
* out 1-: arbitrary payouts
*
* disputeTx: Spends sessionTx.0 (opener on asset chain)
*
* in 0: spends sessionTx.0
* in 1-: anything
* out 0: OP_RETURN hash of payouts
* out 1-: anything
*/
bool Eval::ImportPayout(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
{
if (importTx.vout.size() == 0) return Invalid("no-vouts");
// load data from vout[0]
MoMProof proof;
CTransaction disputeTx;
{
std::vector<unsigned char> vopret;
GetOpReturnData(importTx.vout[0].scriptPubKey, vopret);
if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx))
return Invalid("invalid-payload");
}
// Check disputeTx.0 shows correct payouts
{
uint256 givenPayoutsHash;
GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash);
std::vector<CTxOut> payouts(importTx.vout.begin() + 1, importTx.vout.end());
if (givenPayoutsHash != SerializeHash(payouts))
return Invalid("wrong-payouts");
}
// Check disputeTx spends sessionTx.0
// condition ImportPayout params is session ID from other chain
{
uint256 sessionHash;
if (!E_UNMARSHAL(params, ss >> sessionHash))
return Invalid("malformed-params");
if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0))
return Invalid("wrong-session");
}
// Check disputeTx solves momproof from vout[0]
{
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHash, data))
return Invalid("coudnt-load-mom");
if (data.MoM != proof.Exec(disputeTx.GetHash()))
return Invalid("mom-check-fail");
}
return Valid();
}

View File

@@ -532,11 +532,19 @@ public:
BOOST_STATIC_ASSERT(equihash_parameters_acceptable(N, K));
nEquihashN = N;
nEquihashK = K;
genesis.nTime = 1296688602;
genesis.nBits = KOMODO_MINDIFF_NBITS;
genesis.nNonce = uint256S("0x0000000000000000000000000000000000000000000000000000000000000021");
genesis.nSolution = ParseHex("0f2a976db4c4263da10fd5d38eb1790469cf19bdb4bf93450e09a72fdff17a3454326399");
genesis = CreateGenesisBlock(
1296688602,
uint256S("0x0000000000000000000000000000000000000000000000000000000000000009"),
ParseHex("01936b7db1eb4ac39f151b8704642d0a8bda13ec547d54cd5e43ba142fc6d8877cab07b3"),
0x200f0f0f, 4, 0);
consensus.hashGenesisBlock = genesis.GetHash();
assert(consensus.hashGenesisBlock == uint256S("0x029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327"));
assert(genesis.hashMerkleRoot == uint256S("0xc4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"));
nDefaultPort = 17779;
nPruneAfterHeight = 1000;

View File

@@ -9,6 +9,7 @@
#include "version.h"
#include "policy/fees.h"
#include "komodo_defs.h"
#include "cc/importcoin.h"
#include <assert.h>
@@ -392,11 +393,13 @@ extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTransaction& tx,uint32_t tiptime) const
{
CAmount value,nResult = 0;
if ( interestp != 0 )
*interestp = 0;
if ( tx.IsCoinImport() )
return GetCoinImportValue(tx);
if ( tx.IsCoinBase() != 0 )
return 0;
CAmount value,nResult = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
value = GetOutputFor(tx.vin[i]).nValue;
@@ -421,6 +424,7 @@ CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTr
return nResult;
}
bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
{
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
@@ -457,7 +461,7 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
if (!tx.IsCoinBase()) {
if (!tx.IsMint()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = AccessCoins(prevout.hash);
@@ -482,6 +486,9 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
if (tx.vjoinsplit.size() > 0) {
return MAX_PRIORITY;
}
if (tx.IsCoinImport()) {
return MAX_PRIORITY;
}
double dResult = 0.0;
BOOST_FOREACH(const CTxIn& txin, tx.vin)

View File

@@ -97,6 +97,11 @@ public:
nVersion = tx.nVersion;
//nLockTime = tx.nLockTime;
ClearUnspendable();
// This must live forever
if (tx.IsCoinImport()) {
vout.insert(vout.begin(), CTxOut(0, CScript() << OP_NOP << OP_RETURN));
}
}
//! construct a CCoins from a CTransaction, at a given height

View File

@@ -10,6 +10,7 @@
#include "addrman.h"
#include "alert.h"
#include "arith_uint256.h"
#include "cc/importcoin.h"
#include "chainparams.h"
#include "checkpoints.h"
#include "checkqueue.h"
@@ -21,6 +22,7 @@
#include "metrics.h"
#include "net.h"
#include "pow.h"
#include "script/interpreter.h"
#include "txdb.h"
#include "txmempool.h"
#include "ui_interface.h"
@@ -817,7 +819,10 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs,
{
if (tx.IsCoinBase())
return true; // Coinbases don't use vin normally
if (tx.IsCoinImport())
return tx.vin[0].scriptSig.IsCoinImport();
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]);
@@ -888,7 +893,7 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx)
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs)
{
if (tx.IsCoinBase())
if (tx.IsCoinBase() || tx.IsCoinImport())
return 0;
unsigned int nSigOps = 0;
@@ -945,8 +950,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state,
return state.DoS(dosLevel, error("ContextualCheckTransaction(): transaction is expired"), REJECT_INVALID, "tx-overwinter-expired");
}
}
if (!(tx.IsCoinBase() || tx.vjoinsplit.empty())) {
if (!(tx.IsMint() || tx.vjoinsplit.empty())) {
auto consensusBranchId = CurrentEpochBranchId(nHeight, Params().GetConsensus());
// Empty output script.
CScript scriptCode;
@@ -1001,7 +1006,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state,
if (!CheckTransactionWithoutProofVerification(tx, state)) {
return false;
} else {
// Ensure that zk-SNARKs verify
// Ensure that zk-SNARKs v|| y
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
if (!joinsplit.Verify(*pzcashParams, verifier, tx.joinSplitPubKey)) {
return state.DoS(100, error("CheckTransaction(): joinsplit does not verify"),
@@ -1059,6 +1064,7 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio
// Transactions can contain empty `vin` and `vout` so long as
// `vjoinsplit` is non-empty.
// Migrations may also have empty `vin`
if (tx.vin.empty() && tx.vjoinsplit.empty())
return state.DoS(10, error("CheckTransaction(): vin empty"),
REJECT_INVALID, "bad-txns-vin-empty");
@@ -1167,7 +1173,7 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio
}
}
if (tx.IsCoinBase())
if (tx.IsMint())
{
// There should be no joinsplits in a coinbase transaction
if (tx.vjoinsplit.size() > 0)
@@ -1284,7 +1290,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (pool.exists(hash))
{
fprintf(stderr,"already in mempool\n");
return false;
return state.Invalid(false, REJECT_DUPLICATE, "already in mempool");
}
// Check for conflicts with in-memory transactions
@@ -1326,31 +1332,36 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
view.SetBackend(viewMemPool);
// do we already have it?
// This is what stops coin import transactions from being double spent; they
// grow the UTXO set size slightly
if (view.HaveCoins(hash))
{
fprintf(stderr,"view.HaveCoins(hash) error\n");
return false;
return state.Invalid(false, REJECT_DUPLICATE, "already have coins");
}
// do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
BOOST_FOREACH(const CTxIn txin, tx.vin)
if (!tx.IsCoinImport())
{
if (!view.HaveCoins(txin.prevout.hash))
// do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
BOOST_FOREACH(const CTxIn txin, tx.vin)
{
if (pfMissingInputs)
*pfMissingInputs = true;
//fprintf(stderr,"missing inputs\n");
return false;
if (!view.HaveCoins(txin.prevout.hash))
{
if (pfMissingInputs)
*pfMissingInputs = true;
//fprintf(stderr,"missing inputs\n");
return false;
}
}
// are the actual inputs available?
if (!view.HaveInputs(tx))
{
//fprintf(stderr,"accept failure.1\n");
return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),REJECT_DUPLICATE, "bad-txns-inputs-spent");
}
}
// are the actual inputs available?
if (!view.HaveInputs(tx))
{
//fprintf(stderr,"accept failure.1\n");
return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),REJECT_DUPLICATE, "bad-txns-inputs-spent");
}
// are the joinsplit's requirements met?
if (!view.HaveJoinSplitRequirements(tx))
@@ -1393,11 +1404,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Keep track of transactions that spend a coinbase, which we re-scan
// during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false;
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
const CCoins *coins = view.AccessCoins(txin.prevout.hash);
if (coins->IsCoinBase()) {
fSpendsCoinbase = true;
break;
if (!tx.IsCoinImport()) {
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
const CCoins *coins = view.AccessCoins(txin.prevout.hash);
if (coins->IsCoinBase()) {
fSpendsCoinbase = true;
break;
}
}
}
@@ -1478,6 +1491,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks, however allowing such transactions into the mempool
// can be exploited as a DoS attack.
// XXX: is this neccesary for CryptoConditions?
if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId))
{
fprintf(stderr,"accept failure.10\n");
@@ -1967,7 +1981,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight)
{
if (!tx.IsCoinBase()) // mark inputs spent
if (!tx.IsMint()) // mark inputs spent
{
txundo.vprevout.reserve(tx.vin.size());
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
@@ -2003,7 +2017,8 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, ServerTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), consensusBranchId, &error)) {
ServerTransactionSignatureChecker checker(ptxTo, nIn, amount, cacheStore, *txdata);
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, checker, consensusBranchId, &error)) {
return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error));
}
return true;
@@ -2113,7 +2128,7 @@ bool ContextualCheckInputs(
uint32_t consensusBranchId,
std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase())
if (!tx.IsMint())
{
if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs), consensusParams)) {
return false;
@@ -2165,7 +2180,13 @@ bool ContextualCheckInputs(
}
}
}
if (tx.IsCoinImport())
{
ServerTransactionSignatureChecker checker(&tx, 0, 0, false, txdata);
return VerifyCoinImport(tx.vin[0].scriptSig, checker, state);
}
return true;
}
@@ -2400,7 +2421,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
}
// restore inputs
if (i > 0) { // not coinbases
if (!tx.IsMint()) {
const CTxUndo &txundo = blockUndo.vtxundo[i-1];
if (txundo.vprevout.size() != tx.vin.size())
return error("DisconnectBlock(): transaction and undo data inconsistent");
@@ -2681,7 +2702,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");
//fprintf(stderr,"ht.%d vout0 t%u\n",pindex->nHeight,tx.nLockTime);
if (!tx.IsCoinBase())
if (!tx.IsMint())
{
if (!view.HaveInputs(tx))
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),

View File

@@ -11,6 +11,7 @@
#include "amount.h"
#include "base58.h"
#include "chainparams.h"
#include "cc/importcoin.h"
#include "consensus/consensus.h"
#include "consensus/upgrades.h"
#include "consensus/validation.h"
@@ -224,48 +225,55 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
double dPriority = 0;
CAmount nTotalIn = 0;
bool fMissingInputs = false;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
if (tx.IsCoinImport())
{
// Read prev transaction
if (!view.HaveCoins(txin.prevout.hash))
{
// This should never happen; all transactions in the memory
// pool should connect to either transactions in the chain
// or other transactions in the memory pool.
if (!mempool.mapTx.count(txin.prevout.hash))
{
LogPrintf("ERROR: mempool transaction missing input\n");
if (fDebug) assert("mempool transaction missing input" == 0);
fMissingInputs = true;
if (porphan)
vOrphan.pop_back();
break;
}
// Has to wait for dependencies
if (!porphan)
{
// Use list for automatic deletion
vOrphan.push_back(COrphan(&tx));
porphan = &vOrphan.back();
}
mapDependers[txin.prevout.hash].push_back(porphan);
porphan->setDependsOn.insert(txin.prevout.hash);
nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
continue;
}
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
assert(coins);
CAmount nValueIn = coins->vout[txin.prevout.n].nValue;
CAmount nValueIn = GetCoinImportValue(tx);
nTotalIn += nValueIn;
int nConf = nHeight - coins->nHeight;
dPriority += (double)nValueIn * nConf;
dPriority += (double)nValueIn * 1000; // flat multiplier
} else {
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
// Read prev transaction
if (!view.HaveCoins(txin.prevout.hash))
{
// This should never happen; all transactions in the memory
// pool should connect to either transactions in the chain
// or other transactions in the memory pool.
if (!mempool.mapTx.count(txin.prevout.hash))
{
LogPrintf("ERROR: mempool transaction missing input\n");
if (fDebug) assert("mempool transaction missing input" == 0);
fMissingInputs = true;
if (porphan)
vOrphan.pop_back();
break;
}
// Has to wait for dependencies
if (!porphan)
{
// Use list for automatic deletion
vOrphan.push_back(COrphan(&tx));
porphan = &vOrphan.back();
}
mapDependers[txin.prevout.hash].push_back(porphan);
porphan->setDependsOn.insert(txin.prevout.hash);
nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
continue;
}
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
assert(coins);
CAmount nValueIn = coins->vout[txin.prevout.n].nValue;
nTotalIn += nValueIn;
int nConf = nHeight - coins->nHeight;
dPriority += (double)nValueIn * nConf;
}
nTotalIn += tx.GetJoinSplitValueIn();
}
nTotalIn += tx.GetJoinSplitValueIn();
if (fMissingInputs) continue;
// Priority is sum(valuein * age) / modified_txsize

View File

@@ -452,11 +452,21 @@ public:
// Compute modified tx size for priority calculation (optionally given tx size)
unsigned int CalculateModifiedSize(unsigned int nTxSize=0) const;
bool IsMint() const
{
return (vin.size() == 1 && vin[0].prevout.hash.IsNull());
}
bool IsCoinBase() const
{
return (vin.size() == 1 && vin[0].prevout.IsNull());
}
bool IsCoinImport() const
{
return IsMint() && vin[0].prevout.n == 10e8;
}
friend bool operator==(const CTransaction& a, const CTransaction& b)
{
return a.hash == b.hash;

View File

@@ -9,7 +9,7 @@
#include "checkpoints.h"
#include "base58.h"
#include "consensus/validation.h"
#include "cc/betprotocol.h"
#include "cc/eval.h"
#include "main.h"
#include "primitives/transaction.h"
#include "rpcserver.h"
@@ -1011,7 +1011,7 @@ UniValue txMoMproof(const UniValue& params, bool fHelp)
// Encode and return
CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION);
ssProof << MoMProof(nIndex, branch, notarisationHash);
ssProof << std::make_pair(notarisationHash, MerkleBranch(nIndex, branch));
return HexStr(ssProof.begin(), ssProof.end());
}

View File

@@ -3,17 +3,20 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <cryptoconditions.h>
#include "interpreter.h"
#include "consensus/upgrades.h"
#include "primitives/transaction.h"
#include "cc/eval.h"
#include "crypto/ripemd160.h"
#include "crypto/sha1.h"
#include "crypto/sha256.h"
#include "pubkey.h"
#include "script/script.h"
#include "uint256.h"
#include "cryptoconditions/include/cryptoconditions.h"
using namespace std;

View File

@@ -186,5 +186,4 @@ bool VerifyScript(
const BaseSignatureChecker& checker,
uint32_t consensusBranchId,
ScriptError* serror = NULL);
#endif // BITCOIN_SCRIPT_INTERPRETER_H

View File

@@ -8,8 +8,10 @@
#include "tinyformat.h"
#include "utilstrencodings.h"
#include "script/cc.h"
#include "cc/eval.h"
#include "cryptoconditions/include/cryptoconditions.h"
namespace {
inline std::string ValueString(const std::vector<unsigned char>& vch)
{
@@ -266,6 +268,17 @@ bool CScript::MayAcceptCryptoCondition() const
return out;
}
bool CScript::IsCoinImport() const
{
const_iterator pc = this->begin();
vector<unsigned char> data;
opcodetype opcode;
if (this->GetOp(pc, opcode, data))
if (opcode > OP_0 && opcode <= OP_PUSHDATA4)
return data.begin()[0] == EVAL_IMPORTCOIN;
return false;
}
bool CScript::IsPushOnly() const
{
const_iterator pc = begin();

View File

@@ -570,6 +570,7 @@ public:
bool IsPayToScriptHash() const;
bool IsPayToCryptoCondition() const;
bool IsCoinImport() const;
bool MayAcceptCryptoCondition() const;
/** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */

View File

@@ -1,7 +1,9 @@
#include "key.h"
#include "base58.h"
#include "chainparams.h"
#include "gtest/gtest.h"
#include "crypto/common.h"
#include "testutils.h"
int main(int argc, char **argv) {
@@ -9,6 +11,11 @@ int main(int argc, char **argv) {
ECC_Start();
SelectParams(CBaseChainParams::REGTEST);
CBitcoinSecret vchSecret;
// this returns false due to network prefix mismatch but works anyway
vchSecret.SetString(notarySecret);
notaryKey = vchSecret.GetKey();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,206 @@
#include <cryptoconditions.h>
#include <gtest/gtest.h>
#include "cc/importcoin.h"
#include "cc/eval.h"
#include "base58.h"
#include "core_io.h"
#include "key.h"
#include "main.h"
#include "script/cc.h"
#include "primitives/transaction.h"
#include "script/interpreter.h"
#include "script/serverchecker.h"
#include "txmempool.h"
#include "testutils.h"
extern Eval* EVAL_TEST;
namespace TestCoinImport {
static uint8_t testNum = 0;
class TestCoinImport : public ::testing::Test, public Eval {
public:
CMutableTransaction burnTx;
std::vector<CTxOut> payouts;
MomoProof proof;
uint256 MoMoM;
CMutableTransaction importTx;
uint32_t chainId = 2;
CAmount amount = 100;
void SetImportTx() {
CTxOut burnOutput = MakeBurnOutput(amount, chainId, payouts);
burnTx.vout.push_back(burnOutput);
MoMoM = burnTx.GetHash(); // TODO: an actual branch
importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts));
}
uint32_t GetCurrentLedgerID() const { return chainId; }
bool GetNotarisationData(int notarisationHeight, NotarisationData &data, bool verifyCanonical) const
{
if (MoMoM.IsNull()) return false;
data.MoMoM = MoMoM;
return true;
}
protected:
static void SetUpTestCase() { setupChain(); }
virtual void SetUp() {
ASSETCHAINS_CC = 1;
EVAL_TEST = this;
std::vector<uint8_t> fakepk;
fakepk.resize(33);
fakepk.begin()[0] = testNum++;
payouts.push_back(CTxOut(amount, CScript() << fakepk << OP_CHECKSIG));
SetImportTx();
}
void TestRunCCEval(CMutableTransaction mtx)
{
CTransaction importTx(mtx);
PrecomputedTransactionData txdata(importTx);
ServerTransactionSignatureChecker checker(&importTx, 0, 0, false, txdata);
CValidationState verifystate;
if (!VerifyCoinImport(importTx.vin[0].scriptSig, checker, verifystate))
printf("TestRunCCEval: %s\n", verifystate.GetRejectReason().data());
}
};
TEST_F(TestCoinImport, testProcessImportThroughPipeline)
{
CValidationState mainstate;
CTransaction tx(importTx);
// first should work
acceptTxFail(tx);
// should fail in mempool
ASSERT_FALSE(acceptTx(tx, mainstate));
EXPECT_EQ("already in mempool", mainstate.GetRejectReason());
// should fail in persisted UTXO set
generateBlock();
ASSERT_FALSE(acceptTx(tx, mainstate));
EXPECT_EQ("already have coins", mainstate.GetRejectReason());
ASSERT_TRUE(pcoinsTip->HaveCoins(tx.GetHash()));
// Now disconnect the block
CValidationState invalstate;
if (!InvalidateBlock(invalstate, chainActive.Tip())) {
FAIL() << invalstate.GetRejectReason();
}
ASSERT_FALSE(pcoinsTip->HaveCoins(tx.GetHash()));
// should be back in mempool
ASSERT_FALSE(acceptTx(tx, mainstate));
EXPECT_EQ("already in mempool", mainstate.GetRejectReason());
}
TEST_F(TestCoinImport, testNoVouts)
{
importTx.vout.resize(0);
TestRunCCEval(importTx);
EXPECT_EQ("no-vouts", state.GetRejectReason());
}
TEST_F(TestCoinImport, testInvalidParams)
{
std::vector<uint8_t> payload = E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << 'a');
importTx.vin[0].scriptSig = CScript() << payload;
TestRunCCEval(importTx);
EXPECT_EQ("invalid-params", state.GetRejectReason());
}
TEST_F(TestCoinImport, testNonCanonical)
{
importTx.nLockTime = 10;
TestRunCCEval(importTx);
EXPECT_EQ("non-canonical", state.GetRejectReason());
}
TEST_F(TestCoinImport, testInvalidBurnOutputs)
{
burnTx.vout.resize(0);
MoMoM = burnTx.GetHash(); // TODO: an actual branch
CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts);
TestRunCCEval(tx);
EXPECT_EQ("invalid-burn-outputs", state.GetRejectReason());
}
TEST_F(TestCoinImport, testInvalidBurnParams)
{
burnTx.vout[0].scriptPubKey = CScript() << OP_RETURN << E_MARSHAL(ss << VARINT(chainId));
MoMoM = burnTx.GetHash(); // TODO: an actual branch
CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts);
TestRunCCEval(tx);
EXPECT_EQ("invalid-burn-params", state.GetRejectReason());
}
TEST_F(TestCoinImport, testWrongChainId)
{
chainId = 0;
TestRunCCEval(importTx);
EXPECT_EQ("importcoin-wrong-chain", state.GetRejectReason());
}
TEST_F(TestCoinImport, testInvalidBurnAmount)
{
burnTx.vout[0].nValue = 0;
MoMoM = burnTx.GetHash(); // TODO: an actual branch
CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts);
TestRunCCEval(tx);
EXPECT_EQ("invalid-burn-amount", state.GetRejectReason());
}
TEST_F(TestCoinImport, testPayoutTooHigh)
{
importTx.vout[0].nValue = 101;
TestRunCCEval(importTx);
EXPECT_EQ("payout-too-high", state.GetRejectReason());
}
TEST_F(TestCoinImport, testInvalidPayouts)
{
importTx.vout[0].nValue = 40;
importTx.vout.push_back(importTx.vout[0]);
TestRunCCEval(importTx);
EXPECT_EQ("wrong-payouts", state.GetRejectReason());
}
TEST_F(TestCoinImport, testCouldntLoadMomom)
{
MoMoM.SetNull();
TestRunCCEval(importTx);
EXPECT_EQ("coudnt-load-momom", state.GetRejectReason());
}
TEST_F(TestCoinImport, testMomomCheckFail)
{
MoMoM.SetNull();
MoMoM.begin()[0] = 1;
TestRunCCEval(importTx);
EXPECT_EQ("momom-check-fail", state.GetRejectReason());
}
} /* namespace TestCoinImport */

View File

@@ -12,11 +12,6 @@
#include "testutils.h"
CKey notaryKey;
std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47";
std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU";
class CCTest : public ::testing::Test {
public:
@@ -32,11 +27,6 @@ protected:
virtual void SetUp() {
// enable CC
ASSETCHAINS_CC = 1;
// Notary key
CBitcoinSecret vchSecret;
// this returns false due to network prefix mismatch but works anyway
vchSecret.SetString(secret);
notaryKey = vchSecret.GetKey();
}
};

View File

@@ -208,7 +208,7 @@ public:
int nIndex = 5;
std::vector<uint256> vBranch;
vBranch.resize(3);
return MoMProof(nIndex, vBranch, EvalMock::NotarisationHash());
return {MerkleBranch(nIndex, vBranch), EvalMock::NotarisationHash()};
}
CMutableTransaction ImportPayoutTx()
@@ -237,7 +237,7 @@ public:
eval.currentHeight = currentHeight;
MoMProof proof = GetMoMProof();
eval.MoM = proof.Exec(DisputeTx(Player2).GetHash());
eval.MoM = proof.branch.Exec(DisputeTx(Player2).GetHash());
EVAL_TEST = &eval;
return eval;
@@ -585,7 +585,7 @@ TEST_F(TestBet, testImportPayoutMomFail)
EvalMock eval = ebet.SetEvalMock(12);
MoMProof proof = ebet.GetMoMProof();
proof.nIndex ^= 1;
proof.branch.nIndex ^= 1;
CMutableTransaction importTx = ebet.bet.MakeImportPayoutTx(
ebet.Payouts(Player2), ebet.DisputeTx(Player2), uint256(), proof);

View File

@@ -15,7 +15,6 @@
#include "testutils.h"
extern Eval* EVAL_TEST;
extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
@@ -61,7 +60,7 @@ static auto noop = [&](CMutableTransaction &mtx){};
template<typename Modifier>
void SetEval(EvalMock &eval, CMutableTransaction &notary, Modifier modify)
void SetupEval(EvalMock &eval, CMutableTransaction &notary, Modifier modify)
{
eval.nNotaries = komodo_notaries(eval.notaries, 780060, 1522946781);
@@ -80,8 +79,6 @@ void SetEval(EvalMock &eval, CMutableTransaction &notary, Modifier modify)
eval.txs[notary.GetHash()] = CTransaction(notary);
eval.blocks[notary.GetHash()].nHeight = 780060;
eval.blocks[notary.GetHash()].nTime = 1522946781;
EVAL_TEST = &eval;
}
@@ -94,12 +91,12 @@ static bool init = DecodeHexTx(notaryTx, rawNotaryTx);
static uint256 proofTxHash = uint256S("37f76551a16093fbb0a92ee635bbd45b3460da8fd00cf7d5a6b20d93e727fe4c");
static auto vMomProof = ParseHex("0303faecbdd4b3da128c2cd2701bb143820a967069375b2ec5b612f39bbfe78a8611978871c193457ab1e21b9520f4139f113b8d75892eb93ee247c18bccfd067efed7eacbfcdc8946cf22de45ad536ec0719034fb9bc825048fe6ab61fee5bd6e9aae0bb279738d46673c53d68eb2a72da6dbff215ee41a4d405a74ff7cd355805b"); // $ fiat/bots txMoMproof $proofTxHash
/*
TEST(TestEvalNotarisation, testGetNotarisation)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
SetupEval(eval, notary, noop);
NotarisationData data;
ASSERT_TRUE(eval.GetNotarisationData(notary.GetHash(), data));
@@ -111,7 +108,7 @@ TEST(TestEvalNotarisation, testGetNotarisation)
MoMProof proof;
E_UNMARSHAL(vMomProof, ss >> proof);
EXPECT_EQ(data.MoM, proof.Exec(proofTxHash));
EXPECT_EQ(data.MoM, proof.branch.Exec(proofTxHash));
}
@@ -119,13 +116,14 @@ TEST(TestEvalNotarisation, testInvalidNotaryPubkey)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
SetupEval(eval, notary, noop);
memset(eval.notaries[10], 0, 33);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
*/
TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn)
@@ -134,7 +132,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn)
CMutableTransaction notary(notaryTx);
notary.vout[1].scriptPubKey = CScript() << OP_RETURN << 0;
SetEval(eval, notary, noop);
SetupEval(eval, notary, noop);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
@@ -146,7 +144,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxNotEnoughSigs)
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [](CMutableTransaction &tx) {
SetupEval(eval, notary, [](CMutableTransaction &tx) {
tx.vin.resize(10);
});
@@ -160,7 +158,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxDoesntExist)
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
SetupEval(eval, notary, noop);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(uint256(), data));
@@ -172,7 +170,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationDupeNotary)
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [](CMutableTransaction &tx) {
SetupEval(eval, notary, [](CMutableTransaction &tx) {
tx.vin[1] = tx.vin[3];
});
@@ -186,7 +184,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationInputNotCheckSig)
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [&](CMutableTransaction &tx) {
SetupEval(eval, notary, [&](CMutableTransaction &tx) {
int i = 1;
CMutableTransaction txIn;
txIn.vout.resize(1);

View File

@@ -0,0 +1,129 @@
#include <cryptoconditions.h>
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#include "core_io.h"
#include "key.h"
#include "main.h"
#include "miner.h"
#include "random.h"
#include "rpcserver.h"
#include "rpcprotocol.h"
#include "txdb.h"
#include "util.h"
#include "utilstrencodings.h"
#include "utiltime.h"
#include "consensus/validation.h"
#include "primitives/transaction.h"
#include "script/cc.h"
#include "script/interpreter.h"
#include "testutils.h"
std::string notaryPubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47";
std::string notarySecret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU";
CKey notaryKey;
/*
* We need to have control of clock,
* otherwise block production can fail.
*/
int64_t nMockTime;
extern uint32_t USE_EXTERNAL_PUBKEY;
void setupChain()
{
SelectParams(CBaseChainParams::REGTEST);
// 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();
}
void generateBlock(CBlock *block)
{
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 acceptTxFail(const CTransaction tx)
{
CValidationState state;
if (!acceptTx(tx, state)) FAIL() << state.GetRejectReason();
}
bool acceptTx(const CTransaction tx, CValidationState &state)
{
LOCK(cs_main);
return AcceptToMemoryPool(mempool, state, tx, false, NULL);
}
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, 0, 0);
std::vector<unsigned char> vchSig;
notaryKey.Sign(hash, vchSig);
vchSig.push_back((unsigned char)SIGHASH_ALL);
mtx.vin[0].scriptSig << vchSig;
// Accept
acceptTxFail(mtx);
txIn = CTransaction(mtx);
}

View File

@@ -1,7 +1,7 @@
#ifndef TESTUTILS_H
#define TESTUTILS_H
#include "script/cc.h"
#include "main.h"
#define VCH(a,b) std::vector<unsigned char>(a, a + b)
@@ -12,4 +12,16 @@ static char ccjsonerr[1000] = "\0";
if (!o) FAIL() << "bad json: " << ccjsonerr;
extern std::string notaryPubkey;
extern std::string notarySecret;
extern CKey notaryKey;
void setupChain();
void generateBlock(CBlock *block=NULL);
bool acceptTx(const CTransaction tx, CValidationState &state);
void acceptTxFail(const CTransaction tx);
void getInputTx(CScript scriptPubKey, CTransaction &txIn);
#endif /* TESTUTILS_H */

View File

@@ -104,8 +104,10 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
LOCK(cs);
mapTx.insert(entry);
const CTransaction& tx = mapTx.find(hash)->GetTx();
for (unsigned int i = 0; i < tx.vin.size(); i++)
mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i);
if (!tx.IsCoinImport()) {
for (unsigned int i = 0; i < tx.vin.size(); i++)
mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i);
}
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) {
mapNullifiers[nf] = &tx;