Skip to content

Commit eb61e30

Browse files
committed
Improve performance of the signing window check.
1 parent 49fde63 commit eb61e30

File tree

6 files changed

+126
-20
lines changed

6 files changed

+126
-20
lines changed

nix/.stack.nix/ouroboros-consensus.nix

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ouroboros-consensus/ouroboros-consensus.cabal

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,16 @@ test-suite test-storage
362362
ghc-options: -Wall
363363
-fno-ignore-asserts
364364

365+
test-suite doctests
366+
type: exitcode-stdio-1.0
367+
hs-source-dirs: test-doctest
368+
ghc-options: -threaded
369+
main-is: doctests.hs
370+
build-depends: base
371+
, doctest >= 0.8
372+
, ouroboros-consensus
373+
, QuickCheck
374+
365375
executable byron-db-converter
366376
hs-source-dirs: tools/db-convert
367377
build-depends: base

ouroboros-consensus/src/Ouroboros/Consensus/Node/ProtocolInfo/Byron.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Ouroboros.Consensus.Node.ProtocolInfo.Byron (
1919

2020
import Control.Exception (Exception)
2121
import Control.Monad.Except
22+
import qualified Data.Map.Strict as Map
2223
import qualified Data.Sequence as Seq
2324
import qualified Data.Set as Set
2425
import Data.Maybe
@@ -160,7 +161,7 @@ protocolInfoByron genesisConfig@Genesis.Config {
160161
blsCurrent = initState
161162
, blsSnapshots = Seq.empty
162163
}
163-
, ouroborosChainState = Seq.empty
164+
, ouroborosChainState = Map.empty
164165
}
165166
, pInfoInitState = ()
166167
}

ouroboros-consensus/src/Ouroboros/Consensus/Node/ProtocolInfo/Mock/PBFT.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module Ouroboros.Consensus.Node.ProtocolInfo.Mock.PBFT (
77

88
import Codec.Serialise (Serialise (..))
99
import qualified Data.Bimap as Bimap
10-
import qualified Data.Sequence as Seq
10+
import qualified Data.Map.Strict as Map
1111

1212
import Cardano.Crypto.DSIGN
1313

@@ -41,7 +41,7 @@ protocolInfoMockPBFT (NumCoreNodes numCoreNodes) (CoreNodeId nid) params =
4141
]
4242
}
4343
, pInfoInitLedger = ExtLedgerState (genesisSimpleLedgerState addrDist)
44-
Seq.empty
44+
Map.empty
4545
, pInfoInitState = ()
4646
}
4747
where

