Commit a9d1790
committed
fix(eth,consensus): gate the proposed-block handler on canonicality and storage
importBlockResults handed the tail of every batch to the proposed-block
handler as long as InsertChain returned nil. A nil error does not mean
the tail is canonical: a fork batch is written as side-chain entries, and
a parked tail is not written at all. The engine did not make up for it:
processQC updates highestQuorumCert, lockQuorumCert and the commit block
before its own existence check, and an existence check cannot tell a
reorged-away block from a canonical one, because the fork stays in the
database. The fetcher and the miner call the same handler and had no gate
at all. The miner is not gated separately: it hands the *core.BlockChain
it already holds to the shared handler, so the engine's canonicality and
storage re-checks cover that path too — a block WriteBlockWithState files
as a side-chain entry is skipped, and no processQC or vote runs on it.
Canonicality is not enough on its own either: it is a property of
the header, and the fast sync header phase marks a height canonical
before its body lands, so a node could processQC and vote for a block
that only ever existed as a header. A master node could therefore end up
voting for, and committing state against, a block it had just reorged
away.
Judge the block once, in one place. consensus.ShouldHandleProposedBlock
reports whether a header is the canonical block at its height and whether
its body is stored, together with the reason it must be skipped and the
canonical hash at that height, both for the skip log. It takes a minimal
CanonicalChain (GetHeaderByNumber alone) that both consensus.ChainReader
and the downloader's BlockChain satisfy, so the callers cannot drift into
two diverging judgments; on the downloader side that costs one addition of
GetHeaderByNumber to its BlockChain interface. The storage half goes
through the optional capability interface consensus.BlockStorer, whose
single method is HasBlock rather than GetBlock: only existence matters,
and GetBlock would read and RLP-decode the whole body of every imported
block. core.BlockChain implements BlockStorer — a compile-time assertion
keeps it from dropping off the type — while a HeaderChain deliberately
does not, so handing a header-only chain to the judgment fails it loudly
as the SkipUnjudgeable skip reason instead of every block silently
failing as not stored. The judgment has no error channel: every outcome
is a skip reason, so each caller collapses to a single !ok branch. The
BlockStorer assertion runs before any chain read, so the unjudgeable
skip is reported at every height: a header-only chain must not get a
silent "not stored" from the canonicality half for the heights it
happens to answer.
The downloader gates importBlockResults on that judgment instead of
open-coding the same rule out of HasBlock and GetCanonicalHash, and logs
the skip with its reason and the canonical hash it observed. The v2 engine
re-checks the same judgment twice inside ProposedBlockHandler: in front of
processQC, and again right before sendVote, because x.lock serialises the
handler but not InsertChain, so a reorg can still land between the two
checks. A judgment that cannot run — a chain type that lost the
BlockStorer half to wrapping or replacement — surfaces as the
SkipUnjudgeable skip, which the handler logs and returns as nil: the
fetcher's import loop treats any handler error as an import failure and
would suppress the broadcast of an already imported block, so a skip
must never surface as an error. That skip means the node stops
processing QCs and voting outright — in production the chain always
implements BlockStorer, so it is a wiring bug. The judgment itself counts
every such skip in a metrics counter (consensus/unjudgeable-proposed-block)
at the single return all callers share, so the liveness halt is observable
beyond the logs on every call site: the downloader's pre-filter hits the
same return before its handler would ever run, where an engine-side-only
counter would stay silent exactly where the wiring bug is easiest to trip.
consensus.SkipLogLevel grades the skips by reason:
SkipUnjudgeable is the one Error — not an observation about the block but
a wiring bug that will skip every block — SkipNonCanonical is a genuine
reorg race and stays at Warn, while the sync-phase skips are routine and
stay at Info so they do not drown the level in noise. The empty reason
grades Warn too: it means a caller logged a skip for an accepted block, a
pure misuse that should surface, not hide at Info. The downloader's own
skip log stays at Info on purpose: it is the pre-filter for the routine
cases and correctness rests on the handler's re-checks, so grading it too
would fire a Warn for every fork tail of a sync. That grading now lives in
consensus.PreFilterSkipLogLevel, right next to SkipLogLevel and the
SkipReason constants, instead of an open-coded switch inside the
downloader: both graders sit in one file, and each grades an unregistered
reason as Warn so a forgotten registration surfaces. The downloader's
pre-filter has no dedicated unjudgeable branch — that path is statically
unreachable there, and if an interface change ever makes it reachable,
the table grades SkipUnjudgeable Error at that call site too.
The engine's two re-checks now share one counter of their own
(consensus/skipped-proposed-block), incremented at the gate that declined
the block — at most once per handler invocation, since a first-gate skip
returns before the second gate; the same block judged once through the
fetcher path and once through the downloader path is counted once per
path. It deliberately counts only the engine gates and
not the downloader's pre-filter: fork tails arriving there are routine
sync noise logged at Info, while a block that got past the pre-filter and
was still declined is exactly the anomaly logs alone could bury. The
reason stays in the skip log; one counter for all reasons keeps the
metric surface flat. And because the same proposal can reach the shared
judgment through both the fetcher and the downloader paths, the
unjudgeable counter's increments are judgment counts, not deduplicated
blocks — its comment says so now. The window comment also names what the
second check leaves open: sendVote itself reads the chain again via
getEpochSwitchInfo before signing and broadcasting, so the residual
window spans that read plus the signature and the broadcast. The fetcher
gate's comment now cross-references the engine side — canonicality is
covered there, so the two gates interlock rather than duplicate — and
points at the post-pivot full import in processFastSyncContent, the
downloader call site the ungated closure's justification rests on.
The fetcher is the one caller whose fast sync calls must not reach the
handler at all. While snapSync runs, it discards propagated blocks before
executing them, so a body already written by the fast sync receipt phase
would pass both halves of the judgment — canonical and stored — and drive
processQC and the vote path on a block whose state transition was never
validated. Its callback therefore carries the same snapSync guard the
inserter and prepare closures already have, instead of asking the
judgment a question the judgment cannot answer. The guard sits in a
fetcher-specific wrapper and not in the shared closure: the same closure
also feeds the downloader, whose fast sync handler calls run after the
pivot commit, on blocks InsertChain has fully executed, and snapSync
stays set until the whole Synchronise returns. Behind that flag the
callback also requires the block's executed state (HasBlockAndExecutedState:
GetBlock plus an open state trie) rather than the state root alone: its
GetBlock half is keyed by the block hash, so a discarded import — whose
body was never stored — fails there, before the root-keyed OpenTrie half
could leak a parent's executed state to an empty block whose root repeats
it. The two halves are documented as such: the hash-keyed gate half and
the root-keyed state half each do their part. Unlike the judgment's storage half — which reads
HasBlock to avoid decoding whole bodies — this check pays a GetBlock body
read plus OpenTrie: proposed blocks arrive at consensus cadence, orders
of magnitude rarer than sync-path blocks, so the fuller check is
affordable here and closes the unexecuted-state hole the cheap HasBlock
check would leave open. It deliberately stops short of
HasBlockAndFullState: that method also demands the XDCX trading and
lending state pieces at TIPXDCX heights, and a correctly executed block
missing one of them — a state the node cannot re-derive by reprocessing —
would fail it forever, permanently halting QC processing and voting with
no self-healing path. Completeness of the auxiliary state is an
observation for the trading/lending services, not a voting precondition.
A nil header or nil number fails the same SkipNilHeader guard the shared judgment applies — this closure dereferences the header before any chain read, so it keeps that caller-bug contract instead of panicking — logging Error through consensus.SkipLogLevel, consumed rather than registered so the grading table stays fetcher-free, and without a counter of its own. Its skips are graded by what they mean: the
fast-sync flag skip is routine and stays at Debug, but a state-half skip
still halts QC processing and voting for that block, so it logs at Warn
and increments its own counter (eth/skipped-proposed-block-state)
— the same logs-alone-could-bury-it discipline as the unjudgeable
counter, so a persistent post-sync liveness stall is observable beyond
the logs. Its comment now also names the counter as the sentinel to
alert on: sustained growth means a QC/voting stall, not a transient
race, while trading/lending state completeness is no longer a voting
precondition at all.
The split of storage-check strengths across the three gates — the shared
judgment and engine gates on HasBlock, the fetcher gate on
HasBlockAndExecutedState — is now documented as such in the judgment's own
doc comment: each path is self-consistent (miner/fetcher run on a tip
with full state, the downloader reaches the handler only after the
post-pivot full import), and a future caller with neither guarantee must
add a stronger gate of its own rather than weaken these. The second
engine gate's comment and the vote-drop test's state anchor both state
why the processQC state write is retained, not rolled back, when a block
is reorged away between the gates: the block's embedded QC certifies its
parent, and that certification stays valid after the reorg — only the
vote for the reorged block must go. The fetcher's gated callback is a free function the tests can construct
instead of a test-only accessor on the fetcher.
Tests:
- TestShouldHandleProposedBlock covers the outcomes of the shared
judgment plus the nil-header and nil-number caller-bug skips and the
header-only chain at an absent height, whose
unjudgeable skip is the point of the entry-side assertion; the two
storerless cases also pin that the judgment increments
unjudgeableProposedBlock exactly once per skip, and
TestHeaderChainShouldHandleProposedBlock pins that the header chain's
interface stubs never pass it at any height.
- TestProposedBlockHandlerSkipsNonCanonicalBlock,
TestProposedBlockHandlerSkipsReorgedBlockBeforeProcessQC,
TestProposedBlockHandlerDropsVoteForReorgedBlock and
TestProposedBlockHandlerSkipsBlockWithoutBody cover the two engine
re-checks: highestQuorumCert, lockQC, the timeout certificate, the
voted round and the commit block must stay untouched and no vote may be
broadcast. The two reorg tests inject the reorg through a ChainReader
wrapper that serves a fork header once N judgment gates have completed —
anchored on the gate boundary, where each gate that passes the
canonicality check ends in exactly one HasBlock of the watched height —
so reads added in front of or between the handler's gates stay truthful
and cannot move where the injection lands, instead of coupling the test
to the handler's read ordinals.
- TestProposedBlockHandlerGradesSkipLogLevelByReason pins the log level
of each skip reason.
- TestProposedBlockHandlerSkipsUnjudgeableChain feeds the handler a
ChainReader wrapper without the BlockStorer half — the shape a wrapped
or replaced chain type presents — and pins that the chain skips as
SkipUnjudgeable, logged at Error and returned as nil, not surfaced to a
caller that would treat it as an import failure. No vote may be
broadcast either: that assertion waits out a two-second timeout window
rather than checking BroadcastCh non-blockingly, because a vote travels
through broadcastToBftChannel's own goroutine and an immediate
default-branch check would pass vacuously even if a vote were sent.
- TestFetcherWiresNoopHandlerWithoutConsensus pins the non-XDPoS
wiring: without a consensus engine the fetcher handler is an explicit
no-op that logs nothing in either sync mode, and it is still wired
non-nil as the literal argument handed to fetcher.NewBlockFetcher.
- TestFetcherProposedBlockHandlerGates exercises the gated closure
directly: with snapSync set it is inert and logs the skip before
reaching the handler, the same call in full sync reaches the handler,
and the state-half skip is pinned with a block forged for real: its
header and body are written straight into the database via rawdb so
the hash-keyed GetBlock half finds it, while its root exists nowhere
in the state database so the state half — the OpenTrie of
HasBlockAndExecutedState — is the part that fails — mutating the current header's root alone would change its hash
and fail at the GetBlock half instead, never exercising the state
check — and the counter's one increment per skip is asserted.
A nil header and a nil number fail the SkipNilHeader guard: no panic, no handler call, an Error log and no counter movement.
- TestImportBlockResultsProposedBlockHandler (with its helpers it lives
in eth/downloader/downloader_proposed_block_test.go) covers six
downloader shapes: a parked tail, a fully imported batch, a stored fork batch
re-delivered after the local chain grew past it, a head advanced past
the canonical tail by a concurrent import, a heavier fork that stays
canonical, and a fast sync height that is canonical without a body.
- downloadTester gains a canonical number-to-hash table, picked by total
difficulty the way the real chain resolves a reorg, plus hooks to park
a batch tail and to extend it after the insert.
TestStoreBlockCleansStaleCanonicalMarkers,
TestRollbackClearsCanonicalMarkers and
TestInsertChainErrorReportsPosition pin the parts of that table the
downloader tests rely on.
- TestShouldNotSendVoteMsgIfBlockNotExtendedFromAncestor no longer
proposes a forked block, which the new entry re-check now
short-circuits; it proposes a canonical block below the locked ancestor
instead. The fork case moved to
TestShouldNotSendVoteMsgIfCanonicalBlockNotExtendedFromForkedAncestor,
where the parent walk of isExtendingFromAncestor actually runs.
A review pass over the change tightened it further. The skip-log grading
for the downloader's pre-filter moved out of the downloader into
consensus.PreFilterSkipLogLevel, next to SkipLogLevel and the SkipReason
constants, so both graders live in one file instead of relying on a
cross-package comment to stay in sync; the downloader logs its routine
skips through it and its tests pin the Info level end to end.
SkipLogLevel now grades the misused empty reason Warn instead of Info.
The second pre-vote gate's comment covers processQC's remaining state
writes — commitBlocks and setNewRound (the currentRound bump, the
timeoutPool clear and the newRoundCh signal) — and
TestProposedBlockHandlerSkipsNonCanonicalBlock pins that no newRoundCh
signal is emitted, with the state assertions covering the rest.
TestProposedBlockHandlerSkipsNonCanonicalBlock and
TestProposedBlockHandlerDropsVoteForReorgedBlock assert the engine
gates' skipped-proposed-block counter through the metrics registry, and
TestPreFilterSkipLogLevelGradesByReason pins the pre-filter grading at
its definition site. The fetcher wiring checks avoid reflect
function-pointer comparisons — the spec does not guarantee unique
pointers for function values — in favor of behavioral ones: the wired
callback is called and must produce the log its closure emits. Comments across the touched files were
condensed to the reasoning each spot needs; the call-site grading, the
three-gate strength split and the counter semantics keep their one-line
statements.
A second review pass closed the remaining loose ends. The two log graders
now share one mechanism instead of two parallel switches: the
reason-to-level mappings live in maps next to an allSkipReasons list,
and TestSkipReasonRegisteredInBothGraders fails when a new SkipReason is
added without being registered in both of them; the empty and
unregistered reasons fall to a single Warn fallback in each grader,
ending the asymmetry between the two switches' handling of the misused
empty reason. The unjudgeable counter's comment spells out the alerting
overlap: a skip is counted both there and at the engine gates'
skipped-proposed-block counter, so alerts belong on the unjudgeable
counter alone — one halted node would otherwise raise both alerts at
twice the rate. The fetcher's gated callback moved out of
NewProtocolManager into the free function newFetcherProposedBlockHandler
and the test-only HandleProposedBlockForTest accessor on the fetcher is
gone, so the fetcher package carries no test-only exported surface while
the eth package tests keep their behavioral wiring verification against
the field that is literally handed to fetcher.NewBlockFetcher. The
engine tests' counter helper now fails its test instead of panicking
when the metrics registration is missing, and both test files state
that their cases must not run in parallel: they compare global counter
values before and after a call, which parallel cases would race on.
A third review pass tightened the remaining edges. The fast-sync wiring
test's first handler call now asserts a nil error like its three
siblings instead of swallowing it in an empty branch. The two counter comments now state the full alerting matrix
instead of contradicting each other: unjudgeableProposedBlock alone
owns the wiring-bug alert, skipped-proposed-block alone owns the
gate-stall alert, and neither substitutes for the other (a wiring bug
raises both counters, a gate stall raises only skipped-proposed-block).
The residual reorg window between the second gate check and the vote
broadcast is documented intentional design, recorded here as wontfix.
A fourth review pass collapsed the SkipReason registration from three
hand-maintained places into one. The two per-callsite level maps and
the test-only allSkipReasons list are replaced by a single skipGrades
table mapping each reason to a skipGrade struct with named handler and
preFilter fields, so a newly added reason is registered for both
graders by construction and can no longer be missed for one of them;
the graders keep their signatures and the Warn fallback for
unregistered or empty reasons, so grading behavior is unchanged. The
registration test that policed the old two-map layout lost its purpose
and is deleted along with allSkipReasons, and the reason constants'
comment now points at the one table. newFetcherProposedBlockHandler no
longer takes a blockchain parameter duplicating pm.blockchain: the
closure reads pm.blockchain directly, so its full-state gate can never
silently diverge from the chain the manager holds.
A fifth review pass unified the downloader's pre-filter with the
registration table and stopped charging non-XDPoS chains for gates they
never use. The pre-filter's dedicated SkipUnjudgeable branch — statically
unreachable there, since downloader.BlockChain itself requires HasBlock
so every dynamic type satisfies consensus.BlockStorer — is removed:
every skip now flows through PreFilterSkipLogLevel, so the table's
preFilter entry for SkipUnjudgeable has its call site and the
single-registration claim holds at both graders; grading stays Error,
the reason stays in the log fields, and the grader's comment now says
the downloader has no dedicated branch. Without a consensus engine
(config.XDPoS == nil) the manager wires an explicit no-op into the
fetcher instead of the gated closure and hands nil to the downloader,
which guards on nil, so such chains no longer pay HasBlockAndExecutedState's
GetBlock plus OpenTrie — or the shared judgment — per imported block.
MockChainReader gains the minimal shape handler-level tests need to run
consensus.ShouldHandleProposedBlock without the integration package:
AddHeader also records the canonical header per height for
GetHeaderByNumber, and HasBlock reports a registered header as stored
(the mock stores headers only). The fetcher wiring test is split to
follow the new wiring: TestFetcherWiresNoopHandlerWithoutConsensus pins
the no-op an ethash manager gets, and
TestFetcherProposedBlockHandlerGates exercises the gated closure
directly, keeping the fast-sync skip, the full-sync pass-through and
the forged unexecuted-state skip with its counter assertion. A
comment-only pass condensed the unjudgeable counter's alerting note and
the fetcher gate's rationale without touching the alert matrix or the
wontfix reorg-window record.
A sixth review pass removed the XDCX/lending state halves from the
fetcher gate and hardened the judgment against a nil header. The
fetcher's executed-state check now runs HasBlockAndExecutedState — a new
BlockChain method that checks GetBlock plus an open state trie and
deliberately leaves out HasFullState's trading and lending halves —
because a correctly executed block missing one auxiliary state piece
would otherwise fail the old HasBlockAndFullState gate forever,
permanently halting QC processing and voting with no self-healing path;
HasFullState keeps its full semantics for its other callers (block
validation, dry-run import). The strength split — judgment and engine
gates on HasBlock, the fetcher gate on executed state, the full-state
callers outside the gates — is documented at both the gate and the
judgment. ShouldHandleProposedBlock now fails a nil header or nil number
as a new SkipNilHeader skip before any chain read instead of panicking
on the number dereference; the reason is registered in the skipGrades
table at Error for both graders, TestShouldHandleProposedBlock pins both
shapes, and both grader tests pin the Error level. proposed_block.go
carries a file header naming it XDPoS v2 specific, and the second
pre-vote gate's comment now points at the chain-side invariant that
actually protects highestCommitBlock — BlockChain.reorg refuses any
reorg whose common ancestor sits strictly below
GetLatestCommittedBlockInfo and whose dropped branch contains the
committed block, while a fork point exactly at that height is allowed
(the committed block is then the fork point itself, never dropped) —
instead of resting the non-reversion on the QC argument alone. The two-grader/registry/counter structure serving
the 20-line judgment is reviewed and kept: the single skipGrades table
is what keeps a new reason from being registered at one call site and
missed at the other, and renaming or moving the counters is churn
without behavioral gain. The proposed-block downloader tests moved to
their own file, eth/downloader/downloader_proposed_block_test.go, so
downloader_test.go stays near 3200 lines: a pure move of
TestImportBlockResultsProposedBlockHandler and its helpers, verified
referenced nowhere outside the moved block.
Seventh review round, all comment-accuracy fixes with zero behavior
change. The second gate's reorg-invariant comment in engine.go was
rewritten to match what core/blockchain.go's guard actually enforces —
rejection requires a common ancestor strictly below the committed height
and a dropped branch containing the committed block at that hash — since
the earlier "at or below" phrasing contradicted the referenced code at
the cmp == 0 boundary, where a reorg whose fork point is the committed
block itself is allowed. The skipGrades table comment now explicitly
declares the fetcher's fast-sync/state gate (eth/handler.go
newFetcherProposedBlockHandler, counter skippedProposedBlockState)
outside the single-registration table: it produces no SkipReason — it
gates on the snapSync flag and HasBlockAndExecutedState before the
consensus handler runs — and grades Warn with its own counter, so the
table's "covers every reason" claim no longer overreaches. The fetcher
gate comment's "hash-keyed state check" shorthand was corrected: only
the GetBlock half is hash-keyed, the OpenTrie half stays root-keyed, and
the leak it guards against is unreachable because a discarded block has
no stored body; HasBlockAndExecutedState deliberately keeps GetBlock —
retaining the approved stronger guarantee — so its one whole-body decode
per propagated block is the price of the interlocking design, not an
accident.
Eighth review round. The fetcher's gated closure gains the nil guard the shared judgment already models: a nil header or nil number now fails as SkipNilHeader — logged Error through consensus.SkipLogLevel, consumed rather than registered so the grading table stays fetcher-free as the previous round recorded — returning nil with no counter of its own, instead of panicking on the dereference if a future call site ever passes an unchecked header; the wiring is unreachable with nil today, so this is a contract fix, and the gates test pins both shapes (no panic, no handler call, an Error log, no counter movement). The non-XDPoS wiring comment was tightened to say what the no-op actually skips per block — no snapSync load, state reads, or counter — since the closure call itself still happens. The proposal to replace the skipGrades table with per-call-site switches or a level-returning grader API is declined again, per the standing rulings: the single table is what keeps the two graders from diverging, an unregistered reason already falls back to a loud Warn at both sites by design, and the 'judge once' phrase refers to log grading, not to a single gate — the two storage strengths are documented intentional design. The remaining points are reviewed and kept: the fetcher field is not a pure test seam (it is the production argument handed to fetcher.NewBlockFetcher, and its comment states both roles), the global logger swap cannot race the package's parallel tests (the two swapping tests are sequential, and Go runs a package's parallel tests only after its sequential ones finish), and the text-parsed level assertions verify emitted levels behaviorally, which is what catches a wrong logger stored in the grading table.
Ninth review round. The engine's ProposedBlockHandler now judges the nil shapes itself before getExtraFields — that call dereferences header.Number, so a nil header or nil number used to panic before the shared judgment's own guard could run, leaving SkipNilHeader unreachable on the engine path and the documented "before any chain read" contract broken there. The guard logs Error through consensus.SkipLogLevel (consumed, not registered — the grading table stays engine-free exactly as it stays fetcher-free), returns nil, and touches no counter: a caller bug is not a block observation, matching the ruling that fixed the fetcher closure. A test pins both shapes on the engine path — no panic, nil returned, an Error log, the counter unmoved — and the existing graded-level assertions are unchanged. The newFetcherProposedBlockHandler doc comment lost a duplicated "Kept a free function" sentence left by two successive edits, reordered to purpose, nil guard, chain source, testability. HasFullState and HasBlockAndExecutedState now share a hasExecutedState helper, so the executed-state criterion — a state trie that opens — is defined once and cannot drift between the two gates; pure extraction, no behavior change. The proposal to centralize the three skip counters into the consensus package with exported accessors is declined: each counter increments at exactly one site so there is no divergence to prevent, the fetcher's state counter was ruled outside the shared table in an earlier round, and moving the counters would grow the consensus package's public API and blur counter ownership without any behavioral gain.1 parent 5d08047 commit a9d1790
13 files changed
Lines changed: 2229 additions & 50 deletions
File tree
- consensus
- XDPoS/engines/engine_v2
- tests/engine_v2_tests
- core
- eth
- downloader
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
35 | 47 | | |
36 | 48 | | |
37 | 49 | | |
| |||
834 | 846 | | |
835 | 847 | | |
836 | 848 | | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
837 | 868 | | |
838 | 869 | | |
839 | 870 | | |
| |||
856 | 887 | | |
857 | 888 | | |
858 | 889 | | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
859 | 912 | | |
860 | 913 | | |
861 | 914 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
52 | 57 | | |
53 | 58 | | |
| 59 | + | |
54 | 60 | | |
55 | 61 | | |
56 | 62 | | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| 66 | + | |
60 | 67 | | |
61 | 68 | | |
62 | 69 | | |
63 | | - | |
| 70 | + | |
| 71 | + | |
64 | 72 | | |
65 | 73 | | |
| 74 | + | |
66 | 75 | | |
67 | 76 | | |
68 | 77 | | |
| |||
82 | 91 | | |
83 | 92 | | |
84 | 93 | | |
85 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
86 | 101 | | |
87 | 102 | | |
88 | 103 | | |
| |||
122 | 137 | | |
123 | 138 | | |
124 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
0 commit comments