Skip to content

Commit

Permalink
Working haskey backend
Browse files Browse the repository at this point in the history
  • Loading branch information
duog committed Jan 17, 2022
1 parent 19dbd2a commit bfc9f7d
Show file tree
Hide file tree
Showing 8 changed files with 2,564 additions and 14 deletions.
Expand Up @@ -13,21 +13,30 @@ import Data.ByteString.Short.Base64 (encodeBase64)
import qualified Data.ByteString.Lazy.Char8 as Char8
import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString.Short as Short
import Data.Foldable(for_)
import Data.IntMap (IntMap)
import qualified Data.IntMap.Strict as IntMap
import qualified Data.IntMap.Strict as IntMap
import Data.Map (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Map.Merge.Strict as MM
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text.Short as TextShort
import Data.Word (Word32, Word64)
import qualified Data.Vector as V
import GHC.Clock (getMonotonicTimeNSec)
import qualified Options.Applicative as O
import qualified Control.Monad.State.Strict as Strict
import Control.Monad.IO.Class (liftIO)

import TxIn.Types
import TxIn.GenesisUTxO
import qualified TxIn.UTxODb as UTxODb
import qualified UTxODb.InMemory as UTxODb
import qualified UTxODb.Snapshots as UTxODb
import qualified UTxODb.Haskey.Db as UTxODb

main :: IO ()
main = do
Expand All @@ -48,6 +57,8 @@ chooseAnalysis = do
case whichAnalysis commandLine of
InMemSim -> pure inMemSim
MeasureAge -> pure measureAge
DbInMemSim -> pure utxodbInMemSim
DbHaskeySim -> pure $ utxodbHaskeySim (haskeyBackend commandLine)
where
opts = O.info (commandLineParser O.<**> O.helper) $
O.fullDesc
Expand All @@ -57,10 +68,14 @@ chooseAnalysis = do
data AnalysisName =
InMemSim
| MeasureAge
| DbInMemSim
| DbHaskeySim

deriving (Bounded, Enum, Show)

data CommandLine = CommandLine {
whichAnalysis :: AnalysisName
whichAnalysis :: !AnalysisName,
haskeyBackend :: !(Maybe (IO UTxODb.HaskeyBackend))
}

commandLineParser :: O.Parser CommandLine
Expand All @@ -71,6 +86,7 @@ commandLineParser =
( O.metavar "ANALYSIS_NAME"
<> O.help ("one of: " <> unwords (map fst analysisMap))
)
<*> O.optional UTxODb.haskeyBackendParser

readerAnalysisName :: O.ReadM AnalysisName
readerAnalysisName = O.eitherReader $ \s ->
Expand All @@ -85,6 +101,8 @@ analysisMap =
| analysisName <- [minBound..maxBound]
]



{- DEBUGGING tip
$ echo 'm1dAJQVEGuOSqJ6Vc7p0nnlovsfdBLuPb26/a7VjwlI=' | base64 --decode | hexdump -ve '1/1 "%.2x"'
Expand Down Expand Up @@ -188,14 +206,6 @@ contextT msg (T f) = T $ \i -> case f i of
Parsing a row from the timeline file
-------------------------------------------------------------------------------}

