anchor: the propagation adoption gate (rule C, D-412) is OBSERVE by default - it computes the same verdict and adopts, logging 'would NOT adopt'; ENFORCE only with aere.d412.adoptionGate=enforce. On the public testnet the enforcing form froze a validator that had missed one height's commit messages (D-419). Adds PqTipAdoptionGateModeTest. LICENSING.md regenerated over the 209 files the package carries.
This commit is contained in:
parent
71bce697d1
commit
93f9d5839b
13
LICENSING.md
13
LICENSING.md
@ -123,7 +123,7 @@ the section 4(b) notice of changed files), and each keeps its original notice pe
|
|||||||
|
|
||||||
<!-- GENERAT: lista de mai jos se rescrie de unealta de licentiere din depozitul de constructie -->
|
<!-- GENERAT: lista de mai jos se rescrie de unealta de licentiere din depozitul de constructie -->
|
||||||
|
|
||||||
## anchor/: every file that carries someone else's copyright (generated 2026-09-24)
|
## anchor/: every file that carries someone else's copyright (generated 2026-09-27)
|
||||||
|
|
||||||
This list is **generated from the files this package actually ships**, not written by hand: the
|
This list is **generated from the files this package actually ships**, not written by hand: the
|
||||||
previous hand-written table fell behind the tree on 2026-09-12 and seven upstream files went
|
previous hand-written table fell behind the tree on 2026-09-12 and seven upstream files went
|
||||||
@ -132,8 +132,8 @@ repository, and the publisher refuses to ship a package whose list disagrees wit
|
|||||||
You do not need that tool to check the list: every row below names a file in this package, and
|
You do not need that tool to check the list: every row below names a file in this package, and
|
||||||
`grep -i copyright` on it shows the same line.
|
`grep -i copyright` on it shows the same line.
|
||||||
|
|
||||||
Measured 2026-09-24 over `anchor/`: **202 files**. **90** carry someone
|
Measured 2026-09-27 over `anchor/`: **209 files**. **90** carry someone
|
||||||
else's copyright alone, **57** carry a shared notice naming both upstream and us, and **11** name only
|
else's copyright alone, **64** carry a shared notice naming both upstream and us, and **11** name only
|
||||||
`contributors to the AERE Network`; 44 carry no copyright line at all.
|
`contributors to the AERE Network`; 44 carry no copyright line at all.
|
||||||
The first two groups are the table below. A shared notice is listed too: those are precisely the
|
The first two groups are the table below. A shared notice is listed too: those are precisely the
|
||||||
upstream files we changed, so section 4(b) has something to say about each of them.
|
upstream files we changed, so section 4(b) has something to say about each of them.
|
||||||
@ -145,11 +145,14 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `anchor/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
|
| `anchor/app/src/main/java/org/hyperledger/besu/controller/PqTipAdoptionGate.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
|
| `anchor/app/src/test/java/org/hyperledger/besu/controller/PqTipAdoptionGateModeTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/consensus/common/build.gradle` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/common/build.gradle` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinality.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityTags.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqHeaderSeal.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AerePqHeaderSeal.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftBlockInterface.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
@ -179,6 +182,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
|
|||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/LICENSE-BouncyCastle.txt` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/LICENSE-BouncyCastle.txt` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/NodeEntry.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/NodeEntry.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/PK.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/PK.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/ParallelSigning.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_FORS.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_FORS.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_XMSS.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SIG_XMSS.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
@ -195,6 +199,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
|
|||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/slhdsa/WotsPlus.java` | `Copyright (c) 2000-2025 The Legion of the Bouncy Castle Inc., see LICENTA-BOUNCYCASTLE.txt),` | upstream |
|
||||||
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java` | `Copyright contributors to Besu.` | upstream |
|
| `anchor/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/tools/PqRegistryHashTool.java` | `Copyright contributors to Besu.` | upstream |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityAuthorTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityAuthorTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/AerePqFinalityTagsTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorEmergencyConfigTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorLapseTest.java` | `Copyright contributors to Besu / Aere Network.` | upstream, **modified by Aere Network** |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PqAnchorLapseTest.java` | `Copyright contributors to Besu / Aere Network.` | upstream, **modified by Aere Network** |
|
||||||
@ -218,6 +223,7 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
|
|||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PreparePqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/PreparePqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/ProposalPqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/ProposalPqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
||||||
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundChangePqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundChangePqAttachGateTest.java` | `Copyright contributors to Besu.` | upstream |
|
||||||
|
| `anchor/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/slhdsa/SlhDsaParallelSigningTest.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/messagewrappers/Commit.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/messagewrappers/Commit.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/CommitPayload.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/CommitPayload.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
@ -290,3 +296,4 @@ notice (4(c)), and where we changed it the words **modified by Aere Network** ar
|
|||||||
| `anchor/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AereStateUnavailableIsAnErrorTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream |
|
| `anchor/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AereStateUnavailableIsAnErrorTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream |
|
||||||
| `anchor/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/AereHeadStateRetryTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream |
|
| `anchor/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/AereHeadStateRetryTest.java` | `Copyright contributors to Hyperledger Besu.` | upstream |
|
||||||
| `anchor/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStep.java` | `Copyright ConsenSys AG.` | upstream |
|
| `anchor/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStep.java` | `Copyright ConsenSys AG.` | upstream |
|
||||||
|
| `anchor/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PropagatedBlockAdoptionGate.java` | `Copyright contributors to Besu / AERE Network.` | upstream, **modified by Aere Network** |
|
||||||
|
|||||||
@ -5,9 +5,10 @@ computed them, and the same 276 files once produced two different aggregates her
|
|||||||
|
|
||||||
329e4e2f93143cc8af092f0a951fa23619c12c4ec1793b053654f8e26e088da0 app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
|
329e4e2f93143cc8af092f0a951fa23619c12c4ec1793b053654f8e26e088da0 app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
|
||||||
3f289d74bb1878280a99742320d7a27c19146a2f19cdf6a0857f2aad4361ad5e app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java
|
3f289d74bb1878280a99742320d7a27c19146a2f19cdf6a0857f2aad4361ad5e app/src/main/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptions.java
|
||||||
0ce3fcfdcfa7ab17f4fd1251e9066e3e1c03a59047a01fb548f490391fe4cdb4 app/src/main/java/org/hyperledger/besu/controller/PqTipAdoptionGate.java
|
3676cf2ef881b81437ddec15bf6b8f8c393a5a6459f77179898703c289c9f8bc app/src/main/java/org/hyperledger/besu/controller/PqTipAdoptionGate.java
|
||||||
505066636bdd2a0e6908d490ad0b3341788d270320aed81dc2923572d61216a7 app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
|
feaaa439fc80af14d766683231d46e5b2aa12aa6e651ed83f7ad8e9507fa1413 app/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
|
||||||
129493150c0b13e8020bdf50c0c1ae07268d4f73a69a821b303bf4894451fa5e app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java
|
129493150c0b13e8020bdf50c0c1ae07268d4f73a69a821b303bf4894451fa5e app/src/test/java/org/hyperledger/besu/cli/options/AerePqEmergencyOptionsTest.java
|
||||||
|
2de4b531aa8e2ea377e31908ada2d54e5e7b2df949f2456f2c0913a988f8dd00 app/src/test/java/org/hyperledger/besu/controller/PqTipAdoptionGateModeTest.java
|
||||||
728c29c299edf6011f2c2798cbe930a043112e0e06b924e7edb54284ca44a758 config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
|
728c29c299edf6011f2c2798cbe930a043112e0e06b924e7edb54284ca44a758 config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
|
||||||
a3b93f2602e9755d91358c9b3f473235f4d40ae23d912f797cdb03ed7e1348f9 consensus/common/build.gradle
|
a3b93f2602e9755d91358c9b3f473235f4d40ae23d912f797cdb03ed7e1348f9 consensus/common/build.gradle
|
||||||
68d635954ef33448a661256f50642eeeff2f7bb5874a54ffc2e64193c83fe391 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelay.java
|
68d635954ef33448a661256f50642eeeff2f7bb5874a54ffc2e64193c83fe391 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/AereAnchorProposalDelay.java
|
||||||
|
|||||||
@ -61,11 +61,56 @@ import org.slf4j.LoggerFactory;
|
|||||||
* cache cannot forge the proof. To clear the gate an adversary would need the commit quorum's distinct
|
* cache cannot forge the proof. To clear the gate an adversary would need the commit quorum's distinct
|
||||||
* Falcon signatures over the block's commit message - which is exactly the agreement D-412 shows a single
|
* Falcon signatures over the block's commit message - which is exactly the agreement D-412 shows a single
|
||||||
* key cannot produce.
|
* key cannot produce.
|
||||||
|
*
|
||||||
|
* <p><b>OBSERVE by default, since D-419 (2026-09-27).</b> Measured on testnet 28001: validator
|
||||||
|
* aere-testnet@1 spent five seconds importing its own 3,366-transaction block, missed the commit messages of
|
||||||
|
* the next height, and this gate then refused the block the other four had decided - 226 times over 1 h 45
|
||||||
|
* min - while its own consensus climbed to round 10 and could never decide that height again, and the sync
|
||||||
|
* downloader never engaged ({@code eth_syncing=false} 4,000 blocks behind). The third bullet above was the
|
||||||
|
* premise, and it did not hold. Meanwhile the one scenario the gate was written for had been measured on
|
||||||
|
* 2026-09-26 WITHOUT the gate: honest validators holding the honest block at the tip did not adopt the forged
|
||||||
|
* one (0 of 3), because the honest block wins the race; the real D-412 exposure is readers and catch-up,
|
||||||
|
* which Rule B1 (finality tags) and the anchor bound (PqAnchorSealsRule) cover. So the gate now defaults to
|
||||||
|
* OBSERVE: it computes exactly the same verdict and logs every block it WOULD refuse ({@value
|
||||||
|
* #OBSERVE_MARKER}), and adopts it. ENFORCE ({@value #PROPERTY_MODE}=enforce or {@value #ENV_MODE}=enforce)
|
||||||
|
* keeps the old behaviour and must be asked for; it has no release condition yet, so it can wedge a validator
|
||||||
|
* that missed a height. A safe release needs evidence one key cannot forge (a certified anchor above the
|
||||||
|
* refused block), not a timeout: a proposer that stays silent and waits for the timeout is the D-412 attack.
|
||||||
*/
|
*/
|
||||||
public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(PqTipAdoptionGate.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PqTipAdoptionGate.class);
|
||||||
|
|
||||||
|
/** System property selecting the gate mode: {@code enforce} or {@code observe} (the default). */
|
||||||
|
public static final String PROPERTY_MODE = "aere.d412.adoptionGate";
|
||||||
|
|
||||||
|
/** Environment twin of {@link #PROPERTY_MODE}. */
|
||||||
|
public static final String ENV_MODE = "AERE_D412_ADOPTION_GATE";
|
||||||
|
|
||||||
|
/** Logged, in observe mode, for every propagated block the enforce mode would have refused. */
|
||||||
|
public static final String OBSERVE_MARKER = "AERE D-412 OBSERVE: would NOT adopt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the configuration asks for ENFORCE. Anything other than {@code enforce} (after trimming, any
|
||||||
|
* case), including nothing at all, is OBSERVE: enforcing must be an explicit request.
|
||||||
|
*
|
||||||
|
* @param property the system property value, possibly null
|
||||||
|
* @param env the environment value, possibly null
|
||||||
|
* @return true only for an explicit enforce
|
||||||
|
*/
|
||||||
|
static boolean enforceRequested(final String property, final String env) {
|
||||||
|
final String v = property != null && !property.isBlank() ? property : env;
|
||||||
|
if (v == null || v.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String m = v.trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (!m.equals("enforce") && !m.equals("observe")) {
|
||||||
|
LOG.warn(
|
||||||
|
"AERE D-412: unknown adoption gate mode '{}' ({} / {}); using OBSERVE", m, PROPERTY_MODE, ENV_MODE);
|
||||||
|
}
|
||||||
|
return m.equals("enforce");
|
||||||
|
}
|
||||||
|
|
||||||
/** Verifies one Falcon seal over the commit message of a block; injectable for tests. */
|
/** Verifies one Falcon seal over the commit message of a block; injectable for tests. */
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface SealVerifier {
|
public interface SealVerifier {
|
||||||
@ -86,9 +131,14 @@ public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
|||||||
private final long chainId;
|
private final long chainId;
|
||||||
private final PqSealCache cache;
|
private final PqSealCache cache;
|
||||||
private final SealVerifier verifier;
|
private final SealVerifier verifier;
|
||||||
|
private final boolean enforce;
|
||||||
|
private final java.util.function.LongPredicate stage5Active;
|
||||||
|
private final java.util.concurrent.atomic.AtomicLong wouldRefuse =
|
||||||
|
new java.util.concurrent.atomic.AtomicLong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Production wiring.
|
* Production wiring; the mode comes from {@link #PROPERTY_MODE} / {@link #ENV_MODE} (OBSERVE unless
|
||||||
|
* enforce is asked for).
|
||||||
*
|
*
|
||||||
* @param validatorProvider the validator provider (the set at a height; ids from H_id, addresses below)
|
* @param validatorProvider the validator provider (the set at a height; ids from H_id, addresses below)
|
||||||
* @param localEcdsaAddress this node's ECDSA address (from its node key)
|
* @param localEcdsaAddress this node's ECDSA address (from its node key)
|
||||||
@ -101,7 +151,9 @@ public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
|||||||
localEcdsaAddress,
|
localEcdsaAddress,
|
||||||
chainId,
|
chainId,
|
||||||
PqSealCache.instance(),
|
PqSealCache.instance(),
|
||||||
(n, i, m, s) -> PqSignerRegistry.falconSealSupport().verifyAtHistoric(n, i, m, s));
|
(n, i, m, s) -> PqSignerRegistry.falconSealSupport().verifyAtHistoric(n, i, m, s),
|
||||||
|
enforceRequested(System.getProperty(PROPERTY_MODE), System.getenv(ENV_MODE)),
|
||||||
|
AerePqHeaderSeal::active);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,18 +164,43 @@ public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
|||||||
* @param chainId the chain id
|
* @param chainId the chain id
|
||||||
* @param cache the seal cache to read
|
* @param cache the seal cache to read
|
||||||
* @param verifier the seal verifier
|
* @param verifier the seal verifier
|
||||||
|
* @param enforce true to refuse (ENFORCE), false to log and adopt (OBSERVE)
|
||||||
|
* @param stage5Active whether a height is at or above H_hdr (production: {@link AerePqHeaderSeal#active})
|
||||||
*/
|
*/
|
||||||
PqTipAdoptionGate(
|
PqTipAdoptionGate(
|
||||||
final ValidatorProvider validatorProvider,
|
final ValidatorProvider validatorProvider,
|
||||||
final Address localEcdsaAddress,
|
final Address localEcdsaAddress,
|
||||||
final long chainId,
|
final long chainId,
|
||||||
final PqSealCache cache,
|
final PqSealCache cache,
|
||||||
final SealVerifier verifier) {
|
final SealVerifier verifier,
|
||||||
|
final boolean enforce,
|
||||||
|
final java.util.function.LongPredicate stage5Active) {
|
||||||
this.validatorProvider = validatorProvider;
|
this.validatorProvider = validatorProvider;
|
||||||
this.localEcdsaAddress = localEcdsaAddress;
|
this.localEcdsaAddress = localEcdsaAddress;
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.verifier = verifier;
|
this.verifier = verifier;
|
||||||
|
this.enforce = enforce;
|
||||||
|
this.stage5Active = stage5Active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this gate refuses (ENFORCE) or only logs (OBSERVE).
|
||||||
|
*
|
||||||
|
* @return true in ENFORCE mode
|
||||||
|
*/
|
||||||
|
public boolean isEnforcing() {
|
||||||
|
return enforce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many propagated blocks this gate judged without a post-quantum commit quorum (refused in ENFORCE,
|
||||||
|
* logged and adopted in OBSERVE).
|
||||||
|
*
|
||||||
|
* @return the count since start
|
||||||
|
*/
|
||||||
|
public long wouldRefuseCount() {
|
||||||
|
return wouldRefuse.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,7 +208,7 @@ public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
|||||||
final long h = header.getNumber();
|
final long h = header.getNumber();
|
||||||
// Below H_hdr the header still carries a quorum of ECDSA committed seals, whose upstream rule
|
// Below H_hdr the header still carries a quorum of ECDSA committed seals, whose upstream rule
|
||||||
// already refuses a single-key block; nothing to add here.
|
// already refuses a single-key block; nothing to add here.
|
||||||
if (!AerePqHeaderSeal.active(h)) {
|
if (!stage5Active.test(h)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final Collection<Address> validators = validatorProvider.getValidatorsForBlock(parent);
|
final Collection<Address> validators = validatorProvider.getValidatorsForBlock(parent);
|
||||||
@ -172,16 +249,30 @@ public class PqTipAdoptionGate implements PropagatedBlockAdoptionGate.Gate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
final boolean ok = verified.size() >= quorum;
|
final boolean ok = verified.size() >= quorum;
|
||||||
if (!ok) {
|
if (ok) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
wouldRefuse.incrementAndGet();
|
||||||
|
if (!enforce) {
|
||||||
|
// D-419: OBSERVE. Same verdict, recorded, and the block is adopted as before the gate existed.
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"AERE D-412: propagated block {} at tip height {} holds {} verified post-quantum commit "
|
"{} propagated block {} at tip height {}: it holds {} verified post-quantum commit seal(s), "
|
||||||
+ "seal(s), quorum is {}; NOT adopting it via propagation - this node's own consensus "
|
+ "quorum is {}; adopting it (observe mode, D-419).",
|
||||||
+ "decides this height.",
|
OBSERVE_MARKER,
|
||||||
header.getHash(),
|
header.getHash(),
|
||||||
h,
|
h,
|
||||||
verified.size(),
|
verified.size(),
|
||||||
quorum);
|
quorum);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return ok;
|
LOG.info(
|
||||||
|
"AERE D-412: propagated block {} at tip height {} holds {} verified post-quantum commit "
|
||||||
|
+ "seal(s), quorum is {}; NOT adopting it via propagation - this node's own consensus "
|
||||||
|
+ "decides this height.",
|
||||||
|
header.getHash(),
|
||||||
|
h,
|
||||||
|
verified.size(),
|
||||||
|
quorum);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -619,12 +619,17 @@ public class QbftBesuControllerBuilder extends BesuControllerBuilder {
|
|||||||
// ADOPTION-at-the-tip is gated, only for a validator, only from H_hdr. Inert here on any chain whose
|
// ADOPTION-at-the-tip is gated, only for a validator, only from H_hdr. Inert here on any chain whose
|
||||||
// H_hdr is unset, which is chain 2800 today. Placed after the validator set is resolved (localAddress
|
// H_hdr is unset, which is chain 2800 today. Placed after the validator set is resolved (localAddress
|
||||||
// and validatorProvider both exist) and before the network and the QBFT machine start.
|
// and validatorProvider both exist) and before the network and the QBFT machine start.
|
||||||
|
// D-419 (2026-09-27): the gate defaults to OBSERVE (logs every block it would refuse, adopts it); ENFORCE
|
||||||
|
// wedged a validator that missed one height's commits on testnet 28001 and must be asked for explicitly.
|
||||||
if (aereAnchorConfig.everActive()) {
|
if (aereAnchorConfig.everActive()) {
|
||||||
PropagatedBlockAdoptionGate.install(
|
final PqTipAdoptionGate tipGate =
|
||||||
new PqTipAdoptionGate(validatorProvider, localAddress, aereAnchorConfig.chainId()));
|
new PqTipAdoptionGate(validatorProvider, localAddress, aereAnchorConfig.chainId());
|
||||||
|
PropagatedBlockAdoptionGate.install(tipGate);
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"AERE D-412: propagation adoption gate armed; a propagated block at the tip is adopted only "
|
"AERE D-412: propagation adoption gate armed in {} mode; a propagated block at the tip {} "
|
||||||
+ "with a post-quantum commit quorum from H_hdr={} (unset means never on this node).",
|
+ "without a post-quantum commit quorum from H_hdr={} (unset means never on this node).",
|
||||||
|
tipGate.isEnforcing() ? "ENFORCE" : "OBSERVE",
|
||||||
|
tipGate.isEnforcing() ? "is NOT adopted" : "is adopted and logged",
|
||||||
AerePqHeaderSeal.headerBlock());
|
AerePqHeaderSeal.headerBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright contributors to Besu / AERE Network.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations under the License.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package org.hyperledger.besu.controller;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.hyperledger.besu.consensus.common.bft.AerePqIdentity;
|
||||||
|
import org.hyperledger.besu.consensus.common.bft.FalconSeal;
|
||||||
|
import org.hyperledger.besu.consensus.common.bft.PqSealCache;
|
||||||
|
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
|
||||||
|
import org.hyperledger.besu.datatypes.Address;
|
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader;
|
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* D-419 (2026-09-27): the Rule C tip adoption gate in its two modes. OBSERVE (the default) computes the same
|
||||||
|
* verdict as ENFORCE, counts the blocks it would refuse, and adopts them; ENFORCE refuses. Every case has its
|
||||||
|
* opposite: the same block without a quorum is refused under ENFORCE and adopted under OBSERVE, and a quorum of
|
||||||
|
* seals that do NOT verify is refused under ENFORCE, so the gate verifies seals and does not merely count them.
|
||||||
|
*/
|
||||||
|
class PqTipAdoptionGateModeTest {
|
||||||
|
|
||||||
|
private static final long H_HDR = 100L;
|
||||||
|
private static final long CHAIN_ID = 28098L;
|
||||||
|
private static final Address LOCAL = Address.fromHexString("0x00000000000000000000000000000000000000a1");
|
||||||
|
private static final List<Address> SET =
|
||||||
|
List.of(
|
||||||
|
LOCAL,
|
||||||
|
Address.fromHexString("0x00000000000000000000000000000000000000a2"),
|
||||||
|
Address.fromHexString("0x00000000000000000000000000000000000000a3"),
|
||||||
|
Address.fromHexString("0x00000000000000000000000000000000000000a4"));
|
||||||
|
// a distinct timestamp per header, so every test reads its own seal-cache entry from the shared singleton
|
||||||
|
private static final AtomicLong TS = new AtomicLong(1_900_000_000L);
|
||||||
|
|
||||||
|
private ValidatorProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// no v3 identity installed: isLocal falls back to address equality, which is what this set uses
|
||||||
|
AerePqIdentity.uninstall();
|
||||||
|
provider = mock(ValidatorProvider.class);
|
||||||
|
when(provider.getValidatorsForBlock(any())).thenReturn(SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
AerePqIdentity.uninstall();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockHeader header(final long number) {
|
||||||
|
return new BlockHeaderTestFixture().number(number).timestamp(TS.incrementAndGet()).buildHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PqTipAdoptionGate gate(final boolean enforce, final boolean sealsVerify) {
|
||||||
|
return new PqTipAdoptionGate(
|
||||||
|
provider,
|
||||||
|
LOCAL,
|
||||||
|
CHAIN_ID,
|
||||||
|
PqSealCache.instance(),
|
||||||
|
(n, i, m, s) -> sealsVerify,
|
||||||
|
enforce,
|
||||||
|
h -> h >= H_HDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void recordSeals(final BlockHeader h, final int count) {
|
||||||
|
final List<FalconSeal> seals =
|
||||||
|
java.util.stream.IntStream.range(0, count)
|
||||||
|
.mapToObj(i -> new FalconSeal(i, Bytes.of(i + 1)))
|
||||||
|
.toList();
|
||||||
|
PqSealCache.instance().record(h.getNumber(), h.getHash(), seals);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enforceRefusesATipBlockWithoutAQuorum() {
|
||||||
|
final BlockHeader h = header(150);
|
||||||
|
final PqTipAdoptionGate g = gate(true, true);
|
||||||
|
assertThat(g.mayAdopt(h, header(149))).isFalse();
|
||||||
|
assertThat(g.wouldRefuseCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void observeAdoptsTheSameBlockAndCountsIt() {
|
||||||
|
final BlockHeader h = header(150);
|
||||||
|
final PqTipAdoptionGate g = gate(false, true);
|
||||||
|
assertThat(g.mayAdopt(h, header(149))).isTrue();
|
||||||
|
assertThat(g.wouldRefuseCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enforceAdoptsWithAVerifiedQuorum() {
|
||||||
|
final BlockHeader h = header(151);
|
||||||
|
recordSeals(h, 3); // quorum of 4 validators is 3
|
||||||
|
final PqTipAdoptionGate g = gate(true, true);
|
||||||
|
assertThat(g.mayAdopt(h, header(150))).isTrue();
|
||||||
|
assertThat(g.wouldRefuseCount()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enforceRefusesAQuorumOfSealsThatDoNotVerify() {
|
||||||
|
final BlockHeader h = header(152);
|
||||||
|
recordSeals(h, 4);
|
||||||
|
final PqTipAdoptionGate g = gate(true, false);
|
||||||
|
assertThat(g.mayAdopt(h, header(151))).isFalse();
|
||||||
|
assertThat(g.wouldRefuseCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void belowHdrNeitherModeGatesOrCounts() {
|
||||||
|
final BlockHeader h = header(H_HDR - 1);
|
||||||
|
final PqTipAdoptionGate e = gate(true, true);
|
||||||
|
final PqTipAdoptionGate o = gate(false, true);
|
||||||
|
assertThat(e.mayAdopt(h, header(H_HDR - 2))).isTrue();
|
||||||
|
assertThat(o.mayAdopt(h, header(H_HDR - 2))).isTrue();
|
||||||
|
assertThat(e.wouldRefuseCount()).isZero();
|
||||||
|
assertThat(o.wouldRefuseCount()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aNodeOutsideTheSetIsNotGatedEvenInEnforce() {
|
||||||
|
when(provider.getValidatorsForBlock(any())).thenReturn(SET.subList(1, 4));
|
||||||
|
final PqTipAdoptionGate g = gate(true, true);
|
||||||
|
assertThat(g.mayAdopt(header(153), header(152))).isTrue();
|
||||||
|
assertThat(g.wouldRefuseCount()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enforceMustBeAskedForExplicitly() {
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested(null, null)).isFalse();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested("", " ")).isFalse();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested("observe", null)).isFalse();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested("bogus", null)).isFalse();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested("enforce", null)).isTrue();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested(" ENFORCE ", null)).isTrue();
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested(null, "enforce")).isTrue();
|
||||||
|
// the system property wins over the environment when both are set
|
||||||
|
assertThat(PqTipAdoptionGate.enforceRequested("observe", "enforce")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void theProductionConstructorDefaultsToObserve() {
|
||||||
|
final String before = System.getProperty(PqTipAdoptionGate.PROPERTY_MODE);
|
||||||
|
try {
|
||||||
|
System.clearProperty(PqTipAdoptionGate.PROPERTY_MODE);
|
||||||
|
if (System.getenv(PqTipAdoptionGate.ENV_MODE) == null) {
|
||||||
|
assertThat(new PqTipAdoptionGate(provider, LOCAL, CHAIN_ID).isEnforcing()).isFalse();
|
||||||
|
}
|
||||||
|
System.setProperty(PqTipAdoptionGate.PROPERTY_MODE, "enforce");
|
||||||
|
assertThat(new PqTipAdoptionGate(provider, LOCAL, CHAIN_ID).isEnforcing()).isTrue();
|
||||||
|
} finally {
|
||||||
|
if (before == null) {
|
||||||
|
System.clearProperty(PqTipAdoptionGate.PROPERTY_MODE);
|
||||||
|
} else {
|
||||||
|
System.setProperty(PqTipAdoptionGate.PROPERTY_MODE, before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user