From c19a65d56aba1f670e78dfbe83e48841f9bd413c Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 6 Dec 2014 14:40:48 -0800 Subject: [PATCH] Add sidechain tracking and double-spend proof generation --- src/init.cpp | 21 +++++++ src/main.cpp | 139 +++++++++++++++++++++++++++++++++++++++++- src/main.h | 3 + src/script/script.cpp | 14 +++++ src/script/script.h | 3 + 5 files changed, 177 insertions(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index ed05c15729..9c536c2e4e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -745,6 +745,27 @@ bool AppInit2(boost::thread_group& threadGroup) fIsBareMultisigStd = GetArg("-permitbaremultisig", true) != 0; nMaxDatacarrierBytes = GetArg("-datacarriersize", nMaxDatacarrierBytes); + if (mapMultiArgs.count("-tracksidechain")) + { + if (!GetBoolArg("-txindex", false)) + return InitError(_("Cannot -tracksidechain without keeping a -txindex")); + + BOOST_FOREACH(std::string& str, mapMultiArgs["-tracksidechain"]) + { + if (str == "all") + { + sidechainWithdrawsTracked.clear(); + sidechainWithdrawsTracked.insert(0); + break; + } + else if (IsHex(str) && str.length() == 64) + sidechainWithdrawsTracked.insert(uint256(str)); + else + return InitError(strprintf(_("Invalid value to -tracksidechain: '%s' (must be a hex-encoded genesis block hash)"), + str)); + } + } + // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log // Sanity check diff --git a/src/main.cpp b/src/main.cpp index 43a3246436..5778395b85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,6 +54,9 @@ bool fIsBareMultisigStd = true; bool fCheckBlockIndex = false; unsigned int nCoinCacheSize = 5000; +//TODO: Require reindex if this changes +std::set sidechainWithdrawsTracked; + /** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */ CFeeRate minRelayTxFee = CFeeRate(1000); @@ -1498,10 +1501,19 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach // mark inputs spent if (!tx.IsCoinBase()) { txundo.vprevout.reserve(tx.vin.size()); - BOOST_FOREACH(const CTxIn &txin, tx.vin) { + for (unsigned int i = 0; i < tx.vin.size(); i++) { + const CTxIn &txin = tx.vin[i]; txundo.vprevout.push_back(CTxInUndo()); - bool ret = inputs.ModifyCoins(txin.prevout.hash)->Spend(txin.prevout, txundo.vprevout.back()); - assert(ret); + CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash); + assert(coins->IsAvailable(txin.prevout.n)); + //TODO: Check height is after softfork (for mainchain) + if (sidechainWithdrawsTracked.size() > 0 && coins->vout[txin.prevout.n].scriptPubKey.IsWithdrawLock(0)) { + assert(txin.scriptSig.IsWithdrawProof()); + pair outpoint = make_pair(coins->vout[txin.prevout.n].scriptPubKey.GetWithdrawLockGenesisHash(), txin.scriptSig.GetWithdrawSpent()); + if (sidechainWithdrawsTracked.count(0) || sidechainWithdrawsTracked.count(outpoint.first)) + inputs.MaybeSetWithdrawSpent(outpoint, COutPoint(tx.GetHash(), i)); + } + assert(coins->Spend(txin.prevout, txundo.vprevout.back())); } } @@ -1695,6 +1707,21 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex if (coins->vout.size() < out.n+1) coins->vout.resize(out.n+1); coins->vout[out.n] = undo.txout; + + if (undo.txout.scriptPubKey.IsWithdrawLock(0)) { //TODO: Check height is after softfork (for mainchain) + if (!tx.vin[j].scriptSig.IsWithdrawProof()) + fClean = fClean && error("DisconnectBlock() : lock spent by non-proof"); + else { + pair outpoint = make_pair(undo.txout.scriptPubKey.GetWithdrawLockGenesisHash(), tx.vin[j].scriptSig.GetWithdrawSpent()); + if (sidechainWithdrawsTracked.count(0) || sidechainWithdrawsTracked.count(outpoint.first)) { + COutPoint spender = view.GetWithdrawSpent(outpoint); + if (spender.IsNull()) + fClean = fClean && error("DisconnectBlock() : withdraw not marked spent"); + else if (spender == COutPoint(tx.GetHash(), i)) + view.MaybeSetWithdrawSpent(outpoint, COutPoint()); + } + } + } } } } @@ -1853,6 +1880,112 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL)) return false; control.Add(vChecks); + + // Auto-generate double-spend withdraw proofs (if neccessary) + if (sidechainWithdrawsTracked.size() > 0) { + for (unsigned int j = 0; j < tx.vin.size(); j++) { + const CTxIn &txin = tx.vin[j]; + + const CCoins* coins = view.AccessCoins(txin.prevout.hash); + const CScript &withdrawLockScript = coins->vout[txin.prevout.n].scriptPubKey; + if (withdrawLockScript.IsWithdrawLock(0)) { //TODO: Check height is after softfork (for mainchain) + assert(txin.scriptSig.IsWithdrawProof()); + pair outpoint = make_pair(withdrawLockScript.GetWithdrawLockGenesisHash(), txin.scriptSig.GetWithdrawSpent()); + if (sidechainWithdrawsTracked.count(0) || sidechainWithdrawsTracked.count(outpoint.first)) { + COutPoint doubleSpent = view.GetWithdrawSpent(outpoint); + if (!doubleSpent.IsNull()) { + LogPrintf("Found double-spend of %s in transaction %s! Creating fraud proof.\n", doubleSpent.hash.ToString(), tx.GetHash().ToString()); + CTransaction dsTx; + uint256 dsBlockHash; + CBlock dsBlock; + set txSet; + txSet.insert(tx.GetHash()); + set dsTxSet; + + if (!GetTransaction(doubleSpent.hash, dsTx, dsBlockHash, false) || dsBlockHash == 0 || !mapBlockIndex.count(dsBlockHash)) { + // Same block...find the transaction + bool fTxFound = false; + for (unsigned int k = 0; k < i; k++) { + if (block.vtx[k].GetHash() == doubleSpent.hash) { + fTxFound = true; + dsTx = block.vtx[k]; + break; + } + } + assert(fTxFound); + + dsBlock = block; + dsBlockHash = block.GetHash(); + txSet.insert(doubleSpent.hash); + } else { + assert(ReadBlockFromDisk(dsBlock, mapBlockIndex[dsBlockHash])); + dsTxSet.insert(doubleSpent.hash); + } + + CMerkleBlock dsMerkleBlock(dsBlock, dsTxSet); + + assert(doubleSpent.n < dsTx.vin.size()); + const COutPoint &doubleSpentInpoint = dsTx.vin[doubleSpent.n].prevout; + CTransaction dsInputTx; + uint256 dsInputTxBlockHash; + if (!GetTransaction(doubleSpentInpoint.hash, dsInputTx, dsInputTxBlockHash, false) || dsInputTxBlockHash == 0 || !mapBlockIndex.count(dsInputTxBlockHash)) { + // Same block...find the transaction + bool fTxFound = false; + for (unsigned int k = 0; k < i; k++) { + if (block.vtx[k].GetHash() == doubleSpentInpoint.hash) { + fTxFound = true; + dsInputTx = block.vtx[k]; + break; + } + } + assert(fTxFound); + } + + assert(tx.vout.size() > j); + CMutableTransaction proofTx; + proofTx.vin.push_back(CTxIn(COutPoint(tx.GetHash(), j))); + CScript &scriptSig = proofTx.vin[0].scriptSig; + if (!dsTxSet.empty()) { + CDataStream mb(SER_NETWORK, PROTOCOL_VERSION); + mb << (CMerkleBlock(dsBlock, dsTxSet)); + scriptSig.PushWithdraw(std::vector(mb.begin(), mb.end())); + } + + CDataStream dsInputTxDS(SER_NETWORK, PROTOCOL_VERSION); + dsInputTxDS << dsInputTx; + scriptSig.PushWithdraw(std::vector(dsInputTxDS.begin(), dsInputTxDS.end())); + + scriptSig << doubleSpent.n; + + CDataStream dsTxDS(SER_NETWORK, PROTOCOL_VERSION); + dsTxDS << dsTx; + scriptSig.PushWithdraw(std::vector(dsTxDS.begin(), dsTxDS.end())); + + CDataStream dsMerkleBlockDS(SER_NETWORK, PROTOCOL_VERSION); + dsMerkleBlockDS << (CMerkleBlock(block, txSet)); + scriptSig.PushWithdraw(std::vector(dsMerkleBlockDS.begin(), dsMerkleBlockDS.end())); + + scriptSig << OP_1 << OP_1; + + proofTx.vout.push_back(CTxOut(0, CScript())); + proofTx.vout[0].scriptPubKey << std::vector(outpoint.first.begin(), outpoint.first.end()) << std::vector(withdrawLockScript.end() - 21, withdrawLockScript.end() - 1) << OP_WITHDRAWPROOFVERIFY; + // Because miners can take it anyway, we just devote the whole fraud bounty to miner fee + const CScript &withdrawOutputScript = tx.vout[j].scriptPubKey; + assert(withdrawOutputScript.IsWithdrawOutput()); + proofTx.vout[0].nValue = tx.vout[j].nValue - withdrawOutputScript.GetFraudBounty(); + + //TODO: Add to mempool + CDataStream proofDS(SER_NETWORK, PROTOCOL_VERSION); + proofDS << proofTx; + fprintf(stderr, "DOUBLE-SPEND PROOF TX:\n"); + for (CDataStream::iterator it = proofDS.begin(); it != proofDS.end(); it++) + fprintf(stderr, "%02x", (unsigned char)*it); + fprintf(stderr, "\n"); + } + } + } + } + } } CTxUndo undoDummy; diff --git a/src/main.h b/src/main.h index 57169cf6c2..65f6568c55 100644 --- a/src/main.h +++ b/src/main.h @@ -128,6 +128,9 @@ extern bool fCheckBlockIndex; extern unsigned int nCoinCacheSize; extern CFeeRate minRelayTxFee; +/** Set of sidechains for which we will automatically create double-spend fraud proofs for */ +extern std::set sidechainWithdrawsTracked; + /** Best header we've seen so far (used for getheaders queries' starting points). */ extern CBlockIndex *pindexBestHeader; diff --git a/src/script/script.cpp b/src/script/script.cpp index 14289a9008..ab5eeda991 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -480,6 +480,20 @@ COutPoint CScript::GetWithdrawSpent() const } } +void CScript::PushWithdraw(const vector push) { + int64_t pushCount = 0; + for (vector::const_iterator it = push.begin(); it < push.end(); pushCount++) { + if (push.end() - it < 520) { + *this << vector(it, push.end()); + it = push.end(); + } else { + *this << vector(it, it + 520); + it += 520; + } + } + *this << pushCount; +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: diff --git a/src/script/script.h b/src/script/script.h index 41503b69c7..7ccd311980 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -608,6 +608,9 @@ public: */ bool IsWithdrawLock(const uint256 hashGenesisBlock, bool fRequireDestination=false, bool fRequireToUs=false) const; + //! Push a vector with a length postfix (as used by withdraw proofs) + void PushWithdraw(const vector push); + /** Get the withdraw output spent, asserting IsWithdrawProof first */ COutPoint GetWithdrawSpent() const;