data Row = Row {
rBlockNumber :: {-# UNPACK #-} !Word64
, rSlotNumber :: {-# UNPACK #-} !Word64
, rNumTx :: {-# UNPACK #-} !Int
, rConsumed :: {-# UNPACK #-} !(V.Vector TxIn)
, rCreated :: {-# UNPACK #-} !(V.Vector TxOutputIds)
}

-- | Parse either a @\@@ item or a @#@ item, from the variable length portion
-- of each line
p0Item :: Char -> (ShortByteString -> Word32 -> ans) -> P0 Char8.ByteString ans
Expand Down Expand Up @@ -405,6 +415,35 @@ measureAge =

pure $ AgeMeasures utxo' histo'

{- UTxODb interface
-}

utxodbInMemSim :: [Row] -> IO ()
utxodbInMemSim rows = do
let init_seq_no = UTxODb.SeqNo (-1)
db <- UTxODb.initTVarDb init_seq_no

init_ls <- UTxODb.addTxIns db genesisUTxO $ UTxODb.initLedgerState init_seq_no
flip Strict.execStateT init_ls $ for_ (filterRowsForEBBs rows) $ \r -> do
ls0 <- Strict.get
ls <- liftIO (UTxODb.addRow db r (UTxODb.injectTables UTxODb.emptyTables ls0))
Strict.put ls
pure ()

utxodbHaskeySim :: Maybe (IO UTxODb.HaskeyBackend) -> [Row] -> IO ()
utxodbHaskeySim mb_hb rows = do
hb <- fromMaybe (error "No Haskey Backend") mb_hb

let init_seq_no = UTxODb.SeqNo (-1)
db <- UTxODb.openHaskeyDb init_seq_no hb
init_ls <- UTxODb.addTxIns db genesisUTxO $ UTxODb.initLedgerState init_seq_no
flip Strict.execStateT init_ls $ for_ (filterRowsForEBBs rows) $ \r -> do
ls0 <- Strict.get
ls <- liftIO (UTxODb.addRow db r (UTxODb.injectTables UTxODb.emptyTables ls0))
Strict.put ls
pure ()

{-------------------------------------------------------------------------------
TODO more simulations/statistics etc
-------------------------------------------------------------------------------}
Expand Down
@@ -1,18 +1,58 @@
{-# language DeriveGeneric #-}
module TxIn.Types (
TxIn (..)
, TxOutputIds (..)
, outputTxIns
, Row (..)
, filterRowsForEBBs
) where

import Data.ByteString.Short (ShortByteString)
import Data.Word (Word32)
import Data.Word (Word32, Word64)
import qualified Data.Vector as V
import GHC.Generics (Generic())
import Data.Binary (Binary())

import qualified Data.BTree.Primitives.Value as Haskey(Value(..))
import qualified Data.BTree.Primitives.Key as Haskey(Key(..))

data TxIn = TxIn !ShortByteString !Word32 -- index
deriving (Eq, Ord)
deriving (Eq, Ord, Show, Generic)

instance Binary TxIn

instance Haskey.Value TxIn where
fixedSize _ = Nothing -- TODO is it fixed size? we can be more efficient if it is

instance Haskey.Key TxIn



data TxOutputIds = TxOutputIds !ShortByteString !Word32 -- count
deriving (Eq, Ord)

outputTxIns :: TxOutputIds -> [TxIn]
outputTxIns (TxOutputIds h n) = [ TxIn h (i - 1) | i <- [1 .. n] ]

data Row = Row {
rBlockNumber :: {-# UNPACK #-} !Word64
, rSlotNumber :: {-# UNPACK #-} !Word64
, rNumTx :: {-# UNPACK #-} !Int
, rConsumed :: {-# UNPACK #-} !(V.Vector TxIn)
, rCreated :: {-# UNPACK #-} !(V.Vector TxOutputIds)
}

{-
for some slots in the Byron era there is an addtional psuedo block demarcates the
beginning of the era epoch, it's called an EBB.
It never contains any transactions
-}
filterRowsForEBBs :: [Row] -> [Row]
filterRowsForEBBs = go Nothing where
go _ [] = []
go Nothing (x: xs) = x: go (Just x) xs
go (Just x) (y: ys)
| rBlockNumber x == rBlockNumber y = if rNumTx y /= 0
then error "EBB block has transactions"
else go (Just x) ys
| otherwise = y : go (Just y) ys
@@ -0,0 +1,172 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# language DataKinds #-}
{-# language ScopedTypeVariables #-}
module TxIn.UTxODb where

import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Vector as V
import qualified Data.Text.Short as TextShort
import qualified Data.ByteString.Lazy.Char8 as Char8
import qualified Data.ByteString.Short as Short
import Control.Monad.Catch

import qualified Control.Monad.State.Strict as Strict
import Data.Kind
import Data.Coerce
import Data.ByteString.Short.Base64 (encodeBase64)
import Data.ByteString.Short.Base16 (encodeBase16')
import Data.Functor
import Control.Monad (foldM, unless)
import Data.Int

import TxIn.Types(Row(..), TxIn(..), outputTxIns)
import qualified UTxODb.Snapshots as Db
import qualified UTxODb.Haskey.Tree as HaskeyDb

-- writeDb :: dbhandle
-- -> [Either (TableDiffs state) (TableSnapshots state)]
-- -> SeqNo state -- ^ The old sequence number, as a sanity check
-- -> SeqNo state -- ^ The new sequence number, must be strictly greater
-- -> IO ()

-- data Row = Row {
-- rBlockNumber :: {-# UNPACK #-} !Word64
-- , rSlotNumber :: {-# UNPACK #-} !Word64
-- , rNumTx :: {-# UNPACK #-} !Int
-- , rConsumed :: {-# UNPACK #-} !(V.Vector TxIn)
-- , rCreated :: {-# UNPACK #-} !(V.Vector TxOutputIds)
-- }
data LedgerState table = LedgerState
{ utxo :: !(table Db.TableTypeRW TxIn Bool)
, seq_no :: !Int64
, seq_no_offset :: !Int64
}

instance Db.HasSeqNo LedgerState where
stateSeqNo LedgerState{seq_no, seq_no_offset} = coerce (seq_no + seq_no_offset)

initLedgerState :: Db.SeqNo LedgerState -> LedgerState Db.EmptyTable
initLedgerState sn = LedgerState { seq_no = coerce sn, seq_no_offset = 0, utxo = Db.EmptyTable }

instance Db.HasTables LedgerState where
type StateTableKeyConstraint LedgerState = All
type StateTableValueConstraint LedgerState= All
traverseTables f ls@LedgerState { utxo } =
(\x -> ls {utxo = x}) <$> f Db.TableTagRW utxo

class All k
instance All k

instance Db.HasTables (Db.Tables LedgerState) where
type StateTableKeyConstraint (Db.Tables LedgerState) = All
type StateTableValueConstraint (Db.Tables LedgerState) = All
traverseTables f OnDiskLedgerState {od_utxo} = OnDiskLedgerState <$> f Db.TableTagRW od_utxo

instance Db.HasOnlyTables (Db.Tables LedgerState) where
traverse0Tables f = OnDiskLedgerState <$> f Db.TableTagRW
traverse2Tables f x y = OnDiskLedgerState <$> f Db.TableTagRW (od_utxo x) (od_utxo y)

instance Db.HasOnDiskTables LedgerState where
newtype Tables LedgerState table = OnDiskLedgerState { od_utxo :: table Db.TableTypeRW TxIn Bool }
projectTables LedgerState {utxo} = OnDiskLedgerState {od_utxo = utxo}
injectTables OnDiskLedgerState{od_utxo} ls@LedgerState {} = ls { utxo = od_utxo }

instance HaskeyDb.HasHaskeyOnDiskTables LedgerState where
haskeyTraverseTables f ls@LedgerState {utxo} = f Db.TableTagRW utxo <&> \x -> ls { utxo = x }
haskeyTraverse0Tables f = OnDiskLedgerState <$> f Db.TableTagRW
haskeyTraverse1Tables f OnDiskLedgerState {od_utxo}= OnDiskLedgerState <$> f Db.TableTagRW od_utxo
haskeyTraverse2Tables f od1 od2 = OnDiskLedgerState <$> f Db.TableTagRW (od_utxo od1) (od_utxo od2)

newtype LedgerRulesException = LedgerRulesException String
deriving stock(Show)

instance Exception LedgerRulesException

data RowTxIns = RowTxIns
{ created :: Set TxIn
, consumed :: Set TxIn
}
deriving stock (Show)

keysForRow :: Row -> RowTxIns
keysForRow row = let
consumed0 = Set.fromList $ V.toList $ rConsumed row
created0 = Set.fromList $ concatMap outputTxIns $ V.toList $ rCreated row
created = Set.difference created0 consumed0
consumed = Set.difference consumed0 created0
in RowTxIns{..}

ledgerRules :: (MonadThrow m, Db.MappingW (table Db.TableTypeRW), Db.MappingR (table Db.TableTypeRW)) => Row -> LedgerState table -> m (LedgerState table)
ledgerRules r ls@LedgerState{utxo = utxo0, seq_no = old_seq_no} = do
let RowTxIns{created, consumed} = keysForRow r
sho1 (TxIn h i) = TextShort.toString (encodeBase64 h) <> "@" <> show i
sho2 (TxIn h i) = Char8.unpack (Char8.fromStrict (Short.fromShort (encodeBase16' h))) <> "@" <> show i

(utxo1, missing) <- let
go utxo txin = case Db.lookup txin utxo of
Nothing -> Strict.modify' (<> Set.singleton txin) $> utxo
Just _ -> pure $ Db.delete txin utxo -- don't even look at value
in flip Strict.runStateT mempty $ foldM go utxo0 consumed

let utxo2 = foldr (\k utxo -> Db.insert k True utxo) utxo1 created
unless (Set.null missing) $ let
message = unlines
[ unwords ["ERROR: missing TxIn", show (rBlockNumber r), show (Set.size consumed), show (Set.size missing)]
, unwords $ map sho1 $ Set.toList missing
, unwords $ map sho2 $ Set.toList missing
]
in throwM $ LedgerRulesException message
let
new_seq_no = fromIntegral $ rSlotNumber r
new_ls
| new_seq_no == old_seq_no = ls { utxo = utxo2, seq_no_offset = seq_no_offset ls + 1}
| otherwise = ls { utxo = utxo2, seq_no = new_seq_no}
-- unless (old_seq_no < new_seq_no) $ throwM $ LedgerRulesException $ unwords ["nonmonotonic slot no:", show old_seq_no, ">", show new_seq_no]
pure new_ls


addTxIns :: Db.DiskDb dbhandle LedgerState => dbhandle -> Set TxIn -> LedgerState Db.EmptyTable -> IO (LedgerState Db.EmptyTable)
addTxIns handle txins ls0 = do
putStrLn $ "addTxIns: " <> show (length txins)
let keyset = OnDiskLedgerState { od_utxo = Db.AnnTable (Db.TableKeySet txins) ()}
tracking_tables <-
Db.annotatedReadsetToTrackingTables <$> Db.readDb handle keyset

ls1 <- let
init_ls = Db.injectTables tracking_tables ls0
go !ls txin = case Db.lookup txin (utxo ls ) of
Nothing -> pure $ ls { utxo = Db.insert txin True (utxo ls) }
Just _ -> throwM $ LedgerRulesException $ "addTxIns: duplicate txin:" <> show txin
wrangle ls = ls { seq_no_offset = seq_no_offset ls + 1 }
in wrangle <$> foldM go init_ls txins
let table_diffs = Db.projectTables . Db.trackingTablesToTableDiffs $ ls1
Db.writeDb handle [Left table_diffs] (Db.stateSeqNo ls0) (Db.stateSeqNo ls1)
putStrLn $ "addTxIns: " <> show (length txins)
pure $ Db.injectTables Db.emptyTables ls1

-- addRow :: forall dbhandle. Db.DiskDb dbhandle LedgerState => dbhandle -> Row -> IO ()
addRow :: Db.DiskDb dbhandle LedgerState => dbhandle -> Row -> LedgerState Db.EmptyTable -> IO (LedgerState Db.EmptyTable)
addRow handle r ls0 = do
let RowTxIns {created, consumed} = keysForRow r
keyset :: Db.AnnTableKeySets LedgerState ()
keyset = OnDiskLedgerState { od_utxo = Db.AnnTable (Db.TableKeySet consumed) ()}

tracking_tables <-
Db.annotatedReadsetToTrackingTables <$> Db.readDb handle keyset
let ls = Db.injectTables tracking_tables ls0
ls1 <- ledgerRules r ls
let table_diffs = Db.projectTables . Db.trackingTablesToTableDiffs $ ls1
Db.writeDb handle [Left table_diffs] (Db.stateSeqNo ls0) (Db.stateSeqNo ls1)
putStrLn $ show (rBlockNumber r)
<> "\t" <> show (rSlotNumber r)
<> "\t" <> show (Set.size consumed)
<> "\t" <> show (Set.size created)

pure $ Db.injectTables Db.emptyTables ls1

0 comments on commit bfc9f7d

Please sign in to comment.