Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 4bad8c1

Browse files
iohk-bors[bot]erikdmhueschdisassembler
committed
Merge #4153
4153: CBR-525: Fix failure to sync issue when switching to OBFT r=disassembler a=erikd ## Description Fix issue that caused the node to loose sync with the chain in OBFT era and then fail to re-sync once out of sync. ## Linked issue https://iohk.myjetbrains.com/youtrack/issue/CBR-525 - [x] CHANGELOG entry has been added and is linked to the correct PR on GitHub. ## Testing checklist <!-- If you aren't providing any tests as part of this PR, use this section to state clearly why. It needs to be a strong motivation and definitely the exception, not the rule. --> - [x] I have added tests to cover my changes. - [x] All new and existing tests passed. ## QA Steps Synced the OBFT testnet numerous times. Co-authored-by: Erik de Castro Lopo <[email protected]> Co-authored-by: Michael Hueschen <[email protected]> Co-authored-by: Samuel Leathers <[email protected]>
2 parents a566b51 + be04da9 commit 4bad8c1

File tree

27 files changed

+716
-266
lines changed

27 files changed

+716
-266
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## Cardano SL 3.0.2
4+
5+
### Fixes
6+
7+
- Fix issue that caused the node to lose sync with the chain in the OBFT era and then fail to re-sync once out of sync ([CBR-525]https://iohk.myjetbrains.com/youtrack/issue/CBR-525) [#4153](https://github.com/input-output-hk/cardano-sl/pull/4153))
8+
39
## Cardano SL 3.0.1
410

511
### Fixes

chain/cardano-sl-chain.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ library
1616
hs-source-dirs: src
1717
exposed-modules:
1818
Pos.Chain.Block
19+
Pos.Chain.Block.Slog.LastBlkSlots
1920
Pos.Chain.Delegation
2021
Pos.Chain.Genesis
2122
Pos.Chain.Lrc
@@ -247,6 +248,7 @@ test-suite chain-test
247248
Test.Pos.Chain.Block.CborSpec
248249
Test.Pos.Chain.Block.Gen
249250
Test.Pos.Chain.Block.SafeCopySpec
251+
Test.Pos.Chain.Block.Slog.LastBlkSlots
250252
Test.Pos.Chain.Delegation.Arbitrary
251253
Test.Pos.Chain.Delegation.Bi
252254
Test.Pos.Chain.Delegation.Example

chain/src/Pos/Chain/Block/Header.hs

+18-2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ import Pos.Chain.Update.BlockVersion (BlockVersion,
100100
import Pos.Chain.Update.SoftwareVersion (HasSoftwareVersion (..),
101101
SoftwareVersion)
102102
import Pos.Core.Attributes (mkAttributes)
103-
import Pos.Core.Common (ChainDifficulty, HasDifficulty (..))
103+
import Pos.Core.Common (ChainDifficulty, HasDifficulty (..),
104+
addressHash)
104105
import Pos.Core.Slotting (EpochIndex (..), EpochOrSlot (..),
105106
HasEpochIndex (..), HasEpochOrSlot (..), SlotCount (..),
106107
SlotId (..), flattenSlotId, slotIdF)
@@ -185,7 +186,11 @@ headerLastSlotInfo slotCount = \case
185186
convert bh =
186187
LastSlotInfo
187188
(flattenSlotId slotCount . _mcdSlot $ _gbhConsensus bh)
188-
(_mcdLeaderKey $ _gbhConsensus bh)
189+
-- Since _mcdLeaderKey is confirmed (see comment in function
190+
-- `mkMainHeaderExplicit`) to be the delegator's key, we will be
191+
-- enforcing block-minting-count thresholds by the delegator's key,
192+
-- which is the desired behavior.
193+
(addressHash . _mcdLeaderKey $ _gbhConsensus bh)
189194

190195
--------------------------------------------------------------------------------
191196
-- HeaderHash
@@ -427,6 +432,17 @@ mkMainHeaderExplicit pm prevHash difficulty slotId sk pske body extra =
427432
(BlockSignature $ sign pm SignMainBlock sk toSign)
428433
(makeSignature toSign)
429434
pske
435+
-- If `pske :: ProxySKBlockInfo` is Just, indicating delegation, we use
436+
-- the PublicKey of the delegator (in Original era, this is the Stakeholder
437+
-- whose stake was selected by FTS. In OBFT era, this is the Genesis
438+
-- Stakeholder, selected via round-robin. In both cases, the delegator is
439+
-- delegating the right to mint a block to the delegatee).
440+
--
441+
-- If `pske :: ProxySKBlockInfo` is Nothing, we use `toPublic` of the `sk`
442+
-- provided, which should be provided by the node issuing the block who was
443+
-- thus selected themselves. See `db/src/Pos/DB/Block/Logic/Creation.hs`
444+
-- for the top of the function call chain which provides the `sk` which
445+
-- makes its way down to here.
430446
leaderPk = maybe (toPublic sk) snd pske
431447
consensus =
432448
MainConsensusData

chain/src/Pos/Chain/Block/Logic/Integrity.hs

+9-9
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import Pos.Chain.Block.Header (BlockHeader (..), HasHeaderHash (..),
3535
mainHeaderLeaderKey, verifyBlockHeader)
3636
import Pos.Chain.Block.IsHeader (headerSlotL)
3737
import Pos.Chain.Block.Main (mebAttributes, mehAttributes)
38-
import Pos.Chain.Block.Slog (ConsensusEraLeaders (..),
39-
LastSlotInfo (..))
38+
import Pos.Chain.Block.Slog (ConsensusEraLeaders (..), LastBlkSlots)
39+
import qualified Pos.Chain.Block.Slog.LastBlkSlots as LastBlkSlots
4040
import Pos.Chain.Genesis as Genesis (Config (..))
4141
import Pos.Chain.Txp (TxValidationRules)
4242
import Pos.Chain.Update (BlockVersionData (..), ConsensusEra (..))
@@ -211,7 +211,7 @@ verifyHeader pm VerifyHeaderParams {..} h =
211211
, ( obftLeaderCanMint blockSlotLeader blkSecurityParam lastBlkSlots
212212
, sformat ("ObftLenient: slot leader who published block, "%build%", has minted too many blocks ("% build %") in the past "%build%" slots.")
213213
blockSlotLeader
214-
(blocksMintedByLeaderInLastKSlots blockSlotLeader $ getOldestFirst lastBlkSlots)
214+
(blocksMintedByLeaderInLastKSlots blockSlotLeader lastBlkSlots)
215215
(getBlockCount blkSecurityParam)
216216
)
217217
]
@@ -240,22 +240,22 @@ verifyHeader pm VerifyHeaderParams {..} h =
240240
where
241241
-- Determine whether the leader is allowed to mint a block based on
242242
-- whether blocksMintedByLeaderInLastKSlots <= floor (k * t)
243-
obftLeaderCanMint :: AddressHash PublicKey -> BlockCount -> OldestFirst [] LastSlotInfo -> Bool
244-
obftLeaderCanMint leaderAddrHash blkSecurityParam (OldestFirst lastBlkSlots) =
243+
obftLeaderCanMint :: AddressHash PublicKey -> BlockCount -> LastBlkSlots -> Bool
244+
obftLeaderCanMint leaderAddrHash blkSecurityParam lastBlkSlots =
245245
blocksMintedByLeaderInLastKSlots leaderAddrHash lastBlkSlots
246246
<= leaderMintThreshold blkSecurityParam
247247

248-
blocksMintedByLeaderInLastKSlots :: AddressHash PublicKey -> [LastSlotInfo] -> Int
248+
blocksMintedByLeaderInLastKSlots :: AddressHash PublicKey -> LastBlkSlots -> Int
249249
blocksMintedByLeaderInLastKSlots leaderAddrHash lastBlkSlots =
250-
length $
251-
filter (\lsi -> leaderAddrHash == (addressHash $ lsiLeaderPubkeyHash lsi))
252-
lastBlkSlots
250+
LastBlkSlots.getKeyCount lastBlkSlots leaderAddrHash
253251

254252
leaderMintThreshold :: BlockCount -> Int
255253
leaderMintThreshold blkSecurityParam =
256254
let k = getBlockCount blkSecurityParam
257255
in floor $ (fromIntegral k :: Double) * t
258256

257+
-- This value was arrived at by deciding on the upper bound of the number of
258+
-- blocks in the last 'k' blocks (not slots) signed by the same signing key.
259259
t :: Double
260260
t = 0.22
261261

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
-- | Slog-related types.
2+
3+
module Pos.Chain.Block.Slog.LastBlkSlots
4+
( LastBlkSlots
5+
, LastSlotInfo (..)
6+
7+
-- * Create LastBlkSlots
8+
, create
9+
, fromList
10+
11+
-- * Access LastBlkSlots components
12+
, getList
13+
, lbsCount
14+
, lbsList
15+
, lbsMap
16+
17+
-- * LastBlkSlots operations
18+
, getKeyCount
19+
, isFull
20+
, listLength
21+
, mapSize
22+
, totalKeyCount
23+
, update
24+
, updateMany
25+
, updateManyR
26+
) where
27+
28+
import Universum
29+
30+
import qualified Data.List as List
31+
import qualified Data.Map.Strict as Map
32+
import Formatting (bprint, build, int, (%))
33+
import qualified Formatting.Buildable as Buildable
34+
35+
import Pos.Binary.Class (Cons (..), Field (..), deriveSimpleBi)
36+
import Pos.Core (AddressHash, FlatSlotId)
37+
import Pos.Core.Chrono (OldestFirst (..))
38+
import Pos.Crypto (PublicKey (..))
39+
40+
41+
-- Make sure its the actual genesis keys that are being counted.
42+
43+
data LastSlotInfo = LastSlotInfo
44+
{ lsiFlatSlotId :: !FlatSlotId
45+
-- ^ The flattened SlotId of this block.
46+
, lsiLeaderPubkeyHash :: !(AddressHash PublicKey)
47+
-- ^ The hash of the public key of the slot leader for this slot.
48+
} deriving (Eq, Show, Generic)
49+
50+
instance Buildable LastSlotInfo where
51+
build (LastSlotInfo i ahpk) =
52+
bprint ( "LastSlotInfo "% int %" "% build) i ahpk
53+
54+
instance NFData LastSlotInfo
55+
56+
-- | This type contains 'FlatSlotId's of the blocks whose depth is
57+
-- less than 'blkSecurityParam'. 'FlatSlotId' is chosen in favor of
58+
-- 'SlotId', because the main use case is chain quality calculation,
59+
-- for which flat slot is more convenient.
60+
-- Version 1 of this data type was:
61+
-- type LastBlkSlots = OldestFirst [] FlatSlotId
62+
-- Version 2 of this data type was:
63+
-- data LastBlkSlots = LastBlkSlots
64+
-- { lbsList :: !(OldestFirst [] LastSlotInfo)
65+
-- , lbsMap :: !(Map (AddressHash PublicKey) Int)
66+
-- } deriving (Eq, Show, Generic)
67+
data LastBlkSlots = LastBlkSlots
68+
{ lbsCount :: !Int
69+
, lbsList :: !(OldestFirst [] LastSlotInfo)
70+
, lbsMap :: !(Map (AddressHash PublicKey) Int)
71+
} deriving (Eq, Show, Generic)
72+
73+
instance NFData LastBlkSlots
74+
75+
create :: Int -> LastBlkSlots
76+
create k = LastBlkSlots k (OldestFirst []) mempty
77+
78+
getKeyCount :: LastBlkSlots -> AddressHash PublicKey -> Int
79+
getKeyCount lbs key =
80+
fromMaybe 0 $ Map.lookup key (lbsMap lbs)
81+
82+
totalKeyCount :: LastBlkSlots -> Int
83+
totalKeyCount =
84+
sum . map snd . Map.toList . lbsMap
85+
86+
isFull :: LastBlkSlots -> Bool
87+
isFull lbs =
88+
length (getList lbs) == lbsCount lbs
89+
90+
-- | Update LastBlkSlots with a single LastSlotInfo
91+
update :: LastBlkSlots -> LastSlotInfo -> LastBlkSlots
92+
update (LastBlkSlots k (OldestFirst lst) mp) lsi =
93+
if length lst < k
94+
then LastBlkSlots k (OldestFirst $ lst ++ [lsi]) (increment mp $ lsiLeaderPubkeyHash lsi)
95+
else case lst of
96+
[] -> error "Pos.Chain.Block.Slog.LastBlkSlots: Impossible empty list"
97+
(x:xs) ->
98+
LastBlkSlots k
99+
(OldestFirst $ xs ++ [lsi])
100+
(increment (decrement mp (lsiLeaderPubkeyHash x)) $ lsiLeaderPubkeyHash lsi)
101+
102+
-- | Update 'LastBlkSlots' with the elements from the list (head first).
103+
updateMany :: LastBlkSlots -> OldestFirst [] LastSlotInfo -> LastBlkSlots
104+
updateMany lbs = List.foldl' update lbs . getOldestFirst
105+
106+
-- | Like 'updateMany` but returns a tuple of the new 'LastBlkSlots' and a list
107+
-- of the elements removed.
108+
updateManyR :: LastBlkSlots -> OldestFirst [] LastSlotInfo -> (LastBlkSlots, OldestFirst [] LastSlotInfo)
109+
updateManyR lbs (OldestFirst xs) =
110+
let removed = List.take (length xs + listLength lbs - lbsCount lbs) (getList lbs ++ xs)
111+
in (List.foldl' update lbs xs, OldestFirst removed)
112+
113+
getList :: LastBlkSlots -> [LastSlotInfo]
114+
getList = getOldestFirst . lbsList
115+
116+
listLength :: LastBlkSlots -> Int
117+
listLength = length . lbsList
118+
119+
mapSize :: LastBlkSlots -> Int
120+
mapSize = Map.size . lbsMap
121+
122+
fromList :: Int -> OldestFirst [] LastSlotInfo -> LastBlkSlots
123+
fromList k = List.foldl' update (create k) . getOldestFirst
124+
125+
-- -----------------------------------------------------------------------------
126+
-- Private
127+
128+
increment :: Map (AddressHash PublicKey) Int -> AddressHash PublicKey -> Map (AddressHash PublicKey) Int
129+
increment m k =
130+
Map.alter incr k m
131+
where
132+
incr Nothing = Just 1
133+
incr (Just x) = Just $ x + 1
134+
135+
decrement :: Map (AddressHash PublicKey) Int -> AddressHash PublicKey -> Map (AddressHash PublicKey) Int
136+
decrement m k =
137+
Map.alter decr k m
138+
where
139+
decr Nothing = Nothing
140+
decr (Just x)
141+
| x > 1 = Just $ x - 1
142+
| otherwise = Nothing
143+
144+
-- -----------------------------------------------------------------------------
145+
-- TH derived instances at the end of the file.
146+
147+
deriveSimpleBi ''LastSlotInfo [
148+
Cons 'LastSlotInfo [
149+
Field [| lsiFlatSlotId :: FlatSlotId |],
150+
Field [| lsiLeaderPubkeyHash :: AddressHash PublicKey |]
151+
]
152+
]

chain/src/Pos/Chain/Block/Slog/Types.hs

+3-41
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module Pos.Chain.Block.Slog.Types
44
( LastBlkSlots
55
, LastSlotInfo (..)
6-
, noLastBlkSlots
76

87
, ConsensusEraLeaders (..)
98

@@ -19,50 +18,20 @@ module Pos.Chain.Block.Slog.Types
1918

2019
import Universum
2120

22-
import qualified Cardano.Crypto.Wallet as CC
2321
import Control.Lens (makeClassy)
24-
import qualified Data.ByteString.Base16 as B16
25-
import qualified Data.ByteString.Char8 as BS
26-
import Formatting (Format, bprint, int, later, string, (%))
27-
import qualified Formatting.Buildable as Buildable
22+
import Formatting (Format, bprint, later)
2823

2924
import System.Metrics.Label (Label)
3025

3126
import Pos.Binary.Class (Cons (..), Field (..), deriveSimpleBi)
27+
import Pos.Chain.Block.Slog.LastBlkSlots (LastBlkSlots,
28+
LastSlotInfo (..))
3229
import Pos.Core (BlockCount, ChainDifficulty, EpochIndex, FlatSlotId,
3330
LocalSlotIndex, SlotCount, SlotLeaders, StakeholderId,
3431
slotIdF, unflattenSlotId)
35-
import Pos.Core.Chrono (OldestFirst (..))
3632
import Pos.Core.Reporting (MetricMonitorState)
37-
import Pos.Crypto (PublicKey (..))
3833

3934

40-
data LastSlotInfo = LastSlotInfo
41-
{ lsiFlatSlotId :: !FlatSlotId
42-
-- ^ The flattened SlotId of this block.
43-
, lsiLeaderPubkeyHash :: !PublicKey
44-
-- ^ The hash of the public key of the slot leader for this slot.
45-
} deriving (Eq, Show, Generic)
46-
47-
instance Buildable LastSlotInfo where
48-
build (LastSlotInfo i (PublicKey pk)) =
49-
bprint ( "LastSlotInfo "% int %" "% string)
50-
i (take 16 . BS.unpack . B16.encode $ CC.xpubPublicKey pk)
51-
52-
instance NFData LastSlotInfo
53-
54-
-- | This type contains 'FlatSlotId's of the blocks whose depth is
55-
-- less than 'blkSecurityParam'. 'FlatSlotId' is chosen in favor of
56-
-- 'SlotId', because the main use case is chain quality calculation,
57-
-- for which flat slot is more convenient.
58-
-- Version 1 of this data type was:
59-
-- type LastBlkSlots = OldestFirst [] FlatSlotId
60-
type LastBlkSlots = OldestFirst [] LastSlotInfo
61-
62-
63-
noLastBlkSlots :: LastBlkSlots
64-
noLastBlkSlots = OldestFirst []
65-
6635
-- | This data type is used for block verification. It specifies which slot
6736
-- leader verification algorithm to use and the parameters required to do so.
6837
data ConsensusEraLeaders
@@ -146,10 +115,3 @@ deriveSimpleBi ''SlogUndo [
146115
Cons 'SlogUndo [
147116
Field [| getSlogUndo :: Maybe FlatSlotId |]
148117
]]
149-
150-
deriveSimpleBi ''LastSlotInfo [
151-
Cons 'LastSlotInfo [
152-
Field [| lsiFlatSlotId :: FlatSlotId |],
153-
Field [| lsiLeaderPubkeyHash :: PublicKey |]
154-
]
155-
]

