diff --git a/deepcoin-qt.pro b/deepcoin-qt.pro index 28707187..7286f7ac 100644 --- a/deepcoin-qt.pro +++ b/deepcoin-qt.pro @@ -300,7 +300,8 @@ SOURCES += src/qt/bitcoin.cpp \ src/cubehash.c \ src/shavite.c \ src/echo.c \ - src/simd.c + src/simd.c \ + src/checkpointsync.cpp RESOURCES += src/qt/bitcoin.qrc diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 3c9d4513..37d533a5 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -248,6 +248,9 @@ static const CRPCCommand vRPCCommands[] = { "getworkex", &getworkex, true, false, true }, { "listaccounts", &listaccounts, false, false, true }, { "makekeypair", &makekeypair, true, false, false }, + { "getcheckpoint", &getcheckpoint, true, false, false }, + { "sendcheckpoint", &sendcheckpoint, true, false, false }, + { "enforcecheckpoint", &enforcecheckpoint, true, false, false }, { "sendalert", &sendalert, true, false, false }, { "settxfee", &settxfee, false, false, true }, { "getblocktemplate", &getblocktemplate, true, false, false }, @@ -1204,6 +1207,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 4) ConvertTo(params[4]); if (strMethod == "sendalert" && n > 5) ConvertTo(params[5]); if (strMethod == "sendalert" && n > 6) ConvertTo(params[6]); + if (strMethod == "enforcecheckpoint" && n > 0) ConvertTo(params[0]); return params; } diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 6afb92cb..58532017 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -175,6 +175,9 @@ extern json_spirit::Value listtransactions(const json_spirit::Array& params, boo extern json_spirit::Value listaddressgroupings(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listaccounts(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value makekeypair(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp); // in checkpointsync.cpp +extern json_spirit::Value sendcheckpoint(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value enforcecheckpoint(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listsinceblock(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value gettransaction(const json_spirit::Array& params, bool fHelp); diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 2676c591..c79dade3 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -132,4 +132,21 @@ namespace Checkpoints } return NULL; } + + uint256 GetLastAvailableCheckpoint() { + const MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints); + + BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, checkpoints) { + const uint256& hash = i.second; + if(mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain()) + return(hash); + } + return(hashGenesisBlock); + } + + uint256 GetLatestHardenedCheckpoint() + { + const MapCheckpoints& checkpoints = *Checkpoints().mapCheckpoints; + return (checkpoints.rbegin()->second); + } } diff --git a/src/checkpoints.h b/src/checkpoints.h index 3d568855..78e3cd18 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -23,6 +23,12 @@ namespace Checkpoints // Returns last CBlockIndex* in mapBlockIndex that is a checkpoint CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex); + /* Returns the last available checkpoint in the main chain */ + uint256 GetLastAvailableCheckpoint(); + + // Returns the block hash of latest hardened checkpoint + uint256 GetLatestHardenedCheckpoint(); + double GuessVerificationProgress(CBlockIndex *pindex); } diff --git a/src/checkpointsync.cpp b/src/checkpointsync.cpp new file mode 100644 index 00000000..c83197f8 --- /dev/null +++ b/src/checkpointsync.cpp @@ -0,0 +1,526 @@ +// Copyright (c) 2012-2013 PPCoin developers +// Copyright (c) 2013 Primecoin developers +// Distributed under conditional MIT/X11 software license, +// see the accompanying file COPYING +// +// The synchronized checkpoint system is first developed by Sunny King for +// ppcoin network in 2012, giving cryptocurrency developers a tool to gain +// additional network protection against 51% attack. +// +// Primecoin also adopts this security mechanism, and the enforcement of +// checkpoints is explicitly granted by user, thus granting only temporary +// consensual central control to developer at the threats of 51% attack. +// +// Concepts +// +// In the network there can be a privileged node known as 'checkpoint master'. +// This node can send out checkpoint messages signed by the checkpoint master +// key. Each checkpoint is a block hash, representing a block on the blockchain +// that the network should reach consensus on. +// +// Besides verifying signatures of checkpoint messages, each node also verifies +// the consistency of the checkpoints. If a conflicting checkpoint is received, +// it means either the checkpoint master key is compromised, or there is an +// operator mistake. In this situation the node would discard the conflicting +// checkpoint message and display a warning message. This precaution controls +// the damage to network caused by operator mistake or compromised key. +// +// Operations +// +// Checkpoint master key can be established by using the 'makekeypair' command +// The public key in source code should then be updated and private key kept +// in a safe place. +// +// Any node can be turned into checkpoint master by setting the 'checkpointkey' +// configuration parameter with the private key of the checkpoint master key. +// Operator should exercise caution such that at any moment there is at most +// one node operating as checkpoint master. When switching master node, the +// recommended procedure is to shutdown the master node and restart as +// regular node, note down the current checkpoint by 'getcheckpoint', then +// compare to the checkpoint at the new node to be upgraded to master node. +// When the checkpoint on both nodes match then it is safe to switch the new +// node to checkpoint master. +// +// The configuration parameter 'checkpointdepth' specifies how many blocks +// should the checkpoints lag behind the latest block in auto checkpoint mode. +// A depth of 0 is the strongest auto checkpoint policy and offers the greatest +// protection against 51% attack. A negative depth means that the checkpoints +// should not be automatically generated by the checkpoint master, but instead +// be manually entered by operator via the 'sendcheckpoint' command. The manual +// mode is also the default mode (default value -1 for checkpointdepth). +// +// Command 'enforcecheckpoint' and configuration parameter 'checkpointenforce' +// are for the users to explicitly consent to enforce the checkpoints issued +// from checkpoint master. To enforce checkpoint, user needs to either issue +// command 'enforcecheckpoint true', or set configuration parameter +// checkpointenforce=1. The current enforcement setting can be queried via +// command 'getcheckpoint', where 'subscribemode' displays either 'enforce' +// or 'advisory'. The 'enforce' mode of subscribemode means checkpoints are +// enforced. The 'advisory' mode of subscribemode means checkpoints are not +// enforced but a warning message would be displayed if the node is on a +// different blockchain fork from the checkpoint, and this is the default mode. +// + +#include + +#include "checkpoints.h" +#include "checkpointsync.h" + +#include "base58.h" +#include "bitcoinrpc.h" +#include "main.h" +#include "txdb.h" +#include "uint256.h" + +using namespace json_spirit; +using namespace std; + + +// sync-checkpoint master key +const std::string CSyncCheckpoint::strMainPubKey = "04418950ec1d6dad618c8cea3c74ee29612ad507b5fe7ec4e096f337ddd6b9caf031bc5bac132b9800d0f02b6225532ead6f1c0ed3db2d040049b7b01df5198f7d"; +const std::string CSyncCheckpoint::strTestPubKey = "0409ae1486f04f96e0fec3dfa5ff35e5f4086702fe4b80f00bafd1cbec17ba0768a759f0a914476323007a4e3b16f908bdc962ab11547777219f623939aced231a"; +std::string CSyncCheckpoint::strMasterPrivKey = ""; + + +// synchronized checkpoint (centrally broadcasted) +uint256 hashSyncCheckpoint = 0; +uint256 hashPendingCheckpoint = 0; +CSyncCheckpoint checkpointMessage; +CSyncCheckpoint checkpointMessagePending; +uint256 hashInvalidCheckpoint = 0; +CCriticalSection cs_hashSyncCheckpoint; +std::string strCheckpointWarning; + +// get last synchronized checkpoint +CBlockIndex* GetLastSyncCheckpoint() +{ + LOCK(cs_hashSyncCheckpoint); + if (!mapBlockIndex.count(hashSyncCheckpoint)) + error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str()); + else + return mapBlockIndex[hashSyncCheckpoint]; + return NULL; +} + +// only descendant of current sync-checkpoint is allowed +bool ValidateSyncCheckpoint(uint256 hashCheckpoint) +{ + if (!mapBlockIndex.count(hashSyncCheckpoint)) + return error("ValidateSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str()); + if (!mapBlockIndex.count(hashCheckpoint)) + return error("ValidateSyncCheckpoint: block index missing for received sync-checkpoint %s", hashCheckpoint.ToString().c_str()); + + CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint]; + CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint]; + + if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight) + { + // Received an older checkpoint, trace back from current checkpoint + // to the same height of the received checkpoint to verify + // that current checkpoint should be a descendant block + CBlockIndex* pindex = pindexSyncCheckpoint; + while (pindex->nHeight > pindexCheckpointRecv->nHeight) + if (!(pindex = pindex->pprev)) + return error("ValidateSyncCheckpoint: pprev1 null - block index structure failure"); + if (pindex->GetBlockHash() != hashCheckpoint) + { + hashInvalidCheckpoint = hashCheckpoint; + return error("ValidateSyncCheckpoint: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str()); + } + return false; // ignore older checkpoint + } + + // Received checkpoint should be a descendant block of the current + // checkpoint. Trace back to the same height of current checkpoint + // to verify. + CBlockIndex* pindex = pindexCheckpointRecv; + while (pindex->nHeight > pindexSyncCheckpoint->nHeight) + if (!(pindex = pindex->pprev)) + return error("ValidateSyncCheckpoint: pprev2 null - block index structure failure"); + if (pindex->GetBlockHash() != hashSyncCheckpoint) + { + hashInvalidCheckpoint = hashCheckpoint; + return error("ValidateSyncCheckpoint: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str()); + } + return true; +} + +bool WriteSyncCheckpoint(const uint256& hashCheckpoint) +{ + if (!pblocktree->WriteSyncCheckpoint(hashCheckpoint)) + { + return error("WriteSyncCheckpoint(): failed to write to txdb sync checkpoint %s", hashCheckpoint.ToString().c_str()); + } + if (!pblocktree->Sync()) + return error("WriteSyncCheckpoint(): failed to commit to txdb sync checkpoint %s", hashCheckpoint.ToString().c_str()); + + hashSyncCheckpoint = hashCheckpoint; + return true; +} + +bool IsSyncCheckpointEnforced() +{ + return (GetBoolArg("-checkpointenforce", true) || mapArgs.count("-checkpointkey")); // checkpoint master node is always enforced +} + +bool AcceptPendingSyncCheckpoint() +{ + LOCK(cs_hashSyncCheckpoint); + if (hashPendingCheckpoint != 0 && mapBlockIndex.count(hashPendingCheckpoint)) + { + if (!ValidateSyncCheckpoint(hashPendingCheckpoint)) + { + hashPendingCheckpoint = 0; + checkpointMessagePending.SetNull(); + return false; + } + + CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint]; + if (IsSyncCheckpointEnforced() && !pindexCheckpoint->IsInMainChain()) + { + CValidationState state; + if (!SetBestChain(state, pindexCheckpoint)) + { + hashInvalidCheckpoint = hashPendingCheckpoint; + return error("AcceptPendingSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str()); + } + } + + if (!WriteSyncCheckpoint(hashPendingCheckpoint)) + return error("AcceptPendingSyncCheckpoint(): failed to write sync checkpoint %s", hashPendingCheckpoint.ToString().c_str()); + hashPendingCheckpoint = 0; + checkpointMessage = checkpointMessagePending; + checkpointMessagePending.SetNull(); + printf("AcceptPendingSyncCheckpoint : sync-checkpoint at %s\n", hashSyncCheckpoint.ToString().c_str()); + // relay the checkpoint + if (!checkpointMessage.IsNull()) + { + BOOST_FOREACH(CNode* pnode, vNodes) + checkpointMessage.RelayTo(pnode); + } + return true; + } + return false; +} + +// Automatically select a suitable sync-checkpoint +uint256 AutoSelectSyncCheckpoint() +{ + // Search backward for a block with specified depth policy + const CBlockIndex *pindex = pindexBest; + while (pindex->pprev && pindex->nHeight + (int)GetArg("-checkpointdepth", -1) > pindexBest->nHeight) + pindex = pindex->pprev; + return pindex->GetBlockHash(); +} + +// Check against synchronized checkpoint +bool CheckSyncCheckpoint(const uint256& hashBlock, const CBlockIndex* pindexPrev) +{ + int nHeight = pindexPrev->nHeight + 1; + LOCK(cs_hashSyncCheckpoint); + // sync-checkpoint should always be accepted block + assert(mapBlockIndex.count(hashSyncCheckpoint)); + const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint]; + + if (nHeight > pindexSync->nHeight) + { + // trace back to same height as sync-checkpoint + const CBlockIndex* pindex = pindexPrev; + while (pindex->nHeight > pindexSync->nHeight) + if (!(pindex = pindex->pprev)) + return error("CheckSyncCheckpoint: pprev null - block index structure failure"); + if (pindex->nHeight < pindexSync->nHeight || pindex->GetBlockHash() != hashSyncCheckpoint) + return false; // only descendant of sync-checkpoint can pass check + } + if (nHeight == pindexSync->nHeight && hashBlock != hashSyncCheckpoint) + return false; // same height with sync-checkpoint + if (nHeight < pindexSync->nHeight && !mapBlockIndex.count(hashBlock)) + return false; // lower height than sync-checkpoint + return true; +} + +bool WantedByPendingSyncCheckpoint(uint256 hashBlock) +{ + LOCK(cs_hashSyncCheckpoint); + if (hashPendingCheckpoint == 0) + return false; + if (hashBlock == hashPendingCheckpoint) + return true; + if (mapOrphanBlocks.count(hashPendingCheckpoint) + && hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint])) + return true; + return false; +} + +// reset synchronized checkpoint to last hardened checkpoint +bool ResetSyncCheckpoint() +{ + LOCK(cs_hashSyncCheckpoint); + uint256 hash = Checkpoints::GetLatestHardenedCheckpoint(); + if (mapBlockIndex.count(hash) && !mapBlockIndex[hash]->IsInMainChain()) + { + // checkpoint block accepted but not yet in main chain + printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str()); + CValidationState state; + if (!SetBestChain(state, mapBlockIndex[hash])) + { + return error("ResetSyncCheckpoint: SetBestChain failed for hardened checkpoint %s", hash.ToString().c_str()); + } + } else { + /* Reset to the last available checkpoint block in the main chain */ + checkpointMessagePending.SetNull(); + hash = Checkpoints::GetLastAvailableCheckpoint(); + } + + if (!WriteSyncCheckpoint(hash)) + return error("ResetSyncCheckpoint: failed to write sync checkpoint %s", hash.ToString().c_str()); + printf("ResetSyncCheckpoint: sync-checkpoint reset to %s\n", hashSyncCheckpoint.ToString().c_str()); + return true; +} + +void AskForPendingSyncCheckpoint(CNode* pfrom) +{ + LOCK(cs_hashSyncCheckpoint); + if (pfrom && hashPendingCheckpoint != 0 && (!mapBlockIndex.count(hashPendingCheckpoint)) && (!mapOrphanBlocks.count(hashPendingCheckpoint))) + pfrom->AskFor(CInv(MSG_BLOCK, hashPendingCheckpoint)); +} + +// Verify sync checkpoint master pubkey and reset sync checkpoint if changed +bool CheckCheckpointPubKey() +{ + std::string strPubKey = ""; + std::string strMasterPubKey = fTestNet? CSyncCheckpoint::strTestPubKey : CSyncCheckpoint::strMainPubKey; + if (!pblocktree->ReadCheckpointPubKey(strPubKey) || strPubKey != strMasterPubKey) + { + // write checkpoint master key to db + if (!pblocktree->WriteCheckpointPubKey(strMasterPubKey)) + return error("CheckCheckpointPubKey() : failed to write new checkpoint master key to db"); + if (!pblocktree->Sync()) + return error("CheckCheckpointPubKey() : failed to commit new checkpoint master key to db"); + if (!ResetSyncCheckpoint()) + return error("CheckCheckpointPubKey() : failed to reset sync-checkpoint"); + } + return true; +} + +bool SetCheckpointPrivKey(std::string strPrivKey) +{ + // Test signing a sync-checkpoint with genesis block + CSyncCheckpoint checkpoint; + checkpoint.hashCheckpoint = hashGenesisBlock; + CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); + sMsg << (CUnsignedSyncCheckpoint)checkpoint; + checkpoint.vchMsg = std::vector(sMsg.begin(), sMsg.end()); + + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(strPrivKey)) + return error("SendSyncCheckpoint: Checkpoint master key invalid"); + CKey key = vchSecret.GetKey(); // if key is not correct openssl may crash + if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig)) + return false; + + // Test signing successful, proceed + CSyncCheckpoint::strMasterPrivKey = strPrivKey; + return true; +} + +bool SendSyncCheckpoint(uint256 hashCheckpoint) +{ + CSyncCheckpoint checkpoint; + checkpoint.hashCheckpoint = hashCheckpoint; + CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); + sMsg << (CUnsignedSyncCheckpoint)checkpoint; + checkpoint.vchMsg = std::vector(sMsg.begin(), sMsg.end()); + + if (CSyncCheckpoint::strMasterPrivKey.empty()) + return error("SendSyncCheckpoint: Checkpoint master key unavailable."); + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(CSyncCheckpoint::strMasterPrivKey)) + return error("SendSyncCheckpoint: Checkpoint master key invalid"); + CKey key = vchSecret.GetKey(); // if key is not correct openssl may crash + if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig)) + return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?"); + + if(!checkpoint.ProcessSyncCheckpoint(NULL)) + { + printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n"); + return false; + } + + // Relay checkpoint + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + return true; +} + +// Is the sync-checkpoint outside maturity window? +bool IsMatureSyncCheckpoint() +{ + LOCK(cs_hashSyncCheckpoint); + // sync-checkpoint should always be accepted block + assert(mapBlockIndex.count(hashSyncCheckpoint)); + const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint]; + return (nBestHeight >= pindexSync->nHeight + COINBASE_MATURITY); +} + +// Is the sync-checkpoint too old? +bool IsSyncCheckpointTooOld(unsigned int nSeconds) +{ + LOCK(cs_hashSyncCheckpoint); + // sync-checkpoint should always be accepted block + assert(mapBlockIndex.count(hashSyncCheckpoint)); + const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint]; + return (pindexSync->GetBlockTime() + nSeconds < GetAdjustedTime()); +} + +// find block wanted by given orphan block +uint256 WantedByOrphan(const CBlock* pblockOrphan) +{ + // Work back to the first block in the orphan chain + while (mapOrphanBlocks.count(pblockOrphan->hashPrevBlock)) + pblockOrphan = mapOrphanBlocks[pblockOrphan->hashPrevBlock]; + return pblockOrphan->hashPrevBlock; +} + +// verify signature of sync-checkpoint message +bool CSyncCheckpoint::CheckSignature() +{ + std::string strMasterPubKey = fTestNet? CSyncCheckpoint::strTestPubKey : CSyncCheckpoint::strMainPubKey; + CPubKey key(ParseHex(strMasterPubKey)); + if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) + return error("CSyncCheckpoint::CheckSignature() : verify signature failed"); + + // Now unserialize the data + CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); + sMsg >> *(CUnsignedSyncCheckpoint*)this; + return true; +} + +// process synchronized checkpoint +bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom) +{ + if (!CheckSignature()) + return false; + + LOCK(cs_hashSyncCheckpoint); + if (!mapBlockIndex.count(hashCheckpoint)) + { + // We haven't received the checkpoint chain, keep the checkpoint as pending + hashPendingCheckpoint = hashCheckpoint; + checkpointMessagePending = *this; + printf("ProcessSyncCheckpoint: pending for sync-checkpoint %s\n", hashCheckpoint.ToString().c_str()); + // Ask this guy to fill in what we're missing + if (pfrom) + { + pfrom->PushGetBlocks(pindexBest, hashCheckpoint); + // ask directly as well in case rejected earlier by duplicate + // proof-of-stake because getblocks may not get it this time + pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint]) : hashCheckpoint)); + } + return false; + } + + if (!ValidateSyncCheckpoint(hashCheckpoint)) + return false; + + CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint]; + if (IsSyncCheckpointEnforced() && !pindexCheckpoint->IsInMainChain()) + { + // checkpoint chain received but not yet main chain + CValidationState state; + if (!SetBestChain(state, pindexCheckpoint)) + { + hashInvalidCheckpoint = hashCheckpoint; + return error("ProcessSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashCheckpoint.ToString().c_str()); + } + } + + if (!WriteSyncCheckpoint(hashCheckpoint)) + return error("ProcessSyncCheckpoint(): failed to write sync checkpoint %s", hashCheckpoint.ToString().c_str()); + checkpointMessage = *this; + hashPendingCheckpoint = 0; + checkpointMessagePending.SetNull(); + printf("ProcessSyncCheckpoint: sync-checkpoint at %s\n", hashCheckpoint.ToString().c_str()); + return true; +} + + +// RPC commands related to sync checkpoints +// get information of sync-checkpoint (first introduced in ppcoin) +Value getcheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getcheckpoint\n" + "Show info of synchronized checkpoint.\n"); + + Object result; + CBlockIndex* pindexCheckpoint; + + result.push_back(Pair("synccheckpoint", hashSyncCheckpoint.ToString().c_str())); + if (mapBlockIndex.count(hashSyncCheckpoint)) + { + pindexCheckpoint = mapBlockIndex[hashSyncCheckpoint]; + result.push_back(Pair("height", pindexCheckpoint->nHeight)); + result.push_back(Pair("timestamp", (boost::int64_t) pindexCheckpoint->GetBlockTime())); + } + result.push_back(Pair("subscribemode", IsSyncCheckpointEnforced()? "enforce" : "advisory")); + if (mapArgs.count("-checkpointkey")) + result.push_back(Pair("checkpointmaster", true)); + + return result; +} + +Value sendcheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "sendcheckpoint \n" + "Send a synchronized checkpoint.\n"); + + if (!mapArgs.count("-checkpointkey") || CSyncCheckpoint::strMasterPrivKey.empty()) + throw runtime_error("Not a checkpointmaster node, first set checkpointkey in configuration and restart client. "); + + std::string strHash = params[0].get_str(); + uint256 hash(strHash); + + if (!SendSyncCheckpoint(hash)) + throw runtime_error("Failed to send checkpoint, check log. "); + + Object result; + CBlockIndex* pindexCheckpoint; + + result.push_back(Pair("synccheckpoint", hashSyncCheckpoint.ToString().c_str())); + if (mapBlockIndex.count(hashSyncCheckpoint)) + { + pindexCheckpoint = mapBlockIndex[hashSyncCheckpoint]; + result.push_back(Pair("height", pindexCheckpoint->nHeight)); + result.push_back(Pair("timestamp", (boost::int64_t) pindexCheckpoint->GetBlockTime())); + } + result.push_back(Pair("subscribemode", IsSyncCheckpointEnforced()? "enforce" : "advisory")); + if (mapArgs.count("-checkpointkey")) + result.push_back(Pair("checkpointmaster", true)); + + return result; +} + +Value enforcecheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "enforcecheckpoint \n" + " is true or false to enable or disable enforcement of broadcasted checkpoints by developer."); + + bool fEnforceCheckpoint = params[0].get_bool(); + if (mapArgs.count("-checkpointkey") && !fEnforceCheckpoint) + throw runtime_error( + "checkpoint master node must enforce synchronized checkpoints."); + if (fEnforceCheckpoint) + strCheckpointWarning = ""; + mapArgs["-checkpointenforce"] = (fEnforceCheckpoint ? "1" : "0"); + return Value::null; +} + diff --git a/src/checkpointsync.h b/src/checkpointsync.h new file mode 100644 index 00000000..983ed4d3 --- /dev/null +++ b/src/checkpointsync.h @@ -0,0 +1,131 @@ +// Copyright (c) 2011-2013 PPCoin developers +// Copyright (c) 2013 WORLDCOIN developers +// Distributed under conditional MIT/X11 open source software license +// see the accompanying file COPYING +#ifndef WORLDCOIN_CHECKPOINTSYNC_H +#define WORLDCOIN_CHECKPOINTSYNC_H + +#include "net.h" +#include "util.h" + +#define CHECKPOINT_MAX_SPAN (60 * 60 * 4) // max 4 hours before latest block + +class uint256; +class CBlock; +class CBlockIndex; +class CSyncCheckpoint; + +extern uint256 hashSyncCheckpoint; +extern CSyncCheckpoint checkpointMessage; +extern uint256 hashInvalidCheckpoint; +extern CCriticalSection cs_hashSyncCheckpoint; +extern std::string strCheckpointWarning; + +CBlockIndex* GetLastSyncCheckpoint(); +bool WriteSyncCheckpoint(const uint256& hashCheckpoint); +bool IsSyncCheckpointEnforced(); +bool AcceptPendingSyncCheckpoint(); +uint256 AutoSelectSyncCheckpoint(); +bool CheckSyncCheckpoint(const uint256& hashBlock, const CBlockIndex* pindexPrev); +bool WantedByPendingSyncCheckpoint(uint256 hashBlock); +bool ResetSyncCheckpoint(); +void AskForPendingSyncCheckpoint(CNode* pfrom); +bool CheckCheckpointPubKey(); +bool SetCheckpointPrivKey(std::string strPrivKey); +bool SendSyncCheckpoint(uint256 hashCheckpoint); +bool IsMatureSyncCheckpoint(); +bool IsSyncCheckpointTooOld(unsigned int nSeconds); +uint256 WantedByOrphan(const CBlock* pblockOrphan); + +// Synchronized checkpoint (introduced first in ppcoin) +class CUnsignedSyncCheckpoint +{ +public: + int nVersion; + uint256 hashCheckpoint; // checkpoint block + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(hashCheckpoint); + ) + + void SetNull() + { + nVersion = 1; + hashCheckpoint = 0; + } + + std::string ToString() const + { + return strprintf( + "CSyncCheckpoint(\n" + " nVersion = %d\n" + " hashCheckpoint = %s\n" + ")\n", + nVersion, + hashCheckpoint.ToString().c_str()); + } + + void print() const + { + printf("%s", ToString().c_str()); + } +}; + +class CSyncCheckpoint : public CUnsignedSyncCheckpoint +{ +public: + static const std::string strMainPubKey; + static const std::string strTestPubKey; + static std::string strMasterPrivKey; + + std::vector vchMsg; + std::vector vchSig; + + CSyncCheckpoint() + { + SetNull(); + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(vchMsg); + READWRITE(vchSig); + ) + + void SetNull() + { + CUnsignedSyncCheckpoint::SetNull(); + vchMsg.clear(); + vchSig.clear(); + } + + bool IsNull() const + { + return (hashCheckpoint == 0); + } + + uint256 GetHash() const + { + return Hash(this->vchMsg.begin(), this->vchMsg.end()); + } + + bool RelayTo(CNode* pnode) const + { + // returns true if wasn't already sent + if (pnode->hashCheckpointKnown != hashCheckpoint) + { + pnode->hashCheckpointKnown = hashCheckpoint; + pnode->PushMessage("checkpoint", *this); + return true; + } + return false; + } + + bool CheckSignature(); + bool ProcessSyncCheckpoint(CNode* pfrom); +}; + +#endif diff --git a/src/init.cpp b/src/init.cpp index 0ab480e0..c8e0033d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -10,6 +10,7 @@ #include "init.h" #include "util.h" #include "ui_interface.h" +#include "checkpointsync.h" #include #include @@ -315,6 +316,7 @@ std::string HelpMessage() " -externalip= " + _("Specify your own public address") + "\n" + " -onlynet= " + _("Only connect to nodes in network (IPv4, IPv6 or Tor)") + "\n" + " -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n" + + " -checkpointenforce " + _("Only accept block chain matching checkpoints issued by the Auto-Checkpoint systems Master Node (default: 1)") + "\n" + " -checkpoints " + _("Only accept block chain matching built-in checkpoints (default: 1)") + "\n" + " -listen " + _("Accept connections from outside (default: 1 if no -proxy or -connect)") + "\n" + " -bind= " + _("Bind to given address and always listen on it. Use [host]:port notation for IPv6") + "\n" + @@ -635,6 +637,12 @@ bool AppInit2(boost::thread_group& threadGroup) if (!ParseMoney(mapArgs["-mininput"], nMinimumInputValue)) return InitError(strprintf(_("Invalid amount for -mininput=: '%s'"), mapArgs["-mininput"].c_str())); } + + if (mapArgs.count("-checkpointkey")) // checkpoint master priv key + { + if (!SetCheckpointPrivKey(GetArg("-checkpointkey", ""))) + return InitError(_("Unable to sign checkpoint, wrong checkpointkey?")); + } // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log diff --git a/src/main.cpp b/src/main.cpp index e5e99dfc..64943976 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "init.h" #include "ui_interface.h" #include "checkqueue.h" +#include "checkpointsync.h" #include #include #include @@ -1944,6 +1945,14 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) // strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user: strMiscWarning = _("Warning: This version is obsolete, upgrade required!"); } + + if (!IsSyncCheckpointEnforced()) // checkpoint advisory mode + { + if (pindexBest->pprev && !CheckSyncCheckpoint(pindexBest->GetBlockHash(), pindexBest->pprev)) + strCheckpointWarning = _("Warning: checkpoint on different blockchain fork, contact developers to resolve the issue"); + else + strCheckpointWarning = ""; + } std::string strCmd = GetArg("-blocknotify", ""); @@ -2204,10 +2213,9 @@ bool CBlock::AcceptBlock(CValidationState &state, CDiskBlockPos *dbp) if (!Checkpoints::CheckBlock(nHeight, hash)) return state.DoS(100, error("AcceptBlock() : rejected by checkpoint lock-in at %d", nHeight)); - // Don't accept any forks from the main chain prior to last checkpoint - CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); - if (pcheckpoint && nHeight < pcheckpoint->nHeight) - return state.DoS(100, error("AcceptBlock() : forked chain older than last checkpoint (height %d)", nHeight)); + // Check that the block satisfies synchronized checkpoint + if (IsSyncCheckpointEnforced() && !IsInitialBlockDownload() && !CheckSyncCheckpoint(hash, pindexPrev)) + return error("AcceptBlock() : rejected by synchronized checkpoint"); // Reject block.nVersion=1 blocks when 95% (75% on testnet) of the network has upgraded: if (nVersion < 2) @@ -2259,6 +2267,9 @@ bool CBlock::AcceptBlock(CValidationState &state, CDiskBlockPos *dbp) if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) pnode->PushInventory(CInv(MSG_BLOCK, hash)); } + + // Check pending sync-checkpoint + AcceptPendingSyncCheckpoint(); return true; } @@ -2293,9 +2304,15 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) { - + if((pblock->GetBlockTime() - pcheckpoint->nTime) < 0) { + if(pfrom) pfrom->Misbehaving(100); + return error("ProcessBlock() : block has a time stamp of %lld before the last checkpoint of %u", pblock->GetBlockTime(), pcheckpoint->nTime); + } } + // Ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + AskForPendingSyncCheckpoint(pfrom); // If we don't already have its previous block, shunt it off to holding area until we get it if (pblock->hashPrevBlock != 0 && !mapBlockIndex.count(pblock->hashPrevBlock)) @@ -2340,6 +2357,10 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl } printf("ProcessBlock: ACCEPTED\n"); + + if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty() && (int)GetArg("-checkpointdepth", -1) >= 0) + SendSyncCheckpoint(AutoSelectSyncCheckpoint()); + return true; } @@ -2608,6 +2629,11 @@ bool static LoadBlockIndexDB() if (pblocktree->ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile)) printf("LoadBlockIndexDB(): last block file info: %s\n", infoLastBlockFile.ToString().c_str()); + if (!pblocktree->ReadSyncCheckpoint(hashSyncCheckpoint)) + printf("LoadBlockIndexDB(): synchronized checkpoint not read\n"); + else + printf("LoadBlockIndexDB(): synchronized checkpoint %s\n", hashSyncCheckpoint.ToString().c_str()); + // Load nBestInvalidWork, OK if it doesn't exist CBigNum bnBestInvalidWork; pblocktree->ReadBestInvalidWork(bnBestInvalidWork); @@ -2757,8 +2783,12 @@ bool LoadBlockIndex() bool InitBlockIndex() { // Check whether we're already initialized - if (pindexGenesisBlock != NULL) + if (pindexGenesisBlock != NULL) { + // Check whether the master checkpoint key has changed and reset the sync checkpoint if needed. + if (!CheckCheckpointPubKey()) + return error("LoadBlockIndex() : failed to reset checkpoint master pubkey"); return true; + } // Use the provided setting for -txindex in the new database fTxIndex = GetBoolArg("-txindex", false); @@ -2816,11 +2846,17 @@ bool InitBlockIndex() { return error("LoadBlockIndex() : writing genesis block to disk failed"); if (!block.AddToBlockIndex(state, blockPos)) return error("LoadBlockIndex() : genesis block not accepted"); + if (!WriteSyncCheckpoint(hashGenesisBlock)) + return error("LoadBlockIndex() : failed to init sync checkpoint"); } catch(std::runtime_error &e) { return error("LoadBlockIndex() : failed to initialize block database: %s", e.what()); } } + // If checkpoint master key changed must reset sync-checkpoint + if (!CheckCheckpointPubKey()) + return error("LoadBlockIndex() : failed to reset checkpoint master pubkey"); + return true; } @@ -2997,6 +3033,13 @@ string GetWarnings(string strFor) if (!CLIENT_VERSION_IS_RELEASE) strStatusBar = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"); + // Checkpoint warning + if (strCheckpointWarning != "") + { + nPriority = 900; + strStatusBar = strCheckpointWarning; + } + // Misc warnings like out of disk space and clock is wrong if (strMiscWarning != "") { @@ -3011,6 +3054,13 @@ string GetWarnings(string strFor) strStatusBar = strRPC = _("Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."); } + // If detected invalid checkpoint enter safe mode + if (hashInvalidCheckpoint != 0) + { + nPriority = 3000; + strStatusBar = strRPC = "WARNING: Inconsistent checkpoint found! Stop enforcing checkpoints and notify developers to resolve the issue."; + } + // Alerts { LOCK(cs_mapAlerts); @@ -3101,28 +3151,11 @@ void static ProcessGetData(CNode* pfrom) if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK) { - bool send = true; + // Send block from disk map::iterator mi = mapBlockIndex.find(inv.hash); pfrom->nBlocksRequested++; if (mi != mapBlockIndex.end()) { - // If the requested block is at a height below our last - // checkpoint, only serve it if it's in the checkpointed chain - int nHeight = ((*mi).second)->nHeight; - CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); - if (pcheckpoint && nHeight < pcheckpoint->nHeight) { - if (!((*mi).second)->IsInMainChain()) - { - printf("ProcessGetData(): ignoring request for old block that isn't in the main chain\n"); - send = false; - } - } - } else { - send = false; - } - if (send) - { - // Send block from disk CBlock block; block.ReadFromDisk((*mi).second); if (inv.type == MSG_BLOCK) @@ -3321,12 +3354,22 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) item.second.RelayTo(pfrom); } + + // Relay sync-checkpoint + { + LOCK(cs_hashSyncCheckpoint); + if (!checkpointMessage.IsNull()) + checkpointMessage.RelayTo(pfrom); + } pfrom->fSuccessfullyConnected = true; printf("receive version message: %s: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->cleanSubVer.c_str(), pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); cPeerBlockCounts.input(pfrom->nStartingHeight); + + if (!IsInitialBlockDownload()) + AskForPendingSyncCheckpoint(pfrom); } @@ -3745,6 +3788,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->addr.ToString().c_str()); } + else if (strCommand == "checkpoint") // Synchronized checkpoint + { + CSyncCheckpoint checkpoint; + vRecv >> checkpoint; + + if (checkpoint.ProcessSyncCheckpoint(pfrom)) + { + // Relay + pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + } + else if (strCommand == "filterload") { CBloomFilter filter; diff --git a/src/main.h b/src/main.h index 1b391596..7ce5865e 100644 --- a/src/main.h +++ b/src/main.h @@ -102,6 +102,7 @@ extern bool fBenchmark; extern int nScriptCheckThreads; extern bool fTxIndex; extern unsigned int nCoinCacheSize; +extern std::map mapOrphanBlocks; // Settings extern int64 nTransactionFee; diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw index 4a24f2d6..c7bb8293 100644 --- a/src/makefile.linux-mingw +++ b/src/makefile.linux-mingw @@ -94,7 +94,8 @@ OBJS= \ obj/aes_helper.o \ obj/echo.o \ obj/shavite.o \ - obj/simd.o + obj/simd.o \ + obj/checkpointsync.o all: deepcoind.exe diff --git a/src/makefile.mingw b/src/makefile.mingw index 2873f055..bf2eb8c5 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -106,7 +106,8 @@ OBJS= \ obj/aes_helper.o \ obj/echo.o \ obj/shavite.o \ - obj/simd.o + obj/simd.o \ + obj/checkpointsync.o all: deepcoind.exe diff --git a/src/makefile.osx b/src/makefile.osx index ace9b0f8..ce6f18c0 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -112,7 +112,8 @@ OBJS= \ obj/aes_helper.o \ obj/echo.o \ obj/shavite.o \ - obj/simd.o + obj/simd.o \ + obj/checkpointsync.o ifndef USE_UPNP override USE_UPNP = - diff --git a/src/makefile.unix b/src/makefile.unix index 6fbf1c65..ef0c9843 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -148,7 +148,8 @@ OBJS= \ obj/aes_helper.o \ obj/echo.o \ obj/shavite.o \ - obj/simd.o + obj/simd.o \ + obj/checkpointsync.o all: deepcoind diff --git a/src/net.h b/src/net.h index 0f4ba4f4..fa3f573f 100644 --- a/src/net.h +++ b/src/net.h @@ -219,6 +219,7 @@ class CNode std::set setAddrKnown; bool fGetAddr; std::set setKnown; + uint256 hashCheckpointKnown; // inventory based relay mruset setInventoryKnown; @@ -248,6 +249,7 @@ class CNode fNetworkNode = false; fSuccessfullyConnected = false; fDisconnect = false; + hashCheckpointKnown = 0; nRefCount = 0; nSendSize = 0; nSendOffset = 0; diff --git a/src/txdb.cpp b/src/txdb.cpp index 3d34710d..1eb4b778 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -241,3 +241,23 @@ bool CBlockTreeDB::LoadBlockIndexGuts() return true; } + +bool CBlockTreeDB::ReadSyncCheckpoint(uint256& hashCheckpoint) +{ + return Read(string("hashSyncCheckpoint"), hashCheckpoint); +} + +bool CBlockTreeDB::WriteSyncCheckpoint(uint256 hashCheckpoint) +{ + return Write(string("hashSyncCheckpoint"), hashCheckpoint); +} + +bool CBlockTreeDB::ReadCheckpointPubKey(string& strPubKey) +{ + return Read(string("strCheckpointPubKey"), strPubKey); +} + +bool CBlockTreeDB::WriteCheckpointPubKey(const string& strPubKey) +{ + return Write(string("strCheckpointPubKey"), strPubKey); +} diff --git a/src/txdb.h b/src/txdb.h index f59fc5da..946ee324 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -48,6 +48,10 @@ class CBlockTreeDB : public CLevelDB bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(); + bool ReadSyncCheckpoint(uint256& hashCheckpoint); + bool WriteSyncCheckpoint(uint256 hashCheckpoint); + bool ReadCheckpointPubKey(std::string& strPubKey); + bool WriteCheckpointPubKey(const std::string& strPubKey); }; #endif // BITCOIN_TXDB_LEVELDB_H