haskoin-node-2.2.0: test/Haskoin/NodeSpec.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoFieldSelectors #-}
module Haskoin.NodeSpec (spec) where
import Conduit
import Control.Monad (forM_, forever, replicateM)
import Control.Monad.Cont
import Control.Monad.Trans (lift)
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
import Data.ByteString.Base64 (decodeBase64Lenient)
import Data.Default (def)
import Data.Either (fromRight)
import Data.List (find)
import Data.Maybe (isJust, listToMaybe, mapMaybe)
import Data.Serialize (decode, get, runGet, runPut)
import Data.Time.Clock (NominalDiffTime)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Database.RocksDB qualified as R
import Haskoin
import Haskoin.Node
import NQE
import Network.Socket (AddrInfo (addrAddress), SockAddr (..), tupleToHostAddress, tupleToHostAddress6)
import System.Random (randomIO)
import Test.Hspec
import Test.Hspec.QuickCheck
import UnliftIO
import UnliftIO.Concurrent (threadDelay)
data TestNode = TestNode
{ testMgr :: PeerMgr,
testChain :: Chain,
nodeEvents :: Inbox NodeEvent,
testDB :: R.DB,
-- | how many getheaders messages the mock peer has received
getHeadersCount :: TVar Int
}
-- | What the mock peer sends back for each message: a message to serialize
-- for the test network, or raw bytes.
type React = Message -> [Either ByteString Message]
-- | How to set up a test node.
data TestOpts = TestOpts
{ react :: React,
-- | runs on the database before the node starts
setup :: R.DB -> IO (),
timeout :: NominalDiffTime
}
defOpts :: [Block] -> TestOpts
defOpts blocks =
TestOpts
{ react = map Right . mockPeerReact blocks,
setup = \_ -> return (),
timeout = 120
}
-- | Connect to a mock peer that reacts to messages like this.
dummyPeerConnect ::
React ->
TVar Int ->
Network ->
NetworkAddress ->
SockAddr ->
WithConnection
dummyPeerConnect react count net ad sa f = do
r <- newInbox
s <- newInbox
let s' = inboxToMailbox s
withAsync (go r s') $ \_ -> do
let o = awaitForever (`send` r)
i = forever (receive s >>= yield)
f (Conduits i o) :: IO ()
where
go :: Inbox ByteString -> Mailbox ByteString -> IO ()
go r s = do
nonce <- randomIO
now <- round <$> getPOSIXTime
let rmt = NetworkAddress 0 (sockToHostAddress sa)
ver = buildVersion net nonce 0 ad rmt now
runPut (putMessage net (MVersion ver)) `send` s
runConduit $
forever (receive r >>= yield)
.| inc
.| iterMC count_getheaders
.| concatMapC react
.| outc
.| awaitForever (`send` s)
outc = mapMC $ return . either id (runPut . putMessage net)
count_getheaders = \case
MGetHeaders _ -> atomically (modifyTVar' count (+ 1))
_ -> return ()
inc =
forever $ do
x <- takeCE 24 .| foldC
y <- case decode x of
Left _ -> error "Dummy peer not decode message header"
Right (MessageHeader _ _ len _) ->
takeCE (fromIntegral len) .| foldC
case runGet (getMessage net) $ x `B.append` y of
Left e ->
error $
"Dummy peer could not decode payload: " <> show e
Right msg -> yield msg
mockPeerReact :: [Block] -> Message -> [Message]
mockPeerReact _ (MPing (Ping n)) = [MPong (Pong n)]
mockPeerReact _ (MVersion _) = [MVerAck]
mockPeerReact blocks (MGetHeaders (GetHeaders _ _hs _)) = [MHeaders (Headers hs')]
where
f b = (b.header, (VarInt . fromIntegral . length) b.txs)
hs' = map f blocks
mockPeerReact blocks (MGetData (GetData ivs)) = mapMaybe f ivs
where
f (InvVector InvBlock h) = MBlock <$> find (l h) blocks
f _ = Nothing
l h b = headerHash b.header == BlockHash h
mockPeerReact _ _ = []
spec :: Spec
spec = do
let net = bchRegTest
describe "reads address/port combinations" $ do
-- IPv4 only: resolving an IPv6 address needs IPv6 on the host, which
-- containers and CI runners often lack. The IPv6 forms are still parsed
-- by the toHostService tests below, which need no resolver.
prop "reads arbitrary IPv4 addresses" $ \(e, w) -> do
let p = toEnum (e `mod` 65536)
a = SockAddrInet p w
s <- toSockAddr net (show a)
s `shouldStartWith` [a]
it "reads some specific addresses" $ do
toHostService "localhost" `shouldBe` (Just "localhost", Nothing)
toHostService "::1" `shouldBe` (Just "::1", Nothing)
toHostService "localhost:8080" `shouldBe` (Just "localhost", Just "8080")
toHostService "example.com" `shouldBe` (Just "example.com", Nothing)
toHostService "api.example.com:443" `shouldBe` (Just "api.example.com", Just "443")
toHostService "api.example.com:http" `shouldBe` (Just "api.example.com", Just "http")
toHostService "[::1]" `shouldBe` (Just "::1", Nothing)
toHostService "[::1]:8080" `shouldBe` (Just "::1", Just "8080")
toHostService "[2002::dead:beef]:ssh" `shouldBe` (Just "2002::dead:beef", Just "ssh")
describe "peer manager on test network" $ do
it "connects to a peer" $
withTestNode net "connect-one-peer" $ \TestNode {..} -> do
p <- waitForPeer nodeEvents
Just OnlinePeer {version = Just Version {version = ver}} <-
getOnlinePeer testMgr p
ver `shouldSatisfy` (>= 70002)
it "downloads some blocks" $
withTestNode net "get-blocks" $ \TestNode {..} -> do
let h1 =
"3094ed3592a06f3d8e099eed2d9c1192329944f5df4a48acb29e08f12cfbb660"
h2 =
"0c89955fc5c9f98ecc71954f167b938138c90c6a094c4737f2e901669d26763f"
p <- waitForPeer nodeEvents
pbs <- getBlocks net 10 p [h1, h2]
pbs `shouldSatisfy` isJust
let Just [b1, b2] = pbs
headerHash b1.header `shouldBe` h1
headerHash b2.header `shouldBe` h2
let ths b = map txHash b.txs
testMerkle b = buildMerkleRoot (ths b) `shouldBe` Right b.header.merkle
testMerkle b1
testMerkle b2
describe "untrusted peers" $ do
it "imports a full batch of 2000 headers" $
withTestNodeOpts
(defOpts allBlocks) {react = reactAfterVerAck [Right (manyHeaders net 2000)]}
net
"full-batch"
$ \TestNode {..} -> do
bn <-
receiveMatch nodeEvents $ \case
ChainEvent (ChainBestBlock bn) | bn.height > 0 -> Just bn
_ -> Nothing
bn.height `shouldBe` 2000
it "disconnects a peer that sends more than 2000 headers" $
withTestNodeOpts
(defOpts allBlocks) {react = reactAfterVerAck [Right (manyHeaders net 2001)]}
net
"too-many-headers"
$ \TestNode {..} -> do
p <- waitForPeer nodeEvents
waitForDisconnect nodeEvents p
(.height) <$> chainGetBest testChain `shouldReturn` 0
it "disconnects a peer that sends more than 1000 addresses" $ do
let na = NetworkAddress 0 (sockToHostAddress (SockAddrInet 8333 0x08080808))
addrs = MAddr (Addr (replicate 1001 (0, na)))
withTestNodeOpts
(defOpts allBlocks) {react = reactAfterVerAck [Right addrs]}
net
"too-many-addrs"
$ \TestNode {..} -> do
p <- waitForPeer nodeEvents
waitForDisconnect nodeEvents p
it "disconnects a peer that sends the wrong network magic" $ do
let bad = runPut (putMessage btc (MPing (Ping 1)))
withTestNodeOpts
(defOpts allBlocks) {react = reactAfterVerAck [Left bad]}
net
"wrong-magic"
$ \TestNode {..} -> do
p <- waitForPeer nodeEvents
waitForDisconnect nodeEvents p
it "disconnects a peer that sends a second verack" $
withTestNodeOpts
(defOpts allBlocks) {react = reactAfterVerAck [Right MVerAck]}
net
"duplicate-verack"
$ \TestNode {..} -> do
p <- waitForPeer nodeEvents
waitForDisconnect nodeEvents p
it "ignores headers sent before the handshake" $ do
let hs = MHeaders (Headers [(b.header, VarInt 0) | b <- allBlocks])
react = \case
MVersion _ -> [Right hs, Right MVerAck]
MPing (Ping n) -> [Right (MPong (Pong n))]
_ -> []
withTestNodeOpts (defOpts allBlocks) {react = react} net "early-headers" $
\TestNode {..} -> do
_ <- waitForPeer nodeEvents
threadDelay (500 * 1000)
(.height) <$> chainGetBest testChain `shouldReturn` 0
it "disconnects a peer that chatters instead of answering a ping" $ do
-- Answer the node's ping with a ping of our own, and its pong with
-- another ping, so that the peer is never silent.
let react = \case
MVersion _ -> [Right MVerAck]
MPing _ -> [Right (MPing (Ping 1))]
MPong _ -> [Right (MPing (Ping 1))]
-- end the header sync so its own timeout does not apply
MGetHeaders _ -> [Right (MHeaders (Headers []))]
_ -> []
withTestNodeOpts
(defOpts allBlocks) {react = react, timeout = 2}
net
"ignored-ping"
$ \TestNode {..} -> do
p <- waitForPeer nodeEvents
waitForDisconnect nodeEvents p
it "does not connect to private addresses that peers relay" $ do
let v4 a b c d p = SockAddrInet p (tupleToHostAddress (a, b, c, d))
v6 t p = SockAddrInet6 p 0 (tupleToHostAddress6 t) 0
isRoutable (v4 8 8 8 8 8333) `shouldBe` True
isRoutable (v4 8 8 8 8 0) `shouldBe` False
isRoutable (v4 127 0 0 1 8333) `shouldBe` False
isRoutable (v4 10 1 2 3 8333) `shouldBe` False
isRoutable (v4 172 16 0 1 8333) `shouldBe` False
isRoutable (v4 172 32 0 1 8333) `shouldBe` True
isRoutable (v4 192 168 1 1 8333) `shouldBe` False
isRoutable (v4 169 254 1 1 8333) `shouldBe` False
isRoutable (v4 100 64 0 1 8333) `shouldBe` False
isRoutable (v4 224 0 0 1 8333) `shouldBe` False
isRoutable (v6 (0x2a01, 0x4f8, 0, 0, 0, 0, 0, 1) 8333) `shouldBe` True
isRoutable (v6 (0, 0, 0, 0, 0, 0, 0, 1) 8333) `shouldBe` False
isRoutable (v6 (0xfd00, 0, 0, 0, 0, 0, 0, 1) 8333) `shouldBe` False
isRoutable (v6 (0xfe80, 0, 0, 0, 0, 0, 0, 1) 8333) `shouldBe` False
isRoutable (v6 (0, 0, 0, 0, 0, 0xffff, 0x7f00, 1) 8333) `shouldBe` False
isRoutable (v6 (0, 0, 0, 0, 0, 0xffff, 0x0808, 0x0808) 8333) `shouldBe` True
isRoutable (hostToSockAddr (sockToHostAddress (v4 127 0 0 1 8333)))
`shouldBe` False
describe "chain database" $ do
it "leaves the default column family alone when purging" $ do
-- The same key in both families: the purge must delete it only from
-- the family that holds the chain.
let k = B.pack [0x90, 1, 2, 3]
setup db = do
R.put db k "keep"
forM_ (R.columnFamilies db) $ \cf -> R.putCF db cf k "stale"
withTestNodeOpts (defOpts allBlocks) {setup = setup} net "purge-cf" $
\TestNode {..} -> do
R.get testDB k `shouldReturn` Just "keep"
describe "chain on test network" $ do
it "stops asking a peer that has no more headers" $
-- The peer answers getheaders with an empty headers message, as a
-- peer does when the node already has its whole chain. The node must
-- take that as the end of the sync instead of asking again.
withTestNodeBlocks [] net "no-headers" $ \TestNode {..} -> do
_ <- waitForPeer nodeEvents
atomically $ readTVar getHeadersCount >>= checkSTM . (>= 1)
threadDelay (1000 * 1000)
readTVarIO getHeadersCount `shouldReturn` 1
it "syncs some headers" $
withTestNode net "connect-sync" $ \TestNode {..} -> do
let bh =
"3bfa0c6da615fc45aa44ddea6854ac19d16f3ca167e0e21ac2cc262a49c9b002"
ah =
"7dc835a78a55fa76f9184dc4f6663a73e418c7afec789c5ae25e432fd7fc8467"
bn <-
receiveMatch nodeEvents $ \case
ChainEvent (ChainBestBlock bn)
| bn.height > 0 -> Just bn
_ -> Nothing
bb <- chainGetBest testChain
bb.height `shouldSatisfy` (== 15)
an <-
maybe (error "No ancestor found") return
=<< chainGetAncestor testChain 10 bn
headerHash bn.header `shouldBe` bh
headerHash an.header `shouldBe` ah
it "downloads some block parents" $
withTestNode net "parents" $ \TestNode {..} -> do
let hs =
[ "52e886df7b166d961ac2d3d2d561d806325d51a609dc0a5d9d5fcb65d47906d7",
"2537a081b9e2b24d217fac2886f387758cb3aa4e4956b3be7ed229bafbb71b0f",
"7c72f306215a296f9714320a497b1f2cb5f9b99f162d7e04333c243fac9a54d8"
]
[_, bn] <-
replicateM 2 $
receiveMatch nodeEvents $ \case
ChainEvent (ChainBestBlock bn) -> Just bn
_ -> Nothing
bn.height `shouldBe` 15
ps <- chainGetParents testChain 12 bn
length ps `shouldBe` 3
forM_ (zip ps hs) $ \(p, h) ->
headerHash p.header `shouldBe` h
-- | Wait for the node to disconnect from this peer, failing after a while.
waitForDisconnect :: Inbox NodeEvent -> Peer -> IO ()
waitForDisconnect inbox p =
timeout (20 * 1000 * 1000) wait >>= \case
Just () -> return ()
Nothing -> expectationFailure "peer was not disconnected"
where
wait =
receiveMatch inbox $ \case
PeerEvent (PeerDisconnected p') | p == p' -> Just ()
_ -> Nothing
-- | Mock peer that answers the handshake and pings, and sends these right
-- after its version ack.
reactAfterVerAck :: [Either ByteString Message] -> React
reactAfterVerAck extra = \case
MVersion _ -> Right MVerAck : extra
MPing (Ping n) -> [Right (MPong (Pong n))]
_ -> []
-- | Headers message with a valid chain of this many headers on top of
-- genesis.
manyHeaders :: Network -> Int -> Message
manyHeaders net n =
MHeaders . Headers $
map (,VarInt 0) (appendBlocks net 0 net.genesisHeader n)
waitForPeer :: (MonadIO m) => Inbox NodeEvent -> m Peer
waitForPeer inbox =
receiveMatch inbox $ \case
PeerEvent (PeerConnected p) -> Just p
_ -> Nothing
withLifted :: (MonadUnliftIO m) => ((a -> IO b) -> IO b) -> (a -> m b) -> m b
withLifted w m = withRunInIO $ \io -> w (io . m)
withTestNode ::
(MonadUnliftIO m) =>
Network -> String -> (TestNode -> m a) -> m a
withTestNode = withTestNodeBlocks allBlocks
-- | Like 'withTestNode', with the mock peer serving these blocks.
withTestNodeBlocks ::
(MonadUnliftIO m) =>
[Block] -> Network -> String -> (TestNode -> m a) -> m a
withTestNodeBlocks blocks = withTestNodeOpts (defOpts blocks)
withTestNodeOpts ::
(MonadUnliftIO m) =>
TestOpts -> Network -> String -> (TestNode -> m a) -> m a
withTestNodeOpts opts net str f = flip runContT return $ do
w <- ContT $ withSystemTempDirectory ("haskoin-node-test-" <> str <> "-")
count <- newTVarIO 0
pub <- ContT withPublisher
sub <- ContT $ withSubscription pub
db <- ContT $ withLifted (R.withDBCF w cfg cols)
liftIO (opts.setup db)
let ad =
NetworkAddress
nodeNetwork
(sockToHostAddress (SockAddrInet 0 0))
na =
NetworkAddress
0
(sockToHostAddress (SockAddrInet 0 0))
cfg' =
NodeConfig
{ maxPeers = 20,
db = db,
cf = listToMaybe (R.columnFamilies db),
-- IPv4, so that the mock peer is reachable without IPv6
peers = ["127.0.0.1:17486"],
discover = False,
address = na,
net = net,
pub = pub,
timeout = opts.timeout,
maxPeerLife = 48 * 3600,
connect = dummyPeerConnect opts.react count net ad,
logger = Logger {level = LevelError, source = "", push = \_ -> return ()}
}
Node mgr ch <- ContT $ withNode cfg'
lift $
f
TestNode
{ testMgr = mgr,
testChain = ch,
nodeEvents = sub,
testDB = db,
getHeadersCount = count
}
where
cfg = def {R.createIfMissing = True, R.errorIfExists = True}
cols = [("node", def)]
allBlocks :: [Block]
allBlocks =
fromRight (error "Could not decode blocks") $
runGet f (decodeBase64Lenient allBlocksBase64)
where
f = mapM (const get) [(1 :: Int) .. 15]
allBlocksBase64 :: ByteString
allBlocksBase64 =
"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\
\wT6Wn6LtY47B39Th44JKsZ8BGJCvc6wAAAAA"