haskoin-store-0.31.0: src/Haskoin/Store/BlockStore.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Haskoin.Store.BlockStore
( -- * Block Store
BlockStore
, BlockStoreMessage
, BlockStoreConfig(..)
, blockStore
, blockStorePeerConnect
, blockStorePeerConnectSTM
, blockStorePeerDisconnect
, blockStorePeerDisconnectSTM
, blockStoreHead
, blockStoreHeadSTM
, blockStoreBlock
, blockStoreBlockSTM
, blockStoreNotFound
, blockStoreNotFoundSTM
, blockStoreTx
, blockStoreTxSTM
, blockStoreTxHash
, blockStoreTxHashSTM
) where
import Control.Applicative ((<|>))
import Control.Monad (forM, forM_, forever, mzero,
unless, void, when)
import Control.Monad.Except (ExceptT, MonadError (..),
runExceptT)
import Control.Monad.Logger (MonadLoggerIO, logDebugS,
logErrorS, logInfoS, logWarnS)
import Control.Monad.Reader (MonadReader, ReaderT (..), asks)
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (MaybeT (MaybeT), runMaybeT)
import qualified Data.ByteString as B
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
import Data.Maybe (catMaybes, fromJust, isJust,
listToMaybe, mapMaybe)
import Data.Serialize (encode)
import Data.String (fromString)
import Data.String.Conversions (cs)
import Data.Text (Text)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Time.Clock.System (getSystemTime, systemSeconds)
import Data.Time.Format (defaultTimeLocale, formatTime,
iso8601DateFormat)
import Haskoin (Block (..), BlockHash (..),
BlockHeader (..), BlockHeight,
BlockNode (..), GetData (..),
InvType (..), InvVector (..),
Message (..), Network (..),
OutPoint (..), Tx (..),
TxHash (..), TxIn (..),
blockHashToHex, headerHash,
txHash, txHashToHex)
import Haskoin.Node (Chain, OnlinePeer (..), Peer,
PeerException (..), PeerManager,
chainBlockMain,
chainGetAncestor, chainGetBest,
chainGetBlock, chainGetParents,
killPeer, managerGetPeer,
managerGetPeers,
managerPeerText, sendMessage)
import Haskoin.Store.Common (StoreEvent (..), StoreRead (..),
sortTxs)
import Haskoin.Store.Data (TxData (..), TxRef (..),
UnixTime, Unspent (..))
import Haskoin.Store.Database.Reader (DatabaseReader)
import Haskoin.Store.Database.Writer (Writer, runWriter)
import Haskoin.Store.Logic (ImportException (Orphan),
deleteTx, getOldMempool,
importBlock, initBest,
newMempoolTx, revertBlock)
import Network.Socket (SockAddr)
import NQE (Inbox, Listen, Mailbox,
inboxToMailbox, query, receive,
send, sendSTM)
import System.Random (randomRIO)
import UnliftIO (Exception, MonadIO,
MonadUnliftIO, STM, TVar,
atomically, liftIO, modifyTVar,
newTVarIO, readTVar, readTVarIO,
throwIO, withAsync, writeTVar)
import UnliftIO.Concurrent (threadDelay)
-- | Messages for block store actor.
data BlockStoreMessage
= BlockNewBest !BlockNode
| BlockPeerConnect !Peer !SockAddr
| BlockPeerDisconnect !Peer !SockAddr
| BlockReceived !Peer !Block
| BlockNotFound !Peer ![BlockHash]
| TxRefReceived !Peer !Tx
| TxRefAvailable !Peer ![TxHash]
| BlockPing !(Listen ())
-- ^ internal housekeeping ping
-- | Mailbox for block store.
type BlockStore = Mailbox BlockStoreMessage
data BlockException
= BlockNotInChain !BlockHash
| Uninitialized
| CorruptDatabase
| AncestorNotInChain !BlockHeight !BlockHash
| MempoolImportFailed
deriving (Show, Eq, Ord, Exception)
data Syncing = Syncing
{ syncingPeer :: !Peer
, syncingTime :: !UnixTime
, syncingHead :: !BlockNode
}
data PendingTx = PendingTx
{ pendingTxTime :: !UnixTime
, pendingTx :: !Tx
, pendingDeps :: !(HashSet TxHash)
}
deriving (Show, Eq, Ord)
-- | Block store process state.
data BlockRead = BlockRead
{ mySelf :: !BlockStore
, myConfig :: !BlockStoreConfig
, myPeer :: !(TVar (Maybe Syncing))
, myTxs :: !(TVar (HashMap TxHash PendingTx))
}
-- | Configuration for a block store.
data BlockStoreConfig = BlockStoreConfig
{ blockConfManager :: !PeerManager
-- ^ peer manager from running node
, blockConfChain :: !Chain
-- ^ chain from a running node
, blockConfListener :: !(Listen StoreEvent)
-- ^ listener for store events
, blockConfDB :: !DatabaseReader
-- ^ RocksDB database handle
, blockConfNet :: !Network
-- ^ network constants
, blockConfWipeMempool :: !Bool
-- ^ wipe mempool at start
, blockConfPeerTimeout :: !Int
-- ^ disconnect syncing peer if inactive for this long
}
type BlockT m = ReaderT BlockRead m
type WriterT m = ReaderT Writer (ExceptT ImportException (BlockT m))
runImport ::
MonadLoggerIO m
=> WriterT m a
-> BlockT m (Either ImportException a)
runImport f = do
db <- asks (blockConfDB . myConfig)
runExceptT (runWriter db f)
runRocksDB :: ReaderT DatabaseReader m a -> BlockT m a
runRocksDB f =
ReaderT $ \BlockRead {myConfig = BlockStoreConfig {blockConfDB = db}} ->
runReaderT f db
instance MonadIO m => StoreRead (BlockT m) where
getMaxGap =
runRocksDB getMaxGap
getInitialGap =
runRocksDB getInitialGap
getNetwork =
runRocksDB getNetwork
getBestBlock =
runRocksDB getBestBlock
getBlocksAtHeight =
runRocksDB . getBlocksAtHeight
getBlock =
runRocksDB . getBlock
getTxData =
runRocksDB . getTxData
getSpender =
runRocksDB . getSpender
getSpenders =
runRocksDB . getSpenders
getUnspent =
runRocksDB . getUnspent
getBalance =
runRocksDB . getBalance
getMempool =
runRocksDB getMempool
getAddressesTxs as =
runRocksDB . getAddressesTxs as
getAddressesUnspents as =
runRocksDB . getAddressesUnspents as
getAddressUnspents a =
runRocksDB . getAddressUnspents a
getAddressTxs a =
runRocksDB . getAddressTxs a
-- | Run block store process.
blockStore ::
(MonadUnliftIO m, MonadLoggerIO m)
=> BlockStoreConfig
-> Inbox BlockStoreMessage
-> m ()
blockStore cfg inbox = do
pb <- newTVarIO Nothing
ts <- newTVarIO HashMap.empty
runReaderT
(ini >> wipe >> run)
BlockRead { mySelf = inboxToMailbox inbox
, myConfig = cfg
, myPeer = pb
, myTxs = ts
}
where
del x n txs =
forM (zip [x ..] txs) $ \(i, tx) -> do
$(logInfoS) "BlockStore" $
"Deleting "
<> cs (show i) <> "/" <> cs (show n) <> ": "
<> txHashToHex (txRefHash tx) <> "…"
deleteTx True False (txRefHash tx)
wipeit x n txs = do
let (txs1, txs2) = splitAt 1000 txs
unless (null txs1) $ runImport (del x n txs1) >>= \case
Left e -> do
$(logErrorS) "BlockStore" $
"Could not delete mempool, database corrupt: "
<> cs (show e)
throwIO CorruptDatabase
Right _ -> wipeit (x + length txs1) n txs2
wipe
| blockConfWipeMempool cfg =
getMempool >>= \mem -> wipeit 1 (length mem) mem
| otherwise = return ()
ini =
runImport initBest >>= \case
Left e -> do
$(logErrorS) "BlockStore" $
"Could not initialize block store: "
<> fromString (show e)
throwIO e
Right () -> return ()
run = withAsync (pingMe (inboxToMailbox inbox)) . const . forever $
receive inbox >>= ReaderT . runReaderT . processBlockStoreMessage
isInSync :: (MonadLoggerIO m, StoreRead m, MonadReader BlockRead m) => m Bool
isInSync = getBestBlock >>= \case
Nothing -> do
$(logErrorS) "BlockStore" "Block database uninitialized"
throwIO Uninitialized
Just bb -> do
cb <- asks (blockConfChain . myConfig) >>= chainGetBest
return (headerHash (nodeHeader cb) == bb)
mempool :: MonadLoggerIO m => Peer -> m ()
mempool p = do
$(logDebugS) "BlockStore" "Requesting mempool from network peer"
MMempool `sendMessage` p
processBlock ::
(MonadUnliftIO m, MonadLoggerIO m)
=> Peer
-> Block
-> BlockT m ()
processBlock peer block = void . runMaybeT $ do
checks <- checkPeer peer
unless checks mzero
blocknode <- MaybeT $ getBlockNode peer blockhash
pt <- managerPeerText peer =<< asks (blockConfManager . myConfig)
$(logDebugS) "BlockStore" $
"Processing block : " <> blockText blocknode Nothing
<> " (peer" <> pt <> ")"
imported <- lift . runImport $ importBlock block blocknode
either (failure pt) (success blocknode) imported
where
header = blockHeader block
blockhash = headerHash header
hexhash = blockHashToHex blockhash
success blocknode deletedtxids = do
$(logInfoS) "BlockStore" $
"Best block: " <> blockText blocknode (Just block)
notify deletedtxids
failure pt e = do
$(logErrorS) "BlockStore" $
"Error importing block: "
<> hexhash <> ": " <> cs (show e)
<> " (peer " <> pt <> ")"
killPeer (PeerMisbehaving (show e)) peer
notify deletedtxids = do
listener <- asks (blockConfListener . myConfig)
atomically $ do
mapM_ (listener . StoreTxDeleted) deletedtxids
listener (StoreBestBlock blockhash)
lift (touchPeer peer >> syncMe peer)
checkPeer :: (MonadLoggerIO m, MonadReader BlockRead m) => Peer -> m Bool
checkPeer peer =
asks (blockConfManager . myConfig)
>>= managerGetPeer peer
>>= maybe disconnected (const connected)
where
disconnected = do
$(logDebugS) "BlockStore" "Ignoring data from disconnected peer"
return False
connected = touchPeer peer >>= \case
True -> return True
False -> remove
remove = do
pt <- managerPeerText peer =<< asks (blockConfManager . myConfig)
$(logDebugS) "BlockStore" $
"Ignoring data from non-syncing peer: " <> pt
killPeer (PeerMisbehaving "Sent unpexpected data") peer
return False
getBlockNode :: (MonadUnliftIO m, MonadLoggerIO m, MonadReader BlockRead m)
=> Peer -> BlockHash -> m (Maybe BlockNode)
getBlockNode peer blockhash = runMaybeT $
asks (blockConfChain . myConfig)
>>= chainGetBlock blockhash
>>= \case
Nothing -> do
p' <- managerPeerText peer =<< asks (blockConfManager . myConfig)
$(logErrorS) "BlockStore" $
"Header not found for block: "
<> blockHashToHex blockhash
<> " (peer " <> p' <> ")"
killPeer (PeerMisbehaving "Sent unknown block") peer
mzero
Just n -> return n
processNoBlocks ::
(MonadUnliftIO m, MonadLoggerIO m)
=> Peer
-> [BlockHash]
-> BlockT m ()
processNoBlocks p hs = do
p' <- managerPeerText p =<< asks (blockConfManager . myConfig)
forM_ (zip [(1 :: Int) ..] hs) $ \(i, h) ->
$(logErrorS) "BlockStore" $
"Block " <> cs (show i) <> "/" <> cs (show (length hs)) <> " "
<> blockHashToHex h <> " not found (peer " <> p' <> ")"
killPeer (PeerMisbehaving "Did not find requested block(s)") p
processTx :: (MonadUnliftIO m, MonadLoggerIO m) => Peer -> Tx -> BlockT m ()
processTx p tx = do
t <- fromIntegral . systemSeconds <$> liftIO getSystemTime
p' <- managerPeerText p =<< asks (blockConfManager . myConfig)
$(logDebugS) "BlockManager" $
"Received tx " <> txHashToHex (txHash tx) <> " (peer " <> p' <> ")"
addPendingTx $ PendingTx t tx HashSet.empty
pruneOrphans :: MonadIO m => BlockT m ()
pruneOrphans = do
ts <- asks myTxs
now <- fromIntegral . systemSeconds <$> liftIO getSystemTime
atomically . modifyTVar ts . HashMap.filter $ \p ->
pendingTxTime p > now - 600
addPendingTx :: MonadIO m => PendingTx -> BlockT m ()
addPendingTx p = do
ts <- asks myTxs
atomically $ modifyTVar ts $ HashMap.insert th p
where
th = txHash (pendingTx p)
isPending :: MonadIO m => TxHash -> BlockT m Bool
isPending th = do
ts <- asks myTxs
atomically $ HashMap.member th <$> readTVar ts
pendingTxs :: MonadIO m => Int -> BlockT m [PendingTx]
pendingTxs i = asks myTxs >>= \box -> atomically $ do
pending <- readTVar box
let (selected, rest) = select pending
writeTVar box rest
return selected
where
select pend =
let eligible = HashMap.filter (null . pendingDeps) pend
orphans = HashMap.difference pend eligible
selected = take i $ sortit eligible
remaining = HashMap.filter (`notElem` selected) eligible
in (selected, remaining <> orphans)
sortit m =
let sorted = sortTxs $ map pendingTx $ HashMap.elems m
txids = map (txHash . snd) sorted
in mapMaybe (`HashMap.lookup` m) txids
fulfillOrphans :: ( MonadIO m
, MonadReader BlockRead m
)
=> TxHash
-> m ()
fulfillOrphans th = asks myTxs >>= \box ->
atomically $ modifyTVar box (HashMap.map fulfill)
where
fulfill p = p {pendingDeps = HashSet.delete th (pendingDeps p)}
updateOrphans
:: ( StoreRead m
, MonadLoggerIO m
, MonadReader BlockRead m
)
=> m ()
updateOrphans = do
box <- asks myTxs
pending <- readTVarIO box
let orphans = HashMap.filter (not . null . pendingDeps) pending
updated <- forM orphans $ \p -> do
let tx = pendingTx p
exists (txHash tx) >>= \case
True -> return Nothing
False -> Just <$> fill_deps p
let pruned = HashMap.map fromJust $ HashMap.filter isJust updated
atomically $ writeTVar box pruned
where
exists th = getTxData th >>= \case
Nothing -> return False
Just TxData {txDataDeleted = True} -> return False
Just TxData {txDataDeleted = False} -> return True
prev_utxos tx = catMaybes <$> mapM (getUnspent . prevOutput) (txIn tx)
fulfill p unspent =
let unspent_hash = outPointHash (unspentPoint unspent)
new_deps = HashSet.delete unspent_hash (pendingDeps p)
in p {pendingDeps = new_deps}
fill_deps p = do
let tx = pendingTx p
unspents <- prev_utxos tx
return $ foldl fulfill p unspents
newOrphanTx :: (MonadUnliftIO m, MonadLoggerIO m)
=> UnixTime -> Tx -> WriterT m ()
newOrphanTx time tx = do
$(logDebugS) "BlockStore" $
"Mempool "
<> txHashToHex (txHash tx)
<> ": Orphan"
box <- lift $ asks myTxs
unspents <- catMaybes <$> mapM getUnspent prevs
let unspent_set = HashSet.fromList (map unspentPoint unspents)
missing_set = HashSet.difference prev_set unspent_set
missing_txs = HashSet.map outPointHash missing_set
atomically . modifyTVar box $
HashMap.insert
(txHash tx)
PendingTx { pendingTxTime = time
, pendingTx = tx
, pendingDeps = missing_txs
}
where
prev_set = HashSet.fromList prevs
prevs = map prevOutput (txIn tx)
importMempoolTx :: (MonadUnliftIO m, MonadLoggerIO m)
=> UnixTime -> Tx -> WriterT m (Maybe [TxHash])
importMempoolTx time tx =
catchError new_mempool_tx handle_error
where
tx_hash = txHash tx
handle_error Orphan = do
newOrphanTx time tx
return Nothing
handle_error _ = do
$(logDebugS) "BlockStore" $
"Mempool " <> txHashToHex tx_hash <> ": Failed"
return Nothing
new_mempool_tx =
newMempoolTx tx time >>= \case
Just deleted -> do
$(logDebugS) "BlockStore" $
"Mempool " <> txHashToHex (txHash tx) <> ": OK"
lift $ fulfillOrphans tx_hash
return (Just deleted)
Nothing -> do
$(logDebugS) "BlockStore" $
"Mempool " <> txHashToHex (txHash tx) <> ": No action"
return Nothing
processMempool :: (MonadUnliftIO m, MonadLoggerIO m) => BlockT m ()
processMempool = do
txs <- pendingTxs 2000
unless (null txs) $ import_txs txs >>= either failed success
where
run_import p = importMempoolTx (pendingTxTime p) (pendingTx p)
add_txid p deleted = (txHash (pendingTx p), deleted)
import_pending p = fmap (add_txid p) <$> run_import p
import_txs = runImport . mapM import_pending
failed e = do
$(logErrorS) "BlockStore" $
"Mempool import failed: " <> cs (show e)
throwIO MempoolImportFailed
success = mapM_ (uncurry notify) . catMaybes
notify txid deleted = do
l <- asks (blockConfListener . myConfig)
atomically $ do
mapM_ (l . StoreTxDeleted) deleted
l (StoreMempoolNew txid)
processTxs ::
(MonadUnliftIO m, MonadLoggerIO m)
=> Peer
-> [TxHash]
-> BlockT m ()
processTxs p hs = isInSync >>= \s -> when s $ do
pt <- managerPeerText p =<< asks (blockConfManager . myConfig)
$(logDebugS) "BlockStore" $
"Received inventory with "
<> cs (show (length hs))
<> " transactions from peer " <> pt
xs <- catMaybes <$> zip_counter (process_tx pt)
unless (null xs) $ go xs
where
len = length hs
zip_counter = forM (zip [(1 :: Int) ..] hs) . uncurry
have_it h = isPending h >>= \case
True -> return True
False -> getTxData h >>= \case
Nothing -> return False
Just txd -> return (not (txDataDeleted txd))
process_tx pt i h = have_it h >>= \case
True -> do_have pt i h
False -> dont_have pt i h
do_have pt i h = do
$(logDebugS) "BlockStore" $
"Tx inv " <> cs (show i) <> "/" <> cs (show len)
<> ": " <> txHashToHex h
<> ": Already have (peer " <> pt <> ")"
return Nothing
dont_have pt i h = do
$(logDebugS) "BlockStore" $
"Tx inv " <> cs (show i) <> "/" <> cs (show len)
<> ": " <> txHashToHex h
<> ": Requesting… (peer " <> pt <> ")"
return (Just h)
go xs = do
net <- asks (blockConfNet . myConfig)
let inv = if getSegWit net then InvWitnessTx else InvTx
vec = map (InvVector inv . getTxHash) xs
msg = MGetData (GetData vec)
msg `sendMessage` p
touchPeer :: (MonadIO m, MonadReader BlockRead m)
=> Peer -> m Bool
touchPeer p =
getSyncingState >>= \case
Just Syncing {syncingPeer = s}
| p == s -> do
box <- asks myPeer
now <- fromIntegral . systemSeconds <$>
liftIO getSystemTime
atomically . modifyTVar box . fmap $ \x ->
x {syncingTime = now}
return True
_ -> return False
checkTime :: (MonadUnliftIO m, MonadLoggerIO m) => BlockT m ()
checkTime =
asks myPeer >>= readTVarIO >>= \case
Nothing -> return ()
Just Syncing {syncingTime = t, syncingPeer = p} -> do
n <- fromIntegral . systemSeconds <$> liftIO getSystemTime
peertout <- asks (blockConfPeerTimeout . myConfig)
when (n > t + fromIntegral peertout) $ do
p' <- managerPeerText p =<< asks (blockConfManager . myConfig)
$(logErrorS) "BlockStore" $ "Timeout syncing peer " <> p'
resetPeer
killPeer PeerTimeout p
processDisconnect
:: (MonadUnliftIO m, MonadLoggerIO m)
=> Peer
-> BlockT m ()
processDisconnect p =
asks myPeer >>= readTVarIO >>= \case
Just Syncing {syncingPeer = p'} | p == p' -> dc
_ -> return ()
where
dc = do
$(logDebugS) "BlockStore" "Syncing peer disconnected"
resetPeer
getPeer >>= \case
Nothing -> $(logWarnS) "BlockStore" "No peers available"
Just peer -> do
ns <- managerPeerText peer =<<
asks (blockConfManager . myConfig)
$(logDebugS) "BlockStore" $ "New syncing peer " <> ns
syncMe peer
pruneMempool :: (MonadUnliftIO m, MonadLoggerIO m) => BlockT m ()
pruneMempool = isInSync >>= \sync -> when sync $ do
now <- fromIntegral . systemSeconds <$> liftIO getSystemTime
getOldMempool now >>= \old -> unless (null old) $ delete_txs old
where
delete_txs = mapM_ delete_it
failed txid e =
$(logErrorS) "BlockStore" $
"Could not delete old mempool tx: "
<> txHashToHex txid <> ": "
<> cs (show e)
success txids = do
$(logDebugS) "BlockStore" $
"Deleted " <> cs (show (length txids)) <> " txs"
l <- asks (blockConfListener . myConfig)
atomically $ mapM_ (l . StoreTxDeleted) txids
delete_it txid = do
$(logDebugS) "BlockStore" $
"Deleting "
<> ": " <> txHashToHex txid
<> " (old mempool tx)…"
runImport (deleteTx True False txid)
>>= either (failed txid) success
syncMe :: (MonadUnliftIO m, MonadLoggerIO m) => Peer -> BlockT m ()
syncMe peer = void . runMaybeT $ do
checksyncingpeer
reverttomainchain
syncbest <- syncbestnode
bestblock <- bestblocknode
chainbest <- chainbestnode
end syncbest bestblock chainbest
blocknodes <- selectblocks chainbest syncbest
setPeer peer (last blocknodes)
net <- asks (blockConfNet . myConfig)
let inv = if getSegWit net then InvWitnessBlock else InvBlock
vecf = InvVector inv . getBlockHash . headerHash . nodeHeader
vectors = map vecf blocknodes
pt <- managerPeerText peer =<< asks (blockConfManager . myConfig)
$(logDebugS) "BlockStore" $
"Requesting "
<> fromString (show (length vectors))
<> " blocks from peer: " <> pt
MGetData (GetData vectors) `sendMessage` peer
where
checksyncingpeer = getSyncingState >>= \case
Nothing -> return ()
Just Syncing {syncingPeer = p}
| p == peer -> return ()
| otherwise -> mzero
chainbestnode = chainGetBest =<< asks (blockConfChain . myConfig)
bestblocknode = do
bb <- lift getBestBlock >>= \case
Nothing -> do
$(logErrorS) "BlockStore" "No best block set"
throwIO Uninitialized
Just b -> return b
ch <- asks (blockConfChain . myConfig)
chainGetBlock bb ch >>= \case
Nothing -> do
$(logErrorS) "BlockStore" $
"Header not found for best block: " <> blockHashToHex bb
throwIO (BlockNotInChain bb)
Just x -> return x
syncbestnode =
asks myPeer >>= readTVarIO >>= \case
Just Syncing {syncingHead = b} -> return b
Nothing -> bestblocknode
end syncbest bestblock chainbest
| nodeHeader bestblock == nodeHeader chainbest = do
lift updateOrphans
resetPeer
mempool peer
mzero
| nodeHeader syncbest == nodeHeader chainbest =
mzero
| nodeHeight syncbest > nodeHeight bestblock + 500 =
mzero
| otherwise =
return ()
selectblocks chainbest syncbest = do
let sync_height = maxsyncheight
(nodeHeight chainbest)
(nodeHeight syncbest)
synctop <- top chainbest sync_height
ch <- asks (blockConfChain . myConfig)
parents <- chainGetParents (nodeHeight syncbest + 1) synctop ch
return $ if length parents < 500
then parents <> [chainbest]
else parents
maxsyncheight chainheight syncbestheight
| chainheight <= syncbestheight + 501 = chainheight
| otherwise = syncbestheight + 501
top chainbest syncheight =
if syncheight == nodeHeight chainbest
then return chainbest
else findancestor chainbest syncheight
findancestor chainbest syncheight = do
ch <- asks (blockConfChain . myConfig)
m <- chainGetAncestor syncheight chainbest ch
case m of
Just x -> return x
Nothing -> do
$(logErrorS) "BlockStore" $
"Could not find header for ancestor of block: "
<> blockHashToHex (headerHash (nodeHeader chainbest))
throwIO $ AncestorNotInChain
syncheight
(headerHash (nodeHeader chainbest))
reverttomainchain = do
bestblockhash <- headerHash . nodeHeader <$> bestblocknode
ch <- asks (blockConfChain . myConfig)
chainBlockMain bestblockhash ch >>= \y ->
unless y $ do_revert bestblockhash
fail_revert e = do
$(logErrorS) "BlockStore" $
"Could not revert best block: " <> cs (show e)
throwIO e
success_revert bestblockhash txids = do
listener <- asks (blockConfListener . myConfig)
atomically $ do
mapM_ (listener . StoreTxDeleted) txids
listener (StoreBlockReverted bestblockhash)
reverttomainchain
do_revert bestblockhash = do
$(logErrorS) "BlockStore" $
"Reverting best block: "
<> blockHashToHex bestblockhash
resetPeer
lift (runImport (revertBlock bestblockhash)) >>=
either fail_revert (success_revert bestblockhash)
resetPeer :: (MonadLoggerIO m, MonadReader BlockRead m) => m ()
resetPeer = do
box <- asks myPeer
atomically $ writeTVar box Nothing
setPeer :: (MonadIO m, MonadReader BlockRead m) => Peer -> BlockNode -> m ()
setPeer p b = do
box <- asks myPeer
now <- fromIntegral . systemSeconds <$> liftIO getSystemTime
atomically . writeTVar box $
Just Syncing {syncingPeer = p, syncingHead = b, syncingTime = now}
getPeer :: (MonadIO m, MonadReader BlockRead m) => m (Maybe Peer)
getPeer = runMaybeT $ MaybeT syncingpeer <|> MaybeT onlinepeer
where
syncingpeer = fmap syncingPeer <$> getSyncingState
onlinepeer =
listToMaybe . map onlinePeerMailbox <$>
(managerGetPeers =<< asks (blockConfManager . myConfig))
getSyncingState :: (MonadIO m, MonadReader BlockRead m) => m (Maybe Syncing)
getSyncingState = readTVarIO =<< asks myPeer
processBlockStoreMessage ::
(MonadUnliftIO m, MonadLoggerIO m)
=> BlockStoreMessage
-> BlockT m ()
processBlockStoreMessage (BlockNewBest _) =
getPeer >>= \case
Nothing -> return ()
Just p -> syncMe p
processBlockStoreMessage (BlockPeerConnect p _) = syncMe p
processBlockStoreMessage (BlockPeerDisconnect p _sa) = processDisconnect p
processBlockStoreMessage (BlockReceived p b) = processBlock p b
processBlockStoreMessage (BlockNotFound p bs) = processNoBlocks p bs
processBlockStoreMessage (TxRefReceived p tx) = processTx p tx
processBlockStoreMessage (TxRefAvailable p ts) = processTxs p ts
processBlockStoreMessage (BlockPing r) = do
processMempool
pruneOrphans
checkTime
pruneMempool
atomically (r ())
pingMe :: MonadLoggerIO m => BlockStore -> m ()
pingMe mbox =
forever $ do
threadDelay =<< liftIO (randomRIO (100 * 1000, 1000 * 1000))
BlockPing `query` mbox
blockStorePeerConnect :: MonadIO m => Peer -> SockAddr -> BlockStore -> m ()
blockStorePeerConnect peer addr store = BlockPeerConnect peer addr `send` store
blockStorePeerDisconnect :: MonadIO m => Peer -> SockAddr -> BlockStore -> m ()
blockStorePeerDisconnect peer addr store =
BlockPeerDisconnect peer addr `send` store
blockStoreHead :: MonadIO m => BlockNode -> BlockStore -> m ()
blockStoreHead node store = BlockNewBest node `send` store
blockStoreBlock :: MonadIO m => Peer -> Block -> BlockStore -> m ()
blockStoreBlock peer block store = BlockReceived peer block `send` store
blockStoreNotFound :: MonadIO m => Peer -> [BlockHash] -> BlockStore -> m ()
blockStoreNotFound peer blocks store = BlockNotFound peer blocks `send` store
blockStoreTx :: MonadIO m => Peer -> Tx -> BlockStore -> m ()
blockStoreTx peer tx store = TxRefReceived peer tx `send` store
blockStoreTxHash :: MonadIO m => Peer -> [TxHash] -> BlockStore -> m ()
blockStoreTxHash peer txhashes store =
TxRefAvailable peer txhashes `send` store
blockStorePeerConnectSTM :: Peer -> SockAddr -> BlockStore -> STM ()
blockStorePeerConnectSTM peer addr store = BlockPeerConnect peer addr `sendSTM` store
blockStorePeerDisconnectSTM :: Peer -> SockAddr -> BlockStore -> STM ()
blockStorePeerDisconnectSTM peer addr store =
BlockPeerDisconnect peer addr `sendSTM` store
blockStoreHeadSTM :: BlockNode -> BlockStore -> STM ()
blockStoreHeadSTM node store = BlockNewBest node `sendSTM` store
blockStoreBlockSTM :: Peer -> Block -> BlockStore -> STM ()
blockStoreBlockSTM peer block store = BlockReceived peer block `sendSTM` store
blockStoreNotFoundSTM :: Peer -> [BlockHash] -> BlockStore -> STM ()
blockStoreNotFoundSTM peer blocks store = BlockNotFound peer blocks `sendSTM` store
blockStoreTxSTM :: Peer -> Tx -> BlockStore -> STM ()
blockStoreTxSTM peer tx store = TxRefReceived peer tx `sendSTM` store
blockStoreTxHashSTM :: Peer -> [TxHash] -> BlockStore -> STM ()
blockStoreTxHashSTM peer txhashes store =
TxRefAvailable peer txhashes `sendSTM` store
blockText :: BlockNode -> Maybe Block -> Text
blockText bn mblock = case mblock of
Nothing ->
height <> sep <> time <> sep <> hash
Just block ->
height <> sep <> time <> sep <> hash <> sep <> size block
where
height = cs $ show (nodeHeight bn)
systime =
posixSecondsToUTCTime (fromIntegral (blockTimestamp (nodeHeader bn)))
time =
cs $
formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M")) systime
hash = blockHashToHex (headerHash (nodeHeader bn))
sep = " | "
size = (<> " bytes") . cs . show . B.length . encode