diff --git a/RUN-A-NODE.md b/RUN-A-NODE.md index 220fe6b..087baa8 100644 --- a/RUN-A-NODE.md +++ b/RUN-A-NODE.md @@ -83,7 +83,12 @@ There is a second Aere change to the client, and it is not in patch `0001`: `patches/0004-aere-basefee-floor.patch`, one file, `LondonFeeMarket.java`. From block **10,141,734** chain 2800 clamps the EIP-1559 base fee to a floor of 1 Gwei. The patch is fork gated by two JVM system properties and is a byte behaviour no-op when they are unset, so a node that applies it -without setting them is stock Besu: +without setting them is stock Besu. Regenerated 2026-09-05 from the production tree: the previous +revision of this patch hard-coded both values as constants, so on a node built from this recipe the +two properties were dead (the floor was still 1 Gwei from 10,141,734, so the computed base fee was +the same, but the sentence above was not true of the code). Found by comparing the fleet's jars +with a build of this recipe class by class; the three consensus jars were already identical, this +one class was not. ``` -Daere.basefee.floor.forkBlock=10141734 -Daere.basefee.floor.value=1000000000 diff --git a/patches/0004-aere-basefee-floor.patch b/patches/0004-aere-basefee-floor.patch index b400044..afd68eb 100644 --- a/patches/0004-aere-basefee-floor.patch +++ b/patches/0004-aere-basefee-floor.patch @@ -1,129 +1,209 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aere Network -Date: Mon, 3 Aug 2026 04:00:00 +0300 -Subject: [PATCH] Aere Network: fork-gated consensus floor for the EIP-1559 - base fee +Date: Sat, 05 Sep 2026 09:53:25 +0000 +Subject: [PATCH] Aere Network: base-fee floor for chain 2800 (production form) -Adds a real consensus minimum base fee to LondonFeeMarket, gated on a fork -block. When the block number reaches aere.basefee.floor.forkBlock, the computed -EIP-1559 base fee is clamped to max(computed, aere.basefee.floor.value). +Apache License 2.0, section 4(b): the files this patch modifies are the work of +Hyperledger Besu and carry its copyright notice; the modifications are by +contributors to the Aere Network under the same licence. -computeBaseFee is used for BOTH block production and block validation, so this -is a genuine consensus rule and not a display trick: every node must carry the -change, and a node without it computes the old, lower base fee and rejects the -floored block. - -Default forkBlock is Long.MAX_VALUE, so with no configuration the behaviour is -identical to the unmodified file and pre-fork blocks validate unchanged. - -ACTIVE ON AERE NETWORK CHAIN 2800 since block 10,141,734, with a floor of -1,000,000,000 wei, delivered to every node as system properties -(-Daere.basefee.floor.forkBlock=10141734 -Daere.basefee.floor.value=1000000000, -see RUN-A-NODE.md). A node without this patch, or with the properties unset, -computes a lower base fee from that height on and rejects every block. - -Upstream-Status: Inappropriate [Aere Network specific consensus rule] - -Modified upstream files. One file touched by this patch is a modified copy of a -Hyperledger Besu source, not new work by Aere Network: - - ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java - -Aere Network changed it, against upstream commit -d2032017bb3b8cb215a97303980a1e4a643f7180. Its original "Copyright ConsenSys AG" -header is kept unchanged, which is what Apache License 2.0 section 4(c) -requires, and this patch adds below it a separate "Modifications Copyright" -block naming exactly what was changed. That in-file block is the notice -required by Apache License 2.0 section 4(b). It travels inside the diff, so a -tree with this patch applied carries the notice whether you used git am or git -apply. This patch creates no new files. - -Note that the copyright holder on this file is ConsenSys AG, not "contributors -to Hyperledger Besu" as on the files touched by patches 0001 and 0002. Both -holders are named in NOTICE, as Apache License 2.0 section 4(d) requires. +Regenerated 2026-09-05 from the production tree, so that the public recipe +reproduces the live binary class for class: the floor and its fork height are +read from the system properties aere.basefee.floor.forkBlock and +aere.basefee.floor.value (or the AERE_BASEFEE_FLOOR_* environment variables), +absent = disarmed, exactly as RUN-A-NODE.md tells an operator to set them. +The previous revision of this patch hard-coded both as constants, so the +operator's property was dead on a node built from the public recipe (found +2026-09-05 by comparing the fleet's jars with the public build class by class). --- - .../mainnet/feemarket/LondonFeeMarket.java | 48 +++++++++++++++++-- - 1 file changed, 45 insertions(+), 3 deletions(-) - diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java -index 0ee4f7409..83a639c1b 100644 +index 0ee4f7409..558076166 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java -@@ -1,17 +1,35 @@ - /* - * Copyright ConsenSys AG. - * - * 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 - */ -+ -+/* -+ * Modifications Copyright 2026 Aere Network. -+ * -+ * This file was changed by Aere Network: a fork-gated consensus minimum for the -+ * EIP-1559 base fee was added. Two system properties configure it -+ * (aere.basefee.floor.forkBlock, aere.basefee.floor.value), a private helper -+ * applyAereBaseFeeFloor was added, and the three return paths of computeBaseFee -+ * now pass through that helper. The default fork block is Long.MAX_VALUE, so an -+ * unconfigured node behaves exactly as the unmodified file does. Nothing upstream -+ * was removed or rewritten. -+ * -+ * The unmodified original is Hyperledger Besu commit -+ * d2032017bb3b8cb215a97303980a1e4a643f7180. The upstream copyright header above is -+ * left exactly as it was found. -+ * -+ * This notice is required by Apache License 2.0 section 4(b). -+ */ - package org.hyperledger.besu.ethereum.mainnet.feemarket; - +@@ -17,121 +17,230 @@ package org.hyperledger.besu.ethereum.mainnet.feemarket; import org.hyperledger.besu.config.GenesisConfig; -@@ -34,6 +52,19 @@ public class LondonFeeMarket implements BaseFeeMarket { + import org.hyperledger.besu.datatypes.Wei; + import org.hyperledger.besu.ethereum.core.Transaction; + import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator; + + import java.util.Optional; + + import org.apache.tuweni.units.bigints.UInt256s; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class LondonFeeMarket implements BaseFeeMarket { + private static final Logger LOG = LoggerFactory.getLogger(LondonFeeMarket.class); + + static final Wei DEFAULT_BASEFEE_INITIAL_VALUE = GenesisConfig.BASEFEE_AT_GENESIS_DEFAULT_VALUE; + static final long DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR = 8L; + static final long DEFAULT_SLACK_COEFFICIENT = 2L; private static final Wei DEFAULT_BASEFEE_FLOOR = Wei.of(7L); -+ // === AERE base-fee floor fork (fork-gated, system-property configured) === -+ // A real consensus minimum base fee, not a display trick. When the running block -+ // number reaches AERE_BASEFEE_FLOOR_FORK_BLOCK, the EIP-1559 base fee is clamped to -+ // max(computed, AERE_BASEFEE_FLOOR_VALUE). Because computeBaseFee is used for BOTH -+ // block production AND block validation, this is a genuine consensus rule: every node -+ // must agree, and a node without this change computes the old (lower) base fee and -+ // rejects the floored block. Default forkBlock = Long.MAX_VALUE => never active => -+ // byte-identical behavior to stock Besu (pre-fork blocks are unchanged). -+ private static final long AERE_BASEFEE_FLOOR_FORK_BLOCK = -+ Long.getLong("aere.basefee.floor.forkBlock", Long.MAX_VALUE); -+ private static final Wei AERE_BASEFEE_FLOOR_VALUE = -+ Wei.of(Long.getLong("aere.basefee.floor.value", 1_000_000_000L)); ++ // AERE base-fee-floor fork (fork-gated, no-op until an operator activates it). ++ // ++ // Raises the consensus EIP-1559 base-fee floor from the emergent 7-wei integer-division floor ++ // to a configurable value (default 1 Gwei) at and after a chosen fork block, so the public RPC ++ // can serve a TRUE, block-hash-verifiable baseFeePerGas instead of a proxy-faked constant. ++ // ++ // Activation is read once at fee-market construction (node start) from a JVM system property ++ // with an environment-variable fallback, mirroring how the AERE PQC/Block-STM fork reads its ++ // own JVM-level configuration: ++ // -Daere.basefee.floor.forkBlock= (env AERE_BASEFEE_FLOOR_FORKBLOCK) default Long.MAX_VALUE (UNSET => no-op) ++ // -Daere.basefee.floor.value= (env AERE_BASEFEE_FLOOR_VALUE) default 1_000_000_000 (1 Gwei) ++ // ++ // UNSET (default) means the fork block is Long.MAX_VALUE, so no real block ever reaches it and ++ // every code path below is byte-identical to upstream London behaviour (7-wei emergent floor). ++ static final long DEFAULT_AERE_BASEFEE_FLOOR_VALUE = 1_000_000_000L; // 1 Gwei in wei ++ private static final String AERE_FLOOR_FORK_BLOCK_PROPERTY = "aere.basefee.floor.forkBlock"; ++ private static final String AERE_FLOOR_FORK_BLOCK_ENV = "AERE_BASEFEE_FLOOR_FORKBLOCK"; ++ private static final String AERE_FLOOR_VALUE_PROPERTY = "aere.basefee.floor.value"; ++ private static final String AERE_FLOOR_VALUE_ENV = "AERE_BASEFEE_FLOOR_VALUE"; + protected final Wei baseFeeInitialValue; private final long londonForkBlockNumber; private final TransactionPriceCalculator txPriceCalculator; -@@ -83,6 +114,17 @@ public class LondonFeeMarket implements BaseFeeMarket { + private final Wei baseFeeFloor; + ++ // Activation block for the AERE base-fee floor. Long.MAX_VALUE == UNSET == no-op. ++ private final long aereBaseFeeFloorForkBlock; ++ // Floor value applied at and after aereBaseFeeFloorForkBlock (wei). ++ private final long aereBaseFeeFloorValue; ++ + LondonFeeMarket(final long londonForkBlockNumber, final Optional baseFeePerGasOverride) { + this(TransactionPriceCalculator.eip1559(), londonForkBlockNumber, baseFeePerGasOverride); + } + + LondonFeeMarket( + final TransactionPriceCalculator txPriceCalculator, + final long londonForkBlockNumber, + final Optional baseFeePerGasOverride) { ++ // Production path: resolve the AERE base-fee-floor fork activation from JVM configuration. ++ this( ++ txPriceCalculator, ++ londonForkBlockNumber, ++ baseFeePerGasOverride, ++ readAereFloorForkBlockConfig(), ++ readAereFloorValueConfig()); ++ } ++ ++ // Package-private constructor taking explicit AERE floor-fork parameters. Used by the ++ // production constructor above (after resolving JVM config) and by unit tests so the fork can be ++ // exercised deterministically without mutating global JVM system properties / environment. ++ LondonFeeMarket( ++ final TransactionPriceCalculator txPriceCalculator, ++ final long londonForkBlockNumber, ++ final Optional baseFeePerGasOverride, ++ final long aereBaseFeeFloorForkBlock, ++ final long aereBaseFeeFloorValue) { + this.txPriceCalculator = txPriceCalculator; + this.londonForkBlockNumber = londonForkBlockNumber; + this.baseFeeInitialValue = baseFeePerGasOverride.orElse(DEFAULT_BASEFEE_INITIAL_VALUE); + this.baseFeeFloor = baseFeeInitialValue.isZero() ? Wei.ZERO : DEFAULT_BASEFEE_FLOOR; ++ this.aereBaseFeeFloorForkBlock = aereBaseFeeFloorForkBlock; ++ this.aereBaseFeeFloorValue = aereBaseFeeFloorValue; ++ } ++ ++ private static long readAereFloorForkBlockConfig() { ++ return readLongConfig( ++ AERE_FLOOR_FORK_BLOCK_PROPERTY, AERE_FLOOR_FORK_BLOCK_ENV, Long.MAX_VALUE); ++ } ++ ++ private static long readAereFloorValueConfig() { ++ return readLongConfig( ++ AERE_FLOOR_VALUE_PROPERTY, AERE_FLOOR_VALUE_ENV, DEFAULT_AERE_BASEFEE_FLOOR_VALUE); ++ } ++ ++ private static long readLongConfig( ++ final String systemProperty, final String envVar, final long defaultValue) { ++ String raw = System.getProperty(systemProperty); ++ if (raw == null || raw.isBlank()) { ++ raw = System.getenv(envVar); ++ } ++ if (raw == null || raw.isBlank()) { ++ return defaultValue; ++ } ++ try { ++ final long parsed = Long.parseLong(raw.trim()); ++ if (parsed < 0L) { ++ LOG.warn( ++ "Ignoring negative {}/{} value '{}', using default {}", ++ systemProperty, ++ envVar, ++ raw, ++ defaultValue); ++ return defaultValue; ++ } ++ return parsed; ++ } catch (final NumberFormatException e) { ++ LOG.warn( ++ "Ignoring unparseable {}/{} value '{}', using default {}", ++ systemProperty, ++ envVar, ++ raw, ++ defaultValue); ++ return defaultValue; ++ } ++ } ++ ++ // Clamp a computed baseFee up to the active AERE floor at and after the fork block. Before the ++ // fork block (and when UNSET, since Long.MAX_VALUE is never reached) this returns baseFee ++ // unchanged, keeping pre-fork behaviour byte-identical to upstream. ++ private Wei applyAereBaseFeeFloor(final long blockNumber, final Wei baseFee) { ++ if (blockNumber >= aereBaseFeeFloorForkBlock) { ++ return UInt256s.max(baseFee, Wei.of(aereBaseFeeFloorValue)); ++ } ++ return baseFee; ++ } ++ ++ // Effective tx-pool minimum-fee floor. Pre-fork (UNSET) this is exactly the legacy baseFeeFloor ++ // (7 wei, or 0 on a zero-basefee chain), so tx-pool acceptance is byte-identical. Once an ++ // operator activates the AERE base-fee-floor fork, the tx-pool floor rises to the active AERE ++ // floor so tx-pool pricing agrees with the >= 1 Gwei header baseFee produced by computeBaseFee. ++ // A zero-basefee chain (baseFeeFloor == 0) is never raised. ++ private Wei effectiveBaseFeeFloor() { ++ if (baseFeeFloor.isZero() || aereBaseFeeFloorForkBlock == Long.MAX_VALUE) { ++ return baseFeeFloor; ++ } ++ return UInt256s.max(baseFeeFloor, Wei.of(aereBaseFeeFloorValue)); + } + + @Override + public long getBasefeeMaxChangeDenominator() { + return DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR; + } + + @Override + public Wei getInitialBasefee() { + return baseFeeInitialValue; + } + + @Override + public long getSlackCoefficient() { + return DEFAULT_SLACK_COEFFICIENT; + } + + @Override + public TransactionPriceCalculator getTransactionPriceCalculator() { + return txPriceCalculator; + } + + @Override + public boolean satisfiesFloorTxFee(final Transaction txn) { + // ensure effective baseFee is at least above floor + return txn.getGasPrice() + .map(Optional::of) + .orElse(txn.getMaxFeePerGas()) +- .filter(fee -> fee.greaterOrEqualThan(baseFeeFloor)) ++ .filter(fee -> fee.greaterOrEqualThan(effectiveBaseFeeFloor())) .isPresent(); } -+ // AERE base-fee floor fork: clamp the computed base fee to the floor once the fork -+ // block is reached. No-op before the fork block (and always a no-op when the fork is -+ // unset, i.e. forkBlock = Long.MAX_VALUE), so pre-fork blocks validate byte-identically. -+ private Wei applyAereBaseFeeFloor(final long blockNumber, final Wei fee) { -+ if (blockNumber >= AERE_BASEFEE_FLOOR_FORK_BLOCK -+ && !fee.greaterOrEqualThan(AERE_BASEFEE_FLOOR_VALUE)) { -+ return AERE_BASEFEE_FLOOR_VALUE; -+ } -+ return fee; -+ } -+ @Override public Wei computeBaseFee( final long blockNumber, -@@ -90,13 +132,13 @@ public class LondonFeeMarket implements BaseFeeMarket { + final Wei parentBaseFee, final long parentBlockGasUsed, final long targetGasUsed) { if (londonForkBlockNumber == blockNumber) { @@ -139,7 +219,20 @@ index 0ee4f7409..83a639c1b 100644 } else if (parentBlockGasUsed > targetGasUsed) { gasDelta = parentBlockGasUsed - targetGasUsed; final long denominator = getBasefeeMaxChangeDenominator(); -@@ -117,7 +159,7 @@ public class LondonFeeMarket implements BaseFeeMarket { + feeDelta = + UInt256s.max( + parentBaseFee.multiply(gasDelta).divide(targetGasUsed).divide(denominator), Wei.ONE); + baseFee = parentBaseFee.add(feeDelta); + } else { + gasDelta = targetGasUsed - parentBlockGasUsed; + final long denominator = getBasefeeMaxChangeDenominator(); + feeDelta = parentBaseFee.multiply(gasDelta).divide(targetGasUsed).divide(denominator); + baseFee = parentBaseFee.subtract(feeDelta); + } + LOG.trace( + "block #{} parentBaseFee: {} parentGasUsed: {} parentGasTarget: {} baseFee: {}", + blockNumber, + parentBaseFee, parentBlockGasUsed, targetGasUsed, baseFee); @@ -148,3 +241,17 @@ index 0ee4f7409..83a639c1b 100644 } @Override + public ValidationMode baseFeeValidationMode(final long blockNumber) { + return londonForkBlockNumber == blockNumber ? ValidationMode.INITIAL : ValidationMode.ONGOING; + } + + @Override + public ValidationMode gasLimitValidationMode(final long blockNumber) { + return londonForkBlockNumber == blockNumber ? ValidationMode.INITIAL : ValidationMode.ONGOING; + } + + @Override + public boolean isBeforeForkBlock(final long blockNumber) { + return londonForkBlockNumber > blockNumber; + } + }