private UserOperationSimulationResult SimulateValidation(
            Transaction transaction,
            UserOperation userOperation,
            BlockHeader parent,
            ITransactionProcessor transactionProcessor)
        {
            bool paymasterWhitelisted      = _whitelistedPaymasters.Contains(userOperation.Paymaster);
            UserOperationTxTracer txTracer = new(
                transaction,
                paymasterWhitelisted,
                userOperation.InitCode != Bytes.Empty, userOperation.Sender,
                userOperation.Paymaster,
                _entryPointContractAddress,
                _logger
                );

            transactionProcessor.Trace(transaction, parent, txTracer);

            FailedOp?failedOp = _userOperationTxBuilder.DecodeEntryPointOutputError(txTracer.Output);

            string?error = null;

            if (!txTracer.Success)
            {
                if (failedOp is not null)
                {
                    error = failedOp.ToString() !;
                }
                else
                {
                    error = txTracer.Error;
                }
                return(UserOperationSimulationResult.Failed(error));
            }

            UserOperationAccessList userOperationAccessList = new(txTracer.AccessedStorage);

            IDictionary <Address, Keccak> addressesToCodeHashes = new Dictionary <Address, Keccak>();

            foreach (Address accessedAddress in txTracer.AccessedAddresses)
            {
                addressesToCodeHashes[accessedAddress] = _stateProvider.GetCodeHash(accessedAddress);
            }

            return(new UserOperationSimulationResult()
            {
                Success = true,
                AccessList = userOperationAccessList,
                AddressesToCodeHashes = addressesToCodeHashes,
                Error = error
            });
        }
        public IEnumerable <Transaction> GetTransactions(BlockHeader parent, long gasLimit)
        {
            IDictionary <Address, HashSet <UInt256> > usedAccessList = new Dictionary <Address, HashSet <UInt256> >();
            IList <UserOperation> userOperationsToInclude            = new List <UserOperation>();
            ulong gasUsed = 0;

            IEnumerable <UserOperation> userOperations =
                _userOperationPool
                .GetUserOperations()
                .Where(op => op.MaxFeePerGas >= parent.BaseFeePerGas)
                .OrderByDescending(op => CalculateUserOperationPremiumGasPrice(op, parent.BaseFeePerGas));

            foreach (UserOperation userOperation in userOperations)
            {
                if (gasUsed >= (ulong)gasLimit)
                {
                    continue;
                }

                // no intersect of accessed addresses between ops
                if (userOperation.AccessList.AccessListOverlaps(usedAccessList))
                {
                    continue;
                }

                // simulate again to make sure the op is still valid
                ResultWrapper <Keccak> result = _userOperationSimulator.Simulate(userOperation, parent);
                if (result.Result != Result.Success)
                {
                    //if (_logger.IsDebug) commented out for testing
                    {
                        _logger.Debug($"UserOperation {userOperation.Hash} resimulation unsuccessful: {result.Result.Error}");
                        // TODO: Remove logging, just for testing
                        _logger.Info($"UserOperation {userOperation.Hash} resimulation unsuccessful: {result.Result.Error}");

                        bool removeResult = _userOperationPool.RemoveUserOperation(userOperation.Hash);
                        if (_logger.IsDebug)
                        {
                            _logger.Debug(
                                removeResult ?
                                "Removed UserOperation {userOperation.Hash} from Pool"
                                : "Failed to remove UserOperation {userOperation} from Pool");
                        }
                    }

                    continue;
                }

                userOperationsToInclude.Add(userOperation);
                gasUsed += (ulong)userOperation.CallGas +
                           (ulong)userOperation.PreVerificationGas +
                           (ulong)userOperation.VerificationGas;

                // add userOp accessList to combined list
                foreach (KeyValuePair <Address, HashSet <UInt256> > kv in userOperation.AccessList.Data)
                {
                    if (usedAccessList.ContainsKey(kv.Key))
                    {
                        usedAccessList[kv.Key].UnionWith(kv.Value);
                    }
                    else
                    {
                        usedAccessList[kv.Key] = kv.Value;
                    }
                }
            }

            if (userOperationsToInclude.Count == 0)
            {
                return(new List <Transaction>());
            }

            Transaction userOperationTransaction =
                _userOperationTxBuilder.BuildTransactionFromUserOperations(
                    userOperationsToInclude,
                    parent,
                    100_000_000, // high gas to test
                    _specProvider.GetSpec(parent.Number + 1));

            if (_logger.IsDebug)
            {
                _logger.Debug($"Constructed tx from {userOperationsToInclude.Count} userOperations: {userOperationTransaction.Hash}");
            }
            // TODO: Remove logging, just for testing
            _logger.Info($"Constructed tx from {userOperationsToInclude.Count} userOperations: {userOperationTransaction.Hash}");

            BlockchainBridge.CallOutput callOutput = _userOperationSimulator.EstimateGas(parent, userOperationTransaction, CancellationToken.None);
            FailedOp?failedOp = _userOperationTxBuilder.DecodeEntryPointOutputError(callOutput.OutputData);

            if (failedOp is not null)
            {
                // TODO punish paymaster
            }

            Transaction updatedUserOperationTransaction =
                _userOperationTxBuilder.BuildTransactionFromUserOperations(
                    userOperationsToInclude,
                    parent,
                    callOutput.GasSpent,
                    _specProvider.GetSpec(parent.Number + 1));

            return(new List <Transaction> {
                updatedUserOperationTransaction
            });
        }
