Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Head first mining #152

Closed

Conversation

gavinandresen
Copy link

Implement "head first mining" : propagate new 80-byte block headers as quickly as possible across the network, and give miners the opportunity to start mining an empty block as soon as they hear about the block header.

Only valid proof-of-work headers that connect to the most-work chain are relayed, so there are no feasible denial-of-service attacks.

Miners will switch to mining a regular block as soon as the full block is received and validated.

There is a hard-coded 30-second timeout; if the full block data takes longer than 30 seconds to get validated and propagated across the network, or is never sent, miners switch back to mining non-empty blocks on the last fully-validated block.

Details:

New getblocktemplate behavior: getblocktemplate returns an empty (just coinbase transaction) block template in the time between receiving a new block's 'headers' message and receiving and validating the 'block' message. Long-polling getblocktemplate connections are interrupted when a new best-header is received and when blocks are fully validated.

New command-line option, -blockheadernotify. Just like -blocknotify, but notified when a new most-work-header is received.

New p2p message, 'invalidblock'. Just like the 'block' message, but sent to peers when an invalid block that has valid proof-of-work is received, to tell them they should stop mining on the block's header.

Only peers that follow the BIP130 'sendheaders' protocol will be relayed headers immediately, and only they will receive 'invalidblock' messages. SPV clients don't 'sendheaders', so should be unaffected. Older clients that don't understand the 'invalidblock' message will ignore it.


Please limit discussion here on github to code review and testing results. Debate on whether or not head-first mining is a benefit to the network should happen in the bitcoin-classic #debate slack channel and/or the #bitcoin-dev IRC channel.

@ChainQuery
Copy link

utACK - Great work Gavin!

@olympischbriesje
Copy link

Excellent!

@cypherdoc
Copy link

nice job Gavin!

@zander
Copy link

zander commented Mar 16, 2016

Debate on whether or not head-first mining is a benefit to the network should happen in the bitcoin-classic #debate slack channel and/or the #bitcoin-dev IRC channel.

Yes, that is the place where you can find the Classic guys to find out more details.
Discussion is encouraged also on other forums. But I will mirror Gavins point that Github is for talking about code and testing results etc only. Please keep that in mind everyone!

@gavinandresen
Copy link
Author

Travis error is use of map::erase method that is C++11; I'll fix.

Also I didn't mention how to test:

Run bitcoind -blocknotify='/tmp/notify.sh block %s' -blockheadernotify='/tmp/notify.sh header %s' where /tmp/notify.sh is something like:

#!/usr/bin/env bash

echo -n $@ " ">> /tmp/NOTIFY_MSGS
date >> /tmp/NOTIFY_MSGS

... and you'll get a /tmp/NOTIFY_MSGS that tells you how many seconds between receiving a header and receiving and validating the full block.

On my machine, with a debug build, it is a consistent 5 or 6 seconds. So if I was a miner, using this code should drop my orphan rate by about 0.8 to 1 percent.

@cpacia
Copy link

cpacia commented Mar 16, 2016

New p2p message, 'invalidblock'. Just like the 'block' message, but sent to peers when an invalid block that has valid proof-of-work is received, to tell them they should stop mining on the block's header.

Question here. Does this message open up the possibility of a competing miner relaying invalidblock messages to try to get other miners to stop mining the header? Or is there logic to prevent that?

@gavinandresen
Copy link
Author

@cpacia : good question, @zander asked the same thing. If a peer lies and sends a valid block in an 'invalidblock' message, the peer is marked as misbehaving (and will be kick-banned if it keeps doing it). The valid block IS added to the best chain, though, and will be sent to peers in 'block' messages.

Un-asked-for 'invalidblock' messages are ignored (they must be sent in response to 'getdata' requests).

A peer could do this:

send 'inv'
   we send 'getdata'
they send 'invalidblock' with random garbage

... but that is no different from sending a 'block' message with random garbage, and is harmless (either the proof-of-work or the merkle root will fail to validate very quickly).

@cpacia
Copy link

cpacia commented Mar 16, 2016

Thanks, makes sense now.

@gavinandresen
Copy link
Author

@cpacia : I'll write a regression test to make sure a miner can't tweak a valid block's transaction data and relay it as 'invalidblock' (invalidblock messages that fail either the PoW or have transaction data that don't match the merkle root in the header should be ignored).

@gavinandresen
Copy link
Author

Two more regression tests added for the 'invalidblock' message.

The travis error is a spurious timeout; maybe the travis machine is less busy now and all the tests will succeed...

@SartoNess
Copy link

Pretty interesting

@lrvl
Copy link

lrvl commented Mar 16, 2016

ACK

@JohnHanks
Copy link

NACK

I dont understand the point of recieving the last block header and wasting time mining an empty block waiting for the last full block data in order to mine a non empty block. Or am I just taking crazy pills here?

@rebroad
Copy link

rebroad commented Mar 16, 2016

