Bitcoin full node and query server

Oct 27, 2024

View in context in the libbitcoin Slack #general mirror.

A
Antoine Poinsot02:27
Finally i'd be interesting in learning how you manage your peers. You mentioned a few times how you had put a lot of work into this, i guess it would be interesting to include in my writeup. Then i'll stop bothering you all with all my questions.
18 replies · evoskuil, Antoine Poinsot · last Oct 27, 2024
evoskuil02:52
The questions are fine, no worries. We don’t ban, but we drop and connect aggressively. We currently have a target outbound peer count, inbound limit, and explicit “manual” connections that are maintained (reconnected on drop). At startup there are also seed connections, dropped after seeding. These are managed by four specialized “sessions”, and a base class. The C++ API also allows for addition of manual connections, and for subscription to various callbacks, such as connection and messages by type. These are used by the many protocol specializations. There are then “chaser” specializations that impose order for the chain building process, as we’ve been discussing.
evoskuil03:02
The protocols are attached to each channel and the block download protocol supports performance metrics (block byte totals) and feeds them to the outbound session (we don’t manage inbound or manual for block sync perf), which maintains a rate table. Configurable timers are used to measure download rates on a per channel basis, and to perform standard deviation computation across the set (if more than 2). Stalled channels are dropped (even if less that 3 or if SD is not enabled by config). Slow channels are defined by a configurable negative deviation from the SD and dropped. Drops are very common, so rapid connection is important. We can very rapidly connect even thousands of peers, but best sync on a 2Gbps network is 100-200 peers. Rapid connection is part address book hygiene and part concurrency. To connect 1 we attempt 5 (by default) with a race established between them, dropping all but the first to complete handshake. We find about 1/5 addresses are good on average.
evoskuil03:05
Completion for speed is not used for header sync, we want to see all peers, not just the fast ones. But with a strong header chain we just want the blocks as fast as we can get them. Dividing work up among a larger set of peers doesn’t increase load on the network, and rate management moves that load to where it is more available (load balancing). We will eventually make the total connection count more dynamic, so that it throttles back once the chain is “current” and back up again if it falls behind (eg after a shutdown period).
evoskuil03:15
The node rapidly finds the fastest available peers on the network and uses very little of their resources because of work splitting. There are no redundant block requests. Work is managed by the check chaser such that channels request work (hashes to download) and if the channel is dropped, the hashes are returned to the chaser. As channels become starved, more work is generated, and if no more is pending, the slowest channels are dropped (to prevent redundant downloads, since there is no way to withdraw a request made to a peer, and we always request the full batch of work) and their work divided up. This can cause a few seconds of delay in each download “window” as the last work is shuffled around, but a peer cannot delay the sync. The window can also be configured to be the whole chain, but breaking it up is good for preventing validation delay.
evoskuil03:18
Full DoS protect is not implemented, but that consists mainly of rate limiting requests based on config. Otherwise, as long as the protocol is complied with the peer is valid, and if not is immediately dropped. We do have ip/host configurable white/black listing as well, but it’s not really a DoS thing and I don’t use it while perf testing sync.
evoskuil03:33
*competition for speed.
evoskuil03:33
Slack threading blows.
A
Antoine Poinsot16:09
At startup there are also seed connections, dropped after seeding.
Where do you seed from? DNS seeds like Core? Fixed ones? For seeding Core has an indirection, it gets ip addresses from either the DNS or fixed seeds, queries these ips for addresses, and syncs from those final received addresses.
There are no redundant block requests.
So: (highly) redundant but not-monitored header requests, and non-redundant but monitored block requests?
evoskuil16:13
For seeding we just connect to the configured set of peers and drop on completion and timers. These can be IP addresses or DNS names, and port. The defaults are currently all DNS names that point to libbitcoin.net, which allows us to redirect them for testing. However these are not typically operational (until we release v4).
(edited)
evoskuil16:15
The node operator can specify any node(s) for this in config, and/or can prepopulate the address cache file with the same values. The node won't start unless it as [batch x outbound] unique addresses in the cache, either from seeding or otherwise. Seeding is bypassed if the address cache is sufficiently populated.
(edited)
evoskuil16:16
So: (highly) redundant but not-monitored header requests, and non-redundant but monitored block requests?
yes
A
Antoine Poinsot16:21
Stalled channels are dropped
After how long do you consider a peer as stalled?
A
Antoine Poinsot16:22
Slow channels are defined by a configurable negative deviation from the SD and dropped.
What's the default for this value?
evoskuil18:54
There is internal optimization in the redundant header downloads. Headers that are already stored get discarded as they are redundantly received, after identification via hash. But we download the full header chain from each peer at first startup. However once there is a candidate chain (often just a few seconds) a populated locator is provided to peers, so unless they disagree, there is no redundancy.