Beispiel #3
0
        public IEnumerable <Transaction> GetTransactions(BlockHeader parent, long gasLimit)
        {
            IDictionary <Address, HashSet <UInt256> > usedAccessList = new Dictionary <Address, HashSet <UInt256> >();
            // IList<UserOperation> userOperationsToInclude = new List<UserOperation>();
            IDictionary <Address, IList <UserOperation> > userOperationsToIncludeByEntryPoint =
                new Dictionary <Address, IList <UserOperation> >();
            ulong gasUsed = 0;

            IList <Tuple <Address, UserOperation> > combinedUserOperations = new List <Tuple <Address, UserOperation> >();

            foreach (Address entryPoint in _userOperationPools.Keys)
            {
                IEnumerable <UserOperation> entryPointUserOperations =
                    _userOperationPools[entryPoint]
                    .GetUserOperations()
                    .Where(op => op.MaxFeePerGas >= parent.BaseFeePerGas);

                foreach (UserOperation userOperation in entryPointUserOperations)
                {
                    combinedUserOperations.Add(Tuple.Create(entryPoint, userOperation));
                }
            }
            IList <Tuple <Address, UserOperation> > sortedUserOperations =
                combinedUserOperations.OrderByDescending(
                    op =>
                    CalculateUserOperationPremiumGasPrice(op.Item2, parent.BaseFeePerGas))
                .ToList();

            foreach (Tuple <Address, UserOperation> addressedUserOperation in sortedUserOperations)
            {
                (Address entryPoint, UserOperation userOperation) = addressedUserOperation;

                ulong userOperationTotalGasLimit = (ulong)userOperation.CallGas +
                                                   (ulong)userOperation.PreVerificationGas +
                                                   (ulong)userOperation.VerificationGas;

                if (gasUsed + userOperationTotalGasLimit > (ulong)gasLimit)
                {
                    continue;
                }

                // no intersect of accessed addresses between ops
                if (userOperation.AccessList.AccessListOverlaps(usedAccessList))
                {
                    continue;
                }

                // simulate again to make sure the op is still valid
                ResultWrapper <Keccak> result = _userOperationSimulators[entryPoint].Simulate(userOperation, parent);
                if (result.Result != Result.Success)
                {
                    //if (_logger.IsDebug) commented out for testing
                    {
                        _logger.Debug($"UserOperation {userOperation.RequestId!} resimulation unsuccessful: {result.Result.Error}");
                        // TODO: Remove logging, just for testing
                        _logger.Info($"UserOperation {userOperation.RequestId!} resimulation unsuccessful: {result.Result.Error}");

                        bool removeResult = _userOperationPools[entryPoint].RemoveUserOperation(userOperation.RequestId !);
                        if (_logger.IsDebug)
                        {
                            _logger.Debug(
                                removeResult ?
                                "Removed UserOperation {userOperation.Hash} from Pool"
                                : "Failed to remove UserOperation {userOperation} from Pool");
                        }
                    }

                    continue;
                }

                gasUsed += userOperationTotalGasLimit;

                // add user operation with correct entryPoint
                if (userOperationsToIncludeByEntryPoint.TryGetValue(entryPoint, out IList <UserOperation>?userOperations))
                {
                    userOperations.Add(userOperation);
                }
                else
                {
                    userOperationsToIncludeByEntryPoint[entryPoint] = new List <UserOperation> {
                        userOperation
                    };
                }

                // add userOp accessList to combined list
                foreach (KeyValuePair <Address, HashSet <UInt256> > kv in userOperation.AccessList.Data)
                {
                    if (usedAccessList.ContainsKey(kv.Key))
                    {
                        usedAccessList[kv.Key].UnionWith(kv.Value);
                    }
                    else
                    {
                        usedAccessList[kv.Key] = kv.Value;
                    }
                }
            }

            if (userOperationsToIncludeByEntryPoint.Count == 0)
            {
                yield break;
            }

            UInt256 initialNonce = _stateProvider.GetNonce(_signer.Address);
            UInt256 txsBuilt     = 0;

            // build transaction for each entryPoint with ops to be included
            foreach (KeyValuePair <Address, UserOperationTxBuilder> kv in _userOperationTxBuilders)
            {
                Address entryPoint = kv.Key;
                IUserOperationTxBuilder txBuilder = kv.Value;

                bool foundUserOperations =
                    userOperationsToIncludeByEntryPoint.TryGetValue(entryPoint, out IList <UserOperation>?userOperationsToInclude);
                if (!foundUserOperations)
                {
                    continue;
                }

                long totalGasUsed = userOperationsToInclude !.Aggregate((long)0,
                                                                        (sum, op) =>
                                                                        sum +
                                                                        (long)op.CallGas +
                                                                        (long)op.PreVerificationGas +
                                                                        (long)op.VerificationGas);

                // build test transaction to make sure it succeeds as a batch of ops
                Transaction userOperationTransaction =
                    txBuilder.BuildTransactionFromUserOperations(
                        userOperationsToInclude !,
                        parent,
                        totalGasUsed,
                        initialNonce,
                        _specProvider.GetSpec(parent.Number + 1));
                if (_logger.IsDebug)
                {
                    _logger.Debug($"Constructed tx from {userOperationsToInclude!.Count} userOperations: {userOperationTransaction.Hash}");
                }
                // TODO: Remove logging, just for testing
                _logger.Info($"Constructed tx from {userOperationsToInclude!.Count} userOperations: {userOperationTransaction.Hash}");

                BlockchainBridge.CallOutput callOutput = _userOperationSimulators[entryPoint].EstimateGas(parent, userOperationTransaction, CancellationToken.None);
                FailedOp?failedOp = txBuilder.DecodeEntryPointOutputError(callOutput.OutputData);
                if (failedOp is not null)
                {
                    UserOperation opToRemove = userOperationsToInclude[(int)failedOp.Value._opIndex];
                    _userOperationPools[entryPoint].RemoveUserOperation(opToRemove.RequestId !);
                    continue;
                }
                if (callOutput.Error != null)
                {
                    if (_logger.IsWarn)
                    {
                        _logger.Warn($"AA Simulation error for entryPoint {entryPoint}: {callOutput.Error}");
                    }
                    continue;
                }

                // construct tx with previously estimated gas limit
                Transaction updatedUserOperationTransaction =
                    _userOperationTxBuilders[entryPoint].BuildTransactionFromUserOperations(
                        userOperationsToInclude,
                        parent,
                        callOutput.GasSpent + 200000,
                        initialNonce + txsBuilt,
                        _specProvider.GetSpec(parent.Number + 1));

                txsBuilt++;
                yield return(updatedUserOperationTransaction);
            }
        }