diff --git a/src/cc/eval.cpp b/src/cc/eval.cpp index 8ed29da9d..edd5c6850 100644 --- a/src/cc/eval.cpp +++ b/src/cc/eval.cpp @@ -147,12 +147,6 @@ 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 (on KMD) */ @@ -171,6 +165,19 @@ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data) */ bool Eval::GetProofRoot(uint256 kmdNotarisationHash, uint256 &momom) const { + return false; // TODO +} + + +uint32_t Eval::GetAssetchainsCC() const +{ + return ASSETCHAINS_CC; +} + + +std::string Eval::GetAssetchainsSymbol() const +{ + return std::string(ASSETCHAINS_SYMBOL); } diff --git a/src/cc/eval.h b/src/cc/eval.h index 2a30bba90..aacec3847 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -74,7 +74,8 @@ public: virtual bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const; virtual bool GetProofRoot(uint256 kmdNotarisationHash, uint256 &momom) const; virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const; - virtual uint32_t GetCurrentLedgerID() const; + virtual uint32_t GetAssetchainsCC() const; + virtual std::string GetAssetchainsSymbol() const; }; diff --git a/src/cc/import.cpp b/src/cc/import.cpp index 202d75468..506b2a989 100644 --- a/src/cc/import.cpp +++ b/src/cc/import.cpp @@ -12,30 +12,31 @@ */ bool Eval::ImportCoin(const std::vector params, const CTransaction &importTx, unsigned int nIn) { - if (importTx.vout.size() == 0) return Invalid("no-vouts"); + if (importTx.vout.size() < 2) + return Invalid("too-few-vouts"); // params TxProof proof; CTransaction burnTx; - if (!E_UNMARSHAL(params, ss >> proof; ss >> burnTx)) + std::vector payouts; + + if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) 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()) + // It should not be at all malleable + if (MakeImportCoinTransaction(proof, burnTx, payouts).GetHash() != importTx.GetHash()) return Invalid("non-canonical"); // burn params - uint32_t chain; // todo + uint32_t targetCcid; + std::string targetSymbol; uint256 payoutsHash; - std::vector 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()) + if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCcid, payoutsHash)) + return Invalid("invalid-burn-tx"); + + if (targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol()) return Invalid("importcoin-wrong-chain"); // check burn amount @@ -51,7 +52,7 @@ bool Eval::ImportCoin(const std::vector params, const CTransaction &imp } // Check burntx shows correct outputs hash - if (payoutsHash != SerializeHash(importTx.vout)) + if (payoutsHash != SerializeHash(payouts)) return Invalid("wrong-payouts"); // Check proof confirms existance of burnTx diff --git a/src/importcoin.cpp b/src/importcoin.cpp index e0c260916..bbada7e99 100644 --- a/src/importcoin.cpp +++ b/src/importcoin.cpp @@ -6,42 +6,48 @@ #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 TxProof proof, const CTransaction burnTx, const std::vector payouts) { - std::vector payload = - E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx); + std::vector 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, int targetChain, const std::vector payouts) + +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts) { - std::vector opret = E_MARSHAL(ss << VARINT(targetChain); ss << SerializeHash(payouts)); + std::vector opret = E_MARSHAL(ss << VARINT(targetCCid); + ss << targetSymbol; + ss << SerializeHash(payouts)); return CTxOut(value, CScript() << OP_RETURN << opret); } -static bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx) +bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, + std::vector &payouts) { - CScript scriptSig = importTx.vin[0].scriptSig; - auto pc = scriptSig.begin(); - opcodetype opcode; - std::vector evalScript; - int code; - bool out = false; - if (scriptSig.GetOp(pc, opcode, evalScript)) - if (pc == scriptSig.end()) - out = E_UNMARSHAL(evalScript, ss >> VARINT(code); ss >> proof; ss >> burnTx); - return code == EVAL_IMPORTCOIN && out; + std::vector vData; + GetOpReturnData(importTx.vout[0].scriptPubKey, vData); + if (importTx.vout.size() < 1) return false; + payouts = std::vector(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 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); } @@ -53,7 +59,8 @@ CAmount GetCoinImportValue(const CTransaction &tx) { TxProof proof; CTransaction burnTx; - if (UnmarshalImportTx(tx, proof, burnTx)) { + std::vector payouts; + if (UnmarshalImportTx(tx, proof, burnTx, payouts)) { return burnTx.vout.size() ? burnTx.vout[0].nValue : 0; } return 0; diff --git a/src/importcoin.h b/src/importcoin.h index f59851c44..8e43f9376 100644 --- a/src/importcoin.h +++ b/src/importcoin.h @@ -13,10 +13,13 @@ CAmount GetCoinImportValue(const CTransaction &tx); CTransaction MakeImportCoinTransaction(const TxProof proof, const CTransaction burnTx, const std::vector payouts); -CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector payouts); +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts); -bool VerifyCoinImport(const CScript& scriptSig, - TransactionSignatureChecker& checker, CValidationState &state); +bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash); +bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, + std::vector &payouts); + +bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state); void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, int nHeight); diff --git a/src/rpccrosschain.cpp b/src/rpccrosschain.cpp index a7bb49d2e..c66a805e7 100644 --- a/src/rpccrosschain.cpp +++ b/src/rpccrosschain.cpp @@ -3,9 +3,11 @@ #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" @@ -156,3 +158,126 @@ UniValue calc_MoM(const UniValue& params, bool fHelp) 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 \"hexstring\" \"dest_symbol\" \"burn_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 vouts, should be passed to " + "the \"importtransaction\" 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 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 = params[2].get_int64(); + { + CAmount needed; + for (int i=0; i txData(ParseHexV(params[0], "argument 1")); + + CTransaction burnTx; + if (!E_UNMARSHAL(txData, ss >> burnTx)) + throw runtime_error("Couldn't parse burnTx"); + + + vector payouts; + if (!E_UNMARSHAL(ParseHexV(params[0], "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(""); + + if (ASSETCHAINS_SYMBOL[0] != 0) + throw runtime_error("Must be called on KMD"); + + CTransaction importTx; + if (!E_UNMARSHAL(ParseHexV(params[0], "argument 2"), ss >> importTx)) + throw runtime_error("Couldn't parse importTx"); + + TxProof proof; + CTransaction burnTx; + vector payouts; + if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) + throw runtime_error("Couldn't parse importTx data"); + + std::string targetSymbol; + uint32_t targetCCid; + uint256 payoutsHash; + if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash)) + throw runtime_error("Couldn't parse burnTx data"); + + proof = GetCrossChainProof(burnTx.GetHash(), targetSymbol.data(), targetCCid, proof); + + importTx = MakeImportCoinTransaction(proof, burnTx, importTx.vout); + + return HexStr(E_MARSHAL(ss << importTx)); +} diff --git a/src/test-komodo/test_coinimport.cpp b/src/test-komodo/test_coinimport.cpp index c1db6cc78..3cd6eff6e 100644 --- a/src/test-komodo/test_coinimport.cpp +++ b/src/test-komodo/test_coinimport.cpp @@ -31,17 +31,19 @@ public: TxProof proof; uint256 MoMoM; CMutableTransaction importTx; - uint32_t chainId = 2; + uint32_t testCcid = 2; + std::string testSymbol = "PIZZA"; CAmount amount = 100; void SetImportTx() { burnTx.vout.resize(0); - burnTx.vout.push_back(MakeBurnOutput(amount, chainId, payouts)); - MoMoM = burnTx.GetHash(); // TODO: an actual branch + 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 GetCurrentLedgerID() const { return chainId; } + uint32_t GetAssetchainsCC() const { return testCcid; } + std::string GetAssetchainsSymbol() const { return testSymbol; } bool GetProofRoot(uint256 hash, uint256 &momom) const { @@ -145,7 +147,7 @@ TEST_F(TestCoinImport, testNoVouts) { importTx.vout.resize(0); TestRunCCEval(importTx); - EXPECT_EQ("no-vouts", state.GetRejectReason()); + EXPECT_EQ("too-few-vouts", state.GetRejectReason()); } @@ -172,23 +174,23 @@ TEST_F(TestCoinImport, testInvalidBurnOutputs) MoMoM = burnTx.GetHash(); // TODO: an actual branch CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); TestRunCCEval(tx); - EXPECT_EQ("invalid-burn-outputs", state.GetRejectReason()); + EXPECT_EQ("invalid-burn-tx", state.GetRejectReason()); } TEST_F(TestCoinImport, testInvalidBurnParams) { - burnTx.vout[0].scriptPubKey = CScript() << OP_RETURN << E_MARSHAL(ss << VARINT(chainId)); + 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-params", state.GetRejectReason()); + EXPECT_EQ("invalid-burn-tx", state.GetRejectReason()); } TEST_F(TestCoinImport, testWrongChainId) { - chainId = 0; + testCcid = 0; TestRunCCEval(importTx); EXPECT_EQ("importcoin-wrong-chain", state.GetRejectReason()); } @@ -206,15 +208,24 @@ TEST_F(TestCoinImport, testInvalidBurnAmount) TEST_F(TestCoinImport, testPayoutTooHigh) { - importTx.vout[0].nValue = 101; + 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[0].nValue = 40; + importTx.vout[1].nValue = 40; importTx.vout.push_back(importTx.vout[0]); TestRunCCEval(importTx); EXPECT_EQ("wrong-payouts", state.GetRejectReason()); diff --git a/src/test-komodo/testutils.cpp b/src/test-komodo/testutils.cpp index e8b57b6b9..59ea4307a 100644 --- a/src/test-komodo/testutils.cpp +++ b/src/test-komodo/testutils.cpp @@ -47,6 +47,9 @@ void setupChain() COINBASE_MATURITY = 1; // Global mock time nMockTime = GetTime(); + + // Unload + UnloadBlockIndex(); // Init blockchain ClearDatadirCache();