Skip to content

Commit 46772e1

Browse files
iohk-bors[bot]Heinrich Apfelmus
andauthored
Merge #3172
3172: Change `applyBlocks` to also accept `BlockSummary` r=HeinrichApfelmus a=HeinrichApfelmus ### Issue number ADP-1422, ADP-1428 ### Overview [Light-mode][] (Epic ADP-1422) aims to make synchronisation to the blockchain faster by trusting an off-chain source of aggregated blockchain data. [light-mode]: https://input-output-hk.github.io/cardano-wallet/design/specs/light-mode In this pull request, we change the `applyBlocks` function to work with both a list of `Block` and a `BlockSummary`. Specifically, we introduce a function `applyBlockData` which performs the following steps: * `discoverFromBlockData`: discover addresses and and transaction data (`ChainEvents`) contained in the sequence of blocks * `applyBlockEventsToUTxO`: update the wallet UTxO from the given `ChainEvents`. ### Details * Property tests for `discoverFromBlockData` check that address discovery gives the same result for both a `List` of blocks and a `Summary`. * When processing a `List` of `Block`, we have to be a bit careful about performance, because we need to traverse ~ 32M transactions (as of Feb 2022). In this case, the `transactions` contained in `BlockEvents` are essentially a full copy of the `transactions` in `Block`. The best way to copy data is to copy a single reference to the data; for this purpose we introduce a `Sublist` type which avoids deconstructing and reconstructing the transaction list in the case where we do not filter transactions (`All`). * In order to preserve the current checkpointing mechanism, `applyBlocks` actually calls `applyBlockData` repeatedly instead of passing the full list of blocks. I will need to rethink the checkpointing mechanism in the future — the main issue is that in its current form, a consumer of `BlockSummary` cannot ask for a specific block height in order to set a suitable checkpoint there. In the future, the checkpointing logic will need to mediate between producer and consumer. ### Comments * I expect a slight regression in benchmarks, due to overhead involved in calling `applyBlockData` repeatedly. However, the introduction of `Sublist` should prevent a serious deterioration. Co-authored-by: Heinrich Apfelmus <heinrich.apfelmus@iohk.io>
2 parents c09d7c4 + bfd7474 commit 46772e1

File tree

5 files changed

+339
-107
lines changed

5 files changed

+339
-107
lines changed

lib/core/src/Cardano/Wallet.hs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ import Cardano.Wallet.Primitive.Types
385385
, WalletMetadata (..)
386386
, WalletName (..)
387387
, WalletPassphraseInfo (..)
388+
, WithOrigin (..)
388389
, dlgCertPoolId
389390
, toSlot
390391
, wholeRange
@@ -1047,17 +1048,16 @@ restoreBlocks ctx tr wid blocks nodeTip = db & \DBLayer{..} -> mapExceptT atomic
10471048
, pretty (firstHeader blocks)
10481049
]
10491050

