public ResultWrapper <byte[]> eth_call(TransactionForRpc transactionCall, BlockParameter blockParameter = null) { BlockHeader block = _blockchainBridge.GetHeader(blockParameter ?? BlockParameter.Latest); var tx = transactionCall.ToTransaction(); tx.GasPrice = 0; if (tx.GasLimit < 21000) { tx.GasLimit = 10000000; } if (tx.To == null) { return(ResultWrapper <byte[]> .Fail($"Recipient address not specified on the transaction.", ErrorType.InvalidParams)); } BlockchainBridge.CallOutput result = _blockchainBridge.Call(block, tx); if (result.Error != null) { return(ResultWrapper <byte[]> .Fail($"VM Exception while processing transaction: {result.Error}", ErrorType.ExecutionError, result.OutputData)); } return(ResultWrapper <byte[]> .Success(result.OutputData)); }
public ResultWrapper <string> eth_call(TransactionForRpc transactionCall, BlockParameter blockParameter = null) { SearchResult <BlockHeader> searchResult = _blockFinder.SearchForHeader(blockParameter); if (searchResult.IsError) { return(ResultWrapper <string> .Fail(searchResult)); } BlockHeader header = searchResult.Object; if (!HasStateForBlock(header)) { return(ResultWrapper <string> .Fail($"No state available for block {header.Hash}", ErrorCodes.ResourceUnavailable)); } FixCallTx(transactionCall, header); using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(_cancellationTokenTimeout); CancellationToken cancellationToken = cancellationTokenSource.Token; Transaction tx = transactionCall.ToTransaction(); BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx, cancellationToken); return(result.Error != null ? ResultWrapper <string> .Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error) : ResultWrapper <string> .Success(result.OutputData.ToHexString(true))); }
private ResultWrapper <UInt256?> EstimateGas(TransactionForRpc transactionCall, BlockHeader head) { // 2021-03-04 08:54:18.6489|DEBUG|101|Responded to ID 40, eth_estimateGas({ // "from": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf", // "to": "0xa28afda14be5789564ae5fa03665c4180e3c680b", // "data": "0x25936984" // }) // 2021-03-04 08:54:18.6533|DEBUG|13|Responded to ID 41, eth_sendTransaction({ // "gas": "0x1f815f1", // "from": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf", // "to": "0xa28afda14be5789564ae5fa03665c4180e3c680b", // "data": "0x25936984", // "gasPrice": "0x4a817c800" // }) FixCallTx(transactionCall); // using CancellationTokenSource cancellationTokenSource = new(_cancellationTokenTimeout); CancellationToken cancellationToken = CancellationToken.None; BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(head, transactionCall.ToTransaction(), cancellationToken); if (result.Error == null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(ResultWrapper <UInt256?> .Fail(result.Error, result.InputError?ErrorCodes.InvalidInput : ErrorCodes.InternalError)); }
protected override ResultWrapper <UInt256?> ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(header, tx, token); if (result.Error is null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(result.InputError ? GetInputError(result) : ResultWrapper <UInt256?> .Fail(result.Error, ErrorCodes.InternalError)); }
protected override ResultWrapper <string> ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx, token); if (result.Error is null) { return(ResultWrapper <string> .Success(result.OutputData.ToHexString(true))); } return(result.InputError ? GetInputError(result) : ResultWrapper <string> .Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error)); }
protected override ResultWrapper <AccessListForRpc> ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { BlockchainBridge.CallOutput result = _blockchainBridge.CreateAccessList(header, tx, token, _optimize); if (result.Error is null) { return(ResultWrapper <AccessListForRpc> .Success(new(GetResultAccessList(tx, result), GetResultGas(tx, result)))); } return(result.InputError ? GetInputError(result) : ResultWrapper <AccessListForRpc> .Fail(result.Error, ErrorCodes.InternalError, new(GetResultAccessList(tx, result), GetResultGas(tx, result)))); }
public ResultWrapper <UInt256?> eth_estimateGas(TransactionForRpc transactionCall) { BlockHeader head = _blockchainBridge.FindLatestHeader(); FixCallTx(transactionCall, head); BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(head, transactionCall.ToTransaction()); if (result.Error == null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(ResultWrapper <UInt256?> .Fail(result.Error)); }
private static UInt256 GetResultGas(Transaction transaction, BlockchainBridge.CallOutput result) { long gas = result.GasSpent; if (result.AccessList is not null) { // if we generated access list, we need to fix actual gas cost, as all storage was considered warm gas -= IntrinsicGasCalculator.Calculate(transaction, Berlin.Instance); transaction.AccessList = result.AccessList; gas += IntrinsicGasCalculator.Calculate(transaction, Berlin.Instance); } return((UInt256)gas); }
public async Task call_should_invoke_blockchain_bridge_call_and_return_data() { Block head = Build.A.Block.TestObject; Transaction transaction = Build.A.Transaction.TestObject; byte[] data = new byte[] { 0, 1, 2 }; _blockchainBridge.HeadBlock.Returns(head); BlockchainBridge.CallOutput output = new BlockchainBridge.CallOutput(data, 0, null); _blockchainBridge.Call(head?.Header, transaction, default).Returns(output); byte[] result = await _ndmBridge.CallAsync(transaction); _blockchainBridge.Received().Call(head?.Header, transaction, default); result.Should().BeSameAs(data); }
public uint VerifyDeposit(Address onBehalfOf, Keccak depositId, BlockHeader head) { var txData = _abiEncoder.Encode(AbiEncodingStyle.IncludeSignature, ContractData.VerifyDepositAbiSig, depositId.Bytes); Transaction transaction = new Transaction(); transaction.Value = 0; transaction.Data = txData; transaction.To = _contractAddress; transaction.SenderAddress = onBehalfOf; transaction.GasLimit = 100000; transaction.GasPrice = 0.GWei(); transaction.Nonce = (UInt256)_blockchainBridge.GetNonce(onBehalfOf); BlockchainBridge.CallOutput callOutput = _blockchainBridge.Call(head, transaction); return((callOutput.OutputData ?? new byte[] { 0 }).ToUInt32()); }
public async Task call_should_invoke_blockchain_bridge_call_and_return_data() { var head = Build.A.BlockHeader.TestObject; var transaction = Build.A.Transaction.TestObject; var data = new byte[] { 0, 1, 2 }; _blockchainBridge.Head.Returns(head); var output = new BlockchainBridge.CallOutput(data, 0, null); _blockchainBridge.Call(head, transaction).Returns(output); var result = await _ndmBridge.CallAsync(transaction); _blockchainBridge.Received().Call(head, transaction); result.Should().BeSameAs(data); }
public async Task call_with_transaction_number_should_invoke_blockchain_bridge_call_and_return_data() { Block block = Build.A.Block.TestObject; Transaction transaction = Build.A.Transaction.TestObject; byte[] data = new byte[] { 0, 1, 2 }; _blockFinder.FindBlock(block.Number).Returns(block); BlockchainBridge.CallOutput output = new BlockchainBridge.CallOutput(data, 0, null); _blockchainBridge.Call(block.Header, transaction, default).Returns(output); byte[] result = await _ndmBridge.CallAsync(transaction, block.Number); _blockFinder.Received().FindBlock(block.Number); _blockchainBridge.Received().Call(block.Header, transaction, default); result.Should().BeSameAs(data); }
public UInt256 ReadDepositBalance(Address onBehalfOf, Keccak depositId) { var txData = _abiEncoder.Encode(AbiEncodingStyle.IncludeSignature, ContractData.DepositBalanceAbiSig, depositId.Bytes); Transaction transaction = new Transaction(); transaction.Value = 0; transaction.Data = txData; transaction.To = _contractAddress; transaction.SenderAddress = onBehalfOf; transaction.GasLimit = 100000; transaction.GasPrice = 0.GWei(); transaction.Nonce = (UInt256)_blockchainBridge.GetNonce(onBehalfOf); _wallet.Sign(transaction, _blockchainBridge.GetNetworkId()); BlockchainBridge.CallOutput callOutput = _blockchainBridge.Call(_blockchainBridge.Head, transaction); return((callOutput.OutputData ?? new byte[] { 0 }).ToUInt256()); }
private ResultWrapper <UInt256?> EstimateGas(TransactionForRpc transactionCall, BlockHeader head) { FixCallTx(transactionCall, head); var tokenTimeout = TimeSpan.FromMilliseconds(_rpcConfig.TracerTimeout); CancellationToken cancellationToken = new CancellationTokenSource(tokenTimeout).Token; BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(head, transactionCall.ToTransaction(), cancellationToken); if (result.Error == null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(ResultWrapper <UInt256?> .Fail(result.Error)); }
private ResultWrapper <UInt256?> EstimateGas(TransactionForRpc transactionCall, BlockHeader head) { FixCallTx(transactionCall, head); using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(_cancellationTokenTimeout); CancellationToken cancellationToken = cancellationTokenSource.Token; BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(head, transactionCall.ToTransaction(), cancellationToken); if (result.Error == null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(ResultWrapper <UInt256?> .Fail(result.Error)); }
public ResultWrapper <UInt256?> eth_estimateGas(TransactionForRpc transactionCall) { BlockHeader head = _blockchainBridge.FindLatestHeader(); if (!HasStateForBlock(head)) { return(ResultWrapper <UInt256?> .Fail($"No state available for block {head.Hash}", ErrorCodes.ResourceUnavailable)); } FixCallTx(transactionCall, head); BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(head, transactionCall.ToTransaction()); if (result.Error == null) { return(ResultWrapper <UInt256?> .Success((UInt256)result.GasSpent)); } return(ResultWrapper <UInt256?> .Fail(result.Error)); }
public ResultWrapper <string> eth_call(TransactionForRpc transactionCall, BlockParameter blockParameter = null) { SearchResult <BlockHeader> searchResult = _blockchainBridge.SearchForHeader(blockParameter); if (searchResult.IsError) { return(ResultWrapper <string> .Fail(searchResult)); } BlockHeader header = searchResult.Object; FixCallTx(transactionCall, header); Transaction tx = transactionCall.ToTransaction(); BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx); return(result.Error != null ? ResultWrapper <string> .Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error) : ResultWrapper <string> .Success(result.OutputData.ToHexString(true))); }
public ResultWrapper <byte[]> eth_call(TransactionForRpc transactionCall, BlockParameter blockParameter = null) { try { _readerWriterLockSlim.EnterWriteLock(); BlockHeader block = blockParameter == null ? _blockchainBridge.Head : GetBlock(blockParameter).Data.Header; BlockchainBridge.CallOutput result = _blockchainBridge.Call(block, transactionCall.ToTransaction()); if (result.Error != null) { return(ResultWrapper <byte[]> .Fail($"VM Exception while processing transaction: {result.Error}", ErrorType.ExecutionError, result.OutputData)); } return(ResultWrapper <byte[]> .Success(result.OutputData)); } finally { _readerWriterLockSlim.ExitWriteLock(); } }
public ResultWrapper <string> eth_call(TransactionForRpc transactionCall, BlockParameter blockParameter = null) { BlockHeader block = _blockchainBridge.GetHeader(blockParameter ?? BlockParameter.Latest); var tx = transactionCall.ToTransaction(); tx.GasPrice = 0; if (tx.GasLimit < 21000) { tx.GasLimit = 10000000; } BlockchainBridge.CallOutput result = _blockchainBridge.Call(block, tx); if (result.Error != null) { return(ResultWrapper <string> .Fail("VM execution error.", ErrorType.ExecutionError, result.Error)); } return(ResultWrapper <string> .Success(result.OutputData.ToHexString(true))); }
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); } }
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 }); }
protected ResultWrapper <TResult> GetInputError(BlockchainBridge.CallOutput result) => ResultWrapper <TResult> .Fail(result.Error ?? string.Empty, ErrorCodes.InvalidInput);
private static AccessListItemForRpc[] GetResultAccessList(Transaction tx, BlockchainBridge.CallOutput result) { AccessList?accessList = result.AccessList ?? tx.AccessList; return(accessList is null?Array.Empty <AccessListItemForRpc>() : AccessListItemForRpc.FromAccessList(accessList)); }