mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
"Canonical if recorded, else the outer id" reads as two trivial lines, which is why it was re-typed inline across the package and behind three separately-named helpers -- decision, certCanonical, canonicalIDOf -- each expressing the same meaning in its own words. A rule with N copies is a rule with N chances to diverge, and a suite that pins one copy says nothing about the others. certCanonical and canonicalIDOf differ only in where they FETCH the pair from: a cert's position, or a VM block's optional commitment. That difference is real and stays; the rule they then apply is not theirs to restate, so both hand the pair to decision. The degrade direction is the part worth pinning: an absent canonical becomes the OUTER id, never Empty. Empty would make every unwrapped block equal to every other one, which is the opposite of what a bare chain means. Co-authored-by: Hanzo Dev <[email protected]>
58 lines
2.3 KiB
Go
58 lines
2.3 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// decision_test.go — the envelope-vs-canonical rule has one meaning, so it must
|
|
// have one implementation. It reads as two trivial lines, which is exactly why
|
|
// it was re-typed inline at a dozen sites and behind three separately-named
|
|
// helpers; a rule with N copies is a rule with N chances to diverge.
|
|
package chain
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// TestDecisionDegradesToTheOuterID pins the rule itself. Degrading an absent
|
|
// canonical to the OUTER id, never to Empty, is what keeps a bare chain
|
|
// comparing byte-for-byte as it always did: there, two blocks are the same
|
|
// decision only when they are the same block. Degrading to Empty would make
|
|
// every unwrapped block equal to every other.
|
|
func TestDecisionDegradesToTheOuterID(t *testing.T) {
|
|
outer := ids.GenerateTestID()
|
|
inner := ids.GenerateTestID()
|
|
|
|
if got := decision(inner, outer); got != inner {
|
|
t.Fatalf("a recorded canonical is the decision: got %s want %s", got, inner)
|
|
}
|
|
if got := decision(ids.Empty, outer); got != outer {
|
|
t.Fatalf("an absent canonical degrades to the outer id: got %s want %s", got, outer)
|
|
}
|
|
if decision(ids.Empty, outer) == ids.Empty {
|
|
t.Fatal("degrading to Empty would make every unwrapped block equal to every other")
|
|
}
|
|
}
|
|
|
|
// TestCertAndBlockAgreeOnTheRule is the property the three helpers existed to
|
|
// break. certCanonical reads a cert's position and canonicalIDOf reads a VM
|
|
// block; they fetch the pair from different places and must apply the SAME rule
|
|
// to it. A future fourth extractor that re-types the two lines fails here.
|
|
func TestCertAndBlockAgreeOnTheRule(t *testing.T) {
|
|
outer := ids.GenerateTestID()
|
|
inner := ids.GenerateTestID()
|
|
|
|
wrapped := &QuorumCert{Position: VotePosition{BlockID: outer, CanonicalID: inner}}
|
|
if got := certCanonical(wrapped); got != decision(inner, outer) {
|
|
t.Fatalf("certCanonical disagrees with the rule: %s vs %s", got, decision(inner, outer))
|
|
}
|
|
|
|
bare := &QuorumCert{Position: VotePosition{BlockID: outer}}
|
|
if got := certCanonical(bare); got != decision(ids.Empty, outer) {
|
|
t.Fatalf("certCanonical disagrees with the rule on a bare position: %s vs %s",
|
|
got, decision(ids.Empty, outer))
|
|
}
|
|
if certCanonical(bare) != outer {
|
|
t.Fatal("a cert with no canonical commits to its own block id")
|
|
}
|
|
}
|