Skip to content

feat: upstream changes for ArbOS 60 multi-gas constraints#10765

Open
AnkushinDaniil wants to merge 12 commits intomasterfrom
daniil/fix-multigas-comparison
Open

feat: upstream changes for ArbOS 60 multi-gas constraints#10765
AnkushinDaniil wants to merge 12 commits intomasterfrom
daniil/fix-multigas-comparison

Conversation

@AnkushinDaniil
Copy link
Copy Markdown
Contributor

@AnkushinDaniil AnkushinDaniil commented Mar 10, 2026

Changes

  • Add IResettableBlockTree interface for test state reset (following IClearableCache pattern from feat: Add ClearCache() to store interfaces for testing support #10577)
  • Add ConsumeSelfDestructBeneficiaryAccessGas to IGasPolicy for multi-gas resource tracking — separate from ConsumeAccountAccessGas because the resource split differs: cold access charges full ColdAccountAccess to StorageAccess only (no Computation split), and warm access charges nothing (vs. WarmStateRead as Computation in regular access)
  • Track StorageGrowth for SELFDESTRUCT new account creation via ConsumeNewAccountCreation
  • Support tuple ABI types in AbiType.TryParse for dynamic decoding in precompiles
  • Add TrieStore.ClearForTesting() and make CacheCodeInfoRepository.Clear() public
  • Implement IClearableCache on CacheCodeInfoRepository via nested CacheClearService
  • Extract ITestableTrieStore interface for test isolation

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

Remarks

Consumer PR: NethermindEth/nethermind-arbitrum#700 (ArbOS 60 multi-gas constraint pricing).
Pattern reference: #10577 (IClearableCache).

@AnkushinDaniil AnkushinDaniil force-pushed the daniil/fix-multigas-comparison branch from a90e97a to 41f5be0 Compare March 10, 2026 09:25
@AnkushinDaniil AnkushinDaniil changed the title fix(blocktree): add ResetInternalState method for test reinitialization feat(blocktree): add IResettableBlockTree interface for test state reset Mar 10, 2026
@github-actions github-actions bot added the evm label Mar 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

EVM Opcode Benchmark Diff

Aggregated runs: base=1, pr=1

No significant regressions or improvements detected.

@LukaszRozmej
Copy link
Copy Markdown
Member

Can't you achieve similar with inheritance, or probably better - decorator, that on reset would recreate internal BlockTree from scratch?

@github-actions github-actions bot added the trie label Mar 20, 2026
@AnkushinDaniil AnkushinDaniil force-pushed the daniil/fix-multigas-comparison branch 2 times, most recently from 4d96f5f to 2acad04 Compare March 22, 2026 15:08
@AnkushinDaniil AnkushinDaniil changed the title feat(blocktree): add IResettableBlockTree interface for test state reset feat: upstream changes for ArbOS 60 multi-gas constraints Mar 22, 2026
@AnkushinDaniil AnkushinDaniil marked this pull request as ready for review March 22, 2026 20:18
Comment on lines +71 to +89
if (!spec.UseHotAndColdStorage) return true;
if (isTracingAccess)
{
if (isTracingAccess)
{
// Ensure that tracing simulates access-list behavior.
accessTracker.WarmUp(address);
}
// Ensure that tracing simulates access-list behavior.
accessTracker.WarmUp(address);
}

// If the account is cold (and not a precompile), charge the cold access cost.
if (!spec.IsPrecompile(address) && accessTracker.WarmUp(address))
{
result = UpdateGas(ref gas, GasCostOf.ColdAccountAccess);
}
else if (chargeForWarm)
bool result;
// If the account is cold (and not a precompile), charge the cold access cost.
if (!spec.IsPrecompile(address) && accessTracker.WarmUp(address))
result = UpdateGas(ref gas, GasCostOf.ColdAccountAccess);
else
{
result = kind switch
{
// Otherwise, if warm access should be charged, apply the warm read cost.
result = UpdateGas(ref gas, GasCostOf.WarmStateRead);
}
AccountAccessKind.SelfDestructBeneficiary => true, // no warm charge
AccountAccessKind.Default => UpdateGas(ref gas, GasCostOf.WarmStateRead),
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null)
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could do something like:

        if (!spec.UseHotAndColdStorage) return true;

        if (isTracingAccess) accessTracker.WarmUp(address);

        long gasCost = !spec.IsPrecompile(address) && accessTracker.WarmUp(address)
            ? GasCostOf.ColdAccountAccess
            : kind == AccountAccessKind.SelfDestructBeneficiary
                ? GasCostOf.Free
                : GasCostOf.WarmStateRead;

        return UpdateGas(ref gas, gasCost);

but not sure if calling UpdateGas with Free would change logic or not - maybe?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could do:

        if (!spec.UseHotAndColdStorage) return true;

        if (isTracingAccess) accessTracker.WarmUp(address);

        long gasCost = !spec.IsPrecompile(address) && accessTracker.WarmUp(address)
            ? GasCostOf.ColdAccountAccess
            : kind == AccountAccessKind.SelfDestructBeneficiary
                ? GasCostOf.Free
                : GasCostOf.WarmStateRead;

        return gasCost  == GasCostOf.Free || UpdateGas(ref gas, gasCost);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also have switch expression:

        long gasCost = (!spec.IsPrecompile(address) && accessTracker.WarmUp(address)) switch
        {
            true => GasCostOf.ColdAccountAccess,
            false when kind == AccountAccessKind.SelfDestructBeneficiary => GasCostOf.Free,
            false => GasCostOf.WarmStateRead
        };

Add IResettableBlockTree interface following the IClearableCache pattern
from PR #10577. BlockTree implements it explicitly, allowing derived classes
to reset internal state (Head, BestSuggestedHeader, etc.) for testing.

Usage: (blockTree as IResettableBlockTree)?.ResetInternalState()

This enables ArbitrumBlockTree to properly reset state during comparison
testing without recreating the DI container.
Add BestSuggestedBeaconHeader, BestSuggestedBeaconBody, FinalizedHash,
and SafeHash resets to IResettableBlockTree.ResetInternalState().

Also add thread-safety documentation to the interface and implementation
clarifying that callers must ensure no concurrent operations.
Use ConsumeNewAccountCreation instead of UpdateGas for SELFDESTRUCT
new account creation cost. This properly tracks the 25000 gas as
StorageGrowth resource kind, matching Nitro's CreateBySelfdestructGas
handling in operations_acl.go and gas_table.go.
…racking

Add dedicated method for SELFDESTRUCT beneficiary account access gas charging.
This enables Arbitrum to charge cold access as full StorageAccess (no Computation
split) matching Nitro's makeSelfdestructGasFn behavior in operations_acl.go.

- Add ConsumeSelfDestructBeneficiaryAccessGas to IGasPolicy interface
- Add EthereumGasPolicy implementation (delegates to ConsumeAccountAccessGas)
- Update EvmInstructions.ControlFlow.cs to use new method for SELFDESTRUCT
….Clear() public

TrieStore.ClearForTesting() resets all internal state (dirty nodes, persisted
hashes, commit queue, counters) for test isolation in comparison mode.
CommitSetQueue.Clear() added to support this.
CacheCodeInfoRepository.Clear() made public for external test access.
Add tuple type recognition in AbiType.GetTupleFromString so that ABI
definitions with tuple parameters (e.g. MultiGas pricing constraints)
can be parsed at runtime. Without this, ArbOwnerParser fails to register
function selectors for tuple-based methods.
- TrieStore implements IClearableCache directly (eliminates external wrapper)
- CacheCodeInfoRepository implements IClearableCache, revert Clear() to internal
- Remove IResettableBlockTree interface (replaced by Arbitrum decorator)
Replace public static Clear() with explicit IClearableCache interface
implementation so cache clearing is only accessible through the DI-
discovered interface, not via cross-assembly visibility hacks.

Rejected: public static Clear() | exposes global cache destruction to all consumers
Rejected: InternalsVisibleTo("Nethermind.Arbitrum") | anti-pattern for production assemblies
Constraint: Benchmark assembly uses InternalsVisibleTo already, kept Clear() as internal static
Confidence: high
Scope-risk: narrow
…ssGas

AccountAccessKind enum already encodes whether warm charge should be
applied — SelfDestructBeneficiary skips warm charge, Default always
charges. The chargeForWarm boolean was only ever false in the
SELFDESTRUCT context, making it fully redundant with the kind
discriminator.

Constraint: chargeForWarm was only false at the SELFDESTRUCT call site
Rejected: Keep both parameters for flexibility | no other caller needs chargeForWarm=false
Replace if/else with nested switch with a single switch expression
on the cold/warm result, as suggested in code review.
@AnkushinDaniil AnkushinDaniil force-pushed the daniil/fix-multigas-comparison branch from cb8645c to 900b21a Compare March 25, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants