From 3b7e3351066decc13257367d1824165ccc72b111 Mon Sep 17 00:00:00 2001 From: KhairallahA Date: Sat, 28 Sep 2024 23:39:23 +0300 Subject: [PATCH 1/5] feat: filterload --- src/network/protocol/messages/filterload.zig | 117 +++++++++++++++++++ src/network/protocol/messages/lib.zig | 7 ++ src/network/wire/lib.zig | 2 + 3 files changed, 126 insertions(+) create mode 100644 src/network/protocol/messages/filterload.zig diff --git a/src/network/protocol/messages/filterload.zig b/src/network/protocol/messages/filterload.zig new file mode 100644 index 0000000..6f602d7 --- /dev/null +++ b/src/network/protocol/messages/filterload.zig @@ -0,0 +1,117 @@ +const std = @import("std"); +const protocol = @import("../lib.zig"); + +const Sha256 = std.crypto.hash.sha2.Sha256; + +/// FilterLoadMessage represents the "filterload" message +/// +/// https://developer.bitcoin.org/reference/p2p_networking.html#filterload +pub const FilterLoadMessage = struct { + hash_func: u32, + tweak: u32, + flags: u8, + filter: []const u8, + + const Self = @This(); + + pub fn name() *const [12]u8 { + return protocol.CommandNames.FILTERLOAD ++ [_]u8{0} ** 2; + } + + /// Returns the message checksum + pub fn checksum(self: *const Self) [4]u8 { + var digest: [32]u8 = undefined; + var hasher = Sha256.init(.{}); + const writer = hasher.writer(); + self.serializeToWriter(writer) catch unreachable; // Sha256.write is infaible + hasher.final(&digest); + + Sha256.hash(&digest, &digest, .{}); + + return digest[0..4].*; + } + + /// Serialize the message as bytes and write them to the Writer. + pub fn serializeToWriter(self: *const Self, w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects w to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects w to have fn 'writeAll'."); + } + + try w.writeInt(u32, self.hash_func, .little); + try w.writeInt(u32, self.tweak, .little); + try w.writeInt(u8, self.flags, .little); + try w.writeAll(self.filter); + } + + /// Serialize a message as bytes and return them. + pub fn serialize(self: *const Self, allocator: std.mem.Allocator) ![]u8 { + const serialized_len = self.hintSerializedLen(); + + const ret = try allocator.alloc(u8, serialized_len); + errdefer allocator.free(ret); + + try self.serializeToSlice(ret); + + return ret; + } + + /// Serialize a message as bytes and write them to the buffer. + pub fn serializeToSlice(self: *const Self, buffer: []u8) !void { + var fbs = std.io.fixedBufferStream(buffer); + try self.serializeToWriter(fbs.writer()); + } + + pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !Self { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); + if (!std.meta.hasFn(@TypeOf(r), "readAllAlloc")) @compileError("Expects r to have fn 'readAllAlloc'."); + } + + var fl: Self = undefined; + + fl.hash_func = try r.readInt(u32, .little); + fl.tweak = try r.readInt(u32, .little); + fl.flags = try r.readInt(u8, .little); + fl.filter = try r.readAllAlloc(allocator, 36000); + + return fl; + } + + pub fn deserializeSlice(allocator: std.mem.Allocator, bytes: []const u8) !Self { + var fbs = std.io.fixedBufferStream(bytes); + return try Self.deserializeReader(allocator, fbs.reader()); + } + + pub fn hintSerializedLen(self: *const Self) usize { + const fixed_length = 4 + 4 + 1; // hash_func (4 bytes) + tweak (4 bytes) + flags (1 byte) + return self.filter.len + fixed_length; + } + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + allocator.free(self.filter); + } +}; + +test "ok_fullflow_filterload_message" { + const allocator = std.testing.allocator; + + const filter = "this is a test filter"; + var fl = FilterLoadMessage{ + .hash_func = 0xdeadbeef, + .tweak = 0xfeedface, + .flags = 0x02, + .filter = filter, + }; + + const payload = try fl.serialize(allocator); + defer allocator.free(payload); + + var deserialized_fl = try FilterLoadMessage.deserializeSlice(allocator, payload); + defer deserialized_fl.deinit(allocator); + + try std.testing.expect(fl.hash_func == deserialized_fl.hash_func); + try std.testing.expect(fl.tweak == deserialized_fl.tweak); + try std.testing.expect(fl.flags == deserialized_fl.flags); + try std.testing.expectEqualSlices(u8, filter, deserialized_fl.filter); +} diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index facc74a..794e06d 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -9,6 +9,7 @@ pub const PongMessage = @import("pong.zig").PongMessage; pub const FeeFilterMessage = @import("feefilter.zig").FeeFilterMessage; pub const SendCmpctMessage = @import("sendcmpct.zig").SendCmpctMessage; pub const FilterClearMessage = @import("filterclear.zig").FilterClearMessage; +pub const FilterLoadMessage = @import("filterload.zig").FilterLoadMessage; pub const MessageTypes = enum { version, @@ -21,6 +22,7 @@ pub const MessageTypes = enum { sendcmpct, feefilter, filterclear, + filterload, }; pub const Message = union(MessageTypes) { @@ -34,6 +36,7 @@ pub const Message = union(MessageTypes) { sendcmpct: SendCmpctMessage, feefilter: FeeFilterMessage, filterclear: FilterClearMessage, + filterload: FilterLoadMessage, pub fn name(self: Message) *const [12]u8 { return switch (self) { @@ -47,6 +50,7 @@ pub const Message = union(MessageTypes) { .sendcmpct => |m| @TypeOf(m).name(), .feefilter => |m| @TypeOf(m).name(), .filterclear => |m| @TypeOf(m).name(), + .filterload => |m| @TypeOf(m).name(), }; } @@ -62,6 +66,7 @@ pub const Message = union(MessageTypes) { .sendcmpct => {}, .feefilter => {}, .filterclear => {}, + .filterload => {}, } } @@ -77,6 +82,7 @@ pub const Message = union(MessageTypes) { .sendcmpct => |m| m.checksum(), .feefilter => |m| m.checksum(), .filterclear => |m| m.checksum(), + .filterload => |m| m.checksum(), }; } @@ -92,6 +98,7 @@ pub const Message = union(MessageTypes) { .sendcmpct => |m| m.hintSerializedLen(), .feefilter => |m| m.hintSerializedLen(), .filterclear => |m| m.hintSerializedLen(), + .filterload => |m| m.hintSerializedLen(), }; } }; diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 8640c97..21ec717 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -126,6 +126,8 @@ pub fn receiveMessage( protocol.messages.Message{ .sendcmpct = try protocol.messages.SendCmpctMessage.deserializeReader(allocator, r) } else if (std.mem.eql(u8, &command, protocol.messages.FilterClearMessage.name())) protocol.messages.Message{ .filterclear = try protocol.messages.FilterClearMessage.deserializeReader(allocator, r) } + else if (std.mem.eql(u8, &command, protocol.messages.FilterLoadMessage.name())) + protocol.messages.Message{ .filterload = try protocol.messages.FilterLoadMessage.deserializeReader(allocator, r) } else { try r.skipBytes(payload_len, .{}); // Purge the wire return error.UnknownMessage; From 2784daa2859b592a86ebf2b701e9fe169b10a7e9 Mon Sep 17 00:00:00 2001 From: KhairallahA Date: Mon, 30 Sep 2024 21:53:49 +0300 Subject: [PATCH 2/5] Update requests completed --- src/network/protocol/messages/filterload.zig | 24 ++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/network/protocol/messages/filterload.zig b/src/network/protocol/messages/filterload.zig index 6f602d7..db53360 100644 --- a/src/network/protocol/messages/filterload.zig +++ b/src/network/protocol/messages/filterload.zig @@ -2,15 +2,16 @@ const std = @import("std"); const protocol = @import("../lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; /// FilterLoadMessage represents the "filterload" message /// /// https://developer.bitcoin.org/reference/p2p_networking.html#filterload pub const FilterLoadMessage = struct { + filter: []const u8, hash_func: u32, tweak: u32, flags: u8, - filter: []const u8, const Self = @This(); @@ -38,10 +39,13 @@ pub const FilterLoadMessage = struct { if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects w to have fn 'writeAll'."); } + const compact_filter_len = CompactSizeUint.new(self.filter.len); + try compact_filter_len.encodeToWriter(w); + + try w.writeAll(self.filter); try w.writeInt(u32, self.hash_func, .little); try w.writeInt(u32, self.tweak, .little); try w.writeInt(u8, self.flags, .little); - try w.writeAll(self.filter); } /// Serialize a message as bytes and return them. @@ -65,15 +69,20 @@ pub const FilterLoadMessage = struct { pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !Self { comptime { if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); - if (!std.meta.hasFn(@TypeOf(r), "readAllAlloc")) @compileError("Expects r to have fn 'readAllAlloc'."); + if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects r to have fn 'readNoEof'."); } var fl: Self = undefined; + const filter_len = (try CompactSizeUint.decodeReader(r)).value(); + const filter = try allocator.alloc(u8, filter_len); + errdefer allocator.free(filter); + try r.readNoEof(filter); + + fl.filter = filter; fl.hash_func = try r.readInt(u32, .little); fl.tweak = try r.readInt(u32, .little); fl.flags = try r.readInt(u8, .little); - fl.filter = try r.readAllAlloc(allocator, 36000); return fl; } @@ -85,7 +94,8 @@ pub const FilterLoadMessage = struct { pub fn hintSerializedLen(self: *const Self) usize { const fixed_length = 4 + 4 + 1; // hash_func (4 bytes) + tweak (4 bytes) + flags (1 byte) - return self.filter.len + fixed_length; + const compact_filter_len = CompactSizeUint.new(self.filter.len).hint_encoded_len(); + return compact_filter_len + self.filter.len + fixed_length; } pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { @@ -98,10 +108,10 @@ test "ok_fullflow_filterload_message" { const filter = "this is a test filter"; var fl = FilterLoadMessage{ + .filter = filter, .hash_func = 0xdeadbeef, .tweak = 0xfeedface, .flags = 0x02, - .filter = filter, }; const payload = try fl.serialize(allocator); @@ -110,8 +120,8 @@ test "ok_fullflow_filterload_message" { var deserialized_fl = try FilterLoadMessage.deserializeSlice(allocator, payload); defer deserialized_fl.deinit(allocator); + try std.testing.expectEqualSlices(u8, filter, deserialized_fl.filter); try std.testing.expect(fl.hash_func == deserialized_fl.hash_func); try std.testing.expect(fl.tweak == deserialized_fl.tweak); try std.testing.expect(fl.flags == deserialized_fl.flags); - try std.testing.expectEqualSlices(u8, filter, deserialized_fl.filter); } From c3a3c2b4ce7e64d33fb0d5ea2b1298f18c4a4a57 Mon Sep 17 00:00:00 2001 From: KhairallahA Date: Tue, 1 Oct 2024 14:41:32 +0300 Subject: [PATCH 3/5] Update requests completed --- src/network/protocol/messages/filterload.zig | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/network/protocol/messages/filterload.zig b/src/network/protocol/messages/filterload.zig index db53360..153b802 100644 --- a/src/network/protocol/messages/filterload.zig +++ b/src/network/protocol/messages/filterload.zig @@ -72,19 +72,21 @@ pub const FilterLoadMessage = struct { if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects r to have fn 'readNoEof'."); } - var fl: Self = undefined; - const filter_len = (try CompactSizeUint.decodeReader(r)).value(); const filter = try allocator.alloc(u8, filter_len); errdefer allocator.free(filter); try r.readNoEof(filter); - fl.filter = filter; - fl.hash_func = try r.readInt(u32, .little); - fl.tweak = try r.readInt(u32, .little); - fl.flags = try r.readInt(u8, .little); - - return fl; + const hash_func = try r.readInt(u32, .little); + const tweak = try r.readInt(u32, .little); + const flags = try r.readInt(u8, .little); + + return Self{ + .filter = filter, + .hash_func = hash_func, + .tweak = tweak, + .flags = flags, + }; } pub fn deserializeSlice(allocator: std.mem.Allocator, bytes: []const u8) !Self { From f829468bfd1bcd49c884642a70d7626d768d374e Mon Sep 17 00:00:00 2001 From: KhairallahA Date: Tue, 1 Oct 2024 14:58:48 +0300 Subject: [PATCH 4/5] Update --- src/network/protocol/messages/filterload.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/protocol/messages/filterload.zig b/src/network/protocol/messages/filterload.zig index 7931042..afa51db 100644 --- a/src/network/protocol/messages/filterload.zig +++ b/src/network/protocol/messages/filterload.zig @@ -1,9 +1,9 @@ const std = @import("std"); const protocol = @import("../lib.zig"); +const genericChecksum = @import("lib.zig").genericChecksum; const Sha256 = std.crypto.hash.sha2.Sha256; const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; -const genericChecksum = @import("lib.zig").genericChecksum; /// FilterLoadMessage represents the "filterload" message /// From 56d6fa24cafd09ce142379970d634e9ffc4e20b3 Mon Sep 17 00:00:00 2001 From: KhairallahA Date: Tue, 1 Oct 2024 15:22:50 +0300 Subject: [PATCH 5/5] Update requests completed --- src/network/protocol/messages/lib.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 9af1226..5a27d62 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -165,7 +165,7 @@ pub const Message = union(MessageTypes) { .filteradd => |*m| m.hintSerializedLen(), .notfound => |m| m.hintSerializedLen(), .sendheaders => |m| m.hintSerializedLen(), - .filterload => |m| m.hintSerializedLen(), + .filterload => |*m| m.hintSerializedLen(), }; } };