From 102d172ca3a3382e7345593f053b7b32cee768fc Mon Sep 17 00:00:00 2001 From: Miguel Freitas Date: Sun, 19 Jan 2014 22:25:22 -0200 Subject: [PATCH] highly experimental soft checkpoint based on consensus --- Makefile.am | 1 + src/checkpoints.cpp | 11 +- src/checkpoints.h | 3 + src/clientversion.h | 2 +- src/init.cpp | 2 + src/main.cpp | 22 ++++ src/makefile.android | 1 + src/makefile.osx | 1 + src/makefile.unix | 1 + src/softcheckpoint.cpp | 254 +++++++++++++++++++++++++++++++++++++++++ src/softcheckpoint.h | 72 ++++++++++++ src/version.h | 5 +- twister-qt.pro | 2 + 13 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 src/softcheckpoint.cpp create mode 100644 src/softcheckpoint.h diff --git a/Makefile.am b/Makefile.am index 143e76ff..c443da7c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -146,6 +146,7 @@ BITCOIN_TWISTER_SOURCES = \ src/rpcrawtransaction.cpp \ src/script.cpp \ src/scrypt.cpp \ + src/softcheckpoint.cpp \ src/sync.cpp \ src/util.cpp \ src/wallet.cpp \ diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 873960fd..ebcae8d6 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -9,6 +9,7 @@ #include "main.h" #include "uint256.h" +#include "softcheckpoint.h" namespace Checkpoints { @@ -79,7 +80,8 @@ namespace Checkpoints MapCheckpoints::const_iterator i = checkpoints.find(nHeight); if (i == checkpoints.end()) return true; - return hash == i->second; + if (hash != i->second) return false; + return SoftCheckpoints::CheckBlock(nHeight, hash); } // Guess how far we are in the verification process at the given block index @@ -139,4 +141,11 @@ namespace Checkpoints } return NULL; } + + int GetHighestCheckpoint() + { + const MapCheckpoints& checkpoints = *Checkpoints().mapCheckpoints; + MapCheckpoints::const_reverse_iterator i = checkpoints.rbegin(); + return (*i).first; + } } diff --git a/src/checkpoints.h b/src/checkpoints.h index a49a908a..027e0a5a 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -23,6 +23,9 @@ namespace Checkpoints // Returns last CBlockIndex* in mapBlockIndex that is a checkpoint CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex); + // Returns the height of the highest checkpoint + int GetHighestCheckpoint(); + double GuessVerificationProgress(CBlockIndex *pindex); extern bool fEnabled; diff --git a/src/clientversion.h b/src/clientversion.h index 8696ae21..0b295d5b 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -8,7 +8,7 @@ // These need to be macros, as version.cpp's and bitcoin-qt.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 9 -#define CLIENT_VERSION_REVISION 07 +#define CLIENT_VERSION_REVISION 8 #define CLIENT_VERSION_BUILD 0 // Set to true for release, false for prerelease or test build diff --git a/src/init.cpp b/src/init.cpp index 339e2959..a4105abf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -14,6 +14,7 @@ #include "util.h" #include "ui_interface.h" #include "checkpoints.h" +#include "softcheckpoint.h" #include "twister.h" #include @@ -402,6 +403,7 @@ bool AppInit2(boost::thread_group& threadGroup) // ********************************************************* Step 2: parameter interactions Checkpoints::fEnabled = GetBoolArg("-checkpoints", true); + SoftCheckpoints::fEnabled = GetBoolArg("-softcheckpoints", true); if (!SelectParamsFromCommandLine()) { return InitError("Invalid combination of -testnet and -regtest."); } diff --git a/src/main.cpp b/src/main.cpp index 9790e7ae..314e8d45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "alert.h" #include "checkpoints.h" +#include "softcheckpoint.h" #include "db.h" #include "txdb.h" #include "net.h" @@ -1731,6 +1732,8 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp) if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) pnode->PushInventory(CInv(MSG_BLOCK, hash)); } + + SoftCheckpoints::NewBlockAccepted(); return true; } @@ -2753,6 +2756,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) item.second.RelayTo(pfrom); } + SoftCheckpoints::RelayLastCPToNode(pfrom); + pfrom->fSuccessfullyConnected = true; printf("receive version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); @@ -3016,6 +3021,23 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } + else if (strCommand == "cp") + { + CSoftCheckpoint cp; + vRecv >> cp; + + const std::string username( cp.vchUsername.data(), cp.vchUsername.size() ); + const std::string sign( cp.vchSign.data(), cp.vchSign.size() ); + + if (SoftCheckpoints::CastVoteSoftCheckpoint(cp.nHeight,cp.blockHash,username,sign)) { + SoftCheckpoints::RelayCP(cp, pfrom); + } else { + //TODO: misbehaving only if invalid signature etc. not because we have it already. + //pfrom->Misbehaving(nDoS); + } + } + + else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing { CBlock block; diff --git a/src/makefile.android b/src/makefile.android index 8a319fdd..6434e1b6 100644 --- a/src/makefile.android +++ b/src/makefile.android @@ -122,6 +122,7 @@ OBJS= \ obj/rpcrawtransaction.o \ obj/script.o \ obj/scrypt.o \ + obj/softcheckpoint.o \ obj/sync.o \ obj/util.o \ obj/wallet.o \ diff --git a/src/makefile.osx b/src/makefile.osx index 522a3d72..ebe621be 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -136,6 +136,7 @@ OBJS= \ obj/rpcrawtransaction.o \ obj/script.o \ obj/scrypt.o \ + obj/softcheckpoint.o \ obj/sync.o \ obj/util.o \ obj/wallet.o \ diff --git a/src/makefile.unix b/src/makefile.unix index 80b3c728..f9c0485b 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -138,6 +138,7 @@ OBJS= \ obj/rpcrawtransaction.o \ obj/script.o \ obj/scrypt.o \ + obj/softcheckpoint.o \ obj/sync.o \ obj/util.o \ obj/wallet.o \ diff --git a/src/softcheckpoint.cpp b/src/softcheckpoint.cpp new file mode 100644 index 00000000..79b6a193 --- /dev/null +++ b/src/softcheckpoint.cpp @@ -0,0 +1,254 @@ +// Copyright (c) 2014 Miguel Freitas +// TODO: write description for the soft checkpoint +// More info: +// https://groups.google.com/forum/#!topic/twister-dev/tH3HlVQ_wmo + +#include // for 'map_list_of()' +#include +#include + +#include "softcheckpoint.h" + +#include "main.h" +#include "checkpoints.h" +#include "uint256.h" +#include "script.h" +#include "init.h" +#include "twister.h" + +#define dbgprintf OutputDebugStringF +//#define dbgprintf(...) // no debug printf + +namespace SoftCheckpoints +{ + bool fEnabled = true; + CCriticalSection cs_softCP; + + typedef std::pair Checkpoint; // height, hash + typedef std::map CPSigMap; // user, sign + typedef std::pair CPSigPair; // user, sign + + static Checkpoint lastSoftCP; + static CPSigMap lastSoftCPSigs; + + static std::map nextCandidates; + static std::map uncheckedCandidates; + + static std::set uniqueUsersList = + boost::assign::list_of + ("mf1")("mf1a")("mf2")("mf2a")("mf3"); + + + void SetSoftCPBestChain() { + // requires cs_main and cs_softCP locked (in this order) + if( !fEnabled ) + return; + + if( lastSoftCP.first && mapBlockIndex.count(lastSoftCP.second) && + !mapBlockIndex[lastSoftCP.second]->IsInMainChain() ) { + dbgprintf("SoftCheckpoints::SetSoftCPBestChain: lastSoftCP %d not in main chain\n", lastSoftCP.first); + + // ? use !mapOrphanBlocks.count() ? + + CBlockIndex *pindex = mapBlockIndex[lastSoftCP.second]; + while( pindex && !pindex->IsInMainChain() ) { + pindex = pindex->pprev; + } + + if( !pindex ) { + dbgprintf("SoftCheckpoints::SetSoftCPBestChain: lastSoftCP %d currently orphaned? strange.\n", lastSoftCP.first); + return; + } + + dbgprintf("SoftCheckpoints::SetSoftCPBestChain: trying SetBestChain with lastSoftCP %d\n", lastSoftCP.first); + CValidationState state; + if (!SetBestChain(state, mapBlockIndex[lastSoftCP.second])) { + dbgprintf("SoftCheckpoints::lastSoftCPUpdated: SetBestChain failed\n"); + } + } + } + + void LastSoftCPUpdated() { + //LOCK(cs_main); // FIXME: not needed if called from ProcessMessage. check. wrong mutex order is bad. + SetSoftCPBestChain(); + } + + std::string CPtoString(Checkpoint &cp) { + CScript cs = CScript() << cp.first << cp.second; + return std::string((const char *)cs.data(), cs.size()); + } + + void NewBlockAccepted() { + LOCK(cs_softCP); + SetSoftCPBestChain(); + + if( (nBestHeight % SOFT_CHECKPOINT_PERIOD) == 0 && + nBestHeight > Checkpoints::GetHighestCheckpoint() && + nBestHeight > lastSoftCP.first + SOFT_CHECKPOINT_PERIOD && + !fImporting && !fReindex) { + LOCK(pwalletMain->cs_wallet); + BOOST_FOREACH(const PAIRTYPE(CKeyID, CKeyMetadata)& item, pwalletMain->mapKeyMetadata) + { + const std::string &username = item.second.username; + if(uniqueUsersList.count(username)) { + int height = nBestHeight - SOFT_CHECKPOINT_PERIOD; + dbgprintf("SoftCheckpoints::NewBlockAccepted: user '%s' will vote for %d\n", + username.c_str(), height); + + CBlockIndex* pblockindex = FindBlockByHeight(height); + assert( pblockindex ); + + Checkpoint cpPair = std::make_pair(height, *pblockindex->phashBlock); + std::string dataToSign = CPtoString(cpPair); + + std::string sign = createSignature(dataToSign, username); + + if( sign.size() ) { + if( CastVoteSoftCheckpoint(height, *pblockindex->phashBlock, username, sign) ) { + dbgprintf("SoftCheckpoints::NewBlockAccepted: relaying our own vote\n"); + CSoftCheckpoint cp; + cp.nHeight = height; + cp.blockHash = *pblockindex->phashBlock; + cp.vchUsername = std::vector(username.begin(), username.end()); + cp.vchSign = std::vector(sign.begin(), sign.end()); + SoftCheckpoints::RelayCP(cp, NULL); + } else { + dbgprintf("SoftCheckpoints::NewBlockAccepted: CastVoteSoftCheckpoint failed for our own vote!\n"); + } + } else { + dbgprintf("SoftCheckpoints::NewBlockAccepted: createSignature failed for user '%s'\n", + username.c_str()); + } + break; + } + } + } + } + + bool CastVerifiedVote(Checkpoint &cp, const std::string &username, const std::string &sign) { + if( cp.first == lastSoftCP.first ) { + if( lastSoftCPSigs.count(username) ) { + dbgprintf("SoftCheckpoints::CastVerifiedVote: '%s' already voted for lastSoftCP %d\n", username.c_str(), cp.first); + return false; + } + if( cp != lastSoftCP ) { + dbgprintf("SoftCheckpoints::CastVerifiedVote: '%s' voted for a different hash than lastSoftCP %d\n", username.c_str(), cp.first); + return false; + } + dbgprintf("SoftCheckpoints::CastVerifiedVote: new vote for lastSoftCP %d by '%s'\n", cp.first, username.c_str()); + lastSoftCPSigs[username] = sign; + return true; + } + + if( nextCandidates.count(cp) && nextCandidates[cp].count(username) ) { + dbgprintf("SoftCheckpoints::CastVerifiedVote: '%s' already voted for candidate %d\n", username.c_str(), cp.first); + return false; + } + + nextCandidates[cp][username] = sign; + if( nextCandidates[cp].size() > uniqueUsersList.size() / 2) { + dbgprintf("SoftCheckpoints::CastVerifiedVote: new soft checkpoint %d wins!\n", cp.first); + lastSoftCP = cp; + lastSoftCPSigs = nextCandidates[cp]; + nextCandidates.clear(); + LastSoftCPUpdated(); + } + return true; + } + + // returns true if vote is to be restransmitted + bool CastVoteSoftCheckpoint(int height, const uint256 &hash, const std::string &username, const std::string &sign) { + LOCK(cs_softCP); + + if( (height % SOFT_CHECKPOINT_PERIOD) != 0 ) { + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: height %d not multiple of SOFT_CHECKPOINT_PERIOD\n", height); + return false; + } + + int hardCheckPointHeight = Checkpoints::GetHighestCheckpoint(); + + if( height < hardCheckPointHeight ) { + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: height %d < hard checkpoint %d\n", height, hardCheckPointHeight); + return false; + } + + if( height < lastSoftCP.first ) { + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: height %d < soft checkpoint %d\n", height, lastSoftCP.first); + return false; + } + + if( !uniqueUsersList.count(username) ) { + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: username '%s' not accepted\n", username.c_str()); + return false; + } + + Checkpoint cp = std::make_pair(height, hash); + + if( nBestHeight < hardCheckPointHeight ) { + // still downloading blocks, we can't check signatures yet + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: vote for %d by '%s' added to unchecked\n", height, username.c_str()); + uncheckedCandidates[cp][username] = sign; + return false; + } + + if( !verifySignature( CPtoString(cp), username, sign) ) { + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: invalid signature by '%s'\n", username.c_str()); + return false; + } + + dbgprintf("SoftCheckpoints::CastVoteSoftCheckpoint: signature by '%s' verified, casting vote\n", username.c_str()); + return CastVerifiedVote( cp, username, sign ); + } + + bool CheckBlock(int nHeight, const uint256& hash) { + LOCK(cs_softCP); + if (!fEnabled) + return true; + + if (!lastSoftCP.first || nHeight != lastSoftCP.first) + return true; + dbgprintf("SoftCheckpoints::CheckBlock: height %d isOk=%d\n", nHeight, hash == lastSoftCP.second); + return hash == lastSoftCP.second; + } + + void RelayCP(const CSoftCheckpoint& cp, CNode* pfrom) { + LOCK(cs_vNodes); + dbgprintf("SoftCheckpoints::RelayCP: relaying softCP height %d\n", cp.nHeight); + BOOST_FOREACH(CNode* pnode, vNodes) { + if(pnode == pfrom) + continue; + if (pnode->nVersion >= SOFT_CHECKPOINT_VERSION) { + dbgprintf("SoftCheckpoints::RelayCP: pushMessage to %s\n", pnode->addr.ToString().c_str()); + pnode->PushMessage("cp", cp); + } + } + + if( pfrom && lastSoftCP.first == cp.nHeight ) { + if( !mapBlockIndex.count(lastSoftCP.second) ) { + dbgprintf("SoftCheckpoints::RelayCP: requesting block height %d from node\n", cp.nHeight); + PushGetBlocks(pfrom, pindexBest, cp.blockHash); + } + } + } + + void RelayLastCPToNode(CNode* pnode) { + LOCK(cs_softCP); + if (!lastSoftCP.first) + return; + + if (pnode->nVersion >= SOFT_CHECKPOINT_VERSION) { + dbgprintf("SoftCheckpoints::RelayToLastCP: relaying lastSoftCP height %d (size %zd) to %s\n", + lastSoftCP.first, lastSoftCPSigs.size(), pnode->addr.ToString().c_str()); + + BOOST_FOREACH(const CPSigMap::value_type& i, lastSoftCPSigs) { + CSoftCheckpoint cp; + + cp.nHeight = lastSoftCP.first; + cp.blockHash = lastSoftCP.second; + cp.vchUsername = std::vector(i.first.begin(), i.first.end()); + cp.vchSign = std::vector(i.second.begin(), i.second.end()); + pnode->PushMessage("cp", cp); + } + } + } +} diff --git a/src/softcheckpoint.h b/src/softcheckpoint.h new file mode 100644 index 00000000..2cb5934d --- /dev/null +++ b/src/softcheckpoint.h @@ -0,0 +1,72 @@ +// Copyright (c) 2014 Miguel Freitas + +#ifndef SOFT_CHECKPOINT_H +#define SOFT_CHECKPOINT_H + +#define SOFT_CHECKPOINT_PERIOD 6 + +#include "serialize.h" +#include "net.h" +#include "uint256.h" + +#include + +class CSoftCheckpoint; + +/** Block-chain checkpoints are compiled-in sanity checks. + * They are updated every release or three. + */ +namespace SoftCheckpoints +{ + extern bool fEnabled; + + // Returns true if block passes checkpoint checks + bool CheckBlock(int nHeight, const uint256& hash); + + void NewBlockAccepted(); + + // returns true if vote is to be restransmitted + bool CastVoteSoftCheckpoint(int height, const uint256 &hash, const std::string &username, const std::string &sign); + + void RelayCP(const CSoftCheckpoint& cp, CNode* pfrom); + + void RelayLastCPToNode(CNode* pnode); +} + +class CSoftCheckpoint +{ +public: + int nHeight; + uint256 blockHash; + std::vector vchUsername; + std::vector vchSign; + + CSoftCheckpoint() + { + SetNull(); + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(nHeight); + READWRITE(blockHash); + READWRITE(vchUsername); + READWRITE(vchSign); + ) + + void SetNull() + { + nHeight = 0; + blockHash = uint256(); + vchUsername.clear(); + vchSign.clear(); + } + + bool IsNull() const + { + return !nHeight; + } +}; + + +#endif diff --git a/src/version.h b/src/version.h index f1e7c4cd..d8b315d4 100644 --- a/src/version.h +++ b/src/version.h @@ -25,7 +25,7 @@ extern const std::string CLIENT_DATE; // network protocol versioning // -static const int PROTOCOL_VERSION = 70001; +static const int PROTOCOL_VERSION = 70002; // earlier versions not supported as of Feb 2012, and are disconnected static const int MIN_PROTO_VERSION = 209; @@ -41,6 +41,9 @@ static const int NOBLKS_VERSION_END = 32400; // BIP 0031, pong message, is enabled for all versions AFTER this one static const int BIP0031_VERSION = 60000; +// soft checkpoint min version +static const int SOFT_CHECKPOINT_VERSION = 70002; + // "mempool" command, enhanced "getdata" behavior starts with this version: static const int MEMPOOL_GD_VERSION = 60002; diff --git a/twister-qt.pro b/twister-qt.pro index 841791f7..251e0206 100644 --- a/twister-qt.pro +++ b/twister-qt.pro @@ -157,6 +157,7 @@ HEADERS += \ src/bignum.h \ src/chainparams.h \ src/checkpoints.h \ + src/softcheckpoint.h \ src/compat.h \ src/sync.h \ src/util.h \ @@ -256,6 +257,7 @@ SOURCES += \ #src/qt/bitcoin.cpp \ src/net.cpp \ src/bloom.cpp \ src/checkpoints.cpp \ + src/softcheckpoint.cpp \ src/addrman.cpp \ src/db.cpp \ src/walletdb.cpp \