NACK. I thought Classic was supposed to be an alternative to Core providing greater capacity... mining empty blocks would completely defeat the main purpose for which I thought Classic exists - who would beneft from this change? the miners perhaps, but not the users. The miners are already benefiting from the smaller block sizes - i.e. Core. Classic is supposed to be an alternative where the users are put before the miners, isn't it?

@gdassori
Copy link

In the beginning I was thinking the same about empty blocks, but someone
rightly pointed this would happen only on the first 30 seconds, and so it's
only a 30s delay on the block interval. A block is "wasted", but the
consequent delay is minimal.

Head first, instead, is safer than the already widely done SPV mining and
reduce the risk that miners would mine on invalid headers. The benefit is
both for the miners than the users (empty or not, another valid block is
added and a confirmation is added).

ACK

2016-03-17 0:41 GMT+01:00 R E Broadley notifications@github.com:

NACK. I thought Classic was supposed to be an alternative to Core
providing greater capacity... mining empty blocks would completely defeat
the main purpose for which I thought Classic exists - who would beneft from
this change? the miners perhaps, but not the users. The miners are already
benefiting from the smaller block sizes - i.e. Core. Classic is supposed to
be an alternative where the users are put before the miners, isn't it?


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
#152 (comment)

@cpacia
Copy link

cpacia commented Mar 17, 2016

@JohnHanks @rebroad The alternative is the miner mines on top of a previous block (essentially working on a fork) which is just going to be orphaned anyway. The purpose is to reduce orphans and level the playing field for small miners.

@fraggle222
Copy link

Is there something here to prevent mining empty block on top of empty block? So if header transmission included claim of transactions or no transactions, then at least for miners following 'rules' we would avoid having 2 blocks in a row that are empty? So headerImEmpty wold not lead to mining on top of that, but headerIHaveTxs would.

CNodeState* s = State(pnode->GetId());
if (s && !PeerHasHeader(s, pindexBestHeader)) {
s->pindexBestHeaderSent = pindexBestHeader;
pnode->PushMessage(NetMsgType::HEADERS, vHeaders);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we PushMessage to peers in paralllel in order to decrease delay?

@saltduck
Copy link

@gavinandresen Don't forget to pick a new PROTOCOL_VERSION.

@bitkevin
Copy link

utACK. As I know not matter bitcoind is support header-first mining or not, most pools are doing the work beside bitcoind. They patched bitcoind to get notification like '-blockheadernotify' or listen to other pool's stratum server to get the new height job and make empty block util they received full block.

@rebroad
Copy link

rebroad commented Mar 17, 2016

@gdassori No, it wouldn't be 30 seconds, it would be 10 minutes as the bitcoin network always adjusts to maintain an average of 1 block every 10 minutes. So, for example, ten empty blocks in 2 weeks might look like it only wasted 5 minutes of time, but it will cause the difficulty to rise such that those ten blocks add 100 minutes to the next 2 weeks worth of blocks (assuming no change in mining power), so overall, 100 minutes of network usefulness is lost due to those 10 empty blocks.

@rebroad
Copy link

rebroad commented Mar 17, 2016

@cpacia You are correct-ish... this is assuming the header received has transactions still to arrive. The problem, if I understand correctly, is that we're allowing mining of empty blocks where the previous block was also empty, thereby getting empty blocks in the main chain. Yes, allow mining on headers, but ONLY when the previous block wasn't empty. This would be a far better solution, and would still allow mining on headers, but would stop empty blocks getting into the main chain by miners using this code.

@Polve
Copy link

Polve commented Mar 17, 2016

@rebroad: when you do not have the new block, if you don't mine on his header you have to shutdown the miners.
that's ok in a scenario where blocks have no more subsidy and all miner revenues are from tx fees, but until then header first mining will be the default for a conscious miner

@zander
Copy link

zander commented Mar 17, 2016

@rebroad keep it about the code here, we asked politely to move the rest of the talk elsewhere. See my previous message.
Everyone else; please don't get sucked into defending. Keep the noise down, thanks! :)

@Zaromet
Copy link

Zaromet commented Mar 17, 2016

I don't know if this is correct or not but I remember reading that CGminer and/or BFGminer have/had something that will not allowed them to switch back to block they already mined...

@@ -147,6 +148,8 @@ class CBlockIndex

//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
uint32_t nSequenceId;
//! (memory only) The time this node first heard about blocks
static std::map<const CBlockIndex*, int64_t> firstSeenTime;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bad idea. But I think that this means that the destructor of CBlockIndex needs to be introduced that removes itself from this map to avoid a memory leak.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may also want to either delete the copy constructor and assignment operator, or implement them. If not, the copies won't have the firstSeenTime information.

@rebroad
Copy link

rebroad commented Mar 17, 2016

@zander I'm not sure I understand what you mean; my comments are purely prompted by this pull request and code within. It would make more efficient use of the blockchain if the average time between blocks remains fixed.

@rebroad
Copy link

rebroad commented Mar 17, 2016

I may have misunderstood this pull request.. if it already is coded to avoid empty blocks going into the main chain, then ACK, e.g. if after receiving an empty block (or header) the txs haven't been seen, then I totally approve/ACK dropping back to the previous block. Anyway, it's quite simply a scaling issue IMHO, and given Classic aims to improve scalability, I'd expect the developers to be in favour of not encouraging empty blocks.

@zander
Copy link

zander commented Mar 17, 2016

@rebroad

I'm not sure I understand what you mean; my comments are purely prompted by this pull request and code within.

Reading your answers I suggest https://www.reddit.com/r/btc/comments/4aogb9/head_first_mining_by_gavinandresen_pull_request/

Ask your questions there, general information and ideas about approaches and concepts make much more sense to discuss on a forum or similar. Please.

@JohnHanks
Copy link

get it all out on the table here. forget reddit @zander

if there are issues with codes' pull requests @rebroad has every right to discuss it here.

follow up to my previous post: if you can detect using the header that the block is empty or not or check the utxo pool in order to not waste time mining empty blocks when in fact txs need to commit in to the chain then ACK

@gavinandresen
Copy link
Author

Added a CBlockIndex destructor as suggested by @zander , and squashed it into the first commit.

@rebroad @JohnHanks : empty blocks are not a capacity problem, please see my comment here for why: https://www.reddit.com/r/btc/comments/4aogb9/head_first_mining_by_gavinandresen_pull_request/d12gmir

@@ -327,13 +327,11 @@ static UniValue BIP22ValidationResult(const CValidationState& state)
// they are the same, they are different only in the time between receiving a
// block header and receiving and then fully validating the block with
// all its transactions.
static CBlockIndex* GetBuildTip(CBlockIndex* validated_tip, CBlockIndex* header_tip)
static CBlockIndex* GetBuildTip(int64_t nHeadFirst, CBlockIndex* validated_tip, CBlockIndex* header_tip)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change nHeadFirst to nHeadFirstTimeout, a better name?

@gavinandresen
Copy link
Author

I had some time to do some testing of the networking part of this:

Test setup:

5 servers connected in a chain:

NJ USA - London UK - Tokyo JP - Sydney AU - LA USA - NJ USA

Start and end of the chain are on different bitcoind's running on the same server, with the new -logtimemicros option set to get accurate microsecond timing (all servers are running ntp, but I only report/compare times at the beginning/end of the chain, which are on the same machine so times are directly comparable).

$10/month Vultr.com VPS servers (binaries compiled on a $20/month Vultr.com VPS server with 2GB memory).

RESULTS for git HEAD April 4 a914968:
Ten empty blocks, one minute apart: average 1.5 seconds

RESULTS for head-first networking change:
Ten empty blocks, one minute apart: average 0.8 seconds

That is what I expected; latency is dropped almost in half, because a request-response round-trip is avoided after first networking hop.

I'll test with full (1MB and 2MB) blocks next-- hopefully tomorrow, but that might not happen until next week (I'm traveling again).

