Enforce configurable maximum ledger transaction size - #7992
Enforce configurable maximum ledger transaction size#7992Amaury Chamayou (achamayou) with Copilot wants to merge 20 commits into
Conversation
Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
…nsaction_size_limit e2e test - clang-tidy (modernize-use-nodiscard) required get_max_transaction_size() to be marked [[nodiscard]], matching the convention already used for other const getters in AbstractStore. - The transaction_size_limit e2e test configured ledger.max_transaction_size to 20KB before starting the network, which is smaller than the constitution scripts written to the KV store during service creation, causing genesis to fail. Raise the configured limit to 512KB (comfortably above the genesis transaction size) and the oversized test payload to 1MB so the 413 path is still exercised.
|
Copilot look at the CI failure during documentation build, and make the necessary fix. |
Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
|
Copilot resolve the merge conflicts in this pull request |
…gurable-max-transaction-size # Conflicts: # src/kv/generic_serialise_wrapper.h Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
- Exempt snapshots from the per-transaction limit (serialise and deserialise) and add a regression test; snapshots capture whole-store state and may legitimately exceed a single-transaction cap. - Move the CHANGELOG entry from the released 5.0.0 section to 7.0.7 (Changed), fix 'serialized' -> 'serialised', and reference PR #7992 instead of tracking issue #7488. - Rename describe_serialised_entry_size_error to British spelling to match the surrounding code. - Document that historical-query stores deliberately do not apply the limit (read-only reconstruction). - Add a Store::set_max_transaction_size out-of-range validation test. - Add missing trailing newline to serialised_entry_format.h.
- Exempt all deserialisation from the size cap; only serialisation of new transactions is capped. Remove the limit parameter and check from the deserialiser init, the abstract deserialiser interface, deserialise_views, get_entry, and both recovery loops; drop recovery_store's limit. Buffer- bounds safety checks (header size vs buffer, get_entry truncation) remain. - Apply the cap to the whole serialised ledger entry (fixed 8-byte header plus body) instead of just the body. - Update the error message, CHANGELOG, and host config schema description; remove a stale get_entry doc comment. - Replace the deserialise-rejection unit test with a deserialise-exemption test.
…-size Resolve conflicts in doc/host_config_schema/host_config.json (take main's formatting and re-add max_transaction_size) and src/kv/test/kv_serialisation.cpp (keep both sets of includes). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Enforcing the limit during serialisation meant the transaction had already taken a version and mutated the maps, so it could only be undone by calling Store::rollback. That is unsafe: apply_changes notes that other non-conflicting transactions may commit at later versions concurrently, so rolling back to the pre-apply TxID can discard their changes, and Store::rollback also clears pending_txs and bumps rollback_count. Project the exact size of the ledger entry before apply_changes instead, by serialising the change sets (which apply_changes does not modify) through a serialiser that only measures them. An oversized transaction is then rejected without the store having been touched, so it neither writes a value nor stops later transactions. The limit is still checked when serialising, but as a fatal KvSerialiserException, matching how other post-apply serialisation failures are treated. Use a distinct TransactionTooLarge error code, so the rejection cannot be confused with the HTTP parser's RequestBodyTooLarge, which is also a 413 and closes the session. Move the CHANGELOG entry to the current version, under Added. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Serialise transaction domains once before applying changes, reuse the retained bytes after assigning the transaction version, and exempt reserved signature transactions from the configurable cap. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a configurable ledger transaction-size limit to prevent oversized allocations and reject excessive writes before store mutation.
Changes:
- Adds
ledger.max_transaction_size, documentation, andTransactionTooLarge. - Pre-serialises transactions for size validation while exempting signatures and snapshots.
- Adds ledger-read bounds checking and unit/e2e coverage.
Custom instructions used
.github/copilot-instructions.md.github/instructions/changelog.instructions.md.github/instructions/reviewing.instructions.md
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/limits.py |
Adds transaction-limit e2e coverage. |
tests/infra/network.py |
Forwards the new test argument. |
tests/infra/e2e_args.py |
Adds the ledger-limit CLI option. |
tests/config.jinja |
Emits the new configuration field. |
src/node/rpc/frontend.h |
Maps oversized transactions to HTTP 413. |
src/node/historical_queries.h |
Documents historical-query exemption. |
src/kv/test/kv_snapshot.cpp |
Tests snapshot exemption. |
src/kv/test/kv_serialisation.cpp |
Tests limits, boundaries, and exemptions. |
src/kv/store.h |
Stores and validates the configured limit. |
src/kv/serialised_entry_format.h |
Defines representable entry limits. |
src/kv/raw_serialise.h |
Adds size estimation and field patching. |
src/kv/kv_types.h |
Adds the exception and store API. |
src/kv/generic_serialise_wrapper.h |
Enforces incremental size accounting. |
src/kv/committable_tx.h |
Pre-serialises before applying writes. |
src/enclave/main.cpp |
Passes configuration into the enclave. |
src/enclave/enclave.h |
Applies the limit to the store. |
src/consensus/ledger_enclave.h |
Validates ledger entry bounds. |
src/common/configuration.h |
Adds JSON serialisation support. |
include/ccf/odata_error.h |
Adds TransactionTooLarge. |
include/ccf/node/startup_config.h |
Defines the 100 MB default. |
doc/host_config_schema/host_config.json |
Documents the configuration schema. |
CHANGELOG.md |
Records the user-facing behavior. |
Suppressed comments (1)
src/consensus/ledger_enclave.h:35
- This validation is bypassed for entries in an already-deserialised prefix:
raft.h:1308-1311callsLedgerEnclave::skip_entrydirectly and outside thestd::logic_errorcatch aroundget_entry.skip_entrystill trusts the header and throwsserialized::InsufficientSpaceExceptionfor a truncated body, so malformed duplicate AppendEntries can escape rather than being rejected with a NACK. Apply equivalent validation and exception handling to the skip path.
// The size in the entry header is not trusted: check it against the
// buffer we were given before allocating. This is distinct from the
// configured max_transaction_size, which applies only when serialising
// new transactions, so that entries written under a larger or unset
// limit can always be read back.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Cover exact size enforcement with a real encryption header and mixed public/private writes, and satisfy clang-tidy by initializing the serialised version offset in the constructor initializer list. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reject truncated ledger headers and bodies consistently on read and skip paths, NACK malformed duplicate entries, and use checked integer scaling for size strings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Measure transaction entries without materialising bytes before conflict detection, serialise only successful writes, align the transaction default to 64MB, and preserve ring-buffer response headroom with a 65MB message default and startup validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
DescriptionComparing 3 available runs from this branch (#7992) against the trend of the last 30 Each chart plots every benchmark as an axis, with values normalized so 100 is the EWMA baseline of recent Axis labels show the latest branch value and its difference from the main EWMA baseline, where 0% is on the baseline. They are coloured green where the latest run improves on the baseline, red where it regresses, and grey where the difference is within one std dev of the baseline (within noise). Higher is better for throughput and rate, lower for latency and memory. Throughput (tx/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-6{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#808A94!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#808A94!important}
.radarAxisLabel:nth-of-type(7){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 67,816 tx/s ▬ +2%"]
axis b1["Basic Blocking: 1,022 tx/s ▬ +1%"]
axis b2["Basic JS: 4,700 tx/s ▬ -1%"]
axis b3["Basic Multi-Threaded: 85,478 tx/s ▬ 0%"]
axis b4["Historical Queries: 212,382 tx/s ▬ +4%"]
axis b5["Logging: 63,529 tx/s ▬ +3%"]
axis b6["Logging JWT: 10,370 tx/s ▬ +3%"]
curve stddev2_high["main EWMA + 2 std dev"]{106.27, 103.47, 106.15, 109.12, 109.30, 107.90, 107.72}
curve stddev1_high["main EWMA + 1 std dev"]{103.14, 101.74, 103.08, 104.56, 104.65, 103.95, 103.86}
curve stddev1_low["main EWMA - 1 std dev"]{96.86, 98.26, 96.92, 95.44, 95.35, 96.05, 96.14}
curve stddev2_low["main EWMA - 2 std dev"]{93.73, 96.53, 93.85, 90.88, 90.70, 92.10, 92.28}
curve branch_0["#7992 (2 runs earlier)"]{103.31, 100.81, 103.26, 100.20, 102.58, 96.37, 98.58}
curve branch_1["#7992 (1 run earlier)"]{92.30, 98.66, 98.77, 89.41, 90.94, 102.43, 102.13}
curve branch_2["#7992"]{101.68, 100.85, 98.84, 100.33, 103.53, 102.64, 102.96}
graticule polygon
max 117
min 82
ticks 0
showLegend false
Latency (ms)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-6{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Commit Latency 16ms: 4.36 ms ▬ -26%"]
axis b1["Commit Latency 1ms: 1.96 ms ▬ +4%"]
axis b2["Commit Latency 256ms: 204 ms ▬ 0%"]
curve stddev2_high["main EWMA + 2 std dev"]{169.45, 110.36, 101.91}
curve stddev1_high["main EWMA + 1 std dev"]{134.72, 105.18, 100.95}
curve stddev1_low["main EWMA - 1 std dev"]{65.28, 94.82, 99.05}
curve stddev2_low["main EWMA - 2 std dev"]{30.55, 89.64, 98.09}
curve branch_0["#7992 (2 runs earlier)"]{89.45, 103.14, 99.59}
curve branch_1["#7992 (1 run earlier)"]{68.42, 105.69, 99.65}
curve branch_2["#7992"]{74.42, 103.51, 99.61}
graticule polygon
max 219
ticks 0
showLegend false
Memory (bytes)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-6{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#E5484D!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 82.5 MiB ▼ 5%"]
axis b1["Basic Blocking: 71.6 MiB ▬ 0%"]
axis b2["Basic JS: 71.3 MiB ▬ +1%"]
axis b3["Basic Multi-Threaded: 87.9 MiB ▼ 1%"]
axis b4["Logging: 76.2 MiB ▬ 0%"]
axis b5["Logging JWT: 69.4 MiB ▲ 1%"]
curve stddev2_high["main EWMA + 2 std dev"]{106.88, 100.58, 103.40, 102.09, 101.51, 101.58}
curve stddev1_high["main EWMA + 1 std dev"]{103.44, 100.29, 101.70, 101.04, 100.75, 100.79}
curve stddev1_low["main EWMA - 1 std dev"]{96.56, 99.71, 98.30, 98.96, 99.25, 99.21}
curve stddev2_low["main EWMA - 2 std dev"]{93.12, 99.42, 96.60, 97.91, 98.49, 98.42}
curve branch_0["#7992 (2 runs earlier)"]{99.53, 100.15, 100.23, 101.12, 100.17, 99.41}
curve branch_1["#7992 (1 run earlier)"]{96.10, 100.09, 100.09, 103.19, 98.96, 100.59}
curve branch_2["#7992"]{94.67, 100.05, 100.58, 98.70, 100.12, 100.87}
graticule polygon
max 112
min 88
ticks 0
showLegend false
Rate (ops/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-6{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["CHAMP get: 37,840,435 ops/s ▬ 0%"]
axis b1["CHAMP put: 5,456,270 ops/s ▬ 0%"]
axis b2["KV deserialisation: 1,645,007 ops/s ▬ +2%"]
axis b3["KV serialisation: 1,354,830 ops/s ▼ 6%"]
axis b4["KV snapshot deserialis...: 4,186 ops/s ▬ +1%"]
axis b5["KV snapshot serialisation: 4,668 ops/s ▬ +4%"]
curve stddev2_high["main EWMA + 2 std dev"]{106.51, 106.12, 105.71, 104.65, 105.75, 111.77}
curve stddev1_high["main EWMA + 1 std dev"]{103.26, 103.06, 102.86, 102.32, 102.87, 105.89}
curve stddev1_low["main EWMA - 1 std dev"]{96.74, 96.94, 97.14, 97.68, 97.13, 94.11}
curve stddev2_low["main EWMA - 2 std dev"]{93.49, 93.88, 94.29, 95.35, 94.25, 88.23}
curve branch_0["#7992 (2 runs earlier)"]{100.90, 100.27, 101.06, 95.46, 103.03, 101.55}
curve branch_1["#7992 (1 run earlier)"]{97.97, 98.12, 102.40, 93.39, 101.43, 96.89}
curve branch_2["#7992"]{99.92, 100.12, 101.56, 93.90, 101.04, 104.40}
graticule polygon
max 121
min 79
ticks 0
showLegend false
|
Replace the obsolete expectation that an oversized transaction terminates a node with assertions for TransactionTooLarge, continued node health, and a successful subsequent commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Closes #7488.
Ledger entries store their body size in a 6-byte field, with no further bound, so malformed input could trigger an out-of-bounds read or an excessive allocation. This adds a configurable write-time bound and rejects oversized transactions before they mutate the KV store.
Configuration
New
ledger.max_transaction_size, defaulting to64MB:The limit covers the complete ledger entry: the fixed 8-byte entry header, ledger encryption header, public-domain size field, public domain and encrypted private domain. Size strings now use checked integer scaling, so values that cannot fit in
size_tfail deterministically during configuration parsing.The default
memory.max_msg_sizeis increased to65MB, leaving sufficient ring-buffer and ledger-range response headroom for a maximum-sized transaction. Startup validation rejects custom configurations unlessledger.max_transaction_sizeis at least 2048 bytes smaller thanmemory.max_msg_size.Reserved transactions are used solely for internal signatures and remain deliberately exempt: they must fill their pre-reserved ledger version, so rejecting one would create a permanent sequence hole. Snapshots are also exempt because they represent accumulated state rather than a single transaction.
Enforcement
Writes. Before
apply_changes(), each non-reserved transaction is walked with aSizeWriterthat performs no allocation or byte copying. It uses the same per-field size implementation asRawWriter, includes encryption and fixed-entry overhead, and rejects entries above the configured maximum with413/TransactionTooLargewhile the store is unchanged.Conflict detection then runs normally. Conflicting transactions discard only the scalar size accounting; they never materialise serialised public/private domains. After a successful apply assigns the transaction version, the write set is serialised and encrypted once with the final version and commit-evidence digest. Debug builds assert that the projected and actual entry sizes agree, while the final serialiser check remains a fatal safeguard for post-apply invariant failures.
get_header_length()is intentionally not cached. After separating sizing from serialisation it is called once by each short-lived wrapper, so caching offers no meaningful hot-path benefit and would introduce state whose validity depends on the encryptor implementation.TransactionTooLargeis distinct from the HTTP parser'sRequestBodyTooLarge, which also uses status 413 but closes the session.Reads.
LedgerEnclave::get_entryandskip_entryshare bounds validation for both the fixed header and claimed body size. Malformed new or duplicate AppendEntries are rejected withstd::logic_error, which Raft catches and answers with a NACK, rather than reading beyond the supplied buffer or allocating from an untrusted size.Deserialisation, historical queries and snapshots are not subject to the configured transaction limit. Entries written under a larger or previously unset limit therefore remain readable during recovery and historical access.
Testing
RawWriter/SizeWriteragreement, reserved-signature and snapshot exemptions, deserialisation exemption, commit-evidence metadata, checked size-string overflow, and unrepresentable configured values.tests/limits.pyassertsTransactionTooLarge, keeps the payload belowmax_http_body_size, and confirms rejection writes nothing and does not prevent later transactions.