Skip to content

Commit 15731aa

Browse files
committed
Support leios-late-ib-inclusion
1 parent 199680a commit 15731aa

File tree

12 files changed

+288
-88
lines changed

12 files changed

+288
-88
lines changed

data/simulation/config.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ export interface Config {
3939
* Only supported by Haskell simulation. */
4040
"leios-vote-send-recv-stages": boolean;
4141
/**
42-
* Extends Leios so that EB producers include IBs directly from previous pipelines
43-
* where no certified EB was observed.
44-
*
45-
* Only supported by Rust simulation. */
42+
* Extends Leios so that EB producers include IBs directly from previous pipelines.
43+
* Due to casuality, the EB must always include them, even if those IBs end up being
44+
* certified in their own pipeline.
45+
*/
4646
"leios-late-ib-inclusion": boolean;
4747
/**
4848
* The expected time it takes a header to fully diffuse across the network.

data/simulation/variants/config.full.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ leios-variant: full
44
# "Random" is meant to ensure that different producers include different TXs.
55
leios-mempool-sampling-strategy: random
66

7-
# Allow EBs to include IBs from previous slots
7+
# Allow EBs to include IBs from previous pipelines
88
leios-late-ib-inclusion: true
99

1010
# Chain quality controls how far back EB recursion can reach.

leios-trace-hs/src/LeiosConfig.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ data Config = Config
9999
, leiosStageActiveVotingSlots :: Word
100100
, leiosVoteSendRecvStages :: Bool
101101
, leiosVariant :: LeiosVariant
102+
, leiosLateIbInclusion :: Bool
102103
, leiosHeaderDiffusionTimeMs :: DurationMs
103104
, praosChainQuality :: Double
104105
, txGenerationDistribution :: Distribution
@@ -171,6 +172,7 @@ instance Default Config where
171172
, leiosStageActiveVotingSlots = 1
172173
, leiosVoteSendRecvStages = False
173174
, leiosVariant = Short
175+
, leiosLateIbInclusion = True
174176
, leiosHeaderDiffusionTimeMs = 1000
175177
, praosChainQuality = 40
176178
, txGenerationDistribution = Exp{lambda = 0.85, scale = Just 1000}
@@ -243,6 +245,7 @@ configToKVsWith getter cfg =
243245
, get @"treatBlocksAsFull" getter cfg
244246
, get @"cleanupPolicies" getter cfg
245247
, get @"leiosVariant" getter cfg
248+
, get @"leiosLateIbInclusion" getter cfg
246249
, get @"leiosHeaderDiffusionTimeMs" getter cfg
247250
, get @"praosChainQuality" getter cfg
248251
, get @"simulateTransactions" getter cfg
@@ -329,6 +332,7 @@ instance FromJSON Config where
329332
treatBlocksAsFull <- parseFieldOrDefault @Config @"treatBlocksAsFull" obj
330333
cleanupPolicies <- parseFieldOrDefault @Config @"cleanupPolicies" obj
331334
leiosVariant <- parseFieldOrDefault @Config @"leiosVariant" obj
335+
leiosLateIbInclusion <- parseFieldOrDefault @Config @"leiosLateIbInclusion" obj
332336
leiosHeaderDiffusionTimeMs <- parseFieldOrDefault @Config @"leiosHeaderDiffusionTimeMs" obj
333337
praosChainQuality <- parseFieldOrDefault @Config @"praosChainQuality" obj
334338
simulateTransactions <- parseFieldOrDefault @Config @"simulateTransactions" obj

leios-trace-verifier/hs-src/test/Spec/Scenario.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import qualified Data.Map.Strict as M
1919
import qualified Data.Set as S
2020

2121
config :: Config
22-
config = Config{relayStrategy = RequestFromFirst, tcpCongestionControl = True, multiplexMiniProtocols = True, treatBlocksAsFull = False, cleanupPolicies = CleanupPolicies (S.fromList [CleanupExpiredVote]), simulateTransactions = True, leiosStageLengthSlots = 2, leiosStageActiveVotingSlots = 1, leiosVoteSendRecvStages = False, leiosVariant = Short, leiosHeaderDiffusionTimeMs = 1000.0, praosChainQuality = 20.0, txGenerationDistribution = Exp{lambda = 0.85, scale = pure 1000.0}, txSizeBytesDistribution = LogNormal{mu = 6.833, sigma = 1.127}, txValidationCpuTimeMs = 1.5, txMaxSizeBytes = 16384, rbGenerationProbability = 5.0e-2, rbGenerationCpuTimeMs = 1.0, rbHeadValidationCpuTimeMs = 1.0, rbHeadSizeBytes = 1024, rbBodyMaxSizeBytes = 90112, rbBodyLegacyPraosPayloadValidationCpuTimeMsConstant = 50.0, rbBodyLegacyPraosPayloadValidationCpuTimeMsPerByte = 5.0e-4, rbBodyLegacyPraosPayloadAvgSizeBytes = 0, ibGenerationProbability = 5.0, ibGenerationCpuTimeMs = 130.0, ibHeadSizeBytes = 304, ibHeadValidationCpuTimeMs = 1.0, ibBodyValidationCpuTimeMsConstant = 50.0, ibBodyValidationCpuTimeMsPerByte = 5.0e-4, ibBodyMaxSizeBytes = 327680, ibBodyAvgSizeBytes = 98304, ibDiffusionStrategy = FreshestFirst, ibDiffusionMaxWindowSize = 100, ibDiffusionMaxHeadersToRequest = 100, ibDiffusionMaxBodiesToRequest = 1, ibShards = 50, ebGenerationProbability = 1.5, ebGenerationCpuTimeMs = 75.0, ebValidationCpuTimeMs = 1.0, ebSizeBytesConstant = 240, ebSizeBytesPerIb = 32, ebDiffusionStrategy = PeerOrder, ebDiffusionMaxWindowSize = 100, ebDiffusionMaxHeadersToRequest = 100, ebDiffusionMaxBodiesToRequest = 1, ebMaxAgeSlots = 100, ebMaxAgeForRelaySlots = 40, voteGenerationProbability = 500.0, voteGenerationCpuTimeMsConstant = 0.164, voteGenerationCpuTimeMsPerIb = 0.0, voteValidationCpuTimeMs = 0.816, voteThreshold = 300, voteBundleSizeBytesConstant = 0, voteBundleSizeBytesPerEb = 105, voteDiffusionStrategy = PeerOrder, voteDiffusionMaxWindowSize = 100, voteDiffusionMaxHeadersToRequest = 100, voteDiffusionMaxBodiesToRequest = 1, certGenerationCpuTimeMsConstant = 90.0, certGenerationCpuTimeMsPerNode = 0.0, certValidationCpuTimeMsConstant = 130.0, certValidationCpuTimeMsPerNode = 0.0, certSizeBytesConstant = 7168, certSizeBytesPerNode = 0}
22+
config = Config{relayStrategy = RequestFromFirst, tcpCongestionControl = True, multiplexMiniProtocols = True, treatBlocksAsFull = False, cleanupPolicies = CleanupPolicies (S.fromList [CleanupExpiredVote]), simulateTransactions = True, leiosStageLengthSlots = 2, leiosStageActiveVotingSlots = 1, leiosVoteSendRecvStages = False, leiosVariant = Short, leiosLateIbInclusion = False, leiosHeaderDiffusionTimeMs = 1000.0, praosChainQuality = 20.0, txGenerationDistribution = Exp{lambda = 0.85, scale = pure 1000.0}, txSizeBytesDistribution = LogNormal{mu = 6.833, sigma = 1.127}, txValidationCpuTimeMs = 1.5, txMaxSizeBytes = 16384, rbGenerationProbability = 5.0e-2, rbGenerationCpuTimeMs = 1.0, rbHeadValidationCpuTimeMs = 1.0, rbHeadSizeBytes = 1024, rbBodyMaxSizeBytes = 90112, rbBodyLegacyPraosPayloadValidationCpuTimeMsConstant = 50.0, rbBodyLegacyPraosPayloadValidationCpuTimeMsPerByte = 5.0e-4, rbBodyLegacyPraosPayloadAvgSizeBytes = 0, ibGenerationProbability = 5.0, ibGenerationCpuTimeMs = 130.0, ibHeadSizeBytes = 304, ibHeadValidationCpuTimeMs = 1.0, ibBodyValidationCpuTimeMsConstant = 50.0, ibBodyValidationCpuTimeMsPerByte = 5.0e-4, ibBodyMaxSizeBytes = 327680, ibBodyAvgSizeBytes = 98304, ibDiffusionStrategy = FreshestFirst, ibDiffusionMaxWindowSize = 100, ibDiffusionMaxHeadersToRequest = 100, ibDiffusionMaxBodiesToRequest = 1, ibShards = 50, ebGenerationProbability = 1.5, ebGenerationCpuTimeMs = 75.0, ebValidationCpuTimeMs = 1.0, ebSizeBytesConstant = 240, ebSizeBytesPerIb = 32, ebDiffusionStrategy = PeerOrder, ebDiffusionMaxWindowSize = 100, ebDiffusionMaxHeadersToRequest = 100, ebDiffusionMaxBodiesToRequest = 1, ebMaxAgeSlots = 100, ebMaxAgeForRelaySlots = 40, voteGenerationProbability = 500.0, voteGenerationCpuTimeMsConstant = 0.164, voteGenerationCpuTimeMsPerIb = 0.0, voteValidationCpuTimeMs = 0.816, voteThreshold = 300, voteBundleSizeBytesConstant = 0, voteBundleSizeBytesPerEb = 105, voteDiffusionStrategy = PeerOrder, voteDiffusionMaxWindowSize = 100, voteDiffusionMaxHeadersToRequest = 100, voteDiffusionMaxBodiesToRequest = 1, certGenerationCpuTimeMsConstant = 90.0, certGenerationCpuTimeMsPerNode = 0.0, certValidationCpuTimeMsConstant = 130.0, certValidationCpuTimeMsPerNode = 0.0, certSizeBytesConstant = 7168, certSizeBytesPerNode = 0}
2323

2424
topology :: Topology 'COORD2D
2525
topology = Topology{nodes = M.fromList [(NodeName "node-0", Node{nodeInfo = NodeInfo{stake = 500, cpuCoreCount = CpuCoreCount mzero, location = LocCoord2D{coord2D = Point{_1 = 0.12000040231003672, _2 = 0.1631004621065356}}, adversarial = mzero}, producers = M.fromList [(NodeName "node-1", LinkInfo{latencyMs = 141.01364015418432, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000}), (NodeName "node-2", LinkInfo{latencyMs = 254.6249782835189, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000})]}), (NodeName "node-1", Node{nodeInfo = NodeInfo{stake = 200, cpuCoreCount = CpuCoreCount mzero, location = LocCoord2D{coord2D = Point{_1 = 0.34276660615051174, _2 = 0.2636899791034371}}, adversarial = mzero}, producers = M.fromList [(NodeName "node-2", LinkInfo{latencyMs = 175.32530255486685, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000}), (NodeName "node-3", LinkInfo{latencyMs = 379.1167948193313, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000})]}), (NodeName "node-2", Node{nodeInfo = NodeInfo{stake = 100, cpuCoreCount = CpuCoreCount mzero, location = LocCoord2D{coord2D = Point{_1 = 0.5150493264153491, _2 = 0.27873594531347595}}, adversarial = mzero}, producers = M.fromList [(NodeName "node-3", LinkInfo{latencyMs = 248.31457793649423, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000})]}), (NodeName "node-3", Node{nodeInfo = NodeInfo{stake = 0, cpuCoreCount = CpuCoreCount mzero, location = LocCoord2D{coord2D = Point{_1 = 0.3503537969220088, _2 = 0.13879558055660354}}, adversarial = mzero}, producers = M.fromList [(NodeName "node-0", LinkInfo{latencyMs = 140.19739576271448, bandwidthBytesPerSecond = BandwidthBps $ pure 1024000})]})]}

simulation/ouroboros-leios-sim.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ test-suite ols-test
218218
, bytestring
219219
other-modules:
220220
Paths_ouroboros_leios_sim
221-
Test.Topology
222221
Test.Config
222+
Test.ShortToFull
223+
Test.Topology
223224
default-language: Haskell2010

simulation/src/LeiosProtocol/Relay.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ data SubmitPolicy = SubmitInOrder | SubmitAll
498498

499499
data RelayConsumerConfig id header body m = RelayConsumerConfig
500500
{ relay :: !RelayConfig
501-
, shouldIgnore :: m (header -> Bool)
501+
, shouldNotRequest :: m (header -> Bool)
502502
-- ^ headers to ignore, e.g. already received or coming too late.
503503
, validateHeaders :: [header] -> m ()
504504
, headerId :: !(header -> id)
@@ -761,7 +761,7 @@ relayConsumerPipelined config sst =
761761
if (min (Map.size lst0.available) (fromIntegral config.maxBodiesToRequest)) == 0
762762
then return (Left lst0)
763763
else return . Right . TS.Effect $ do
764-
isIgnored <- config.shouldIgnore
764+
isIgnored <- config.shouldNotRequest
765765
atomically $ do
766766
-- New headers are filtered before becoming available, but we have
767767
-- to filter `lst.available` again in the same STM tx that sets them as
@@ -987,7 +987,7 @@ relayConsumerPipelined config sst =
987987
m (RelayConsumerLocalState id header body n)
988988
acknowledgeIds lst idsSeq _ | Seq.null idsSeq = pure lst
989989
acknowledgeIds lst idsSeq idsMap = do
990-
isIgnored <- config.shouldIgnore
990+
isIgnored <- config.shouldNotRequest
991991
inFlight <- readTVarIO sst.inFlightVar
992992

993993
let lst1 =

simulation/src/LeiosProtocol/Short.hs

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ data LeiosConfig = forall p. IsPipeline p => LeiosConfig
111111
, variant :: LeiosVariant
112112
, headerDiffusionTime :: NominalDiffTime
113113
-- ^ Δ_{hdr}.
114+
, lateIbInclusion :: Bool
115+
-- ^ Whether an EB also includes IBs from the two previous iterations.
116+
--
117+
-- TODO Merely one previous iteration if 'pipeline' is 'SingleVote'?
114118
, pipelinesToReferenceFromEB :: Int
115119
-- ^ how many older pipelines to reference from an EB when `variant = Full`.
116120
, votingFrequencyPerStage :: Double
@@ -147,6 +151,7 @@ convertConfig disk =
147151
, cleanupPolicies = disk.cleanupPolicies
148152
, variant = disk.leiosVariant
149153
, headerDiffusionTime = realToFrac $ durationMsToDiffTime disk.leiosHeaderDiffusionTimeMs
154+
, lateIbInclusion = disk.leiosLateIbInclusion
150155
, pipelinesToReferenceFromEB =
151156
if disk.leiosVariant == Full
152157
then
@@ -285,6 +290,7 @@ delaysAndSizesAsFull cfg@LeiosConfig{pipeline, voteSendStage} =
285290
, cleanupPolicies = cfg.cleanupPolicies
286291
, variant = cfg.variant
287292
, headerDiffusionTime = cfg.headerDiffusionTime
293+
, lateIbInclusion = cfg.lateIbInclusion
288294
, pipelinesToReferenceFromEB = cfg.pipelinesToReferenceFromEB
289295
, activeVotingStageLength = cfg.activeVotingStageLength
290296
, votingFrequencyPerStage = cfg.votingFrequencyPerStage
@@ -461,21 +467,38 @@ isStage cfg stage slot = fromEnum slot >= cfg.sliceLength * fromEnum stage
461467
newtype PipelineNo = PipelineNo Word64
462468
deriving (Bounded, Enum, Show, Eq, Ord)
463469

470+
pipelineMonus :: PipelineNo -> Word64 -> PipelineNo
471+
pipelineMonus (PipelineNo w) i = PipelineNo $ w - min w i
472+
464473
stageRangeOf :: forall p. IsPipeline p => LeiosConfig -> PipelineNo -> Stage p -> (SlotNo, SlotNo)
465474
stageRangeOf cfg pl stage =
466475
fromMaybe
467476
undefined
468477
(stageRange cfg minBound (toEnum (fromEnum pl * cfg.sliceLength)) stage)
469478

479+
-- | WARNING This fails if the slot is earlier than the beginning of the stage
480+
-- in the first iteration (ie @'PipelineNo' 0@)
470481
pipelineOf :: forall p. IsPipeline p => LeiosConfig -> Stage p -> SlotNo -> PipelineNo
471482
pipelineOf cfg stage sl =
472-
toEnum $
473-
fromMaybe undefined (fromEnum <$> stageStart cfg stage sl minBound)
474-
`div` cfg.sliceLength
483+
maybe err cnv $ stageStart cfg stage sl minBound
484+
where
485+
cnv = toEnum . (`div` cfg.sliceLength) . fromEnum
486+
487+
err = error $ show (cfg.sliceLength, x, stage, sl)
488+
489+
x :: String
490+
x = case cfg of
491+
LeiosConfig{pipeline} -> case pipeline of
492+
SingSingleVote -> "SingleVote"
493+
SingSplitVote -> "SplitVote"
475494

476495
forEachPipeline :: (forall p. Stage p) -> (forall p. IsPipeline p => Stage p -> a) -> [a]
477496
forEachPipeline s k = [k @SingleVote s, k @SplitVote s]
478497

498+
lastEndorse :: LeiosConfig -> PipelineNo -> SlotNo
499+
lastEndorse leios@LeiosConfig{pipeline = _ :: SingPipeline p} pipelineNo =
500+
snd $ stageRangeOf @p leios pipelineNo Endorse
501+
479502
lastVoteSend :: LeiosConfig -> PipelineNo -> SlotNo
480503
lastVoteSend leios@LeiosConfig{pipeline} pipelineNo = case pipeline of
481504
SingSingleVote -> snd (stageRangeOf leios pipelineNo Vote)
@@ -657,28 +680,76 @@ data EndorseBlocksSnapshot = EndorseBlocksSnapshot
657680
, certifiedEndorseBlocks :: (PipelineNo, PipelineNo) -> [(PipelineNo, [(EndorseBlock, Certificate, UTCTime)])]
658681
}
659682

683+
-- | In which contemporary stage was an IB delivered
684+
--
685+
-- IBs cannot be deliver earlier than any of these options, due to the
686+
-- 'LeiosProtocol.Relay.shouldNotRequest' logic of the
687+
-- 'LeiosProtocol.Short.Node.relayIBState'.
688+
--
689+
-- IBs that are delivered later than any of these options are discarded,
690+
-- ignored.
691+
data IbDeliveryStage
692+
= -- | The node will not vote for an EB that excludes IBs that arrived during
693+
-- Propose or Deliver1.
694+
--
695+
-- The node will include IBs that arrived during Propose or Deliver1 in an
696+
-- EB it makes.
697+
IbDuringProposeOrDeliver1
698+
| -- | The node will include IBs that arrived during Deliver2 in an EB it makes.
699+
IbDuringDeliver2
700+
| -- | The node will not vote for an EB that includes IBs that arrived later
701+
-- than Endorse.
702+
IbDuringEndorse
703+
deriving (Bounded, Enum, Eq, Ord, Show)
704+
660705
-- | Both constraints are inclusive.
661706
data InputBlocksQuery = InputBlocksQuery
662-
{ generatedBetween :: (SlotNo, SlotNo)
663-
, receivedBy :: SlotNo
707+
{ generatedBetween :: (PipelineNo, PipelineNo)
708+
, receivedBy :: IbDeliveryStage
664709
-- ^ This is checked against time the body is downloaded, before validation.
665710
}
666711

712+
ibWasDeliveredLate :: LeiosConfig -> SlotConfig -> SlotNo -> UTCTime -> Bool
713+
ibWasDeliveredLate cfg slotCfg sl deliveryTime =
714+
case ibDeliveryStage cfg slotCfg sl deliveryTime of
715+
Nothing -> True
716+
Just{} -> False
717+
718+
ibDeliveryStage :: LeiosConfig -> SlotConfig -> SlotNo -> UTCTime -> Maybe IbDeliveryStage
719+
ibDeliveryStage
720+
cfg@LeiosConfig{pipeline = _ :: SingPipeline p}
721+
slotCfg
722+
ibSlot
723+
deliveryTime
724+
| before loPropose = Nothing -- TODO future blocks?
725+
| before loDeliver2 = Just IbDuringProposeOrDeliver1
726+
| before loEndorse = Just IbDuringDeliver2
727+
| before (succ hiEndorse) = Just IbDuringEndorse
728+
| otherwise = Nothing -- TODO late blocks?
729+
where
730+
p = pipelineOf @p cfg Propose ibSlot
731+
732+
before sl = deliveryTime < slotTime slotCfg sl
733+
734+
(loPropose, _) = stageRangeOf @p cfg p Propose
735+
(loDeliver2, _) = stageRangeOf @p cfg p Deliver2
736+
(loEndorse, hiEndorse) = stageRangeOf @p cfg p Endorse
737+
667738
inputBlocksToEndorse ::
668739
LeiosConfig ->
669740
-- | current slot
670741
SlotNo ->
671742
InputBlocksSnapshot ->
672743
[InputBlockId]
673-
inputBlocksToEndorse cfg@LeiosConfig{pipeline = _ :: SingPipeline p} current buffer = fromMaybe [] $ do
674-
generatedBetween <- stageRange @p cfg Endorse current Propose
675-
receivedBy <- stageEnd @p cfg Endorse current Deliver2
676-
pure $
677-
buffer.validInputBlocks
678-
InputBlocksQuery
679-
{ generatedBetween
680-
, receivedBy
681-
}
744+
inputBlocksToEndorse cfg@LeiosConfig{pipeline = _ :: SingPipeline p} current buffer =
745+
buffer.validInputBlocks
746+
InputBlocksQuery
747+
{ generatedBetween = (lo, hi)
748+
, receivedBy = IbDuringDeliver2
749+
}
750+
where
751+
hi = pipelineOf @p cfg Endorse current
752+
lo = if cfg.lateIbInclusion then pipelineMonus hi 2 else hi
682753

683754
-- | Returns possible EBs to reference from current pipeline EB.
684755
endorseBlocksToReference ::
@@ -734,21 +805,24 @@ shouldVoteOnEB ::
734805
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} _ slot _buffers _
735806
-- checks whether a pipeline has been started before.
736807
| Nothing <- stageRange cfg voteSendStage slot Propose = const False
737-
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} slotConfig slot buffers ebuffers = cond
808+
shouldVoteOnEB cfg@LeiosConfig{voteSendStage = voteSendStage :: Stage p} slotConfig slot buffers ebuffers = cond
738809
where
739-
generatedBetween = fromMaybe (error "impossible") $ stageRange cfg voteSendStage slot Propose
810+
generatedBetween = (lo, hi)
811+
where
812+
hi = pipelineOf @p cfg voteSendStage slot
813+
lo = if cfg.lateIbInclusion then pipelineMonus hi 2 else hi
740814
receivedByEndorse =
741815
buffers.validInputBlocks
742816
InputBlocksQuery
743817
{ generatedBetween
744-
, receivedBy = fromMaybe (error "impossible") $ stageEnd cfg voteSendStage slot Endorse
818+
, receivedBy = IbDuringEndorse
745819
}
746820
receivedByDeliver1 = buffers.validInputBlocks q
747821
where
748822
q =
749823
InputBlocksQuery
750824
{ generatedBetween
751-
, receivedBy = fromMaybe (error "impossible") $ stageEnd cfg voteSendStage slot Deliver1
825+
, receivedBy = IbDuringProposeOrDeliver1
752826
}
753827
-- Order of references in EndorseBlock matters for ledger state, so we stick to lists.
754828
-- Note: maybe order on (slot, subSlot, vrf proof) should be used instead?

0 commit comments

Comments
 (0)