Replaces CBlockIndex::nSequenceId with the time a
block is first seen, and moves it from the CBlockIndex structure
to a separate map (to save memory).

Fully validated blocks are still preferred to un- or partially
validated blocks.
Refactor: have CreateNewBlock take transactions from a passed-in
memory pool object instead of the global mempool object, and
build on a passed-in chain tip.
This is a do-nothing refactor that just replaces calls to
chainActive.Tip() and direct use of the global mempool object
with build_tip and a reference to either the mempool or an
empty mempool.
Mine empty blocks on top of valid proof-of-work block headers
(for at most 30 seconds, to prevent block data withholding attacks)
When we get block header(s) with more proof-of-work than our
current tip, send them immediately to peers that we don't
think already have them.

And if we receive an invalid block for one of those
more-proof-of-work headers, tell peers about it
immediately with a new 'invalidblock' message.
This makes head-first mining opt-in. To get empty
blocks built on validated headers, pass:

    { "mode" : "template", "headfirst" : 30 }

... in the getblocktemplate call. The argument
to "headfirst" is the maximum number of seconds
getblocktemplate will return an empty block between
receiving and validating the block header and receiving
and validating the full block data.

A new parameter is added to the getblocktemplate result
object:

    "previousfullyvalidated" : true|false

... which will be false if the template returned is an
empty block built on one or more headers.
@gavinandresen
Copy link
Author

I'm going to close this, reorganize the commits a bit, and re-open as two separate pull requests:

One that just optimizes the network relay code, and adds -blockheadernotify

And a second that teaches getblocktemplate to mine empty blocks on headers if the miner so desires.

jamoes pushed a commit to jamoes/bitcoinclassic that referenced this pull request Feb 3, 2017
…atch/remove_obsolete_rpc-tests_shell_script

remove rpc-tests.sh which was replaced by Python script
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.