Skip to content

Commit

Permalink
server: Always respond to getheaders.
Browse files Browse the repository at this point in the history
The existing logic for responding to getheaders is based on the old sync
model before headers-first syncing was introduced and thus avoids
responding whenever the current local chain is not considered to be
current.  This existing behavior was important under the the old sync
model since it would otherwise lead to unnecessarily downloading a bunch
of blocks among other undesirable corner cases, however, it does come
with some downsides.

For example, one less than ideal consequence of not responding is that
it can lead to peers appearing to be unresponsive and/or stalled rather
than merely not having anything interesting yet.

Also, there are a variety of corner cases where a peer might temporarily
no longer consider itself current such as after being unable to
communicate with the network for a long time, or in testing scenarios
where there are necessarily long periods of time without any new blocks.

However, since peers now use the headers to determine what blocks to
download, serving up headers before the peer is known to be current no
longer leads to undesirable behavior.

Taking the aforementioned factors into consideration, this modifies the
semantics to always respond to getheaders requests, even before the
local chain is fully synced.

Further, in order to avoid needlessly sending extra data early during
the initial sync process and to provide some additional protection
against low-effort DoS attempts, a new check is introduced to respond
with empty headers when the local chain does not yet have the minimum
cumulative work value already known from the chain parameters to have
been achieved on the network.  Responding with empty headers signals to
the remote peer that there are not any interesting headers available
without the local peer appearing unresponsive.
  • Loading branch information
davecgh committed Nov 30, 2022
1 parent 79402f2 commit 0e7920c
Showing 1 changed file with 29 additions and 6 deletions.
35 changes: 29 additions & 6 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/decred/dcrd/internal/netsync"
"github.com/decred/dcrd/internal/rpcserver"
"github.com/decred/dcrd/internal/version"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/peer/v3"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/wire"
Expand Down Expand Up @@ -471,6 +472,13 @@ type server struct {
bytesSent uint64 // Total bytes sent by all peers since start.
shutdown int32

// minKnownWork houses the minimum known work from the associated network
// params converted to a uint256 so the conversion only needs to be
// performed once when the server is initialized. Ideally, the chain params
// should be updated to use the new type, but that will be a major version
// bump, so a one-time conversion is a good tradeoff in the mean time.
minKnownWork uint256.Uint256

chainParams *chaincfg.Params
addrManager *addrmgr.AddrManager
connManager *connmgr.ConnManager
Expand Down Expand Up @@ -1258,13 +1266,18 @@ func (sp *serverPeer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) {

// OnGetHeaders is invoked when a peer receives a getheaders wire message.
func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
// Ignore getheaders requests if not in sync unless the local best chain
// is exactly at the same tip as the requesting peer.
locatorHashes := msg.BlockLocatorHashes
// Send an empty headers message in the case the local best known chain does
// not have the minimum cumulative work value already known to have been
// achieved on the network. This signals to the remote peer that there are
// no interesting headers available without appearing unresponsive.
chain := sp.server.chain
if !sp.server.syncManager.IsCurrent() && (len(locatorHashes) == 0 ||
*locatorHashes[0] != chain.BestSnapshot().PrevHash) {

tipHash := chain.BestSnapshot().Hash
workSum, err := chain.ChainWork(&tipHash)
if err == nil && workSum.Lt(&sp.server.minKnownWork) {
srvrLog.Debugf("Sending empty headers to peer %s in response to "+
"getheaders due to local best known tip having too little work",
sp.Peer)
sp.QueueMessage(&wire.MsgHeaders{}, nil)
return
}

Expand All @@ -1276,6 +1289,7 @@ func (sp *serverPeer) OnGetHeaders(_ *peer.Peer, msg *wire.MsgGetHeaders) {
// Use the block after the genesis block if no other blocks in the
// provided locator are known. This does mean the client will start
// over with the genesis block if unknown block locators are provided.
locatorHashes := msg.BlockLocatorHashes
headers := chain.LocateHeaders(locatorHashes, &msg.HashStop)

// Send found headers to the requesting peer.
Expand Down Expand Up @@ -3406,6 +3420,15 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB,
quit: make(chan struct{}),
}

// Convert the minimum known work to a uint256 when it exists. Ideally, the
// chain params should be updated to use the new type, but that will be a
// major version bump, so a one-time conversion is a good tradeoff in the
// mean time.
minKnownWorkBig := chainParams.MinKnownChainWork
if minKnownWorkBig != nil {
s.minKnownWork.SetBig(minKnownWorkBig)
}

feC := fees.EstimatorConfig{
MinBucketFee: cfg.minRelayTxFee,
MaxBucketFee: dcrutil.Amount(fees.DefaultMaxBucketFeeMultiplier) * cfg.minRelayTxFee,
Expand Down

0 comments on commit 0e7920c

Please sign in to comment.