haskoin-node 2.5.0 → 2.5.1
raw patch · 7 files changed
+77/−19 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Haskoin.Node.PeerMgr: instance GHC.Classes.Eq Haskoin.Node.PeerMgr.Connected
+ Haskoin.Node.PeerMgr: instance GHC.Internal.Show.Show Haskoin.Node.PeerMgr.Connected
Files
- CHANGELOG.md +16/−0
- README.md +11/−2
- haskoin-node.cabal +1/−1
- src/Haskoin/Node/Chain.hs +2/−1
- src/Haskoin/Node/Logger.hs +8/−4
- src/Haskoin/Node/Peer.hs +8/−2
- src/Haskoin/Node/PeerMgr.hs +31/−9
CHANGELOG.md view
@@ -6,6 +6,22 @@ ## [Unreleased] +## [2.5.1] - 2026-11-11++### Changed++- Log levels say who has the problem. An error is now something wrong with+ this node, and nothing a peer does is one:+ - a peer that closes its connection is logged for debugging, and one+ that sends something invalid as a warning; both were errors;+ - headers that do not connect are logged once, as the warning that+ disconnects the peer, and no longer as an error before it;+ - connecting to a peer, its connection and its end are logged for+ debugging, where they were information;+ - the number of connected peers is logged for debugging at every change,+ as a warning when the node loses its last peer, and as information+ when it has peers again.+ ## [2.5.0] - 2026-11-10 This release fixes the findings of a security review. Several fixes change
README.md view
@@ -169,7 +169,7 @@ ```yaml extra-deps:- - haskoin-node-2.5.0+ - haskoin-node-2.5.1 - haskoin-core-1.6.1 - secp256k1-haskell-2.0.1 - base16-2.0@@ -198,7 +198,7 @@ stack run ``` - Within a few seconds it logs connections and starts printing header+ Within a few seconds it has peers and starts printing header heights. A full header sync of Bitcoin usually takes minutes and on the order of a hundred megabytes of disk; it resumes where it stopped if you interrupt it.@@ -494,6 +494,15 @@ `withLogger level` logs to standard error, one line per message, such as `[Info#Chain] Best block header at height 870000`. Build a `Logger` with your own `push` function to log elsewhere.++| Level | What it is for |+| --- | --- |+| Debug | Everything: each peer that connects or leaves, each message |+| Info | Milestones: the best block, headers in sync, peers again after none |+| Warn | Something unusual that the node dealt with: a misbehaving peer, no peers left |+| Error | Something wrong with this node. Nothing that a peer does is an error |++An alert on errors is then an alert about the node, not about the network. ## Configuration
haskoin-node.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: haskoin-node-version: 2.5.0+version: 2.5.1 synopsis: P2P library for Bitcoin and Bitcoin Cash description: Connect to Bitcoin (BTC) and Bitcoin Cash (BCH) peers, keep them healthy, and sync block headers into a RocksDB database. Blocks and transactions can be
src/Haskoin/Node/Chain.hs view
@@ -470,7 +470,8 @@ chainEvent ch (ChainBestBlock after) case result of Left e -> do- logError ch.config.logger ("connectBlocks: " <> cs e)+ -- the caller disconnects the peer and says why, with a warning+ logDebug ch.config.logger ("connectBlocks: " <> cs e) return (Left e) Right [] -> return (Left "no headers") Right (lst : _) -> do
src/Haskoin/Node/Logger.hs view
@@ -32,13 +32,17 @@ -- | Severity of a log message, from least to most severe. data LogLevel- = -- | detailed tracing, useful when debugging+ = -- | everything, for debugging: each peer that connects or leaves, each+ -- message LevelDebug- | -- | normal operation, such as peers connecting and blocks arriving+ | -- | what an operator wants in a quiet log: the best block, headers in+ -- sync, peers again after having none LevelInfo- | -- | unexpected events that the node recovers from+ | -- | something unusual that the node dealt with: a peer that sent+ -- something invalid and was disconnected, no peers left LevelWarn- | -- | failures+ | -- | something wrong with this node, that someone should look at.+ -- Nothing that a peer does is an error. LevelError deriving (Eq, Ord, Show)
src/Haskoin/Node/Peer.hs view
@@ -279,7 +279,7 @@ yield m return True dispatchMessage PeerConfig {label, logger} KillPeer = do- liftIO $ logInfo logger (label <> " disconnecting")+ liftIO $ logDebug logger (label <> " disconnecting") return False -- | Internal conduit to parse messages coming from peer. The header is checked@@ -333,7 +333,13 @@ _ -> return () bad :: PeerException -> ConduitT i o IO a bad e = liftIO $ do- logError logger (label <> ": " <> cs (show e))+ -- a peer that hangs up is the ordinary end of a connection; one that+ -- sends what it should not is worth a look, and is not an error of+ -- this node either+ let level = case e of+ ConnectionClosed -> logDebug+ _ -> logWarn+ level logger (label <> ": " <> cs (show e)) throwIO e -- The reason is on the first line of the parser's error text. The lines -- after it are call stack noise.
src/Haskoin/Node/PeerMgr.hs view
@@ -171,9 +171,18 @@ -- | addresses to connect to addresses :: !(TVar (AddressBook SockAddr)), -- | open connections, fastest peers first- peers :: !(TVar [OnlinePeer])+ peers :: !(TVar [OnlinePeer]),+ -- | how well connected the log last said the node was ('Connected')+ told :: !(TVar Connected) } +-- | How well connected the node is, as its log says at the level of+-- information. Peers come and go all the time, and a line for each change+-- buries the ones that matter: that the node lost its last peer, and that+-- it has peers again.+data Connected = NoPeers | SomePeers | AllPeers+ deriving (Eq, Show)+ -- | Messages handled by the peer manager process. data PeerMgrMessage = -- | connect to a peer address@@ -275,6 +284,7 @@ best <- newTVarIO 0 addresses <- newTVarIO emptyBook peers <- newTVarIO []+ told <- newTVarIO NoPeers let mgr = PeerMgr { config = cfg,@@ -282,7 +292,8 @@ mailbox = inboxToMailbox inbox, best = best, addresses = addresses,- peers = peers+ peers = peers,+ told = told } withAsync (peerManager mgr inbox) $ \a -> withConnectLoop mgr (link a >> action mgr)@@ -346,7 +357,7 @@ ReplyVerAck -> MVerAck `sendMessage` p Announce -> do atomically $ modifyTVar' mgr.addresses (markConnected o.address)- logInfo mgr.config.logger ("Connected to peer " <> p.label)+ logDebug mgr.config.logger ("Connected to peer " <> p.label) publish (PeerConnected p) mgr.config.pub logConnectedPeers mgr SendPing n -> MPing (Ping n) `sendMessage` p@@ -405,7 +416,7 @@ case gone of Nothing -> logError mgr.config.logger "Unknown peer process stopped" Just (o, outcome) -> do- logInfo mgr.config.logger $+ logDebug mgr.config.logger $ "Connection to peer " <> o.mailbox.label <> " ended: "@@ -437,13 +448,24 @@ fromException x <|> (fromException x >>= \(ExceptionInLinkedThread _ inner) -> fromException inner) --- | Log how many peers are online out of the maximum.+-- | Log how many peers are online out of the maximum: for debugging at+-- every change, and at a level that an operator reads when the node loses+-- its last peer or has one again ('Connected'). logConnectedPeers :: PeerMgr -> IO () logConnectedPeers mgr = do l <- length <$> getPeers mgr- logInfo- mgr.config.logger- ("Peers connected: " <> cs (show l) <> "/" <> cs (show mgr.config.maxPeers))+ let now+ | l == 0 = NoPeers+ | l >= mgr.config.maxPeers = AllPeers+ | otherwise = SomePeers+ count = "Peers connected: " <> cs (show l) <> "/" <> cs (show mgr.config.maxPeers)+ before <- atomically (swapTVar mgr.told now)+ if+ | now == before -> logDebug mgr.config.logger count+ | now == NoPeers -> logWarn mgr.config.logger count+ | before == NoPeers -> logInfo mgr.config.logger count+ -- a node that rotates a peer is as well connected as it was+ | otherwise -> logDebug mgr.config.logger count -- | Next address to connect to. The configured peers and, if discovery is on, -- the seeds are loaded when there is nothing to try and they were not loaded@@ -499,7 +521,7 @@ | source /= Just Configured && netGroup sa `elem` usedGroups ops -> giveBack "network group in use" | otherwise -> do- logInfo mgr.config.logger ("Connecting to peer " <> cs (show sa))+ logDebug mgr.config.logger ("Connecting to peer " <> cs (show sa)) nonce <- randomIO height <- readTVarIO mgr.best now <- getCurrentTime