From 452c1ffc1f05637d674f90e0e8743cdcd27dcfa2 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Mon, 23 Sep 2024 14:23:09 +0200 Subject: [PATCH 01/15] feat(protocol): data message --- src/network/protocol/messages/getdata.zig | 158 ++++++++++++++++++++++ src/network/protocol/messages/lib.zig | 32 +---- src/network/wire/lib.zig | 2 + 3 files changed, 162 insertions(+), 30 deletions(-) create mode 100644 src/network/protocol/messages/getdata.zig diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig new file mode 100644 index 0000000..4148fb5 --- /dev/null +++ b/src/network/protocol/messages/getdata.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; + +const Sha256 = std.crypto.hash.sha2.Sha256; + +const protocol = @import("../lib.zig"); + +pub const GetdataMessage = struct { + inventory: []const InventoryItem, + + pub const InventoryItem = struct { + type: u32, + hash: [32]u8, + }; + + pub inline fn name() *const [12]u8 { + return protocol.CommandNames.GETDATA ++ [_]u8{0} ** 5; + } + + /// Returns the message checksum + /// + /// Computed as `Sha256(Sha256(self.serialize()))[0..4]` + pub fn checksum(self: GetdataMessage) [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. + /// + /// `w` should be a valid `Writer`. + pub fn serializeToWriter(self: *const GetdataMessage, w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects writer to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects writer to have fn 'writeAll'."); + } + + const count = CompactSizeUint.new(self.inventory.len); + try count.encodeToWriter(w); + + for (self.inventory) |item| { + try w.writeInt(u32, item.type, .little); + + try w.writeAll(&item.hash); + } + } + + pub fn serialize(self: *const GetdataMessage, 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. + /// + /// buffer.len must be >= than self.hintSerializedLen() + pub fn serializeToSlice(self: *const GetdataMessage, buffer: []u8) !void { + var fbs = std.io.fixedBufferStream(buffer); + const writer = fbs.writer(); + try self.serializeToWriter(writer); + } + + pub fn hintSerializedLen(self: *const GetdataMessage) usize { + var length: usize = 0; + + // Adding the length of CompactSizeUint for the count + const count = CompactSizeUint.new(self.inventory.len); + length += count.hint_encoded_len(); + + // Adding the length of each inventory item + length += self.inventory.len * (4 + 32); // Type (4 bytes) + Hash (32 bytes) + + return length; + } + + pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !GetdataMessage { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have fn 'readInt'."); + if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects reader to have fn 'readNoEof'."); + } + + const compact_count = try CompactSizeUint.decodeReader(r); + const count = compact_count.value(); + + const inventory = try allocator.alloc(GetdataMessage.InventoryItem, count); + + for (inventory) |*item| { + item.type = try r.readInt(u32, .little); + try r.readNoEof(&item.hash); + } + + return GetdataMessage{ + .inventory = inventory, + }; + } + + pub fn deserializeSlice(allocator: std.mem.Allocator, bytes: []const u8) !GetdataMessage { + var fbs = std.io.fixedBufferStream(bytes); + const reader = fbs.reader(); + return try GetdataMessage.deserializeReader(allocator, reader); + } + + + pub fn eql(self: *const GetdataMessage, other: *const GetdataMessage) bool { + if (self.inventory.len != other.inventory.len) return false; + + var i: usize = 0; + for (self.inventory) |item| { + if (item.type != other.inventory[i].type) return false; + if (!std.mem.eql(u8, &item.hash, &other.inventory[i].hash)) return false; + i += 1; + } + return true; + } +}; + + +// TESTS + +test "ok_full_flow_GetdataMessage" { + const allocator = std.testing.allocator; + + // With some inventory items + { + const inventory_items = [_]GetdataMessage.InventoryItem{ + .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, + .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, + }; + + const gd = GetdataMessage{ + .inventory = inventory_items[0..], + }; + + // Serialize + const payload = try gd.serialize(allocator); + defer allocator.free(payload); + + // Deserialize + const deserialized_gd = try GetdataMessage.deserializeSlice(allocator, payload); + + // Test equality + try std.testing.expect(gd.eql(&deserialized_gd)); + + // Free allocated memory for deserialized inventory + defer allocator.free(deserialized_gd.inventory); + } +} \ No newline at end of file diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 5a27d62..84df64c 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -17,36 +17,7 @@ const Sha256 = std.crypto.hash.sha2.Sha256; pub const NotFoundMessage = @import("notfound.zig").NotFoundMessage; pub const SendHeadersMessage = @import("sendheaders.zig").SendHeadersMessage; pub const FilterLoadMessage = @import("filterload.zig").FilterLoadMessage; - -pub const InventoryVector = struct { - type: u32, - hash: [32]u8, - - pub fn serializeToWriter(self: InventoryVector, writer: anytype) !void { - comptime { - if (!std.meta.hasFn(@TypeOf(writer), "writeInt")) @compileError("Expects writer to have fn 'writeInt'."); - if (!std.meta.hasFn(@TypeOf(writer), "writeAll")) @compileError("Expects writer to have fn 'writeAll'."); - } - try writer.writeInt(u32, self.type, .little); - try writer.writeAll(&self.hash); - } - - pub fn deserializeReader(r: anytype) !InventoryVector { - comptime { - if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); - if (!std.meta.hasFn(@TypeOf(r), "readBytesNoEof")) @compileError("Expects r to have fn 'readBytesNoEof'."); - } - - const type_value = try r.readInt(u32, .little); - var hash: [32]u8 = undefined; - try r.readNoEof(&hash); - - return InventoryVector{ - .type = type_value, - .hash = hash, - }; - } -}; +pub const GetdataMessage = @import("getdata.zig").GetdataMessage; pub const MessageTypes = enum { version, @@ -166,6 +137,7 @@ pub const Message = union(MessageTypes) { .notfound => |m| m.hintSerializedLen(), .sendheaders => |m| m.hintSerializedLen(), .filterload => |*m| m.hintSerializedLen(), + .getdata => |m| m.hintSerializedLen(), }; } }; diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index bf0d022..d7be588 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -141,6 +141,8 @@ pub fn receiveMessage( protocol.messages.Message{ .sendheaders = try protocol.messages.SendHeadersMessage.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 if (std.mem.eql(u8, &command, protocol.messages.GetdataMessage.name())) + protocol.messages.Message{ .Getdata = try protocol.messages.GetdataMessage.deserializeReader(allocator, r) } else { try r.skipBytes(payload_len, .{}); // Purge the wire return error.UnknownMessage; From 4a77e0fa437a373f49e98129b1599496d1dfbe56 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Mon, 23 Sep 2024 20:02:46 +0200 Subject: [PATCH 02/15] feat(protocol): complete method & test --- src/network/protocol/messages/getdata.zig | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index 4148fb5..119a7b3 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -20,7 +20,7 @@ pub const GetdataMessage = struct { /// Returns the message checksum /// /// Computed as `Sha256(Sha256(self.serialize()))[0..4]` - pub fn checksum(self: GetdataMessage) [4]u8 { + pub fn checksum(self: *const GetdataMessage) [4]u8 { var digest: [32]u8 = undefined; var hasher = Sha256.init(.{}); const writer = hasher.writer(); @@ -32,6 +32,11 @@ pub const GetdataMessage = struct { return digest[0..4].*; } + /// Free the `inventory` + pub fn deinit(self: GetdataMessage, allocator: std.mem.Allocator) void { + allocator.free(self.inventory); + } + /// Serialize the message as bytes and write them to the Writer. /// /// `w` should be a valid `Writer`. @@ -105,6 +110,7 @@ pub const GetdataMessage = struct { }; } + /// Deserialize bytes into a `GetdataMessage` pub fn deserializeSlice(allocator: std.mem.Allocator, bytes: []const u8) !GetdataMessage { var fbs = std.io.fixedBufferStream(bytes); const reader = fbs.reader(); @@ -115,12 +121,15 @@ pub const GetdataMessage = struct { pub fn eql(self: *const GetdataMessage, other: *const GetdataMessage) bool { if (self.inventory.len != other.inventory.len) return false; - var i: usize = 0; - for (self.inventory) |item| { - if (item.type != other.inventory[i].type) return false; - if (!std.mem.eql(u8, &item.hash, &other.inventory[i].hash)) return false; - i += 1; + for (0..self.inventory.len) |i| { + if (self.inventory[i].type != other.inventory[i].type) { + return false; + } + if (!std.mem.eql(u8, &self.inventory[i].hash, &other.inventory[i].hash)) { + return false; + } } + return true; } }; From d4e5a320566bf201e790c3d6fdfa989e680cb24b Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Wed, 25 Sep 2024 19:28:20 +0200 Subject: [PATCH 03/15] rebase & update test --- src/network/protocol/messages/getdata.zig | 4 +-- src/network/wire/lib.zig | 40 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index 119a7b3..1e82bbe 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -145,20 +145,18 @@ test "ok_full_flow_GetdataMessage" { const inventory_items = [_]GetdataMessage.InventoryItem{ .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, + .{ .type = 2, .hash = [_]u8{0xef} ** 32 }, }; const gd = GetdataMessage{ .inventory = inventory_items[0..], }; - // Serialize const payload = try gd.serialize(allocator); defer allocator.free(payload); - // Deserialize const deserialized_gd = try GetdataMessage.deserializeSlice(allocator, payload); - // Test equality try std.testing.expect(gd.eql(&deserialized_gd)); // Free allocated memory for deserialized inventory diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index d7be588..319a20e 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -263,6 +263,46 @@ test "ok_send_mempool_message" { } } +test "ok_send_getdata_message" { + const Config = @import("../../config/config.zig").Config; + const ArrayList = std.ArrayList; + const test_allocator = std.testing.allocator; + const GetdataMessage = protocol.messages.GetdataMessage; + + var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); + defer list.deinit(); + + const inventory = try test_allocator.alloc(GetdataMessage.InventoryItem, 5); + defer test_allocator.free(inventory); + + for (inventory) |*item| { + item.type = 1; + for (&item.hash) |*byte| { + byte.* = 0xab; + } + } + + const message = GetdataMessage{ + .inventory = inventory, + }; + + const writer = list.writer(); + try sendMessage(test_allocator, writer, Config.PROTOCOL_VERSION, Config.BitcoinNetworkId.MAINNET, message); + + var fbs: std.io.FixedBufferStream([]u8) = std.io.fixedBufferStream(list.items); + const reader = fbs.reader(); + + const received_message = try receiveMessage(test_allocator, reader); + + switch (received_message) { + .Getdata => |rm| { + try std.testing.expect(message.eql(&rm)); + defer rm.deinit(test_allocator); + }, + else => unreachable, + } +} + test "ok_send_getblocks_message" { const Config = @import("../../config/config.zig").Config; From 819a419b1de99ccd6e8f8dad09e2534874cfa3d3 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Fri, 27 Sep 2024 19:13:18 +0200 Subject: [PATCH 04/15] move InvetoryItem to messages/lib --- src/network/protocol/messages/getdata.zig | 13 ++----------- src/network/protocol/messages/lib.zig | 6 ++++++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index 1e82bbe..2802179 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -8,10 +8,6 @@ const protocol = @import("../lib.zig"); pub const GetdataMessage = struct { inventory: []const InventoryItem, - pub const InventoryItem = struct { - type: u32, - hash: [32]u8, - }; pub inline fn name() *const [12]u8 { return protocol.CommandNames.GETDATA ++ [_]u8{0} ** 5; @@ -121,13 +117,8 @@ pub const GetdataMessage = struct { pub fn eql(self: *const GetdataMessage, other: *const GetdataMessage) bool { if (self.inventory.len != other.inventory.len) return false; - for (0..self.inventory.len) |i| { - if (self.inventory[i].type != other.inventory[i].type) { - return false; - } - if (!std.mem.eql(u8, &self.inventory[i].hash, &other.inventory[i].hash)) { - return false; - } + if(!std.mem.eql(Inventory, self.inventory, other.inventory)) { + return false; } return true; diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 84df64c..71696cc 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -38,6 +38,12 @@ pub const MessageTypes = enum { filterload, }; +pub const InventoryItem = struct { + type: u32, + hash: [32]u8, +}; + + pub const Message = union(MessageTypes) { version: VersionMessage, verack: VerackMessage, From cc6e9705d0e91d31ffe1038ab234feb3351d5926 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Fri, 27 Sep 2024 19:28:05 +0200 Subject: [PATCH 05/15] update test --- src/network/protocol/messages/getdata.zig | 16 +++++++++++----- src/network/wire/lib.zig | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index 2802179..5c9d314 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -1,12 +1,13 @@ const std = @import("std"); const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; +const message = @import("./lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; const protocol = @import("../lib.zig"); pub const GetdataMessage = struct { - inventory: []const InventoryItem, + inventory: []const message.InventoryItem, pub inline fn name() *const [12]u8 { @@ -94,7 +95,7 @@ pub const GetdataMessage = struct { const compact_count = try CompactSizeUint.decodeReader(r); const count = compact_count.value(); - const inventory = try allocator.alloc(GetdataMessage.InventoryItem, count); + const inventory = try allocator.alloc(message.InventoryItem, count); for (inventory) |*item| { item.type = try r.readInt(u32, .little); @@ -117,8 +118,13 @@ pub const GetdataMessage = struct { pub fn eql(self: *const GetdataMessage, other: *const GetdataMessage) bool { if (self.inventory.len != other.inventory.len) return false; - if(!std.mem.eql(Inventory, self.inventory, other.inventory)) { - return false; + for (0..self.inventory.len) |i| { + if (self.inventory[i].type != other.inventory[i].type) { + return false; + } + if (!std.mem.eql(u8, &self.inventory[i].hash, &other.inventory[i].hash)) { + return false; + } } return true; @@ -133,7 +139,7 @@ test "ok_full_flow_GetdataMessage" { // With some inventory items { - const inventory_items = [_]GetdataMessage.InventoryItem{ + const inventory_items = [_]message.InventoryItem{ .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, .{ .type = 2, .hash = [_]u8{0xef} ** 32 }, diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 319a20e..e487b45 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -11,6 +11,7 @@ const std = @import("std"); const protocol = @import("../protocol/lib.zig"); +const messagesProtocol = @import("../protocol/messages/lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; @@ -272,7 +273,7 @@ test "ok_send_getdata_message" { var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); defer list.deinit(); - const inventory = try test_allocator.alloc(GetdataMessage.InventoryItem, 5); + const inventory = try test_allocator.alloc(messagesProtocol.InventoryItem, 5); defer test_allocator.free(inventory); for (inventory) |*item| { From 0e312efd094d1942146c9c856c73d3ec28e30c0c Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Fri, 27 Sep 2024 19:57:03 +0200 Subject: [PATCH 06/15] some changes --- src/network/protocol/messages/lib.zig | 4 ++++ src/network/wire/lib.zig | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 71696cc..cc466c3 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -36,6 +36,7 @@ pub const MessageTypes = enum { notfound, sendheaders, filterload, + getdata, }; pub const InventoryItem = struct { @@ -61,6 +62,7 @@ pub const Message = union(MessageTypes) { notfound: NotFoundMessage, sendheaders: SendHeadersMessage, filterload: FilterLoadMessage, + getdata: GetdataMessage, pub fn name(self: Message) *const [12]u8 { return switch (self) { @@ -80,6 +82,7 @@ pub const Message = union(MessageTypes) { .notfound => |m| @TypeOf(m).name(), .sendheaders => |m| @TypeOf(m).name(), .filterload => |m| @TypeOf(m).name(), + .getdata => |m| @TypeOf(m).name(), }; } @@ -122,6 +125,7 @@ pub const Message = union(MessageTypes) { .notfound => |*m| m.checksum(), .sendheaders => |*m| m.checksum(), .filterload => |*m| m.checksum(), + .getdata => |m| m.deinit(allocator), }; } diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index e487b45..afdd172 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -143,7 +143,7 @@ pub fn receiveMessage( else if (std.mem.eql(u8, &command, protocol.messages.FilterLoadMessage.name())) protocol.messages.Message{ .filterload = try protocol.messages.FilterLoadMessage.deserializeReader(allocator, r) } else if (std.mem.eql(u8, &command, protocol.messages.GetdataMessage.name())) - protocol.messages.Message{ .Getdata = try protocol.messages.GetdataMessage.deserializeReader(allocator, r) } + protocol.messages.Message{ .getdata = try protocol.messages.GetdataMessage.deserializeReader(allocator, r) } else { try r.skipBytes(payload_len, .{}); // Purge the wire return error.UnknownMessage; @@ -287,19 +287,17 @@ test "ok_send_getdata_message" { .inventory = inventory, }; - const writer = list.writer(); - try sendMessage(test_allocator, writer, Config.PROTOCOL_VERSION, Config.BitcoinNetworkId.MAINNET, message); - - var fbs: std.io.FixedBufferStream([]u8) = std.io.fixedBufferStream(list.items); - const reader = fbs.reader(); - - const received_message = try receiveMessage(test_allocator, reader); + const received_message = try write_and_read_message( + test_allocator, + &list, + Config.BitcoinNetworkId.MAINNET, + Config.PROTOCOL_VERSION, + message, + ) orelse unreachable; + defer received_message.deinit(test_allocator); switch (received_message) { - .Getdata => |rm| { - try std.testing.expect(message.eql(&rm)); - defer rm.deinit(test_allocator); - }, + .getdata => |rm| try std.testing.expect(message.eql(&rm)), else => unreachable, } } From 05e394f97c50c85b73f8950469ad015b4bbbace4 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Mon, 30 Sep 2024 20:41:27 +0200 Subject: [PATCH 07/15] rework InventoryItem struct --- src/network/protocol/messages/getdata.zig | 15 +++++--------- src/network/protocol/messages/lib.zig | 25 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index 5c9d314..dd574c4 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -9,7 +9,6 @@ const protocol = @import("../lib.zig"); pub const GetdataMessage = struct { inventory: []const message.InventoryItem, - pub inline fn name() *const [12]u8 { return protocol.CommandNames.GETDATA ++ [_]u8{0} ** 5; } @@ -47,9 +46,7 @@ pub const GetdataMessage = struct { try count.encodeToWriter(w); for (self.inventory) |item| { - try w.writeInt(u32, item.type, .little); - - try w.writeAll(&item.hash); + try item.serialize(w); } } @@ -98,8 +95,7 @@ pub const GetdataMessage = struct { const inventory = try allocator.alloc(message.InventoryItem, count); for (inventory) |*item| { - item.type = try r.readInt(u32, .little); - try r.readNoEof(&item.hash); + item.* = try message.InventoryItem.deserialize(r); } return GetdataMessage{ @@ -119,10 +115,9 @@ pub const GetdataMessage = struct { if (self.inventory.len != other.inventory.len) return false; for (0..self.inventory.len) |i| { - if (self.inventory[i].type != other.inventory[i].type) { - return false; - } - if (!std.mem.eql(u8, &self.inventory[i].hash, &other.inventory[i].hash)) { + const item_self = self.inventory[i]; + const item_other = other.inventory[i]; + if (!item_self.eql(&item_other)) { return false; } } diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index cc466c3..7a2c8bd 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -42,6 +42,31 @@ pub const MessageTypes = enum { pub const InventoryItem = struct { type: u32, hash: [32]u8, + + pub fn serialize(self: *const InventoryItem, w: anytype) !void { + try w.writeInt(u32, self.type, .little); + try w.writeAll(&self.hash); + } + + pub fn deserialize(r: anytype) !InventoryItem { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have fn 'readInt'."); + if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects reader to have fn 'readNoEof'."); + } + + const item_type = try r.readInt(u32, .little); + var hash: [32]u8 = undefined; + try r.readNoEof(&hash); + + return InventoryItem{ + .type = item_type, + .hash = hash, + }; + } + + pub fn eql(self: *const InventoryItem, other: *const InventoryItem) bool { + return self.type == other.type and std.mem.eql(u8, &self.hash, &other.hash); + } }; From bb46eeed6b4da46807b17f7f9ec622518ede55a9 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Wed, 2 Oct 2024 13:24:28 +0200 Subject: [PATCH 08/15] messages/lib: add comptime --- src/network/protocol/messages/lib.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 7a2c8bd..a910a19 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -44,6 +44,10 @@ pub const InventoryItem = struct { hash: [32]u8, pub fn serialize(self: *const InventoryItem, w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects r to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'."); + } try w.writeInt(u32, self.type, .little); try w.writeAll(&self.hash); } From c1e31de131cc2e32bfe4cbfcdb5082cf5e239228 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Wed, 2 Oct 2024 13:36:39 +0200 Subject: [PATCH 09/15] messages/lib: merge fix --- src/network/protocol/messages/lib.zig | 2 ++ src/network/wire/lib.zig | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index a910a19..3571273 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -11,6 +11,7 @@ pub const MerkleBlockMessage = @import("merkleblock.zig").MerkleBlockMessage; pub const FeeFilterMessage = @import("feefilter.zig").FeeFilterMessage; pub const SendCmpctMessage = @import("sendcmpct.zig").SendCmpctMessage; pub const FilterClearMessage = @import("filterclear.zig").FilterClearMessage; +pub const GetdataMessage = @import("getdata.zig").GetdataMessage; pub const Block = @import("block.zig").BlockMessage; pub const FilterAddMessage = @import("filteradd.zig").FilterAddMessage; const Sha256 = std.crypto.hash.sha2.Sha256; @@ -133,6 +134,7 @@ pub const Message = union(MessageTypes) { .notfound => {}, .sendheaders => {}, .filterload => {}, + .getdata => |*m| m.deinit(allocator), } } diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index afdd172..8203a4a 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -287,7 +287,7 @@ test "ok_send_getdata_message" { .inventory = inventory, }; - const received_message = try write_and_read_message( + var received_message = try write_and_read_message( test_allocator, &list, Config.BitcoinNetworkId.MAINNET, From ae970956392f5a1b7d454bcfaac19232e70592c6 Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Wed, 2 Oct 2024 20:23:35 +0200 Subject: [PATCH 10/15] rework some messages --- src/network/protocol/lib.zig | 5 +- src/network/protocol/messages/getdata.zig | 36 ++++++--------- src/network/protocol/messages/lib.zig | 46 +------------------ src/network/protocol/messages/notfound.zig | 13 +++--- src/network/protocol/types/InventoryItem.zig | 33 +++++++++++++ .../protocol/types/InventoryVector.zig | 29 ++++++++++++ .../protocol/{ => types}/NetworkAddress.zig | 0 src/network/wire/lib.zig | 3 +- 8 files changed, 87 insertions(+), 78 deletions(-) create mode 100644 src/network/protocol/types/InventoryItem.zig create mode 100644 src/network/protocol/types/InventoryVector.zig rename src/network/protocol/{ => types}/NetworkAddress.zig (100%) diff --git a/src/network/protocol/lib.zig b/src/network/protocol/lib.zig index 3c7549e..2e78230 100644 --- a/src/network/protocol/lib.zig +++ b/src/network/protocol/lib.zig @@ -1,5 +1,8 @@ pub const messages = @import("./messages/lib.zig"); -pub const NetworkAddress = @import("NetworkAddress.zig"); +pub const NetworkAddress = @import("types/NetworkAddress.zig"); +pub const InventoryItem = @import("types/InventoryItem.zig"); +pub const InventoryVector = @import("types/InventoryVector.zig"); + /// Network services pub const ServiceFlags = struct { pub const NODE_NETWORK: u64 = 0x1; diff --git a/src/network/protocol/messages/getdata.zig b/src/network/protocol/messages/getdata.zig index dd574c4..3ef18d6 100644 --- a/src/network/protocol/messages/getdata.zig +++ b/src/network/protocol/messages/getdata.zig @@ -1,13 +1,14 @@ const std = @import("std"); const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; const message = @import("./lib.zig"); +const genericChecksum = @import("lib.zig").genericChecksum; const Sha256 = std.crypto.hash.sha2.Sha256; const protocol = @import("../lib.zig"); pub const GetdataMessage = struct { - inventory: []const message.InventoryItem, + inventory: []const protocol.InventoryItem, pub inline fn name() *const [12]u8 { return protocol.CommandNames.GETDATA ++ [_]u8{0} ** 5; @@ -17,15 +18,7 @@ pub const GetdataMessage = struct { /// /// Computed as `Sha256(Sha256(self.serialize()))[0..4]` pub fn checksum(self: *const GetdataMessage) [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].*; + return genericChecksum(self); } /// Free the `inventory` @@ -37,16 +30,11 @@ pub const GetdataMessage = struct { /// /// `w` should be a valid `Writer`. pub fn serializeToWriter(self: *const GetdataMessage, w: anytype) !void { - comptime { - if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects writer to have fn 'writeInt'."); - if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects writer to have fn 'writeAll'."); - } - const count = CompactSizeUint.new(self.inventory.len); try count.encodeToWriter(w); for (self.inventory) |item| { - try item.serialize(w); + try item.encodeToWriter(w); } } @@ -84,18 +72,20 @@ pub const GetdataMessage = struct { } pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !GetdataMessage { - comptime { - if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have fn 'readInt'."); - if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects reader to have fn 'readNoEof'."); - } const compact_count = try CompactSizeUint.decodeReader(r); const count = compact_count.value(); + if (count == 0) { + return GetdataMessage{ + .inventory = &[_]protocol.InventoryItem{}, + }; + } - const inventory = try allocator.alloc(message.InventoryItem, count); + const inventory = try allocator.alloc(protocol.InventoryItem, count); + errdefer allocator.free(inventory); for (inventory) |*item| { - item.* = try message.InventoryItem.deserialize(r); + item.* = try protocol.InventoryItem.decodeReader(r); } return GetdataMessage{ @@ -134,7 +124,7 @@ test "ok_full_flow_GetdataMessage" { // With some inventory items { - const inventory_items = [_]message.InventoryItem{ + const inventory_items = [_]protocol.InventoryItem{ .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, .{ .type = 2, .hash = [_]u8{0xef} ** 32 }, diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 3571273..c76320a 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -40,40 +40,6 @@ pub const MessageTypes = enum { getdata, }; -pub const InventoryItem = struct { - type: u32, - hash: [32]u8, - - pub fn serialize(self: *const InventoryItem, w: anytype) !void { - comptime { - if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects r to have fn 'writeInt'."); - if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'."); - } - try w.writeInt(u32, self.type, .little); - try w.writeAll(&self.hash); - } - - pub fn deserialize(r: anytype) !InventoryItem { - comptime { - if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have fn 'readInt'."); - if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects reader to have fn 'readNoEof'."); - } - - const item_type = try r.readInt(u32, .little); - var hash: [32]u8 = undefined; - try r.readNoEof(&hash); - - return InventoryItem{ - .type = item_type, - .hash = hash, - }; - } - - pub fn eql(self: *const InventoryItem, other: *const InventoryItem) bool { - return self.type == other.type and std.mem.eql(u8, &self.hash, &other.hash); - } -}; - pub const Message = union(MessageTypes) { version: VersionMessage, @@ -119,22 +85,12 @@ pub const Message = union(MessageTypes) { pub fn deinit(self: *Message, allocator: std.mem.Allocator) void { switch (self.*) { .version => |*m| m.deinit(allocator), - .verack => {}, - .mempool => {}, - .getaddr => {}, .getblocks => |*m| m.deinit(allocator), - .ping => {}, - .pong => {}, .merkleblock => |*m| m.deinit(allocator), - .sendcmpct => {}, - .feefilter => {}, - .filterclear => {}, .block => |*m| m.deinit(allocator), .filteradd => |*m| m.deinit(allocator), - .notfound => {}, - .sendheaders => {}, - .filterload => {}, .getdata => |*m| m.deinit(allocator), + else => {} } } diff --git a/src/network/protocol/messages/notfound.zig b/src/network/protocol/messages/notfound.zig index af814a1..b11ebbb 100644 --- a/src/network/protocol/messages/notfound.zig +++ b/src/network/protocol/messages/notfound.zig @@ -1,13 +1,12 @@ const std = @import("std"); const protocol = @import("../lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; -const InventoryVector = @import("lib.zig").InventoryVector; /// NotFoundMessage represents the "notfound" message /// /// https://developer.bitcoin.org/reference/p2p_networking.html#notfound pub const NotFoundMessage = struct { - inventory: []const InventoryVector, + inventory: []const protocol.InventoryVector, const Self = @This(); @@ -42,7 +41,7 @@ pub const NotFoundMessage = struct { pub fn serializeToWriter(self: *const Self, writer: anytype) !void { try writer.writeInt(u32, @intCast(self.inventory.len), .little); for (self.inventory) |inv| { - try InventoryVector.serializeToWriter(inv, writer); + try protocol.InventoryVector.serializeToWriter(inv, writer); } } @@ -65,11 +64,11 @@ pub const NotFoundMessage = struct { } const count = try r.readInt(u32, .little); - const inventory = try allocator.alloc(InventoryVector, count); + const inventory = try allocator.alloc(protocol.InventoryVector, count); errdefer allocator.free(inventory); for (inventory) |*inv| { - inv.* = try InventoryVector.deserializeReader(r); + inv.* = try protocol.InventoryVector.deserializeReader(r); } return Self{ @@ -93,7 +92,7 @@ pub const NotFoundMessage = struct { allocator.free(self.inventory); } - pub fn new(inventory: []const InventoryVector) Self { + pub fn new(inventory: []const protocol.InventoryVector) Self { return .{ .inventory = inventory, }; @@ -106,7 +105,7 @@ test "ok_fullflow_notfound_message" { const allocator = std.testing.allocator; { - const inventory = [_]InventoryVector{ + const inventory = [_]protocol.InventoryVector{ .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, }; diff --git a/src/network/protocol/types/InventoryItem.zig b/src/network/protocol/types/InventoryItem.zig new file mode 100644 index 0000000..d3c2045 --- /dev/null +++ b/src/network/protocol/types/InventoryItem.zig @@ -0,0 +1,33 @@ +const std = @import("std"); + +type: u32, +hash: [32]u8, + +pub fn encodeToWriter(self: *const @This(), w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects r to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'."); + } + try w.writeInt(u32, self.type, .little); + try w.writeAll(&self.hash); +} + +pub fn decodeReader(r: anytype) !@This() { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have fn 'readInt'."); + if (!std.meta.hasFn(@TypeOf(r), "readNoEof")) @compileError("Expects reader to have fn 'readNoEof'."); + } + + const item_type = try r.readInt(u32, .little); + var hash: [32]u8 = undefined; + try r.readNoEof(&hash); + + return @This(){ + .type = item_type, + .hash = hash, + }; +} + +pub fn eql(self: *const @This(), other: *const @This()) bool { + return self.type == other.type and std.mem.eql(u8, &self.hash, &other.hash); +} \ No newline at end of file diff --git a/src/network/protocol/types/InventoryVector.zig b/src/network/protocol/types/InventoryVector.zig new file mode 100644 index 0000000..0ad5232 --- /dev/null +++ b/src/network/protocol/types/InventoryVector.zig @@ -0,0 +1,29 @@ +const std = @import("std"); + +type: u32, +hash: [32]u8, + +pub fn serializeToWriter(self: @This(), writer: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(writer), "writeInt")) @compileError("Expects writer to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(writer), "writeAll")) @compileError("Expects writer to have fn 'writeAll'."); + } + try writer.writeInt(u32, self.type, .little); + try writer.writeAll(&self.hash); +} + +pub fn deserializeReader(r: anytype) !@This() { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); + if (!std.meta.hasFn(@TypeOf(r), "readBytesNoEof")) @compileError("Expects r to have fn 'readBytesNoEof'."); + } + + const type_value = try r.readInt(u32, .little); + var hash: [32]u8 = undefined; + try r.readNoEof(&hash); + + return @This(){ + .type = type_value, + .hash = hash, + }; +} \ No newline at end of file diff --git a/src/network/protocol/NetworkAddress.zig b/src/network/protocol/types/NetworkAddress.zig similarity index 100% rename from src/network/protocol/NetworkAddress.zig rename to src/network/protocol/types/NetworkAddress.zig diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 8203a4a..ad13069 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -11,7 +11,6 @@ const std = @import("std"); const protocol = @import("../protocol/lib.zig"); -const messagesProtocol = @import("../protocol/messages/lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; @@ -273,7 +272,7 @@ test "ok_send_getdata_message" { var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); defer list.deinit(); - const inventory = try test_allocator.alloc(messagesProtocol.InventoryItem, 5); + const inventory = try test_allocator.alloc(protocol.InventoryItem, 5); defer test_allocator.free(inventory); for (inventory) |*item| { From 4a110b6decf5065a37b675246eb8ee190d62b06a Mon Sep 17 00:00:00 2001 From: Yannick Bensacq Date: Thu, 3 Oct 2024 15:19:57 +0200 Subject: [PATCH 11/15] Network/protocol: Remove InventoryItem --- src/network/protocol/lib.zig | 1 - src/network/protocol/messages/notfound.zig | 12 ++++---- .../protocol/types/InventoryVector.zig | 29 ------------------- 3 files changed, 6 insertions(+), 36 deletions(-) delete mode 100644 src/network/protocol/types/InventoryVector.zig diff --git a/src/network/protocol/lib.zig b/src/network/protocol/lib.zig index 2e78230..e5acf0a 100644 --- a/src/network/protocol/lib.zig +++ b/src/network/protocol/lib.zig @@ -1,7 +1,6 @@ pub const messages = @import("./messages/lib.zig"); pub const NetworkAddress = @import("types/NetworkAddress.zig"); pub const InventoryItem = @import("types/InventoryItem.zig"); -pub const InventoryVector = @import("types/InventoryVector.zig"); /// Network services pub const ServiceFlags = struct { diff --git a/src/network/protocol/messages/notfound.zig b/src/network/protocol/messages/notfound.zig index b11ebbb..f23954f 100644 --- a/src/network/protocol/messages/notfound.zig +++ b/src/network/protocol/messages/notfound.zig @@ -6,7 +6,7 @@ const Sha256 = std.crypto.hash.sha2.Sha256; /// /// https://developer.bitcoin.org/reference/p2p_networking.html#notfound pub const NotFoundMessage = struct { - inventory: []const protocol.InventoryVector, + inventory: []const protocol.InventoryItem, const Self = @This(); @@ -41,7 +41,7 @@ pub const NotFoundMessage = struct { pub fn serializeToWriter(self: *const Self, writer: anytype) !void { try writer.writeInt(u32, @intCast(self.inventory.len), .little); for (self.inventory) |inv| { - try protocol.InventoryVector.serializeToWriter(inv, writer); + try inv.encodeToWriter(writer); } } @@ -64,11 +64,11 @@ pub const NotFoundMessage = struct { } const count = try r.readInt(u32, .little); - const inventory = try allocator.alloc(protocol.InventoryVector, count); + const inventory = try allocator.alloc(protocol.InventoryItem, count); errdefer allocator.free(inventory); for (inventory) |*inv| { - inv.* = try protocol.InventoryVector.deserializeReader(r); + inv.* = try protocol.InventoryItem.decodeReader(r); } return Self{ @@ -92,7 +92,7 @@ pub const NotFoundMessage = struct { allocator.free(self.inventory); } - pub fn new(inventory: []const protocol.InventoryVector) Self { + pub fn new(inventory: []const protocol.InventoryItem) Self { return .{ .inventory = inventory, }; @@ -105,7 +105,7 @@ test "ok_fullflow_notfound_message" { const allocator = std.testing.allocator; { - const inventory = [_]protocol.InventoryVector{ + const inventory = [_]protocol.InventoryItem{ .{ .type = 1, .hash = [_]u8{0xab} ** 32 }, .{ .type = 2, .hash = [_]u8{0xcd} ** 32 }, }; diff --git a/src/network/protocol/types/InventoryVector.zig b/src/network/protocol/types/InventoryVector.zig deleted file mode 100644 index 0ad5232..0000000 --- a/src/network/protocol/types/InventoryVector.zig +++ /dev/null @@ -1,29 +0,0 @@ -const std = @import("std"); - -type: u32, -hash: [32]u8, - -pub fn serializeToWriter(self: @This(), writer: anytype) !void { - comptime { - if (!std.meta.hasFn(@TypeOf(writer), "writeInt")) @compileError("Expects writer to have fn 'writeInt'."); - if (!std.meta.hasFn(@TypeOf(writer), "writeAll")) @compileError("Expects writer to have fn 'writeAll'."); - } - try writer.writeInt(u32, self.type, .little); - try writer.writeAll(&self.hash); -} - -pub fn deserializeReader(r: anytype) !@This() { - comptime { - if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); - if (!std.meta.hasFn(@TypeOf(r), "readBytesNoEof")) @compileError("Expects r to have fn 'readBytesNoEof'."); - } - - const type_value = try r.readInt(u32, .little); - var hash: [32]u8 = undefined; - try r.readNoEof(&hash); - - return @This(){ - .type = type_value, - .hash = hash, - }; -} \ No newline at end of file From de8591c16de572f8508e02d333e810cc03a1ab09 Mon Sep 17 00:00:00 2001 From: Khairallah AL-Awady Date: Wed, 2 Oct 2024 18:45:02 +0300 Subject: [PATCH 12/15] doc: update readme file with opcode impl status (#151) --- README.md | 86 +++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 150052b..e39f6db 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,13 @@ pie showData | Opcode | Hex | Supported | Description | | ---------------------- | --------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| op0 / opFalse | 0x00 | ❗ | An empty array of bytes is pushed onto the stack. | -| opData1-opData75 | 0x01-0x4b | ❗ | The next opcode bytes is data to be pushed onto the stack. | +| op0 / opFalse | 0x00 | ❌ | An empty array of bytes is pushed onto the stack. | +| opData1-opData75 | 0x01-0x4b | ❌ | The next opcode bytes is data to be pushed onto the stack. | | opPushData1 | 0x4c | ✅ | The next byte contains the number of bytes to be pushed onto the stack. | | opPushData2 | 0x4d | ✅ | The next two bytes contain the number of bytes to be pushed onto the stack in little endian order. | | opPushData4 | 0x4e | ✅ | The next four bytes contain the number of bytes to be pushed onto the stack in little endian order. | | op1Negate | 0x4f | ✅ | The number -1 is pushed onto the stack. | -| opResereved | 0x50 | ❗ | Transaction is invalid unless occurring in an unexecuted opIF branch | +| opReserved | 0x50 | ❌ | Transaction is invalid unless occurring in an unexecuted opIF branch | | op1 / opTrue | 0x51 | ✅ | The number 1 is pushed onto the stack. | | op2 | 0x52 | ✅ | The number 2 is pushed onto the stack. | | op3 | 0x53 | ✅ | The number 3 is pushed onto the stack. | @@ -188,55 +188,55 @@ pie showData | op7 | 0x57 | ✅ | The number 7 is pushed onto the stack. | | op8 | 0x58 | ✅ | The number 8 is pushed onto the stack. | | op9 | 0x59 | ✅ | The number 9 is pushed onto the stack. | -| op10 | 0x5a | ❗ | The number 10 is pushed onto the stack. | -| op11 | 0x5b | ❗ | The number 11 is pushed onto the stack. | -| op12 | 0x5c | ❗ | The number 12 is pushed onto the stack. | -| op13 | 0x5d | ❗ | The number 13 is pushed onto the stack. | -| op14 | 0x5e | ❗ | The number 14 is pushed onto the stack. | -| op15 | 0x5f | ❗ | The number 15 is pushed onto the stack. | -| op16 | 0x60 | ❗ | The number 16 is pushed onto the stack. | +| op10 | 0x5a | ✅ | The number 10 is pushed onto the stack. | +| op11 | 0x5b | ✅ | The number 11 is pushed onto the stack. | +| op12 | 0x5c | ✅ | The number 12 is pushed onto the stack. | +| op13 | 0x5d | ✅ | The number 13 is pushed onto the stack. | +| op14 | 0x5e | ✅ | The number 14 is pushed onto the stack. | +| op15 | 0x5f | ✅ | The number 15 is pushed onto the stack. | +| op16 | 0x60 | ✅ | The number 16 is pushed onto the stack. | | opNop | 0x61 | ✅ | Does nothing. | -| opVer | 0x62 | ❗ | Transaction is invalid unless occurring in an unexecuted opIF branch | -| opIf | 0x63 | ❗ | If the top stack value is not False, the statements are executed. The top stack value is removed. | -| opNotIf | 0x64 | ❗ | If the top stack value is False, the statements are executed. The top stack value is removed. | -| opVerIf | 0x65 | ❗ | Transaction is invalid even when occurring in an unexecuted opIF branch | -| opVerNotIf | 0x66 | ❗ | Transaction is invalid even when occurring in an unexecuted opIF branch | -| opElse | 0x67 | ❗ | If the preceding opIF or opNOTIF or opELSE was not executed then these statements are and if the preceding opIF or opNOTIF or opELSE was executed then these statements are not. | -| opEndIf | 0x68 | ❗ | Ends an if/else block. | +| opVer | 0x62 | ❌ | Transaction is invalid unless occurring in an unexecuted opIF branch | +| opIf | 0x63 | ❌ | If the top stack value is not False, the statements are executed. The top stack value is removed. | +| opNotIf | 0x64 | ❌ | If the top stack value is False, the statements are executed. The top stack value is removed. | +| opVerIf | 0x65 | ❌ | Transaction is invalid even when occurring in an unexecuted opIF branch | +| opVerNotIf | 0x66 | ❌ | Transaction is invalid even when occurring in an unexecuted opIF branch | +| opElse | 0x67 | ❌ | If the preceding opIF or opNOTIF or opELSE was not executed then these statements are and if the preceding opIF or opNOTIF or opELSE was executed then these statements are not. | +| opEndIf | 0x68 | ❌ | Ends an if/else block. | | opVerify | 0x69 | ✅ | Marks transaction as invalid if top stack value is not true. | | opReturn | 0x6a | ✅ | Marks transaction as invalid. | -| opTotalStack | 0x6b | ❗ | Puts the input onto the top of the alt stack. Removes it from the main stack. | -| opFromAltStack | 0x6c | ❗ | Puts the input onto the top of the main stack. Removes it from the alt stack. | +| opToAltStack | 0x6b | ✅ | Puts the input onto the top of the alt stack. Removes it from the main stack. | +| opFromAltStack | 0x6c | ✅ | Puts the input onto the top of the main stack. Removes it from the alt stack. | | op2Drop | 0x6d | ✅ | Removes the top two stack items. | | op2Dup | 0x6e | ✅ | Duplicates the top two stack items. | | op3Dup | 0x6f | ✅ | Duplicates the top three stack items. | -| op2Over | 0x70 | ❗ | Copies the pair of items two spaces back in the stack to the front. | -| op2Rot | 0x71 | ❗ | The fifth and sixth items back are moved to the top of the stack. | -| op2Swap | 0x72 | ❗ | Swaps the top two pairs of items. | +| op2Over | 0x70 | ✅ | Copies the pair of items two spaces back in the stack to the front. | +| op2Rot | 0x71 | ✅ | The fifth and sixth items back are moved to the top of the stack. | +| op2Swap | 0x72 | ✅ | Swaps the top two pairs of items. | | opIfDup | 0x73 | ✅ | If the top stack value is not 0, duplicate it. | | opDepth | 0x74 | ✅ | Puts the number of stack items onto the stack. | | opDrop | 0x75 | ✅ | Removes the top stack item. | | opDup | 0x76 | ✅ | Duplicates the top stack item. | -| opNip | 0x77 | ❗ | Removes the second-to-top stack item. | -| opOver | 0x78 | ❗ | Copies the second-to-top stack item to the top. | -| opPick | 0x79 | ❗ | The item n back in the stack is copied to the top. | -| opRoll | 0x7a | ❗ | The item n back in the stack is moved to the top. | -| opRot | 0x7b | ❗ | The top three items on the stack are rotated to the left. | -| opSwap | 0x7c | ❗ | The top two items on the stack are swapped. | -| opTuck | 0x7d | ❗ | The item at the top of the stack is copied and inserted before the second-to-top item. | +| opNip | 0x77 | ✅ | Removes the second-to-top stack item. | +| opOver | 0x78 | ✅ | Copies the second-to-top stack item to the top. | +| opPick | 0x79 | ✅ | The item n back in the stack is copied to the top. | +| opRoll | 0x7a | ✅ | The item n back in the stack is moved to the top. | +| opRot | 0x7b | ✅ | The top three items on the stack are rotated to the left. | +| opSwap | 0x7c | ✅ | The top two items on the stack are swapped. | +| opTuck | 0x7d | ✅ | The item at the top of the stack is copied and inserted before the second-to-top item. | | opCat | 0x7e | ❗ | Concatenates two strings. Disabled. | | opSubStr | 0x7f | ❗ | Returns a section of a string. Disabled. | | opLeft | 0x80 | ❗ | Keeps only characters left of the specified point in a string. Disabled. | | opRight | 0x81 | ❗ | Keeps only characters right of the specified point in a string. Disabled. | -| opSize | 0x82 | ❗ | Pushes the string length of the top element of the stack (without popping it). | +| opSize | 0x82 | ✅ | Pushes the string length of the top element of the stack (without popping it). | | opInvert | 0x83 | ❗ | Flips all of the bits in the input. Disabled. | | opAnd | 0x84 | ❗ | Boolean and between each bit in the inputs. Disabled. | | opOr | 0x85 | ❗ | Boolean or between each bit in the inputs. Disabled. | | opXor | 0x86 | ❗ | Boolean exclusive or between each bit in the inputs. Disabled. | | opEqual | 0x87 | ✅ | Returns 1 if the inputs are exactly equal, 0 otherwise. | | opEqualVerify | 0x88 | ✅ | Same as opEQUAL, but runs opVERIFY afterward. | -| opReserved1 | 0x89 | ❗ | Transaction is invalid unless occurring in an unexecuted opIF branch | -| opReserved2 | 0x8a | ❗ | Transaction is invalid unless occurring in an unexecuted opIF branch | +| opReserved1 | 0x89 | ❌ | Transaction is invalid unless occurring in an unexecuted opIF branch | +| opReserved2 | 0x8a | ❌ | Transaction is invalid unless occurring in an unexecuted opIF branch | | op1Add | 0x8b | ✅ | 1 is added to the input. | | op1Sub | 0x8c | ✅ | 1 is subtracted from the input. | | op2Mul | 0x8d | ❗ | The input is multiplied by 2. Disabled. | @@ -264,20 +264,20 @@ pie showData | opMin | 0xa3 | ✅ | Returns the smaller of a and b. | | opMax | 0xa4 | ✅ | Returns the larger of a and b. | | opWithin | 0xa5 | ✅ | Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. | -| opRipeMd160 | 0xa6 | ❗ | The input is hashed using RIPEMD-160. | -| opSha1 | 0xa7 | ❗ | The input is hashed using SHA-1. | +| opRipeMd160 | 0xa6 | ✅ | The input is hashed using RIPEMD-160. | +| opSha1 | 0xa7 | ✅ | The input is hashed using SHA-1. | | opSha256 | 0xa8 | ✅ | The input is hashed using SHA-256. | | opHash160 | 0xa9 | ✅ | The input is hashed twice: first with SHA-256 and then with RIPEMD-160. | -| opHash256 | 0xaa | ❗ | The input is hashed two times with SHA-256. | -| opCodeSeparator | 0xab | ❗ | All of the signature checking words will only match signatures to the data after the most recently-executed opCODESEPARATOR. | +| opHash256 | 0xaa | ❌ | The input is hashed two times with SHA-256. | +| opCodeSeparator | 0xab | ❌ | All of the signature checking words will only match signatures to the data after the most recently-executed opCODESEPARATOR. | | opCheckSig | 0xac | ✅ | The entire transaction's outputs, inputs, and script are hashed. The signature used by opCHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise. | -| opCheckSigVerify | 0xad | ❗ | Same as opCHECKSIG, but opVERIFY is executed afterward. | -| opCheckMultiSig | 0xae | ❗ | Compares the first signature against each public key until it finds an ECDSA match. Starting with the subsequent public key, it compares the second signature against each remaining public key until it finds an ECDSA match. The process is repeated until all signatures have been checked or not enough public keys remain to produce a successful result. All signatures need to match a public key. If all signatures are valid, 1 is returned, 0 otherwise. Due to a bug, one extra unused value is removed from the stack. | -| opCheckMultiSigVerify | 0xaf | ❗ | Same as opCHECKMULTISIG, but opVERIFY is executed afterward. | -| opNop1 | 0xb0 | ❗ | The word is ignored. Does not mark transaction as invalid. | -| opCheckLockTimeVerify | 0xb1 | ❗ | Marks transaction as invalid if the top stack item is greater than the transaction's nLockTime field, otherwise script evaluation continues as though an opNOP was executed. | -| opCheckSequenceVerify | 0xb2 | ❗ | Marks transaction as invalid if the relative lock time of the input is not equal to or longer than the value of the top stack item. | -| opNop4-opNop10 | 0xb3-0xb9 | ❗ | The word is ignored. Does not mark transaction as invalid. | +| opCheckSigVerify | 0xad | ❌ | Same as opCHECKSIG, but opVERIFY is executed afterward. | +| opCheckMultiSig | 0xae | ❌ | Compares the first signature against each public key until it finds an ECDSA match. Starting with the subsequent public key, it compares the second signature against each remaining public key until it finds an ECDSA match. The process is repeated until all signatures have been checked or not enough public keys remain to produce a successful result. All signatures need to match a public key. If all signatures are valid, 1 is returned, 0 otherwise. Due to a bug, one extra unused value is removed from the stack. | +| opCheckMultiSigVerify | 0xaf | ❌ | Same as opCHECKMULTISIG, but opVERIFY is executed afterward. | +| opNop1 | 0xb0 | ❌ | The word is ignored. Does not mark transaction as invalid. | +| opCheckLockTimeVerify | 0xb1 | ❌ | Marks transaction as invalid if the top stack item is greater than the transaction's nLockTime field, otherwise script evaluation continues as though an opNOP was executed. | +| opCheckSequenceVerify | 0xb2 | ❌ | Marks transaction as invalid if the relative lock time of the input is not equal to or longer than the value of the top stack item. | +| opNop4-opNop10 | 0xb3-0xb9 | ❌ | The word is ignored. Does not mark transaction as invalid. | | opCheckSigAdd | 0xba | | Increments n by one and returns to the stack if the signature is valid for the public key and transaction. Only available in tapscript. ## Contributors ✨ From a292e083b67e239818653c76fd3934dccc91915c Mon Sep 17 00:00:00 2001 From: ptisserand Date: Thu, 3 Oct 2024 11:14:40 +0200 Subject: [PATCH 13/15] feat(p2p/messages): add headers message (#148) --- src/network/protocol/messages/headers.zig | 154 ++++++++++++++++++++++ src/network/protocol/messages/lib.zig | 10 +- src/types/block_header.zig | 28 ++++ 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/network/protocol/messages/headers.zig diff --git a/src/network/protocol/messages/headers.zig b/src/network/protocol/messages/headers.zig new file mode 100644 index 0000000..b945123 --- /dev/null +++ b/src/network/protocol/messages/headers.zig @@ -0,0 +1,154 @@ +const std = @import("std"); +const protocol = @import("../lib.zig"); +const genericChecksum = @import("lib.zig").genericChecksum; + +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; + +const BlockHeader = @import("../../../types/lib.zig").BlockHeader; + +/// HeadersMessage represents the "headers" message +/// +/// https://developer.bitcoin.org/reference/p2p_networking.html#headers +pub const HeadersMessage = struct { + headers: []BlockHeader, + + const Self = @This(); + + pub inline fn name() *const [12]u8 { + return protocol.CommandNames.HEADERS ++ [_]u8{0} ** 5; + } + + pub fn checksum(self: HeadersMessage) [4]u8 { + return genericChecksum(self); + } + + pub fn deinit(self: *HeadersMessage, allocator: std.mem.Allocator) void { + allocator.free(self.headers); + } + + /// Serialize the message as bytes and write them to the Writer. + /// + /// `w` should be a valid `Writer`. + pub fn serializeToWriter(self: *const Self, w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeByte")) @compileError("Expects r to have fn 'writeByte'."); + } + try CompactSizeUint.new(self.headers.len).encodeToWriter(w); + + for (self.headers) |header| { + try header.serializeToWriter(w); + try w.writeByte(0); + } + } + + /// Serialize a message as bytes and write them to the buffer. + /// + /// buffer.len must be >= than self.hintSerializedLen() + pub fn serializeToSlice(self: *const Self, buffer: []u8) !void { + var fbs = std.io.fixedBufferStream(buffer); + try self.serializeToWriter(fbs.writer()); + } + + /// Serialize a message as bytes and return them. + pub fn serialize(self: *const Self, allocator: std.mem.Allocator) ![]u8 { + const serialized_len = self.hintSerializedLen(); + if (serialized_len != 0) { + const ret = try allocator.alloc(u8, serialized_len); + errdefer allocator.free(ret); + + try self.serializeToSlice(ret); + + return ret; + } else { + return &.{}; + } + } + + pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !Self { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readByte")) @compileError("Expects r to have fn 'readByte'."); + } + + const headers_count = try CompactSizeUint.decodeReader(r); + + var headers = try allocator.alloc(BlockHeader, headers_count.value()); + errdefer allocator.free(headers); + + for (0..headers_count.value()) |i| { + headers[i] = try BlockHeader.deserializeReader(r); + _ = try r.readByte(); + } + + return Self{ .headers = headers }; + } + + /// Deserialize bytes into a `HeaderMessage` + 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: Self) usize { + const headers_number_length = CompactSizeUint.new(self.headers.len).hint_encoded_len(); + const headers_length = self.headers.len * (BlockHeader.serializedLen() + 1); + return headers_number_length + headers_length; + } +}; + +// TESTS + +test "ok_fullflow_headers_message" { + const allocator = std.testing.allocator; + + { + // payload example from https://developer.bitcoin.org/reference/p2p_networking.html#headers + const payload = [_]u8{ + 0x01, // header count + // block header + 0x02, 0x00, 0x00, 0x00, // block version: 2 + 0xb6, 0xff, 0x0b, 0x1b, 0x16, 0x80, 0xa2, 0x86, // hash of previous block + 0x2a, 0x30, 0xca, 0x44, 0xd3, 0x46, 0xd9, 0xe8, // hash of previous block + 0x91, 0x0d, 0x33, 0x4b, 0xeb, 0x48, 0xca, 0x0c, // hash of previous block + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hash of previous block + 0x9d, 0x10, 0xaa, 0x52, 0xee, 0x94, 0x93, 0x86, // merkle root + 0xca, 0x93, 0x85, 0x69, 0x5f, 0x04, 0xed, 0xe2, // merkle root + 0x70, 0xdd, 0xa2, 0x08, 0x10, 0xde, 0xcd, 0x12, // merkle root + 0xbc, 0x9b, 0x04, 0x8a, 0xaa, 0xb3, 0x14, 0x71, // merkle root + 0x24, 0xd9, 0x5a, 0x54, // unix time (1415239972) + 0x30, 0xc3, 0x1b, 0x18, // bits + 0xfe, 0x9f, 0x08, 0x64, // nonce + // end of block header + 0x00, // transaction count + }; + + var deserialized_msg = try HeadersMessage.deserializeSlice(allocator, &payload); + defer deserialized_msg.deinit(allocator); + + const expected_block_header = BlockHeader{ + .version = 2, + .prev_block = [_]u8{ + 0xb6, 0xff, 0x0b, 0x1b, 0x16, 0x80, 0xa2, 0x86, + 0x2a, 0x30, 0xca, 0x44, 0xd3, 0x46, 0xd9, 0xe8, + 0x91, 0x0d, 0x33, 0x4b, 0xeb, 0x48, 0xca, 0x0c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + .merkle_root = [_]u8{ + 0x9d, 0x10, 0xaa, 0x52, 0xee, 0x94, 0x93, 0x86, + 0xca, 0x93, 0x85, 0x69, 0x5f, 0x04, 0xed, 0xe2, + 0x70, 0xdd, 0xa2, 0x08, 0x10, 0xde, 0xcd, 0x12, + 0xbc, 0x9b, 0x04, 0x8a, 0xaa, 0xb3, 0x14, 0x71, + }, + .timestamp = 1415239972, + .nbits = 404472624, + .nonce = 1678286846, + }; + + try std.testing.expectEqual(1, deserialized_msg.headers.len); + try std.testing.expect(expected_block_header.eql(&deserialized_msg.headers[0])); + + const serialized_payload = try deserialized_msg.serialize(allocator); + defer allocator.free(serialized_payload); + + try std.testing.expect(std.mem.eql(u8, &payload, serialized_payload)); + } +} diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index c76320a..9f51863 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -19,6 +19,8 @@ pub const NotFoundMessage = @import("notfound.zig").NotFoundMessage; pub const SendHeadersMessage = @import("sendheaders.zig").SendHeadersMessage; pub const FilterLoadMessage = @import("filterload.zig").FilterLoadMessage; pub const GetdataMessage = @import("getdata.zig").GetdataMessage; +pub const HeadersMessage = @import("headers.zig").HeadersMessage; + pub const MessageTypes = enum { version, @@ -38,6 +40,7 @@ pub const MessageTypes = enum { sendheaders, filterload, getdata, + headers, }; @@ -59,6 +62,7 @@ pub const Message = union(MessageTypes) { sendheaders: SendHeadersMessage, filterload: FilterLoadMessage, getdata: GetdataMessage, + headers: HeadersMessage, pub fn name(self: Message) *const [12]u8 { return switch (self) { @@ -79,6 +83,7 @@ pub const Message = union(MessageTypes) { .sendheaders => |m| @TypeOf(m).name(), .filterload => |m| @TypeOf(m).name(), .getdata => |m| @TypeOf(m).name(), + .headers => |m| @TypeOf(m).name(), }; } @@ -90,6 +95,7 @@ pub const Message = union(MessageTypes) { .block => |*m| m.deinit(allocator), .filteradd => |*m| m.deinit(allocator), .getdata => |*m| m.deinit(allocator), + .headers => |*m| m.deinit(allocator), else => {} } } @@ -112,7 +118,8 @@ pub const Message = union(MessageTypes) { .notfound => |*m| m.checksum(), .sendheaders => |*m| m.checksum(), .filterload => |*m| m.checksum(), - .getdata => |m| m.deinit(allocator), + .getdata => |m| *m.checksum(), + .headers => |*m| m.checksum(), }; } @@ -135,6 +142,7 @@ pub const Message = union(MessageTypes) { .sendheaders => |m| m.hintSerializedLen(), .filterload => |*m| m.hintSerializedLen(), .getdata => |m| m.hintSerializedLen(), + .headers => |*m| m.hintSerializedLen(), }; } }; diff --git a/src/types/block_header.zig b/src/types/block_header.zig index 0199cdd..ec12506 100644 --- a/src/types/block_header.zig +++ b/src/types/block_header.zig @@ -38,3 +38,31 @@ pub fn deserializeReader(r: anytype) !Self { pub fn serializedLen() usize { return 80; } + +pub fn eql(self: *const Self, other: *const Self) bool { + if (self.version != other.version) { + return false; + } + + if (!std.mem.eql(u8, &self.prev_block, &other.prev_block)) { + return false; + } + + if (!std.mem.eql(u8, &self.merkle_root, &other.merkle_root)) { + return false; + } + + if (self.timestamp != other.timestamp) { + return false; + } + + if (self.nbits != other.nbits) { + return false; + } + + if (self.nonce != other.nonce) { + return false; + } + + return true; +} From 1028fa77ccc2f895a1d018e79ccf617d0053191c Mon Sep 17 00:00:00 2001 From: Supreme Labs <100731397+supreme2580@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:56:16 +0100 Subject: [PATCH 14/15] feat: (p2p/messages): add cmpctblock message (#144) --- src/network/protocol/messages/cmpctblock.zig | 225 +++++++++++++++++++ src/network/protocol/messages/lib.zig | 11 +- src/network/wire/lib.zig | 72 ++++++ 3 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/network/protocol/messages/cmpctblock.zig diff --git a/src/network/protocol/messages/cmpctblock.zig b/src/network/protocol/messages/cmpctblock.zig new file mode 100644 index 0000000..76439be --- /dev/null +++ b/src/network/protocol/messages/cmpctblock.zig @@ -0,0 +1,225 @@ +const std = @import("std"); +const protocol = @import("../lib.zig"); +const Transaction = @import("../../../types/transaction.zig"); + +const Sha256 = std.crypto.hash.sha2.Sha256; +const BlockHeader = @import("../../../types/block_header.zig"); +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; +const genericChecksum = @import("lib.zig").genericChecksum; + +pub const CmpctBlockMessage = struct { + header: BlockHeader, + nonce: u64, + short_ids: []u64, + prefilled_txns: []PrefilledTransaction, + + const Self = @This(); + + pub const PrefilledTransaction = struct { + index: usize, + tx: Transaction, + }; + + pub fn name() *const [12]u8 { + return protocol.CommandNames.CMPCTBLOCK; + } + + pub fn checksum(self: *const Self) [4]u8 { + return genericChecksum(self); + } + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + allocator.free(self.short_ids); + for (self.prefilled_txns) |*txn| { + txn.tx.deinit(); + } + allocator.free(self.prefilled_txns); + } + + pub fn serializeToWriter(self: *const Self, w: anytype) !void { + comptime { + if (!@hasDecl(@TypeOf(w), "writeInt")) { + @compileError("Writer must have a writeInt method"); + } + } + + try self.header.serializeToWriter(w); + try w.writeInt(u64, self.nonce, .little); + + const short_ids_count = CompactSizeUint.new(self.short_ids.len); + try short_ids_count.encodeToWriter(w); + for (self.short_ids) |id| { + try w.writeInt(u64, id, .little); + } + + const prefilled_txns_count = CompactSizeUint.new(self.prefilled_txns.len); + try prefilled_txns_count.encodeToWriter(w); + + for (self.prefilled_txns) |txn| { + try CompactSizeUint.new(txn.index).encodeToWriter(w); + try txn.tx.serializeToWriter(w); + } + } + + pub fn serializeToSlice(self: *const Self, buffer: []u8) !void { + var fbs = std.io.fixedBufferStream(buffer); + try self.serializeToWriter(fbs.writer()); + } + + pub fn serialize(self: *const Self, allocator: std.mem.Allocator) ![]u8 { + const serialized_len = self.hintSerializedLen(); + if (serialized_len == 0) return &.{}; + const ret = try allocator.alloc(u8, serialized_len); + errdefer allocator.free(ret); + + try self.serializeToSlice(ret); + + return ret; + } + + pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !Self { + comptime { + if (!@hasDecl(@TypeOf(r), "readInt")) { + @compileError("Reader must have a readInt method"); + } + } + + const header = try BlockHeader.deserializeReader(r); + const nonce = try r.readInt(u64, .little); + + const short_ids_count = try CompactSizeUint.decodeReader(r); + const short_ids = try allocator.alloc(u64, short_ids_count.value()); + errdefer allocator.free(short_ids); + + for (short_ids) |*id| { + id.* = try r.readInt(u64, .little); + } + + const prefilled_txns_count = try CompactSizeUint.decodeReader(r); + const prefilled_txns = try allocator.alloc(PrefilledTransaction, prefilled_txns_count.value()); + errdefer allocator.free(prefilled_txns); + + for (prefilled_txns) |*txn| { + const index = try CompactSizeUint.decodeReader(r); + const tx = try Transaction.deserializeReader(allocator, r); + + txn.* = PrefilledTransaction{ + .index = index.value(), + .tx = tx, + }; + } + + return Self{ + .header = header, + .nonce = nonce, + .short_ids = short_ids, + .prefilled_txns = prefilled_txns, + }; + } + + 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 { + var len: usize = 80 + 8; // BlockHeader + nonce + len += CompactSizeUint.new(self.short_ids.len).hint_encoded_len(); + len += self.short_ids.len * 8; + len += CompactSizeUint.new(self.prefilled_txns.len).hint_encoded_len(); + for (self.prefilled_txns) |txn| { + len += CompactSizeUint.new(txn.index).hint_encoded_len(); + len += txn.tx.hintEncodedLen(); + } + return len; + } + + pub fn eql(self: *const Self, other: *const Self) bool { + if (self.header.version != other.header.version or + !std.mem.eql(u8, &self.header.prev_block, &other.header.prev_block) or + !std.mem.eql(u8, &self.header.merkle_root, &other.header.merkle_root) or + self.header.timestamp != other.header.timestamp or + self.header.nbits != other.header.nbits or + self.header.nonce != other.header.nonce or + self.nonce != other.nonce) return false; + + if (self.short_ids.len != other.short_ids.len) return false; + for (self.short_ids, other.short_ids) |a, b| { + if (a != b) return false; + } + if (self.prefilled_txns.len != other.prefilled_txns.len) return false; + for (self.prefilled_txns, other.prefilled_txns) |a, b| { + if (a.index != b.index or !a.tx.eql(b.tx)) return false; + } + return true; + } +}; + +test "CmpctBlockMessage serialization and deserialization" { + const testing = std.testing; + const Hash = @import("../../../types/hash.zig"); + const Script = @import("../../../types/script.zig"); + const OutPoint = @import("../../../types/outpoint.zig"); + const OpCode = @import("../../../script/opcodes/constant.zig").Opcode; + + const test_allocator = testing.allocator; + + // Create a sample BlockHeader + const header = BlockHeader{ + .version = 1, + .prev_block = [_]u8{0} ** 32, // Zero-filled array of 32 bytes + .merkle_root = [_]u8{0} ** 32, // Zero-filled array of 32 bytes + .timestamp = 1631234567, + .nbits = 0x1d00ffff, + .nonce = 12345, + }; + + // Create sample short_ids + const short_ids = try test_allocator.alloc(u64, 2); + defer test_allocator.free(short_ids); + short_ids[0] = 123456789; + short_ids[1] = 987654321; + + // Create a sample Transaction + var tx = try Transaction.init(test_allocator); + defer tx.deinit(); + try tx.addInput(OutPoint{ .hash = Hash.newZeroed(), .index = 0 }); + { + var script_pubkey = try Script.init(test_allocator); + defer script_pubkey.deinit(); + try script_pubkey.push(&[_]u8{ OpCode.OP_DUP.toBytes(), OpCode.OP_HASH160.toBytes(), OpCode.OP_EQUALVERIFY.toBytes(), OpCode.OP_CHECKSIG.toBytes() }); + try tx.addOutput(50000, script_pubkey); + } + + // Create sample prefilled_txns + const prefilled_txns = try test_allocator.alloc(CmpctBlockMessage.PrefilledTransaction, 1); + defer test_allocator.free(prefilled_txns); + prefilled_txns[0] = .{ + .index = 0, + .tx = tx, + }; + + // Create CmpctBlockMessage + const msg = CmpctBlockMessage{ + .header = header, + .nonce = 9876543210, + .short_ids = short_ids, + .prefilled_txns = prefilled_txns, + }; + + // Test serialization + const serialized = try msg.serialize(test_allocator); + defer test_allocator.free(serialized); + + // Test deserialization + var deserialized = try CmpctBlockMessage.deserializeSlice(test_allocator, serialized); + defer deserialized.deinit(test_allocator); + + // Verify deserialized data + try std.testing.expect(msg.eql(&deserialized)); + + // Test hintSerializedLen + const hint_len = msg.hintSerializedLen(); + try testing.expect(hint_len > 0); + try testing.expect(hint_len == serialized.len); +} diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 9f51863..fa25f2d 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -20,7 +20,7 @@ pub const SendHeadersMessage = @import("sendheaders.zig").SendHeadersMessage; pub const FilterLoadMessage = @import("filterload.zig").FilterLoadMessage; pub const GetdataMessage = @import("getdata.zig").GetdataMessage; pub const HeadersMessage = @import("headers.zig").HeadersMessage; - +pub const CmpctBlockMessage = @import("cmpctblock.zig").CmpctBlockMessage; pub const MessageTypes = enum { version, @@ -41,6 +41,7 @@ pub const MessageTypes = enum { filterload, getdata, headers, + cmpctblock, }; @@ -63,6 +64,7 @@ pub const Message = union(MessageTypes) { filterload: FilterLoadMessage, getdata: GetdataMessage, headers: HeadersMessage, + cmpctblock: CmpctBlockMessage, pub fn name(self: Message) *const [12]u8 { return switch (self) { @@ -84,6 +86,7 @@ pub const Message = union(MessageTypes) { .filterload => |m| @TypeOf(m).name(), .getdata => |m| @TypeOf(m).name(), .headers => |m| @TypeOf(m).name(), + .cmpctblock => |m| @TypeOf(m).name(), }; } @@ -95,6 +98,10 @@ pub const Message = union(MessageTypes) { .block => |*m| m.deinit(allocator), .filteradd => |*m| m.deinit(allocator), .getdata => |*m| m.deinit(allocator), + .notfound => {}, + .cmpctblock => |*m| m.deinit(allocator), + .sendheaders => {}, + .filterload => {}, .headers => |*m| m.deinit(allocator), else => {} } @@ -120,6 +127,7 @@ pub const Message = union(MessageTypes) { .filterload => |*m| m.checksum(), .getdata => |m| *m.checksum(), .headers => |*m| m.checksum(), + .cmpctblock => |*m| m.checksum(), }; } @@ -143,6 +151,7 @@ pub const Message = union(MessageTypes) { .filterload => |*m| m.hintSerializedLen(), .getdata => |m| m.hintSerializedLen(), .headers => |*m| m.hintSerializedLen(), + .cmpctblock => |*m| m.hintSerializedLen(), }; } }; diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index ad13069..168ad32 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -143,6 +143,8 @@ pub fn receiveMessage( protocol.messages.Message{ .filterload = try protocol.messages.FilterLoadMessage.deserializeReader(allocator, r) } else if (std.mem.eql(u8, &command, protocol.messages.GetdataMessage.name())) protocol.messages.Message{ .getdata = try protocol.messages.GetdataMessage.deserializeReader(allocator, r) } + else if (std.mem.eql(u8, &command, protocol.messages.CmpctBlockMessage.name())) + protocol.messages.Message{ .cmpctblock = try protocol.messages.CmpctBlockMessage.deserializeReader(allocator, r) } else { try r.skipBytes(payload_len, .{}); // Purge the wire return error.UnknownMessage; @@ -619,3 +621,73 @@ test "ok_send_sendcmpct_message" { else => unreachable, } } + +test "ok_send_cmpctblock_message" { + const Transaction = @import("../../types/transaction.zig"); + const OutPoint = @import("../../types/outpoint.zig"); + const OpCode = @import("../../script/opcodes/constant.zig").Opcode; + const Hash = @import("../../types/hash.zig"); + const Script = @import("../../types/script.zig"); + const CmpctBlockMessage = @import("../protocol/messages/cmpctblock.zig").CmpctBlockMessage; + + const allocator = std.testing.allocator; + + // Create a sample BlockHeader + const header = BlockHeader{ + .version = 1, + .prev_block = [_]u8{0} ** 32, // Zero-filled array of 32 bytes + .merkle_root = [_]u8{0} ** 32, // Zero-filled array of 32 bytes + .timestamp = 1631234567, + .nbits = 0x1d00ffff, + .nonce = 12345, + }; + + // Create sample short_ids + const short_ids = try allocator.alloc(u64, 2); + defer allocator.free(short_ids); + short_ids[0] = 123456789; + short_ids[1] = 987654321; + + // Create a sample Transaction + var tx = try Transaction.init(allocator); + defer tx.deinit(); + try tx.addInput(OutPoint{ .hash = Hash.newZeroed(), .index = 0 }); + { + var script_pubkey = try Script.init(allocator); + defer script_pubkey.deinit(); + try script_pubkey.push(&[_]u8{ OpCode.OP_DUP.toBytes(), OpCode.OP_HASH160.toBytes(), OpCode.OP_EQUALVERIFY.toBytes(), OpCode.OP_CHECKSIG.toBytes() }); + try tx.addOutput(50000, script_pubkey); + } + + // Create sample prefilled_txns + const prefilled_txns = try allocator.alloc(CmpctBlockMessage.PrefilledTransaction, 1); + defer allocator.free(prefilled_txns); + prefilled_txns[0] = .{ + .index = 0, + .tx = tx, + }; + + // Create CmpctBlockMessage + const msg = CmpctBlockMessage{ + .header = header, + .nonce = 9876543210, + .short_ids = short_ids, + .prefilled_txns = prefilled_txns, + }; + + // Test serialization + const serialized = try msg.serialize(allocator); + defer allocator.free(serialized); + + // Test deserialization + var deserialized = try CmpctBlockMessage.deserializeSlice(allocator, serialized); + defer deserialized.deinit(allocator); + + // Verify deserialized data + try std.testing.expect(msg.eql(&deserialized)); + + // Test hintSerializedLen + const hint_len = msg.hintSerializedLen(); + try std.testing.expect(hint_len > 0); + try std.testing.expect(hint_len == serialized.len); +} From 346312dec110ff2212a2ec1678760ccbe3eefe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Delabrouille?= <34384633+tdelabro@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:57:26 +0200 Subject: [PATCH 15/15] build: revert to zig v0.13 (#156) --- .github/workflows/check.yml | 2 +- .github/workflows/docs.yml | 2 +- build.zig | 4 ++++ build.zig.zon | 20 ++++++++++++-------- src/network/protocol/messages/lib.zig | 6 +----- src/network/rpc.zig | 17 ++++++++--------- src/vanitygen.zig | 1 - 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6b977f7..56144ca 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,7 +7,7 @@ on: branches: [main] env: - ZIG_VERSION: 0.14.0-dev.1550+4fba7336a + ZIG_VERSION: 0.13.0 jobs: build: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index adef7a1..4a2f31a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,7 +7,7 @@ on: permissions: write-all env: - ZIG_VERSION: 0.14.0-dev.1550+4fba7336a + ZIG_VERSION: 0.13.0 jobs: docs: diff --git a/build.zig b/build.zig index f337d32..c56f905 100644 --- a/build.zig +++ b/build.zig @@ -21,6 +21,10 @@ const external_dependencies = [_]build_helpers.Dependency{ .name = "bitcoin-primitives", .module_name = "bitcoin-primitives", }, + .{ + .name = "libxev", + .module_name = "xev", + }, }; pub fn build(b: *std.Build) !void { diff --git a/build.zig.zon b/build.zig.zon index 8a26c05..ea0f537 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,14 +6,6 @@ .url = "https://github.com/karlseguin/zul/archive/ae0c27350c0db6b460f22cba30b6b0c4a02d1ffd.zip", .hash = "1220457e2c8867f6734520d9b335f01e1d851d6fe7adaa7f6f0756158acaf6c5e87f", }, - .httpz = .{ - .url = "git+https://github.com/karlseguin/http.zig#d3e3fb3cf2f3caa2432338282dbe750f85e24254", - .hash = "12205748a52926d9e6dbb1ac07462d8772c1e2fd6f52e4d822d8bf282f425efd1330", - }, - .clap = .{ - .url = "git+https://github.com/Hejsil/zig-clap#2d9db156ae928860a9acf2f1260750d3b44a4c98", - .hash = "122005e589ab3b6bff8e589b45f5b12cd27ce79f266bdac17e9f33ebfe2fbaff7fe3", - }, .lmdb = .{ .url = "git+https://github.com/zig-bitcoin/zig-lmdb#eb7436d091464131551759b0f80d4f1d1a15ece1", .hash = "1220f9e1eb744c8dc2750c1e6e1ceb1c2d521bedb161ddead1a6bb772032e576d74a", @@ -22,6 +14,18 @@ .url = "git+https://github.com/zig-bitcoin/bitcoin-primitives#6595846b34c8c157175c52380f5c7cc6fc9ca108", .hash = "12208a138853cd57db1c5e3348d60a74aa54d5c0a63393b6367098f1c150a0c31438", }, + .clap = .{ + .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz", + .hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b", + }, + .libxev = .{ + .url = "https://github.com/mitchellh/libxev/archive/main.tar.gz", + .hash = "1220612bc023c21d75234882ec9a8c6a1cbd9d642da3dfb899297f14bb5bd7b6cd78", + }, + .httpz = .{ + .url = "https://github.com/karlseguin/http.zig/archive/zig-0.13.tar.gz", + .hash = "12208c1f2c5f730c4c03aabeb0632ade7e21914af03e6510311b449458198d0835d6", + }, }, .paths = .{ "build.zig", diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index fa25f2d..8d897d9 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -18,7 +18,6 @@ const Sha256 = std.crypto.hash.sha2.Sha256; pub const NotFoundMessage = @import("notfound.zig").NotFoundMessage; pub const SendHeadersMessage = @import("sendheaders.zig").SendHeadersMessage; pub const FilterLoadMessage = @import("filterload.zig").FilterLoadMessage; -pub const GetdataMessage = @import("getdata.zig").GetdataMessage; pub const HeadersMessage = @import("headers.zig").HeadersMessage; pub const CmpctBlockMessage = @import("cmpctblock.zig").CmpctBlockMessage; @@ -98,10 +97,7 @@ pub const Message = union(MessageTypes) { .block => |*m| m.deinit(allocator), .filteradd => |*m| m.deinit(allocator), .getdata => |*m| m.deinit(allocator), - .notfound => {}, .cmpctblock => |*m| m.deinit(allocator), - .sendheaders => {}, - .filterload => {}, .headers => |*m| m.deinit(allocator), else => {} } @@ -125,7 +121,7 @@ pub const Message = union(MessageTypes) { .notfound => |*m| m.checksum(), .sendheaders => |*m| m.checksum(), .filterload => |*m| m.checksum(), - .getdata => |m| *m.checksum(), + .getdata => |*m| m.checksum(), .headers => |*m| m.checksum(), .cmpctblock => |*m| m.checksum(), }; diff --git a/src/network/rpc.zig b/src/network/rpc.zig index 56c24de..7e0e44a 100644 --- a/src/network/rpc.zig +++ b/src/network/rpc.zig @@ -54,13 +54,12 @@ pub const RPC = struct { /// The RPC server will start a HTTP server and listen on the RPC port. pub fn start(self: *RPC) !void { std.log.info("Starting RPC server on port {}", .{self.config.rpc_port}); - var handler = Handler{}; - var server = try httpz.Server(*Handler).init(self.allocator, .{ .port = self.config.rpc_port }, &handler); - var router = server.router(.{}); + var server = try httpz.Server().init(self.allocator, .{ .port = self.config.rpc_port }); + var router = server.router(); // Register routes. - router.get("/", index, .{}); - router.get("/error", @"error", .{}); + router.get("/", index); + router.get("/error", @"error"); std.log.info("RPC server listening on http://localhost:{d}/\n", .{self.config.rpc_port}); @@ -74,7 +73,7 @@ const Handler = struct { // If the handler defines a special "notFound" function, it'll be called // when a request is made and no route matches. - pub fn notFound(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void { + pub fn notFound(_: *httpz.Request, res: *httpz.Response) !void { res.status = 404; res.body = "NOPE!"; } @@ -83,7 +82,7 @@ const Handler = struct { // called when an action returns an error. // Note that this function takes an additional parameter (the error) and // returns a `void` rather than a `!void`. - pub fn uncaughtError(_: *Handler, req: *httpz.Request, res: *httpz.Response, err: anyerror) void { + pub fn uncaughtError(req: *httpz.Request, res: *httpz.Response, err: anyerror) void { std.debug.print("uncaught http error at {s}: {}\n", .{ req.url.path, err }); // Alternative to res.content_type = .TYPE @@ -96,13 +95,13 @@ const Handler = struct { } }; -fn index(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void { +fn index(_: *httpz.Request, res: *httpz.Response) !void { res.body = \\ \\

Running Bitcoin. ; } -fn @"error"(_: *Handler, _: *httpz.Request, _: *httpz.Response) !void { +fn @"error"(_: *httpz.Request, _: *httpz.Response) !void { return error.ActionError; } diff --git a/src/vanitygen.zig b/src/vanitygen.zig index cf33704..74f3dbf 100644 --- a/src/vanitygen.zig +++ b/src/vanitygen.zig @@ -151,7 +151,6 @@ fn threadSearcher( // send to sender if (f) { - @branchHint(.cold); return sender.send(keys_and_address) catch return; } }