Repository navigation
Leios: omnibus for production-grade EB diffusion - #2282
Conversation
|
It's in draft because of unacceptable commit names and no tests. |
| -- 'Nothing' if the protocol parameter does not yet exist on the current era. | ||
| getCurrentThreshold :: LedgerState blk EmptyMK -> Maybe Weight | ||
|
|
||
| -- | The committee and quorum threshold according to a /forecast/ ledger view |
There was a problem hiding this comment.
Maybe the two methods above this one could be retired, since we can compose this new one with protocolLedgerView?
There was a problem hiding this comment.
Yes, retired; see the thread below.
| , KES.Signable (KES c) (PraosCodec.HeaderBody c) | ||
| , VRF.Signable (VRF c) InputVRF | ||
| ) => | ||
| PraosCrypto c |
There was a problem hiding this comment.
I didn't parameterize this over pext because:
- I know it occurs in very many places.
- We can use
KnownPraosExtension pextas a small workaround to recover theSignable (KES c) HeaderBodydict for somepextin the few places we need it.
| reject cert why = | ||
| pure $ Left $ LeiosCertificateForecastRejected cert predSlot why | ||
|
|
||
| -- | Record the invalid block in the given map and change its fingerprint |
There was a problem hiding this comment.
This was merely floated out of its where clause so it could be called from a second location.
| LedgerState blk mk -> | ||
| AF.AnchoredFragment (Header blk) -> | ||
| AF.AnchoredFragment (HeaderWithTime blk) | ||
| mkHeadersWithTime cfg lst frag = |
There was a problem hiding this comment.
TODO is this impl too allocation-heavy?
88a21b6 to
7e7cb83
Compare
7e7cb83 to
eecca00
Compare
|
My latest pushes are just trying to fix all the CI failures. I'm only compiling with 9.12 -Werror locally. |
ed2c7a8 to
215a564
Compare
215a564 to
7e82b51
Compare
1191375 to
4365b9f
Compare
|
There are a couple testing modules where I haven't yet combed through the comments Claude wrote, but otherwise, I've reviewed every line (and trimmed down a lot of comment, pushed back on extra complexity, etc). |
|
For what it's worth, Claude breaks down the diff's line counts as in the table below. My highlights:
|
|
Just now added the commit Config: allow default leiosMinOfferLead to be overriden I anticipated the |
|
I added this warning to the PR description:
(edit: I kludged the local build of |
The LeiosFetch state was handling this case well, but its interactions with the LeiosDB/ChainSel state are not yet.
This fixes the bug where cdbAcquiredEbs wasn't necessary storing the youngest slot for some EB. It also ensures AcquiredEb and AcquiredEbTxs are emitted for a LeiosPoint even if its EB was already in the LeiosDb when this LeiosPoint was first announced.
(This commit message is written as if comparing/contrasting with origin/main instead of the preceding commits on this feature branch.) This incurs some duplication, but it's worth it. - The duplication is not overly burdensome to compensate for by factoring the Praos impl somewhat so its types and functions can be reused as much as possible. For Leios, at least, that's easy since Leios only adds a few independent fields to the header semantics. - The risk of the two accidentally drifting apart is low: Praos has been running on mainnet for years, so any incompatible changes to it need a great deal of scrutiny. (And any changes to it at all need to be worth the trouble.) - Duplicating instances between Praos and PraosWithLeios instead of having them share some instances (ala a `BasePraos leiosFlag` protocol type) has a couple benefits. First, code is either monomorphic or reuses the existing parameterizations over `proto`, which is already ubiquitous. Second, it avoids _implicit_ reuse of today's Praos's rules for Leios, which makes _accidental_ reuse less likely---compare to type class method defaults. We do _not_ duplicate more than we need to, though. In particular, many of Praos's data types and some of its classes gain a `proto` parameter, which is used so that the single data type definition can be reused for Praos with and without Leios. The benefit is that there is just one constructor/field per Praos concept, regardless of whether Leios is enabled. This is not a fully modular design: subsequent additional extensions will also need to add to these same definitions (eg adding fields for Ouroboros Phalanx). That is intentional. This code does not need to be classically extensible, since there is, unfortunately, no such thing as an extensible security proof. Our protocol changes are well studied before implemented, and have never happened concurrently. In other words: it's a very important benefit that there is _one definition_ to look at in order to see everything all of the Praos extensions _cumulatively_ do. The type-level DSL used to isolate extension components is simple and legible; see the `EitherLeiosF` data family.
SQLite rejects a body written for a point that writeEbPoint never inserted.
InMemory instead registered the point silently, so InMemory-based tests could
not catch a missing registration.
Today processLeiosBlock always registers the point first, so nothing hits this
and the tests still pass. The guard matters once this TODO lands:
-- TODO remove the 'writeEbPoint' call below once no important node's
-- VolatileDB still holds an announcing RB that it fetched without this
-- patch. [...]
pointWritten <- writeEbPoint writer point ebBytesSize'
After that, any body reaching processLeiosBlock without a centrally-processed
announcement would fail on SQLite. With this change InMemory fails the same way,
so the tests surface it instead of passing:
```
$> cabal test ouroboros-consensus:consensus-test --test-options='--pattern "/LeiosDemoLogic.Invariants/"'
Test suite consensus-test: RUNNING...
ouroboros-consensus
Leios
LeiosDemoLogic.Invariants
curated sequences
forge purges a body it already holds (offered first): FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:136:
expected: Right []
but got: Left "exception on Forge [0,1] 12: LeiosDbWriteException {writeJob = \"WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}}"
Use -p '/LeiosDemoLogic.Invariants/&&/forge purges a body it already holds (offered first)/' to rerun this test only.
an offer of a self-forged EB is not re-fetched (forged first): FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:138:
expected: Right []
but got: Left "exception on Forge [0,1] 12: LeiosDbWriteException {writeJob = \"WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}}"
Use -p '/LeiosDemoLogic.Invariants/&&/an offer of a self-forged EB is not re-fetched (forged first)/' to rerun this test only.
a body is claimed acquired only by a settled write: OK
acquired EB kept until its greatest slot is below the immutable tip: OK
an announcement raises a forged EB's max slot (so it isn't pruned early): OK
an announcement's onset is recorded (earliest kept); an offer never clobbers it: OK
start-up seeding marks each completed EB held, with an empty pool: OK
start-up seeding: a peer's offer of a seeded EB is not re-fetched: OK
a big-ledger peer has a larger, but still finite, closure budget: OK
job assignment draws within the least-requested bucket, at random, respecting exclusions: OK
only the announcement that takes an election's focus is tracked: OK
EB-hash collision (ported from #2309)
forged, then the same EB announced at a new slot: both points registered and notified: FAIL
Exception: WriteEbBody (5, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99) failed: writeEbBody: point not registered (programmer error)
CallStack (from HasCallStack):
throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
submitted from:
CallStack (from HasCallStack):
resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common
a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic
HasCallStack backtrace:
collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
throwIO, called at ./Control/Concurrent/Async.hs:78:13 in tsty-1.5.4-843ddea7:Control.Concurrent.Async
Use -p '/LeiosDemoLogic.Invariants/&&/forged, then the same EB announced at a new slot: both points registered and notified/' to rerun this test only.
body held, then the same EB announced at a new slot: both points registered: OK
both announced before the fetch: both points registered: OK
forged, then the same EB merely offered at a new slot: the offer registers nothing: FAIL
Exception: WriteEbBody (5, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99) failed: writeEbBody: point not registered (programmer error)
CallStack (from HasCallStack):
throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
submitted from:
CallStack (from HasCallStack):
resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common
a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic
HasCallStack backtrace:
collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
throwIO, called at ./Control/Concurrent/Async.hs:78:13 in tsty-1.5.4-843ddea7:Control.Concurrent.Async
Use -p '/LeiosDemoLogic.Invariants/&&/forged, then the same EB merely offered at a new slot: the offer registers nothing/' to rerun this test only.
a certificate tracks an endorser block no announcement did: OK
an endorser block that references too many tx bytes is dropped, not fetched: OK
an endorser block at exactly its bound is fetched: FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:476:
expected BodyAcquired, got Just (MkEbState (SlotNo 5) SNothing NoBody)
Use -p '/LeiosDemoLogic.Invariants/&&/an endorser block at exactly its bound is fetched/' to rerun this test only.
a certificate leaves a body we already hold held: OK
outstanding-state invariants hold across arbitrary sequences: FAIL
*** Failed! Falsified (after 4 tests and 1 shrink):
[ArriveBody [2,3,4] 11,Announce [1,2] 11]
Left "exception on Announce [1,2] 11: ExceptionInLinkedThread \"ThreadId [1]\" (LeiosDbWriteException {writeJob = \"WriteEbBody (11, ff3e90aa6fbe36b630f58be98da978061e94c0a998da2376f0b4abf615082391)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}})" /= Right ()
Use --quickcheck-replay="(SMGen 14245007439037612623 12544114665117986533,3)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/outstanding-state invariants hold across arbitrary sequences/' to rerun this test only.
the fetch logic never requests an already-held EB body: FAIL
*** Failed! Falsified (after 4 tests and 1 shrink):
[ArriveBody [2,3,4] 10,Disconnect]
exception on Disconnect: ExceptionInLinkedThread "ThreadId [1]" (LeiosDbWriteException {writeJob = "WriteEbBody (10, ff3e90aa6fbe36b630f58be98da978061e94c0a998da2376f0b4abf615082391)", submittedFrom = "CallStack (from HasCallStack):\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic", writeFailure = LeiosDbException {errorMessage = "writeEbBody: point not registered (programmer error)", callStack = "CallStack (from HasCallStack):\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory"}})
Use --quickcheck-replay="(SMGen 10024954925442282638 16722750794533007117,3)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/the fetch logic never requests an already-held EB body/' to rerun this test only.
a concurrent offer, announcement and body arrival keep the reverse index in sync (IOSimPOR): FAIL
*** Failed! Falsified (after 1 test):
Schedule control: ControlDefault
No thread delayed
Failure: FailureException (ExceptionInLinkedThread "RacyThreadId [2,2,1]" (LeiosDbWriteException {writeJob = "WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)", submittedFrom = "CallStack (from HasCallStack):\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic", writeFailure = LeiosDbException {errorMessage = "writeEbBody: point not registered (programmer error)", callStack = "CallStack (from HasCallStack):\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory"}}))
Use --quickcheck-replay="(SMGen 7877884416199475454 17480834384350021,0)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/a concurrent offer, announcement and body arrival keep the reverse index in sync (IOSimPOR)/' to rerun this test only.
8 out of 22 tests failed (0.01s)
```
SQLite rejects a body written for a point that `writeEbPoint` never
inserted. InMemory instead registered the point silently, so
InMemory-based tests could not catch a missing registration.
Today `processLeiosBlock` always registers the point first, so nothing
hits this and the tests still pass. The guard matters once this TODO
lands:
```haskell
-- TODO remove the 'writeEbPoint' call below once no important node's
-- VolatileDB still holds an announcing RB that it fetched without this
-- patch. [...]
pointWritten <- writeEbPoint writer point ebBytesSize'
```
After that, any body reaching processLeiosBlock without a
centrally-processed announcement would fail on SQLite. With this change
InMemory fails the same way, so the tests surface it instead of passing:
```bash
$> cabal test ouroboros-consensus:consensus-test --test-options='--pattern "/LeiosDemoLogic.Invariants/"'
Test suite consensus-test: RUNNING...
ouroboros-consensus
Leios
LeiosDemoLogic.Invariants
curated sequences
forge purges a body it already holds (offered first): FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:136:
expected: Right []
but got: Left "exception on Forge [0,1] 12: LeiosDbWriteException {writeJob = \"WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}}"
Use -p '/LeiosDemoLogic.Invariants/&&/forge purges a body it already holds (offered first)/' to rerun this test only.
an offer of a self-forged EB is not re-fetched (forged first): FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:138:
expected: Right []
but got: Left "exception on Forge [0,1] 12: LeiosDbWriteException {writeJob = \"WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}}"
Use -p '/LeiosDemoLogic.Invariants/&&/an offer of a self-forged EB is not re-fetched (forged first)/' to rerun this test only.
a body is claimed acquired only by a settled write: OK
acquired EB kept until its greatest slot is below the immutable tip: OK
an announcement raises a forged EB's max slot (so it isn't pruned early): OK
an announcement's onset is recorded (earliest kept); an offer never clobbers it: OK
start-up seeding marks each completed EB held, with an empty pool: OK
start-up seeding: a peer's offer of a seeded EB is not re-fetched: OK
a big-ledger peer has a larger, but still finite, closure budget: OK
job assignment draws within the least-requested bucket, at random, respecting exclusions: OK
only the announcement that takes an election's focus is tracked: OK
EB-hash collision (ported from #2309)
forged, then the same EB announced at a new slot: both points registered and notified: FAIL
Exception: WriteEbBody (5, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99) failed: writeEbBody: point not registered (programmer error)
CallStack (from HasCallStack):
throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
submitted from:
CallStack (from HasCallStack):
resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common
a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic
HasCallStack backtrace:
collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
throwIO, called at ./Control/Concurrent/Async.hs:78:13 in tsty-1.5.4-843ddea7:Control.Concurrent.Async
Use -p '/LeiosDemoLogic.Invariants/&&/forged, then the same EB announced at a new slot: both points registered and notified/' to rerun this test only.
body held, then the same EB announced at a new slot: both points registered: OK
both announced before the fetch: both points registered: OK
forged, then the same EB merely offered at a new slot: the offer registers nothing: FAIL
Exception: WriteEbBody (5, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99) failed: writeEbBody: point not registered (programmer error)
CallStack (from HasCallStack):
throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
submitted from:
CallStack (from HasCallStack):
resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory
writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common
a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic
HasCallStack backtrace:
collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:169:13 in ghc-internal:GHC.Internal.Exception
toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
throwIO, called at ./Control/Concurrent/Async.hs:78:13 in tsty-1.5.4-843ddea7:Control.Concurrent.Async
Use -p '/LeiosDemoLogic.Invariants/&&/forged, then the same EB merely offered at a new slot: the offer registers nothing/' to rerun this test only.
a certificate tracks an endorser block no announcement did: OK
an endorser block that references too many tx bytes is dropped, not fetched: OK
an endorser block at exactly its bound is fetched: FAIL
ouroboros-consensus/test/consensus-test/Test/LeiosDemoLogic/Invariants.hs:476:
expected BodyAcquired, got Just (MkEbState (SlotNo 5) SNothing NoBody)
Use -p '/LeiosDemoLogic.Invariants/&&/an endorser block at exactly its bound is fetched/' to rerun this test only.
a certificate leaves a body we already hold held: OK
outstanding-state invariants hold across arbitrary sequences: FAIL
*** Failed! Falsified (after 4 tests and 1 shrink):
[ArriveBody [2,3,4] 11,Announce [1,2] 11]
Left "exception on Announce [1,2] 11: ExceptionInLinkedThread \"ThreadId [1]\" (LeiosDbWriteException {writeJob = \"WriteEbBody (11, ff3e90aa6fbe36b630f58be98da978061e94c0a998da2376f0b4abf615082391)\", submittedFrom = \"CallStack (from HasCallStack):\\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic\", writeFailure = LeiosDbException {errorMessage = \"writeEbBody: point not registered (programmer error)\", callStack = \"CallStack (from HasCallStack):\\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\"}})" /= Right ()
Use --quickcheck-replay="(SMGen 14245007439037612623 12544114665117986533,3)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/outstanding-state invariants hold across arbitrary sequences/' to rerun this test only.
the fetch logic never requests an already-held EB body: FAIL
*** Failed! Falsified (after 4 tests and 1 shrink):
[ArriveBody [2,3,4] 10,Disconnect]
exception on Disconnect: ExceptionInLinkedThread "ThreadId [1]" (LeiosDbWriteException {writeJob = "WriteEbBody (10, ff3e90aa6fbe36b630f58be98da978061e94c0a998da2376f0b4abf615082391)", submittedFrom = "CallStack (from HasCallStack):\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic", writeFailure = LeiosDbException {errorMessage = "writeEbBody: point not registered (programmer error)", callStack = "CallStack (from HasCallStack):\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory"}})
Use --quickcheck-replay="(SMGen 10024954925442282638 16722750794533007117,3)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/the fetch logic never requests an already-held EB body/' to rerun this test only.
a concurrent offer, announcement and body arrival keep the reverse index in sync (IOSimPOR): FAIL
*** Failed! Falsified (after 1 test):
Schedule control: ControlDefault
No thread delayed
Failure: FailureException (ExceptionInLinkedThread "RacyThreadId [2,2,1]" (LeiosDbWriteException {writeJob = "WriteEbBody (12, 47f6c6404a56ea658d0b40e31c47eba73beaad3d3d71a5e61fc0e90ea48d6d99)", submittedFrom = "CallStack (from HasCallStack):\n resolved, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:197:11 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory\n writeEbBody, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/Common.hs:135:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.Common\n a use of `writeEbBody', called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoLogic.hs:1112:22 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoLogic", writeFailure = LeiosDbException {errorMessage = "writeEbBody: point not registered (programmer error)", callStack = "CallStack (from HasCallStack):\n throwLeiosDbException, called at ouroboros-consensus/src/ouroboros-consensus/LeiosDemoDb/InMemory.hs:297:5 in ouroboros-consensus-3.0.1.0-inplace:LeiosDemoDb.InMemory"}}))
Use --quickcheck-replay="(SMGen 7877884416199475454 17480834384350021,0)" to reproduce.
Use -p '/LeiosDemoLogic.Invariants/&&/a concurrent offer, announcement and body arrival keep the reverse index in sync (IOSimPOR)/' to rerun this test only.
8 out of 22 tests failed (0.01s)
```
-Wno-x-partial only exists from GHC 9.8 and foldl' is only in the Prelude from GHC 9.10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New declarations were inserted between existing haddocks and what they documented, so the comments attached to the wrong thing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
HasLeiosVoting keeps only getLeiosCommitteeFromView for the committee; leiosCommitteeOfTip reads a ledger state through ledgerViewOfTip, so the state- and view-derived committees are one definition. LeiosMissingThreshold becomes unreachable and goes. Also from review: minCertificationSlot takes the ledger view instead of three bare durations, the deployed testnet's exception to the announcement size limit is dropped ahead of the respin, and focusElection gets its haddock back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A body arriving twice before its write settles is written twice, and each write notifies AcquiredEb, so the server offered the body twice and the honest client disconnected with ExnLeiosRepeatedOffer. This is the Leios ThreadNet "late join" failure reported by Pascal Grange: #2282 (comment) The server now remembers per announced point what it offered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Translating the Conway ledger view invented the Leios fields, including a maximum EB size of 0, so a node whose selection or immutable tip was still in Conway rejected every announcing Dijkstra header. Translate the ledger state instead, as the hard fork combinator does when ticking across the boundary, and forecast with Dijkstra's rules. The new ThreadNet test forks into Dijkstra at epoch 1, which no Leios test did. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rt-up The keep-set held the selection's blocks but not its anchor, which the VolatileDB still holds. A CertRB there was forgotten, so its successor's copy to the ImmutableDB could not find the EB it announced to promote. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3132c9d to
36adfc6
Compare
|
Rebased and had a 🧞 fix several things:
|
ch1bo
left a comment
There was a problem hiding this comment.
Rebased onto leios-prototype and addressed my remaining comments. I have six commits on top, ready to push to this branch:
consensus-test: build with GHC 9.6— this is the 9.6.7 CI failure (-Wno-x-partial), plus afoldl'import 9.6 would have hit nextPut orphaned haddocks back on their declarationsLeios: derive the committee from the ledger view onlyLeiosNotify server: offer each part of an EB at most once per peer— fixes the "late join" failure @pgrange reportedForecast Dijkstra views from Conway with Dijkstra's Leios parametersChainDB: keep the immutable tip's cert when forgetting CertRBs at start-up
Must: the cross-era forecast. Forecasting Dijkstra from a Conway state made up the Leios fields, including a max EB size of 0. The testnet kludge masked it. Without the kludge, a node whose selection or immutable tip is still in Conway rejects every announcing Dijkstra header and drops honest peers via LeiosNotify. No Leios ThreadNet crossed that boundary (they all fork at epoch 0), so there is now one that forks at epoch 1. It fails without the fix. This matters for the musashi respin and other hard-forks.
Could you also update the PR description? It still says batchRetrieveTxs takes a byte budget, but we dropped that.
TODO Downstream: LeiosMissingThreshold is gone, so the cardano-node commit needs to drop its two clauses in Tracers/ChainDB.hs.
| -- ledger's value stalls the sync there. Exception granted here and | ||
| -- here only: every other use of the limit, and the genesis file | ||
| -- itself, are untouched. | ||
| maximum' = max 200000 maxEbBodySize |
There was a problem hiding this comment.
Dropped. It was also hiding a real bug: the Dijkstra view forecast from Conway had a max EB size of 0. That's fixed in "Forecast Dijkstra views from Conway with Dijkstra's Leios parameters" (see the review summary).
| -- | ||
| -- The ChainDB instantiates all the various type parameters of these databases | ||
| -- to conform to the unified interface we provide here. | ||
| -- | What the caller knows about the block's predecessor |
There was a problem hiding this comment.
Fixed, and three more of the same kind (praosSharedBlockForging, test_multipleSlotsSameHash and the LedgerSupportsProtocol class).
| minCertificationSlot :: | ||
| SlotLength -> | ||
| -- | Announcement period length | ||
| Milliseconds32 -> | ||
| -- | Vote period length | ||
| Milliseconds32 -> | ||
| -- | Diffusion period length | ||
| Milliseconds32 -> | ||
| -- | Slot of the announcing block | ||
| SlotNo -> | ||
| SlotNo |
There was a problem hiding this comment.
Done. It now lives next to its only caller in Protocol.Leios and takes the PraosWithLeios ledger view. certificationGapOfPeriods stays the shared core with minCertificationGap.
| -- 'Nothing' if the protocol parameter does not yet exist on the current era. | ||
| getCurrentThreshold :: LedgerState blk EmptyMK -> Maybe Weight | ||
|
|
||
| -- | The committee and quorum threshold according to a /forecast/ ledger view |
There was a problem hiding this comment.
Yes, retired; see the thread below.
| (shouldPersist, bodyClass, mempoolIngest, missedBoth, fills) <- MVar.modifyMVar outstandingVar $ \outstanding -> do | ||
| let tooOld = point.pointSlotNo < Leios.acquiredEbBodiesPrunedSlot outstanding | ||
| let tooOld = point.pointSlotNo < Leios.outstandingPrunedSlot outstanding | ||
| novel = not $ maybe False Leios.ebStateHasBody (Map.lookup ebHash (Leios.ebState outstanding)) |
There was a problem hiding this comment.
Should: mark the body write as in flight, so a second arrival is not novel.
novel only turns false once the async settle runs acquireEbBody. A second delivery of the same body in that window gets written again, and each writeEbBody notifies AcquiredEb. That is what made the server repeat its offer in the "late join" failure. I guarded the server side (offer each part once per peer), but the double write is still there. On SQLite it also resets missingTxCount and can re-notify AcquiredEbTxs for a complete closure (sql_init_missing_tx_count has no IS NULL guard).
The comment above ("keyed off the lock, two peers delivering the same body cannot both write it") and the one on acquireEbBody ("a concurrent offer may redundantly re-fetch") contradict each other. Which one do we want?
…wire limit The fixture set maxEndorserBlockReferencesSize to 1 MiB, above the 512 KiB maxLeiosEbBytesSize, so guardLeiosWireLimit refused to start the mempool and the replay test failed. 100,000 is what the testnet and proto-devnet genesis files use, and leaves ample room for the 80 KiB of transactions the fixture's endorser blocks hold. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Merging despite the "late join" threadnet tests timing out. I saw various seeds succeed and they might have regressed in their overall lifetime. But also don't want to hold up integration into the prototype any longer - we need to soak it before we can cut a release again. |
Closes all of input-output-hk/ouroboros-leios#1074 subissues.
Also introduces committee-via-forecasting infrastructure that I suspect Issue input-output-hk/ouroboros-leios#1073 should use for validating votes.
This development lead to these two upstream PRs:
LedgerViewdirectly accompanies the block when it arrivesWhen this PR merges, we'll need to add a few small commits to downstream repos:
cardano-apineedsd9bf166c7 - (HEAD -> nfrisby/integrate-PraosWithLeios, origin/nfrisby/integrate-PraosWithLeios) Consensus: change Dijkstra to PraosWithLeioscardano-nodeneedsaddf5beb1 - (HEAD -> nfrisby/leios-issue-1074-omnibus, origin/nfrisby/leios-issue-1074-omnibus) Add ChainDB tracers for forgetting CertRBs at start-up(and its handful of predecessors)ouroboros-leiosneeds99179913e - (HEAD -> nfrisby/integrate-LeiosMinOfferLead, origin/nfrisby/integrate-LeiosMinOfferLead) Add appropriate LeiosMinOfferLead to devnets' config filesAnother consequence of merging this PR is that the subsequent prototype will have a different codec for pre-Dijkstra eras than prior prototypes: this PR changes the prototype to (correctly) use the unchanged Praos header codecs for pre-Dijkstra and only introduce the Leios fields as of Dijkstra. Our testnet does not start in Dijkstra. So shipping this PR requires respinning the testnet's chain, and requires all nodes to update their header codecs in the same way.
Overview of major changes, roughly in commit order:
Issue Validate certificate in a CertRB _before_ ChainSel begins selecting it input-output-hk/ouroboros-leios#1071:
data Praoswithdata BasePraosandtype Praos = BasePraos PextNoneto make room fortype PraosWithLeios = BasePraos PextLeios. Integrate/dispatch to the new upstreamHeaderBodytypes accordingly.LedgerViewtoBlockToAdd, so we can know whichLeiosPointthe block certifies and which Leios committee to use when validating a CertRB's cert in ChainSel's first pass (before we have its predecessor's ledger state).MatchedBlockannotation. In order to convey that info from ChainSync to BlockFetch, we addedhwtPredecessorSlotandhwtLedgerViewOfPredecessor(which makesHeaderWithTimea misnomer, but I left that as a TODO to fix, for now).setTentativeHeader(ie "block diffusion pipelining") if the block it's trying to select just arrived (ie arrived "in order" and after the EB it certifies, if any) (seeTentativeHeaderPermission)precheckLeiosCert). Because the correspondingLedgerViewis immediately available (see above), that certificate can always be validated.Issue Bound bookkeeping of EB offers that arrive via ChainSync input-output-hk/ouroboros-leios#1069
Issue Enable the recovery path in LeiosFetch logic input-output-hk/ouroboros-leios#1070
isCertifiedEbcan answer whether an announcement is certified, andfocusCertifiedEbmoves a verified certificate's election onto the EB it names — which is what lists that second body to fetch, if we had been mislead by an equivocation. We also disconnect from peers that claim committee equivocation; those claims arrive over ChainSync rather than LeiosNotify.initNodeKernel. Some of the subsequent enrich it in order to also test their cases while increasing the node-under-test's fidelity.valid_claims_startup.md.Issue Require announcements precede offers and tighten LeiosFetch logic input-output-hk/ouroboros-leios#1082
Issue BUG: the prototype relays EBs that lied about their tx's sizes input-output-hk/ouroboros-leios#1115
Issue Disconnect if a peer requests an EB body or EB closure that we don't have input-output-hk/ouroboros-leios#1083 and Issue Abort a LeiosFetch request processing if it turns out to be too many bytes input-output-hk/ouroboros-leios#1124
batchRetrieveTxstakes a byte budget, so the LeiosFetch server aborts if it realizes a request is for more bytes than an honest client would put in a single request.maxLeiosEbBytesSize/maxLeiosTxsRequestBytesSizewere corrected to be the design ceilings,dropping
maxTxsPerEbfrom 71,428 to 14,979.Issue Integrate LeiosFetch with Genesis's ChainSync Jumping (CSJ) input-output-hk/ouroboros-leios#1072
CSJ.leiosJumpAcceptedCallbackconfig field.maxAcceptedJumpSlotfield toLeios.PeerVarsto limit this processing to just the headers that weren't also part of previous jumps.