Skip to content

Nitro has two separate gas prices and Nethermind only one (sepolia sync) #402

@wurdum

Description

@wurdum

The issue faced at 55713322.

Nitro has two different gas prices:

Message's gas price is being set in https://github.com/OffchainLabs/go-ethereum/blob/master/core/state_transition.go#L337
TxContext's price is set from Message's https://github.com/OffchainLabs/go-ethereum/blob/master/core/evm.go#L89

Then... Message's gas price can be overridden https://github.com/OffchainLabs/go-ethereum/blob/master/core/state_transition.go#L601

In Nethermind we have only Message's gas price equivalent. That leads to an incorrect gas price being assigned to EvmData at StylusPrograms.

Quick fix of TransactionProcessorBase like below resolves the issue (see effectiveGasPriceForTxContext variable added)

protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, ExecutionOptions opts)
{
    if (Out.IsTargetBlock)
        Out.Log($"evm execute options={opts}");

    BlockHeader header = VirtualMachine.BlockExecutionContext.Header;
    IReleaseSpec spec = GetSpec(header);

    // restore is CallAndRestore - previous call, we will restore state after the execution
    bool restore = opts.HasFlag(ExecutionOptions.Restore);
    // commit - is for standard execute, we will commit thee state after execution
    // !commit - is for build up during block production, we won't commit state after each transaction to support rollbacks
    // we commit only after all block is constructed
    bool commit = opts.HasFlag(ExecutionOptions.Commit) || (!opts.HasFlag(ExecutionOptions.SkipValidation) && !spec.IsEip658Enabled);

    TransactionResult result;
    IntrinsicGas intrinsicGas = CalculateIntrinsicGas(tx, spec);

    if (Out.IsTargetBlock)
        Out.Log($"intrinsic gas standard={intrinsicGas.Standard} floor={intrinsicGas.FloorGas}");

    if (!(result = ValidateStatic(tx, header, spec, opts, in intrinsicGas))) return result;

    UInt256 effectiveGasPriceForTxContext = tx.CalculateEffectiveGasPrice(spec.IsEip1559Enabled, in header.BaseFeePerGas);
    UInt256 effectiveGasPrice = CalculateEffectiveGasPrice(tx, spec.IsEip1559Enabled, header.BaseFeePerGas);

    if (Out.IsTargetBlock)
        Out.Log($"evm call effectiveGasPrice msg={effectiveGasPrice} txContext={effectiveGasPriceForTxContext} " +
                $"maxPriorityFeePerGas={tx.MaxPriorityFeePerGas} maxFeePerGas={tx.MaxFeePerGas} " +
                $"baseFeePerGas={header.BaseFeePerGas} eip1559Enabled={spec.IsEip1559Enabled}");

    VirtualMachine.SetTxExecutionContext(new(tx.SenderAddress, _codeInfoRepository, tx.BlobVersionedHashes, in effectiveGasPriceForTxContext));

    UpdateMetrics(opts, effectiveGasPrice);

    bool deleteCallerAccount = RecoverSenderIfNeeded(tx, spec, opts, effectiveGasPrice);

    if (!(result = ValidateSender(tx, header, spec, tracer, opts))) return result;
    if (!(result = BuyGas(tx, spec, tracer, opts, effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee))) return result;
    if (!(result = IncrementNonce(tx, header, spec, tracer, opts))) return result;

    if (commit) WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullTxTracer.Instance, commitRoots: false);

    // substate.Logs contains a reference to accessTracker.Logs so we can't Dispose until end of the method
    using StackAccessTracker accessTracker = new();

    int delegationRefunds = (!spec.IsEip7702Enabled || !tx.HasAuthorizationList) ? 0 : ProcessDelegations(tx, spec, accessTracker);

    if (!(result = CalculateAvailableGas(tx, intrinsicGas, out long gasAvailable))) return result;
    if (!(result = BuildExecutionEnvironment(tx, spec, _codeInfoRepository, accessTracker, out ExecutionEnvironment env))) return result;

    if (Out.IsTargetBlock)
        Out.Log($"evm call from={tx.SenderAddress} to={tx.To} gasAvailable={gasAvailable} value={tx.Value}");

    int statusCode = !tracer.IsTracingInstructions ?
        ExecuteEvmCall<OffFlag>(tx, header, spec, tracer, opts, delegationRefunds, intrinsicGas, accessTracker, gasAvailable, env, out TransactionSubstate substate, out GasConsumed spentGas, in effectiveGasPrice) :
        ExecuteEvmCall<OnFlag>(tx, header, spec, tracer, opts, delegationRefunds, intrinsicGas, accessTracker, gasAvailable, env, out substate, out spentGas, in effectiveGasPrice);

    PayFees(tx, header, spec, tracer, in substate, spentGas.SpentGas, premiumPerGas, blobBaseFee, statusCode);
    tx.SpentGas = spentGas.SpentGas;

    // Finalize
    if (restore)
    {
        WorldState.Reset(resetBlockChanges: false);
        if (deleteCallerAccount)
        {
            WorldState.DeleteAccount(tx.SenderAddress!);
        }
        else
        {
            if (!opts.HasFlag(ExecutionOptions.SkipValidation))
                WorldState.AddToBalance(tx.SenderAddress!, senderReservedGasPayment, spec);
            DecrementNonce(tx);

            WorldState.Commit(spec, commitRoots: false);
        }
    }
    else if (commit)
    {
        WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullStateTracer.Instance, commitRoots: !spec.IsEip658Enabled);
    }
    else
    {
        WorldState.ResetTransient();
    }

    if (tracer.IsTracingReceipt)
    {
        Hash256 stateRoot = null;
        if (!spec.IsEip658Enabled)
        {
            WorldState.RecalculateStateRoot();
            stateRoot = WorldState.StateRoot;
        }

        if (Out.IsTargetBlock)
            Out.Log($"receipt statusCode={statusCode} spentGas={spentGas} error={substate.Error} " +
                    $"logsCount={substate.Logs.Count} evmExceptionType={substate.EvmExceptionType}");

        if (statusCode == StatusCode.Failure)
        {
            byte[] output = substate.ShouldRevert ? substate.Output.Bytes.ToArray() : [];
            tracer.MarkAsFailed(env.ExecutingAccount, spentGas, output, substate.Error, stateRoot);
            if (Out.IsTargetBlock)
                Out.Log($"receipt markAsFailed evmExceptionType={result.EvmExceptionType} result={result.ToString()}");
        }
        else
        {
            LogEntry[] logs = substate.Logs.Count != 0 ? substate.Logs.ToArray() : [];
            tracer.MarkAsSuccess(env.ExecutingAccount, spentGas, substate.Output.Bytes.ToArray(), logs, stateRoot);
            if (Out.IsTargetBlock)
                Out.Log($"receipt markAsSuccess evmExceptionType={result.EvmExceptionType} result={result.ToString()} " +
                        $"logs={string.Join(";", logs?.Select(l => $"a={l.Address}, d={l.Data.ToHexString()}") ?? [])}");
        }
    }

    if (substate.EvmExceptionType != EvmExceptionType.None)
        return TransactionResult.EvmException(substate.EvmExceptionType);

    return TransactionResult.Ok;
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No fields configured for Bug.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions