Commit 73763b96 authored by Jeremy Bokobza's avatar Jeremy Bokobza

import of HBitcoin's Tracker and its dependencies

parent c5350c48
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ConcurrentHashSet" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" /> <PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" />
</ItemGroup> </ItemGroup>
......
//from https://github.com/brianchance/MonoTouchMVVMCrossValidationTester/blob/master/Validation.Core/ConcurrentObservableDictionary.cs
//modified
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace System.Collections.ObjectModel
{
public class ConcurrentObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private readonly object Lock = new object();
protected ConcurrentDictionary<TKey, TValue> ConcurrentDictionary { get; private set; }
#region Constructors
public ConcurrentObservableDictionary()
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>();
}
public ConcurrentObservableDictionary(ConcurrentDictionary<TKey, TValue> dictionary)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary);
}
public ConcurrentObservableDictionary(IEqualityComparer<TKey> comparer)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
}
public ConcurrentObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
ConcurrentDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value) => Insert(key, value, true);
public bool ContainsKey(TKey key) => ConcurrentDictionary.ContainsKey(key);
public ICollection<TKey> Keys => ConcurrentDictionary.Keys;
public bool Remove(TKey key) => Remove(key, suppressNotifications: false);
private bool Remove(TKey key, bool suppressNotifications)
{
lock(Lock)
{
TValue value;
var ret = ConcurrentDictionary.TryRemove(key, out value);
if(ret && !suppressNotifications) OnCollectionChanged();
return ret;
}
}
public bool TryGetValue(TKey key, out TValue value) => ConcurrentDictionary.TryGetValue(key, out value);
public ICollection<TValue> Values => ConcurrentDictionary.Values;
public TValue this[TKey key]
{
get
{
TValue value;
return TryGetValue(key, out value) ? value : default(TValue);
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item) => Insert(item.Key, item.Value, true);
public void Clear()
{
lock(Lock)
{
if (ConcurrentDictionary.Count > 0)
{
ConcurrentDictionary.Clear();
OnCollectionChanged();
}
}
}
public bool Contains(KeyValuePair<TKey, TValue> item) => ConcurrentDictionary.Contains(item);
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
/// <summary>
/// NotImplementedException
/// </summary>
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public int Count => ConcurrentDictionary.Count;
public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => ConcurrentDictionary.GetEnumerator();
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)ConcurrentDictionary).GetEnumerator();
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddOrReplace(TKey key, TValue value)
{
if (ContainsKey(key))
{
Remove(key, suppressNotifications: true);
Add(key, value);
}
else
{
Add(key, value);
}
}
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="items"></param>
public void AddRange(IDictionary<TKey, TValue> items)
{
throw new NotImplementedException();
}
private void Insert(TKey key, TValue value, bool add)
{
lock(Lock)
{
if (key == null) throw new ArgumentNullException(nameof(key));
TValue item;
if (ConcurrentDictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
ConcurrentDictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
OnPropertyChanged(key.ToString());
}
else
{
ConcurrentDictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
OnPropertyChanged(key.ToString());
}
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, changedItem, 0));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, 0));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItems, 0));
}
}
}
\ No newline at end of file
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using ConcurrentCollections;
namespace System.Collections.ObjectModel
{
public class ConcurrentObservableHashSet<T> : INotifyCollectionChanged, IReadOnlyCollection<T>
{
protected ConcurrentHashSet<T> ConcurrentHashSet { get; }
private readonly object Lock = new object();
public ConcurrentObservableHashSet()
{
ConcurrentHashSet = new ConcurrentHashSet<T>();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged()
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<T> GetEnumerator() => ConcurrentHashSet.GetEnumerator();
public bool TryAdd(T item)
{
lock(Lock)
{
if(ConcurrentHashSet.Add(item))
{
OnCollectionChanged();
return true;
}
return false;
}
}
public void Clear()
{
lock(Lock)
{
if(ConcurrentHashSet.Count > 0)
{
ConcurrentHashSet.Clear();
OnCollectionChanged();
}
}
}
public bool Contains(T item) => ConcurrentHashSet.Contains(item);
public bool TryRemove(T item)
{
lock(Lock)
{
if(ConcurrentHashSet.TryRemove(item))
{
OnCollectionChanged();
return true;
}
return false;
}
}
public int Count => ConcurrentHashSet.Count;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HBitcoin.Models
{
public struct Height : IEquatable<Height>, IEquatable<int>, IComparable<Height>, IComparable<int>
{
public HeightType Type { get; }
private readonly int _value;
public int Value
{
get
{
if (Type == HeightType.Chain)
return _value;
if (Type == HeightType.MemPool)
return int.MaxValue - 1;
//if(Type == HeightType.Unknown)
return int.MaxValue;
}
}
public static Height MemPool => new Height(HeightType.MemPool);
public static Height Unknown => new Height(HeightType.Unknown);
public Height(int height)
{
if(height < 0) throw new ArgumentException($"{nameof(height)} : {height} cannot be < 0");
if(height == int.MaxValue) Type = HeightType.Unknown;
else if(height == int.MaxValue - 1) Type = HeightType.MemPool;
else Type = HeightType.Chain;
_value = height;
}
public Height(string heightOrHeightType)
{
var trimmed = heightOrHeightType.Trim();
if (trimmed == HeightType.MemPool.ToString())
this = MemPool;
else if (trimmed == HeightType.Unknown.ToString())
this = Unknown;
else this = new Height(int.Parse(trimmed));
}
public Height(HeightType type)
{
if(type == HeightType.Chain) throw new NotSupportedException($"For {type} height must be specified");
Type = type;
if (Type == HeightType.MemPool)
_value = int.MaxValue - 1;
else _value = int.MaxValue; // HeightType.Unknown
}
public override string ToString()
{
if(Type == HeightType.Chain) return Value.ToString();
else return Type.ToString();
}
#region EqualityAndComparison
public override bool Equals(object obj) => obj is Height && this == (Height) obj;
public bool Equals(Height other) => this == other;
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(Height x, Height y) => x.Value == y.Value;
public static bool operator !=(Height x, Height y) => !(x == y);
public bool Equals(int other) => Value == other;
public static bool operator ==(int x, Height y) => x == y.Value;
public static bool operator ==(Height x, int y) => x.Value == y;
public static bool operator !=(int x, Height y) => !(x == y);
public static bool operator !=(Height x, int y) => !(x == y);
public int CompareTo(Height other) => Value.CompareTo(other.Value);
public int CompareTo(int other)
{
if (Value > other) return -1;
if (Value == other) return 0;
return 1;
}
public static bool operator >(Height x, Height y) => x.Value > y.Value;
public static bool operator <(Height x, Height y) => x.Value < y.Value;
public static bool operator >=(Height x, Height y) => x.Value >= y.Value;
public static bool operator <=(Height x, Height y) => x.Value <= y.Value;
public static bool operator >(int x, Height y) => x > y.Value;
public static bool operator >(Height x, int y) => x.Value > y;
public static bool operator <(int x, Height y) => x < y.Value;
public static bool operator <(Height x, int y) => x.Value < y;
public static bool operator >=(int x, Height y) => x >= y.Value;
public static bool operator <=(int x, Height y) => x <= y.Value;
public static bool operator >=(Height x, int y) => x.Value >= y;
public static bool operator <=(Height x, int y) => x.Value <= y;
#endregion
}
public enum HeightType
{
Chain,
MemPool,
Unknown
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using ConcurrentCollections;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.Models
{
public class SmartMerkleBlock : IEquatable<SmartMerkleBlock>, IComparable<SmartMerkleBlock>
{
#region Members
public Height Height { get; }
public MerkleBlock MerkleBlock { get; }
public IEnumerable<uint256> GetMatchedTransactions() => MerkleBlock.PartialMerkleTree.GetMatchedTransactions();
public uint TransactionCount => MerkleBlock.PartialMerkleTree.TransactionCount;
#endregion
#region Constructors
public SmartMerkleBlock()
{
}
public SmartMerkleBlock(Height height, Block block, params uint256[] interestedTransactionIds)
{
Height = height;
MerkleBlock = interestedTransactionIds == null || interestedTransactionIds.Length == 0 ? block.Filter() : block.Filter(interestedTransactionIds);
}
public SmartMerkleBlock(int height, Block block, params uint256[] interestedTransactionIds)
{
Height = new Height(height);
MerkleBlock = interestedTransactionIds == null || interestedTransactionIds.Length == 0 ? block.Filter() : block.Filter(interestedTransactionIds);
}
public SmartMerkleBlock(Height height, MerkleBlock merkleBlock)
{
Height = height;
MerkleBlock = merkleBlock;
}
#endregion
#region Formatting
public static byte[] ToBytes(SmartMerkleBlock smartMerkleBlock) =>
BitConverter.GetBytes(smartMerkleBlock.Height.Value) // 4bytes
.Concat(smartMerkleBlock.MerkleBlock.ToBytes())
.ToArray();
public byte[] ToBytes() => ToBytes(this);
public static SmartMerkleBlock FromBytes(byte[] bytes)
{
var heightBytes = bytes.Take(4).ToArray();
var merkleBlockBytes = bytes.Skip(4).ToArray();
var height = new Height(BitConverter.ToInt32(heightBytes, startIndex: 0));
// Bypass NBitcoin bug
var merkleBlock = new MerkleBlock();
if(!merkleBlock.ToBytes().SequenceEqual(merkleBlockBytes)) // if not default MerkleBlock
{
merkleBlock.FromBytes(merkleBlockBytes);
}
return new SmartMerkleBlock(height, merkleBlock);
}
#endregion
#region EqualityAndComparison
public override bool Equals(object obj) => obj is SmartMerkleBlock && this == (SmartMerkleBlock)obj;
public bool Equals(SmartMerkleBlock other) => this == other;
public override int GetHashCode()
{
var hash = Height.GetHashCode();
hash = hash ^ MerkleBlock.Header.GetHash().GetHashCode();
hash = hash ^ MerkleBlock.Header.HashPrevBlock.GetHashCode();
hash = hash ^ MerkleBlock.Header.HashMerkleRoot.GetHashCode();
foreach(uint256 txhash in GetMatchedTransactions())
hash = hash ^ txhash.GetHashCode();
return hash;
}
public static bool operator ==(SmartMerkleBlock x, SmartMerkleBlock y)
{
if (x.Height != y.Height)
return false;
if(x.MerkleBlock.Header.GetHash() != y.MerkleBlock.Header.GetHash())
return false;
if (x.MerkleBlock.Header.HashPrevBlock != y.MerkleBlock.Header.HashPrevBlock)
return false;
if (x.MerkleBlock.Header.HashMerkleRoot != y.MerkleBlock.Header.HashMerkleRoot)
return false;
if (x.TransactionCount != y.TransactionCount)
return false;
if(x.TransactionCount == 0) return true;
if (!x.GetMatchedTransactions().SequenceEqual(y.GetMatchedTransactions()))
return false;
return true;
}
public static bool operator !=(SmartMerkleBlock x, SmartMerkleBlock y) => !(x == y);
public int CompareTo(SmartMerkleBlock other) => Height.CompareTo(other.Height);
public static bool operator >(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height > y.Height;
public static bool operator <(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height < y.Height;
public static bool operator >=(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height >= y.Height;
public static bool operator <=(SmartMerkleBlock x, SmartMerkleBlock y) => x.Height <= y.Height;
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.Models
{
public class SmartTransaction: IEquatable<SmartTransaction>
{
#region Members
public Height Height { get; }
public Transaction Transaction { get; }
public bool Confirmed => Height.Type == HeightType.Chain;
public uint256 GetHash() => Transaction.GetHash();
#endregion
#region Constructors
public SmartTransaction()
{
}
public SmartTransaction(Transaction transaction, Height height)
{
Height = height;
Transaction = transaction;
}
#endregion
#region Equality
public bool Equals(SmartTransaction other) => GetHash().Equals(other.GetHash());
public bool Equals(Transaction other) => GetHash().Equals(other.GetHash());
public override bool Equals(object obj)
{
bool rc = false;
if (obj is SmartTransaction)
{
var transaction = (SmartTransaction)obj;
rc = GetHash().Equals(transaction.GetHash());
}
else if (obj is Transaction)
{
var transaction = (Transaction)obj;
rc = GetHash().Equals(transaction.GetHash());
}
return rc;
}
public override int GetHashCode()
{
return GetHash().GetHashCode();
}
public static bool operator !=(SmartTransaction tx1, SmartTransaction tx2)
{
return !(tx1 == tx2);
}
public static bool operator ==(SmartTransaction tx1, SmartTransaction tx2)
{
bool rc;
if(ReferenceEquals(tx1, tx2)) rc = true;
else if((object) tx1 == null || (object) tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator ==(Transaction tx1, SmartTransaction tx2)
{
bool rc;
if ((object)tx1 == null || (object)tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator !=(Transaction tx1, SmartTransaction tx2)
{
return !(tx1 == tx2);
}
public static bool operator ==(SmartTransaction tx1, Transaction tx2)
{
bool rc;
if ((object)tx1 == null || (object)tx2 == null)
{
rc = false;
}
else
{
rc = tx1.GetHash().Equals(tx2.GetHash());
}
return rc;
}
public static bool operator !=(SmartTransaction tx1, Transaction tx2)
{
return !(tx1 == tx2);
}
#endregion
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ConcurrentCollections;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.FullBlockSpv
{
public class Tracker
{
#region Members
public Network Network { get; private set; }
public ConcurrentHashSet<SmartMerkleBlock> MerkleChain { get; } = new ConcurrentHashSet<SmartMerkleBlock>();
/// <summary>
///
/// </summary>
/// <param name="scriptPubKey"></param>
/// <param name="receivedTransactions">int: block height</param>
/// <param name="spentTransactions">int: block height</param>
/// <returns></returns>
public bool TryFindConfirmedTransactions(Script scriptPubKey, out ConcurrentHashSet<SmartTransaction> receivedTransactions, out ConcurrentHashSet<SmartTransaction> spentTransactions)
{
var found = false;
receivedTransactions = new ConcurrentHashSet<SmartTransaction>();
spentTransactions = new ConcurrentHashSet<SmartTransaction>();
foreach(var tx in TrackedTransactions.Where(x=>x.Confirmed))
{
// if already has that tx continue
if(receivedTransactions.Any(x => x.GetHash() == tx.GetHash()))
continue;
foreach(var output in tx.Transaction.Outputs)
{
if(output.ScriptPubKey.Equals(scriptPubKey))
{
receivedTransactions.Add(tx);
found = true;
}
}
}
if(found)
{
foreach(var tx in TrackedTransactions.Where(x => x.Confirmed))
{
// if already has that tx continue
if(spentTransactions.Any(x => x.GetHash() == tx.GetHash()))
continue;
foreach(var input in tx.Transaction.Inputs)
{
if(receivedTransactions.Select(x => x.GetHash()).Contains(input.PrevOut.Hash))
{
spentTransactions.Add(tx);
found = true;
}
}
}
}
return found;
}
/// <summary>
///
/// </summary>
/// <param name="scriptPubKey"></param>
/// <returns>if never had any money on it</returns>
public bool IsClean(Script scriptPubKey) => TrackedTransactions.All(tx => !tx.Transaction.Outputs.Any(output => output.ScriptPubKey.Equals(scriptPubKey)));
public ConcurrentObservableHashSet<SmartTransaction> TrackedTransactions { get; }
= new ConcurrentObservableHashSet<SmartTransaction>();
public ConcurrentHashSet<Script> TrackedScriptPubKeys { get; }
= new ConcurrentHashSet<Script>();
public readonly UnprocessedBlockBuffer UnprocessedBlockBuffer = new UnprocessedBlockBuffer();
private Height _bestHeight = Height.Unknown;
public Height BestHeight
{
get { return _bestHeight; }
private set
{
if (_bestHeight == value) return;
_bestHeight = value;
OnBestHeightChanged();
}
}
public event EventHandler BestHeightChanged;
private void OnBestHeightChanged() => BestHeightChanged?.Invoke(this, EventArgs.Empty);
public int BlockCount => MerkleChain.Count;
#endregion
#region Constructors
private Tracker()
{
}
public Tracker(Network network)
{
Network = network;
UnprocessedBlockBuffer.HaveBlocks += UnprocessedBlockBuffer_HaveBlocks;
}
#endregion
#region Tracking
private IEnumerable<SmartTransaction> GetNotYetFoundTrackedTransactions()
{
var notFound = new HashSet<SmartTransaction>();
foreach (var tx in TrackedTransactions.Where(x=> !x.Confirmed))
{
notFound.Add(tx);
}
return notFound;
}
#endregion
public void ReorgOne()
{
// remove the last block
if (MerkleChain.Count != 0)
{
if(MerkleChain.TryRemove(MerkleChain.Max()))
{
BestHeight = MerkleChain.Max().Height;
}
}
}
public void AddOrReplaceBlock(Height height, Block block)
{
UnprocessedBlockBuffer.TryAddOrReplace(height, block);
}
#region TransactionProcessing
/// <returns>if processed it transaction</returns>
public bool ProcessTransaction(SmartTransaction transaction)
{
// 1. If already tracking can we update it?
var found = TrackedTransactions.FirstOrDefault(x => x == transaction);
if (found != default(SmartTransaction))
{
// if in a lower level don't track
if(found.Height.Type <= transaction.Height.Type)
return false;
else
{
// else update
TrackedTransactions.TryRemove(transaction);
TrackedTransactions.TryAdd(transaction);
return true;
}
}
// 2. If this transaction arrived to any of our scriptpubkey track it!
if (transaction.Transaction.Outputs.Any(output => TrackedScriptPubKeys.Contains(output.ScriptPubKey)))
{
TrackedTransactions.TryAdd(transaction);
return true;
}
// 3. If this transaction spends any of our scriptpubkeys track it!
if(transaction.Transaction.Inputs.Any(input => TrackedTransactions
.Where(ttx => ttx.GetHash() == input.PrevOut.Hash)
.Any(ttx => TrackedScriptPubKeys
.Contains(ttx.Transaction.Outputs[input.PrevOut.N].ScriptPubKey))))
{
TrackedTransactions.TryAdd(transaction);
return true;
}
// if got so far we are not interested
return false;
}
/// <returns>transactions it processed, empty if not processed any</returns>
private HashSet<SmartTransaction> ProcessTransactions(IEnumerable<Transaction> transactions, Height height)
{
var processedTransactions = new HashSet<SmartTransaction>();
try
{
// Process all transactions
foreach(var tx in transactions)
{
var smartTx = new SmartTransaction(tx, height);
if(ProcessTransaction(smartTx))
{
processedTransactions.Add(smartTx);
}
}
// If processed any then do it again recursively until zero new is processed
if(processedTransactions.Count > 0)
{
var newlyProcessedTransactions = ProcessTransactions(transactions, height);
foreach(var ptx in newlyProcessedTransactions)
{
processedTransactions.Add(ptx);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Ignoring {nameof(ProcessBlock)} exception at height {height}:");
Debug.WriteLine(ex);
}
return processedTransactions;
}
/// <returns>transactions it processed, empty if not processed any</returns>
private HashSet<SmartTransaction> ProcessBlock(Height height, Block block)
{
var foundTransactions = ProcessTransactions(block.Transactions, height);
var smartMerkleBlock = new SmartMerkleBlock(height, block, foundTransactions.Select(x => x.GetHash()).ToArray());
var sameHeights = MerkleChain.Where(x => x.Height == height);
foreach (var elem in sameHeights)
{
MerkleChain.TryRemove(elem);
}
MerkleChain.Add(smartMerkleBlock);
BestHeight = height;
return foundTransactions;
}
#endregion
private void UnprocessedBlockBuffer_HaveBlocks(object sender, EventArgs e)
{
Height height;
Block block;
while (UnprocessedBlockBuffer.TryGetAndRemoveOldest(out height, out block))
{
ProcessBlock(height, block);
}
}
#region Saving
private readonly SemaphoreSlim Saving = new SemaphoreSlim(1, 1);
private const string TrackedScriptPubKeysFileName = "TrackedScriptPubKeys.dat";
private const string TrackedTransactionsFileName = "TrackedTransactions.dat";
private const string MerkleChainFileName = "MerkleChain.dat";
private static readonly byte[] blockSep = new byte[] { 0x10, 0x1A, 0x7B, 0x23, 0x5D, 0x12, 0x7D };
public async Task SaveAsync(string trackerFolderPath)
{
await Saving.WaitAsync().ConfigureAwait(false);
try
{
if (TrackedScriptPubKeys.Count > 0 || TrackedTransactions.Count > 0 || MerkleChain.Count > 0)
{
Directory.CreateDirectory(trackerFolderPath);
}
if (TrackedScriptPubKeys.Count > 0)
{
File.WriteAllLines(
Path.Combine(trackerFolderPath, TrackedScriptPubKeysFileName),
TrackedScriptPubKeys.Select(x => x.ToString()));
}
if (TrackedTransactions.Count > 0)
{
File.WriteAllLines(
Path.Combine(trackerFolderPath, TrackedTransactionsFileName),
TrackedTransactions
.Select(x => $"{x.Transaction.ToHex()}:{x.Height}"));
}
if (MerkleChain.Count > 0)
{
var path = Path.Combine(trackerFolderPath, MerkleChainFileName);
if(File.Exists(path))
{
const string backupName = MerkleChainFileName + "_backup";
var backupPath = Path.Combine(trackerFolderPath, backupName);
File.Copy(path, backupPath, overwrite: true);
File.Delete(path);
}
using(FileStream stream = File.OpenWrite(path))
{
var toFile = MerkleChain.First().ToBytes();
await stream.WriteAsync(toFile, 0, toFile.Length).ConfigureAwait(false);
foreach(var block in MerkleChain.Skip(1))
{
await stream.WriteAsync(blockSep, 0, blockSep.Length).ConfigureAwait(false);
var blockBytes = block.ToBytes();
await stream.WriteAsync(blockBytes, 0, blockBytes.Length).ConfigureAwait(false);
}
}
}
}
finally
{
Saving.Release();
}
}
public async Task LoadAsync(string trackerFolderPath)
{
await Saving.WaitAsync().ConfigureAwait(false);
try
{
if (!Directory.Exists(trackerFolderPath))
throw new DirectoryNotFoundException($"No Blockchain found at {trackerFolderPath}");
var tspb = Path.Combine(trackerFolderPath, TrackedScriptPubKeysFileName);
if (File.Exists(tspb) && new FileInfo(tspb).Length != 0)
{
foreach (var line in File.ReadAllLines(tspb))
{
TrackedScriptPubKeys.Add(new Script(line));
}
}
var tt = Path.Combine(trackerFolderPath, TrackedTransactionsFileName);
if (File.Exists(tt) && new FileInfo(tt).Length != 0)
{
foreach (var line in File.ReadAllLines(tt))
{
var pieces = line.Split(':');
ProcessTransaction(new SmartTransaction(new Transaction(pieces[0]), new Height(pieces[1])));
}
}
var pbc = Path.Combine(trackerFolderPath, MerkleChainFileName);
if (File.Exists(pbc) && new FileInfo(pbc).Length != 0)
{
foreach (var block in Util.Separate(File.ReadAllBytes(pbc), blockSep))
{
SmartMerkleBlock smartMerkleBlock = SmartMerkleBlock.FromBytes(block);
MerkleChain.Add(smartMerkleBlock);
}
BestHeight = MerkleChain.Max().Height;
}
}
finally
{
Saving.Release();
}
}
#endregion
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using HBitcoin.Models;
using NBitcoin;
namespace HBitcoin.FullBlockSpv
{
public class UnprocessedBlockBuffer
{
public const int Capacity = 50;
private readonly ConcurrentObservableDictionary<Height, Block> _blocks = new ConcurrentObservableDictionary<Height, Block>();
public event EventHandler HaveBlocks;
private void OnHaveBlocks() => HaveBlocks?.Invoke(this, EventArgs.Empty);
/// <summary>
///
/// </summary>
/// <param name="height"></param>
/// <param name="block"></param>
/// <returns>false if we have more than UnprocessedBlockBuffer.Capacity blocks in memory already</returns>
public bool TryAddOrReplace(Height height, Block block)
{
if (_blocks.Count > Capacity) return false;
_blocks.AddOrReplace(height, block);
if (_blocks.Count == 1) OnHaveBlocks();
return true;
}
public bool Full => _blocks.Count == Capacity;
public Height BestHeight => _blocks.Count == 0 ? Height.Unknown : _blocks.Keys.Max();
/// <summary>
///
/// </summary>
/// <returns>false if empty</returns>
public bool TryGetAndRemoveOldest(out Height height, out Block block)
{
height = Height.Unknown;
block = default(Block);
if(_blocks.Count == 0) return false;
height = _blocks.Keys.Min();
block = _blocks[height];
_blocks.Remove(height);
return true;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HBitcoin
{
internal static class Util
{
internal static byte[][] Separate(byte[] source, byte[] separator)
{
var Parts = new List<byte[]>();
var Index = 0;
byte[] Part;
for (var I = 0; I < source.Length; ++I)
{
if (Equals(source, separator, I))
{
Part = new byte[I - Index];
Array.Copy(source, Index, Part, 0, Part.Length);
Parts.Add(Part);
Index = I + separator.Length;
I += separator.Length - 1;
}
}
Part = new byte[source.Length - Index];
Array.Copy(source, Index, Part, 0, Part.Length);
Parts.Add(Part);
return Parts.ToArray();
}
private static bool Equals(byte[] source, byte[] separator, int index)
{
for (int i = 0; i < separator.Length; ++i)
if (index + i >= source.Length || source[index + i] != separator[i])
return false;
return true;
}
/// <summary>
/// Splits an array into several smaller arrays.
/// </summary>
/// <typeparam name="T">The type of the array.</typeparam>
/// <param name="array">The array to split.</param>
/// <param name="size">The size of the smaller arrays.</param>
/// <returns>An array containing smaller arrays.</returns>
public static IEnumerable<IEnumerable<T>> Split<T>(T[] array, int size)
{
for (var i = 0; i < (float)array.Length / size; i++)
{
yield return array.Skip(i * size).Take(size);
}
}
}
}
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