From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aere Network Date: Thu, 10 Sep 2026 22:39:39 +0300 Subject: [PATCH] Aere Network: base-fee floor for chain 2800 (production form) 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. 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). Regenerated 2026-09-10 from the production tree over the same file set, so that the public recipe carries the same Apache-2.0 section 4(b) modification notices that the production sources carry; measured the same day: a build from this recipe reproduces the fleet's consensus jars class for class. --- 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..e7c8c9724 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,137 +1,252 @@ /* * 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 contributors to the Aere Network. + * + * This file was modified by contributors to the Aere Network, as required by section 4(b) of the + * Apache License 2.0. The copyright header above is the upstream one and is left exactly as it was + * found, as section 4(c) requires. The change itself is in patches/0004-aere-basefee-floor.patch. */ package org.hyperledger.besu.ethereum.mainnet.feemarket; import org.hyperledger.besu.config.GenesisConfig; 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, 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; 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(); } @Override public Wei computeBaseFee( final long blockNumber, final Wei parentBaseFee, final long parentBlockGasUsed, final long targetGasUsed) { if (londonForkBlockNumber == blockNumber) { - return getInitialBasefee(); + return applyAereBaseFeeFloor(blockNumber, getInitialBasefee()); } long gasDelta; Wei feeDelta, baseFee; if (parentBlockGasUsed == targetGasUsed) { - return parentBaseFee; + return applyAereBaseFeeFloor(blockNumber, parentBaseFee); } else if (parentBlockGasUsed > targetGasUsed) { gasDelta = parentBlockGasUsed - targetGasUsed; final long denominator = getBasefeeMaxChangeDenominator(); 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); - return baseFee; + return applyAereBaseFeeFloor(blockNumber, baseFee); } @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; } }