From 92bc14233f7bc1937f3784460de29c65e8d47f28 Mon Sep 17 00:00:00 2001 From: janko33bd Date: Fri, 5 Jan 2018 22:06:35 +0100 Subject: [PATCH] Implementation of OP_COUNT_ACK --- src/Makefile.am | 2 + src/main.cpp | 64 ++++++++- src/main.h | 8 +- src/script/drivechain.cpp | 142 ++++++++++++++++++++ src/script/drivechain.h | 260 +++++++++++++++++++++++++++++++++++++ src/script/interpreter.cpp | 35 ++++- src/script/interpreter.h | 5 + src/script/script.h | 15 ++- src/serialize.h | 23 ++++ 9 files changed, 545 insertions(+), 9 deletions(-) create mode 100644 src/script/drivechain.cpp create mode 100644 src/script/drivechain.h diff --git a/src/Makefile.am b/src/Makefile.am index c07ba8880..0269b0e69 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -219,6 +219,8 @@ libbitcoin_server_a_SOURCES = \ rpcrawtransaction.cpp \ rpcserver.cpp \ script/sigcache.cpp \ + script/drivechain.cpp \ + script/drivechain.h \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ diff --git a/src/main.cpp b/src/main.cpp index 197065334..51d349b74 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ #include "pubkey.h" #include "primitives/block.h" #include "primitives/transaction.h" +#include "script/drivechain.h" #include "script/script.h" #include "script/sigcache.h" #include "script/standard.h" @@ -1882,9 +1883,63 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach UpdateCoins(tx, state, inputs, txundo, nHeight); } +class CachingTransactionSignatureCheckerWithBlockReader : public CachingTransactionSignatureChecker, public BaseBlockReader { + int nHeight; + uint256 hash; +public: + virtual int GetBlockNumber() const; + + virtual CTransaction GetBlockCoinbase(int blockNumber) const; + + virtual bool CountAcks(const std::vector& chainId, int periodAck, int periodLiveness, int& positive, int& negative) const; + + CachingTransactionSignatureCheckerWithBlockReader(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, bool storeIn, int height) + :CachingTransactionSignatureChecker(txToIn, nInIn, storeIn), nHeight(height), hash(txToIn->GetHash()) + { + } +}; + +int CachingTransactionSignatureCheckerWithBlockReader::GetBlockNumber() const +{ + return nHeight; +} + +CTransaction CachingTransactionSignatureCheckerWithBlockReader::GetBlockCoinbase(int blockNumber) const +{ + //AssertLockHeld(cs_main); + + int nHeight = blockNumber; + if (nHeight < 0 || nHeight > chainActive.Height()) + return CTransaction(); + + CBlockIndex* pblockindex = chainActive[nHeight]; + + CBlock block; + + if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) + return CTransaction(); + + if(!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) + return CTransaction(); + + if (block.vtx.empty()) + return CTransaction(); + + if (!block.vtx[0].IsCoinBase()) + return CTransaction(); + + return block.vtx[0]; +} + +bool CachingTransactionSignatureCheckerWithBlockReader::CountAcks(const std::vector& chainId, int periodAck, int periodLiveness, int& positiveAcks, int& negativeAcks) const +{ + std::vector hashSpend(hash.begin(), hash.end()); + return ::CountAcks(hashSpend, chainId, periodAck, periodLiveness, positiveAcks, negativeAcks, *this); +} + bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; - if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) { + if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, nHeight, cacheStore), &error)) { return false; } return true; @@ -1959,7 +2014,8 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi { if (!tx.IsCoinBase()) { - if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs))) + int height = GetSpendHeight(inputs); + if (!Consensus::CheckTxInputs(tx, state, inputs, height)) return false; if (pvChecks) @@ -1979,7 +2035,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi assert(coins); // Verify signature - CScriptCheck check(*coins, tx, i, flags, cacheStore); + CScriptCheck check(*coins, tx, i, flags, cacheStore, height); if (pvChecks) { pvChecks->push_back(CScriptCheck()); check.swap(pvChecks->back()); @@ -1992,7 +2048,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi // avoid splitting the network between upgraded and // non-upgraded nodes. CScriptCheck check2(*coins, tx, i, - flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore); + flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, height); if (check2()) return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } diff --git a/src/main.h b/src/main.h index 10c5c63f4..eb996bbd6 100644 --- a/src/main.h +++ b/src/main.h @@ -747,12 +747,13 @@ private: unsigned int nFlags; bool cacheStore; ScriptError error; + int nHeight; public: - CScriptCheck(): ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {} - CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn) : + CScriptCheck(): ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR), nHeight(-1) {} + CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, int height) : scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey), - ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR) { } + ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), nHeight(height) { } bool operator()(); @@ -763,6 +764,7 @@ public: std::swap(nFlags, check.nFlags); std::swap(cacheStore, check.cacheStore); std::swap(error, check.error); + std::swap(nHeight, check.nHeight); } ScriptError GetScriptError() const { return error; } diff --git a/src/script/drivechain.cpp b/src/script/drivechain.cpp new file mode 100644 index 000000000..90cc2901f --- /dev/null +++ b/src/script/drivechain.cpp @@ -0,0 +1,142 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "drivechain.h" + +#include "crypto/sha256.h" +#include "script/interpreter.h" +#include "streams.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +std::pair FindAckLabel(const CTransaction& coinbase) +{ + for (const CTxOut& txout : coinbase.vout) { + const CScript& scriptPubKey = txout.scriptPubKey; + auto result = std::search(scriptPubKey.begin(), scriptPubKey.end(), ACK_LABEL, ACK_LABEL + ACK_LABEL_LENGTH); + if (result != scriptPubKey.end()) { + // Skip ACK label, we know result + ACK_LABEL_LENGTH <= scriptPubKey.end() + return std::make_pair(result + ACK_LABEL_LENGTH, scriptPubKey.end()); + } + } + return std::make_pair(CScript::const_iterator(nullptr), CScript::const_iterator(nullptr)); +} + +FullAckList ParseFullAckList(const std::vector& data) +{ + try { + FullAckList fullAckList; + CDataStream ss(data, SER_DISK, 0); + ss >> fullAckList; + return fullAckList; + } catch (...) { + } + return FullAckList(); +} + +struct ChainVote { + std::vector hash; + uint32_t positiveAcks; + uint32_t negativeAcks; +}; + +std::vector::const_iterator FindPrefix(const std::vector& votes, const std::vector& prefix) +{ + assert(prefix.size() > 0); + for (std::vector::const_iterator itVote = votes.begin(); itVote != votes.end(); ++itVote) { + if (memcmp(begin_ptr(itVote->hash), begin_ptr(prefix), prefix.size()) == 0) { + return itVote; + } + } + return votes.end(); +} + +bool CountAcks(const std::vector hashSpend, const std::vector& chainId, int periodAck, int periodLiveness, int& positiveAcks, int& negativeAcks, const BaseBlockReader& blockReader) +{ + int blockNumber = blockReader.GetBlockNumber(); + // Check valid block range + if (blockNumber - periodLiveness - periodAck < 0) + return false; + std::vector A; + int poll_start = blockNumber - periodLiveness - periodAck; + for (int i = poll_start; i < poll_start + periodAck; ++i) { + CTransaction coinbase = blockReader.GetBlockCoinbase(i); + auto result = FindAckLabel(coinbase); + if (result.first == result.second) + continue; + // Parse votes + FullAckList fullAckList = ParseFullAckList(std::vector(result.first, result.second)); + for (const ChainAckList& chainAcks : fullAckList.vChainAcks) { + // Ensure correct ChainId + if (chainAcks.chainId != chainId) + continue; + std::set new_acks; // votes found + for (const Ack& ack : chainAcks.ackList.vAck) { + std::vector tx_hash = ack.prefix; + std::vector tx_hash_preimage = ack.preimage; + // Check vote validity + bool valid = ((tx_hash.size() <= 32) && (tx_hash_preimage.size() == 0)) || + ((tx_hash.size() == 0) && (tx_hash_preimage.size() == 32)); + if ((tx_hash.size() == 32) && (tx_hash_preimage.size() == 32)) { + std::vector hash(32); + // Check hash correspond to given preimage + CSHA256().Write(begin_ptr(tx_hash_preimage), tx_hash_preimage.size()).Finalize(begin_ptr(hash)); + valid = memcmp(begin_ptr(hash), begin_ptr(tx_hash), 32) == 0; + } + if (!valid) + continue; + // New proposal with empty hash + if ((tx_hash.size() == 0) && (tx_hash_preimage.size() == 32)) { + tx_hash.resize(32); + CSHA256().Write(&tx_hash_preimage[0], tx_hash_preimage.size()).Finalize(begin_ptr(tx_hash)); + } + // Empty hash here is a negative vote + if (tx_hash.size() != 0) { + // Check existing prefix + auto it = FindPrefix(A, tx_hash); + // If it is not a prefix of a hash in A + if (it == A.end()) { + // It is a new proposal add to A + if (tx_hash_preimage.size() == 32) { + A.push_back(ChainVote{tx_hash, uint32_t(0), uint32_t(0)}); + new_acks.insert(A.size() - 1); + } else { + if (memcmp(begin_ptr(hashSpend), begin_ptr(tx_hash), tx_hash.size()) != 0) { + continue; + } + } + } else { + // Existing proposal record to ack later + uint32_t index = it - A.begin(); + new_acks.insert(index); + } + } + } + // Account for votes found + for (uint32_t k = 0; k < A.size(); ++k) { + if (new_acks.count(k) == 1) { + A[k].positiveAcks++; + } else /* if (allowed_negative_acks) */ { + A[k].negativeAcks++; + } + } + break; + } + } + + // Search if hashSpend is among the proposals + auto it = FindPrefix(A, hashSpend); + if (it != A.end()) { + positiveAcks = it->positiveAcks; + negativeAcks = it->negativeAcks; + return true; + } + return false; +} diff --git a/src/script/drivechain.h b/src/script/drivechain.h new file mode 100644 index 000000000..bcf1b4e47 --- /dev/null +++ b/src/script/drivechain.h @@ -0,0 +1,260 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_SCRIPT_DRIVECHAIN_H +#define BITCOIN_SCRIPT_DRIVECHAIN_H + +#include "serialize.h" +#include "primitives/transaction.h" +#include +#include +#include +#include + +const unsigned char ACK_LABEL[] = {0x41, 0x43, 0x4B, 0x3A}; // "ACK:" +const size_t ACK_LABEL_LENGTH = sizeof(ACK_LABEL); + +class Ack +{ +public: + std::vector prefix; + std::vector preimage; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + uint64_t nPayload = 0; + if (!ser_action.ForRead()) + nPayload = CalcPayloadSize(); + READWRITE(COMPACTSIZE(nPayload)); + READWRITE(prefix); + // Empty preimage should not be serialized + if (ser_action.ForRead()) { + uint64_t nPrefix = prefix.size(); + nPrefix += GetSizeOfCompactSize(nPrefix); + if (nPayload > nPrefix) + READWRITE(preimage); + if (CalcPayloadSize() != nPayload) + throw std::runtime_error("Not valid ACK"); + } else { + if (preimage.size() > 0) + READWRITE(preimage); + } + } + + uint64_t CalcPayloadSize() const + { + uint64_t nPayload = 0; + nPayload += GetSizeOfCompactSize(prefix.size()); + nPayload += prefix.size(); + // Empty preimage should not be serialized + if (!preimage.empty()) { + nPayload += GetSizeOfCompactSize(preimage.size()); + nPayload += preimage.size(); + } + return nPayload; + } + + uint64_t CalcSize() const + { + uint64_t nPayload = CalcPayloadSize(); + return GetSizeOfCompactSize(nPayload) + nPayload; + } + + Ack() {} + Ack(std::vector prefix, std::vector preimage = std::vector()) + : prefix(prefix), preimage(preimage) + { + } +}; + +class AckList +{ +public: + std::vector vAck; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + uint64_t sizePayload = 0; + if (!ser_action.ForRead()) + sizePayload = CalcPayloadSize(); + READWRITE(COMPACTSIZE(sizePayload)); + if (ser_action.ForRead()) { + uint64_t read = 0; + while (read < sizePayload) { + Ack ack; + READWRITE(ack); + read += ack.CalcSize(); + vAck.push_back(ack); + } + if (read != sizePayload) + throw std::runtime_error("Not valid ACK LIST"); + } else { + for (Ack& ack : vAck) { + READWRITE(ack); + } + } + } + + uint64_t CalcPayloadSize() const + { + uint64_t nPayload = 0; + for (const Ack& ack : vAck) { + nPayload += ack.CalcSize(); + } + return nPayload; + } + + uint64_t CalcSize() const + { + uint64_t nSize = 0; + uint64_t nPayloadSize = CalcPayloadSize(); + nSize += GetSizeOfCompactSize(nPayloadSize); + nSize += nPayloadSize; + return nSize; + } + + AckList() {} + AckList(std::vector acks) : vAck(acks) {} +}; + +class ChainAckList +{ +public: + std::vector chainId; + AckList ackList; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + uint64_t nPayload = 0; + if (!ser_action.ForRead()) + nPayload = CalcPayloadSize(); + READWRITE(COMPACTSIZE(nPayload)); + READWRITE(chainId); + READWRITE(ackList); + if (ser_action.ForRead() && nPayload != CalcPayloadSize()) + throw std::runtime_error("Not valid CHAIN ACK LIST"); + } + + uint64_t CalcPayloadSize() const + { + uint64_t nPayload = 0; + nPayload += GetSizeOfCompactSize(chainId.size()); + nPayload += chainId.size(); + nPayload += ackList.CalcSize(); + return nPayload; + } + + uint64_t CalcSize() const + { + uint64_t nSize = 0; + uint64_t nPayloadSize = CalcPayloadSize(); + nSize += GetSizeOfCompactSize(nPayloadSize); + nSize += nPayloadSize; + return nSize; + } + + ChainAckList& operator<<(Ack ack) + { + ackList.vAck.push_back(ack); + return *this; + } + + ChainAckList() {} + ChainAckList(std::vector chainId) : chainId(chainId) {} +}; + +class FullAckList +{ +public: + std::vector vChainAcks; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + uint64_t sizePayload = 0; + if (!ser_action.ForRead()) + sizePayload = CalcPayloadSize(); + READWRITE(COMPACTSIZE(sizePayload)); + if (ser_action.ForRead()) { + uint64_t read = 0; + while (read < sizePayload) { + ChainAckList chainAcks; + READWRITE(chainAcks); + read += chainAcks.CalcSize(); + vChainAcks.push_back(chainAcks); + } + if (read != sizePayload) + throw std::runtime_error("Not valid FULL ACK LIST"); + } else { + for (auto& chainAcks : vChainAcks) { + READWRITE(chainAcks); + } + } + } + + uint64_t CalcPayloadSize() const + { + uint64_t nPayloadSize = 0; + for (const auto& chainAcks : vChainAcks) { + nPayloadSize += chainAcks.CalcSize(); + } + return nPayloadSize; + } + + uint64_t CalcSize() const + { + uint64_t nSize = 0; + uint64_t nPayloadSize = CalcPayloadSize(); + nSize += GetSizeOfCompactSize(nPayloadSize); + nSize += nPayloadSize; + return nSize; + } + + FullAckList& operator<<(Ack ack) + { + if (!vChainAcks.empty()) { + vChainAcks.rbegin()[0].ackList.vAck.push_back(ack); + } else { + throw std::runtime_error("Empty Chain"); + } + return *this; + } + + FullAckList& operator<<(ChainAckList chainAckList) + { + vChainAcks.push_back(chainAckList); + return *this; + } + + FullAckList() {} +}; + +class BaseBlockReader +{ +public: + virtual int GetBlockNumber() const + { + return -1; + } + + virtual CTransaction GetBlockCoinbase(int blockNumber) const + { + return CTransaction(); + } +}; + +bool CountAcks(const std::vector hashSpend, const std::vector& chainId, int periodAck, int periodLiveness, int& positiveAcks, int& negativeAcks, const BaseBlockReader& blockReader); + +#endif // BITCOIN_SCRIPT_DRIVECHAIN_H diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 253df86fa..a6f8fd0e1 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -426,7 +426,40 @@ bool EvalScript(vector >& stack, const CScript& script, un break; } - case OP_NOP1: case OP_NOP4: case OP_NOP5: + case OP_COUNT_ACKS: + { + + // (secondary_chain_id ack_period liveness_period -- ) + if (stack.size() < 3) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& secondaryChainId = stacktop(-3); + if (secondaryChainId.size() < 1 || secondaryChainId.size() > MAX_CHAIN_ID_LENGTH) + return set_error(serror, SCRIPT_ERR_COUNT_ACKS_INVALID_PARAM); + + CScriptNum periodAck(stacktop(-2), fRequireMinimal); + if (periodAck < 1 || periodAck > MAX_ACK_PERIOD) + return set_error(serror, SCRIPT_ERR_COUNT_ACKS_INVALID_PARAM); + + CScriptNum periodLiveness(stacktop(-1), fRequireMinimal); + if (periodLiveness < MIN_LIVENESS_PERIOD || periodLiveness > MAX_LIVENESS_PERIOD) + return set_error(serror, SCRIPT_ERR_COUNT_ACKS_INVALID_PARAM); + + int positiveAcks, negativeAcks; + if (checker.CountAcks(secondaryChainId, periodAck.getint(), periodLiveness.getint(), positiveAcks, negativeAcks)) { + popstack(stack); + popstack(stack); + popstack(stack); + stack.push_back(CScriptNum(positiveAcks).getvch()); + stack.push_back(CScriptNum(negativeAcks).getvch()); + } else { + return set_error(serror, SCRIPT_ERR_COUNT_ACKS_INVALID_PARAM); + } + + break; + } + + case OP_NOP1: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) diff --git a/src/script/interpreter.h b/src/script/interpreter.h index f890ccc78..500c768f1 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -114,6 +114,11 @@ public: return false; } + virtual bool CountAcks(const std::vector& chainId, int periodAck, int periodLiveness, int& positiveAcks, int& negativeAcks) const + { + return -1; + } + virtual ~BaseSignatureChecker() {} }; diff --git a/src/script/script.h b/src/script/script.h index 4d84276f5..3c294f82d 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -31,6 +31,18 @@ static const int MAX_PUBKEYS_PER_MULTISIG = 20; // otherwise as UNIX timestamp. static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC +// Maximum chain id length +static const int MAX_CHAIN_ID_LENGTH = 20; + +// Maximum chain ack period (in blocks) +static const int MAX_ACK_PERIOD = 144; + +// Minimum chain ack liveness period +static const int MIN_LIVENESS_PERIOD = 100; + +// Maximum chain ack liveness period +static const int MAX_LIVENESS_PERIOD = 144; + template std::vector ToByteVector(const T& in) { @@ -166,7 +178,8 @@ enum opcodetype OP_NOP2 = OP_CHECKLOCKTIMEVERIFY, OP_NOP3 = 0xb2, OP_CHECKSEQUENCEVERIFY = OP_NOP3, - OP_NOP4 = 0xb3, + OP_COUNT_ACKS = 0xb3, + OP_NOP4 = OP_COUNT_ACKS, OP_NOP5 = 0xb4, OP_NOP6 = 0xb5, OP_NOP7 = 0xb6, diff --git a/src/serialize.h b/src/serialize.h index 41a51d237..3fa23e27d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -384,6 +384,7 @@ I ReadVarInt(Stream& is) #define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))) #define VARINT(obj) REF(WrapVarInt(REF(obj))) +#define COMPACTSIZE(obj) REF(CCompactSize(REF(obj))) #define LIMITED_STRING(obj,n) REF(LimitedString< n >(REF(obj))) /** @@ -454,6 +455,28 @@ public: } }; +class CCompactSize +{ +protected: + uint64_t &n; +public: + CCompactSize(uint64_t& nIn) : n(nIn) { } + + unsigned int GetSerializeSize(int, int) const { + return GetSizeOfCompactSize(n); + } + + template + void Serialize(Stream &s, int, int) const { + WriteCompactSize(s, n); + } + + template + void Unserialize(Stream& s, int, int) { + n = ReadCompactSize(s); + } +}; + template class LimitedString {