private async Task ExecuteInlineTransactions(int depth, Timestamp currentBlockTime, ITransactionContext txContext, TieredStateCache internalStateCache, IChainContext internalChainContext, CancellationToken cancellationToken) { var trace = txContext.Trace; internalStateCache.Update(txContext.Trace.GetStateSets()); foreach (var inlineTx in txContext.Trace.InlineTransactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = depth + 1, ChainContext = internalChainContext, Transaction = inlineTx, CurrentBlockTime = currentBlockTime, Origin = txContext.Origin }; var inlineTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (inlineTrace == null) { break; } trace.InlineTraces.Add(inlineTrace); if (!inlineTrace.IsSuccessful()) { Logger.LogWarning($"Method name: {inlineTx.MethodName}, {inlineTrace.Error}"); // Already failed, no need to execute remaining inline transactions break; } internalStateCache.Update(inlineTrace.GetStateSets()); } }
private TransactionContext CreateTransactionContext(SingleTransactionExecutingDto singleTxExecutingDto, out TransactionTrace trace) { if (singleTxExecutingDto.Transaction.To == null || singleTxExecutingDto.Transaction.From == null) { throw new Exception($"error tx: {singleTxExecutingDto.Transaction}"); } trace = new TransactionTrace { TransactionId = singleTxExecutingDto.Transaction.GetHash() }; var txContext = new TransactionContext { PreviousBlockHash = singleTxExecutingDto.ChainContext.BlockHash, CurrentBlockTime = singleTxExecutingDto.CurrentBlockTime, Transaction = singleTxExecutingDto.Transaction, BlockHeight = singleTxExecutingDto.ChainContext.BlockHeight + 1, Trace = trace, CallDepth = singleTxExecutingDto.Depth, StateCache = singleTxExecutingDto.ChainContext.StateCache, Origin = singleTxExecutingDto.Origin != null ? singleTxExecutingDto.Origin : singleTxExecutingDto.Transaction.From }; return(txContext); }
protected override async Task <TransactionTrace> ExecuteOneAsync( SingleTransactionExecutingDto singleTxExecutingDto, CancellationToken cancellationToken) { if (singleTxExecutingDto.IsCancellable) { cancellationToken.ThrowIfCancellationRequested(); } singleTxExecutingDto.OriginTransactionId = _pluginOriginId; var txContext = CreateTransactionContext(singleTxExecutingDto); var trace = txContext.Trace; var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); IExecutive executive; try { executive = await _smartContractExecutiveService.GetExecutiveAsync( internalChainContext, singleTxExecutingDto.Transaction.To); } catch (SmartContractFindRegistrationException) { txContext.Trace.ExecutionStatus = ExecutionStatus.ContractError; txContext.Trace.Error += "Invalid contract address.\n"; return(trace); } try { await executive.ApplyAsync(txContext); if (txContext.Trace.IsSuccessful()) { await ExecuteInlineTransactions(singleTxExecutingDto.Depth, singleTxExecutingDto.CurrentBlockTime, txContext, internalStateCache, internalChainContext, singleTxExecutingDto.OriginTransactionId, cancellationToken); } } catch (Exception ex) { Logger.LogError(ex, "Transaction execution failed."); txContext.Trace.ExecutionStatus = ExecutionStatus.ContractError; txContext.Trace.Error += ex + "\n"; throw; } finally { await _smartContractExecutiveService.PutExecutiveAsync(singleTxExecutingDto.ChainContext, singleTxExecutingDto.Transaction.To, executive); } return(trace); }
private async Task <bool> ExecutePluginOnPreTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; foreach (var plugin in _prePlugins) { var transactions = await plugin.GetPreTransactionsAsync(executive.Descriptors, txContext); foreach (var preTx in transactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = internalChainContext, Transaction = preTx, CurrentBlockTime = currentBlockTime }; var preTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (preTrace == null) { return(false); } trace.PreTransactions.Add(preTx); trace.PreTraces.Add(preTrace); if (preTx.MethodName == "ChargeTransactionFees") { var txFee = new TransactionFee(); txFee.MergeFrom(preTrace.ReturnValue); trace.TransactionFee = txFee; } if (!preTrace.IsSuccessful()) { return(false); } var stateSets = preTrace.GetStateSets().ToList(); internalStateCache.Update(stateSets); var parentStateCache = txContext.StateCache as TieredStateCache; parentStateCache?.Update(stateSets); if (trace.TransactionFee == null || !trace.TransactionFee.IsFailedToCharge) { continue; } preTrace.ExecutionStatus = ExecutionStatus.Executed; return(false); } } return(true); }
private async Task <bool> ExecutePluginOnPreTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; foreach (var plugin in _prePlugins) { var transactions = await plugin.GetPreTransactionsAsync(executive.Descriptors, txContext); foreach (var preTx in transactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, //TODO: this 0 means it is possible that pre/post txs could have own pre/post txs ChainContext = internalChainContext, Transaction = preTx, CurrentBlockTime = currentBlockTime, OriginTransactionId = txContext.OriginTransactionId }; var preTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (preTrace == null) { return(false); } trace.PreTransactions.Add(preTx); trace.PreTraces.Add(preTrace); if (!preTrace.IsSuccessful()) { return(false); } var stateSets = preTrace.GetStateSets().ToList(); internalStateCache.Update(stateSets); var parentStateCache = txContext.StateCache as TieredStateCache; parentStateCache?.Update(stateSets); if (!plugin.IsStopExecuting(preTrace.ReturnValue, out var error)) { continue; } // If pre-tx fails, still commit the changes, but return false to notice outside to stop the execution. preTrace.Error = error; preTrace.ExecutionStatus = ExecutionStatus.Executed; return(false); } } return(true); }
private async Task <bool> ExecutePluginOnPostTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; if (!trace.IsSuccessful()) { // If failed to execute this tx, at least we need to commit pre traces. internalStateCache = new TieredStateCache(txContext.StateCache); foreach (var preTrace in txContext.Trace.PreTraces) { var stateSets = preTrace.GetStateSets(); internalStateCache.Update(stateSets); } internalChainContext.StateCache = internalStateCache; } foreach (var plugin in _postPlugins) { var transactions = await plugin.GetPostTransactionsAsync(executive.Descriptors, txContext); foreach (var postTx in transactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = internalChainContext, Transaction = postTx, CurrentBlockTime = currentBlockTime, OriginTransactionId = txContext.OriginTransactionId }; var postTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (postTrace == null) { return(false); } trace.PostTransactions.Add(postTx); trace.PostTraces.Add(postTrace); if (!postTrace.IsSuccessful()) { return(false); } internalStateCache.Update(postTrace.GetStateSets()); } } return(true); }
private TransactionTrace ValidateCancellationRequest(SingleTransactionExecutingDto singleTxExecutingDto, bool isCancellationRequested) { if (singleTxExecutingDto.IsCancellable && isCancellationRequested) { return(new TransactionTrace { TransactionId = singleTxExecutingDto.Transaction.GetHash(), ExecutionStatus = ExecutionStatus.Canceled, Error = "Execution cancelled" }); } return(null); }
protected override async Task <TransactionTrace> ExecuteOneAsync(SingleTransactionExecutingDto singleTxExecutingDto, CancellationToken cancellationToken) { TransactionTrace trace = null; try { trace = await base.ExecuteOneAsync(singleTxExecutingDto, cancellationToken); } finally { await LocalEventBus.PublishAsync(new TransactionExecutedEventData { TransactionTrace = trace }); } return(trace); }
protected ITransactionContext CreateTransactionContext(SingleTransactionExecutingDto singleTxExecutingDto) { if (singleTxExecutingDto.Transaction.To == null || singleTxExecutingDto.Transaction.From == null) { throw new Exception($"error tx: {singleTxExecutingDto.Transaction}"); } var origin = singleTxExecutingDto.Origin != null ? singleTxExecutingDto.Origin : singleTxExecutingDto.Transaction.From; var txContext = _transactionContextFactory.Create(singleTxExecutingDto.Transaction, singleTxExecutingDto.ChainContext, singleTxExecutingDto.OriginTransactionId, origin, singleTxExecutingDto.Depth, singleTxExecutingDto.CurrentBlockTime); return(txContext); }
private async Task ExecuteInlineTransactions(int depth, Timestamp currentBlockTime, ITransactionContext txContext, TieredStateCache internalStateCache, IChainContext internalChainContext, CancellationToken cancellationToken) { var trace = txContext.Trace; internalStateCache.Update(txContext.Trace.GetStateSets()); foreach (var inlineTx in txContext.Trace.InlineTransactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = depth + 1, ChainContext = internalChainContext, Transaction = inlineTx, CurrentBlockTime = currentBlockTime, Origin = txContext.Origin }; // Only system contract can send TransferFrom tx as inline tx. if (!_inlineTransactionValidationService.Validate(inlineTx)) { break; } var inlineTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (inlineTrace == null) { break; } trace.InlineTraces.Add(inlineTrace); if (!inlineTrace.IsSuccessful()) { // Already failed, no need to execute remaining inline transactions break; } internalStateCache.Update(inlineTrace.GetStateSets()); } }
private async Task <bool> ExecutePluginOnPostTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; if (!trace.IsSuccessful()) { internalStateCache = new TieredStateCache(txContext.StateCache); foreach (var preTrace in txContext.Trace.PreTraces) { var stateSets = preTrace.GetStateSets(); internalStateCache.Update(stateSets); } internalChainContext.StateCache = internalStateCache; } foreach (var plugin in _postPlugins) { var transactions = await plugin.GetPostTransactionsAsync(executive.Descriptors, txContext); foreach (var postTx in transactions) { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = internalChainContext, Transaction = postTx, CurrentBlockTime = currentBlockTime }; var postTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken); if (postTrace == null) { return(false); } trace.PostTransactions.Add(postTx); trace.PostTraces.Add(postTrace); if (postTx.MethodName == "ChargeResourceToken") { var consumedResourceTokens = new ConsumedResourceTokens(); consumedResourceTokens.MergeFrom(postTrace.ReturnValue); trace.ConsumedResourceTokens = consumedResourceTokens; } if (!postTrace.IsSuccessful()) { return(false); } internalStateCache.Update(postTrace.GetStateSets()); } } return(true); }
private async Task <bool> ExecutePluginOnPostTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; if (!trace.IsSuccessful()) { internalStateCache = new TieredStateCache(txContext.StateCache); foreach (var preTrace in txContext.Trace.PreTraces) { var stateSets = preTrace.GetStateSets(); internalStateCache.Update(stateSets); } internalChainContext.StateCache = internalStateCache; } foreach (var plugin in _postPlugins) { var transactions = await plugin.GetPostTransactionsAsync(executive.Descriptors, txContext); foreach (var postTx in transactions) { TransactionTrace postTrace; try { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = internalChainContext, Transaction = postTx, CurrentBlockTime = currentBlockTime, IsCancellable = false }; postTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken).WithCancellation(cancellationToken); } catch (OperationCanceledException) { Logger.LogTrace("execute transaction timeout"); return(false); } if (postTrace == null) { return(false); } trace.PostTransactions.Add(postTx); trace.PostTraces.Add(postTrace); if (!postTrace.IsSuccessful()) { trace.ExecutionStatus = ExecutionStatus.Postfailed; postTrace.SurfaceUpError(); trace.Error += postTrace.Error; return(false); } internalStateCache.Update(postTrace.GetStateSets()); } } return(true); }
public async Task <List <ExecutionReturnSet> > ExecuteAsync(TransactionExecutingDto transactionExecutingDto, CancellationToken cancellationToken, bool throwException) { try { var groupStateCache = transactionExecutingDto.PartialBlockStateSet == null ? new TieredStateCache() : new TieredStateCache( new StateCacheFromPartialBlockStateSet(transactionExecutingDto.PartialBlockStateSet)); var groupChainContext = new ChainContextWithTieredStateCache( transactionExecutingDto.BlockHeader.PreviousBlockHash, transactionExecutingDto.BlockHeader.Height - 1, groupStateCache); var transactionResults = new List <TransactionResult>(); var returnSets = new List <ExecutionReturnSet>(); foreach (var transaction in transactionExecutingDto.Transactions) { TransactionTrace trace; if (cancellationToken.IsCancellationRequested) { break; } var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = groupChainContext, Transaction = transaction, CurrentBlockTime = transactionExecutingDto.BlockHeader.Time, }; try { var task = Task.Run(() => ExecuteOneAsync(singleTxExecutingDto, cancellationToken), cancellationToken); trace = await task.WithCancellation(cancellationToken); } catch (OperationCanceledException) { Logger.LogTrace($"transaction canceled"); break; } if (trace == null) { break; } // Will be useful when debugging MerkleTreeRootOfWorldState is different from each miner. /* * Logger.LogTrace(transaction.MethodName); * Logger.LogTrace(trace.StateSet.Writes.Values.Select(v => v.ToBase64().ComputeHash().ToHex()) * .JoinAsString("\n")); */ if (!trace.IsSuccessful()) { if (throwException) { Logger.LogError(trace.Error); } // Do not package this transaction if any of his inline transactions canceled. if (IsTransactionCanceled(trace)) { break; } var transactionExecutingStateSets = new List <TransactionExecutingStateSet>(); foreach (var preTrace in trace.PreTraces) { if (preTrace.IsSuccessful()) { transactionExecutingStateSets.AddRange(preTrace.GetStateSets()); } } foreach (var postTrace in trace.PostTraces) { if (postTrace.IsSuccessful()) { transactionExecutingStateSets.AddRange(postTrace.GetStateSets()); } } groupStateCache.Update(transactionExecutingStateSets); trace.SurfaceUpError(); } else { groupStateCache.Update(trace.GetStateSets()); } if (trace.Error != string.Empty) { Logger.LogError(trace.Error); } var result = GetTransactionResult(trace, transactionExecutingDto.BlockHeader.Height); if (result != null) { result.TransactionFee = trace.TransactionFee; transactionResults.Add(result); } var returnSet = GetReturnSet(trace, result); returnSets.Add(returnSet); } await _transactionResultService.AddTransactionResultsAsync(transactionResults, transactionExecutingDto.BlockHeader); return(returnSets); } catch (Exception e) { Logger.LogTrace("Failed while executing txs in block.", e); throw; } }
public async Task <List <ExecutionReturnSet> > ExecuteAsync(TransactionExecutingDto transactionExecutingDto, CancellationToken cancellationToken) { try { var groupStateCache = transactionExecutingDto.PartialBlockStateSet == null ? new TieredStateCache() : new TieredStateCache( new StateCacheFromPartialBlockStateSet(transactionExecutingDto.PartialBlockStateSet)); var groupChainContext = new ChainContextWithTieredStateCache( transactionExecutingDto.BlockHeader.PreviousBlockHash, transactionExecutingDto.BlockHeader.Height - 1, groupStateCache); var transactionResults = new List <TransactionResult>(); var returnSets = new List <ExecutionReturnSet>(); foreach (var transaction in transactionExecutingDto.Transactions) { TransactionTrace trace; if (cancellationToken.IsCancellationRequested) { break; } var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = groupChainContext, Transaction = transaction, CurrentBlockTime = transactionExecutingDto.BlockHeader.Time, }; try { var transactionExecutionTask = Task.Run(() => ExecuteOneAsync(singleTxExecutingDto, cancellationToken), cancellationToken); trace = await transactionExecutionTask.WithCancellation(cancellationToken); } catch (OperationCanceledException) { Logger.LogTrace("Transaction canceled."); if (cancellationToken.IsCancellationRequested) { break; } continue; } if (trace == null) { break; } if (!trace.IsSuccessful()) { // Do not package this transaction if any of his inline transactions canceled. if (IsTransactionCanceled(trace)) { break; } var transactionExecutingStateSets = new List <TransactionExecutingStateSet>(); foreach (var preTrace in trace.PreTraces) { if (preTrace.IsSuccessful()) { transactionExecutingStateSets.AddRange(preTrace.GetStateSets()); } } foreach (var postTrace in trace.PostTraces) { if (postTrace.IsSuccessful()) { transactionExecutingStateSets.AddRange(postTrace.GetStateSets()); } } groupStateCache.Update(transactionExecutingStateSets); trace.SurfaceUpError(); } else { groupStateCache.Update(trace.GetStateSets()); } #if DEBUG if (trace.Error != string.Empty) { Logger.LogError(trace.Error); } #endif var result = GetTransactionResult(trace, transactionExecutingDto.BlockHeader.Height); transactionResults.Add(result); var returnSet = GetReturnSet(trace, result); returnSets.Add(returnSet); } await _transactionResultService.AddTransactionResultsAsync(transactionResults, transactionExecutingDto.BlockHeader); return(returnSets); } catch (Exception e) { Logger.LogError(e, "Failed while executing txs in block."); throw; } }
private async Task <TransactionTrace> ExecuteOneAsync(SingleTransactionExecutingDto singleTxExecutingDto, CancellationToken cancellationToken) { if (singleTxExecutingDto.IsCancellable) { cancellationToken.ThrowIfCancellationRequested(); } var txContext = CreateTransactionContext(singleTxExecutingDto, out var trace); var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); IExecutive executive; try { executive = await _smartContractExecutiveService.GetExecutiveAsync( internalChainContext, singleTxExecutingDto.Transaction.To); } catch (SmartContractFindRegistrationException) { txContext.Trace.ExecutionStatus = ExecutionStatus.ContractError; txContext.Trace.Error += "Invalid contract address.\n"; return(trace); } try { #region PreTransaction if (singleTxExecutingDto.Depth == 0) { if (!await ExecutePluginOnPreTransactionStageAsync(executive, txContext, singleTxExecutingDto.CurrentBlockTime, internalChainContext, internalStateCache, cancellationToken)) { trace.ExecutionStatus = ExecutionStatus.Prefailed; return(trace); } } #endregion await executive.ApplyAsync(txContext); if (txContext.Trace.IsSuccessful()) { await ExecuteInlineTransactions(singleTxExecutingDto.Depth, singleTxExecutingDto.CurrentBlockTime, txContext, internalStateCache, internalChainContext, cancellationToken); } #region PostTransaction if (singleTxExecutingDto.Depth == 0) { if (!await ExecutePluginOnPostTransactionStageAsync(executive, txContext, singleTxExecutingDto.CurrentBlockTime, internalChainContext, internalStateCache, cancellationToken)) { trace.ExecutionStatus = ExecutionStatus.Postfailed; return(trace); } } #endregion } catch (Exception ex) { Logger.LogError(ex, "Transaction execution failed."); txContext.Trace.ExecutionStatus = ExecutionStatus.ContractError; txContext.Trace.Error += ex + "\n"; throw; } finally { await _smartContractExecutiveService.PutExecutiveAsync(singleTxExecutingDto.ChainContext, singleTxExecutingDto.Transaction.To, executive); #if DEBUG await LocalEventBus.PublishAsync(new TransactionExecutedEventData { TransactionTrace = trace }); #endif } return(trace); }
public async Task <List <ExecutionReturnSet> > ExecuteAsync(TransactionExecutingDto transactionExecutingDto, CancellationToken cancellationToken) { try { var groupStateCache = transactionExecutingDto.PartialBlockStateSet.ToTieredStateCache(); var groupChainContext = new ChainContextWithTieredStateCache( transactionExecutingDto.BlockHeader.PreviousBlockHash, transactionExecutingDto.BlockHeader.Height - 1, groupStateCache); var returnSets = new List <ExecutionReturnSet>(); foreach (var transaction in transactionExecutingDto.Transactions) { TransactionTrace trace; if (cancellationToken.IsCancellationRequested) { break; } var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = groupChainContext, Transaction = transaction, CurrentBlockTime = transactionExecutingDto.BlockHeader.Time, OriginTransactionId = transaction.GetHash() }; try { var transactionExecutionTask = Task.Run(() => ExecuteOneAsync(singleTxExecutingDto, cancellationToken), cancellationToken); trace = await transactionExecutionTask.WithCancellation(cancellationToken); } catch (OperationCanceledException) { Logger.LogTrace("Transaction canceled."); if (cancellationToken.IsCancellationRequested) { break; } continue; } if (!TryUpdateStateCache(trace, groupStateCache)) { break; } #if DEBUG if (!string.IsNullOrEmpty(trace.Error)) { Logger.LogInformation(trace.Error); } #endif var result = GetTransactionResult(trace, transactionExecutingDto.BlockHeader.Height); var returnSet = GetReturnSet(trace, result); returnSets.Add(returnSet); } return(returnSets); } catch (Exception e) { Logger.LogError(e, "Failed while executing txs in block."); throw; } }
private async Task <bool> ExecutePluginOnPreTransactionStageAsync(IExecutive executive, ITransactionContext txContext, Timestamp currentBlockTime, IChainContext internalChainContext, TieredStateCache internalStateCache, CancellationToken cancellationToken) { var trace = txContext.Trace; foreach (var plugin in _prePlugins) { var transactions = await plugin.GetPreTransactionsAsync(executive.Descriptors, txContext); foreach (var preTx in transactions) { TransactionTrace preTrace; try { var singleTxExecutingDto = new SingleTransactionExecutingDto { Depth = 0, ChainContext = internalChainContext, Transaction = preTx, CurrentBlockTime = currentBlockTime }; preTrace = await ExecuteOneAsync(singleTxExecutingDto, cancellationToken).WithCancellation(cancellationToken); } catch (OperationCanceledException) { Logger.LogTrace("execute transaction timeout"); return(false); } if (preTrace == null) { return(false); } trace.PreTransactions.Add(preTx); trace.PreTraces.Add(preTrace); if (preTx.MethodName == "ChargeTransactionFees") { var txFee = new TransactionFee(); txFee.MergeFrom(preTrace.ReturnValue); trace.TransactionFee = txFee; } if (!preTrace.IsSuccessful()) { trace.ExecutionStatus = IsTransactionCanceled(preTrace) ? ExecutionStatus.Canceled : ExecutionStatus.Prefailed; preTrace.SurfaceUpError(); trace.Error += preTrace.Error; return(false); } var stateSets = preTrace.GetStateSets().ToList(); internalStateCache.Update(stateSets); var parentStateCache = txContext.StateCache as TieredStateCache; parentStateCache?.Update(stateSets); } } return(true); }