tombstone for import

This commit is contained in:
Scott Sadler
2018-04-17 08:40:02 -05:00
parent 0cb91a8d20
commit 3c7604133e
6 changed files with 109 additions and 29 deletions

View File

@@ -1,4 +1,5 @@
#include "cc/importcoin.h" #include "cc/importcoin.h"
#include "coins.h"
#include "hash.h" #include "hash.h"
#include "script/cc.h" #include "script/cc.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
@@ -16,9 +17,7 @@ CTransaction MakeImportCoinTransaction(const MomoProof proof, const CTransaction
std::vector<uint8_t> payload = std::vector<uint8_t> payload =
E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx); E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx);
CMutableTransaction mtx; CMutableTransaction mtx;
mtx.vin.resize(1); mtx.vin.push_back(CTxIn(COutPoint(burnTx.GetHash(), 10e8), CScript() << payload));
mtx.vin[0].scriptSig << payload;
mtx.vin[0].prevout.n = 10e8;
mtx.vout = payouts; mtx.vout = payouts;
return CTransaction(mtx); return CTransaction(mtx);
} }
@@ -95,25 +94,33 @@ bool Eval::ImportCoin(const std::vector<uint8_t> params, const CTransaction &imp
} }
/* static bool UnmarshalImportTx(const CTransaction &importTx, MomoProof &proof, CTransaction &burnTx)
* Required by main
*/
CAmount GetCoinImportValue(const CTransaction &tx)
{ {
CScript scriptSig = tx.vin[0].scriptSig; CScript scriptSig = importTx.vin[0].scriptSig;
auto pc = scriptSig.begin(); auto pc = scriptSig.begin();
opcodetype opcode; opcodetype opcode;
std::vector<uint8_t> evalScript; std::vector<uint8_t> evalScript;
if (!scriptSig.GetOp(pc, opcode, evalScript)) int code;
return false; bool out = false;
if (pc != scriptSig.end()) if (scriptSig.GetOp(pc, opcode, evalScript))
return false; if (pc == scriptSig.end())
EvalCode code; out = E_UNMARSHAL(evalScript, ss >> VARINT(code); ss >> proof; ss >> burnTx);
return code == EVAL_IMPORTCOIN && out;
}
/*
* Required by main
* TODO: test
*/
CAmount GetCoinImportValue(const CTransaction &tx)
{
MomoProof proof; MomoProof proof;
CTransaction burnTx; CTransaction burnTx;
if (!E_UNMARSHAL(evalScript, ss >> proof; ss >> burnTx)) if (UnmarshalImportTx(tx, proof, burnTx)) {
return 0; return burnTx.vout.size() ? burnTx.vout[0].nValue : 0;
return burnTx.vout.size() ? burnTx.vout[0].nValue : 0; }
return 0;
} }
@@ -145,3 +152,27 @@ bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& che
return f() ? true : state.Invalid(false, 0, "invalid-coin-import"); 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);
}

View File

@@ -2,6 +2,7 @@
#define CC_IMPORTCOIN_H #define CC_IMPORTCOIN_H
#include "cc/eval.h" #include "cc/eval.h"
#include "coins.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "script/interpreter.h" #include "script/interpreter.h"
#include <cryptoconditions.h> #include <cryptoconditions.h>
@@ -30,4 +31,9 @@ CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector<CTxOut>
bool VerifyCoinImport(const CScript& scriptSig, bool VerifyCoinImport(const CScript& scriptSig,
TransactionSignatureChecker& checker, CValidationState &state); 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 /* CC_IMPORTCOIN_H */ #endif /* CC_IMPORTCOIN_H */

View File

@@ -97,11 +97,6 @@ public:
nVersion = tx.nVersion; nVersion = tx.nVersion;
//nLockTime = tx.nLockTime; //nLockTime = tx.nLockTime;
ClearUnspendable(); 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 //! construct a CCoins from a CTransaction, at a given height

View File

@@ -1332,15 +1332,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
view.SetBackend(viewMemPool); view.SetBackend(viewMemPool);
// do we already have it? // 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)) if (view.HaveCoins(hash))
{ {
fprintf(stderr,"view.HaveCoins(hash) error\n"); fprintf(stderr,"view.HaveCoins(hash) error\n");
return state.Invalid(false, REJECT_DUPLICATE, "already have coins"); return state.Invalid(false, REJECT_DUPLICATE, "already have coins");
} }
if (!tx.IsCoinImport()) if (tx.IsCoinImport())
{
// Inverse of normal case; if input exists, it's been spent
if (ExistsImportTombstone(tx, view))
return state.Invalid(false, REJECT_DUPLICATE, "import tombstone exists");
}
else
{ {
// do all inputs exist? // do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that), // Note that this does not check for the presence of actual outputs (see the next check for that),
@@ -2007,6 +2011,13 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
} }
} }
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); // add outputs inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); // add outputs
// Unorthodox state
if (tx.IsCoinImport()) {
// add a tombstone for the burnTx
AddImportTombstone(tx, inputs, nHeight);
}
} }
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
@@ -2465,6 +2476,10 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
} }
} }
} }
else if (tx.IsCoinImport())
{
RemoveImportTombstone(tx, view);
}
} }
// set the old best anchor back // set the old best anchor back

View File

@@ -454,7 +454,7 @@ public:
bool IsMint() const bool IsMint() const
{ {
return (vin.size() == 1 && vin[0].prevout.hash.IsNull()); return IsCoinImport() || IsCoinBase();
} }
bool IsCoinBase() const bool IsCoinBase() const
@@ -464,7 +464,7 @@ public:
bool IsCoinImport() const bool IsCoinImport() const
{ {
return IsMint() && vin[0].prevout.n == 10e8; return (vin.size() == 1 && vin[0].prevout.n == 10e8);
} }
friend bool operator==(const CTransaction& a, const CTransaction& b) friend bool operator==(const CTransaction& a, const CTransaction& b)

View File

@@ -35,8 +35,8 @@ public:
CAmount amount = 100; CAmount amount = 100;
void SetImportTx() { void SetImportTx() {
CTxOut burnOutput = MakeBurnOutput(amount, chainId, payouts); burnTx.vout.resize(0);
burnTx.vout.push_back(burnOutput); burnTx.vout.push_back(MakeBurnOutput(amount, chainId, payouts));
MoMoM = burnTx.GetHash(); // TODO: an actual branch MoMoM = burnTx.GetHash(); // TODO: an actual branch
importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts)); importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts));
} }
@@ -88,7 +88,7 @@ TEST_F(TestCoinImport, testProcessImportThroughPipeline)
ASSERT_FALSE(acceptTx(tx, mainstate)); ASSERT_FALSE(acceptTx(tx, mainstate));
EXPECT_EQ("already in mempool", mainstate.GetRejectReason()); EXPECT_EQ("already in mempool", mainstate.GetRejectReason());
// should fail in persisted UTXO set // should be in persisted UTXO set
generateBlock(); generateBlock();
ASSERT_FALSE(acceptTx(tx, mainstate)); ASSERT_FALSE(acceptTx(tx, mainstate));
EXPECT_EQ("already have coins", mainstate.GetRejectReason()); EXPECT_EQ("already have coins", mainstate.GetRejectReason());
@@ -107,6 +107,39 @@ TEST_F(TestCoinImport, testProcessImportThroughPipeline)
} }
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) TEST_F(TestCoinImport, testNoVouts)
{ {
importTx.vout.resize(0); importTx.vout.resize(0);