Skip to content

Commit 73742e9

Browse files
authored
Merge pull request IntersectMBO#1272 from input-output-hk/erikd/db-tool-report
db-tool: Update/improve/fix reward queries
2 parents 7884cdb + 3ec228c commit 73742e9

File tree

8 files changed

+183
-107
lines changed

8 files changed

+183
-107
lines changed

cardano-db-tool/app/cardano-db-tool.hs

+12-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ pReport =
219219
pReward :: Parser Report
220220
pReward =
221221
Opt.subparser $ mconcat
222-
[ Opt.command "latest"
222+
[ Opt.command "epoch"
223+
$ Opt.info (ReportEpochRewards <$> pEpochNo <*> pStakeAddress)
224+
(Opt.progDesc "Report the rewards fof the gievn epoch and stake address (or addresses)")
225+
, Opt.command "latest"
223226
$ Opt.info (ReportLatestRewards <$> pStakeAddress)
224227
(Opt.progDesc "Report the latest epoch rewards for a given stake address (or addresses)")
225228
, Opt.command "history"
@@ -234,3 +237,11 @@ pReport =
234237
( Opt.long "stake-address"
235238
<> Opt.help "Either a single stake address or a comma separated list."
236239
)
240+
241+
pEpochNo :: Parser Word64
242+
pEpochNo =
243+
Opt.option Opt.auto $ mconcat
244+
[ Opt.long "epoch"
245+
, Opt.metavar "WORD"
246+
, Opt.help "The epoch number."
247+
]

cardano-db-tool/cardano-db-tool.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ library
8282
, random
8383
, random-shuffle
8484
, text
85+
, text-icu
8586
, time
8687
, transformers
8788
, transformers-except

cardano-db-tool/src/Cardano/DbTool/Report.hs

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ module Cardano.DbTool.Report
55
) where
66

77
import Cardano.DbTool.Report.Balance (reportBalance)
8-
import Cardano.DbTool.Report.StakeReward (reportLatestStakeRewards,
9-
reportStakeRewardHistory)
8+
import Cardano.DbTool.Report.StakeReward (reportEpochStakeRewards,
9+
reportLatestStakeRewards, reportStakeRewardHistory)
1010
import Cardano.DbTool.Report.Synced as X
1111
import Cardano.DbTool.Report.Transactions (reportTransactions)
1212

1313
import Data.Text (Text)
14+
import Data.Word (Word64)
1415

1516

1617
data Report
1718
= ReportAllRewards [Text]
1819
| ReportBalance [Text]
20+
| ReportEpochRewards Word64 [Text]
1921
| ReportLatestRewards [Text]
2022
| ReportTransactions [Text]
2123

@@ -25,5 +27,6 @@ runReport report = do
2527
case report of
2628
ReportAllRewards sas -> mapM_ reportStakeRewardHistory sas
2729
ReportBalance sas -> reportBalance sas
30+
ReportEpochRewards ep sas -> reportEpochStakeRewards ep sas
2831
ReportLatestRewards sas -> reportLatestStakeRewards sas
2932
ReportTransactions sas -> reportTransactions sas
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
{-# LANGUAGE OverloadedStrings #-}
22
module Cardano.DbTool.Report.Display
33
( leftPad
4+
, rightPad
45
, separator
56
, spaces
67
) where
78

9+
import qualified Data.List as List
810
import Data.Text (Text)
911
import qualified Data.Text as Text
10-
12+
import qualified Data.Text.ICU as ICU
1113

1214
leftPad :: Int -> Text -> Text
13-
leftPad width txt = Text.take (width - Text.length txt) spaces <> txt
15+
leftPad width txt = Text.take (width - textDisplayLen txt) spaces <> txt
16+
17+
rightPad :: Int -> Text -> Text
18+
rightPad width txt = txt <> Text.take (width - textDisplayLen txt) spaces
1419

1520
separator :: Text
1621
separator = " | "
1722

1823
spaces :: Text
1924
spaces = " "
25+
26+
-- Calculates the screen character count a `Text` object will use when printed.
27+
textDisplayLen :: Text -> Int
28+
textDisplayLen = List.length . ICU.breaks (ICU.breakCharacter ICU.Root)

cardano-db-tool/src/Cardano/DbTool/Report/StakeReward/History.hs

+55-38
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ module Cardano.DbTool.Report.StakeReward.History
99
import Cardano.Db
1010
import Cardano.DbTool.Report.Display
1111

12+
import Control.Monad (join)
1213
import Control.Monad.IO.Class (MonadIO)
1314
import Control.Monad.Trans.Reader (ReaderT)
1415

1516
import qualified Data.List as List
16-
import Data.Maybe (fromMaybe, mapMaybe)
1717
import Data.Text (Text)
1818
import qualified Data.Text as Text
1919
import qualified Data.Text.IO as Text
2020
import Data.Time.Clock (UTCTime)
2121
import Data.Word (Word64)
2222

23-
import Database.Esqueleto.Experimental (SqlBackend, Value (..), asc, from, innerJoin,
24-
max_, on, orderBy, select, table, type (:&) ((:&)), val, where_, (<=.), (==.),
25-
(^.))
23+
import Database.Esqueleto.Experimental (SqlBackend, Value (..), asc, desc, from,
24+
innerJoin, max_, on, orderBy, select, table, type (:&) ((:&)), unSqlBackendKey,
25+
val, where_, (<=.), (==.), (^.))
2626

2727
import Text.Printf (printf)
2828

@@ -49,52 +49,63 @@ data EpochReward = EpochReward
4949
, erEpochNo :: !Word64
5050
, erDate :: !UTCTime
5151
, erAddress :: !Text
52+
, erPoolId :: !Word64
53+
, erPoolTicker :: !Text
5254
, erReward :: !Ada
5355
, erDelegated :: !Ada
5456
, erPercent :: !Double
5557
}
56-
5758
queryHistoryStakeRewards :: MonadIO m => Text -> ReaderT SqlBackend m [EpochReward]
5859
queryHistoryStakeRewards address = do
59-
maxEpoch <- queryMaxEpochRewardNo
60+
maxEpoch <- queryLatestMemberRewardEpochNo
6061
mapM queryReward =<< queryDelegation maxEpoch
6162
where
6263
queryDelegation
6364
:: MonadIO m
64-
=> Word64 -> ReaderT SqlBackend m [(StakeAddressId, Word64, UTCTime, DbLovelace)]
65+
=> Word64 -> ReaderT SqlBackend m [(StakeAddressId, Word64, UTCTime, DbLovelace, PoolHashId)]
6566
queryDelegation maxEpoch = do
6667
res <- select $ do
67-
(epoch :& es :& saddr) <-
68-
from $ table @Epoch
69-
`innerJoin` table @EpochStake
70-
`on` (\(epoch :& es) -> epoch ^. EpochNo ==. es ^. EpochStakeEpochNo)
71-
`innerJoin` table @StakeAddress
72-
`on` (\(_epoch :& es :& saddr) ->saddr ^. StakeAddressId ==. es ^. EpochStakeAddrId)
73-
where_ (saddr ^. StakeAddressView ==. val address)
74-
where_ (es ^. EpochStakeEpochNo <=. val maxEpoch)
75-
pure (es ^. EpochStakeAddrId, es ^. EpochStakeEpochNo, epoch ^.EpochEndTime, es ^. EpochStakeAmount)
76-
pure $ map unValue4 res
68+
(ep :& es :& saddr) <-
69+
from $ table @Epoch
70+
`innerJoin` table @EpochStake
71+
`on` (\(ep :& es) -> ep ^. EpochNo ==. es ^. EpochStakeEpochNo)
72+
`innerJoin` table @StakeAddress
73+
`on` (\(_ep :& es :& saddr) ->saddr ^. StakeAddressId ==. es ^. EpochStakeAddrId)
74+
where_ (saddr ^. StakeAddressView ==. val address)
75+
where_ (es ^. EpochStakeEpochNo <=. val maxEpoch)
76+
pure (es ^. EpochStakeAddrId, es ^. EpochStakeEpochNo, ep ^.EpochEndTime
77+
, es ^. EpochStakeAmount, es ^. EpochStakePoolId)
78+
pure $ map unValue5 res
7779

7880
queryReward
7981
:: MonadIO m
80-
=> (StakeAddressId, Word64, UTCTime, DbLovelace)
82+
=> (StakeAddressId, Word64, UTCTime, DbLovelace, PoolHashId)
8183
-> ReaderT SqlBackend m EpochReward
82-
queryReward (saId, en, date, DbLovelace delegated) = do
84+
queryReward (saId, en, date, DbLovelace delegated, poolId) = do
8385
res <- select $ do
84-
( saddr :& reward :& epoch) <-
85-
from $ table @StakeAddress
86-
`innerJoin` table @Reward
87-
`on` (\(saddr :& reward) -> saddr ^. StakeAddressId ==. reward ^. RewardAddrId)
88-
`innerJoin` table @Epoch
89-
`on` (\(_saddr :& reward :& epoch) -> epoch ^. EpochNo ==. reward ^. RewardEarnedEpoch)
90-
where_ (epoch ^. EpochNo ==. val en)
91-
where_ (saddr ^. StakeAddressId ==. val saId)
92-
orderBy [asc (epoch ^. EpochNo)]
93-
pure (reward ^. RewardAmount)
86+
(saddr :& rwd :& ep) <-
87+
from $ table @StakeAddress
88+
`innerJoin` table @Reward
89+
`on` (\(saddr :& rwd) -> saddr ^. StakeAddressId ==. rwd ^. RewardAddrId)
90+
`innerJoin` table @Epoch
91+
`on` (\(_saddr :& rwd :& ep) -> ep ^. EpochNo ==. rwd ^. RewardEarnedEpoch)
92+
where_ (ep ^. EpochNo ==. val en)
93+
where_ (saddr ^. StakeAddressId ==. val saId)
94+
orderBy [asc (ep ^. EpochNo)]
95+
pure (rwd ^. RewardAmount)
96+
97+
mtn <- select $ do
98+
pod <- from $ table @PoolOfflineData
99+
where_ (pod ^. PoolOfflineDataPoolId ==. val poolId)
100+
-- Use the `id` column as a proxy for time where larger `id` means later time.
101+
orderBy [desc (pod ^. PoolOfflineDataId)]
102+
pure (pod ^. PoolOfflineDataTickerName)
94103

95104
let reward = maybe 0 (unDbLovelace . unValue) (listToMaybe res)
96105
pure $ EpochReward
97106
{ erAddressId = saId
107+
, erPoolId = fromIntegral $ unSqlBackendKey (unPoolHashKey poolId)
108+
, erPoolTicker = maybe "???" unValue (listToMaybe mtn)
98109
, erEpochNo = en
99110
, erDate = date
100111
, erAddress = address
@@ -103,20 +114,22 @@ queryHistoryStakeRewards address = do
103114
, erPercent = rewardPercent reward (if delegated == 0 then Nothing else Just delegated)
104115
}
105116

106-
queryMaxEpochRewardNo
107-
:: MonadIO m
108-
=> ReaderT SqlBackend m Word64
109-
queryMaxEpochRewardNo = do
117+
-- Find the latest epoch where member rewards have been distributed.
118+
-- Can't use the Reward table for this because that table may have been partially
119+
-- populated for the next epcoh.
120+
queryLatestMemberRewardEpochNo :: MonadIO m => ReaderT SqlBackend m Word64
121+
queryLatestMemberRewardEpochNo = do
110122
res <- select $ do
111-
reward <- from $ table @Reward
112-
pure (max_ (reward ^. RewardEarnedEpoch))
113-
pure $ fromMaybe 0 (listToMaybe $ mapMaybe unValue res)
123+
blk <- from $ table @Block
124+
where_ (isJust $ blk ^. BlockEpochNo)
125+
pure $ max_ (blk ^. BlockEpochNo)
126+
pure $ maybe 0 (pred . pred) (join $ unValue =<< listToMaybe res)
114127

115128
renderRewards :: Text -> [EpochReward] -> IO ()
116129
renderRewards saddr xs = do
117130
Text.putStrLn $ mconcat [ "\nRewards for: ", saddr, "\n" ]
118-
putStrLn " epoch | reward_date | delegated | reward | RoS (%pa)"
119-
putStrLn "-------+-------------------------+----------------+--------------+-----------"
131+
putStrLn " epoch | reward_date | delegated | pool_id | ticker | reward | RoS (%pa)"
132+
putStrLn "-------+-------------------------+----------------+---------+--------+--------------+-----------"
120133
mapM_ renderReward xs
121134
putStrLn ""
122135
where
@@ -129,6 +142,10 @@ renderRewards saddr xs = do
129142
, separator
130143
, leftPad 14 (renderAda (erDelegated er))
131144
, separator
145+
, leftPad 7 (textShow $ erPoolId er)
146+
, separator
147+
, rightPad 6 (erPoolTicker er)
148+
, separator
132149
, leftPad 12 (specialRenderAda (erReward er))
133150
, separator
134151
, Text.pack (if erPercent er == 0.0 then " 0.0" else printf "%8.3f" (erPercent er))

0 commit comments

Comments
 (0)