-
Notifications
You must be signed in to change notification settings - Fork 720
fix(xdc): restore the XDCX special-transaction window on Apothem and mainnet #12872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f48b896
03753f3
f82ef4e
b14796f
0c2fcc3
7e6f659
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| using Nethermind.Core; | ||
| using Nethermind.Logging; | ||
| using Nethermind.Serialization.Json; | ||
| using Nethermind.Specs.ChainSpecStyle; | ||
| using Nethermind.Xdc.Spec; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Nethermind.Xdc.Test; | ||
|
|
||
| /// <summary> | ||
| /// Pins what our chain specs produce for the XDCX special-transaction path on both networks. | ||
| /// </summary> | ||
| [TestFixture, Parallelizable(ParallelScope.All)] | ||
| public class XdcChainSpecTests | ||
| { | ||
| [TestCase("xdc.json")] | ||
| [TestCase("xdc-testnet.json")] | ||
| public void System_contract_addresses_are_deserialized(string chainSpecFile) | ||
| { | ||
| XdcChainSpecEngineParameters engineParameters = EngineParameters(LoadChainSpec(chainSpecFile)); | ||
|
|
||
| Assert.Multiple(() => | ||
| { | ||
| Assert.That(engineParameters.MasternodeVotingContract, Is.EqualTo(new Address("0x0000000000000000000000000000000000000088"))); | ||
| Assert.That(engineParameters.BlockSignerContract, Is.EqualTo(new Address("0x0000000000000000000000000000000000000089"))); | ||
| Assert.That(engineParameters.RandomizeSMCBinary, Is.EqualTo(new Address("0x0000000000000000000000000000000000000090"))); | ||
| Assert.That(engineParameters.XDCXAddressBinary, Is.EqualTo(new Address("0x0000000000000000000000000000000000000091"))); | ||
| Assert.That(engineParameters.TradingStateAddressBinary, Is.EqualTo(new Address("0x0000000000000000000000000000000000000092"))); | ||
| Assert.That(engineParameters.XDCXLendingAddressBinary, Is.EqualTo(new Address("0x0000000000000000000000000000000000000093"))); | ||
| Assert.That(engineParameters.XDCXLendingFinalizedTradeAddressBinary, Is.EqualTo(new Address("0x0000000000000000000000000000000000000094"))); | ||
| }); | ||
| } | ||
|
|
||
| [TestCase("xdc.json")] | ||
| [TestCase("xdc-testnet.json")] | ||
| public void XDCX_flags_flip_on_their_own_blocks(string chainSpecFile) | ||
| { | ||
| ChainSpec chainSpec = LoadChainSpec(chainSpecFile); | ||
| XdcChainSpecEngineParameters engineParameters = EngineParameters(chainSpec); | ||
| XdcChainSpecBasedSpecProvider specProvider = new(chainSpec, engineParameters, LimboLogs.Instance); | ||
|
|
||
| ulong activation = engineParameters.TipXDCX!.Value; | ||
| ulong minerDisable = engineParameters.TIPXDCXMinerDisable!.Value; | ||
| ulong receiverDisable = engineParameters.TIPXDCXReceiverDisable!.Value; | ||
|
|
||
| Assert.Multiple(() => | ||
| { | ||
| Assert.That(specProvider.GetXdcSpec(activation - 1).IsTIPXDCXMiner, Is.False); | ||
| Assert.That(specProvider.GetXdcSpec(activation).IsTIPXDCXMiner, Is.True); | ||
| Assert.That(specProvider.GetXdcSpec(minerDisable - 1).IsTIPXDCXMiner, Is.True); | ||
| Assert.That(specProvider.GetXdcSpec(minerDisable).IsTIPXDCXMiner, Is.False); | ||
|
Comment on lines
+51
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low (informational) — the four // XdcExtensions.Transactions.cs:21-24 — all four use IsTIPXDCXReceiver
public static bool IsTradingTransaction(this Transaction currentTx, IXdcReleaseSpec spec)
=> currentTx.To is not null && currentTx.To == spec.XDCXAddressBinary && spec.IsTIPXDCXReceiver;No change needed here — pinning it is cheap and correct for when a consumer lands. But it does mean the PR body's impact table overstates the mainnet blast radius: the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed and corrected in the PR body. Grepping I dropped the inert mainnet |
||
|
|
||
| Assert.That(specProvider.GetXdcSpec(activation - 1).IsTIPXDCXReceiver, Is.False); | ||
| Assert.That(specProvider.GetXdcSpec(activation).IsTIPXDCXReceiver, Is.True); | ||
| Assert.That(specProvider.GetXdcSpec(receiverDisable - 1).IsTIPXDCXReceiver, Is.True); | ||
| Assert.That(specProvider.GetXdcSpec(receiverDisable).IsTIPXDCXReceiver, Is.False); | ||
| }); | ||
| } | ||
|
|
||
| private static ChainSpec LoadChainSpec(string chainSpecFile) => | ||
| new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance).LoadEmbeddedOrFromFile(chainSpecFile); | ||
|
|
||
| private static XdcChainSpecEngineParameters EngineParameters(ChainSpec chainSpec) => | ||
| chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters<XdcChainSpecEngineParameters>(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -109,6 +109,12 @@ public void AddTransitions(SortedSet<ulong> blockNumbers, SortedSet<ulong> times | |||||||||||||||||||||||||||||||||||||
| // Without its own release spec boundary the flag would only flip on whichever transition encloses it. | ||||||||||||||||||||||||||||||||||||||
| if (DynamicGasLimitBlock is not null) | ||||||||||||||||||||||||||||||||||||||
| blockNumbers.Add(DynamicGasLimitBlock.Value); | ||||||||||||||||||||||||||||||||||||||
| if (TipXDCX is not null) | ||||||||||||||||||||||||||||||||||||||
| blockNumbers.Add(TipXDCX.Value); | ||||||||||||||||||||||||||||||||||||||
| if (TIPXDCXMinerDisable is not null) | ||||||||||||||||||||||||||||||||||||||
| blockNumbers.Add(TIPXDCXMinerDisable.Value); | ||||||||||||||||||||||||||||||||||||||
| if (TIPXDCXReceiverDisable is not null) | ||||||||||||||||||||||||||||||||||||||
| blockNumbers.Add(TIPXDCXReceiverDisable.Value); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
118
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low — two siblings with the identical defect are still unregistered.
I checked both chainspecs and neither is live today — the values happen to coincide with existing EIP transitions:
So no divergence to fix, but the same silent-failure mode is one chainspec edit away: if (TIP2019Block > 0)
blockNumbers.Add(TIP2019Block);
if (BlackListHFNumber is not null)
blockNumbers.Add(BlackListHFNumber.Value);Fork IDs stay neutral for the same reason as the three you added: |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Medium — the divergence window is only half closed by this rename.
IsTIPXDCXReceiveris baked into each release spec fromreleaseStartBlock:but
TIPXDCXReceiverDisable(andTipXDCX,TIPXDCXMinerDisable) are not registered inXdcChainSpecEngineParameters.AddTransitions, so the flag can only flip on whichever unrelated transition encloses the block — the exact caveat already documented forDynamicGasLimitBlockatXdcChainSpecEngineParameters.cs:110.Concretely:
TIPXDCXReceiverDisable66,825,000TIPXDCXReceiverDisable80,370,900TIPXDCXMinerDisable80,370,000TipXDCX(23,779,191 / 38,383,838) does coincide witheip152Transition, so activation is fine — only deactivation is late.Net effect of this PR on Apothem: trading txs to
0x…91go from "never special" (wrong for 23,779,191–66,824,999) to "special up to 71,549,999" (wrong for 66,825,000–71,549,999). Strictly better, but the special-transaction path still diverges from the reference client, which evaluatesTIPXDCXReceiverper block. The lending / trading-state keys (0x…92–0x…94) were already spelled correctly, so this part is pre-existing rather than introduced here — but it is the other half of the same bug the PR description sets out to fix.Suggested fix (same shape as the existing
DynamicGasLimitBlockhandling), plus a spec-provider test case pinningIsTIPXDCXReceiverat 66,825,000 and at 66,824,999:If you'd rather keep this PR minimal, a follow-up issue plus a note here is fine — but as it stands the PR body's claim of parity with the reference client on Apothem DEX handling doesn't hold for blocks ≥ 66,825,000.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed and folded into this PR in 03753f3 — thanks, this was the more consequential half.
Verified your analysis against the chainspec transition sets independently: activation lands on a transition on both chains, deactivation does not, except Apothem
TIPXDCXMinerDisable61,290,000 which coincides witheip1559Transition.AddTransitionsnow registers all three blocks, following theDynamicGasLimitBlockprecedent.XdcChainSpecTests.XDCX_flags_flip_on_their_own_blockspins the boundaries, deriving them from the engine parameters so the numbers stay in one place (the schedule itself is already pinned to the reference client byXdcForkIdConformanceTests). Without the fix it fails with exactly the spread you predicted: 2 assertions on mainnet, 1 on apothem. Full suite green at 621 tests, and the fork IDs are unperturbed.Findings 2 and 3 I left alone deliberately: the duplicated loader is three lines, and the unmapped-key guard needs
rewardCheckpointresolved first. Both are noted in the PR body as follow-ups.