ouroboros-consensus/src/Ouroboros/Consensus/Protocol/PBFT.hs

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
{-# LANGUAGE DeriveGeneric #-}
33
{-# LANGUAGE FlexibleContexts #-}
44
{-# LANGUAGE FlexibleInstances #-}
5+
{-# LANGUAGE LambdaCase #-}
56
{-# LANGUAGE MultiParamTypeClasses #-}
67
{-# LANGUAGE NamedFieldPuns #-}
78
{-# LANGUAGE RecordWildCards #-}
89
{-# LANGUAGE ScopedTypeVariables #-}
910
{-# LANGUAGE StandaloneDeriving #-}
11+
{-# LANGUAGE TupleSections #-}
1012
{-# LANGUAGE TypeFamilyDependencies #-}
1113
{-# LANGUAGE TypeOperators #-}
1214
{-# LANGUAGE UndecidableInstances #-}
@@ -35,7 +37,11 @@ import Crypto.Random (MonadRandom)
3537
import Data.Bimap (Bimap)
3638
import qualified Data.Bimap as Bimap
3739
import Data.Constraint
40+
import Data.List (sortOn)
3841
import Data.Reflection (Given (..), give)
42+
import Data.Map.Strict (Map)
43+
import qualified Data.Map.Strict as Map
44+
import Data.Maybe (catMaybes, fromJust)
3945
import qualified Data.Set as Set
4046
import Data.Sequence (Seq)
4147
import qualified Data.Sequence as Seq
@@ -167,11 +173,10 @@ instance (PBftCrypto c, Typeable c) => OuroborosTag (PBft c) where
167173

168174
type IsLeader (PBft c) = PBftIsLeader c
169175

170-
-- | Chain state consists of two things:
171-
-- - a list of the last 'pbftSignatureWindow' signatures.
172-
-- - The last seen block slot
176+
-- | Chain state consists of a map from genesis keys to the list of blocks
177+
-- which they have issued.
173178
type ChainState (PBft c) =
174-
Seq (PBftVerKeyHash c, SlotNo)
179+
Map (PBftVerKeyHash c) (Seq SlotNo)
175180

176181
protocolSecurityParam = pbftSecurityParam . pbftParams
177182

@@ -201,9 +206,13 @@ instance (PBftCrypto c, Typeable c) => OuroborosTag (PBft c) where
201206
Right () -> return ()
202207
Left err -> throwError $ PBftInvalidSignature err
203208

204-
let (signers, lastSlot) = ( takeR winSize $ fst <$> chainState
205-
, maybe (SlotNo 0) snd $ Seq.lookup (Seq.length chainState) chainState
206-
)
209+
-- We always include slot number 0 in case there are no signers yet.
210+
let lastSlot = maximum . (SlotNo 0 :)
211+
$ (\case
212+
_ Seq.:|> l -> l
213+
_ -> SlotNo 0
214+
)
215+
<$> Map.elems chainState
207216

208217
-- FIXME confirm that non-strict inequality is ok in general.
209218
-- It's here because EBBs have the same slot as the first block of their
@@ -214,26 +223,101 @@ instance (PBftCrypto c, Typeable c) => OuroborosTag (PBft c) where
214223
case Bimap.lookupR (hashVerKey pbftIssuer) dms of
215224
Nothing -> throwError $ PBftNotGenesisDelegate (hashVerKey pbftIssuer) lv
216225
Just gk -> do
217-
let totalSigners = Seq.length signers
218-
gkSigners = Seq.length (Seq.filter (== gk) signers)
226+
let totalSigners = chainStateSize chainState
227+
gkSigners = maybe 0 Seq.length $ Map.lookup gk chainState
219228
when (totalSigners >= winSize && gkSigners > wt)
220229
$ throwError (PBftExceededSignThreshold totalSigners gkSigners)
221-
return $! takeR (winSize + 2*k) chainState Seq.|> (gk, blockSlot b)
230+
return $! insertSigner gk (blockSlot b) $ pruneChainState (winSize + 2*k) chainState
222231
where
223232
PBftParams{..} = pbftParams
224233
PBftFields{..} = headerPBftFields cfg b
225234
winSize = fromIntegral pbftSignatureWindow
226235
SecurityParam (fromIntegral -> k) = pbftSecurityParam
227236
wt = floor $ pbftSignatureThreshold * fromIntegral winSize
228-
-- Take the rightmost n elements of a sequence
229-
takeR :: Integral i => i -> Seq a -> Seq a
230-
takeR (fromIntegral -> n) s = Seq.drop (Seq.length s - n - 1) s
231237

232238
rewindChainState _ cs mSlot = case mSlot of
233-
Origin -> Just Seq.empty
234-
At slot -> case Seq.takeWhileL (\(_, s) -> s <= slot) cs of
235-
_ Seq.:<| _ -> Just cs
236-
_ -> Nothing
239+
Origin -> Just Map.empty
240+
At slot -> let oldCs = Map.map (Seq.takeWhileL (\s -> s <= slot)) cs in
241+
if (all Seq.null $ Map.elems oldCs) then Nothing else Just oldCs
242+
243+
-- | Prune the chain state to the given size by dropping the signers in the
244+
-- oldest slots.
245+
--
246+
-- Examples
247+
--
248+
-- $setup
249+
-- >>> :set -XOverloadedLists
250+
-- >>> :set -XScopedTypeVariables
251+
-- >>> import Test.QuickCheck
252+
-- >>> :{
253+
-- newtype TestChainState = TestChainState (Map Int (Seq Int))
254+
-- instance Arbitrary TestChainState where
255+
-- arbitrary = do
256+
-- numElts <- arbitrary
257+
-- ub <- arbitrary
258+
-- return . TestChainState $ Map.fromList
259+
-- [(idx, elts) | idx <- [0..numElts]
260+
-- , let elts = Seq.fromList [i | i <- [0..ub], i `mod` numElts == idx]
261+
-- ]
262+
-- :}
263+
264+
-- prop> \cs -> pruneChainState (chainStateSize cs) cs == cs
265+
--
266+
-- prop> \n (TestChainState cs) -> n > 0 && (chainStateSize cs) > n ==> (chainStateSize (pruneChainState n cs)) == n
267+
--
268+
-- >>> :{
269+
-- let (cs :: Map Char (Seq Int)) = [('a', [1,2,5]), ('b', [3, 4, 6])]
270+
-- in pruneChainState 2 cs
271+
-- :}
272+
-- fromList [('a',fromList [5]),('b',fromList [6])]
273+
--
274+
pruneChainState :: forall k v. (Ord k, Ord v) => Int -> Map k (Seq v) -> Map k (Seq v)
275+
pruneChainState toSize cs = go
276+
cs
277+
(sortOn snd . catMaybes $ strengthr . (fmap (Seq.lookup 0)) <$> (Map.toAscList cs))
278+
(max 0 $ chainStateSize cs - toSize)
279+
where
280+
go :: Map k (Seq v)
281+
-> [(k, v)]
282+
-> Int
283+
-> Map k (Seq v)
284+
go fromCS idx toDrop = if toDrop <= 0 then fromCS else case idx of
285+
[] -> fromCS
286+
(gk,_):xs@((_,nextLowest):_) ->
287+
let (newSeq, numDropped) = fromJust $ dropWhileL (< nextLowest) <$> Map.lookup gk fromCS
288+
newIdx = case newSeq of
289+
x Seq.:<| _ -> sortOn snd $ (gk, x) : xs
290+
_ -> xs
291+
in go (Map.insert gk newSeq fromCS) newIdx (toDrop - numDropped)
292+
-- Only one genesis key
293+
(gk,_):[] ->
294+
let newSeq = fromJust $ Seq.drop toDrop <$> Map.lookup gk fromCS
295+
in Map.insert gk newSeq fromCS
296+
297+
chainStateSize :: Map k (Seq v) -> Int
298+
chainStateSize cs = sum $ Seq.length <$> Map.elems cs
299+
300+
-- | Functorial strength on the right
301+
strengthr :: Functor f => (a, f b) -> f (a, b)
302+
strengthr (a, fb) = fmap (a,) fb
303+
304+
-- | Variant of 'dropWhileL' which also returns the number of elements dropped
305+
dropWhileL :: (a -> Bool) -> Seq a -> (Seq a, Int)
306+
dropWhileL f s = let res = Seq.dropWhileL f s in
307+
(res, Seq.length s - Seq.length res)
308+
309+
-- | Insert a signatory into the chain state.
310+
insertSigner
311+
:: PBftCrypto c
312+
=> PBftVerKeyHash c
313+
-> SlotNo
314+
-> ChainState (PBft c)
315+
-> ChainState (PBft c)
316+
insertSigner gk s =
317+
Map.alter (\case
318+
Just es -> Just $ es Seq.|> s
319+
Nothing -> Just $ Seq.singleton s
320+
) gk
237321

238322
{-------------------------------------------------------------------------------
239323
PBFT node order
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Test.DocTest
2+
3+
main = doctest ["-isrc", "src/Ouroboros/Consensus/Protocol/PBFT.hs"]

0 commit comments

Comments
 (0)