1050-
1051-
(filteredBlocks, cps') <- liftIO $ NE.unzip <$> applyBlocks @s blocks cp0
1051+
(filteredBlocks', cps') <- liftIO $ NE.unzip <$> applyBlocks @s blocks cp0
10521052
let cps = NE.map snd cps'
1053-
let List blocks' = blocks
1054-
slots = view #slotNo . view #header <$> blocks'
1055-
delegations = view #delegations <$> filteredBlocks
1056-
slotPoolDelegations =
1057-
[ (slotNo, cert)
1058-
| (slotNo, certs) <- NE.toList $ NE.zip slots delegations
1059-
, cert <- certs
1053+
filteredBlocks = concat filteredBlocks'
1054+
let slotPoolDelegations =
1055+
[ (pseudoSlotNo (fblock ^. #slot), cert)
1056+
| fblock <- filteredBlocks
1057+
, cert <- view #delegations fblock
10601058
]
1059+
pseudoSlotNo Origin = 0
1060+
pseudoSlotNo (At sl) = sl
10611061
let txs = fold $ view #transactions <$> filteredBlocks
10621062
let epochStability = (3*) <$> getSecurityParameter sp
10631063
let localTip = currentTip $ NE.last cps
@@ -1068,6 +1068,14 @@ restoreBlocks ctx tr wid blocks nodeTip = db & \DBLayer{..} -> mapExceptT atomic
10681068
liftIO $ logDelegation delegation
10691069
putDelegationCertificate wid cert slotNo
10701070

1071+
-- FIXME LATER during ADP-1403
1072+
-- We need to rethink checkpoint creation and consider the case
1073+
-- where the blocks are given as a 'Summary' and not a full 'List'
1074+
-- of blocks. In this case, it could happen that the current
1075+
-- scheme fails to create sufficiently many checkpoint as
1076+
-- it was never able to touch the corresponding block.
1077+
-- For now, we avoid this situation by being always supplied a 'List'
1078+
-- in the unstable region close to the tip.
10711079
let unstable = Set.fromList $ sparseCheckpoints cfg (nodeTip ^. #blockHeight)
10721080
where
10731081
-- NOTE

lib/core/src/Cardano/Wallet/Primitive/BlockSummary.hs

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,16 @@ module Cardano.Wallet.Primitive.BlockSummary
2323
, BlockEvents (..)
2424
, fromEntireBlock
2525

26-
-- * Testing
26+
-- * Sublist
27+
, Sublist
28+
, filterSublist
29+
, wholeList
30+
31+
-- * Internal & Testing
2732
, summarizeOnTxOut
2833
, mkChainEvents
34+
, mergeSublist
35+
, unsafeMkSublist
2936
) where
3037

3138
import Prelude
@@ -45,6 +52,8 @@ import Cardano.Wallet.Primitive.Types.Address
4552
( Address )
4653
import Cardano.Wallet.Primitive.Types.Tx
4754
( Tx (..), TxOut (..) )
55+
import Data.Foldable
56+
( Foldable (toList) )
4857
import Data.Functor.Identity
4958
( Identity (..) )
5059
import Data.List.NonEmpty
@@ -123,17 +132,59 @@ toAscBlockEvents (ChainEvents bs) = Map.elems bs
123132
data BlockEvents = BlockEvents
124133
{ slot :: !Slot
125134
, blockHeight :: !(Quantity "block" Word32)
126-
, transactions :: [(Int, Tx)]
135+
, transactions :: Sublist Tx
127136
-- ^ (Index of the transaction within the block, transaction data)
128137
-- INVARIANT: The list is ordered by ascending index.
129-
, delegations :: [(Int, DelegationCertificate)]
138+
, delegations :: Sublist DelegationCertificate
130139
-- ^ (Index of the delegation within the block, delegation data)
131140
-- INVARIANT: The list is ordered by ascending index.
132141
} deriving (Eq, Ord, Generic, Show)
133142

143+
-- | A data type representing a sublist of a total list.
144+
-- Such a sublist typically arises by filtering and keeps
145+
-- track of the indices of the filtered list elements.
146+
--
147+
-- The main purpose of this data type is optimization:
148+
-- When processing whole 'Block', we want to avoid copying
149+
-- and redecorating the entire list of transactions in that 'Block';
150+
-- instead, we want to copy a pointer to this list.
151+
data Sublist a = All [a] | Some [(Int, a)]
152+
deriving (Eq, Ord, Show)
153+
154+
-- | Construct a 'Sublist' representing the whole list.
155+
wholeList :: [a] -> Sublist a
156+
wholeList = All
157+
158+
-- | Construct a 'Sublist' from a list of indexed items.
159+
unsafeMkSublist :: [(Int,a)] -> Sublist a
160+
unsafeMkSublist = Some
161+
162+
-- | Filter a 'Sublist' by a predicate.
163+
filterSublist :: (a -> Bool) -> Sublist a -> Sublist a
164+
filterSublist p (All xs) = filterSublist p $ Some $ zip [0..] xs
165+
filterSublist p (Some ixs) = Some [ ix | ix <- ixs, p (snd ix) ]
166+
167+
instance Functor Sublist where
168+
fmap f (All xs) = All (map f xs)
169+
fmap f (Some ixs) = Some [ (i, f x) | (i,x) <- ixs ]
170+
171+
instance Foldable Sublist where
172+
foldr f b = foldr f b . toList
173+
null = null . toList
174+
toList (All as) = as
175+
toList (Some ixs) = map snd ixs
176+
177+
-- | Returns 'True' if the 'BlockEvents' contains empty
178+
-- 'transactions' and 'delegations'.
134179
nullBlockEvents :: BlockEvents -> Bool
135-
nullBlockEvents BlockEvents{transactions=[],delegations=[]} = True
136-
nullBlockEvents _ = False
180+
nullBlockEvents BlockEvents{transactions,delegations}
181+
= null transactions && null delegations
182+
183+
-- | Merge two 'Sublist' assuming that they are sublists of the /same/ list.
184+
mergeSublist :: Sublist a -> Sublist a -> Sublist a
185+
mergeSublist (All xs) _ = All xs -- result cannot be larger
186+
mergeSublist _ (All ys) = All ys
187+
mergeSublist (Some xs) (Some ys) = Some $ mergeOn fst const xs ys
137188

138189
-- | Merge block events that belong to the same block.
139190
mergeSameBlock :: BlockEvents -> BlockEvents -> BlockEvents
@@ -143,8 +194,8 @@ mergeSameBlock
143194
= BlockEvents
144195
{ slot
145196
, blockHeight
146-
, transactions = mergeOn fst const txs1 txs2
147-
, delegations = mergeOn fst const dlg1 dlg2
197+
, transactions = mergeSublist txs1 txs2
198+
, delegations = mergeSublist dlg1 dlg2
148199
}
149200

150201
-- | Merge two lists in sorted order. Remove duplicate items.
@@ -176,8 +227,8 @@ fromEntireBlock :: Block -> BlockEvents
176227
fromEntireBlock Block{header,transactions,delegations} = BlockEvents
177228
{ slot = toSlot $ chainPointFromBlockHeader header
178229
, blockHeight = Block.blockHeight header
179-
, transactions = zip [0..] transactions
180-
, delegations = zip [0..] delegations
230+
, transactions = All transactions
231+
, delegations = All delegations
181232
}
182233

183234
{-------------------------------------------------------------------------------
@@ -201,15 +252,15 @@ filterBlock question block = case fromEntireBlock block of
201252
{ slot
202253
, blockHeight
203254
, transactions = case question of
204-
Left addr -> filter (isRelevantTx addr) transactions
205-
Right _ -> []
255+
Left addr -> filterSublist (isRelevantTx addr) transactions
256+
Right _ -> Some []
206257
, delegations = case question of
207-
Left _ -> []
208-
Right racc -> filter (isRelevantDelegation racc) delegations
258+
Left _ -> Some []
259+
Right racc -> filterSublist (isRelevantDelegation racc) delegations
209260
}
210261
where
211262
-- NOTE: Currently used the full address,
212263
-- containing both payment and staking parts.
213264
-- We may want to query only for the payment part at some point.
214-
isRelevantTx addr = any ((addr ==) . address) . outputs . snd
215-
isRelevantDelegation racc = (racc == ) . dlgCertAccount . snd
265+
isRelevantTx addr = any ((addr ==) . address) . outputs
266+
isRelevantDelegation racc = (racc == ) . dlgCertAccount

0 commit comments

Comments
 (0)