Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Relay blocks as a "preview" before checking the transactions in them #1586

Closed
wants to merge 2 commits into from

3 participants

@luke-jr

DRAFT status, please help test! Trying to coordinate testing on IRC, possibly using NTP and https://gist.github.com/3094657

@jgarzik
Owner

@gmaxwell and I are definitely interested in this, at least. Will keep this pull request open.

A rebase would be nice, if you have time.

@luke-jr

Unfortunately, it seems p2p connections are handled synchronously, so everything this does to rush out the relaying is for naught. The only simple way I can see to refactor this to be asynchronous would be either to use coroutines (which Boost won't have until 2013) or one thread per command/connection. Either one is probably too late for 0.7, as would be an even larger refactor, I presume?

@BitcoinPullTester

Automatic sanity-testing: PASSED, see http://jenkins.bluematt.me/pull-tester/4e54ea804ccdd2223e622497f0d46cceb27b9d22 for binaries and test log.

@BitcoinPullTester

Automatic sanity-testing: FAILED MERGE, see http://jenkins.bluematt.me/pull-tester/4e54ea804ccdd2223e622497f0d46cceb27b9d22 for test log.

This pull does not merge cleanly onto current master
This test script verifies pulls every time they are updated. It, however, dies sometimes and fails to test properly. If you are waiting on a test, please check timestamps to verify that the test.log is moving at http://jenkins.bluematt.me/pull-tester/current/
Contact BlueMatt on freenode if something looks broken.

@jgarzik
Owner

Closing - timeout. Interesting proposal, needs more work and dev momentum.

@jgarzik jgarzik closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 12, 2012
  1. @luke-jr
Commits on Aug 13, 2012
  1. @luke-jr

    Begin relaying blocks immediately after doing validity checks on the …

    luke-jr authored
    …header, even before our own download has completed
This page is out of date. Refresh to see the latest.
Showing with 153 additions and 34 deletions.
  1. +133 −34 src/main.cpp
  2. +18 −0 src/main.h
  3. +1 −0  src/net.h
  4. +1 −0  src/protocol.cpp
View
167 src/main.cpp
@@ -1741,34 +1741,42 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
-bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const
+bool CBlock::CheckBlockHeader(bool fCheckPOW) const
{
- // These are checks that are independent of context
- // that can be verified before saving an orphan block.
-
- // Size limits
- if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
- return DoS(100, error("CheckBlock() : size limits failed"));
+ // These are checks that are independent of transaction count/content
+ // that can be verified with just the block header
// Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(GetHash(), nBits))
- return DoS(50, error("CheckBlock() : proof of work failed"));
+ return DoS(50, error("CheckBlockHeader() : proof of work failed"));
// Check timestamp
if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
- return error("CheckBlock() : block timestamp too far in the future");
+ return error("CheckBlockHeader() : block timestamp too far in the future");
+
+ return true;
+}
+
+bool CBlock::CheckBlockBody(bool fCheckMerkleRoot) const
+{
+ // These are checks that are independent of context
+ // that can be verified before saving an orphan block.
+
+ // Size limits
+ if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
+ return DoS(100, error("CheckBlockBody() : size limits failed"));
// First transaction must be coinbase, the rest must not be
if (vtx.empty() || !vtx[0].IsCoinBase())
- return DoS(100, error("CheckBlock() : first tx is not coinbase"));
+ return DoS(100, error("CheckBlockBody() : first tx is not coinbase"));
for (unsigned int i = 1; i < vtx.size(); i++)
if (vtx[i].IsCoinBase())
- return DoS(100, error("CheckBlock() : more than one coinbase"));
+ return DoS(100, error("CheckBlockBody() : more than one coinbase"));
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
if (!tx.CheckTransaction())
- return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
+ return DoS(tx.nDoS, error("CheckBlockBody() : CheckTransaction failed"));
// Check for duplicate txids. This is caught by ConnectInputs(),
// but catching it earlier avoids a potential DoS attack:
@@ -1778,7 +1786,7 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const
uniqueTx.insert(tx.GetHash());
}
if (uniqueTx.size() != vtx.size())
- return DoS(100, error("CheckBlock() : duplicate transaction"));
+ return DoS(100, error("CheckBlockBody() : duplicate transaction"));
unsigned int nSigOps = 0;
BOOST_FOREACH(const CTransaction& tx, vtx)
@@ -1786,15 +1794,20 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const
nSigOps += tx.GetLegacySigOpCount();
}
if (nSigOps > MAX_BLOCK_SIGOPS)
- return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));
+ return DoS(100, error("CheckBlockBody() : out-of-bounds SigOpCount"));
// Check merkle root
if (fCheckMerkleRoot && hashMerkleRoot != BuildMerkleTree())
- return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
+ return DoS(100, error("CheckBlockBody() : hashMerkleRoot mismatch"));
return true;
}
+bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const
+{
+ return CheckBlockHeader(fCheckPOW) && CheckBlockBody(fCheckMerkleRoot);
+}
+
bool CBlock::AcceptBlock()
{
// Check for duplicate
@@ -1849,18 +1862,17 @@ bool CBlock::AcceptBlock()
return true;
}
-bool ProcessBlock(CNode* pfrom, CBlock* pblock)
+bool ProcessBlockHeader(CNode* pfrom, CBlock* pblock, const uint256& hash, bool& fEnablePreview)
{
// Check for duplicate
- uint256 hash = pblock->GetHash();
if (mapBlockIndex.count(hash))
- return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,20).c_str());
+ return error("ProcessBlockHeader() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,20).c_str());
if (mapOrphanBlocks.count(hash))
- return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
+ return error("ProcessBlockHeader() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
// Preliminary checks
- if (!pblock->CheckBlock())
- return error("ProcessBlock() : CheckBlock FAILED");
+ if (!pblock->CheckBlockHeader(true))
+ return error("ProcessBlockHeader() : CheckBlockHeader FAILED");
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain)
@@ -1871,7 +1883,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
if (pfrom)
pfrom->Misbehaving(100);
- return error("ProcessBlock() : block with timestamp before last checkpoint");
+ return error("ProcessBlockHeader() : block with timestamp before last checkpoint");
}
CBigNum bnNewBlock;
bnNewBlock.SetCompact(pblock->nBits);
@@ -1881,15 +1893,48 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
if (pfrom)
pfrom->Misbehaving(100);
- return error("ProcessBlock() : block with too little proof-of-work");
+ return error("ProcessBlockHeader() : block with too little proof-of-work");
+ }
+ }
+
+ // Only enable block preview broadcasts when the difficulty in making them is high enough that it's not worth DoSing with them
+ {
+ double dDiff = (double)0x0000ffff / (double)(pblock->nBits & 0x00ffffff);
+ for (int nShift = pblock->nBits >> 24; nShift < 29; ++nShift)
+ dDiff *= 0x100;
+ fEnablePreview = dDiff >= 0x20000;
+ }
+
+ if (pblock->hashPrevBlock == pindexBest->GetBlockHash())
+ {
+ // We can check the immediate next block's time and bits headers earlier
+ if (pblock->GetBlockTime() <= pindexBest->GetMedianTimePast())
+ return error("ProcessBlockHeader() : block's timestamp is too early");
+
+ if (pblock->nBits != GetNextWorkRequired(pindexBest, pblock))
+ return pblock->DoS(100, error("ProcessBlockHeader() : incorrect proof of work"));
+
+ if (fEnablePreview)
+ {
+ // Relay blocks after only checking their header, so we don't bias them against including transactions too much
+ pblock->csBlockDownload = new std::pair<boost::mutex, boost::condition_variable>();
+ pblock->fBlockDownloading = true;
+ RelayMessage(CInv(MSG_BLOCK_PREVIEW, hash), *pblock);
}
}
+ return true;
+}
+
+bool ProcessBlockBody(CNode* pfrom, CBlock* pblock, const uint256& hash)
+{
+ if (!pblock->CheckBlockBody(true))
+ return error("ProcessBlockBody() : CheckBlockBody FAILED");
// If don't already have its previous block, shunt it off to holding area until we get it
if (!mapBlockIndex.count(pblock->hashPrevBlock))
{
- printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
+ printf("ProcessBlockBody: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
CBlock* pblock2 = new CBlock(*pblock);
mapOrphanBlocks.insert(make_pair(hash, pblock2));
mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));
@@ -1902,7 +1947,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// Store to disk
if (!pblock->AcceptBlock())
- return error("ProcessBlock() : AcceptBlock FAILED");
+ return error("ProcessBlockBody() : AcceptBlock FAILED");
// Recursively process any orphan blocks that depended on this one
vector<uint256> vWorkQueue;
@@ -1923,7 +1968,22 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
mapOrphanBlocksByPrev.erase(hashPrev);
}
- printf("ProcessBlock: ACCEPTED\n");
+ printf("ProcessBlockBody: ACCEPTED\n");
+ return true;
+}
+
+bool ProcessBlock(CNode* pfrom, CBlock* pblock)
+{
+ uint256 hash = pblock->GetHash();
+ bool fEnablePreview;
+ if (!ProcessBlockHeader(pfrom, pblock, hash, fEnablePreview))
+ return false;
+ if (!ProcessBlockBody(pfrom, pblock, hash))
+ {
+ if (fEnablePreview)
+ pblock->nDoS = 0;
+ return false;
+ }
return true;
}
@@ -2355,6 +2415,7 @@ bool static AlreadyHave(CTxDB& txdb, const CInv& inv)
}
case MSG_BLOCK:
+ case MSG_BLOCK_PREVIEW:
return mapBlockIndex.count(inv.hash) ||
mapOrphanBlocks.count(inv.hash);
}
@@ -2590,7 +2651,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// find last block in inv vector
unsigned int nLastBlock = (unsigned int)(-1);
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) {
- if (vInv[vInv.size() - 1 - nInv].type == MSG_BLOCK) {
+ int& type = vInv[vInv.size() - 1 - nInv].type;
+ if (type == MSG_BLOCK || type == MSG_BLOCK_PREVIEW) {
nLastBlock = vInv.size() - 1 - nInv;
break;
}
@@ -2829,18 +2891,55 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
else if (strCommand == "block")
{
+ bool fFullBlock = vRecv.nType & SER_BLOCKHEADERONLY;
+ if (!fFullBlock)
+ // FIXME: Do we need to support this?
+ printf("received block with SER_BLOCKHEADERONLY already set; this is not supported!\n");
+
CBlock block;
+ vRecv.nType |= SER_BLOCKHEADERONLY;
vRecv >> block;
+ if (fFullBlock)
+ vRecv.nType &= ~SER_BLOCKHEADERONLY;
+ uint256 hashBlock = block.GetHash();
+ printf("received block header %s\n", hashBlock.ToString().substr(0,20).c_str());
+ bool fEnablePreview;
+ // NOTE: ProcessBlockHeader potentially begins this block relaying as a preview.
+ if (!ProcessBlockHeader(pfrom, &block, hashBlock, fEnablePreview))
+ {
+ // We don't want this block. Can we skip the download? TODO
+ if (block.nDoS) pfrom->Misbehaving(block.nDoS);
+ {
+ boost::lock_guard<boost::mutex> lock(block.csBlockDownload->first);
+ block.fBlockDownloading = false;
+ }
+ block.csBlockDownload->second.notify_all();
+ }
+ else
+ {
+ // Download the block contents
+ vRecv >> block.vtx;
+ {
+ boost::lock_guard<boost::mutex> lock(block.csBlockDownload->first);
+ block.fBlockDownloading = false;
+ }
+ block.csBlockDownload->second.notify_all();
+ printf("received block %s\n", block.GetHash().ToString().substr(0,20).c_str());
- printf("received block %s\n", block.GetHash().ToString().substr(0,20).c_str());
- // block.print();
+ CInv inv(MSG_BLOCK, block.GetHash());
+ pfrom->AddInventoryKnown(inv);
- CInv inv(MSG_BLOCK, block.GetHash());
- pfrom->AddInventoryKnown(inv);
+ if (ProcessBlockBody(pfrom, &block, hashBlock))
+ mapAlreadyAskedFor.erase(inv);
- if (ProcessBlock(pfrom, &block))
- mapAlreadyAskedFor.erase(inv);
- if (block.nDoS) pfrom->Misbehaving(block.nDoS);
+ /* Ignore DoS if block previewing is enabled:
+ * - We might have been relayed the body before or without
+ * checking it.
+ * - Previewing is only enabled when the block difficulty is
+ * sufficiently high to make it costly for a DoS.
+ */
+ if (block.nDoS && !fEnablePreview) pfrom->Misbehaving(block.nDoS);
+ }
}
View
18 src/main.h
@@ -12,6 +12,9 @@
#include <list>
+#include <boost/thread/condition_variable.hpp>
+#include <boost/thread/mutex.hpp>
+
class CWallet;
class CBlock;
class CBlockIndex;
@@ -837,6 +840,10 @@ class CBlock
mutable int nDoS;
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
+ // Fast block relay
+ bool fBlockDownloading;
+ std::pair<boost::mutex, boost::condition_variable> *csBlockDownload;
+
CBlock()
{
SetNull();
@@ -854,7 +861,15 @@ class CBlock
// ConnectBlock depends on vtx being last so it can calculate offset
if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY)))
+ {
+ if (fBlockDownloading)
+ {
+ boost::unique_lock<boost::mutex> lock(csBlockDownload->first);
+ while (fBlockDownloading)
+ csBlockDownload->second.wait(lock);
+ }
READWRITE(vtx);
+ }
else if (fRead)
const_cast<CBlock*>(this)->vtx.clear();
)
@@ -870,6 +885,7 @@ class CBlock
vtx.clear();
vMerkleTree.clear();
nDoS = 0;
+ fBlockDownloading = false;
}
bool IsNull() const
@@ -1021,6 +1037,8 @@ class CBlock
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew);
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos);
+ bool CheckBlockHeader(bool fCheckPOW) const;
+ bool CheckBlockBody(bool fCheckMerkleRoot) const;
bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true) const;
bool AcceptBlock();
View
1  src/net.h
@@ -72,6 +72,7 @@ enum
{
MSG_TX = 1,
MSG_BLOCK,
+ MSG_BLOCK_PREVIEW,
};
class CRequestTracker
View
1  src/protocol.cpp
@@ -16,6 +16,7 @@ static const char* ppszTypeName[] =
"ERROR",
"tx",
"block",
+ "block",
};
CMessageHeader::CMessageHeader()
Something went wrong with that request. Please try again.