|
9 | 9 | "github.com/ethereum/go-ethereum/common" |
10 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" |
11 | 11 | ethtypes "github.com/ethereum/go-ethereum/core/types" |
| 12 | + "github.com/ethereum/go-ethereum/core/vm" |
| 13 | + "github.com/ethereum/go-ethereum/eth/tracers/logger" |
| 14 | + "github.com/ethereum/go-ethereum/params" |
12 | 15 | "github.com/pkg/errors" |
13 | 16 |
|
14 | 17 | cmtrpcclient "github.com/cometbft/cometbft/rpc/client" |
@@ -375,3 +378,158 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *cmtrpctypes.ResultBlock, |
375 | 378 | b.EvmChainID, |
376 | 379 | ) |
377 | 380 | } |
| 381 | + |
| 382 | +// CreateAccessList returns the list of addresses and storage keys used by the transaction (except for the |
| 383 | +// sender account and precompiles), plus the estimated gas if the access list were added to the transaction. |
| 384 | +func (b *Backend) CreateAccessList(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccessListResult, error) { |
| 385 | + accessList, gasUsed, vmErr, err := b.createAccessList(args, blockNrOrHash) |
| 386 | + if err != nil { |
| 387 | + return nil, err |
| 388 | + } |
| 389 | + |
| 390 | + hexGasUsed := hexutil.Uint64(gasUsed) |
| 391 | + result := rpctypes.AccessListResult{ |
| 392 | + AccessList: &accessList, |
| 393 | + GasUsed: &hexGasUsed, |
| 394 | + } |
| 395 | + if vmErr != nil { |
| 396 | + result.Error = vmErr.Error() |
| 397 | + } |
| 398 | + return &result, nil |
| 399 | +} |
| 400 | + |
| 401 | +// createAccessList creates the access list for the transaction. |
| 402 | +// It iteratively expands the access list until it converges. |
| 403 | +// If the access list has converged, the access list is returned. |
| 404 | +// If the access list has not converged, an error is returned. |
| 405 | +// If the transaction itself fails, an vmErr is returned. |
| 406 | +func (b *Backend) createAccessList(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash) (ethtypes.AccessList, uint64, error, error) { |
| 407 | + args, err := b.SetTxDefaults(args) |
| 408 | + if err != nil { |
| 409 | + b.Logger.Error("failed to set tx defaults", "error", err) |
| 410 | + return nil, 0, nil, err |
| 411 | + } |
| 412 | + |
| 413 | + blockNum, err := b.BlockNumberFromComet(blockNrOrHash) |
| 414 | + if err != nil { |
| 415 | + b.Logger.Error("failed to get block number", "error", err) |
| 416 | + return nil, 0, nil, err |
| 417 | + } |
| 418 | + |
| 419 | + addressesToExclude, err := b.getAccessListExcludes(args, blockNum) |
| 420 | + if err != nil { |
| 421 | + b.Logger.Error("failed to get access list excludes", "error", err) |
| 422 | + return nil, 0, nil, err |
| 423 | + } |
| 424 | + |
| 425 | + prevTracer, traceArgs, err := b.initAccessListTracer(args, blockNum, addressesToExclude) |
| 426 | + if err != nil { |
| 427 | + b.Logger.Error("failed to init access list tracer", "error", err) |
| 428 | + return nil, 0, nil, err |
| 429 | + } |
| 430 | + |
| 431 | + // iteratively expand the access list |
| 432 | + for { |
| 433 | + accessList := prevTracer.AccessList() |
| 434 | + traceArgs.AccessList = &accessList |
| 435 | + res, err := b.DoCall(*traceArgs, blockNum) |
| 436 | + if err != nil { |
| 437 | + b.Logger.Error("failed to apply transaction", "error", err) |
| 438 | + return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", traceArgs.ToTransaction(ethtypes.LegacyTxType).Hash(), err) |
| 439 | + } |
| 440 | + |
| 441 | + // Check if access list has converged (no new addresses/slots accessed) |
| 442 | + newTracer := logger.NewAccessListTracer(accessList, addressesToExclude) |
| 443 | + if newTracer.Equal(prevTracer) { |
| 444 | + b.Logger.Info("access list converged", "accessList", accessList) |
| 445 | + var vmErr error |
| 446 | + if res.VmError != "" { |
| 447 | + b.Logger.Error("vm error after access list converged", "vmError", res.VmError) |
| 448 | + vmErr = errors.New(res.VmError) |
| 449 | + } |
| 450 | + return accessList, res.GasUsed, vmErr, nil |
| 451 | + } |
| 452 | + prevTracer = newTracer |
| 453 | + } |
| 454 | +} |
| 455 | + |
| 456 | +// getAccessListExcludes returns the addresses to exclude from the access list. |
| 457 | +// This includes the sender account, the target account (if provided), precompiles, |
| 458 | +// and any addresses in the authorization list. |
| 459 | +func (b *Backend) getAccessListExcludes(args evmtypes.TransactionArgs, blockNum rpctypes.BlockNumber) (map[common.Address]struct{}, error) { |
| 460 | + header, err := b.HeaderByNumber(blockNum) |
| 461 | + if err != nil { |
| 462 | + b.Logger.Error("failed to get header by number", "error", err) |
| 463 | + return nil, err |
| 464 | + } |
| 465 | + |
| 466 | + // exclude sender and precompiles |
| 467 | + addressesToExclude := make(map[common.Address]struct{}) |
| 468 | + addressesToExclude[args.GetFrom()] = struct{}{} |
| 469 | + if args.To != nil { |
| 470 | + addressesToExclude[*args.To] = struct{}{} |
| 471 | + } |
| 472 | + |
| 473 | + isMerge := b.ChainConfig().MergeNetsplitBlock != nil |
| 474 | + precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isMerge, header.Time)) |
| 475 | + for _, addr := range precompiles { |
| 476 | + addressesToExclude[addr] = struct{}{} |
| 477 | + } |
| 478 | + |
| 479 | + // check if enough gas was provided to cover all authorization lists |
| 480 | + maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas |
| 481 | + if uint64(len(args.AuthorizationList)) > maxAuthorizations { |
| 482 | + b.Logger.Error("insufficient gas to process all authorizations", "maxAuthorizations", maxAuthorizations) |
| 483 | + return nil, errors.New("insufficient gas to process all authorizations") |
| 484 | + } |
| 485 | + |
| 486 | + for _, auth := range args.AuthorizationList { |
| 487 | + // validate authorization (duplicating stateTransition.validateAuthorization() logic from geth: https://github.com/ethereum/go-ethereum/blob/bf8f63dcd27e178bd373bfe41ea718efee2851dd/core/state_transition.go#L575) |
| 488 | + nonceOverflow := auth.Nonce+1 < auth.Nonce |
| 489 | + invalidChainID := !auth.ChainID.IsZero() && auth.ChainID.CmpBig(b.ChainConfig().ChainID) != 0 |
| 490 | + if nonceOverflow || invalidChainID { |
| 491 | + b.Logger.Error("invalid authorization", "auth", auth) |
| 492 | + continue |
| 493 | + } |
| 494 | + if authority, err := auth.Authority(); err == nil { |
| 495 | + addressesToExclude[authority] = struct{}{} |
| 496 | + } |
| 497 | + } |
| 498 | + |
| 499 | + b.Logger.Debug("access list excludes created", "addressesToExclude", addressesToExclude) |
| 500 | + return addressesToExclude, nil |
| 501 | +} |
| 502 | + |
| 503 | +// initAccessListTracer initializes the access list tracer for the transaction. |
| 504 | +// It sets the default call arguments and creates a new access list tracer. |
| 505 | +// If an access list is provided in args, it uses that instead of creating a new one. |
| 506 | +func (b *Backend) initAccessListTracer(args evmtypes.TransactionArgs, blockNum rpctypes.BlockNumber, addressesToExclude map[common.Address]struct{}) (*logger.AccessListTracer, *evmtypes.TransactionArgs, error) { |
| 507 | + header, err := b.HeaderByNumber(blockNum) |
| 508 | + if err != nil { |
| 509 | + b.Logger.Error("failed to get header by number", "error", err) |
| 510 | + return nil, nil, err |
| 511 | + } |
| 512 | + |
| 513 | + if args.Nonce == nil { |
| 514 | + pending := blockNum == rpctypes.EthPendingBlockNumber |
| 515 | + nonce, err := b.getAccountNonce(args.GetFrom(), pending, blockNum.Int64(), b.Logger) |
| 516 | + if err != nil { |
| 517 | + b.Logger.Error("failed to get account nonce", "error", err) |
| 518 | + return nil, nil, err |
| 519 | + } |
| 520 | + nonce64 := hexutil.Uint64(nonce) |
| 521 | + args.Nonce = &nonce64 |
| 522 | + } |
| 523 | + if err = args.CallDefaults(b.RPCGasCap(), header.BaseFee, b.ChainConfig().ChainID); err != nil { |
| 524 | + b.Logger.Error("failed to set default call args", "error", err) |
| 525 | + return nil, nil, err |
| 526 | + } |
| 527 | + |
| 528 | + tracer := logger.NewAccessListTracer(nil, addressesToExclude) |
| 529 | + if args.AccessList != nil { |
| 530 | + tracer = logger.NewAccessListTracer(*args.AccessList, addressesToExclude) |
| 531 | + } |
| 532 | + |
| 533 | + b.Logger.Debug("access list tracer initialized", "tracer", tracer) |
| 534 | + return tracer, &args, nil |
| 535 | +} |
0 commit comments