chain/test/Test/Pos/Chain/Block/Arbitrary.hs

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import Pos.Binary.Class (biSize)
3737
import Pos.Chain.Block (ConsensusEraLeaders (..), HeaderHash,
3838
headerLastSlotInfo, mkMainBlock, mkMainBlockExplicit)
3939
import qualified Pos.Chain.Block as Block
40+
import qualified Pos.Chain.Block.Slog.LastBlkSlots as LastBlkSlots
4041
import qualified Pos.Chain.Delegation as Core
4142
import Pos.Chain.Genesis (GenesisHash (..))
4243
import Pos.Chain.Update (ConsensusEra (..),
@@ -505,7 +506,7 @@ genHeaderAndParams pm era = do
505506
pure $ ObftLenientLeaders
506507
(Set.fromList $ mapMaybe (fmap Core.addressHash . Block.headerLeaderKey) headers)
507508
blkSecurityParam
508-
(OldestFirst $ mapMaybe (headerLastSlotInfo slotsPerEpoch) headers)
509+
(LastBlkSlots.updateMany (LastBlkSlots.create (fromIntegral $ getBlockCount blkSecurityParam)) . OldestFirst $ mapMaybe (headerLastSlotInfo slotsPerEpoch) headers)
509510
, Block.vhpMaxSize = Just (biSize header)
510511
, Block.vhpVerifyNoUnknown = not hasUnknownAttributes
511512
, Block.vhpConsensusEra = era

0 commit comments

Comments
 (0)