diff --git a/NBitcoin/Protocol/Payloads/CompactFilterCheckPointPayload.cs b/NBitcoin/Protocol/Payloads/CompactFilterCheckPointPayload.cs new file mode 100644 index 0000000000..c514ca780a --- /dev/null +++ b/NBitcoin/Protocol/Payloads/CompactFilterCheckPointPayload.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; + +namespace NBitcoin.Protocol +{ + + [Payload("cfcheckpt")] + public class CompactFilterCheckPointPayload : Payload, IBitcoinSerializable + { + private byte _FilterType = 0; + private uint256 _StopHash = new uint256(); + private uint256[] _FilterHeaders = { }; + private VarInt _FilterHeadersLength = new VarInt(0); + public CompactFilterCheckPointPayload() + { + + } + + public CompactFilterCheckPointPayload(FilterType filterType, uint256 stopHash, uint filtersHeaderLength, byte[] filterHeaders) + { + if (filterType != FilterType.Basic /*&& filterType != FilterType.Extended*/) //Extended filters removed + throw new ArgumentException(nameof(filterType)); + if (stopHash == null) + throw new ArgumentException(nameof(stopHash)); + if (filterHeaders == null) + throw new ArgumentException(nameof(filterHeaders)); + + FilterType = filterType; + _StopHash = stopHash; + _FilterHeaders = GetHashes(filterHeaders); + } + + private uint256[] GetHashes(byte[] filterHeaders) + { + int bytesToTake = 32; + int bytesTaken = 0; + List temp = new List(); + + List blockHeaders = new List(); + + for (; bytesTaken < filterHeaders.Length; bytesTaken += bytesToTake) + { + var subArray = filterHeaders.SafeSubarray(bytesTaken, bytesToTake); + + temp.Add(new uint256(subArray)); + } + + return temp.ToArray(); + } + + public FilterType FilterType + { + get => (FilterType)_FilterType; + internal set => _FilterType = (byte)value; + } + public uint256 StopHash { get => _StopHash; set => _StopHash = value; } + public uint256[] FilterHeaders { get => _FilterHeaders; set => _FilterHeaders = value; } + public VarInt FilterHeadersLength { get => _FilterHeadersLength; set => _FilterHeadersLength = value; } + + public new void ReadWrite(BitcoinStream stream) + { + var length = stream.Inner.Length; + + stream.ReadWrite(ref _FilterType); + stream.ReadWrite(ref _StopHash); + stream.ReadWrite(ref _FilterHeadersLength); + + //when serializing(Writing) we have to fill the tempfilterHeaders + if (stream.Serializing) + { + //turn the uint256[] into a byte array to write back into the bitcoin stream + List _tempfilterHeaders = new List(); + byte[] _tempFilterHeaderBytes = new byte[_FilterHeadersLength.ToLong() * 32]; //Init byte array to hold list after conversion + + foreach (var hash in _FilterHeaders) + { + foreach (var bytee in hash.ToBytes()) + { + _tempfilterHeaders.Add(bytee); + + } + } + //Write bytes + _tempFilterHeaderBytes = _tempfilterHeaders.ToArray(); + stream.ReadWrite(ref _tempFilterHeaderBytes); + } + + + if (!stream.Serializing) + { + //instantiate a byte[] to hold the incoming hashes + var _tempfilterHeaders = new byte[_FilterHeadersLength.ToLong() * 32]; + + //Write filters to temp variable + stream.ReadWrite(ref _tempfilterHeaders); + + //Convert the byte[] into "readable" uint256 hashes + _FilterHeaders = GetHashes(_tempfilterHeaders); + } + } + + + public override string ToString() + { + return $"cfcheckpt - filter type: {this._FilterType}| Stop Hash {this.StopHash}| # of checkpoints: {this._FilterHeadersLength.ToLong()}"; + } + } +} diff --git a/NBitcoin/Protocol/Payloads/CompactFilterHeadersPayload.cs b/NBitcoin/Protocol/Payloads/CompactFilterHeadersPayload.cs new file mode 100644 index 0000000000..3ded831719 --- /dev/null +++ b/NBitcoin/Protocol/Payloads/CompactFilterHeadersPayload.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; + +namespace NBitcoin.Protocol +{ + [Payload("cfheaders")] + public class CompactFilterHeadersPayload: Payload, IBitcoinSerializable + { + private byte _FilterType = 0; + private uint256 _StopHash = new uint256(); + private uint256 _PreviousFilterHeader = new uint256(); + private VarInt _FilterHashesLength = new VarInt(0); + private uint256[] _FilterHashes = { }; + + public CompactFilterHeadersPayload(FilterType filterType, uint256 stopHash, uint256 previousFilterHeader, byte[] filterHashes) + { + if (filterType != FilterType.Basic /*&& filterType != FilterType.Extended*/) //Extended filters removed + throw new ArgumentException($"'{filterType}' is not a valid value. Try with Basic.", nameof(filterType)); + if (stopHash == null) + throw new ArgumentException(nameof(stopHash)); + if (previousFilterHeader == null) + throw new ArgumentException(nameof(previousFilterHeader)); + if (filterHashes == null) + throw new ArgumentException(nameof(filterHashes)); + + + FilterType = filterType; + _StopHash = stopHash; + _PreviousFilterHeader = previousFilterHeader; + _FilterHashes = GetHashes(filterHashes); + } + + public CompactFilterHeadersPayload() { } + + public FilterType FilterType + { + get => (FilterType)_FilterType; + internal set => _FilterType = (byte)value; + } + + public new void ReadWrite(BitcoinStream stream) + { + stream.ReadWrite(ref _FilterType); + stream.ReadWrite(ref _StopHash); + stream.ReadWrite(ref _PreviousFilterHeader); + stream.ReadWrite(ref _FilterHashesLength); + //var _tempfilterHeaders = new byte[_filterHashesLength.ToLong() * 32]; + + if (stream.Serializing) + { + //turn the uint256[] into a byte array to write back into the bitcoin stream + List _tempfilterHeaders = new List(); + byte[] _tempFilterHeaderBytes = new byte[_FilterHashesLength.ToLong() * 32]; //Init byte array to hold list after conversion + + foreach (var hash in _FilterHashes) + { + foreach (var bytee in hash.ToBytes()) + { + _tempfilterHeaders.Add(bytee); + + } + } + //Write bytes + _tempFilterHeaderBytes = _tempfilterHeaders.ToArray(); + stream.ReadWrite(ref _tempFilterHeaderBytes); + } + + + if (!stream.Serializing) + { + //instantiate a byte[] to hold the incoming hashes + var _tempfilterHeaders = new byte[_FilterHashesLength.ToLong() * 32]; + + //Write filters to temp variable + stream.ReadWrite(ref _tempfilterHeaders); + + //Convert the byte[] into "readable" uint256 hashes + _FilterHashes = GetHashes(_tempfilterHeaders); + } + } + + private uint256[] GetHashes(byte[] filterHeaders) + { + int bytesToTake = 32; + int bytesTaken = 0; + List temp = new List(); + + List blockHeaders = new List(); + + for (; bytesTaken < filterHeaders.Length; bytesTaken += bytesToTake) + { + var subArray = filterHeaders.SafeSubarray(bytesTaken, bytesToTake); + + temp.Add(new uint256(subArray)); + } + + return temp.ToArray(); + } + + + public uint256 StopHash => _StopHash; + public uint256 PreviousFilterHeader => _PreviousFilterHeader; + public uint256[] FilterHashes => _FilterHashes; + + + + public override string ToString() + { + return $"Cheaders type: {this.FilterType}| # of headers: {this._FilterHashesLength.ToLong()}| Stop Hash: {this.StopHash}| Previous Filter Header Hash: {this.PreviousFilterHeader}"; + } + } +} diff --git a/NBitcoin/Protocol/Payloads/CompactFilterPayload.cs b/NBitcoin/Protocol/Payloads/CompactFilterPayload.cs new file mode 100644 index 0000000000..5698279a96 --- /dev/null +++ b/NBitcoin/Protocol/Payloads/CompactFilterPayload.cs @@ -0,0 +1,83 @@ +using System; + +namespace NBitcoin.Protocol +{ + + /// + /// Represents the p2p message payload used for sharing a block's compact filter. + /// + [Payload("cfilter")] + public class CompactFilterPayload : Payload + { + private byte _FilterType = 0; + private byte[] _FilterBytes; + private VarInt _NumFilterBytes = new VarInt(0); + private uint256 _BlockHash = new uint256(); + + /// + /// Gets the Filter type for which headers are requested + /// + public FilterType FilterType + { + get => (FilterType)_FilterType; + internal set => _FilterType = (byte)value; + } + /// + /// Gets the serialized compact filter for this block + /// + public byte[] FilterBytes => _FilterBytes; + + + /// + /// Gets block hash of the Bitcoin block for which the filter is being returned + /// + public uint256 BlockHash => _BlockHash; + public CompactFilterPayload(FilterType filterType, uint256 blockhash, byte[] filterBytes) + { + if (filterType != FilterType.Basic /*&& filterType != FilterType.Extended*/) //Extended filters removed + throw new ArgumentException($"'{filterType}' is not a valid value. Try with Basic.", nameof(filterType)); + if (blockhash == null) + throw new ArgumentNullException(nameof(blockhash)); + if (filterBytes == null) + throw new ArgumentNullException(nameof(filterBytes)); + + + FilterType = filterType; + _BlockHash = blockhash; + _FilterBytes = filterBytes; + } + + public CompactFilterPayload() + { + } + + #region IBitcoinSerializable Members + + public new void ReadWrite(BitcoinStream stream) + { + stream.ReadWrite(ref _FilterType); + stream.ReadWrite(ref _BlockHash); + stream.ReadWrite(ref _FilterBytes); + } + + #endregion + + public override void ReadWriteCore(BitcoinStream stream) + { + stream.ReadWrite(ref _FilterType); + + stream.ReadWrite(ref _BlockHash); + + stream.ReadWrite(ref _NumFilterBytes); + + _FilterBytes = new byte[_NumFilterBytes.ToLong()]; + + stream.ReadWrite(ref _FilterBytes); + } + + public override string ToString() + { + return $"Cfilter type: {this.FilterType}| Block hash: {this.BlockHash}| cfilter bytes omitted."; + } + } +} diff --git a/NBitcoin/Protocol/Payloads/GetCompactFiltersPayload.cs b/NBitcoin/Protocol/Payloads/GetCompactFiltersPayload.cs new file mode 100644 index 0000000000..3933b8c7e7 --- /dev/null +++ b/NBitcoin/Protocol/Payloads/GetCompactFiltersPayload.cs @@ -0,0 +1,133 @@ +using System; + +namespace NBitcoin.Protocol +{ + public enum FilterType : byte + { + Basic = (0x00), + //Extended = (0x01) Decison to remove extended filter due to increased filter size when doing recursive parsing. + //https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-May/016037.html + } + + /// + /// Represents the p2p message payload used for requesting a range of compact filter/headers. + /// + public abstract class CompactFiltersQueryPayload : Payload + { + private byte _FilterType = 0; + private uint _StartHeight = 0; + private uint256 _StopHash = new uint256(); + /// + /// Gets the Filter type for which headers are requested + /// + public FilterType FilterType + { + get => (FilterType)_FilterType; + internal set => _FilterType = (byte)value; + } + /// + /// Gets the height of the first block in the requested range + /// + public uint StartHeight => _StartHeight; + /// + /// Gets the hash of the last block in the requested range + /// + public uint256 StopHash => _StopHash; + protected CompactFiltersQueryPayload(FilterType filterType, uint startHeight, uint256 stopHash) + { + if (filterType != FilterType.Basic /*&& filterType != FilterType.Extended*/) //Extended Filter removed + throw new ArgumentException($"'{filterType}' is not a valid value. Try with Basic.", nameof(filterType)); + if (stopHash == null) + throw new ArgumentNullException(nameof(stopHash)); + + FilterType = filterType; + _StartHeight = startHeight; + _StopHash = stopHash; + } + + protected CompactFiltersQueryPayload() { } + + #region IBitcoinSerializable Members + public override void ReadWriteCore(BitcoinStream stream) + { + stream.ReadWrite(ref _FilterType); + stream.ReadWrite(ref _StartHeight); + stream.ReadWrite(ref _StopHash); + } + #endregion + } + /// + /// Represents the p2p message payload used for requesting a range of compact filter. + /// + [Payload("getcfilters")] + public class GetCompactFiltersPayload : CompactFiltersQueryPayload + { + + public GetCompactFiltersPayload(FilterType filterType, uint startHeight, uint256 stopHash) + : base(filterType, startHeight, stopHash) + { + } + + public GetCompactFiltersPayload() { } + + } + /// + /// Represents the p2p message payload used for requesting a range of compact filter headers. + /// + [Payload("getcfheaders")] + public class GetCompactFilterHeadersPayload : CompactFiltersQueryPayload + { + public GetCompactFilterHeadersPayload(FilterType filterType, uint startHeight, uint256 stopHash) + : base(filterType, startHeight, stopHash) + { + } + + public GetCompactFilterHeadersPayload() { } + } + + [Payload("getcfcheckpt")] + public class GetCompactFilterCheckPointPayload : Payload + { + private byte _FilterType; + private uint256 _StopHash; + + public GetCompactFilterCheckPointPayload(FilterType filterType, uint256 stopHash) + { + if (filterType != FilterType.Basic /*&& filterType != FilterType.Extended*/) //Extended Filter removed + throw new ArgumentException($"'{filterType}' is not a valid value. Try with Basic.", nameof(filterType)); + if (stopHash == null) + throw new ArgumentNullException(nameof(stopHash)); + + FilterType = filterType; + _StopHash = stopHash; + } + + public GetCompactFilterCheckPointPayload() { } + + public override void ReadWriteCore(BitcoinStream stream) + { + stream.ReadWrite(ref _FilterType); + stream.ReadWrite(ref _StopHash); + } + + + public FilterType FilterType + { + get => (FilterType)_FilterType; + internal set => _FilterType = (byte)value; + } + /// + /// Gets the height of the first block in the requested range + /// + + + + /// + /// Gets the hash of the last block in the requested range + /// + public uint256 StopHash => _StopHash; + } + + + +} diff --git a/NBitcoin/Protocol/Payloads/VersionPayload.cs b/NBitcoin/Protocol/Payloads/VersionPayload.cs index e17067f69f..4cbec0d7bb 100644 --- a/NBitcoin/Protocol/Payloads/VersionPayload.cs +++ b/NBitcoin/Protocol/Payloads/VersionPayload.cs @@ -43,6 +43,11 @@ public enum NodeServices : ulong /// NODE_WITNESS = (1 << 3), + /// Indicates that a node can be asked for compact filters. + /// If enabled, the node MUST respond to all BIP 157 messages for filter types 0x00 and 0x01 + /// + NODE_COMPACT_FILTERS = (1 << 6), + /// NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only /// serving the last 288 (2 day) blocks /// See BIP159 for details on how this is implemented.