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

- Bugfix

parent 80032683
#if !NOSOCKET
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin.DataEncoders;
using NBitcoin.RPC;
namespace NBitcoin.Tests
{
public enum CoreNodeState
{
Stopped,
Starting,
Running,
Killed
}
public class NodeConfigParameters : Dictionary<string, string>
{
public void Import(NodeConfigParameters configParameters)
{
foreach(var kv in configParameters)
{
if(!ContainsKey(kv.Key))
Add(kv.Key, kv.Value);
}
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
foreach(var kv in this)
builder.AppendLine(kv.Key + "=" + kv.Value);
return builder.ToString();
}
}
public class NodeBuilder : IDisposable
{
/// <summary>
/// Deletes test folders. Stops "bitcoind" if required.
/// </summary>
/// <param name="folder">The folder to remove.</param>
/// <param name="tryKill">If set to true will try to stop "bitcoind" if running.</param>
/// <returns>Returns true if the folder was successfully removed and false otherwise.</returns>
public static bool CleanupTestFolder(string folder, bool tryKill = true)
{
for (int retry = 0; retry < 2; retry++)
{
try
{
Directory.Delete(folder, true);
return true;
}
catch (DirectoryNotFoundException)
{
return true;
}
catch (Exception)
{
}
if (tryKill)
{
tryKill = false;
var x = typeof(NodeBuilder);
foreach (var bitcoind in Process.GetProcessesByName("bitcoind"))
if (bitcoind.MainModule.FileName.Contains("NBitcoin.Tests"))
bitcoind.Kill();
Thread.Sleep(1000);
}
}
return false;
}
public static NodeBuilder Create([CallerMemberName]string caller = null, string version = "0.15.1")
{
CleanupTestFolder(caller);
Directory.CreateDirectory(caller);
return new NodeBuilder(caller, EnsureDownloaded(version));
}
private static string EnsureDownloaded(string version)
{
//is a file
if(version.Length >= 2 && version[1] == ':')
{
return version;
}
var bitcoind = String.Format("bitcoin-{0}/bin/bitcoind.exe", version);
if(File.Exists(bitcoind))
return bitcoind;
var zip = String.Format("bitcoin-{0}-win32.zip", version);
string url = String.Format("https://bitcoin.org/bin/bitcoin-core-{0}/" + zip, version);
HttpClient client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(5.0);
var bytes = client.GetByteArrayAsync(url).GetAwaiter().GetResult();
File.WriteAllBytes(zip, bytes);
try
{
ZipFile.ExtractToDirectory(zip, new FileInfo(zip).Directory.FullName);
}
catch(IOException)
{
//The file probably already exist, continue
}
return bitcoind;
}
int last = 0;
private string _Root;
private string _Bitcoind;
public NodeBuilder(string root, string bitcoindPath)
{
this._Root = root;
this._Bitcoind = bitcoindPath;
}
public string BitcoinD
{
get
{
return _Bitcoind;
}
}
private readonly List<CoreNode> _Nodes = new List<CoreNode>();
public List<CoreNode> Nodes
{
get
{
return _Nodes;
}
}
private readonly NodeConfigParameters _ConfigParameters = new NodeConfigParameters();
public NodeConfigParameters ConfigParameters
{
get
{
return _ConfigParameters;
}
}
public CoreNode CreateNode(bool start = false)
{
var child = Path.Combine(_Root, last.ToString());
last++;
try
{
Directory.Delete(child, true);
}
catch(DirectoryNotFoundException)
{
}
var node = new CoreNode(child, this);
Nodes.Add(node);
if(start)
node.Start();
return node;
}
public void StartAll()
{
Task.WaitAll(Nodes.Where(n => n.State == CoreNodeState.Stopped).Select(n => n.StartAsync()).ToArray());
}
public void Dispose()
{
foreach(var node in Nodes)
node.Kill();
foreach(var disposable in _Disposables)
disposable.Dispose();
}
List<IDisposable> _Disposables = new List<IDisposable>();
internal void AddDisposable(IDisposable group)
{
_Disposables.Add(group);
}
}
public class CoreNode
{
private readonly NodeBuilder _Builder;
private string _Folder;
public string Folder
{
get
{
return _Folder;
}
}
public IPEndPoint Endpoint
{
get
{
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), ports[0]);
}
}
public string Config
{
get
{
return _Config;
}
}
private readonly NodeConfigParameters _ConfigParameters = new NodeConfigParameters();
private string _Config;
public NodeConfigParameters ConfigParameters
{
get
{
return _ConfigParameters;
}
}
public CoreNode(string folder, NodeBuilder builder)
{
this._Builder = builder;
this._Folder = folder;
_State = CoreNodeState.Stopped;
CleanFolder();
Directory.CreateDirectory(folder);
dataDir = Path.Combine(folder, "data");
Directory.CreateDirectory(dataDir);
var pass = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20));
creds = new NetworkCredential(pass, pass);
_Config = Path.Combine(dataDir, "bitcoin.conf");
ConfigParameters.Import(builder.ConfigParameters);
ports = new int[2];
FindPorts(ports);
}
private void CleanFolder()
{
try
{
Directory.Delete(_Folder, true);
}
catch(DirectoryNotFoundException) { }
}
#if !NOSOCKET
public void Sync(CoreNode node, bool keepConnection = false)
{
var rpc = CreateRPCClient();
var rpc1 = node.CreateRPCClient();
rpc.AddNode(node.Endpoint, true);
while(rpc.GetBestBlockHash() != rpc1.GetBestBlockHash())
{
Thread.Sleep(200);
}
if(!keepConnection)
rpc.RemoveNode(node.Endpoint);
}
#endif
private CoreNodeState _State;
public CoreNodeState State
{
get
{
return _State;
}
}
int[] ports;
public int ProtocolPort
{
get
{
return ports[0];
}
}
public void Start()
{
StartAsync().Wait();
}
readonly NetworkCredential creds;
public RPCClient CreateRPCClient()
{
return new RPCClient(GetRPCAuth(), new Uri("http://127.0.0.1:" + ports[1].ToString() + "/"), Network.RegTest);
}
public RestClient CreateRESTClient()
{
return new RestClient(new Uri("http://127.0.0.1:" + ports[1].ToString() + "/"));
}
#if !NOSOCKET
/*
* TODO: Consider importing to FN.
public Node CreateNodeClient()
{
return Node.Connect(Network.RegTest, "127.0.0.1:" + ports[0].ToString());
}
public Node CreateNodeClient(NodeConnectionParameters parameters)
{
return Node.Connect(Network.RegTest, "127.0.0.1:" + ports[0].ToString(), parameters);
}
*/
#endif
string GetRPCAuth()
{
if(!CookieAuth)
return creds.UserName + ":" + creds.Password;
else
return "cookiefile=" + Path.Combine(dataDir, "regtest", ".cookie");
}
public async Task StartAsync()
{
NodeConfigParameters config = new NodeConfigParameters();
config.Add("regtest", "1");
config.Add("rest", "1");
config.Add("server", "1");
config.Add("txindex", "1");
if(!CookieAuth)
{
config.Add("rpcuser", creds.UserName);
config.Add("rpcpassword", creds.Password);
}
config.Add("port", ports[0].ToString());
config.Add("rpcport", ports[1].ToString());
config.Add("printtoconsole", "1");
config.Add("keypool", "10");
config.Add("whitebind", "127.0.0.1:" + ports[0].ToString());
config.Import(ConfigParameters);
File.WriteAllText(_Config, config.ToString());
await Run();
}
private async Task Run()
{
lock(l)
{
_Process = Process.Start(new FileInfo(this._Builder.BitcoinD).FullName, "-conf=bitcoin.conf" + " -datadir=" + dataDir + " -debug=net");
_State = CoreNodeState.Starting;
}
while(true)
{
try
{
await CreateRPCClient().GetBlockHashAsync(0).ConfigureAwait(false);
_State = CoreNodeState.Running;
break;
}
catch { }
if(_Process == null || _Process.HasExited)
break;
}
}
public void Restart()
{
Kill(false);
Run().GetAwaiter().GetResult();
}
Process _Process;
private readonly string dataDir;
private void FindPorts(int[] ports)
{
int i = 0;
while(i < ports.Length)
{
var port = RandomUtils.GetUInt32() % 4000;
port = port + 10000;
if(ports.Any(p => p == port))
continue;
try
{
TcpListener l = new TcpListener(IPAddress.Loopback, (int)port);
l.Start();
l.Stop();
ports[i] = (int)port;
i++;
}
catch(SocketException) { }
}
}
List<Transaction> transactions = new List<Transaction>();
HashSet<OutPoint> locked = new HashSet<OutPoint>();
Money fee = Money.Coins(0.0001m);
public Transaction GiveMoney(Script destination, Money amount, bool broadcast = true)
{
var rpc = CreateRPCClient();
TransactionBuilder builder = new TransactionBuilder();
builder.AddKeys(rpc.ListSecrets().OfType<ISecret>().ToArray());
builder.AddCoins(rpc.ListUnspent().Where(c => !locked.Contains(c.OutPoint)).Select(c => c.AsCoin()));
builder.Send(destination, amount);
builder.SendFees(fee);
builder.SetChange(GetFirstSecret(rpc));
var tx = builder.BuildTransaction(true);
foreach(var outpoint in tx.Inputs.Select(i => i.PrevOut))
{
locked.Add(outpoint);
}
if(broadcast)
Broadcast(tx);
else
transactions.Add(tx);
return tx;
}
public void Rollback(Transaction tx)
{
transactions.Remove(tx);
foreach(var outpoint in tx.Inputs.Select(i => i.PrevOut))
{
locked.Remove(outpoint);
}
}
#if !NOSOCKET
public void Broadcast(Transaction transaction)
{
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
node.SendMessageAsync(new InvPayload(transaction));
node.SendMessageAsync(new TxPayload(transaction));
node.PingPong();
}
*/
}
#else
public void Broadcast(Transaction transaction)
{
var rpc = CreateRPCClient();
rpc.SendRawTransaction(transaction);
}
#endif
public void SelectMempoolTransactions()
{
var rpc = CreateRPCClient();
var txs = rpc.GetRawMempool();
var tasks = txs.Select(t => rpc.GetRawTransactionAsync(t)).ToArray();
Task.WaitAll(tasks);
transactions.AddRange(tasks.Select(t => t.Result).ToArray());
}
public void Split(Money amount, int parts)
{
var rpc = CreateRPCClient();
TransactionBuilder builder = new TransactionBuilder();
builder.AddKeys(rpc.ListSecrets().OfType<ISecret>().ToArray());
builder.AddCoins(rpc.ListUnspent().Select(c => c.AsCoin()));
var secret = GetFirstSecret(rpc);
foreach(var part in (amount - fee).Split(parts))
{
builder.Send(secret, part);
}
builder.SendFees(fee);
builder.SetChange(secret);
var tx = builder.BuildTransaction(true);
Broadcast(tx);
}
object l = new object();
public void Kill(bool cleanFolder = true)
{
lock(l)
{
if(_Process != null && !_Process.HasExited)
{
_Process.Kill();
_Process.WaitForExit();
}
_State = CoreNodeState.Killed;
if(cleanFolder)
CleanFolder();
}
}
public DateTimeOffset? MockTime
{
get;
set;
}
public void SetMinerSecret(BitcoinSecret secret)
{
CreateRPCClient().ImportPrivKey(secret);
MinerSecret = secret;
}
public BitcoinSecret MinerSecret
{
get;
private set;
}
public bool CookieAuth
{
get;
internal set;
}
public Block[] Generate(int blockCount, bool includeUnbroadcasted = true, bool broadcast = true)
{
var rpc = CreateRPCClient();
BitcoinSecret dest = GetFirstSecret(rpc);
var bestBlock = rpc.GetBestBlockHash();
List<Block> blocks = new List<Block>();
DateTimeOffset now = MockTime == null ? DateTimeOffset.UtcNow : MockTime.Value;
#if !NOSOCKET
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
chain = bestBlock == node.Network.GenesisHash ? new ConcurrentChain(node.Network) : node.GetChain();
for(int i = 0; i < blockCount; i++)
{
uint nonce = 0;
Block block = new Block();
block.Header.HashPrevBlock = chain.Tip.HashBlock;
block.Header.Bits = block.Header.GetWorkRequired(rpc.Network, chain.Tip);
block.Header.UpdateTime(now, rpc.Network, chain.Tip);
var coinbase = new Transaction();
coinbase.AddInput(TxIn.CreateCoinbase(chain.Height + 1));
coinbase.AddOutput(new TxOut(rpc.Network.GetReward(chain.Height + 1), dest.GetAddress()));
block.AddTransaction(coinbase);
if(includeUnbroadcasted)
{
transactions = Reorder(transactions);
block.Transactions.AddRange(transactions);
transactions.Clear();
}
block.UpdateMerkleRoot();
while(!block.CheckProofOfWork(node.Network.Consensus))
block.Header.Nonce = ++nonce;
blocks.Add(block);
chain.SetTip(block.Header);
}
if(broadcast)
BroadcastBlocks(blocks.ToArray(), node);
}
*/
return blocks.ToArray();
#endif
}
public void BroadcastBlocks(Block[] blocks)
{
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
BroadcastBlocks(blocks, node);
}
*/
}
/*
* TODO: Consider importing to FN.
public void BroadcastBlocks(Block[] blocks, Node node)
{
Block lastSent = null;
foreach(var block in blocks)
{
node.SendMessageAsync(new InvPayload(block));
node.SendMessageAsync(new BlockPayload(block));
lastSent = block;
}
node.PingPong();
}
*/
public void FindBlock(int blockCount = 1, bool includeMempool = true)
{
SelectMempoolTransactions();
Generate(blockCount, includeMempool);
}
class TransactionNode
{
public TransactionNode(Transaction tx)
{
Transaction = tx;
Hash = tx.GetHash();
}
public uint256 Hash = null;
public Transaction Transaction = null;
public List<TransactionNode> DependsOn = new List<TransactionNode>();
}
private List<Transaction> Reorder(List<Transaction> transactions)
{
if(transactions.Count == 0)
return transactions;
var result = new List<Transaction>();
var dictionary = transactions.ToDictionary(t => t.GetHash(), t => new TransactionNode(t));
foreach(var transaction in dictionary.Select(d => d.Value))
{
foreach(var input in transaction.Transaction.Inputs)
{
var node = dictionary.TryGet(input.PrevOut.Hash);
if(node != null)
{
transaction.DependsOn.Add(node);
}
}
}
while(dictionary.Count != 0)
{
foreach(var node in dictionary.Select(d => d.Value).ToList())
{
foreach(var parent in node.DependsOn.ToList())
{
if(!dictionary.ContainsKey(parent.Hash))
node.DependsOn.Remove(parent);
}
if(node.DependsOn.Count == 0)
{
result.Add(node.Transaction);
dictionary.Remove(node.Hash);
}
}
}
return result;
}
private BitcoinSecret GetFirstSecret(RPCClient rpc)
{
if(MinerSecret != null)
return MinerSecret;
var dest = rpc.ListSecrets().FirstOrDefault();
if(dest == null)
{
var address = rpc.GetNewAddress();
dest = rpc.DumpPrivKey(address);
}
return dest;
}
}
}
#endif
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin.RPC;
namespace NBitcoin.Tests
{
// Require a stratis node running with the following stratis.conf file
//server=1
//rpcuser=rpcuser
//rpcpassword = rpcpassword
//rpcallowip=*
//txindex=1
public class NodeBuilderStratis : IDisposable
{
public static NodeBuilderStratis Create([CallerMemberName]string caller = null, string version = "0.12.1")
{
//version = version ?? "0.12.1";
var path = string.Empty;//EnsureDownloaded(version);
//try
//{
// Directory.Delete(caller, true);
//}
//catch(DirectoryNotFoundException)
//{
//}
//Directory.CreateDirectory(caller);
return new NodeBuilderStratis(caller, path);
}
//private static string EnsureDownloaded(string version)
//{
// //is a file
// if(version.Length >= 2 && version[1] == ':')
// {
// return version;
// }
// var bitcoind = String.Format("bitcoin-{0}/bin/bitcoind.exe", version);
// if(File.Exists(bitcoind))
// return bitcoind;
// var zip = String.Format("bitcoin-{0}-win32.zip", version);
// string url = String.Format("https://bitcoin.org/bin/bitcoin-core-{0}/" + zip, version);
// WebClient client = new WebClient();
// client.DownloadFile(url, zip);
// ZipFile.ExtractToDirectory(zip, new FileInfo(zip).Directory.FullName);
// return bitcoind;
//}
int last = 0;
private string _Root;
private string _Bitcoind;
public NodeBuilderStratis(string root, string bitcoindPath)
{
this._Root = root;
this._Bitcoind = bitcoindPath;
}
public string BitcoinD
{
get
{
return _Bitcoind;
}
}
private readonly List<CoreNodeStratis> _Nodes = new List<CoreNodeStratis>();
public List<CoreNodeStratis> Nodes
{
get
{
return _Nodes;
}
}
private readonly NodeConfigParameters _ConfigParameters = new NodeConfigParameters();
public NodeConfigParameters ConfigParameters
{
get
{
return _ConfigParameters;
}
}
public CoreNodeStratis CreateNode(bool start = false)
{
var child = Path.Combine(_Root, last.ToString());
last++;
//try
//{
// Directory.Delete(child, true);
//}
//catch(DirectoryNotFoundException)
//{
//}
var node = new CoreNodeStratis(child, this);
Nodes.Add(node);
return node;
}
public void StartAll()
{
if (!Process.GetProcesses().Any(p => p.ProcessName.Contains("stratis")))
throw new NotSupportedException("stratis node is not running");
//Task.WaitAll(Nodes.Where(n => n.State == CoreNodeState.Stopped).Select(n => n.StartAsync()).ToArray());
}
public void Dispose()
{
foreach(var node in Nodes)
node.Kill();
foreach(var disposable in _Disposables)
disposable.Dispose();
}
List<IDisposable> _Disposables = new List<IDisposable>();
internal void AddDisposable(IDisposable group)
{
_Disposables.Add(group);
}
}
public class CoreNodeStratis
{
private readonly NodeBuilderStratis _Builder;
private string _Folder;
public string Folder
{
get
{
return _Folder;
}
}
public IPEndPoint Endpoint
{
get
{
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), ports[0]);
}
}
private readonly NodeConfigParameters _ConfigParameters = new NodeConfigParameters();
public NodeConfigParameters ConfigParameters
{
get
{
return _ConfigParameters;
}
}
public CoreNodeStratis(string folder, NodeBuilderStratis builder)
{
this._Builder = builder;
this._Folder = folder;
//_State = CoreNodeState.Stopped;
//CleanFolder();
//Directory.CreateDirectory(folder);
//dataDir = Path.Combine(folder, "data");
//Directory.CreateDirectory(dataDir);
//var pass = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20));
//creds = new NetworkCredential(pass, pass);
//_Config = Path.Combine(dataDir, "bitcoin.conf");
//ConfigParameters.Import(builder.ConfigParameters);
ports = new int[2];
FindPorts(ports);
//ports[1] = Network.StratisMain.RPCPort;
//ports[0] = Network.StratisMain.DefaultPort;
}
private void CleanFolder()
{
try
{
//Directory.Delete(_Folder, true);
}
catch(DirectoryNotFoundException) { }
}
#if !NOSOCKET
public void Sync(CoreNode node, bool keepConnection = false)
{
var rpc = CreateRPCClient();
var rpc1 = node.CreateRPCClient();
rpc.AddNode(node.Endpoint, true);
while(rpc.GetBestBlockHash() != rpc1.GetBestBlockHash())
{
Thread.Sleep(200);
}
if(!keepConnection)
rpc.RemoveNode(node.Endpoint);
}
#endif
private CoreNodeState _State;
public CoreNodeState State
{
get
{
return _State;
}
}
int[] ports;
public int ProtocolPort
{
get
{
return ports[0];
}
}
public RPCClient CreateRPCClient()
{
//return new RPCClient(creds, new Uri("http://127.0.0.1:" + ports[1].ToString() + "/"), Network.RegTest);
// currently only use mainnet
// credentials should be set in advance
return new RPCClient(new NetworkCredential("rpcuser", "rpcpassword"), new Uri("http://127.0.0.1:" + Network.StratisMain.RPCPort + "/"), Network.StratisMain);
}
public RestClient CreateRESTClient()
{
return new RestClient(new Uri("http://127.0.0.1:" + ports[1].ToString() + "/"));
}
/*
* TODO: Consider importing to FN.
public Node CreateNodeClient()
{
return Node.Connect(Network.StratisMain, "127.0.0.1:" + Network.StratisMain.DefaultPort); //ports[0].ToString());
}
public Node CreateNodeClient(NodeConnectionParameters parameters)
{
return Node.Connect(Network.RegTest, "127.0.0.1:" + ports[0].ToString(), parameters);
}
*/
Process _Process = null;
private void FindPorts(int[] ports)
{
int i = 0;
while(i < ports.Length)
{
var port = RandomUtils.GetUInt32() % 4000;
port = port + 10000;
if(ports.Any(p => p == port))
continue;
try
{
TcpListener l = new TcpListener(IPAddress.Loopback, (int)port);
l.Start();
l.Stop();
ports[i] = (int)port;
i++;
}
catch(SocketException) { }
}
}
List<Transaction> transactions = new List<Transaction>();
HashSet<OutPoint> locked = new HashSet<OutPoint>();
Money fee = Money.Coins(0.0001m);
public Transaction GiveMoney(Script destination, Money amount, bool broadcast = true)
{
var rpc = CreateRPCClient();
TransactionBuilder builder = new TransactionBuilder();
builder.AddKeys(rpc.ListSecrets().OfType<ISecret>().ToArray());
builder.AddCoins(rpc.ListUnspent().Where(c => !locked.Contains(c.OutPoint)).Select(c => c.AsCoin()));
builder.Send(destination, amount);
builder.SendFees(fee);
builder.SetChange(GetFirstSecret(rpc));
var tx = builder.BuildTransaction(true);
foreach(var outpoint in tx.Inputs.Select(i => i.PrevOut))
{
locked.Add(outpoint);
}
if(broadcast)
Broadcast(tx);
else
transactions.Add(tx);
return tx;
}
public void Rollback(Transaction tx)
{
transactions.Remove(tx);
foreach(var outpoint in tx.Inputs.Select(i => i.PrevOut))
{
locked.Remove(outpoint);
}
}
#if !NOSOCKET
public void Broadcast(Transaction transaction)
{
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
node.SendMessageAsync(new InvPayload(transaction));
node.SendMessageAsync(new TxPayload(transaction));
node.PingPong();
}
*/
}
#else
public void Broadcast(Transaction transaction)
{
var rpc = CreateRPCClient();
rpc.SendRawTransaction(transaction);
}
#endif
public void SelectMempoolTransactions()
{
var rpc = CreateRPCClient();
var txs = rpc.GetRawMempool();
var tasks = txs.Select(t => rpc.GetRawTransactionAsync(t)).ToArray();
Task.WaitAll(tasks);
transactions.AddRange(tasks.Select(t => t.Result).ToArray());
}
public void Split(Money amount, int parts)
{
var rpc = CreateRPCClient();
TransactionBuilder builder = new TransactionBuilder();
builder.AddKeys(rpc.ListSecrets().OfType<ISecret>().ToArray());
builder.AddCoins(rpc.ListUnspent().Select(c => c.AsCoin()));
var secret = GetFirstSecret(rpc);
foreach(var part in (amount - fee).Split(parts))
{
builder.Send(secret, part);
}
builder.SendFees(fee);
builder.SetChange(secret);
var tx = builder.BuildTransaction(true);
Broadcast(tx);
}
object l = new object();
public void Kill(bool cleanFolder = true)
{
lock(l)
{
if(_Process != null && !_Process.HasExited)
{
_Process.Kill();
_Process.WaitForExit();
}
_State = CoreNodeState.Killed;
if(cleanFolder)
CleanFolder();
}
}
public DateTimeOffset? MockTime
{
get;
set;
}
public void SetMinerSecret(BitcoinSecret secret)
{
CreateRPCClient().ImportPrivKey(secret);
MinerSecret = secret;
}
public BitcoinSecret MinerSecret
{
get;
private set;
}
public Block[] Generate(int blockCount, bool includeUnbroadcasted = true, bool broadcast = true)
{
var rpc = CreateRPCClient();
BitcoinSecret dest = GetFirstSecret(rpc);
var bestBlock = rpc.GetBestBlockHash();
List<Block> blocks = new List<Block>();
DateTimeOffset now = MockTime == null ? DateTimeOffset.UtcNow : MockTime.Value;
#if !NOSOCKET
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
chain = bestBlock == node.Network.GenesisHash ? new ConcurrentChain(node.Network) : node.GetChain();
for(int i = 0; i < blockCount; i++)
{
uint nonce = 0;
Block block = new Block();
block.Header.HashPrevBlock = chain.Tip.HashBlock;
block.Header.Bits = block.Header.GetWorkRequired(rpc.Network, chain.Tip);
block.Header.UpdateTime(now, rpc.Network, chain.Tip);
var coinbase = new Transaction();
coinbase.AddInput(TxIn.CreateCoinbase(chain.Height + 1));
coinbase.AddOutput(new TxOut(rpc.Network.GetReward(chain.Height + 1), dest.GetAddress()));
block.AddTransaction(coinbase);
if(includeUnbroadcasted)
{
transactions = Reorder(transactions);
block.Transactions.AddRange(transactions);
transactions.Clear();
}
block.UpdateMerkleRoot();
while(!block.CheckProofOfWork())
block.Header.Nonce = ++nonce;
blocks.Add(block);
chain.SetTip(block.Header);
}
if(broadcast)
BroadcastBlocks(blocks.ToArray(), node);
}
*/
return blocks.ToArray();
#endif
}
public void BroadcastBlocks(Block[] blocks)
{
/*
* TODO: Consider importing to FN.
using(var node = CreateNodeClient())
{
node.VersionHandshake();
BroadcastBlocks(blocks, node);
}
*/
}
/*
* TODO: Consider importing to FN.
public void BroadcastBlocks(Block[] blocks, Node node)
{
Block lastSent = null;
foreach(var block in blocks)
{
node.SendMessageAsync(new InvPayload(block));
node.SendMessageAsync(new BlockPayload(block));
lastSent = block;
}
node.PingPong();
}
*/
public void FindBlock(int blockCount = 1, bool includeMempool = true)
{
SelectMempoolTransactions();
Generate(blockCount, includeMempool);
}
class TransactionNode
{
public TransactionNode(Transaction tx)
{
Transaction = tx;
Hash = tx.GetHash();
}
public uint256 Hash = null;
public Transaction Transaction = null;
public List<TransactionNode> DependsOn = new List<TransactionNode>();
}
private List<Transaction> Reorder(List<Transaction> transactions)
{
if(transactions.Count == 0)
return transactions;
var result = new List<Transaction>();
var dictionary = transactions.ToDictionary(t => t.GetHash(), t => new TransactionNode(t));
foreach(var transaction in dictionary.Select(d => d.Value))
{
foreach(var input in transaction.Transaction.Inputs)
{
var node = dictionary.TryGet(input.PrevOut.Hash);
if(node != null)
{
transaction.DependsOn.Add(node);
}
}
}
while(dictionary.Count != 0)
{
foreach(var node in dictionary.Select(d => d.Value).ToList())
{
foreach(var parent in node.DependsOn.ToList())
{
if(!dictionary.ContainsKey(parent.Hash))
node.DependsOn.Remove(parent);
}
if(node.DependsOn.Count == 0)
{
result.Add(node.Transaction);
dictionary.Remove(node.Hash);
}
}
}
return result;
}
private BitcoinSecret GetFirstSecret(RPCClient rpc)
{
if(MinerSecret != null)
return MinerSecret;
var dest = rpc.ListSecrets().FirstOrDefault();
if(dest == null)
{
var address = rpc.GetNewAddress();
dest = rpc.DumpPrivKey(address);
}
return dest;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin.RPC;
using Newtonsoft.Json.Linq;
using Xunit;
namespace NBitcoin.Tests
{
//Require a rpc server on test network running on default port with -rpcuser=NBitcoin -rpcpassword=NBitcoinPassword
//For me :
//"bitcoin-qt.exe" -testnet -server -rpcuser=NBitcoin -rpcpassword=NBitcoinPassword
[Trait("RPCClient", "RPCClient")]
public class RPCClientTests
{
const string TestAccount = "NBitcoin.RPCClientTests";
[Fact]
public void InvalidCommandSendRPCException()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
AssertException<RPCException>(() => rpc.SendCommand("donotexist"), (ex) =>
{
Assert.True(ex.RPCCode == RPCErrorCode.RPC_METHOD_NOT_FOUND);
});
}
}
[Fact]
public void CanSendCommand()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
var response = rpc.SendCommand(RPCOperations.getinfo);
Assert.NotNull(response.Result);
}
}
[Fact]
public void CanGetGenesisFromRPC()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
var response = rpc.SendCommand(RPCOperations.getblockhash, 0);
var actualGenesis = (string)response.Result;
Assert.Equal(Network.RegTest.GetGenesis().GetHash().ToString(), actualGenesis);
Assert.Equal(Network.RegTest.GetGenesis().GetHash(), rpc.GetBestBlockHash());
}
}
[Fact]
public void CanSignRawTransaction()
{
using(var builder = NodeBuilder.Create())
{
var node = builder.CreateNode();
var rpc = node.CreateRPCClient();
builder.StartAll();
node.CreateRPCClient().Generate(101);
var tx = new Transaction();
tx.Outputs.Add(new TxOut(Money.Coins(1.0m), new Key()));
var funded = node.CreateRPCClient().FundRawTransaction(tx);
var signed = node.CreateRPCClient().SignRawTransaction(funded.Transaction);
node.CreateRPCClient().SendRawTransaction(signed);
}
}
[Fact]
public void CanGetBlockFromRPC()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
var response = rpc.GetBlockHeader(0);
AssertEx.CollectionEquals(Network.RegTest.GetGenesis().Header.ToBytes(), response.ToBytes());
response = rpc.GetBlockHeader(0);
Assert.Equal(Network.RegTest.GenesisHash, response.GetHash());
}
}
[Fact]
public void TryValidateAddress()
{
using (var builder = NodeBuilder.Create())
{
CoreNode node = builder.CreateNode();
node.Start();
node.Generate(101);
RPCClient rpc = node.CreateRPCClient();
// RegTest
BitcoinAddress pkh = rpc.GetNewAddress();
Assert.True(rpc.ValidateAddress(pkh).IsValid);
}
}
[Fact]
public void TryEstimateFeeRate()
{
using(var builder = NodeBuilder.Create())
{
var node = builder.CreateNode();
node.Start();
node.Generate(101);
var rpc = node.CreateRPCClient();
Assert.Null(rpc.TryEstimateFeeRate(1));
}
}
[Fact]
public void CanGetTxOutNoneFromRPC()
{
using (var builder = NodeBuilder.Create())
{
var node = builder.CreateNode();
node.Start();
var rpc = node.CreateRPCClient();
var txid = rpc.Generate(1).Single();
var resultTxOut = rpc.GetTxOut(txid, 0, true);
Assert.Null(resultTxOut);
}
}
[Fact]
public void CanGetTransactionBlockFromRPC()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
var blockId = rpc.GetBestBlockHash();
var block = rpc.GetBlock(blockId);
Assert.True(block.CheckMerkleRoot());
}
}
[Fact]
public void CanGetPrivateKeysFromAccount()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
Key key = new Key();
rpc.ImportAddress(key.PubKey.GetAddress(Network.RegTest), TestAccount, false);
BitcoinAddress address = rpc.GetAccountAddress(TestAccount);
BitcoinSecret secret = rpc.DumpPrivKey(address);
BitcoinSecret secret2 = rpc.GetAccountSecret(TestAccount);
Assert.Equal(secret.ToString(), secret2.ToString());
Assert.Equal(address.ToString(), secret.GetAddress().ToString());
}
}
[Fact]
public void CanGetPrivateKeysFromLockedAccount()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
Key key = new Key();
var passphrase = "password1234";
rpc.SendCommand(RPCOperations.encryptwallet, passphrase);
builder.Nodes[0].Restart();
rpc.ImportAddress(key.PubKey.GetAddress(Network.RegTest), TestAccount, false);
BitcoinAddress address = rpc.GetAccountAddress(TestAccount);
rpc.WalletPassphrase(passphrase, 60);
BitcoinSecret secret = rpc.DumpPrivKey(address);
BitcoinSecret secret2 = rpc.GetAccountSecret(TestAccount);
Assert.Equal(secret.ToString(), secret2.ToString());
Assert.Equal(address.ToString(), secret.GetAddress().ToString());
}
}
[Fact]
public void CanDecodeAndEncodeRawTransaction()
{
var tests = TestCase.read_json("data/tx_raw.json");
foreach(var test in tests)
{
var format = (RawFormat)Enum.Parse(typeof(RawFormat), (string)test[0], true);
var network = ((string)test[1]) == "Main" ? Network.Main : Network.TestNet;
var testData = ((JObject)test[2]).ToString();
Transaction raw = Transaction.Parse(testData, format, network);
AssertJsonEquals(raw.ToString(format, network), testData);
var raw3 = Transaction.Parse(raw.ToString(format, network), format);
Assert.Equal(raw.ToString(format, network), raw3.ToString(format, network));
}
}
[Fact]
public void CanDecodeUnspentTransaction()
{
var testJson =
@"{
""bestblock"": ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""confirmations"": 1,
""value"": 7.744E-05,
""scriptPubKey"": {
""asm"": ""OP_DUP OP_HASH160 fdb12c93cf639eb38d1998959cfd2f35eb730ede OP_EQUALVERIFY OP_CHECKSIG"",
""hex"": ""76a914fdb12c93cf639eb38d1998959cfd2f35eb730ede88ac"",
""reqSigs"": 1,
""type"": ""pubkeyhash"",
""addresses"": [
""n4eMVrvNqe4EtZDEeei3o63hymTKZNZGhf""
]
},
""coinbase"": true
}";
var testData = JObject.Parse(testJson);
var unspentTransaction = new UnspentTransaction(testData);
Assert.Equal(1, unspentTransaction.confirmations);
Assert.Equal(1, unspentTransaction.scriptPubKey.reqSigs);
Assert.Single(unspentTransaction.scriptPubKey.addresses);
Assert.Equal(7.744E-05m, unspentTransaction.value);
}
[Fact]
public void CanDecodeUnspentCoinWatchOnlyAddress()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""amount"" : 0.00010000,
""confirmations"" : 6210,
""spendable"" : false
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
Assert.Equal("test label", unspentCoin.Account);
Assert.False(unspentCoin.IsSpendable);
Assert.Null(unspentCoin.RedeemScript);
}
[Fact]
public void CanDecodeUnspentCoinLegacyPre_0_10_0()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""amount"" : 0.00010000,
""confirmations"" : 6210
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
// Versions prior to 0.10.0 were always spendable (but had no JSON field)
Assert.True(unspentCoin.IsSpendable);
}
[Fact]
public void CanDecodeUnspentCoinWithRedeemScript()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""redeemScript"" : ""522103310188e911026cf18c3ce274e0ebb5f95b007f230d8cb7d09879d96dbeab1aff210243930746e6ed6552e03359db521b088134652905bd2d1541fa9124303a41e95621029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c7725553ae"",
""amount"" : 0.00010000,
""confirmations"" : 6210,
""spendable"" : true
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
Console.WriteLine("Redeem Script: {0}", unspentCoin.RedeemScript);
Assert.NotNull(unspentCoin.RedeemScript);
}
[Fact]
public void RawTransactionIsConformsToRPC()
{
using(var builder = NodeBuilder.Create())
{
var rpc = builder.CreateNode(true).CreateRPCClient();
builder.StartAll();
var tx = Network.TestNet.GetGenesis().Transactions[0];
var tx2 = rpc.DecodeRawTransaction(tx.ToBytes());
AssertJsonEquals(tx.ToString(RawFormat.Satoshi), tx2.ToString(RawFormat.Satoshi));
}
}
[Fact]
public void CanUseBatchedRequests()
{
using(var builder = NodeBuilder.Create())
{
var nodeA = builder.CreateNode();
builder.StartAll();
var rpc = nodeA.CreateRPCClient();
var blocks = rpc.Generate(10);
Assert.Throws<InvalidOperationException>(() => rpc.SendBatch());
rpc = rpc.PrepareBatch();
List<Task<uint256>> requests = new List<Task<uint256>>();
for(int i = 1; i < 11; i++)
{
requests.Add(rpc.GetBlockHashAsync(i));
}
Thread.Sleep(1000);
foreach(var req in requests)
{
Assert.Equal(TaskStatus.WaitingForActivation, req.Status);
}
rpc.SendBatch();
rpc = rpc.PrepareBatch();
int blockIndex = 0;
foreach(var req in requests)
{
Assert.Equal(blocks[blockIndex], req.Result);
Assert.Equal(TaskStatus.RanToCompletion, req.Status);
blockIndex++;
}
requests.Clear();
requests.Add(rpc.GetBlockHashAsync(10));
requests.Add(rpc.GetBlockHashAsync(11));
requests.Add(rpc.GetBlockHashAsync(9));
requests.Add(rpc.GetBlockHashAsync(8));
rpc.SendBatch();
rpc = rpc.PrepareBatch();
Assert.Equal(TaskStatus.RanToCompletion, requests[0].Status);
Assert.Equal(TaskStatus.Faulted, requests[1].Status);
Assert.Equal(TaskStatus.RanToCompletion, requests[2].Status);
Assert.Equal(TaskStatus.RanToCompletion, requests[3].Status);
requests.Clear();
requests.Add(rpc.GetBlockHashAsync(10));
requests.Add(rpc.GetBlockHashAsync(11));
rpc.CancelBatch();
rpc = rpc.PrepareBatch();
Thread.Sleep(100);
Assert.Equal(TaskStatus.Canceled, requests[0].Status);
Assert.Equal(TaskStatus.Canceled, requests[1].Status);
}
}
#if !NOSOCKET
[Fact]
[Trait("UnitTest", "UnitTest")]
public void CanParseIpEndpoint()
{
var endpoint = Utils.ParseIpEndpoint("google.com:94", 90);
Assert.Equal(94, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("google.com", 90);
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("10.10.1.3", 90);
Assert.Equal("10.10.1.3", endpoint.Address.ToString());
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("10.10.1.3:94", 90);
Assert.Equal("10.10.1.3", endpoint.Address.ToString());
Assert.Equal(94, endpoint.Port);
Assert.Throws<System.Net.Sockets.SocketException>(() => Utils.ParseIpEndpoint("2001:db8:1f70::999:de8:7648:6e8:100", 90));
endpoint = Utils.ParseIpEndpoint("2001:db8:1f70::999:de8:7648:6e8", 90);
Assert.Equal("2001:db8:1f70:0:999:de8:7648:6e8", endpoint.Address.ToString());
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("[2001:db8:1f70::999:de8:7648:6e8]:94", 90);
Assert.Equal("2001:db8:1f70:0:999:de8:7648:6e8", endpoint.Address.ToString());
Assert.Equal(94, endpoint.Port);
}
[Fact]
public void CanAuthWithCookieFile()
{
#if NOFILEIO
Assert.Throws<NotSupportedException>(() => new RPCClient(Network.Main));
#else
using(var builder = NodeBuilder.Create())
{
//Sanity check that it does not throw
#pragma warning disable CS0618
new RPCClient(new NetworkCredential("toto", "tata:blah"), "localhost:10393", Network.Main);
var node = builder.CreateNode();
node.CookieAuth = true;
node.Start();
var rpc = node.CreateRPCClient();
rpc.GetBlockCount();
node.Restart();
rpc.GetBlockCount();
Assert.Throws<ArgumentException>(() => new RPCClient("cookiefile=Data\\tx_valid.json", new Uri("http://localhost/"), Network.RegTest));
Assert.Throws<FileNotFoundException>(() => new RPCClient("cookiefile=Data\\efpwwie.json", new Uri("http://localhost/"), Network.RegTest));
rpc = new RPCClient("bla:bla", null as Uri, Network.RegTest);
Assert.Equal("http://127.0.0.1:" + Network.RegTest.RPCPort + "/", rpc.Address.AbsoluteUri);
rpc = node.CreateRPCClient();
rpc = rpc.PrepareBatch();
var blockCountAsync = rpc.GetBlockCountAsync();
rpc.SendBatch();
var blockCount = blockCountAsync.GetAwaiter().GetResult();
node.Restart();
rpc = rpc.PrepareBatch();
blockCountAsync = rpc.GetBlockCountAsync();
rpc.SendBatch();
blockCount = blockCountAsync.GetAwaiter().GetResult();
rpc = new RPCClient("bla:bla", "http://toto/", Network.RegTest);
}
#endif
}
[Fact]
public void RPCSendRPCException()
{
using(var builder = NodeBuilder.Create())
{
var node = builder.CreateNode();
builder.StartAll();
var rpcClient = node.CreateRPCClient();
try
{
rpcClient.SendCommand("whatever");
Assert.False(true, "Should have thrown");
}
catch(RPCException ex)
{
if(ex.RPCCode != RPCErrorCode.RPC_METHOD_NOT_FOUND)
{
Assert.False(true, "Should have thrown RPC_METHOD_NOT_FOUND");
}
}
}
}
#endif
[Fact]
public void CanBackupWallet()
{
using(var builder = NodeBuilder.Create())
{
var node = builder.CreateNode();
node.Start();
var buildOutputDir = Path.GetDirectoryName(".");
var filePath = Path.Combine(buildOutputDir, "wallet_backup.dat");
try
{
var rpc = node.CreateRPCClient();
rpc.BackupWallet(filePath);
Assert.True(File.Exists(filePath));
}
finally
{
if(File.Exists(filePath))
File.Delete(filePath);
}
}
}
private void AssertJsonEquals(string json1, string json2)
{
foreach(var c in new[] { "\r\n", " ", "\t" })
{
json1 = json1.Replace(c, "");
json2 = json2.Replace(c, "");
}
Assert.Equal(json1, json2);
}
void AssertException<T>(Action act, Action<T> assert) where T : Exception
{
try
{
act();
Assert.False(true, "Should have thrown an exception");
}
catch(T ex)
{
assert(ex);
}
}
}
}
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using NBitcoin.RPC;
using Newtonsoft.Json.Linq;
using Xunit;
namespace NBitcoin.Tests
{
// Require a rpc server on test network running on default port with -rpcuser=NBitcoin -rpcpassword=NBitcoinPassword.
// For me :
// "bitcoin-qt.exe" -testnet -server -rpcuser=NBitcoin -rpcpassword=NBitcoinPassword.
[Trait("RPCClient", "RPCClient")]
public class pos_RPCClientTests
{
public static bool noClient = !Process.GetProcesses().Any(p => p.ProcessName.Contains("stratis"));
private const string TestAccount = "NBitcoin.RPCClientTests";
[Fact]
public void InvalidCommandSendRPCException()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
RPCClient rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
AssertException<RPCException>(() => rpc.SendCommand("donotexist"), (ex) =>
{
Assert.True(ex.RPCCode == RPCErrorCode.RPC_METHOD_NOT_FOUND);
});
}
}
[Fact]
public void CanSendCommand()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
RPCClient rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
RPCResponse response = rpc.SendCommand(RPCOperations.getinfo);
Assert.NotNull(response.Result);
}
}
[Fact]
public void CanGetGenesisFromRPC()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
RPCClient rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
RPCResponse response = rpc.SendCommand(RPCOperations.getblockhash, 0);
var actualGenesis = (string)response.Result;
Assert.Equal(Network.StratisMain.GetGenesis().GetHash().ToString(), actualGenesis);
//Assert.Equal(Network.StratisMain.GetGenesis().GetHash(), rpc.GetBestBlockHash());
}
}
[Fact]
public void CanGetRawMemPool()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
CoreNodeStratis node = builder.CreateNode();
RPCClient rpc = node.CreateRPCClient();
builder.StartAll();
////node.Generate(101);
//var txid = rpc.SendToAddress(new Key().PubKey.GetAddress(rpc.Network), Money.Coins(1.0m), "hello", "world");
uint256[] ids = rpc.GetRawMempool();
Assert.NotNull(ids);
//Assert.Equal(txid, ids[0]);
}
}
[Fact]
public void CanUseAsyncRPC()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
CoreNodeStratis node = builder.CreateNode();
RPCClient rpc = node.CreateRPCClient();
builder.StartAll();
////node.Generate(10);
var blkCount = rpc.GetBlockCountAsync().Result;
Assert.True(blkCount > 10);
}
}
[Fact]
public void CanGetBlockFromRPC()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
RPCClient rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
BlockHeader response = rpc.GetBlockHeader(0);
AssertEx.CollectionEquals(Network.StratisMain.GetGenesis().Header.ToBytes(), response.ToBytes());
response = rpc.GetBlockHeader(0);
Assert.Equal(Network.StratisMain.GenesisHash, response.GetHash());
}
}
//[Fact]
//public void EstimateFeeRate()
//{
// if (RPCClientTests_pos.noClient) return;
// using (var builder = NodeBuilderStratis.Create())
// {
// var node = builder.CreateNode();
// node.Start();
// //node.Generate(101);
// var rpc = node.CreateRPCClient();
// Assert.Throws<NoEstimationException>(() => rpc.EstimateFeeRate(1));
// }
//}
//[Fact]
//public void TryEstimateFeeRate()
//{
// if (RPCClientTests_pos.noClient) return;
// using (var builder = NodeBuilderStratis.Create())
// {
// var node = builder.CreateNode();
// node.Start();
// //node.Generate(101);
// var rpc = node.CreateRPCClient();
// Assert.Null(rpc.TryEstimateFeeRate(1));
// }
//}
[Fact]
public void CanGetTransactionBlockFromRPC()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
RPCClient rpc = builder.CreateNode().CreateRPCClient();
builder.StartAll();
uint256 blockId = rpc.GetBestBlockHash();
RPCBlock block = rpc.GetRPCBlockAsync(blockId).Result;
Assert.NotNull(block);
}
}
//[Fact]
//public void CanGetPrivateKeysFromAccount()
//{
// if (RPCClientTests_pos.noClient) return;
// using (var builder = NodeBuilderStratis.Create())
// {
// var rpc = builder.CreateNode().CreateRPCClient();
// builder.StartAll();
// Key key = new Key();
// rpc.ImportAddress(key.PubKey.GetAddress(Network.StratisMain), TestAccount, false);
// BitcoinAddress address = rpc.GetAccountAddress(TestAccount);
// BitcoinSecret secret = rpc.DumpPrivKey(address);
// BitcoinSecret secret2 = rpc.GetAccountSecret(TestAccount);
// Assert.Equal(secret.ToString(), secret2.ToString());
// Assert.Equal(address.ToString(), secret.GetAddress().ToString());
// }
//}
//[Fact]
//public void CanDecodeAndEncodeRawTransaction()
//{
// var a = new Protocol.AddressManager().Select();
// var tests = TestCase.read_json("data/tx_raw.json");
// foreach(var test in tests)
// {
// var format = (RawFormat)Enum.Parse(typeof(RawFormat), (string)test[0], true);
// var network = ((string)test[1]) == "Main" ? Network.StratisMain : Network.StratisMain;
// var testData = ((JObject)test[2]).ToString();
// Transaction raw = Transaction.Parse(testData, format, network);
// AssertJsonEquals(raw.ToString(format, network), testData);
// var raw3 = Transaction.Parse(raw.ToString(format, network), format);
// Assert.Equal(raw.ToString(format, network), raw3.ToString(format, network));
// }
//}
[Fact]
public void CanDecodeUnspentCoinWatchOnlyAddress()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""amount"" : 0.00010000,
""confirmations"" : 6210,
""spendable"" : false
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
Assert.Equal("test label", unspentCoin.Account);
Assert.False(unspentCoin.IsSpendable);
Assert.Null(unspentCoin.RedeemScript);
}
[Fact]
public void CanDecodeUnspentCoinLegacyPre_0_10_0()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""amount"" : 0.00010000,
""confirmations"" : 6210
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
// Versions prior to 0.10.0 were always spendable (but had no JSON field).
Assert.True(unspentCoin.IsSpendable);
}
[Fact]
public void CanDecodeUnspentCoinWithRedeemScript()
{
var testJson =
@"{
""txid"" : ""d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a"",
""vout"" : 1,
""address"" : ""mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"",
""account"" : ""test label"",
""scriptPubKey"" : ""76a9140dfc8bafc8419853b34d5e072ad37d1a5159f58488ac"",
""redeemScript"" : ""522103310188e911026cf18c3ce274e0ebb5f95b007f230d8cb7d09879d96dbeab1aff210243930746e6ed6552e03359db521b088134652905bd2d1541fa9124303a41e95621029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c7725553ae"",
""amount"" : 0.00010000,
""confirmations"" : 6210,
""spendable"" : true
}";
var testData = JObject.Parse(testJson);
var unspentCoin = new UnspentCoin(testData, Network.TestNet);
Console.WriteLine("Redeem Script: {0}", unspentCoin.RedeemScript);
Assert.NotNull(unspentCoin.RedeemScript);
}
[Fact]
public void RawTransactionIsConformsToRPC()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
CoreNodeStratis node = builder.CreateNode();
builder.StartAll();
RPCClient rpc = node.CreateRPCClient();
var tx = Transaction.Parse("01000000ac55a957010000000000000000000000000000000000000000000000000000000000000000ffffffff0401320103ffffffff010084d717000000001976a9143ac0dad2ad42e35fcd745d7511d47c24ad6580b588ac00000000");
Transaction tx2 = rpc.GetRawTransaction(uint256.Parse("a6783a0933942d37dcb5fb923ddd343522036de23fbc658f2ad2a9f1428ca19d"));
Assert.Equal(tx.GetHash(), tx2.GetHash());
}
}
#if !PORTABLE
[Fact]
public void CanGetPeersInfo()
{
/*
* TODO: Consider importing to FN.
if (pos_RPCClientTests.noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
var nodeA = builder.CreateNode();
builder.StartAll();
var rpc = nodeA.CreateRPCClient();
using(var node = nodeA.CreateNodeClient())
{
node.VersionHandshake();
var peers = rpc.GetStratisPeersInfoAsync().Result;
Assert.NotEmpty(peers);
}
}
*/
}
#endif
#if !NOSOCKET
[Fact]
[Trait("UnitTest", "UnitTest")]
public void CanParseIpEndpoint()
{
IPEndPoint endpoint = Utils.ParseIpEndpoint("google.com:94", 90);
Assert.Equal(94, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("google.com", 90);
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("10.10.1.3", 90);
Assert.Equal("10.10.1.3", endpoint.Address.ToString());
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("10.10.1.3:94", 90);
Assert.Equal("10.10.1.3", endpoint.Address.ToString());
Assert.Equal(94, endpoint.Port);
Assert.Throws<System.Net.Sockets.SocketException>(() => Utils.ParseIpEndpoint("2001:db8:1f70::999:de8:7648:6e8:100", 90));
endpoint = Utils.ParseIpEndpoint("2001:db8:1f70::999:de8:7648:6e8", 90);
Assert.Equal("2001:db8:1f70:0:999:de8:7648:6e8", endpoint.Address.ToString());
Assert.Equal(90, endpoint.Port);
endpoint = Utils.ParseIpEndpoint("[2001:db8:1f70::999:de8:7648:6e8]:94", 90);
Assert.Equal("2001:db8:1f70:0:999:de8:7648:6e8", endpoint.Address.ToString());
Assert.Equal(94, endpoint.Port);
}
[Fact]
public void RPCSendRPCException()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
CoreNodeStratis node = builder.CreateNode();
builder.StartAll();
RPCClient rpcClient = node.CreateRPCClient();
try
{
rpcClient.SendCommand("whatever");
Assert.False(true, "Should have thrown");
}
catch (RPCException ex)
{
if (ex.RPCCode != RPCErrorCode.RPC_METHOD_NOT_FOUND)
{
Assert.False(true, "Should have thrown RPC_METHOD_NOT_FOUND");
}
}
}
}
[Fact]
public void CanAddNodes()
{
if (noClient) return;
using (var builder = NodeBuilderStratis.Create())
{
CoreNodeStratis nodeA = builder.CreateNode();
CoreNodeStratis nodeB = builder.CreateNode();
builder.StartAll();
RPCClient rpc = nodeA.CreateRPCClient();
rpc.RemoveNode(nodeA.Endpoint);
rpc.AddNode(nodeB.Endpoint);
Thread.Sleep(500);
AddedNodeInfo[] info = rpc.GetAddedNodeInfo(true);
Assert.NotNull(info);
Assert.NotEmpty(info);
//For some reason this one does not pass anymore in 0.13.1.
//Assert.Equal(nodeB.Endpoint, info.First().Addresses.First().Address);
AddedNodeInfo oneInfo = rpc.GetAddedNodeInfo(true, nodeB.Endpoint);
Assert.NotNull(oneInfo);
Assert.True(oneInfo.AddedNode.ToString() == nodeB.Endpoint.ToString());
oneInfo = rpc.GetAddedNodeInfo(true, nodeA.Endpoint);
Assert.Null(oneInfo);
//rpc.RemoveNode(nodeB.Endpoint);
//Thread.Sleep(500);
//info = rpc.GetAddedNodeInfo(true);
//Assert.Equal(0, info.Count());
}
}
#endif
//[Fact]
//public void CanBackupWallet()
//{
// if (RPCClientTests_pos.noClient) return;
// using (var builder = NodeBuilderStratis.Create())
// {
// var node = builder.CreateNode();
// node.Start();
// var buildOutputDir = Path.GetDirectoryName(".");
// var filePath = Path.Combine(buildOutputDir, "wallet_backup.dat");
// try
// {
// var rpc = node.CreateRPCClient();
// rpc.BackupWallet(filePath);
// Assert.True(File.Exists(filePath));
// }
// finally
// {
// if(File.Exists(filePath))
// File.Delete(filePath);
// }
// }
//}
//[Fact]
//public void CanEstimatePriority()
//{
// if (RPCClientTests_pos.noClient) return;
// using (var builder = NodeBuilderStratis.Create())
// {
// var node = builder.CreateNode();
// node.Start();
// var rpc = node.CreateRPCClient();
// //node.Generate(101);
// var priority = rpc.EstimatePriority(10);
// Assert.True(priority > 0 || priority == -1);
// }
//}
private void AssertJsonEquals(string json1, string json2)
{
foreach (var c in new[] { "\r\n", " ", "\t" })
{
json1 = json1.Replace(c, "");
json2 = json2.Replace(c, "");
}
Assert.Equal(json1, json2);
}
void AssertException<T>(Action act, Action<T> assert) where T : Exception
{
try
{
act();
Assert.False(true, "Should have thrown an exception");
}
catch (T ex)
{
assert(ex);
}
}
}
}
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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.BlockPulling;
using Stratis.Bitcoin.Features.BlockStore.LoopSteps;
using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.BlockStore
{
/// <summary>
/// The BlockStoreLoop simultaneously finds and downloads blocks and stores them in the BlockRepository.
/// </summary>
public class BlockStoreLoop
{
/// <summary>Factory for creating background async loop tasks.</summary>
private readonly IAsyncLoopFactory asyncLoopFactory;
/// <summary>The async loop we need to wait upon before we can shut down this feature.</summary>
private IAsyncLoop asyncLoop;
public StoreBlockPuller BlockPuller { get; }
public IBlockRepository BlockRepository { get; }
private readonly BlockStoreStats blockStoreStats;
/// <summary>Thread safe access to the best chain of block headers (that the node is aware of) from genesis.</summary>
internal readonly ConcurrentChain Chain;
/// <summary>Provider of IBD state.</summary>
public IInitialBlockDownloadState InitialBlockDownloadState { get; }
public IChainState ChainState { get; }
/// <summary>Provider of time functions.</summary>
private readonly IDateTimeProvider dateTimeProvider;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
/// <summary>Factory for creating loggers.</summary>
protected readonly ILoggerFactory loggerFactory;
/// <summary>Maximum number of bytes the block puller can download before the downloaded blocks are stored to the disk.</summary>
internal const uint MaxInsertBlockSize = 20 * 1024 * 1024;
/// <summary>Maximum number of bytes the pending storage can hold until the downloaded blocks are stored to the disk.</summary>
internal const uint MaxPendingInsertBlockSize = 5 * 1000 * 1000;
/// <summary>Global application life cycle control - triggers when application shuts down.</summary>
private readonly INodeLifetime nodeLifetime;
/// <summary>Blocks that in PendingStorage will be processed first before new blocks are downloaded.</summary>
public ConcurrentDictionary<uint256, BlockPair> PendingStorage { get; }
/// <summary>The minimum amount of blocks that can be stored in Pending Storage before they get processed.</summary>
public const int PendingStorageBatchThreshold = 10;
/// <summary>The chain of steps that gets executed to find and download blocks.</summary>
private BlockStoreStepChain stepChain;
/// <summary>Cached consensus tip.</summary>
/// <remarks>
/// Cached tip is needed in order to avoid race condition in the <see cref="DownloadAndStoreBlocksAsync"/>.
/// <para>
/// This condition happens when the actual ConsensusTip is updated but the block wasn't provided by signaler yet.
/// </para>
/// <para>
/// TODO: remove this quick fix later and solve the race condition by replacing the async loop with trigger-based invoking of <see cref="DownloadAndStoreBlocksAsync"/>.
/// </para>
/// </remarks>
private ChainedHeader CachedConsensusTip;
public virtual string StoreName
{
get { return "BlockStore"; }
}
private readonly StoreSettings storeSettings;
/// <summary>The highest stored block in the repository.</summary>
internal ChainedHeader StoreTip { get; private set; }
/// <summary>Public constructor for unit testing.</summary>
public BlockStoreLoop(IAsyncLoopFactory asyncLoopFactory,
StoreBlockPuller blockPuller,
IBlockRepository blockRepository,
IBlockStoreCache cache,
ConcurrentChain chain,
IChainState chainState,
StoreSettings storeSettings,
INodeLifetime nodeLifetime,
ILoggerFactory loggerFactory,
IInitialBlockDownloadState initialBlockDownloadState,
IDateTimeProvider dateTimeProvider)
{
this.asyncLoopFactory = asyncLoopFactory;
this.BlockPuller = blockPuller;
this.BlockRepository = blockRepository;
this.Chain = chain;
this.ChainState = chainState;
this.nodeLifetime = nodeLifetime;
this.storeSettings = storeSettings;
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
this.loggerFactory = loggerFactory;
this.dateTimeProvider = dateTimeProvider;
this.InitialBlockDownloadState = initialBlockDownloadState;
this.PendingStorage = new ConcurrentDictionary<uint256, BlockPair>();
this.blockStoreStats = new BlockStoreStats(this.BlockRepository, cache, this.dateTimeProvider, this.logger);
}
/// <summary>
/// Initialize the BlockStore
/// <para>
/// If StoreTip is <c>null</c>, the store is out of sync. This can happen when:</para>
/// <list>
/// <item>1. The node crashed.</item>
/// <item>2. The node was not closed down properly.</item>
/// </list>
/// <para>
/// To recover we walk back the chain until a common block header is found
/// and set the BlockStore's StoreTip to that.
/// </para>
/// </summary>
public async Task InitializeAsync()
{
this.logger.LogTrace("()");
if (this.storeSettings.ReIndex)
throw new NotImplementedException();
this.StoreTip = this.Chain.GetBlock(this.BlockRepository.BlockHash);
if (this.StoreTip == null)
{
var blockStoreResetList = new List<uint256>();
Block resetBlock = await this.BlockRepository.GetAsync(this.BlockRepository.BlockHash).ConfigureAwait(false);
uint256 resetBlockHash = resetBlock.GetHash();
while (this.Chain.GetBlock(resetBlockHash) == null)
{
blockStoreResetList.Add(resetBlockHash);
if (resetBlock.Header.HashPrevBlock == this.Chain.Genesis.HashBlock)
{
resetBlockHash = this.Chain.Genesis.HashBlock;
break;
}
resetBlock = await this.BlockRepository.GetAsync(resetBlock.Header.HashPrevBlock).ConfigureAwait(false);
Guard.NotNull(resetBlock, nameof(resetBlock));
resetBlockHash = resetBlock.GetHash();
}
ChainedHeader newTip = this.Chain.GetBlock(resetBlockHash);
await this.BlockRepository.DeleteAsync(newTip.HashBlock, blockStoreResetList).ConfigureAwait(false);
this.StoreTip = newTip;
this.logger.LogWarning("{0} Initialize recovering to block height = {1}, hash = {2}.", this.StoreName, newTip.Height, newTip.HashBlock);
}
if (this.storeSettings.TxIndex != this.BlockRepository.TxIndex)
{
if (this.StoreTip != this.Chain.Genesis)
throw new BlockStoreException($"You need to rebuild the {this.StoreName} database using -reindex-chainstate to change -txindex");
if (this.storeSettings.TxIndex)
await this.BlockRepository.SetTxIndexAsync(this.storeSettings.TxIndex).ConfigureAwait(false);
}
this.SetHighestPersistedBlock(this.StoreTip);
this.stepChain = new BlockStoreStepChain();
this.stepChain.SetNextStep(new ReorganiseBlockRepositoryStep(this, this.loggerFactory));
this.stepChain.SetNextStep(new CheckNextChainedBlockExistStep(this, this.loggerFactory));
this.stepChain.SetNextStep(new ProcessPendingStorageStep(this, this.loggerFactory));
this.stepChain.SetNextStep(new DownloadBlockStep(this, this.loggerFactory, this.dateTimeProvider));
this.CachedConsensusTip = this.ChainState.ConsensusTip;
this.StartLoop();
this.logger.LogTrace("(-)");
}
/// <summary>
/// Adds a block to Pending Storage.
/// <para>
/// The <see cref="BlockStoreSignaled"/> calls this method when a new block is available. Only add the block to pending storage if the store's tip is behind the given block.
/// </para>
/// </summary>
/// <param name="blockPair">The block and its chained header pair to be added to pending storage.</param>
/// <remarks>TODO: Possibly check the size of pending in memory</remarks>
public void AddToPending(BlockPair blockPair)
{
this.logger.LogTrace("({0}:'{1}')", nameof(blockPair), blockPair.ChainedHeader);
if (this.StoreTip.Height < blockPair.ChainedHeader.Height)
{
this.PendingStorage.TryAdd(blockPair.ChainedHeader.HashBlock, blockPair);
this.CachedConsensusTip = blockPair.ChainedHeader;
}
this.logger.LogTrace("(-)");
}
/// <summary>
/// Persists unsaved blocks to disk when the node shuts down.
/// <para>
/// Before we can shut down we need to ensure that the current async loop
/// has completed.
/// </para>
/// </summary>
internal void ShutDown()
{
this.asyncLoop?.Dispose();
this.DownloadAndStoreBlocksAsync(CancellationToken.None, true).Wait();
}
/// <summary>
/// Executes DownloadAndStoreBlocks()
/// </summary>
private void StartLoop()
{
this.asyncLoop = this.asyncLoopFactory.Run($"{this.StoreName}.DownloadAndStoreBlocks", async token =>
{
await this.DownloadAndStoreBlocksAsync(this.nodeLifetime.ApplicationStopping, false).ConfigureAwait(false);
},
this.nodeLifetime.ApplicationStopping,
repeatEvery: TimeSpans.Second,
startAfter: TimeSpans.FiveSeconds);
}
/// <summary>
/// Finds and downloads blocks to store in the BlockRepository.
/// <para>
/// This method executes a chain of steps in order:
/// <list>
/// <item>1. Reorganise the repository</item>
/// <item>2. Check if the block exists in store, if it does move on to the next block</item>
/// <item>3. Process the blocks in pending storage</item>
/// <item>4. Find and download blocks</item>
/// </list>
/// </para>
/// <para>
/// Steps return a <see cref="StepResult"/> which either signals the While loop
/// to break or continue execution.
/// </para>
/// </summary>
/// <param name="cancellationToken">CancellationToken to check</param>
/// <param name="disposeMode">This will <c>true</c> if the Flush() was called</param>
/// <remarks>
/// TODO: add support to BlockStoreLoop to unset LazyLoadingOn when not in IBD
/// When in IBD we may need many reads for the block key without fetching the block
/// So the repo starts with LazyLoadingOn = true, however when not anymore in IBD
/// a read is normally done when a peer is asking for the entire block (not just the key)
/// then if LazyLoadingOn = false the read will be faster on the entire block
/// </remarks>
private async Task DownloadAndStoreBlocksAsync(CancellationToken cancellationToken, bool disposeMode)
{
this.logger.LogTrace("({0}:{1})", nameof(disposeMode), disposeMode);
while (!cancellationToken.IsCancellationRequested)
{
if (this.StoreTip.Height >= this.CachedConsensusTip?.Height)
break;
var nextChainedBlock = this.Chain.GetBlock(this.StoreTip.Height + 1);
if (nextChainedBlock == null)
break;
if (this.blockStoreStats.CanLog)
this.blockStoreStats.Log();
var result = await this.stepChain.ExecuteAsync(nextChainedBlock, disposeMode, cancellationToken).ConfigureAwait(false);
if (result == StepResult.Stop)
break;
}
this.logger.LogTrace("(-)");
}
/// <summary>Set the store's tip</summary>
internal void SetStoreTip(ChainedHeader chainedHeader)
{
this.logger.LogTrace("({0}:'{1}')", nameof(chainedHeader), chainedHeader?.HashBlock);
Guard.NotNull(chainedHeader, nameof(chainedHeader));
this.StoreTip = chainedHeader;
this.SetHighestPersistedBlock(chainedHeader);
this.logger.LogTrace("(-)");
}
/// <summary>Set the highest persisted block in the chain.</summary>
protected virtual void SetHighestPersistedBlock(ChainedHeader block)
{
this.logger.LogTrace("({0}:'{1}')", nameof(block), block?.HashBlock);
if (this.BlockRepository is BlockRepository blockRepository)
blockRepository.HighestPersistedBlock = block;
this.logger.LogTrace("(-)");
}
}
}
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 System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Base.Deployments;
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;
using static Stratis.Bitcoin.Features.Miner.PowBlockAssembler;
namespace Stratis.Bitcoin.Features.Miner
{
public abstract class BlockAssembler
{
/// <summary>
/// Tip of the chain that this instance will work with without touching any shared chain resources.
/// </summary>
protected ChainedHeader ChainTip;
/// <summary>Manager of the longest fully validated chain of blocks.</summary>
protected readonly IConsensusLoop ConsensusLoop;
/// <summary>Provider of date time functions.</summary>
protected readonly IDateTimeProvider DateTimeProvider;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
/// <summary>Transaction memory pool for managing transactions in the memory pool.</summary>
protected readonly ITxMempool Mempool;
/// <summary>Lock for memory pool access.</summary>
protected readonly MempoolSchedulerLock MempoolLock;
/// <summary>The current network.</summary>
protected readonly Network Network;
/// <summary>Assembler options specific to the assembler e.g. <see cref="AssemblerOptions.BlockMaxSize"/>.</summary>
protected AssemblerOptions Options;
private const long TicksPerMicrosecond = 10;
// Limit the number of attempts to add transactions to the block when it is
// close to full; this is just a simple heuristic to finish quickly if the
// mempool has a lot of entries.
protected const int MaxConsecutiveAddTransactionFailures = 1000;
// Unconfirmed transactions in the memory pool often depend on other
// transactions in the memory pool. When we select transactions from the
// pool, we select by highest fee rate of a transaction combined with all
// its ancestors.
protected long LastBlockTx = 0;
protected long LastBlockSize = 0;
protected long LastBlockWeight = 0;
protected long MedianTimePast;
// The constructed block template.
protected BlockTemplate BlockTemplate;
// A convenience pointer that always refers to the CBlock in pblocktemplate.
protected Block block;
// Configuration parameters for the block size.
protected bool IncludeWitness;
protected uint BlockMaxWeight, BlockMaxSize;
protected bool NeedSizeAccounting;
protected FeeRate BlockMinFeeRate;
// Information on the current status of the block.
protected long BlockWeight;
protected long BlockSize;
protected long BlockTx;
protected long BlockSigOpsCost;
public Money fees;
protected TxMempool.SetEntries inBlock;
protected Transaction coinbase;
// Chain context for the block.
protected int height;
protected long LockTimeCutoff;
protected Script scriptPubKey;
protected BlockAssembler(
IConsensusLoop consensusLoop,
IDateTimeProvider dateTimeProvider,
ILoggerFactory loggerFactory,
ITxMempool mempool,
MempoolSchedulerLock mempoolLock,
Network network,
AssemblerOptions options = null)
{
this.ConsensusLoop = consensusLoop;
this.DateTimeProvider = dateTimeProvider;
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
this.Mempool = mempool;
this.MempoolLock = mempoolLock;
this.Network = network;
this.Options = options ?? new AssemblerOptions();
this.BlockMinFeeRate = this.Options.BlockMinFeeRate;
// Limit weight to between 4K and MAX_BLOCK_WEIGHT-4K for sanity.
this.BlockMaxWeight = (uint)Math.Max(4000, Math.Min(PowMining.DefaultBlockMaxWeight - 4000, this.Options.BlockMaxWeight));
// Limit size to between 1K and MAX_BLOCK_SERIALIZED_SIZE-1K for sanity.
this.BlockMaxSize = (uint)Math.Max(1000, Math.Min(network.Consensus.Option<PowConsensusOptions>().MaxBlockSerializedSize - 1000, this.Options.BlockMaxSize));
// Whether we need to account for byte usage (in addition to weight usage).
this.NeedSizeAccounting = (this.BlockMaxSize < network.Consensus.Option<PowConsensusOptions>().MaxBlockSerializedSize - 1000);
this.Configure();
}
private int ComputeBlockVersion(ChainedHeader prevChainedHeader, NBitcoin.Consensus consensus)
{
uint version = ThresholdConditionCache.VersionbitsTopBits;
var thresholdConditionCache = new ThresholdConditionCache(consensus);
IEnumerable<BIP9Deployments> deployments = Enum.GetValues(typeof(BIP9Deployments)).OfType<BIP9Deployments>();
foreach (BIP9Deployments deployment in deployments)
{
ThresholdState state = thresholdConditionCache.GetState(prevChainedHeader, deployment);
if ((state == ThresholdState.LockedIn) || (state == ThresholdState.Started))
version |= thresholdConditionCache.Mask(deployment);
}
return (int)version;
}
/// <summary>
/// Compute the block version.
/// </summary>
protected virtual void ComputeBlockVersion()
{
this.height = this.ChainTip.Height + 1;
this.block.Header.Version = this.ComputeBlockVersion(this.ChainTip, this.Network.Consensus);
}
/// <summary>
/// Create coinbase transaction.
/// Set the coin base with zero money.
/// Once we have the fee we can update the amount.
/// </summary>
protected virtual void CreateCoinbase()
{
this.coinbase = this.Network.Consensus.ConsensusFactory.CreateTransaction();
this.coinbase.Time = (uint)this.DateTimeProvider.GetAdjustedTimeAsUnixTimestamp();
this.coinbase.AddInput(TxIn.CreateCoinbase(this.ChainTip.Height + 1));
this.coinbase.AddOutput(new TxOut(Money.Zero, this.scriptPubKey));
this.block.AddTransaction(this.coinbase);
this.BlockTemplate.VTxFees.Add(-1); // Updated at end.
this.BlockTemplate.TxSigOpsCost.Add(-1); // Updated at end.
}
/// <summary>
/// Configures (resets) the builder to its default state
/// before constructing a new block.
/// </summary>
private void Configure()
{
this.BlockSize = 1000;
this.BlockTemplate = new BlockTemplate(this.Network);
this.BlockTx = 0;
this.BlockWeight = 4000;
this.BlockSigOpsCost = 400;
this.fees = 0;
this.inBlock = new TxMempool.SetEntries();
this.IncludeWitness = false;
}
/// <summary>
/// Constructs a block template which will be passed to consensus.
/// </summary>
/// <param name="chainTip">Tip of the chain that this instance will work with without touching any shared chain resources.</param>
/// <param name="scriptPubKey">Script that explains what conditions must be met to claim ownership of a coin.</param>
/// <returns>The contructed <see cref="Miner.BlockTemplate"/>.</returns>
protected void OnBuild(ChainedHeader chainTip, Script scriptPubKey)
{
this.Configure();
this.ChainTip = chainTip;
this.block = this.BlockTemplate.Block;
this.scriptPubKey = scriptPubKey;
this.CreateCoinbase();
this.ComputeBlockVersion();
// TODO: MineBlocksOnDemand
// -regtest only: allow overriding block.nVersion with
// -blockversion=N to test forking scenarios
//if (this.network. chainparams.MineBlocksOnDemand())
// pblock->nVersion = GetArg("-blockversion", pblock->nVersion);
this.MedianTimePast = Utils.DateTimeToUnixTime(this.ChainTip.GetMedianTimePast());
this.LockTimeCutoff = PowConsensusValidator.StandardLocktimeVerifyFlags.HasFlag(Transaction.LockTimeFlags.MedianTimePast)
? this.MedianTimePast
: this.block.Header.Time;
// TODO: Implement Witness Code
// Decide whether to include witness transactions
// This is only needed in case the witness softfork activation is reverted
// (which would require a very deep reorganization) or when
// -promiscuousmempoolflags is used.
// TODO: replace this with a call to main to assess validity of a mempool
// transaction (which in most cases can be a no-op).
this.IncludeWitness = false; //IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx;
// add transactions from the mempool
int nPackagesSelected;
int nDescendantsUpdated;
this.AddTransactions(out nPackagesSelected, out nDescendantsUpdated);
this.LastBlockTx = this.BlockTx;
this.LastBlockSize = this.BlockSize;
this.LastBlockWeight = this.BlockWeight;
// TODO: Implement Witness Code
// pblocktemplate->CoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
this.BlockTemplate.VTxFees[0] = -this.fees;
this.coinbase.Outputs[0].Value = this.fees + this.ConsensusLoop.Validator.GetProofOfWorkReward(this.height);
this.BlockTemplate.TotalFee = this.fees;
int nSerializeSize = this.block.GetSerializedSize();
this.logger.LogDebug("Serialized size is {0} bytes, block weight is {1}, number of txs is {2}, tx fees are {3}, number of sigops is {4}.", nSerializeSize, this.ConsensusLoop.Validator.GetBlockWeight(this.block), this.BlockTx, this.fees, this.BlockSigOpsCost);
this.OnUpdateHeaders();
//pblocktemplate->TxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);
this.OnTestBlockValidity();
//int64_t nTime2 = GetTimeMicros();
//LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));
}
// Add a tx to the block.
private void AddToBlock(TxMempoolEntry iter)
{
this.logger.LogTrace("({0}.{1}:'{2}')", nameof(iter), nameof(iter.TransactionHash), iter.TransactionHash);
this.block.AddTransaction(iter.Transaction);
this.BlockTemplate.VTxFees.Add(iter.Fee);
this.BlockTemplate.TxSigOpsCost.Add(iter.SigOpCost);
if (this.NeedSizeAccounting)
this.BlockSize += iter.Transaction.GetSerializedSize();
this.BlockWeight += iter.TxWeight;
this.BlockTx++;
this.BlockSigOpsCost += iter.SigOpCost;
this.fees += iter.Fee;
this.inBlock.Add(iter);
//bool fPrintPriority = GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY);
//if (fPrintPriority)
//{
// LogPrintf("fee %s txid %s\n",
// CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(),
// iter->GetTx().GetHash().ToString());
//}
this.logger.LogTrace("(-)");
}
// Methods for how to add transactions to a block.
// Add transactions based on feerate including unconfirmed ancestors
// Increments nPackagesSelected / nDescendantsUpdated with corresponding
// statistics from the package selection (for logging statistics).
// This transaction selection algorithm orders the mempool based
// on feerate of a transaction including all unconfirmed ancestors.
// Since we don't remove transactions from the mempool as we select them
// for block inclusion, we need an alternate method of updating the feerate
// of a transaction with its not-yet-selected ancestors as we go.
// This is accomplished by walking the in-mempool descendants of selected
// transactions and storing a temporary modified state in mapModifiedTxs.
// Each time through the loop, we compare the best transaction in
// mapModifiedTxs with the next transaction in the mempool to decide what
// transaction package to work on next.
protected virtual void AddTransactions(out int nPackagesSelected, out int nDescendantsUpdated)
{
nPackagesSelected = 0;
nDescendantsUpdated = 0;
this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(nPackagesSelected), nPackagesSelected, nameof(nDescendantsUpdated), nDescendantsUpdated);
// mapModifiedTx will store sorted packages after they are modified
// because some of their txs are already in the block.
var mapModifiedTx = new Dictionary<uint256, TxMemPoolModifiedEntry>();
//var mapModifiedTxRes = this.mempoolScheduler.ReadAsync(() => mempool.MapTx.Values).GetAwaiter().GetResult();
// mapModifiedTxRes.Select(s => new TxMemPoolModifiedEntry(s)).OrderBy(o => o, new CompareModifiedEntry());
// Keep track of entries that failed inclusion, to avoid duplicate work.
TxMempool.SetEntries failedTx = new TxMempool.SetEntries();
// Start by adding all descendants of previously added txs to mapModifiedTx
// and modifying them for their already included ancestors.
this.UpdatePackagesForAdded(this.inBlock, mapModifiedTx);
List<TxMempoolEntry> ancestorScoreList = this.MempoolLock.ReadAsync(() => this.Mempool.MapTx.AncestorScore).GetAwaiter().GetResult().ToList();
TxMempoolEntry iter;
int nConsecutiveFailed = 0;
while (ancestorScoreList.Any() || mapModifiedTx.Any())
{
TxMempoolEntry mi = ancestorScoreList.FirstOrDefault();
if (mi != null)
{
// Skip entries in mapTx that are already in a block or are present
// in mapModifiedTx (which implies that the mapTx ancestor state is
// stale due to ancestor inclusion in the block).
// Also skip transactions that we've already failed to add. This can happen if
// we consider a transaction in mapModifiedTx and it fails: we can then
// potentially consider it again while walking mapTx. It's currently
// guaranteed to fail again, but as a belt-and-suspenders check we put it in
// failedTx and avoid re-evaluation, since the re-evaluation would be using
// cached size/sigops/fee values that are not actually correct.
// First try to find a new transaction in mapTx to evaluate.
if (mapModifiedTx.ContainsKey(mi.TransactionHash) || this.inBlock.Contains(mi) || failedTx.Contains(mi))
{
ancestorScoreList.Remove(mi);
continue;
}
}
// Now that mi is not stale, determine which transaction to evaluate:
// the next entry from mapTx, or the best from mapModifiedTx?
bool fUsingModified = false;
TxMemPoolModifiedEntry modit;
var compare = new CompareModifiedEntry();
if (mi == null)
{
modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).First();
iter = modit.iter;
fUsingModified = true;
}
else
{
// Try to compare the mapTx entry to the mapModifiedTx entry
iter = mi;
modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).FirstOrDefault();
if ((modit != null) && (compare.Compare(modit, new TxMemPoolModifiedEntry(iter)) > 0))
{
// The best entry in mapModifiedTx has higher score
// than the one from mapTx..
// Switch which transaction (package) to consider.
iter = modit.iter;
fUsingModified = true;
}
else
{
// Either no entry in mapModifiedTx, or it's worse than mapTx.
// Increment mi for the next loop iteration.
ancestorScoreList.Remove(iter);
}
}
// We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
// contain anything that is inBlock.
Guard.Assert(!this.inBlock.Contains(iter));
long packageSize = iter.SizeWithAncestors;
Money packageFees = iter.ModFeesWithAncestors;
long packageSigOpsCost = iter.SizeWithAncestors;
if (fUsingModified)
{
packageSize = modit.SizeWithAncestors;
packageFees = modit.ModFeesWithAncestors;
packageSigOpsCost = modit.SigOpCostWithAncestors;
}
if (packageFees < this.BlockMinFeeRate.GetFee((int)packageSize))
{
// Everything else we might consider has a lower fee rate
return;
}
if (!this.TestPackage(packageSize, packageSigOpsCost))
{
if (fUsingModified)
{
// Since we always look at the best entry in mapModifiedTx,
// we must erase failed entries so that we can consider the
// next best entry on the next loop iteration
mapModifiedTx.Remove(modit.iter.TransactionHash);
failedTx.Add(iter);
}
nConsecutiveFailed++;
if ((nConsecutiveFailed > MaxConsecutiveAddTransactionFailures) && (this.BlockWeight > this.BlockMaxWeight - 4000))
{
// Give up if we're close to full and haven't succeeded in a while
break;
}
continue;
}
TxMempool.SetEntries ancestors = new TxMempool.SetEntries();
long nNoLimit = long.MaxValue;
string dummy;
this.Mempool.CalculateMemPoolAncestors(iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, out dummy, false);
this.OnlyUnconfirmed(ancestors);
ancestors.Add(iter);
// Test if all tx's are Final.
if (!this.TestPackageTransactions(ancestors))
{
if (fUsingModified)
{
mapModifiedTx.Remove(modit.iter.TransactionHash);
failedTx.Add(iter);
}
continue;
}
// This transaction will make it in; reset the failed counter.
nConsecutiveFailed = 0;
// Package can be added. Sort the entries in a valid order.
// Sort package by ancestor count
// If a transaction A depends on transaction B, then A's ancestor count
// must be greater than B's. So this is sufficient to validly order the
// transactions for block inclusion.
List<TxMempoolEntry> sortedEntries = ancestors.ToList().OrderBy(o => o, new CompareTxIterByAncestorCount()).ToList();
foreach (TxMempoolEntry sortedEntry in sortedEntries)
{
this.AddToBlock(sortedEntry);
// Erase from the modified set, if present
mapModifiedTx.Remove(sortedEntry.TransactionHash);
}
nPackagesSelected++;
// Update transactions that depend on each of these
nDescendantsUpdated += this.UpdatePackagesForAdded(ancestors, mapModifiedTx);
}
this.logger.LogTrace("(-)");
}
// Remove confirmed (inBlock) entries from given set
private void OnlyUnconfirmed(TxMempool.SetEntries testSet)
{
foreach (var setEntry in testSet.ToList())
{
// Only test txs not already in the block
if (this.inBlock.Contains(setEntry))
{
testSet.Remove(setEntry);
}
}
}
// Test if a new package would "fit" in the block.
private bool TestPackage(long packageSize, long packageSigOpsCost)
{
// TODO: Switch to weight-based accounting for packages instead of vsize-based accounting.
if (this.BlockWeight + this.Network.Consensus.Option<PowConsensusOptions>().WitnessScaleFactor * packageSize >= this.BlockMaxWeight)
return false;
if (this.BlockSigOpsCost + packageSigOpsCost >= this.Network.Consensus.Option<PowConsensusOptions>().MaxBlockSigopsCost)
return false;
return true;
}
// Perform transaction-level checks before adding to block:
// - transaction finality (locktime)
// - premature witness (in case segwit transactions are added to mempool before
// segwit activation)
// - serialized size (in case -blockmaxsize is in use)
private bool TestPackageTransactions(TxMempool.SetEntries package)
{
long nPotentialBlockSize = this.BlockSize; // only used with needSizeAccounting
foreach (TxMempoolEntry it in package)
{
if (!it.Transaction.IsFinal(Utils.UnixTimeToDateTime(this.LockTimeCutoff), this.height))
return false;
if (!this.IncludeWitness && it.Transaction.HasWitness)
return false;
if (this.NeedSizeAccounting)
{
int nTxSize = it.Transaction.GetSerializedSize();
if (nPotentialBlockSize + nTxSize >= this.BlockMaxSize)
return false;
nPotentialBlockSize += nTxSize;
}
}
return true;
}
// Add descendants of given transactions to mapModifiedTx with ancestor
// state updated assuming given transactions are inBlock. Returns number
// of updated descendants.
private int UpdatePackagesForAdded(TxMempool.SetEntries alreadyAdded, Dictionary<uint256, TxMemPoolModifiedEntry> mapModifiedTx)
{
int descendantsUpdated = 0;
foreach (TxMempoolEntry setEntry in alreadyAdded)
{
TxMempool.SetEntries setEntries = new TxMempool.SetEntries();
this.MempoolLock.ReadAsync(() => this.Mempool.CalculateDescendants(setEntry, setEntries)).GetAwaiter().GetResult();
foreach (var desc in setEntries)
{
if (alreadyAdded.Contains(desc))
continue;
descendantsUpdated++;
TxMemPoolModifiedEntry modEntry;
if (!mapModifiedTx.TryGetValue(desc.TransactionHash, out modEntry))
{
modEntry = new TxMemPoolModifiedEntry(desc);
mapModifiedTx.Add(desc.TransactionHash, modEntry);
}
modEntry.SizeWithAncestors -= setEntry.GetTxSize();
modEntry.ModFeesWithAncestors -= setEntry.ModifiedFee;
modEntry.SigOpCostWithAncestors -= setEntry.SigOpCost;
}
}
return descendantsUpdated;
}
public abstract BlockTemplate Build(ChainedHeader chainTip, Script scriptPubKey);
public abstract void OnUpdateHeaders();
public abstract void OnTestBlockValidity();
}
public class PowBlockAssembler : BlockAssembler
{
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
// Container for tracking updates to ancestor feerate as we include (parent)
// transactions in a block.
public class TxMemPoolModifiedEntry
{
public TxMemPoolModifiedEntry(TxMempoolEntry entry)
{
this.iter = entry;
this.SizeWithAncestors = entry.SizeWithAncestors;
this.ModFeesWithAncestors = entry.ModFeesWithAncestors;
this.SigOpCostWithAncestors = entry.SigOpCostWithAncestors;
}
public TxMempoolEntry iter;
public long SizeWithAncestors;
public Money ModFeesWithAncestors;
public long SigOpCostWithAncestors;
}
// This matches the calculation in CompareTxMemPoolEntryByAncestorFee,
// except operating on CTxMemPoolModifiedEntry.
// TODO: Refactor to avoid duplication of this logic.
public class CompareModifiedEntry : IComparer<TxMemPoolModifiedEntry>
{
public int Compare(TxMemPoolModifiedEntry a, TxMemPoolModifiedEntry b)
{
Money f1 = a.ModFeesWithAncestors * b.SizeWithAncestors;
Money f2 = b.ModFeesWithAncestors * a.SizeWithAncestors;
if (f1 == f2)
return TxMempool.CompareIteratorByHash.InnerCompare(a.iter, b.iter);
return f1 > f2 ? 1 : -1;
}
}
// A comparator that sorts transactions based on number of ancestors.
// This is sufficient to sort an ancestor package in an order that is valid
// to appear in a block.
public class CompareTxIterByAncestorCount : IComparer<TxMempoolEntry>
{
public int Compare(TxMempoolEntry a, TxMempoolEntry b)
{
if (a.CountWithAncestors != b.CountWithAncestors)
return a.CountWithAncestors < b.CountWithAncestors ? -1 : 1;
return TxMempool.CompareIteratorByHash.InnerCompare(a, b);
}
}
public PowBlockAssembler(
IConsensusLoop consensusLoop,
IDateTimeProvider dateTimeProvider,
ILoggerFactory loggerFactory,
ITxMempool mempool,
MempoolSchedulerLock mempoolLock,
Network network,
AssemblerOptions options = null)
: base(consensusLoop, dateTimeProvider, loggerFactory, mempool, mempoolLock, network)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
/// <inheritdoc/>
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.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.Bits = this.block.Header.GetWorkRequired(this.Network, this.ChainTip);
this.block.Header.Nonce = 0;
this.logger.LogTrace("(-)");
}
public override void OnTestBlockValidity()
{
this.logger.LogTrace("()");
var context = new RuleContext(new BlockValidationContext { Block = this.block }, this.Network.Consensus, this.ConsensusLoop.Tip)
{
CheckPow = false,
CheckMerkleRoot = false,
};
this.ConsensusLoop.ValidateBlock(context);
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