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 }); }
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); } }