Commit 3c8c9867 authored by Pavel Pavlov's avatar Pavel Pavlov

- Bugfix

parent 80032683
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
using NBitcoin;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore
{
/// <summary>
/// Structure made of a block and its chained header.
/// </summary>
public sealed class BlockPair
{
/// <summary>The block.</summary>
public Block Block { get; private set; }
/// <summary>Chained header of the <see cref="Block"/>.</summary>
public ChainedHeader ChainedHeader { get; private set; }
/// <summary>
/// Creates instance of <see cref="BlockPair" />.
/// </summary>
/// <param name="block">The block.</param>
/// <param name="chainedHeader">Chained header of the <paramref name="block"/>.</param>
public BlockPair(Block block, ChainedHeader chainedHeader)
{
Guard.NotNull(block, nameof(block));
Guard.NotNull(chainedHeader, nameof(chainedHeader));
Guard.Assert(block.GetHash() == chainedHeader.HashBlock);
this.Block = block;
this.ChainedHeader = chainedHeader;
}
}
}
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore
{
/// <summary>
/// The chain of block store loop steps that is executed when the
/// BlockStoreLoop's DownloadAndStoreBlocks is called.
/// <seealso cref="BlockStoreLoop.DownloadAndStoreBlocksAsync"/>
/// </summary>
internal sealed class BlockStoreStepChain
{
private List<BlockStoreLoopStep> steps = new List<BlockStoreLoopStep>();
/// <summary>Set the next step to execute in the BlockStoreLoop.</summary>
/// <param name="step">The next step to execute.</param>
internal void SetNextStep(BlockStoreLoopStep step)
{
this.steps.Add(step);
}
/// <summary>
/// Executes the chain of <see cref="BlockStoreLoop"/> steps.
/// <para>
/// Each step will return a <see cref="StepResult"/> which will either:
/// <list>
/// <item>1: Break out of the foreach loop.</item>
/// <item>2: Continue execution of the foreach loop.</item>
/// </list>
/// </para>
/// </summary>
/// <param name="nextChainedHeader">Next chained block to process.</param>
/// <param name="disposeMode">This is <c>true</c> if <see cref="BlockStoreLoop.ShutDown"/> was called.</param>
/// <param name="cancellationToken">Cancellation token to check.</param>
/// <returns>BlockStoreLoopStepResult</returns>
internal async Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, bool disposeMode, CancellationToken cancellationToken)
{
foreach (var step in this.steps)
{
var stepResult = await step.ExecuteAsync(nextChainedHeader, cancellationToken, disposeMode);
if ((stepResult == StepResult.Continue) || (stepResult == StepResult.Stop))
return stepResult;
}
return StepResult.Next;
}
}
/// <summary>Base class for each block store step.</summary>
internal abstract class BlockStoreLoopStep
{
/// <summary>Factory for creating loggers.</summary>
protected readonly ILoggerFactory loggerFactory;
protected BlockStoreLoopStep(BlockStoreLoop blockStoreLoop, ILoggerFactory loggerFactory)
{
Guard.NotNull(blockStoreLoop, nameof(blockStoreLoop));
this.loggerFactory = loggerFactory;
this.BlockStoreLoop = blockStoreLoop;
}
internal BlockStoreLoop BlockStoreLoop;
internal abstract Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, CancellationToken cancellationToken, bool disposeMode);
}
/// <summary>
/// The result that is returned from executing each loop step.
/// </summary>
public enum StepResult
{
/// <summary>Continue execution of the loop.</summary>
Continue,
/// <summary>Execute the next line of code in the loop.</summary>
Next,
/// <summary>Break out of the loop.</summary>
Stop,
}
}
using System;
using System.Text;
using Microsoft.Extensions.Logging;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore
{
public sealed class BlockStoreStats
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
private IBlockRepository repository;
private BlockStoreCache cache;
private BlockStoreRepositoryPerformanceSnapshot lastRepositorySnapshot;
private BlockStoreCachePerformanceSnapshot lastCacheSnapshot;
/// <summary>Provider of time functions.</summary>
private readonly IDateTimeProvider dateTimeProvider;
public BlockStoreStats(IBlockRepository blockRepository, IBlockStoreCache blockStoreCache, IDateTimeProvider dateTimeProvider, ILogger logger)
{
this.repository = blockRepository;
this.cache = blockStoreCache as BlockStoreCache;
this.logger = logger;
this.lastRepositorySnapshot = this.repository?.PerformanceCounter.Snapshot();
this.lastCacheSnapshot = this.cache?.PerformanceCounter.Snapshot();
this.dateTimeProvider = dateTimeProvider;
}
public bool CanLog
{
get
{
return (this.dateTimeProvider.GetUtcNow() - this.lastRepositorySnapshot.Taken) > TimeSpan.FromSeconds(10.0);
}
}
public void Log()
{
StringBuilder performanceLogBuilder = new StringBuilder();
if (this.repository != null)
{
var snapshot = this.repository.PerformanceCounter.Snapshot();
performanceLogBuilder.AppendLine((snapshot - this.lastRepositorySnapshot).ToString());
this.lastRepositorySnapshot = snapshot;
}
if (this.cache != null)
{
var snapshot = this.cache.PerformanceCounter.Snapshot();
performanceLogBuilder.AppendLine((snapshot - this.lastCacheSnapshot).ToString());
this.lastCacheSnapshot = snapshot;
}
this.logger.LogInformation(performanceLogBuilder.ToString());
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Context for the inner steps, <see cref="BlockStoreInnerStepFindBlocks"/> and <see cref="BlockStoreInnerStepReadBlocks"/>.
/// <para>
/// The context also initializes the inner step <see cref="InnerSteps"/>.
/// </para>
/// </summary>
public sealed class BlockStoreInnerStepContext
{
/// <summary>Number of milliseconds to wait after each failed attempt to get a block from the block puller.</summary>
internal const int StallDelayMs = 100;
/// <summary><see cref="DownloadStack"/> is flushed to the disk if more than this amount of milliseconds passed since the last flush was made.</summary>
internal const int MaxDownloadStackFlushTimeMs = 20 * 1000;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
/// <summary>Provider of time functions.</summary>
internal readonly IDateTimeProvider DateTimeProvider;
/// <summary>Number of attempts to obtain a block from the block puller before giving up and requesting the block again.</summary>
/// <remarks>If the threshold is reached, it is increased to allow more attempts next time.</remarks>
internal int StallCountThreshold = 1800;
/// <summary>Timestamp of the last flush of <see cref="DownloadStack"/> to the disk.</summary>
internal DateTime LastDownloadStackFlushTime;
public BlockStoreInnerStepContext(CancellationToken cancellationToken, BlockStoreLoop blockStoreLoop, ChainedHeader nextChainedHeader, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider)
{
Guard.NotNull(blockStoreLoop, nameof(blockStoreLoop));
Guard.NotNull(nextChainedHeader, nameof(nextChainedHeader));
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
this.BlockStoreLoop = blockStoreLoop;
this.CancellationToken = cancellationToken;
this.DateTimeProvider = dateTimeProvider;
this.DownloadStack = new Queue<ChainedHeader>();
this.InnerSteps = new List<BlockStoreInnerStep> { new BlockStoreInnerStepFindBlocks(loggerFactory), new BlockStoreInnerStepReadBlocks(loggerFactory) };
this.InsertBlockSize = 0;
this.LastDownloadStackFlushTime = this.DateTimeProvider.GetUtcNow();
this.NextChainedHeader = nextChainedHeader;
this.StallCount = 0;
this.Store = new List<BlockPair>();
}
/// <summary>The number of blocks pushed to repository. This gets reset when the next
/// set of blocks are asked from the puller</summary>
public int BlocksPushedCount { get; set; }
/// <summary>A queue of blocks to be downloaded.</summary>
public Queue<ChainedHeader> DownloadStack { get; private set; }
/// <summary>The maximum number of blocks to ask for.</summary>
public const int DownloadStackThreshold = 100;
/// <summary>The maximum number of blocks to read from the puller before asking for blocks again.</summary>
public const int DownloadStackPushThreshold = 50;
public BlockStoreLoop BlockStoreLoop { get; private set; }
/// <summary>The chained block header the inner step starts on.</summary>
public ChainedHeader InputChainedHeader { get; private set; }
public ChainedHeader NextChainedHeader { get; private set; }
/// <summary>The routine (list of inner steps) the DownloadBlockStep executes.</summary>
public List<BlockStoreInnerStep> InnerSteps { get; private set; }
public CancellationToken CancellationToken;
/// <summary>
/// A store of blocks that will be pushed to the repository once the <see cref="BlockStoreLoop.MaxInsertBlockSize"/> has been reached.
/// </summary>
public List<BlockPair> Store;
public int InsertBlockSize;
public int StallCount;
/// <summary> Sets the next chained block header to process.</summary>
internal void GetNextBlock()
{
this.logger.LogTrace("()");
this.InputChainedHeader = this.NextChainedHeader;
this.NextChainedHeader = this.BlockStoreLoop.Chain.GetBlock(this.InputChainedHeader.Height + 1);
this.logger.LogTrace("(-):{0}='{1}'", nameof(this.NextChainedHeader), this.NextChainedHeader);
}
/// <summary> Removes BlockStoreInnerStepFindBlocks from the routine.</summary>
internal void StopFindingBlocks()
{
this.logger.LogTrace("()");
this.InnerSteps.Remove(this.InnerSteps.OfType<BlockStoreInnerStepFindBlocks>().First());
this.logger.LogTrace("(-)");
}
}
/// <summary>Abstract class that all DownloadBlockSteps implement</summary>
public abstract class BlockStoreInnerStep
{
public abstract Task<InnerStepResult> ExecuteAsync(BlockStoreInnerStepContext context);
}
}
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Find blocks to download by asking the BlockPuller.
/// <para>
/// Find blocks until <see cref="BlockStoreInnerStepContext.DownloadStack"/> contains
/// <see cref="BlockStoreInnerStepContext.DownloadStackThreshold"/> blocks.
/// </para>
/// <para>
/// If a stop condition is found <see cref="ShouldStopFindingBlocksAsync"/> and
/// there are still blocks to download, stop finding new blocks and only execute
/// the read blocks inner step <see cref="BlockStoreInnerStepReadBlocks"/>.
/// </para>
/// </summary>
public sealed class BlockStoreInnerStepFindBlocks : BlockStoreInnerStep
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
public BlockStoreInnerStepFindBlocks(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
public override async Task<InnerStepResult> ExecuteAsync(BlockStoreInnerStepContext context)
{
var batchSize = BlockStoreInnerStepContext.DownloadStackThreshold - context.DownloadStack.Count;
var batchList = new List<ChainedHeader>(batchSize);
while (batchList.Count < batchSize)
{
if (await this.ShouldStopFindingBlocksAsync(context))
{
context.StopFindingBlocks();
break;
}
batchList.Add(context.NextChainedHeader);
context.DownloadStack.Enqueue(context.NextChainedHeader);
context.GetNextBlock();
}
if (batchList.Any())
{
this.logger.LogTrace("{0} blocks requested to be downloaded by the puller.", batchList.Count);
context.BlockStoreLoop.BlockPuller.AskForMultipleBlocks(batchList.ToArray());
}
return InnerStepResult.Next;
}
private async Task<bool> ShouldStopFindingBlocksAsync(BlockStoreInnerStepContext context)
{
this.logger.LogTrace("()");
if (context.NextChainedHeader == null)
{
this.logger.LogTrace("(-)[NULL_NEXT]:true");
return true;
}
if ((context.InputChainedHeader != null) && (context.NextChainedHeader.Header.HashPrevBlock != context.InputChainedHeader.HashBlock))
{
this.logger.LogTrace("(-)[NEXT_NEQ_INPUT]:true");
return true;
}
if (context.NextChainedHeader.Height > context.BlockStoreLoop.ChainState.ConsensusTip?.Height)
{
this.logger.LogTrace("(-)[NEXT_HEIGHT_GT_CONSENSUS_TIP]:true");
return true;
}
if (context.BlockStoreLoop.PendingStorage.ContainsKey(context.NextChainedHeader.HashBlock))
{
this.logger.LogTrace("Chained block '{0}' already exists in the pending storage.", context.NextChainedHeader);
this.logger.LogTrace("(-)[NEXT_ALREADY_EXISTS_PENDING_STORE]:true");
return true;
}
if (await context.BlockStoreLoop.BlockRepository.ExistAsync(context.NextChainedHeader.HashBlock))
{
this.logger.LogTrace("Chained block '{0}' already exists in the repository.", context.NextChainedHeader);
this.logger.LogTrace("(-)[NEXT_ALREADY_EXISTS_REPOSITORY]:true");
return true;
}
this.logger.LogTrace("(-):false");
return false;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.BlockPulling;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
using static BlockPuller;
/// <summary>
/// Reads blocks from the <see cref="BlockPuller"/> in a loop and removes block
/// from the <see cref="BlockStoreInnerStepContext.DownloadStack"/>.
/// <para>
/// If the block exists in the puller add the the downloaded block to the store to
/// push to the repository. If <see cref="BlockStoreInnerStepReadBlocks.ShouldBlocksBePushedToRepository"/> returns
/// true, push the blocks in the <see cref="BlockStoreInnerStepContext.Store"/> to the block repository.
/// </para>
/// <para>
/// When the download stack is empty return a <see cref="InnerStepResult.Stop"/> result causing the <see cref="BlockStoreLoop"/> to
/// start again.
/// </para>
/// <para>
/// If a block is stalled or lost to the downloader, start again after a threshold <see cref="BlockStoreLoop.StallCount"/>
/// </para>
/// </summary>
public sealed class BlockStoreInnerStepReadBlocks : BlockStoreInnerStep
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
public BlockStoreInnerStepReadBlocks(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
public override async Task<InnerStepResult> ExecuteAsync(BlockStoreInnerStepContext context)
{
if (!context.DownloadStack.Any())
{
this.logger.LogTrace("(-)[EMPTY_STACK1]:{0}", InnerStepResult.Stop);
return InnerStepResult.Stop;
}
while (context.BlocksPushedCount <= BlockStoreInnerStepContext.DownloadStackPushThreshold)
{
DownloadedBlock downloadedBlock;
ChainedHeader nextBlock = context.DownloadStack.Peek();
if (context.BlockStoreLoop.BlockPuller.TryGetBlock(nextBlock, out downloadedBlock))
{
this.logger.LogTrace("Puller provided block '{0}', length {1}.", nextBlock, downloadedBlock.Length);
ChainedHeader lastHeaderToPush = this.AddDownloadedBlockToStore(context, downloadedBlock);
if (this.ShouldBlocksBePushedToRepository(context))
{
await this.PushBlocksToRepositoryAsync(context, lastHeaderToPush);
if (!context.DownloadStack.Any())
{
this.logger.LogTrace("(-)[EMPTY_STACK2]:{0}", InnerStepResult.Stop);
return InnerStepResult.Stop;
}
}
}
else
{
if (context.StallCount > context.StallCountThreshold)
{
// Increase limit by 10 % to allow adjustments for low speed connections.
// Eventually, the limit be high enough to allow normal operation.
context.StallCountThreshold += context.StallCountThreshold / 10;
this.logger.LogTrace("Stall count threshold increased to {0}.", context.StallCountThreshold);
this.logger.LogTrace("(-)[STALLING]:{0}", InnerStepResult.Stop);
return InnerStepResult.Stop;
}
this.logger.LogTrace("Block '{0}' not available, stall count is {1}, waiting {2} ms...", nextBlock, context.StallCount, BlockStoreInnerStepContext.StallDelayMs);
await Task.Delay(BlockStoreInnerStepContext.StallDelayMs, context.CancellationToken);
context.StallCount++;
}
}
context.BlocksPushedCount = 0;
this.logger.LogTrace("(-):{0}", InnerStepResult.Next);
return InnerStepResult.Next;
}
/// <summary> Adds the downloaded block to the store and resets the stall count.</summary>
private ChainedHeader AddDownloadedBlockToStore(BlockStoreInnerStepContext context, DownloadedBlock downloadedBlock)
{
this.logger.LogTrace("({0}.{1}:{2})", nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length);
ChainedHeader chainedHeaderToStore = context.DownloadStack.Dequeue();
context.Store.Add(new BlockPair(downloadedBlock.Block, chainedHeaderToStore));
context.InsertBlockSize += downloadedBlock.Length;
context.StallCount = 0;
this.logger.LogTrace("(-):'{0}'", chainedHeaderToStore);
return chainedHeaderToStore;
}
/// <summary> Determines whether or not its time for <see cref="BlockStoreInnerStepReadBlocks"/>
/// to push (persist) the downloaded blocks to the repository.</summary>
private bool ShouldBlocksBePushedToRepository(BlockStoreInnerStepContext context)
{
this.logger.LogTrace("()");
DateTime now = context.DateTimeProvider.GetUtcNow();
uint lastFlushDiff = (uint)(now - context.LastDownloadStackFlushTime).TotalMilliseconds;
bool pushBufferSizeReached = context.InsertBlockSize > BlockStoreLoop.MaxInsertBlockSize;
bool downloadStackEmpty = !context.DownloadStack.Any();
bool pushTimeReached = lastFlushDiff > BlockStoreInnerStepContext.MaxDownloadStackFlushTimeMs;
this.logger.LogTrace("Insert block size is {0} bytes{1}, download stack contains {2} blocks, last flush time was {3} ms ago{4}.",
context.InsertBlockSize, pushBufferSizeReached ? " (threshold reached)" : "", context.DownloadStack.Count, lastFlushDiff, pushTimeReached ? " (threshold reached)" : "");
bool res = pushBufferSizeReached || downloadStackEmpty || pushTimeReached;
this.logger.LogTrace("(-):{0}", res);
return res;
}
/// <summary>
/// Push (persist) the downloaded blocks to the block repository
/// </summary>
/// <param name="lastDownloadedHeader>Last block in the list to store, also used to set the store tip.</param>
private async Task PushBlocksToRepositoryAsync(BlockStoreInnerStepContext context, ChainedHeader lastDownloadedHeader)
{
this.logger.LogTrace("()");
List<Block> blocksToStore = context.Store.Select(bp => bp.Block).ToList();
await context.BlockStoreLoop.BlockRepository.PutAsync(lastDownloadedHeader.HashBlock, blocksToStore);
context.BlocksPushedCount += blocksToStore.Count;
this.logger.LogTrace("{0} blocks pushed to the repository, {1} blocks pushed in total.", blocksToStore.Count, context.BlocksPushedCount);
context.BlockStoreLoop.SetStoreTip(lastDownloadedHeader);
context.InsertBlockSize = 0;
context.LastDownloadStackFlushTime = context.DateTimeProvider.GetUtcNow();
context.Store.Clear();
this.logger.LogTrace("(-)");
}
}
}
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Check if the next chained block already exists in the <see cref="BlockRepository"/>.
/// <para>
/// If the block exists in the repository the step will return a Continue result which executes
/// "Continue" on the while loop.
/// </para>
/// <para>
/// If the block does not exists in the repository the step
/// will return a Next result which'll cause the <see cref="BlockStoreLoop"/> to execute
/// the next step (<see cref="ProcessPendingStorageStep"/>).
/// </para>
/// </summary>
internal sealed class CheckNextChainedBlockExistStep : BlockStoreLoopStep
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
internal CheckNextChainedBlockExistStep(BlockStoreLoop blockStoreLoop, ILoggerFactory loggerFactory)
: base(blockStoreLoop, loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
internal override async Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, CancellationToken cancellationToken, bool disposeMode)
{
this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(nextChainedHeader), nextChainedHeader, nameof(disposeMode), disposeMode);
if (await this.BlockStoreLoop.BlockRepository.ExistAsync(nextChainedHeader.HashBlock))
{
await this.BlockStoreLoop.BlockRepository.SetBlockHashAsync(nextChainedHeader.HashBlock);
this.BlockStoreLoop.SetStoreTip(nextChainedHeader);
this.logger.LogTrace("(-)[EXIST]:{0}", StepResult.Continue);
return StepResult.Continue;
}
this.logger.LogTrace("(-):{0}", StepResult.Next);
return StepResult.Next;
}
}
}
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Continuously find and download blocks until a stop condition is found.
///<para>
///<list>
/// There are two operations:
/// <item>1: <see cref="BlockStoreInnerStepFindBlocks"/> to ask the block puller to download the blocks.</item>
/// <item>2: <see cref="BlockStoreInnerStepReadBlocks"/> to persist the blocks in a batch to the <see cref="BlockRepository"/>.</item>
/// </list>
/// </para>
/// <para>
/// After a "Stop" condition is found the <see cref="BlockStoreInnerStepFindBlocks"/> will be removed from
/// <see cref="BlockStoreInnerStepContext.InnerSteps"/> and only the
/// <see cref="BlockStoreInnerStepReadBlocks"/> task will continue to execute
/// until the <see cref="BlockStoreInnerStepContext.DownloadStack"/> is empty.
/// </para>
/// </summary>
internal sealed class DownloadBlockStep : BlockStoreLoopStep
{
/// <summary>Provider of time functions.</summary>
private readonly IDateTimeProvider dateTimeProvider;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
internal DownloadBlockStep(BlockStoreLoop blockStoreLoop, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider)
: base(blockStoreLoop, loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
this.dateTimeProvider = dateTimeProvider;
}
/// <inheritdoc/>
internal override async Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, CancellationToken token, bool disposeMode)
{
this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(nextChainedHeader), nextChainedHeader, nameof(disposeMode), disposeMode);
if (disposeMode)
{
this.logger.LogTrace("(-)[DISPOSE]:{0}", StepResult.Stop);
return StepResult.Stop;
}
var context = new BlockStoreInnerStepContext(token, this.BlockStoreLoop, nextChainedHeader, this.loggerFactory, this.dateTimeProvider);
while (!token.IsCancellationRequested)
{
foreach (BlockStoreInnerStep innerStep in context.InnerSteps.ToList())
{
InnerStepResult innerStepResult = await innerStep.ExecuteAsync(context);
if (innerStepResult == InnerStepResult.Stop)
{
this.logger.LogTrace("(-)[INNER]:{0}", StepResult.Next);
return StepResult.Next;
}
}
}
this.logger.LogTrace("(-):{0}", StepResult.Next);
return StepResult.Next;
}
}
/// <summary>
/// The result that is returned from executing each inner step.
/// </summary>
public enum InnerStepResult
{
/// <summary>Execute the next line of code in the loop.</summary>
Next,
/// <summary>Break out of the loop.</summary>
Stop
}
}
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Check if the next block is in pending storage i.e. first process pending storage blocks
/// before find and downloading more blocks.
/// <para>
/// Remove the BlockPair from PendingStorage and return for further processing.
/// If the next chained block does not exist in pending storage
/// return a Next result which cause the <see cref="BlockStoreLoop"/> to execute
/// the next step <see cref="DownloadBlockStep"/>.
/// </para>
/// <para>
/// If in IBD (Initial Block Download) and batch count is not yet reached,
/// return a Break result causing the <see cref="BlockStoreLoop"/> to break out of the while loop
/// and start again.
/// </para>
/// <para>
/// Loop over the pending blocks and push to the repository in batches.
/// if a stop condition is met break from the inner loop and return a Continue() result.
/// This will cause the <see cref="BlockStoreLoop"/> to skip over <see cref="DownloadBlockStep"/> and start
/// the loop again.
/// </para>
/// </summary>
internal sealed class ProcessPendingStorageStep : BlockStoreLoopStep
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
internal ProcessPendingStorageStep(BlockStoreLoop blockStoreLoop, ILoggerFactory loggerFactory)
: base(blockStoreLoop, loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
internal override async Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, CancellationToken cancellationToken, bool disposeMode)
{
this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(nextChainedHeader), nextChainedHeader, nameof(disposeMode), disposeMode);
var context = new ProcessPendingStorageContext(this.logger, this.BlockStoreLoop, nextChainedHeader, cancellationToken);
// Next block does not exist in pending storage, continue onto the download blocks step.
if (!this.BlockStoreLoop.PendingStorage.ContainsKey(context.NextChainedHeader.HashBlock))
{
this.logger.LogTrace("(-)[NOT_FOUND]:{0}", StepResult.Next);
return StepResult.Next;
}
// In case of IBD do not save every single block- persist them in batches.
if (this.BlockStoreLoop.PendingStorage.Count < BlockStoreLoop.PendingStorageBatchThreshold &&
!disposeMode && this.BlockStoreLoop.InitialBlockDownloadState.IsInitialBlockDownload())
{
return StepResult.Stop;
}
while (!context.CancellationToken.IsCancellationRequested)
{
StepResult result = this.PrepareNextBlockFromPendingStorage(context);
if (result == StepResult.Stop)
break;
if (context.PendingStorageBatchSize > BlockStoreLoop.MaxPendingInsertBlockSize)
await this.PushBlocksToRepositoryAsync(context).ConfigureAwait(false);
}
if (context.PendingBlockPairsToStore.Any())
await this.PushBlocksToRepositoryAsync(context).ConfigureAwait(false);
return StepResult.Continue;
}
/// <summary>
/// Tries to get and remove the next block from pending storage. If it exists
/// then add it to <see cref="ProcessPendingStorageContext.PendingBlockPairsToStore"/>.
/// This will also check if the next block can be processed.
/// </summary>
/// <param name="context"><see cref="ProcessPendingStorageContext"/></param>
private StepResult PrepareNextBlockFromPendingStorage(ProcessPendingStorageContext context)
{
var blockIsInPendingStorage = this.BlockStoreLoop.PendingStorage.TryRemove(context.NextChainedHeader.HashBlock, out context.PendingBlockPairToStore);
if (blockIsInPendingStorage)
{
context.PendingBlockPairsToStore.Push(context.PendingBlockPairToStore);
context.PendingStorageBatchSize += context.PendingBlockPairToStore.Block.GetSerializedSize();
}
return context.CanProcessNextBlock() ? StepResult.Next : StepResult.Stop;
}
/// <summary>
/// Store missing blocks and remove them from pending blocks and set the Store's tip to <see cref="ProcessPendingStorageContext.NextChainedHeader"/>
/// </summary>
/// <param name="context"><see cref="ProcessPendingStorageContext"/></param>
private async Task PushBlocksToRepositoryAsync(ProcessPendingStorageContext context)
{
this.logger.LogDebug(context.ToString());
await this.BlockStoreLoop.BlockRepository.PutAsync(context.PendingBlockPairsToStore.First().ChainedHeader.HashBlock, context.PendingBlockPairsToStore.Select(b => b.Block).ToList());
this.BlockStoreLoop.SetStoreTip(context.PendingBlockPairsToStore.First().ChainedHeader);
context.PendingBlockPairToStore = null;
context.PendingBlockPairsToStore.Clear();
context.PendingStorageBatchSize = 0;
}
}
/// <summary>
/// Context class thats used by <see cref="ProcessPendingStorageStep"/>
/// </summary>
internal sealed class ProcessPendingStorageContext
{
internal ProcessPendingStorageContext(ILogger logger, BlockStoreLoop blockStoreLoop, ChainedHeader nextChainedHeader, CancellationToken cancellationToken)
{
this.logger = logger;
this.BlockStoreLoop = blockStoreLoop;
this.NextChainedHeader = nextChainedHeader;
this.CancellationToken = cancellationToken;
}
internal BlockStoreLoop BlockStoreLoop { get; private set; }
internal CancellationToken CancellationToken { get; private set; }
/// <summary>
/// Used to check if we should break execution when the next block's previous hash doesn't
/// match this block's hash.
/// </summary>
internal ChainedHeader PreviousChainedHeader { get; private set; }
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
/// <summary>
/// The block currently being processed.
/// </summary>
internal ChainedHeader NextChainedHeader { get; private set; }
/// <summary>
/// If this value reaches <see cref="BlockStoreLoop.MaxPendingInsertBlockSize"/> the step will exit./>
/// </summary>
internal int PendingStorageBatchSize = 0;
/// <summary>
/// The last item that was dequeued from <see cref="PendingBlockPairsToStore"/>.
/// </summary>
internal BlockPair PendingBlockPairToStore;
/// <summary>
/// A collection of blocks that are pending to be pushed to store.
/// </summary>
internal ConcurrentStack<BlockPair> PendingBlockPairsToStore = new ConcurrentStack<BlockPair>();
/// <summary>
/// Break execution if:
/// <list>
/// <item>1: Next block is null.</item>
/// <item>2: Next block previous hash does not match previous block.</item>
/// <item>3: Next block is at tip.</item>
/// </list>
/// </summary>
/// <returns>Returns <c>true</c> if none of the above condition were met, i.e. the next block can be processed.</returns>
internal bool CanProcessNextBlock()
{
this.logger.LogTrace("()");
this.PreviousChainedHeader = this.NextChainedHeader;
this.NextChainedHeader = this.BlockStoreLoop.Chain.GetBlock(this.NextChainedHeader.Height + 1);
if (this.NextChainedHeader == null)
{
this.logger.LogTrace("(-)[NO_NEXT]:false");
return false;
}
if (this.NextChainedHeader.Header.HashPrevBlock != this.PreviousChainedHeader.HashBlock)
{
this.logger.LogTrace("(-)[REORG]:false");
return false;
}
if (this.NextChainedHeader.Height > this.BlockStoreLoop.ChainState.ConsensusTip?.Height)
{
this.logger.LogTrace("(-)[NEXT_GT_CONSENSUS_TIP]:false");
return false;
}
this.logger.LogTrace("(-):true");
return true;
}
public override string ToString()
{
return (string.Format("{0}:{1} / {2}.{3}:{4} / {5}:{6} / {7}:{8}",
nameof(this.BlockStoreLoop.InitialBlockDownloadState.IsInitialBlockDownload), this.BlockStoreLoop.InitialBlockDownloadState.IsInitialBlockDownload(),
nameof(this.PendingBlockPairsToStore), nameof(this.PendingBlockPairsToStore.Count),
this.PendingBlockPairsToStore?.Count, nameof(this.PendingStorageBatchSize), this.PendingStorageBatchSize,
nameof(this.BlockStoreLoop.StoreTip), this.BlockStoreLoop.StoreTip));
}
}
}
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
namespace Stratis.Bitcoin.Features.BlockStore.LoopSteps
{
/// <summary>
/// Reorganises the <see cref="BlockRepository"/>.
/// <para>
/// This will happen when the block store's tip does not match
/// the next chained block's previous header.
/// </para>
/// <para>
/// Steps:
/// <list type="bullet">
/// <item>1: Add blocks to delete from the repository by walking back the chain until the last chained block is found.</item>
/// <item>2: Delete those blocks from the BlockRepository.</item>
/// <item>3: Set the last stored block (tip) to the last found chained block.</item>
/// </list>
/// </para>
/// <para>
/// If the store/repository does not require reorganising the step will
/// return Next which will cause the <see cref="BlockStoreLoop" /> to
/// execute the next step <see cref="CheckNextChainedBlockExistStep"/>.
/// If not the step will cause the <see cref="BlockStoreLoop" /> to break execution and start again.
/// </para>
/// </summary>
internal sealed class ReorganiseBlockRepositoryStep : BlockStoreLoopStep
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
internal ReorganiseBlockRepositoryStep(BlockStoreLoop blockStoreLoop, ILoggerFactory loggerFactory)
: base(blockStoreLoop, loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
internal override async Task<StepResult> ExecuteAsync(ChainedHeader nextChainedHeader, CancellationToken cancellationToken, bool disposeMode)
{
this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(nextChainedHeader), nextChainedHeader, nameof(disposeMode), disposeMode);
if (this.BlockStoreLoop.StoreTip.HashBlock != nextChainedHeader.Header.HashPrevBlock)
{
if (disposeMode)
{
this.logger.LogTrace("(-)[DISPOSE]:{0}", StepResult.Stop);
return StepResult.Stop;
}
var blocksToDelete = new List<uint256>();
ChainedHeader headerToDelete = this.BlockStoreLoop.StoreTip;
while (this.BlockStoreLoop.Chain.GetBlock(headerToDelete.HashBlock) == null)
{
blocksToDelete.Add(headerToDelete.HashBlock);
headerToDelete = headerToDelete.Previous;
}
await this.BlockStoreLoop.BlockRepository.DeleteAsync(headerToDelete.HashBlock, blocksToDelete);
this.BlockStoreLoop.SetStoreTip(headerToDelete);
this.logger.LogTrace("(-)[MISMATCH]:{0}", StepResult.Stop);
return StepResult.Stop;
}
this.logger.LogTrace("(-):{0}", StepResult.Next);
return StepResult.Next;
}
}
}
using NBitcoin;
namespace Stratis.Bitcoin.Features.Miner
{
public sealed class AssemblerOptions
{
public long BlockMaxWeight = PowMining.DefaultBlockMaxWeight;
public long BlockMaxSize = PowMining.DefaultBlockMaxSize;
public FeeRate BlockMinFeeRate = new FeeRate(PowMining.DefaultBlockMinTxFee);
public bool IsProofOfStake = false;
}
}
\ No newline at end of file
using System.Collections.Generic;
using NBitcoin;
namespace Stratis.Bitcoin.Features.Miner
{
public sealed class BlockTemplate
{
public Block Block;
public List<Money> VTxFees;
public List<long> TxSigOpsCost;
public string CoinbaseCommitment;
public Money TotalFee;
public BlockTemplate(Network network)
{
this.Block = network.Consensus.ConsensusFactory.CreateBlock();
this.VTxFees = new List<Money>();
this.TxSigOpsCost = new List<long>();
}
}
}
\ No newline at end of file
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Features.Consensus;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.MemoryPool.Interfaces;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Miner
{
public class PosBlockAssembler : BlockAssembler
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
/// <summary>Database of stake related data for the current blockchain.</summary>
private readonly IStakeChain stakeChain;
/// <summary>Provides functionality for checking validity of PoS blocks.</summary>
private readonly IStakeValidator stakeValidator;
public PosBlockAssembler(
IConsensusLoop consensusLoop,
IDateTimeProvider dateTimeProvider,
ILoggerFactory loggerFactory,
ITxMempool mempool,
MempoolSchedulerLock mempoolLock,
Network network,
IStakeChain stakeChain,
IStakeValidator stakeValidator)
: base(consensusLoop, dateTimeProvider, loggerFactory, mempool, mempoolLock, network, new AssemblerOptions() { IsProofOfStake = true })
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
this.stakeChain = stakeChain;
this.stakeValidator = stakeValidator;
}
public override BlockTemplate Build(ChainedHeader chainTip, Script scriptPubKey)
{
this.logger.LogTrace("({0}:'{1}',{2}.{3}:{4})", nameof(chainTip), chainTip, nameof(scriptPubKey), nameof(scriptPubKey.Length), scriptPubKey.Length);
this.OnBuild(chainTip, scriptPubKey);
this.coinbase.Outputs[0].ScriptPubKey = new Script();
this.coinbase.Outputs[0].Value = Money.Zero;
IPosConsensusValidator posValidator = this.ConsensusLoop.Validator as IPosConsensusValidator;
Guard.NotNull(posValidator, nameof(posValidator));
this.logger.LogTrace("(-)");
return this.BlockTemplate;
}
public override void OnUpdateHeaders()
{
this.logger.LogTrace("()");
this.block.Header.HashPrevBlock = this.ChainTip.HashBlock;
this.block.Header.UpdateTime(this.DateTimeProvider.GetTimeOffset(), this.Network, this.ChainTip);
this.block.Header.Nonce = 0;
this.block.Header.Bits = this.stakeValidator.GetNextTargetRequired(this.stakeChain, this.ChainTip, this.Network.Consensus, this.Options.IsProofOfStake);
this.logger.LogTrace("(-)");
}
public override void OnTestBlockValidity()
{
this.logger.LogTrace("()");
this.logger.LogTrace("(-)");
}
}
}
\ No newline at end of file
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.RPC.Models
{
/// <summary>
/// Data structure for RPC block headers.
/// <see cref="https://bitcoin.org/en/developer-reference#getblockheader"/>
/// </summary>
public class BlockHeaderModel
{
/// <summary>
/// Constructs a RPC BlockHeaderModel from a block header object.
/// </summary>
/// <param name="blockHeader">The block header.</param>
public BlockHeaderModel(BlockHeader blockHeader)
{
Guard.NotNull(blockHeader, nameof(blockHeader));
this.Version = (uint)blockHeader.Version;
this.PreviousBlockHash = blockHeader.HashPrevBlock.ToString();
this.MerkleRoot = blockHeader.HashMerkleRoot.ToString();
this.Time = blockHeader.Time;
byte[] bytes = this.GetBytes(blockHeader.Bits.ToCompact());
string encodedBytes = Encoders.Hex.EncodeData(bytes);
this.Bits = encodedBytes;
this.Nonce = (int)blockHeader.Nonce;
}
/// <summary>
/// The blocks version number.
/// </summary>
[JsonProperty(PropertyName = "version")]
public uint Version { get; private set; }
/// <summary>
/// The merkle root for this block encoded as hex in RPC byte order.
/// </summary>
[JsonProperty(PropertyName = "merkleroot")]
public string MerkleRoot { get; private set; }
/// <summary>
/// The nonce which was successful at turning this particular block
/// into one that could be added to the best block chain.
/// </summary>
[JsonProperty(PropertyName = "nonce")]
public int Nonce { get; private set; }
/// <summary>
/// The target threshold this block's header had to pass.
/// </summary>
[JsonProperty(PropertyName = "bits")]
public string Bits { get; private set; }
/// <summary>
/// The hash of the header of the previous block,
/// encoded as hex in RPC byte order.
/// </summary>
[JsonProperty(PropertyName = "previousblockhash")]
public string PreviousBlockHash { get; private set; }
/// <summary>
/// The block time in seconds since epoch (Jan 1 1970 GMT).
/// </summary>
[JsonProperty(PropertyName = "time")]
public uint Time { get; private set; }
/// <summary>
/// Convert compact of miner challenge to byte format,
/// serialized for transmission via RPC.
/// </summary>
/// <param name="compact">Compact representation of challenge.</param>
/// <returns>Byte representation of challenge.</returns>
/// <seealso cref="Target"/>
private byte[] GetBytes(uint compact)
{
return new byte[]
{
(byte)(compact >> 24),
(byte)(compact >> 16),
(byte)(compact >> 8),
(byte)(compact)
};
}
}
}
using NBitcoin;
using Newtonsoft.Json;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.RPC.Models
{
/// <summary>
/// A model returned to an RPC gettxout request
/// </summary>
public class GetTxOutModel
{
public GetTxOutModel()
{
}
public GetTxOutModel(UnspentOutputs unspentOutputs, uint vout, Network network, ChainedHeader tip)
{
if (unspentOutputs != null)
{
var output = unspentOutputs.TryGetOutput(vout);
this.BestBlock = tip.HashBlock;
this.Coinbase = unspentOutputs.IsCoinbase;
this.Confirmations = NetworkExtensions.MempoolHeight == unspentOutputs.Height ? 0 : tip.Height - (int)unspentOutputs.Height + 1;
if (output != null)
{
this.Value = output.Value;
this.ScriptPubKey = new ScriptPubKey(output.ScriptPubKey, network);
}
}
}
[JsonProperty(Order = 0, PropertyName = "bestblock")]
public uint256 BestBlock { get; set; }
[JsonProperty(Order = 1, PropertyName = "confirmations")]
public int Confirmations { get; set; }
[JsonProperty(Order = 2, PropertyName = "value")]
public Money Value { get; set; }
[JsonProperty(Order = 3, PropertyName = "scriptPubKey")]
public ScriptPubKey ScriptPubKey { get; set; }
[JsonProperty(Order = 4, PropertyName = "coinbase")]
public bool Coinbase { get; set; }
}
}
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using Stratis.Bitcoin.Features.RPC.Converters;
namespace Stratis.Bitcoin.Features.RPC.Models
{
public abstract class TransactionModel
{
public TransactionModel(Network network = null)
{
}
public TransactionModel(Transaction trx)
{
this.Hex = trx?.ToHex();
}
[JsonProperty(Order = 0, PropertyName = "hex")]
public string Hex { get; set; }
public override string ToString()
{
return this.Hex;
}
}
[JsonConverter(typeof(ToStringJsonConverter))]
public class TransactionBriefModel : TransactionModel
{
public TransactionBriefModel()
{
}
public TransactionBriefModel(Transaction trx) : base(trx)
{
}
}
public class TransactionVerboseModel : TransactionModel
{
public TransactionVerboseModel()
{
}
public TransactionVerboseModel(Transaction trx, Network network, ChainedHeader block = null, ChainedHeader tip = null) : base(trx)
{
if (trx != null)
{
this.TxId = trx.GetHash().ToString();
this.Size = trx.GetSerializedSize();
this.Version = trx.Version;
this.LockTime = trx.LockTime;
this.VIn = trx.Inputs.Select(txin => new Vin(txin.PrevOut, txin.Sequence, txin.ScriptSig)).ToList();
int n = 0;
this.VOut = trx.Outputs.Select(txout => new Vout(n++, txout, network)).ToList();
if (block != null)
{
this.BlockHash = block.HashBlock.ToString();
this.Time = this.BlockTime = Utils.DateTimeToUnixTime(block.Header.BlockTime);
if (tip != null)
this.Confirmations = tip.Height - block.Height + 1;
}
}
}
[JsonProperty(Order = 1, PropertyName = "txid")]
public string TxId { get; set; }
[JsonProperty(Order = 2, PropertyName = "size")]
public int Size { get; set; }
[JsonProperty(Order = 3, PropertyName = "version")]
public uint Version { get; set; }
[JsonProperty(Order = 4, PropertyName = "locktime")]
public uint LockTime { get; set; }
[JsonProperty(Order = 5, PropertyName = "vin")]
public List<Vin> VIn { get; set; }
[JsonProperty(Order = 6, PropertyName = "vout")]
public List<Vout> VOut { get; set; }
[JsonProperty(Order = 7, PropertyName = "blockhash", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BlockHash { get; set; }
[JsonProperty(Order = 8, PropertyName = "confirmations", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Confirmations { get; set; }
[JsonProperty(Order = 9, PropertyName = "time", DefaultValueHandling = DefaultValueHandling.Ignore)]
public uint? Time { get; set; }
[JsonProperty(Order = 10, PropertyName = "blocktime", DefaultValueHandling = DefaultValueHandling.Ignore)]
public uint? BlockTime { get; set; }
}
public class Vin
{
public Vin()
{
}
public Vin(OutPoint prevOut, Sequence sequence, NBitcoin.Script scriptSig)
{
if (prevOut.Hash == uint256.Zero)
{
this.Coinbase = Encoders.Hex.EncodeData(scriptSig.ToBytes());
}
else
{
this.TxId = prevOut.Hash.ToString();
this.VOut = prevOut.N;
this.ScriptSig = new Script(scriptSig);
}
this.Sequence = (uint)sequence;
}
[JsonProperty(Order = 0, PropertyName = "coinbase", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Coinbase { get; set; }
[JsonProperty(Order = 1, PropertyName = "txid", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string TxId { get; set; }
[JsonProperty(Order = 2, PropertyName = "vout", DefaultValueHandling = DefaultValueHandling.Ignore)]
public uint? VOut { get; set; }
[JsonProperty(Order = 3, PropertyName = "scriptSig", DefaultValueHandling = DefaultValueHandling.Ignore)]
public Script ScriptSig { get; set; }
[JsonProperty(Order = 4, PropertyName = "sequence")]
public uint Sequence { get; set; }
}
public class Vout
{
public Vout()
{
}
public Vout(int N, TxOut txout, Network network)
{
this.N = N;
this.Value = txout.Value.ToDecimal(MoneyUnit.BTC);
this.ScriptPubKey = new ScriptPubKey(txout.ScriptPubKey, network);
}
[JsonConverter(typeof(BtcDecimalJsonConverter))]
[JsonProperty(Order = 0, PropertyName = "value")]
public decimal Value { get; set; }
[JsonProperty(Order = 1, PropertyName = "n")]
public int N { get; set; }
[JsonProperty(Order = 2, PropertyName = "scriptPubKey")]
public ScriptPubKey ScriptPubKey { get; set; }
}
public class Script
{
public Script()
{
}
public Script(NBitcoin.Script script)
{
this.Asm = script.ToString();
this.Hex = Encoders.Hex.EncodeData(script.ToBytes());
}
[JsonProperty(Order = 0, PropertyName = "asm")]
public string Asm { get; set; }
[JsonProperty(Order = 1, PropertyName = "hex")]
public string Hex { get; set; }
}
public class ScriptPubKey : Script
{
public ScriptPubKey()
{
}
public ScriptPubKey(NBitcoin.Script script, Network network) : base(script)
{
var destinations = new List<TxDestination> { script.GetDestination(network) };
this.Type = this.GetScriptType(script.FindTemplate(network));
if (destinations[0] == null)
{
destinations = script.GetDestinationPublicKeys(network)
.Select(p => p.Hash)
.ToList<TxDestination>();
}
else
{
if (destinations.Count == 1)
{
this.ReqSigs = 1;
this.Addresses = new List<string> { destinations[0].GetAddress(network).ToString() };
}
else
{
PayToMultiSigTemplateParameters multi = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(network, script);
this.ReqSigs = multi.SignatureCount;
this.Addresses = multi.PubKeys.Select(m => m.GetAddress(network).ToString()).ToList();
}
}
}
[JsonProperty(Order = 2, PropertyName = "reqSigs", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? ReqSigs { get; set; }
[JsonProperty(Order = 3, PropertyName = "type", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Type { get; set; }
[JsonProperty(Order = 4, PropertyName = "addresses", DefaultValueHandling = DefaultValueHandling.Ignore)]
public List<string> Addresses { get; set; }
protected string GetScriptType(ScriptTemplate template)
{
if (template == null)
return "nonstandard";
switch (template.Type)
{
case TxOutType.TX_PUBKEY:
return "pubkey";
case TxOutType.TX_PUBKEYHASH:
return "pubkeyhash";
case TxOutType.TX_SCRIPTHASH:
return "scripthash";
case TxOutType.TX_MULTISIG:
return "multisig";
case TxOutType.TX_NULL_DATA:
return "nulldata";
}
return "nonstandard";
}
}
}
using System;
using NBitcoin.RPC;
namespace Stratis.Bitcoin.Features.RPC
{
public class RPCServerException : Exception
{
public RPCServerException(RPCErrorCode errorCode, string message) : base(message)
{
this.ErrorCode = errorCode;
}
public RPCErrorCode ErrorCode { get; set; }
}
}
using System.Linq;
namespace Stratis.Bitcoin.Utilities.Extensions
{
/// <summary>
/// Extension methods for arguments array.
/// </summary>
public static class ArgsExtensions
{
/// <summary>
/// Obtains a value of command line argument.
/// <para>
/// It is expected that arguments are written on command line as <c>argName=argValue</c>,
/// where argName usually (but does not need to) starts with "-".
/// </para>
/// <para>
/// The argValue can be wrapped with '"' quotes from both sides, in which case the quotes are removed,
/// but it is not allowed for argValue to contain '"' inside the actual value.
/// </para>
/// </summary>
/// <param name="args">Application command line arguments.</param>
/// <param name="arg">Name of the command line argument which value should be obtained.</param>
/// <returns>Value of the specified argument or null if no such argument is found among the given list of arguments.</returns>
public static string GetValueOf(this string[] args, string arg)
{
return args.Where(a => a.StartsWith($"{arg}=")).Select(a => a.Substring($"{arg}=".Length).Replace("\"", "")).FirstOrDefault();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment