![jl777@users.noreply.github.com](/assets/img/avatar_default.png)
committed by
GitHub
![GitHub](/assets/img/avatar_default.png)
57 changed files with 2608 additions and 724 deletions
@ -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. |
@ -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"); |
|||
} |
@ -0,0 +1,72 @@ |
|||
#include "cc/eval.h" |
|||
#include "cc/utils.h" |
|||
#include "importcoin.h" |
|||
#include "primitives/transaction.h" |
|||
|
|||
|
|||
/*
|
|||
* CC Eval method for import coin. |
|||
* |
|||
* This method should control every parameter of the ImportCoin transaction, since it has no signature |
|||
* to protect it from malleability. |
|||
*/ |
|||
bool Eval::ImportCoin(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn) |
|||
{ |
|||
if (importTx.vout.size() < 2) |
|||
return Invalid("too-few-vouts"); |
|||
|
|||
// params
|
|||
TxProof proof; |
|||
CTransaction burnTx; |
|||
std::vector<CTxOut> payouts; |
|||
|
|||
if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) |
|||
return Invalid("invalid-params"); |
|||
|
|||
// Control all aspects of this transaction
|
|||
// It should not be at all malleable
|
|||
if (MakeImportCoinTransaction(proof, burnTx, payouts).GetHash() != importTx.GetHash()) |
|||
return Invalid("non-canonical"); |
|||
|
|||
// burn params
|
|||
uint32_t targetCcid; |
|||
std::string targetSymbol; |
|||
uint256 payoutsHash; |
|||
|
|||
if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCcid, payoutsHash)) |
|||
return Invalid("invalid-burn-tx"); |
|||
|
|||
if (targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol()) |
|||
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(payouts)) |
|||
return Invalid("wrong-payouts"); |
|||
|
|||
// Check proof confirms existance of burnTx
|
|||
{ |
|||
uint256 momom, target; |
|||
if (!GetProofRoot(proof.first, momom)) |
|||
return Invalid("coudnt-load-momom"); |
|||
|
|||
target = proof.second.Exec(burnTx.GetHash()); |
|||
if (momom != proof.second.Exec(burnTx.GetHash())) |
|||
return Invalid("momom-check-fail"); |
|||
} |
|||
|
|||
return Valid(); |
|||
} |
|||
|
|||
|
@ -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(); |
|||
} |
@ -0,0 +1,34 @@ |
|||
#ifndef CC_UTILS_H |
|||
#define CC_UTILS_H |
|||
|
|||
#include "streams.h" |
|||
#include "version.h" |
|||
|
|||
|
|||
/*
|
|||
* Serialisation boilerplate |
|||
*/ |
|||
|
|||
template <class T> |
|||
std::vector<uint8_t> SerializeF(const T f) |
|||
{ |
|||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); |
|||
f(ss); |
|||
return std::vector<unsigned char>(ss.begin(), ss.end()); |
|||
} |
|||
|
|||
template <class T> |
|||
bool DeserializeF(const std::vector<unsigned char> vIn, T f) |
|||
{ |
|||
CDataStream ss(vIn, SER_NETWORK, PROTOCOL_VERSION); |
|||
try { |
|||
f(ss); |
|||
if (ss.eof()) return true; |
|||
} catch(...) {} |
|||
return false; |
|||
} |
|||
|
|||
#define E_MARSHAL(body) SerializeF([&] (CDataStream &ss) {body;}) |
|||
#define E_UNMARSHAL(params, body) DeserializeF(params, [&] (CDataStream &ss) {body;}) |
|||
|
|||
#endif /* CC_UTILS_H */ |
@ -0,0 +1,336 @@ |
|||
#include "cc/eval.h" |
|||
#include "crosschain.h" |
|||
#include "importcoin.h" |
|||
#include "main.h" |
|||
#include "notarisationdb.h" |
|||
|
|||
/*
|
|||
* The crosschain workflow. |
|||
* |
|||
* 3 chains, A, B, and KMD. We would like to prove TX on B. |
|||
* There is a notarisation, nA0, which will include TX via an MoM. |
|||
* The notarisation nA0 must fall between 2 notarisations of B, |
|||
* ie, nB0 and nB1. An MoMoM including this range is propagated to |
|||
* B in notarisation receipt (backnotarisation) bnB2. |
|||
* |
|||
* A: TX bnA0 |
|||
* \ / |
|||
* KMD: nB0 nA0 nB1 nB2 |
|||
* \ \ \ |
|||
* B: bnB0 bnB1 bnB2 |
|||
*/ |
|||
|
|||
// XXX: There are potential crashes wherever we access chainActive without a lock,
|
|||
// because it might be disconnecting blocks at the same time.
|
|||
|
|||
|
|||
int NOTARISATION_SCAN_LIMIT_BLOCKS = 1440; |
|||
|
|||
|
|||
/* On KMD */ |
|||
uint256 CalculateProofRoot(const char* symbol, uint32_t targetCCid, int kmdHeight, |
|||
std::vector<uint256> &moms, uint256 &destNotarisationTxid) |
|||
{ |
|||
/*
|
|||
* Notaries don't wait for confirmation on KMD before performing a backnotarisation, |
|||
* but we need a determinable range that will encompass all merkle roots. Include MoMs |
|||
* including the block height of the last notarisation until the height before the |
|||
* previous notarisation. |
|||
* |
|||
* kmdHeight notarisations-0 notarisations-1 |
|||
* *********************| |
|||
* > scan backwards > |
|||
*/ |
|||
|
|||
if (targetCCid <= 1) |
|||
return uint256(); |
|||
|
|||
if (kmdHeight < 0 || kmdHeight > chainActive.Height()) |
|||
return uint256(); |
|||
|
|||
int seenOwnNotarisations = 0; |
|||
|
|||
for (int i=0; i<NOTARISATION_SCAN_LIMIT_BLOCKS; i++) { |
|||
if (i > kmdHeight) break; |
|||
NotarisationsInBlock notarisations; |
|||
uint256 blockHash = *chainActive[kmdHeight-i]->phashBlock; |
|||
if (!GetBlockNotarisations(blockHash, notarisations)) |
|||
continue; |
|||
|
|||
// See if we have an own notarisation in this block
|
|||
BOOST_FOREACH(Notarisation& nota, notarisations) { |
|||
if (strcmp(nota.second.symbol, symbol) == 0) |
|||
{ |
|||
seenOwnNotarisations++; |
|||
if (seenOwnNotarisations == 1) |
|||
destNotarisationTxid = nota.first; |
|||
else if (seenOwnNotarisations == 2) |
|||
goto end; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (seenOwnNotarisations == 1) { |
|||
BOOST_FOREACH(Notarisation& nota, notarisations) { |
|||
if (nota.second.ccId == targetCCid) |
|||
moms.push_back(nota.second.MoM); |
|||
} |
|||
} |
|||
} |
|||
|
|||
end: |
|||
return GetMerkleRoot(moms); |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Get a notarisation from a given height |
|||
* |
|||
* Will scan notarisations leveldb up to a limit |
|||
*/ |
|||
template <typename IsTarget> |
|||
int ScanNotarisationsFromHeight(int nHeight, const IsTarget f, Notarisation &found) |
|||
{ |
|||
int limit = std::min(nHeight + NOTARISATION_SCAN_LIMIT_BLOCKS, chainActive.Height()); |
|||
|
|||
for (int h=nHeight; h<limit; h++) { |
|||
NotarisationsInBlock notarisations; |
|||
|
|||
if (!GetBlockNotarisations(*chainActive[h]->phashBlock, notarisations)) |
|||
continue; |
|||
|
|||
BOOST_FOREACH(found, notarisations) { |
|||
if (f(found)) { |
|||
return h; |
|||
} |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
/* On KMD */ |
|||
TxProof GetCrossChainProof(const uint256 txid, const char* targetSymbol, uint32_t targetCCid, |
|||
const TxProof assetChainProof) |
|||
{ |
|||
/*
|
|||
* Here we are given a proof generated by an assetchain A which goes from given txid to |
|||
* an assetchain MoM. We need to go from the notarisationTxid for A to the MoMoM range of the |
|||
* backnotarisation for B (given by kmdheight of notarisation), find the MoM within the MoMs for |
|||
* that range, and finally extend the proof to lead to the MoMoM (proof root). |
|||
*/ |
|||
EvalRef eval; |
|||
uint256 MoM = assetChainProof.second.Exec(txid); |
|||
|
|||
// Get a kmd height for given notarisation Txid
|
|||
int kmdHeight; |
|||
{ |
|||
CTransaction sourceNotarisation; |
|||
uint256 hashBlock; |
|||
CBlockIndex blockIdx; |
|||
if (!eval->GetTxConfirmed(assetChainProof.first, sourceNotarisation, blockIdx)) |
|||
throw std::runtime_error("Notarisation not found"); |
|||
kmdHeight = blockIdx.nHeight; |
|||
} |
|||
|
|||
// We now have a kmdHeight of the notarisation from chain A. So we know that a MoM exists
|
|||
// at that height.
|
|||
// If we call CalculateProofRoot with that height, it'll scan backwards, until it finds
|
|||
// a notarisation from B, and it might not include our notarisation from A
|
|||
// at all. So, the thing we need to do is scan forwards to find the notarisation for B,
|
|||
// that is inclusive of A.
|
|||
Notarisation nota; |
|||
auto isTarget = [&](Notarisation ¬a) { |
|||
return strcmp(nota.second.symbol, targetSymbol) == 0; |
|||
}; |
|||
kmdHeight = ScanNotarisationsFromHeight(kmdHeight, isTarget, nota); |
|||
if (!kmdHeight) |
|||
throw std::runtime_error("Cannot find notarisation for target inclusive of source"); |
|||
|
|||
// Get MoMs for kmd height and symbol
|
|||
std::vector<uint256> moms; |
|||
uint256 targetChainNotarisationTxid; |
|||
uint256 MoMoM = CalculateProofRoot(targetSymbol, targetCCid, kmdHeight, moms, targetChainNotarisationTxid); |
|||
if (MoMoM.IsNull()) |
|||
throw std::runtime_error("No MoMs found"); |
|||
|
|||
// Find index of source MoM in MoMoM
|
|||
int nIndex; |
|||
for (nIndex=0; nIndex<moms.size(); nIndex++) { |
|||
if (moms[nIndex] == MoM) |
|||
goto cont; |
|||
} |
|||
throw std::runtime_error("Couldn't find MoM within MoMoM set"); |
|||
cont: |
|||
|
|||
// Create a branch
|
|||
std::vector<uint256> vBranch; |
|||
{ |
|||
CBlock fakeBlock; |
|||
for (int i=0; i<moms.size(); i++) { |
|||
CTransaction fakeTx; |
|||
// first value in CTransaction memory is it's hash
|
|||
memcpy((void*)&fakeTx, moms[i].begin(), 32); |
|||
fakeBlock.vtx.push_back(fakeTx); |
|||
} |
|||
vBranch = fakeBlock.GetMerkleBranch(nIndex); |
|||
} |
|||
|
|||
// Concatenate branches
|
|||
MerkleBranch newBranch = assetChainProof.second; |
|||
newBranch << MerkleBranch(nIndex, vBranch); |
|||
|
|||
// Check proof
|
|||
if (newBranch.Exec(txid) != MoMoM) |
|||
throw std::runtime_error("Proof check failed"); |
|||
|
|||
return std::make_pair(targetChainNotarisationTxid,newBranch); |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Takes an importTx that has proof leading to assetchain root |
|||
* and extends proof to cross chain root |
|||
*/ |
|||
void CompleteImportTransaction(CTransaction &importTx) |
|||
{ |
|||
TxProof proof; |
|||
CTransaction burnTx; |
|||
std::vector<CTxOut> payouts; |
|||
if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) |
|||
throw std::runtime_error("Couldn't parse importTx"); |
|||
|
|||
std::string targetSymbol; |
|||
uint32_t targetCCid; |
|||
uint256 payoutsHash; |
|||
if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash)) |
|||
throw std::runtime_error("Couldn't parse burnTx"); |
|||
|
|||
proof = GetCrossChainProof(burnTx.GetHash(), targetSymbol.data(), targetCCid, proof); |
|||
|
|||
importTx = MakeImportCoinTransaction(proof, burnTx, payouts); |
|||
} |
|||
|
|||
|
|||
bool IsSameAssetChain(const Notarisation ¬a) { |
|||
return strcmp(nota.second.symbol, ASSETCHAINS_SYMBOL) == 0; |
|||
}; |
|||
|
|||
|
|||
/* On assetchain */ |
|||
bool GetNextBacknotarisation(uint256 kmdNotarisationTxid, Notarisation &out) |
|||
{ |
|||
/*
|
|||
* Here we are given a txid, and a proof. |
|||
* We go from the KMD notarisation txid to the backnotarisation, |
|||
* then jump to the next backnotarisation, which contains the corresponding MoMoM. |
|||
*/ |
|||
Notarisation bn; |
|||
if (!GetBackNotarisation(kmdNotarisationTxid, bn)) |
|||
return false; |
|||
|
|||
// Need to get block height of that backnotarisation
|
|||
EvalRef eval; |
|||
CBlockIndex block; |
|||
CTransaction tx; |
|||
if (!eval->GetTxConfirmed(bn.first, tx, block)){ |
|||
fprintf(stderr, "Can't get height of backnotarisation, this should not happen\n"); |
|||
return false; |
|||
} |
|||
|
|||
return (bool) ScanNotarisationsFromHeight(block.nHeight+1, &IsSameAssetChain, out); |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* On assetchain |
|||
* in: txid |
|||
* out: pair<notarisationTxHash,merkleBranch> |
|||
*/ |
|||
TxProof GetAssetchainProof(uint256 hash) |
|||
{ |
|||
int nIndex; |
|||
CBlockIndex* blockIndex; |
|||
Notarisation nota; |
|||
std::vector<uint256> branch; |
|||
|
|||
{ |
|||
uint256 blockHash; |
|||
CTransaction tx; |
|||
if (!GetTransaction(hash, tx, blockHash, true)) |
|||
throw std::runtime_error("cannot find transaction"); |
|||
|
|||
if (blockHash.IsNull()) |
|||
throw std::runtime_error("tx still in mempool"); |
|||
|
|||
blockIndex = mapBlockIndex[blockHash]; |
|||
int h = blockIndex->nHeight; |
|||
// The assumption here is that the first notarisation for a height GTE than
|
|||
// the transaction block height will contain the corresponding MoM. If there
|
|||
// are sequence issues with the notarisations this may fail.
|
|||
auto isTarget = [&](Notarisation ¬a) { |
|||
if (!IsSameAssetChain(nota)) return false; |
|||
return nota.second.height >= blockIndex->nHeight; |
|||
}; |
|||
if (!ScanNotarisationsFromHeight(blockIndex->nHeight, isTarget, nota)) |
|||
throw std::runtime_error("backnotarisation not yet confirmed"); |
|||
|
|||
// index of block in MoM leaves
|
|||
nIndex = nota.second.height - blockIndex->nHeight; |
|||
} |
|||
|
|||
// build merkle chain from blocks to MoM
|
|||
{ |
|||
std::vector<uint256> leaves, tree; |
|||
for (int i=0; i<nota.second.MoMDepth; i++) { |
|||
uint256 mRoot = chainActive[nota.second.height - i]->hashMerkleRoot; |
|||
leaves.push_back(mRoot); |
|||
} |
|||
bool fMutated; |
|||
BuildMerkleTree(&fMutated, leaves, tree); |
|||
branch = GetMerkleBranch(nIndex, leaves.size(), tree); |
|||
|
|||
// Check branch
|
|||
uint256 ourResult = SafeCheckMerkleBranch(blockIndex->hashMerkleRoot, branch, nIndex); |
|||
if (nota.second.MoM != ourResult) |
|||
throw std::runtime_error("Failed merkle block->MoM"); |
|||
} |
|||
|
|||
// Now get the tx merkle branch
|
|||
{ |
|||
CBlock block; |
|||
|
|||
if (fHavePruned && !(blockIndex->nStatus & BLOCK_HAVE_DATA) && blockIndex->nTx > 0) |
|||
throw std::runtime_error("Block not available (pruned data)"); |
|||
|
|||
if(!ReadBlockFromDisk(block, blockIndex,1)) |
|||
throw std::runtime_error("Can't read block from disk"); |
|||
|
|||
// Locate the transaction in the block
|
|||
int nTxIndex; |
|||
for (nTxIndex = 0; nTxIndex < (int)block.vtx.size(); nTxIndex++) |
|||
if (block.vtx[nTxIndex].GetHash() == hash) |
|||
break; |
|||
|
|||
if (nTxIndex == (int)block.vtx.size()) |
|||
throw std::runtime_error("Error locating tx in block"); |
|||
|
|||
std::vector<uint256> txBranch = block.GetMerkleBranch(nTxIndex); |
|||
|
|||
// Check branch
|
|||
if (block.hashMerkleRoot != CBlock::CheckMerkleBranch(hash, txBranch, nTxIndex)) |
|||
throw std::runtime_error("Failed merkle tx->block"); |
|||
|
|||
// concatenate branches
|
|||
nIndex = (nIndex << txBranch.size()) + nTxIndex; |
|||
branch.insert(branch.begin(), txBranch.begin(), txBranch.end()); |
|||
} |
|||
|
|||
// Check the proof
|
|||
if (nota.second.MoM != CBlock::CheckMerkleBranch(hash, branch, nIndex)) |
|||
throw std::runtime_error("Failed validating MoM"); |
|||
|
|||
// All done!
|
|||
CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION); |
|||
return std::make_pair(nota.second.txHash, MerkleBranch(nIndex, branch)); |
|||
} |
@ -0,0 +1,21 @@ |
|||
#ifndef CROSSCHAIN_H |
|||
#define CROSSCHAIN_H |
|||
|
|||
#include "cc/eval.h" |
|||
|
|||
|
|||
/* On assetchain */ |
|||
TxProof GetAssetchainProof(uint256 hash); |
|||
|
|||
/* On KMD */ |
|||
uint256 CalculateProofRoot(const char* symbol, uint32_t targetCCid, int kmdHeight, |
|||
std::vector<uint256> &moms, uint256 &destNotarisationTxid); |
|||
TxProof GetCrossChainProof(const uint256 txid, const char* targetSymbol, uint32_t targetCCid, |
|||
const TxProof assetChainProof); |
|||
void CompleteImportTransaction(CTransaction &importTx); |
|||
|
|||
/* On assetchain */ |
|||
bool GetNextBacknotarisation(uint256 txid, std::pair<uint256,NotarisationData> &bn); |
|||
|
|||
|
|||
#endif /* CROSSCHAIN_H */ |
@ -0,0 +1,2 @@ |
|||
#!/bin/bash |
|||
./komodo-cli -ac_name=RFOX $1 $2 $3 $4 $5 $6 |
@ -0,0 +1,2 @@ |
|||
#!/bin/bash |
|||
./komodo-cli -ac_name=VRSC $1 $2 $3 $4 $5 $6 |
@ -0,0 +1,121 @@ |
|||
#include "crosschain.h" |
|||
#include "importcoin.h" |
|||
#include "cc/utils.h" |
|||
#include "coins.h" |
|||
#include "hash.h" |
|||
#include "script/cc.h" |
|||
#include "primitives/transaction.h" |
|||
|
|||
|
|||
CTransaction MakeImportCoinTransaction(const TxProof proof, const CTransaction burnTx, const std::vector<CTxOut> payouts) |
|||
{ |
|||
std::vector<uint8_t> payload = E_MARSHAL(ss << EVAL_IMPORTCOIN); |
|||
CMutableTransaction mtx; |
|||
mtx.vin.push_back(CTxIn(COutPoint(burnTx.GetHash(), 10e8), CScript() << payload)); |
|||
mtx.vout = payouts; |
|||
auto importData = E_MARSHAL(ss << proof; ss << burnTx); |
|||
mtx.vout.insert(mtx.vout.begin(), CTxOut(0, CScript() << OP_RETURN << importData)); |
|||
return CTransaction(mtx); |
|||
} |
|||
|
|||
|
|||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector<CTxOut> payouts) |
|||
{ |
|||
std::vector<uint8_t> opret = E_MARSHAL(ss << VARINT(targetCCid); |
|||
ss << targetSymbol; |
|||
ss << SerializeHash(payouts)); |
|||
return CTxOut(value, CScript() << OP_RETURN << opret); |
|||
} |
|||
|
|||
|
|||
bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, |
|||
std::vector<CTxOut> &payouts) |
|||
{ |
|||
std::vector<uint8_t> vData; |
|||
GetOpReturnData(importTx.vout[0].scriptPubKey, vData); |
|||
if (importTx.vout.size() < 1) return false; |
|||
payouts = std::vector<CTxOut>(importTx.vout.begin()+1, importTx.vout.end()); |
|||
return importTx.vin.size() == 1 && |
|||
importTx.vin[0].scriptSig == (CScript() << E_MARSHAL(ss << EVAL_IMPORTCOIN)) && |
|||
E_UNMARSHAL(vData, ss >> proof; ss >> burnTx); |
|||
} |
|||
|
|||
|
|||
bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash) |
|||
{ |
|||
std::vector<uint8_t> burnOpret; |
|||
if (burnTx.vout.size() == 0) return false; |
|||
GetOpReturnData(burnTx.vout[0].scriptPubKey, burnOpret); |
|||
return E_UNMARSHAL(burnOpret, ss >> VARINT(*targetCCid); |
|||
ss >> targetSymbol; |
|||
ss >> payoutsHash); |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Required by main |
|||
*/ |
|||
CAmount GetCoinImportValue(const CTransaction &tx) |
|||
{ |
|||
TxProof proof; |
|||
CTransaction burnTx; |
|||
std::vector<CTxOut> payouts; |
|||
if (UnmarshalImportTx(tx, proof, burnTx, payouts)) { |
|||
return burnTx.vout.size() ? burnTx.vout[0].nValue : 0; |
|||
} |
|||
return 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"); |
|||
} |
|||
|
|||
|
|||
void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, int nHeight) |
|||
{ |
|||
uint256 burnHash = importTx.vin[0].prevout.hash; |
|||
CCoinsModifier modifier = inputs.ModifyCoins(burnHash); |
|||
modifier->nHeight = nHeight; |
|||
modifier->nVersion = 1; |
|||
modifier->vout.push_back(CTxOut(0, CScript() << OP_0)); |
|||
} |
|||
|
|||
|
|||
void RemoveImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs) |
|||
{ |
|||
uint256 burnHash = importTx.vin[0].prevout.hash; |
|||
inputs.ModifyCoins(burnHash)->Clear(); |
|||
} |
|||
|
|||
|
|||
int ExistsImportTombstone(const CTransaction &importTx, const CCoinsViewCache &inputs) |
|||
{ |
|||
uint256 burnHash = importTx.vin[0].prevout.hash; |
|||
return inputs.HaveCoins(burnHash); |
|||
} |
@ -0,0 +1,28 @@ |
|||
#ifndef IMPORTCOIN_H |
|||
#define IMPORTCOIN_H |
|||
|
|||
#include "cc/eval.h" |
|||
#include "coins.h" |
|||
#include "primitives/transaction.h" |
|||
#include "script/interpreter.h" |
|||
#include <cryptoconditions.h> |
|||
|
|||
|
|||
CAmount GetCoinImportValue(const CTransaction &tx); |
|||
|
|||
CTransaction MakeImportCoinTransaction(const TxProof proof, |
|||
const CTransaction burnTx, const std::vector<CTxOut> payouts); |
|||
|
|||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector<CTxOut> payouts); |
|||
|
|||
bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash); |
|||
bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, |
|||
std::vector<CTxOut> &payouts); |
|||
|
|||
bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state); |
|||
|
|||
void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, int nHeight); |
|||
void RemoveImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs); |
|||
int ExistsImportTombstone(const CTransaction &importTx, const CCoinsViewCache &inputs); |
|||
|
|||
#endif /* IMPORTCOIN_H */ |
@ -0,0 +1,84 @@ |
|||
#include "leveldbwrapper.h" |
|||
#include "notarisationdb.h" |
|||
#include "uint256.h" |
|||
#include "cc/eval.h" |
|||
|
|||
#include <boost/foreach.hpp> |
|||
|
|||
|
|||
NotarisationDB *pnotarisations; |
|||
|
|||
|
|||
NotarisationDB::NotarisationDB(size_t nCacheSize, bool fMemory, bool fWipe) : CLevelDBWrapper(GetDataDir() / "notarisations", nCacheSize, fMemory, fWipe, false, 64) { } |
|||
|
|||
|
|||
NotarisationsInBlock ScanBlockNotarisations(const CBlock &block, int nHeight) |
|||
{ |
|||
EvalRef eval; |
|||
NotarisationsInBlock vNotarisations; |
|||
|
|||
for (unsigned int i = 0; i < block.vtx.size(); i++) { |
|||
CTransaction tx = block.vtx[i]; |
|||
|
|||
// Special case for TXSCL. Should prob be removed at some point.
|
|||
bool isTxscl = 0; |
|||
{ |
|||
NotarisationData data; |
|||
if (ParseNotarisationOpReturn(tx, data)) |
|||
if (strlen(data.symbol) >= 5 && strncmp(data.symbol, "TXSCL", 5) == 0) |
|||
isTxscl = 1; |
|||
} |
|||
|
|||
if (isTxscl || eval->CheckNotaryInputs(tx, nHeight, block.nTime)) { |
|||
NotarisationData data; |
|||
if (ParseNotarisationOpReturn(tx, data)) { |
|||
vNotarisations.push_back(std::make_pair(tx.GetHash(), data)); |
|||
//printf("Parsed a notarisation for: %s, txid:%s, ccid:%i, momdepth:%i\n",
|
|||
// data.symbol, tx.GetHash().GetHex().data(), data.ccId, data.MoMDepth);
|
|||
//if (!data.MoMoM.IsNull()) printf("MoMoM:%s\n", data.MoMoM.GetHex().data());
|
|||
} |
|||
else |
|||
LogPrintf("WARNING: Couldn't parse notarisation for tx: %s at height %i\n", |
|||
tx.GetHash().GetHex().data(), nHeight); |
|||
} |
|||
} |
|||
return vNotarisations; |
|||
} |
|||
|
|||
|
|||
bool GetBlockNotarisations(uint256 blockHash, NotarisationsInBlock &nibs) |
|||
{ |
|||
return pnotarisations->Read(blockHash, nibs); |
|||
} |
|||
|
|||
|
|||
bool GetBackNotarisation(uint256 notarisationHash, Notarisation &n) |
|||
{ |
|||
return pnotarisations->Read(notarisationHash, n); |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Write an index of KMD notarisation id -> backnotarisation |
|||
*/ |
|||
void WriteBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch) |
|||
{ |
|||
int wrote = 0; |
|||
BOOST_FOREACH(const Notarisation &n, notarisations) |
|||
{ |
|||
if (!n.second.txHash.IsNull()) { |
|||
batch.Write(n.second.txHash, n); |
|||
wrote++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
void EraseBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch) |
|||
{ |
|||
BOOST_FOREACH(const Notarisation &n, notarisations) |
|||
{ |
|||
if (!n.second.txHash.IsNull()) |
|||
batch.Erase(n.second.txHash); |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
#ifndef NOTARISATIONDB_H |
|||
#define NOTARISATIONDB_H |
|||
|
|||
#include "uint256.h" |
|||
#include "leveldbwrapper.h" |
|||
#include "cc/eval.h" |
|||
|
|||
|
|||
class NotarisationDB : public CLevelDBWrapper |
|||
{ |
|||
public: |
|||
NotarisationDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); |
|||
}; |
|||
|
|||
|
|||
extern NotarisationDB *pnotarisations; |
|||
|
|||
typedef std::pair<uint256,NotarisationData> Notarisation; |
|||
typedef std::vector<Notarisation> NotarisationsInBlock; |
|||
|
|||
NotarisationsInBlock ScanBlockNotarisations(const CBlock &block, int nHeight); |
|||
bool GetBlockNotarisations(uint256 blockHash, NotarisationsInBlock &nibs); |
|||
bool GetBackNotarisation(uint256 notarisationHash, Notarisation &n); |
|||
void WriteBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch); |
|||
void EraseBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch); |
|||
|
|||
#endif /* NOTARISATIONDB_H */ |
@ -0,0 +1,253 @@ |
|||
#include "amount.h" |
|||
#include "chain.h" |
|||
#include "chainparams.h" |
|||
#include "checkpoints.h" |
|||
#include "crosschain.h" |
|||
#include "importcoin.h" |
|||
#include "base58.h" |
|||
#include "consensus/validation.h" |
|||
#include "cc/eval.h" |
|||
#include "cc/utils.h" |
|||
#include "main.h" |
|||
#include "primitives/transaction.h" |
|||
#include "rpcserver.h" |
|||
#include "sync.h" |
|||
#include "util.h" |
|||
#include "script/script.h" |
|||
#include "script/script_error.h" |
|||
#include "script/sign.h" |
|||
#include "script/standard.h" |
|||
|
|||
#include <stdint.h> |
|||
#include <univalue.h> |
|||
#include <regex> |
|||
|
|||
|
|||
using namespace std; |
|||
|
|||
int32_t komodo_MoM(int32_t *notarized_htp,uint256 *MoMp,uint256 *kmdtxidp,int32_t nHeight,uint256 *MoMoMp,int32_t *MoMoMoffsetp,int32_t *MoMoMdepthp,int32_t *kmdstartip,int32_t *kmdendip); |
|||
int32_t komodo_MoMoMdata(char *hexstr,int32_t hexsize,struct komodo_ccdataMoMoM *mdata,char *symbol,int32_t kmdheight,int32_t notarized_height); |
|||
struct komodo_ccdata_entry *komodo_allMoMs(int32_t *nump,uint256 *MoMoMp,int32_t kmdstarti,int32_t kmdendi); |
|||
uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); |
|||
|
|||
|
|||
UniValue assetchainproof(const UniValue& params, bool fHelp) |
|||
{ |
|||
uint256 hash; |
|||
|
|||
// parse params and get notarisation data for tx
|
|||
if ( fHelp || params.size() != 1) |
|||
throw runtime_error("assetchainproof needs a txid"); |
|||
|
|||
hash = uint256S(params[0].get_str()); |
|||
|
|||
auto proof = GetAssetchainProof(hash); |
|||
auto proofData = E_MARSHAL(ss << proof); |
|||
return HexStr(proofData); |
|||
} |
|||
|
|||
|
|||
UniValue crosschainproof(const UniValue& params, bool fHelp) |
|||
{ |
|||
|
|||
} |
|||
|
|||
|
|||
UniValue height_MoM(const UniValue& params, bool fHelp) |
|||
{ |
|||
int32_t height,depth,notarized_height,MoMoMdepth,MoMoMoffset,kmdstarti,kmdendi; uint256 MoM,MoMoM,kmdtxid; uint32_t timestamp = 0; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); |
|||
if ( fHelp || params.size() != 1 ) |
|||
throw runtime_error("height_MoM height\n"); |
|||
LOCK(cs_main); |
|||
height = atoi(params[0].get_str().c_str()); |
|||
if ( height <= 0 ) |
|||
{ |
|||
if ( chainActive.Tip() == 0 ) |
|||
{ |
|||
ret.push_back(Pair("error",(char *)"no active chain yet")); |
|||
return(ret); |
|||
} |
|||
height = chainActive.Tip()->nHeight; |
|||
} |
|||
//fprintf(stderr,"height_MoM height.%d\n",height);
|
|||
depth = komodo_MoM(¬arized_height,&MoM,&kmdtxid,height,&MoMoM,&MoMoMoffset,&MoMoMdepth,&kmdstarti,&kmdendi); |
|||
ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); |
|||
ret.push_back(Pair("height",height)); |
|||
ret.push_back(Pair("timestamp",(uint64_t)timestamp)); |
|||
if ( depth > 0 ) |
|||
{ |
|||
ret.push_back(Pair("depth",depth)); |
|||
ret.push_back(Pair("notarized_height",notarized_height)); |
|||
ret.push_back(Pair("MoM",MoM.GetHex())); |
|||
ret.push_back(Pair("kmdtxid",kmdtxid.GetHex())); |
|||
if ( ASSETCHAINS_SYMBOL[0] != 0 ) |
|||
{ |
|||
ret.push_back(Pair("MoMoM",MoMoM.GetHex())); |
|||
ret.push_back(Pair("MoMoMoffset",MoMoMoffset)); |
|||
ret.push_back(Pair("MoMoMdepth",MoMoMdepth)); |
|||
ret.push_back(Pair("kmdstarti",kmdstarti)); |
|||
ret.push_back(Pair("kmdendi",kmdendi)); |
|||
} |
|||
} else ret.push_back(Pair("error",(char *)"no MoM for height")); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
UniValue MoMoMdata(const UniValue& params, bool fHelp) |
|||
{ |
|||
if ( fHelp || params.size() != 3 ) |
|||
throw runtime_error("MoMoMdata symbol kmdheight ccid\n"); |
|||
UniValue ret(UniValue::VOBJ); |
|||
char* symbol = (char *)params[0].get_str().c_str(); |
|||
int kmdheight = atoi(params[1].get_str().c_str()); |
|||
uint32_t ccid = atoi(params[2].get_str().c_str()); |
|||
ret.push_back(Pair("coin",symbol)); |
|||
ret.push_back(Pair("kmdheight",kmdheight)); |
|||
ret.push_back(Pair("ccid", (int) ccid)); |
|||
|
|||
uint256 destNotarisationTxid; |
|||
std::vector<uint256> moms; |
|||
uint256 MoMoM = CalculateProofRoot(symbol, ccid, kmdheight, moms, destNotarisationTxid); |
|||
|
|||
UniValue valMoms(UniValue::VARR); |
|||
for (int i=0; i<moms.size(); i++) valMoms.push_back(moms[i].GetHex()); |
|||
ret.push_back(Pair("MoMs", valMoms)); |
|||
ret.push_back(Pair("notarization_hash", destNotarisationTxid.GetHex())); |
|||
ret.push_back(Pair("MoMoM", MoMoM.GetHex())); |
|||
auto vmomomdata = E_MARSHAL(ss << MoMoM; ss << ((uint32_t)0)); |
|||
ret.push_back(Pair("data", HexStr(vmomomdata))); |
|||
return ret; |
|||
} |
|||
|
|||
|
|||
UniValue calc_MoM(const UniValue& params, bool fHelp) |
|||
{ |
|||
int32_t height,MoMdepth; uint256 MoM; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); |
|||
if ( fHelp || params.size() != 2 ) |
|||
throw runtime_error("calc_MoM height MoMdepth\n"); |
|||
LOCK(cs_main); |
|||
height = atoi(params[0].get_str().c_str()); |
|||
MoMdepth = atoi(params[1].get_str().c_str()); |
|||
if ( height <= 0 || MoMdepth <= 0 || MoMdepth >= height ) |
|||
throw runtime_error("calc_MoM illegal height or MoMdepth\n"); |
|||
//fprintf(stderr,"height_MoM height.%d\n",height);
|
|||
MoM = komodo_calcMoM(height,MoMdepth); |
|||
ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); |
|||
ret.push_back(Pair("height",height)); |
|||
ret.push_back(Pair("MoMdepth",MoMdepth)); |
|||
ret.push_back(Pair("MoM",MoM.GetHex())); |
|||
return ret; |
|||
} |
|||
|
|||
|
|||
UniValue migrate_converttoexport(const UniValue& params, bool fHelp) |
|||
{ |
|||
if (fHelp || params.size() != 3) |
|||
throw runtime_error( |
|||
"migrate_converttoexport rawTx dest_symbol export_amount\n" |
|||
"\nConvert a raw transaction to a cross-chain export.\n" |
|||
"If neccesary, the transaction should be funded using fundrawtransaction.\n" |
|||
"Finally, the transaction should be signed using signrawtransaction\n" |
|||
"The finished export transaction, plus the payouts, should be passed to " |
|||
"the \"migrate_createimporttransaction\" method on a KMD node to get the corresponding " |
|||
"import transaction.\n" |
|||
); |
|||
|
|||
if (ASSETCHAINS_CC < 2) |
|||
throw runtime_error("-ac_cc < 2"); |
|||
|
|||
if (ASSETCHAINS_SYMBOL[0] == 0) |
|||
throw runtime_error("Must be called on assetchain"); |
|||
|
|||
vector<uint8_t> txData(ParseHexV(params[0], "argument 1")); |
|||
CMutableTransaction tx; |
|||
if (!E_UNMARSHAL(txData, ss >> tx)) |
|||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); |
|||
|
|||
string targetSymbol = params[1].get_str(); |
|||
if (targetSymbol.size() == 0 || targetSymbol.size() > 32) |
|||
throw runtime_error("targetSymbol length must be >0 and <=32"); |
|||
|
|||
CAmount burnAmount = AmountFromValue(params[2]); |
|||
if (burnAmount <= 0) |
|||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for export"); |
|||
{ |
|||
CAmount needed = 0; |
|||
for (int i=0; i<tx.vout.size(); i++) needed += tx.vout[i].nValue; |
|||
if (burnAmount < needed) |
|||
throw runtime_error("export_amount too small"); |
|||
} |
|||
|
|||
CTxOut burnOut = MakeBurnOutput(burnAmount, ASSETCHAINS_CC, targetSymbol, tx.vout); |
|||
UniValue ret(UniValue::VOBJ); |
|||
ret.push_back(Pair("payouts", HexStr(E_MARSHAL(ss << tx.vout)))); |
|||
tx.vout.clear(); |
|||
tx.vout.push_back(burnOut); |
|||
ret.push_back(Pair("exportTx", HexStr(E_MARSHAL(ss << tx)))); |
|||
return ret; |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* The process to migrate funds |
|||
* |
|||
* Create a transaction on assetchain: |
|||
* |
|||
* generaterawtransaction |
|||
* migrate_converttoexport |
|||
* fundrawtransaction |
|||
* signrawtransaction |
|||
* |
|||
* migrate_createimportransaction |
|||
* migrate_completeimporttransaction |
|||
*/ |
|||
|
|||
UniValue migrate_createimporttransaction(const UniValue& params, bool fHelp) |
|||
{ |
|||
if (fHelp || params.size() != 2) |
|||
throw runtime_error("migrate_createimporttransaction burnTx payouts\n\n" |
|||
"Create an importTx given a burnTx and the corresponding payouts, hex encoded"); |
|||
|
|||
if (ASSETCHAINS_CC < 2) |
|||
throw runtime_error("-ac_cc < 2"); |
|||
|
|||
if (ASSETCHAINS_SYMBOL[0] == 0) |
|||
throw runtime_error("Must be called on assetchain"); |
|||
|
|||
vector<uint8_t> txData(ParseHexV(params[0], "argument 1")); |
|||
|
|||
CTransaction burnTx; |
|||
if (!E_UNMARSHAL(txData, ss >> burnTx)) |
|||
throw runtime_error("Couldn't parse burnTx"); |
|||
|
|||
|
|||
vector<CTxOut> payouts; |
|||
if (!E_UNMARSHAL(ParseHexV(params[1], "argument 2"), ss >> payouts)) |
|||
throw runtime_error("Couldn't parse payouts"); |
|||
|
|||
uint256 txid = burnTx.GetHash(); |
|||
TxProof proof = GetAssetchainProof(burnTx.GetHash()); |
|||
|
|||
CTransaction importTx = MakeImportCoinTransaction(proof, burnTx, payouts); |
|||
return HexStr(E_MARSHAL(ss << importTx)); |
|||
} |
|||
|
|||
|
|||
UniValue migrate_completeimporttransaction(const UniValue& params, bool fHelp) |
|||
{ |
|||
if (fHelp || params.size() != 1) |
|||
throw runtime_error("migrate_completeimporttransaction importTx\n\n" |
|||
"Takes a cross chain import tx with proof generated on assetchain " |
|||
"and extends proof to target chain proof root"); |
|||
|
|||
if (ASSETCHAINS_SYMBOL[0] != 0) |
|||
throw runtime_error("Must be called on KMD"); |
|||
|
|||
CTransaction importTx; |
|||
if (!E_UNMARSHAL(ParseHexV(params[0], "argument 1"), ss >> importTx)) |
|||
throw runtime_error("Couldn't parse importTx"); |
|||
|
|||
CompleteImportTransaction(importTx); |
|||
|
|||
return HexStr(E_MARSHAL(ss << importTx)); |
|||
} |
@ -1,14 +1,22 @@ |
|||
#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) { |
|||
assert(init_and_check_sodium() != -1); |
|||
ECC_Start(); |
|||
ECCVerifyHandle handle; // Inits secp256k1 verify context
|
|||
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(); |
|||
} |
|||
|
@ -0,0 +1,257 @@ |
|||
|
|||
#include <cryptoconditions.h> |
|||
#include <gtest/gtest.h> |
|||
|
|||
#include "cc/eval.h" |
|||
#include "importcoin.h" |
|||
#include "base58.h" |
|||
#include "core_io.h" |
|||
#include "key.h" |
|||
#include "main.h" |
|||
#include "primitives/transaction.h" |
|||
#include "script/cc.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; |
|||
TxProof proof; |
|||
uint256 MoMoM; |
|||
CMutableTransaction importTx; |
|||
uint32_t testCcid = 2; |
|||
std::string testSymbol = "PIZZA"; |
|||
CAmount amount = 100; |
|||
|
|||
void SetImportTx() { |
|||
burnTx.vout.resize(0); |
|||
burnTx.vout.push_back(MakeBurnOutput(amount, testCcid, testSymbol, payouts)); |
|||
importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts)); |
|||
MoMoM = burnTx.GetHash(); // TODO: an actual branch
|
|||
} |
|||
|
|||
uint32_t GetAssetchainsCC() const { return testCcid; } |
|||
std::string GetAssetchainsSymbol() const { return testSymbol; } |
|||
|
|||
bool GetProofRoot(uint256 hash, uint256 &momom) const |
|||
{ |
|||
if (MoMoM.IsNull()) return false; |
|||
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 be 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, testImportTombstone) |
|||
{ |
|||
CValidationState mainstate; |
|||
// By setting an unspendable output, there will be no addition to UTXO
|
|||
// Nonetheless, we dont want to be able to import twice
|
|||
payouts[0].scriptPubKey = CScript() << OP_RETURN; |
|||
SetImportTx(); |
|||
MoMoM = burnTx.GetHash(); // TODO: an actual branch
|
|||
CTransaction tx(importTx); |
|||
|
|||
// first should work
|
|||
acceptTxFail(tx); |
|||
|
|||
// should be in persisted UTXO set
|
|||
generateBlock(); |
|||
ASSERT_FALSE(acceptTx(tx, mainstate)); |
|||
EXPECT_EQ("import tombstone exists", mainstate.GetRejectReason()); |
|||
ASSERT_TRUE(pcoinsTip->HaveCoins(burnTx.GetHash())); |
|||
|
|||
// Now disconnect the block
|
|||
CValidationState invalstate; |
|||
if (!InvalidateBlock(invalstate, chainActive.Tip())) { |
|||
FAIL() << invalstate.GetRejectReason(); |
|||
} |
|||
// Tombstone should be gone from utxo set
|
|||
ASSERT_FALSE(pcoinsTip->HaveCoins(burnTx.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("too-few-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-tx", state.GetRejectReason()); |
|||
} |
|||
|
|||
|
|||
TEST_F(TestCoinImport, testInvalidBurnParams) |
|||
{ |
|||
burnTx.vout[0].scriptPubKey = CScript() << OP_RETURN << E_MARSHAL(ss << VARINT(testCcid)); |
|||
MoMoM = burnTx.GetHash(); // TODO: an actual branch
|
|||
CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); |
|||
TestRunCCEval(tx); |
|||
EXPECT_EQ("invalid-burn-tx", state.GetRejectReason()); |
|||
} |
|||
|
|||
|
|||
TEST_F(TestCoinImport, testWrongChainId) |
|||
{ |
|||
testCcid = 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[1].nValue = 101; |
|||
TestRunCCEval(importTx); |
|||
EXPECT_EQ("payout-too-high", state.GetRejectReason()); |
|||
} |
|||
|
|||
|
|||
TEST_F(TestCoinImport, testAmountInOpret) |
|||
{ |
|||
importTx.vout[0].nValue = 1; |
|||
TestRunCCEval(importTx); |
|||
EXPECT_EQ("non-canonical", state.GetRejectReason()); |
|||
} |
|||
|
|||
|
|||
|
|||
TEST_F(TestCoinImport, testInvalidPayouts) |
|||
{ |
|||
importTx.vout[1].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()); |
|||
} |
|||
|
|||
|
|||
TEST_F(TestCoinImport, testGetCoinImportValue) |
|||
{ |
|||
ASSERT_EQ(100, GetCoinImportValue(importTx)); |
|||
} |
|||
|
|||
} /* namespace TestCoinImport */ |
@ -0,0 +1,213 @@ |
|||
#include <zmq.h> |
|||
#include <stdio.h> |
|||
#include <unistd.h> |
|||
#include <string.h> |
|||
#include <assert.h> |
|||
|
|||
#include <cryptoconditions.h> |
|||
#include <gtest/gtest.h> |
|||
|
|||
#include "cc/eval.h" |
|||
#include "importcoin.h" |
|||
#include "base58.h" |
|||
#include "core_io.h" |
|||
#include "crosschain.h" |
|||
#include "key.h" |
|||
#include "komodo_structs.h" |
|||
#include "main.h" |
|||
#include "notarisationdb.h" |
|||
#include "primitives/block.h" |
|||
#include "primitives/transaction.h" |
|||
#include "script/cc.h" |
|||
#include "script/interpreter.h" |
|||
#include "script/serverchecker.h" |
|||
#include "txmempool.h" |
|||
#include "crosschain.h" |
|||
|
|||
#include "testutils.h" |
|||
|
|||
|
|||
extern uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); |
|||
extern bool KOMODO_TEST_ASSETCHAIN_SKIP_POW; |
|||
|
|||
|
|||
/*
|
|||
* Tests for the whole process of creating and validating notary proofs |
|||
* using proof roots (MoMoMs). This is to support coin imports. |
|||
*/ |
|||
|
|||
namespace TestCrossChainProof { |
|||
|
|||
|
|||
class TestCrossChain : public ::testing::Test, public Eval { |
|||
public: |
|||
bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const |
|||
{ |
|||
NotarisationData data(2); |
|||
return ParseNotarisationOpReturn(tx, data); // If it parses it's valid
|
|||
} |
|||
protected: |
|||
static void SetUpTestCase() { } |
|||
virtual void SetUp() { |
|||
KOMODO_TEST_ASSETCHAIN_SKIP_POW = 1; |
|||
ASSETCHAINS_CC = 1; |
|||
EVAL_TEST = this; |
|||
} |
|||
}; |
|||
|
|||
|
|||
uint256 endianHash(uint256 h) |
|||
{ |
|||
uint256 out; |
|||
for (int i=0; i<32; i++) { |
|||
out.begin()[31-i] = h.begin()[i]; |
|||
} |
|||
return out; |
|||
} |
|||
|
|||
|
|||
TEST_F(TestCrossChain, testCreateAndValidateImportProof) |
|||
{ |
|||
/*
|
|||
* This tests the full process of creation of a cross chain proof. |
|||
* For the purposes of the test we will use one assetchain and a KMD chain. |
|||
* |
|||
* In order to do this test, we need 2 blockchains, so we'll fork and make a socket |
|||
* for IPC. |
|||
*/ |
|||
|
|||
int childPid = fork(); |
|||
void *ctx = zmq_ctx_new(); |
|||
void *socket = zmq_socket(ctx, ZMQ_PAIR); |
|||
if (!childPid) |
|||
strcpy(ASSETCHAINS_SYMBOL, "PIZZA"); |
|||
setupChain(); |
|||
std::vector<CBlock> blocks; |
|||
blocks.resize(1000); |
|||
NotarisationData a2kmd(0), kmd2a(1); |
|||
int numTestNotarisations = 10; |
|||
|
|||
|
|||
auto SendIPC = [&] (std::vector<uint8_t> v) { |
|||
assert(v.size() == zmq_send(socket, v.data(), v.size(), 0)); |
|||
}; |
|||
|
|||
auto RecvIPC = [&] () { |
|||
std::vector<uint8_t> out; |
|||
out.resize(100000); |
|||
int len = zmq_recv(socket, out.data(), out.size(), 0); |
|||
assert(len != -1); |
|||
out.resize(len); |
|||
return out; |
|||
}; |
|||
|
|||
auto RecordNotarisation = [&] (CTransaction inputTx, NotarisationData data) { |
|||
CMutableTransaction mtx = spendTx(inputTx); |
|||
mtx.vout.resize(2); |
|||
mtx.vout[0].scriptPubKey << VCH(notaryKey.GetPubKey().begin(), 33) << OP_CHECKSIG; |
|||
mtx.vout[1].scriptPubKey << OP_RETURN << E_MARSHAL(ss << data); |
|||
mtx.vout[1].nValue = 0; |
|||
mtx.vin[0].scriptSig << getSig(mtx, inputTx.vout[0].scriptPubKey); |
|||
|
|||
acceptTxFail(CTransaction(mtx)); |
|||
return mtx.GetHash(); |
|||
}; |
|||
|
|||
auto RunTestAssetchain = [&] () |
|||
{ |
|||
NotarisationData n(0), back(1); |
|||
strcpy(n.symbol, "PIZZA"); |
|||
n.ccId = 2; |
|||
int height = 0; |
|||
|
|||
/*
|
|||
* Send notarisations and write backnotarisations |
|||
*/ |
|||
for (int ni=0; ni<numTestNotarisations; ni++) |
|||
{ |
|||
generateBlock(&blocks[++height]); |
|||
generateBlock(&blocks[++height]); |
|||
n.blockHash = blocks[height].GetHash(); |
|||
n.MoM = endianHash(komodo_calcMoM(n.height=height, n.MoMDepth=2)); |
|||
SendIPC(E_MARSHAL(ss << n)); |
|||
assert(E_UNMARSHAL(RecvIPC(), ss >> back)); |
|||
RecordNotarisation(blocks[height].vtx[0], back); |
|||
} |
|||
|
|||
/*
|
|||
* Test a proof |
|||
*/ |
|||
uint256 txid = blocks[7].vtx[0].GetHash(); |
|||
TxProof proof = GetAssetchainProof(txid); |
|||
SendIPC(E_MARSHAL(ss << txid; ss << proof)); |
|||
E_UNMARSHAL(RecvIPC(), ss >> proof); |
|||
|
|||
std::pair<uint256,NotarisationData> bn; |
|||
if (!GetNextBacknotarisation(proof.first, bn)) { |
|||
printf("GetNextBackNotarisation failed\n"); |
|||
return 1; |
|||
} |
|||
if (proof.second.Exec(txid) != bn.second.MoMoM) { |
|||
printf("MoMom incorrect\n"); |
|||
return 1; |
|||
} |
|||
return 0; |
|||
}; |
|||
|
|||
auto RunTestKmd = [&] () |
|||
{ |
|||
NotarisationData n(0); |
|||
int height = 0; |
|||
|
|||
/*
|
|||
* Write notarisations and send backnotarisations |
|||
*/ |
|||
for (int ni=0; ni<numTestNotarisations; ni++) |
|||
{ |
|||
n.IsBackNotarisation = 0; |
|||
E_UNMARSHAL(RecvIPC(), ss >> n); |
|||
// Grab a coinbase input to fund notarisation
|
|||
generateBlock(&blocks[++height]); |
|||
n.txHash = RecordNotarisation(blocks[height].vtx[0], n); |
|||
{ |
|||
std::vector<uint256> moms; |
|||
uint256 destNotarisationTxid; |
|||
n.MoMoM = CalculateProofRoot(n.symbol, 2, height, moms, destNotarisationTxid); |
|||
} |
|||
n.IsBackNotarisation = 1; |
|||
SendIPC(E_MARSHAL(ss << n)); |
|||
} |
|||
|
|||
/*
|
|||
* Extend proof |
|||
*/ |
|||
TxProof proof; |
|||
uint256 txid; |
|||
// Extend proof to MoMoM
|
|||
assert(E_UNMARSHAL(RecvIPC(), ss >> txid; ss >> proof)); |
|||
proof = GetCrossChainProof(txid, (char*)"PIZZA", 2, proof); |
|||
SendIPC(E_MARSHAL(ss << proof)); |
|||
}; |
|||
|
|||
const char endpoint[] = "ipc://tmpKomodoTestCrossChainSock"; |
|||
|
|||
if (!childPid) { |
|||
assert(0 == zmq_connect(socket, endpoint)); |
|||
usleep(20000); |
|||
int out = RunTestAssetchain(); |
|||
if (!out) printf("Assetchain success\n"); |
|||
exit(out); |
|||
} |
|||
else { |
|||
assert(0 == zmq_bind(socket, endpoint)); |
|||
RunTestKmd(); |
|||
int returnStatus; |
|||
waitpid(childPid, &returnStatus, 0); |
|||
unlink("tmpKomodoTestCrossChainSock"); |
|||
ASSERT_EQ(0, returnStatus); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} /* namespace TestCrossChainProof */ |
@ -0,0 +1,54 @@ |
|||
#include <gtest/gtest.h> |
|||
|
|||
#include "cc/eval.h" |
|||
#include "core_io.h" |
|||
#include "key.h" |
|||
|
|||
#include "testutils.h" |
|||
|
|||
|
|||
namespace TestParseNotarisation { |
|||
|
|||
class TestParseNotarisation : public ::testing::Test, public Eval {}; |
|||
|
|||
|
|||
TEST(TestParseNotarisation, test_ee2fa) |
|||
{ |
|||
// ee2fa47820a31a979f9f21cb3fedbc484bf9a8957cb6c9acd0af28ced29bdfe1
|
|||
std::vector<uint8_t> opret = ParseHex("c349ff90f3bce62c1b7b49d1da0423b1a3d9b733130cce825b95b9e047c729066e020d00743a06fdb95ad5775d032b30bbb3680dac2091a0f800cf54c79fd3461ce9b31d4b4d4400"); |
|||
NotarisationData nd; |
|||
ASSERT_TRUE(E_UNMARSHAL(opret, ss >> nd)); |
|||
} |
|||
|
|||
TEST(TestParseNotarisation, test__) |
|||
{ |
|||
// 576e910a1f704207bcbcf724124ff9adc5237f45cb6919589cd0aa152caec424
|
|||
std::vector<uint8_t> opret = ParseHex("b3ed7fbbfbc027caeeeec81e65489ec5d9cd47cda675a5cbb75b4a845e67cf0ef6330300b5a6bd8385feb833f3be961c9d8a46fcecd36dcdfa42ad81a20a892433722f0b4b4d44004125a06024eae24c11f36ea110acd707b041d5355b6e1b42de5e2614357999c6aa02000d26ad0300000000404b4c000000000005130300500d000061f22ba7d19fe29ac3baebd839af8b7127d1f90755534400"); |
|||
NotarisationData nd; |
|||
// We can't parse this one
|
|||
ASSERT_FALSE(E_UNMARSHAL(opret, ss >> nd)); |
|||
} |
|||
|
|||
TEST(TestParseNotarisation, test__a) |
|||
{ |
|||
// be55101e6c5a93fb3611a44bd66217ad8714d204275ea4e691cfff9d65dff85c TXSCL
|
|||
std::vector<uint8_t> opret = ParseHex("fb9ea2818eec8b07f8811bab49d64379db074db478997f8114666f239bd79803cc460000d0fac4e715b7e2b917a5d79f85ece0c423d27bd3648fd39ac1dc7db8e1bd4b16545853434c00a69eab9f23d7fb63c4624973e7a9079d6ada2f327040936356d7af5e849f6d670a0003001caf7b7b9e1c9bc59d0c7a619c9683ab1dd0794b6f3ea184a19f8fda031150e700000000"); |
|||
NotarisationData nd(1); |
|||
bool res = E_UNMARSHAL(opret, ss >> nd); |
|||
ASSERT_TRUE(res); |
|||
} |
|||
|
|||
TEST(TestParseNotarisation, test__b) |
|||
{ |
|||
// 03085dafed656aaebfda25bf43ffe9d1fb72565bb1fc8b2a12a631659f28f877 TXSCL
|
|||
std::vector<uint8_t> opret = ParseHex("48c71a10aa060eab1a43f52acefac3b81fb2a2ce310186b06141884c0501d403c246000052e6d49afd82d9ab3d97c996dd9b6a78a554ffa1625e8dadf0494bd1f8442e3e545853434c007cc5c07e3b67520fd14e23cd5b49f2aa022f411500fd3326ff91e6dc0544a1c90c0003008b69117bb1376ac8df960f785d8c208c599d3a36248c98728256bb6d4737e59600000000"); |
|||
NotarisationData nd(1); |
|||
bool res = E_UNMARSHAL(opret, ss >> nd); |
|||
ASSERT_TRUE(res); |
|||
} |
|||
|
|||
|
|||
|
|||
// for l in `g 'parse notarisation' ~/.komodo/debug.log | pyline 'l.split()[8]'`; do hoek decodeTx '{"hex":"'`src/komodo-cli getrawtransaction "$l"`'"}' | jq '.outputs[1].script.op_return' | pyline 'import base64; print base64.b64decode(l).encode("hex")'; done
|
|||
|
|||
} |
@ -0,0 +1,147 @@ |
|||
#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 "notarisationdb.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; |
|||
extern std::string NOTARY_PUBKEY; |
|||
|
|||
void setupChain() |
|||
{ |
|||
SelectParams(CBaseChainParams::REGTEST); |
|||
|
|||
// Settings to get block reward
|
|||
NOTARY_PUBKEY = notaryPubkey; |
|||
USE_EXTERNAL_PUBKEY = 1; |
|||
mapArgs["-mineraddress"] = "bogus"; |
|||
COINBASE_MATURITY = 1; |
|||
// Global mock time
|
|||
nMockTime = GetTime(); |
|||
|
|||
// Unload
|
|||
UnloadBlockIndex(); |
|||
|
|||
// Init blockchain
|
|||
ClearDatadirCache(); |
|||
auto pathTemp = GetTempPath() / strprintf("test_komodo_%li_%i", GetTime(), GetRand(100000)); |
|||
if (ASSETCHAINS_SYMBOL[0]) |
|||
pathTemp = pathTemp / strprintf("_%s", ASSETCHAINS_SYMBOL); |
|||
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); |
|||
pnotarisations = new NotarisationDB(1 << 20, true); |
|||
InitBlockIndex(); |
|||
} |
|||
|
|||
|
|||
void generateBlock(CBlock *block) |
|||
{ |
|||
UniValue params; |
|||
params.setArray(); |
|||
params.push_back(1); |
|||
uint256 blockId; |
|||
|
|||
SetMockTime(nMockTime+=100); // CreateNewBlock can fail if not enough time passes
|
|||
|
|||
try { |
|||
UniValue out = generate(params, false); |
|||
blockId.SetHex(out[0].getValStr()); |
|||
if (block) ASSERT_TRUE(ReadBlockFromDisk(*block, mapBlockIndex[blockId], false)); |
|||
} catch (const UniValue& e) { |
|||
FAIL() << "failed to create block: " << e.write().data(); |
|||
} |
|||
} |
|||
|
|||
|
|||
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); |
|||
} |
|||
|
|||
|
|||
CMutableTransaction spendTx(const CTransaction &txIn, int nOut) |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
|
|||
std::vector<uint8_t> getSig(const CMutableTransaction mtx, CScript inputPubKey, int nIn) |
|||
{ |
|||
uint256 hash = SignatureHash(inputPubKey, mtx, nIn, SIGHASH_ALL, 0, 0); |
|||
std::vector<uint8_t> vchSig; |
|||
notaryKey.Sign(hash, vchSig); |
|||
vchSig.push_back((unsigned char)SIGHASH_ALL); |
|||
return vchSig; |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* 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); |
|||
} |
Loading…
Reference in new issue