Skip to content

Commit acf21ed

Browse files
committed
Eliminate block polling, use block_interval setting.
1 parent 0e7400b commit acf21ed

File tree

6 files changed

+143
-79
lines changed

6 files changed

+143
-79
lines changed

data/bn.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ bip65 = true
150150
bip90 = true
151151

152152
[node]
153-
# The time period for block polling after initial block download, defaults to 1 (0 disables).
154-
block_poll_seconds = 1
153+
# The time to wait for a requested block, defaults to 5.
154+
block_latency_seconds = 5
155155
# The minimum fee per byte required for transaction acceptance, defaults to 1.
156156
minimum_byte_fee_satoshis = 1
157157
# Request that peers relay transactions, defaults to false.

include/bitcoin/node/protocols/protocol_block_in.hpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
#ifndef LIBBITCOIN_NODE_PROTOCOL_BLOCK_IN_HPP
2020
#define LIBBITCOIN_NODE_PROTOCOL_BLOCK_IN_HPP
2121

22-
#include <atomic>
2322
#include <cstddef>
2423
#include <cstdint>
2524
#include <memory>
25+
#include <queue>
2626
#include <bitcoin/blockchain.hpp>
2727
#include <bitcoin/network.hpp>
2828
#include <bitcoin/node/define.hpp>
@@ -46,9 +46,10 @@ class BCN_API protocol_block_in
4646
virtual void start();
4747

4848
private:
49+
typedef std::queue<hash_digest> hash_queue;
50+
4951
static void report(const chain::block& block);
5052

51-
void get_block_inventory(const code& ec);
5253
void send_get_blocks(const hash_digest& stop_hash);
5354
void send_get_data(const code& ec, get_data_ptr message);
5455

@@ -60,14 +61,19 @@ class BCN_API protocol_block_in
6061
void handle_fetch_block_locator(const code& ec, get_headers_ptr message,
6162
const hash_digest& stop_hash);
6263

64+
void handle_timeout(const code& ec);
6365
void handle_stop(const code& ec);
6466

67+
// These are thread safe.
6568
full_node& node_;
6669
blockchain::safe_chain& chain_;
67-
bc::atomic<hash_digest> last_locator_top_;
68-
const uint32_t block_poll_seconds_;
70+
const asio::duration block_latency_;
6971
const bool headers_from_peer_;
7072
const bool blocks_from_peer_;
73+
74+
// This is protected by mutex.
75+
hash_queue backlog_;
76+
mutable upgrade_mutex mutex;
7177
};
7278

7379
} // namespace node

include/bitcoin/node/settings.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ class BCN_API settings
3636
/// Properties.
3737
uint32_t sync_peers;
3838
uint32_t sync_timeout_seconds;
39-
uint32_t block_poll_seconds;
39+
uint32_t block_latency_seconds;
4040
bool refresh_transactions;
41+
42+
/// Helpers.
43+
asio::duration block_latency() const;
4144
};
4245

4346
} // namespace node

src/parser.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,9 @@ options_metadata parser::load_settings()
406406
//// "The time limit for block response during initial block download, defaults to 5."
407407
////)
408408
(
409-
"node.block_poll_seconds",
410-
value<uint32_t>(&configured.node.block_poll_seconds),
411-
"The time period for block polling after initial block download, defaults to 1 (0 disables)."
409+
"node.block_latency_seconds",
410+
value<uint32_t>(&configured.node.block_latency_seconds),
411+
"The time to wait for a requested block, defaults to 5."
412412
)
413413
(
414414
/* Internally this is blockchain, but it is conceptually a node setting. */

src/protocols/protocol_block_in.cpp

Lines changed: 114 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,12 @@ using namespace bc::network;
4444
using namespace std::chrono;
4545
using namespace std::placeholders;
4646

47-
static constexpr auto perpetual_timer = true;
48-
49-
static inline uint32_t get_poll_seconds(const node::settings& settings)
50-
{
51-
// Set 136 years as equivalent to "never" if configured to disable.
52-
const auto value = settings.block_poll_seconds;
53-
return value == 0 ? max_uint32 : value;
54-
}
55-
5647
protocol_block_in::protocol_block_in(full_node& node, channel::ptr channel,
5748
safe_chain& chain)
58-
: protocol_timer(node, channel, perpetual_timer, NAME),
49+
: protocol_timer(node, channel, false, NAME),
5950
node_(node),
6051
chain_(chain),
61-
last_locator_top_(null_hash),
62-
block_poll_seconds_(get_poll_seconds(node.node_settings())),
52+
block_latency_(node.node_settings().block_latency()),
6353

6454
// TODO: move send_headers to a derived class protocol_block_in_70012.
6555
headers_from_peer_(negotiated_version() >= version::level::bip130),
@@ -77,9 +67,8 @@ protocol_block_in::protocol_block_in(full_node& node, channel::ptr channel,
7767

7868
void protocol_block_in::start()
7969
{
80-
// Use perpetual protocol timer to prevent stall (our heartbeat).
81-
protocol_timer::start(asio::seconds(block_poll_seconds_),
82-
BIND1(get_block_inventory, _1));
70+
// Use timer to drop slow peers.
71+
protocol_timer::start(block_latency_, BIND1(handle_timeout, _1));
8372

8473
// TODO: move headers to a derived class protocol_block_in_31800.
8574
SUBSCRIBE2(headers, handle_receive_headers, _1, _2);
@@ -93,59 +82,18 @@ void protocol_block_in::start()
9382
if (headers_from_peer_)
9483
{
9584
// Ask peer to send headers vs. inventory block announcements.
96-
SEND2(send_headers(), handle_send, _1, send_headers::command);
85+
SEND2(send_headers{}, handle_send, _1, send_headers::command);
9786
}
9887

99-
// Send initial get_[blocks|headers] message by simulating first heartbeat.
100-
set_event(error::success);
88+
send_get_blocks(null_hash);
10189
}
10290

10391
// Send get_[headers|blocks] sequence.
10492
//-----------------------------------------------------------------------------
10593

106-
// This is fired by the callback (i.e. base timer and stop handler).
107-
void protocol_block_in::get_block_inventory(const code& ec)
108-
{
109-
if (stopped(ec))
110-
{
111-
// This may get called more than once per stop.
112-
handle_stop(ec);
113-
return;
114-
}
115-
116-
// Since we need blocks do not stay connected to peer in bad version range.
117-
if (!blocks_from_peer_)
118-
{
119-
stop(ec);
120-
return;
121-
}
122-
123-
if (ec && ec != error::channel_timeout)
124-
{
125-
LOG_DEBUG(LOG_NODE)
126-
<< "Failure in block timer for [" << authority() << "] "
127-
<< ec.message();
128-
stop(ec);
129-
return;
130-
}
131-
132-
send_get_blocks(null_hash);
133-
}
134-
13594
void protocol_block_in::send_get_blocks(const hash_digest& stop_hash)
13695
{
137-
const auto chain_top = node_.top_block();
138-
const auto& chain_top_hash = chain_top.hash();
139-
const auto last_locator_top = last_locator_top_.load();
140-
141-
// Avoid requesting from the same start as last request to this peer.
142-
// This does not guarantee prevention, it's just an optimization.
143-
// If the peer does not respond to the previous request this will stall
144-
// unless a block announcement is connected or another channel advances.
145-
if (chain_top_hash != null_hash && chain_top_hash == last_locator_top)
146-
return;
147-
148-
const auto heights = block::locator_heights(chain_top.height());
96+
const auto heights = block::locator_heights(node_.top_block().height());
14997

15098
chain_.fetch_block_locator(heights,
15199
BIND3(handle_fetch_block_locator, _1, _2, stop_hash));
@@ -189,8 +137,6 @@ void protocol_block_in::handle_fetch_block_locator(const code& ec,
189137
<< encode_hash(stop_hash) << "]";
190138
}
191139

192-
// Save the locator top to prevent a redundant future request.
193-
last_locator_top_.store(last_hash);
194140
message->set_stop_hash(stop_hash);
195141

196142
if (use_headers)
@@ -262,6 +208,25 @@ void protocol_block_in::send_get_data(const code& ec, get_data_ptr message)
262208
if (message->inventories().empty())
263209
return;
264210

211+
///////////////////////////////////////////////////////////////////////////
212+
// Critical Section
213+
mutex.lock_upgrade();
214+
const auto fresh = backlog_.empty();
215+
mutex.unlock_upgrade_and_lock();
216+
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
217+
218+
// Enqueue the block inventory behind the preceding block inventory.
219+
for (const auto& inventory: message->inventories())
220+
if (inventory.type() == inventory::type_id::block)
221+
backlog_.push(inventory.hash());
222+
223+
mutex.unlock();
224+
///////////////////////////////////////////////////////////////////////////
225+
226+
// There was no backlog so the timer must be started now.
227+
if (fresh)
228+
reset_timer();
229+
265230
// inventory|headers->get_data[blocks]
266231
SEND2(*message, handle_send, _1, message->command);
267232
}
@@ -288,15 +253,19 @@ bool protocol_block_in::handle_receive_not_found(const code& ec,
288253
hash_list hashes;
289254
message->to_hashes(hashes, inventory::type_id::block);
290255

291-
// The peer cannot locate a block that it told us it had.
292-
// This only results from reorganization assuming peer is proper.
293-
for (const auto hash: hashes)
256+
for (const auto& hash: hashes)
294257
{
295258
LOG_DEBUG(LOG_NODE)
296259
<< "Block not_found [" << encode_hash(hash) << "] from ["
297260
<< authority() << "]";
298261
}
299262

263+
// The peer cannot locate one or more blocks that it told us it had.
264+
// This only results from reorganization assuming peer is proper.
265+
// Drop the peer so next channgel generates a new locator and backlog.
266+
if (!hashes.empty())
267+
stop(error::channel_stopped);
268+
300269
return true;
301270
}
302271

@@ -309,16 +278,50 @@ bool protocol_block_in::handle_receive_block(const code& ec,
309278
if (stopped(ec))
310279
return false;
311280

312-
// Reset the timer because we just received a block from this peer.
313-
// Once we are at the top this will end up polling the peer.
314-
reset_timer();
281+
///////////////////////////////////////////////////////////////////////////
282+
// Critical Section
283+
mutex.lock();
284+
285+
auto matched = !backlog_.empty() && backlog_.front() == message->hash();
286+
287+
if (matched)
288+
backlog_.pop();
289+
290+
// Empty after pop means we need to make a new request.
291+
const auto cleared = backlog_.empty();
292+
293+
mutex.unlock();
294+
///////////////////////////////////////////////////////////////////////////
295+
296+
// It is common for block announcements to cause block requests to be sent
297+
// out of backlog order due to interleaving of threads. This results in
298+
// channel drops during initial block download but not after sync. The
299+
// resolution to this issue is use of headers-first sync, but short of that
300+
// the current implementation performs well and drops peers no more
301+
// frequently than block announcements occur during initial block download,
302+
// and not typically after it is complete.
303+
if (!matched)
304+
{
305+
LOG_DEBUG(LOG_NODE)
306+
<< "Block [" << encode_hash(message->hash())
307+
<< "] unexpected or out of order from [" << authority() << "]";
308+
stop(error::channel_stopped);
309+
return false;
310+
}
315311

316312
message->validation.originator = nonce();
317313
chain_.organize(message, BIND2(handle_store_block, _1, message));
314+
315+
// Sending a new request will reset the timer as necessary.
316+
if (cleared)
317+
send_get_blocks(null_hash);
318+
else
319+
reset_timer();
320+
318321
return true;
319322
}
320323

321-
// The transaction has been saved to the block chain (or not).
324+
// The block has been saved to the block chain (or not).
322325
// This will be picked up by subscription in block_out and will cause the block
323326
// to be announced to non-originating peers.
324327
void protocol_block_in::handle_store_block(const code& ec,
@@ -372,6 +375,49 @@ void protocol_block_in::handle_store_block(const code& ec,
372375
// Subscription.
373376
//-----------------------------------------------------------------------------
374377

378+
// This is fired by the callback (i.e. base timer and stop handler).
379+
void protocol_block_in::handle_timeout(const code& ec)
380+
{
381+
if (stopped(ec))
382+
{
383+
// This may get called more than once per stop.
384+
handle_stop(ec);
385+
return;
386+
}
387+
388+
// Since we need blocks do not stay connected to peer in bad version range.
389+
if (!blocks_from_peer_)
390+
{
391+
stop(error::channel_stopped);
392+
return;
393+
}
394+
395+
if (ec && ec != error::channel_timeout)
396+
{
397+
LOG_DEBUG(LOG_NODE)
398+
<< "Failure in block timer for [" << authority() << "] "
399+
<< ec.message();
400+
stop(ec);
401+
return;
402+
}
403+
404+
///////////////////////////////////////////////////////////////////////////
405+
// Critical Section
406+
mutex.lock_shared();
407+
const auto backlog_empty = backlog_.empty();
408+
mutex.unlock_shared();
409+
///////////////////////////////////////////////////////////////////////////
410+
411+
// Can only end up here if time was not extended.
412+
if (!backlog_empty)
413+
{
414+
LOG_DEBUG(LOG_NODE)
415+
<< "Peer [" << authority()
416+
<< "] exceeded configured block latency.";
417+
stop(ec);
418+
}
419+
}
420+
375421
void protocol_block_in::handle_stop(const code&)
376422
{
377423
LOG_DEBUG(LOG_NETWORK)

src/settings.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
*/
1919
#include <bitcoin/node/settings.hpp>
2020

21+
#include <bitcoin/bitcoin.hpp>
22+
2123
namespace libbitcoin {
2224
namespace node {
2325

26+
using namespace bc::asio;
27+
2428
settings::settings()
2529
: sync_peers(0),
2630
sync_timeout_seconds(5),
27-
block_poll_seconds(1),
31+
block_latency_seconds(5),
2832
refresh_transactions(true)
2933
{
3034
}
@@ -35,5 +39,10 @@ settings::settings(config::settings context)
3539
{
3640
}
3741

42+
duration settings::block_latency() const
43+
{
44+
return seconds(block_latency_seconds);
45+
}
46+
3847
} // namespace node
3948
} // namespace libbitcoin

0 commit comments

Comments
 (0)