From 1ba41c54c8a562867bd46869c50bbc4260be996a Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Thu, 3 Oct 2024 03:27:57 +0530 Subject: [PATCH 1/3] feat/blocktxn --- src/network/protocol/messages/blocktxn.zig | 128 ++++++++++++++++++ src/network/protocol/messages/lib.zig | 7 + src/network/protocol/messages/merkleblock.zig | 8 ++ src/network/wire/lib.zig | 37 ++++- 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/network/protocol/messages/blocktxn.zig diff --git a/src/network/protocol/messages/blocktxn.zig b/src/network/protocol/messages/blocktxn.zig new file mode 100644 index 0000000..7acc29e --- /dev/null +++ b/src/network/protocol/messages/blocktxn.zig @@ -0,0 +1,128 @@ +const std = @import("std"); +const protocol = @import("../lib.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; +/// BlockTxnMessage represents the "BlockTxn" message +/// +/// https://developer.bitcoin.org/reference/p2p_networking.html#blocktxn +pub const BlockTxnMessage = struct { + block_hash: [32]u8, + indexes: []CompactSizeUint, + + const Self = @This(); + + pub fn name() *const [12]u8 { + return protocol.CommandNames.BLOCKTXN ++ [_]u8{0} ** 4; + } + + /// Returns the message checksum + pub fn checksum(self: *const Self) [4]u8 { + return genericChecksum(self); + } + + /// Free the allocated memory + pub fn deinit(self: *const Self, allocator: std.mem.Allocator) void { + allocator.free(self.indexes); + } + + /// 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 r to have fn 'writeInt'."); + if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'."); + } + try w.writeAll(&self.block_hash); + const indexes_count = CompactSizeUint.new(self.indexes.len); + try indexes_count.encodeToWriter(w); + for (self.indexes) |*index| { + try index.encodeToWriter(w); + } + } + /// 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(); + const ret = try allocator.alloc(u8, serialized_len); + errdefer allocator.free(ret); + + try self.serializeToSlice(ret); + + return ret; + } + + /// Returns the hint of the serialized length of the message. + pub fn hintSerializedLen(self: *const Self) usize { + // 32 bytes for the block header + const fixed_length = 32; + + const indexes_count_length: usize = CompactSizeUint.new(self.indexes.len).hint_encoded_len(); + + var compact_indexes_length: usize = 0; + for (self.indexes) |index| { + compact_indexes_length += index.hint_encoded_len(); + } + + const variable_length = indexes_count_length + compact_indexes_length; + + return fixed_length + variable_length; + } + + pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !Self { + var blocktxn_message: Self = undefined; + try r.readNoEof(&blocktxn_message.block_hash); + + const indexes_count = try CompactSizeUint.decodeReader(r); + blocktxn_message.indexes = try allocator.alloc(CompactSizeUint, indexes_count.value()); + errdefer allocator.free(blocktxn_message.indexes); + + for (blocktxn_message.indexes) |*index| { + index.* = try CompactSizeUint.decodeReader(r); + } + + return blocktxn_message; + } + + /// Deserialize bytes into a `BlockTxnMessage` + 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 new(block_hash: [32]u8, indexes: []CompactSizeUint) Self { + return .{ + .block_hash = block_hash, + .indexes = indexes, + }; + } +}; + +test "BlockTxnMessage serialization and deserialization" { + const test_allocator = std.testing.allocator; + + const block_hash: [32]u8 = [_]u8{0} ** 32; + const indexes = try test_allocator.alloc(CompactSizeUint, 1); + indexes[0] = CompactSizeUint.new(123); + const msg = BlockTxnMessage.new(block_hash, indexes); + + defer msg.deinit(test_allocator); + + const serialized = try msg.serialize(test_allocator); + defer test_allocator.free(serialized); + + const deserialized = try BlockTxnMessage.deserializeSlice(test_allocator, serialized); + defer deserialized.deinit(test_allocator); + + try std.testing.expectEqual(msg.block_hash, deserialized.block_hash); + try std.testing.expectEqual(msg.indexes[0].value(), msg.indexes[0].value()); + try std.testing.expectEqual(msg.hintSerializedLen(), 32 + 1 + 1); +} diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 5a27d62..680139e 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -17,6 +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 BlockTxnMessage = @import("blocktxn.zig").BlockTxnMessage; pub const InventoryVector = struct { type: u32, @@ -65,6 +66,7 @@ pub const MessageTypes = enum { notfound, sendheaders, filterload, + blocktxn, }; pub const Message = union(MessageTypes) { @@ -84,6 +86,7 @@ pub const Message = union(MessageTypes) { notfound: NotFoundMessage, sendheaders: SendHeadersMessage, filterload: FilterLoadMessage, + blocktxn: BlockTxnMessage, pub fn name(self: Message) *const [12]u8 { return switch (self) { @@ -103,6 +106,7 @@ pub const Message = union(MessageTypes) { .notfound => |m| @TypeOf(m).name(), .sendheaders => |m| @TypeOf(m).name(), .filterload => |m| @TypeOf(m).name(), + .blocktxn => |m| @TypeOf(m).name(), }; } @@ -124,6 +128,7 @@ pub const Message = union(MessageTypes) { .notfound => {}, .sendheaders => {}, .filterload => {}, + .blocktxn => |*m| m.deinit(allocator), } } @@ -145,6 +150,7 @@ pub const Message = union(MessageTypes) { .notfound => |*m| m.checksum(), .sendheaders => |*m| m.checksum(), .filterload => |*m| m.checksum(), + .blocktxn => |*m| m.checksum(), }; } @@ -166,6 +172,7 @@ pub const Message = union(MessageTypes) { .notfound => |m| m.hintSerializedLen(), .sendheaders => |m| m.hintSerializedLen(), .filterload => |*m| m.hintSerializedLen(), + .blocktxn => |*m| m.hintSerializedLen(), }; } }; diff --git a/src/network/protocol/messages/merkleblock.zig b/src/network/protocol/messages/merkleblock.zig index 63eeae9..2b3d6b7 100644 --- a/src/network/protocol/messages/merkleblock.zig +++ b/src/network/protocol/messages/merkleblock.zig @@ -33,6 +33,10 @@ pub const MerkleBlockMessage = struct { /// 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 self.block_header.serializeToWriter(w); try w.writeInt(u32, self.transaction_count, .little); const hash_count = CompactSizeUint.new(self.hashes.len); @@ -78,6 +82,10 @@ pub const MerkleBlockMessage = 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), "readNoEof")) @compileError("Expects r to have fn 'readNoEof'."); + } var merkle_block_message: Self = undefined; merkle_block_message.block_header = try BlockHeader.deserializeReader(r); merkle_block_message.transaction_count = try r.readInt(u32, .little); diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index bf0d022..55d4407 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -19,7 +19,8 @@ pub const Error = error{ }; const BlockHeader = @import("../../types/block_header.zig"); -/// Return the checksum of a slice +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; +// Return the checksum of a slice /// /// Use it on serialized messages to compute the header's value fn computePayloadChecksum(payload: []u8) [4]u8 { @@ -141,6 +142,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.BlockTxnMessage.name())) + protocol.messages.Message{ .blocktxn = try protocol.messages.BlockTxnMessage.deserializeReader(allocator, r) } else { try r.skipBytes(payload_len, .{}); // Purge the wire return error.UnknownMessage; @@ -579,3 +582,35 @@ test "ok_send_sendcmpct_message" { else => unreachable, } } +test "ok_send_blocktxn_message" { + const Config = @import("../../config/config.zig").Config; + const ArrayList = std.ArrayList; + const test_allocator = std.testing.allocator; + const BlockTxnMessage = protocol.messages.BlockTxnMessage; + + var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); + defer list.deinit(); + + const block_hash = [_]u8{0} ** 32; + const indexes = try test_allocator.alloc(CompactSizeUint, 1); + indexes[0] = CompactSizeUint.new(1000); + defer test_allocator.free(indexes); + const message = BlockTxnMessage.new(block_hash, indexes); + + var 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) { + .blocktxn => { + try std.testing.expectEqual(message.block_hash, received_message.blocktxn.block_hash); + try std.testing.expectEqual(indexes[0].value(), received_message.blocktxn.indexes[0].value()); + }, + else => unreachable, + } +} From 0610fa44bba54a0362497f3d825dc3e6b1f99be9 Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Thu, 3 Oct 2024 20:18:49 +0530 Subject: [PATCH 2/3] fix: comments --- src/network/protocol/messages/blocktxn.zig | 3 ++- src/network/wire/lib.zig | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/network/protocol/messages/blocktxn.zig b/src/network/protocol/messages/blocktxn.zig index 7acc29e..226b8da 100644 --- a/src/network/protocol/messages/blocktxn.zig +++ b/src/network/protocol/messages/blocktxn.zig @@ -5,6 +5,7 @@ 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; + /// BlockTxnMessage represents the "BlockTxn" message /// /// https://developer.bitcoin.org/reference/p2p_networking.html#blocktxn @@ -62,7 +63,7 @@ pub const BlockTxnMessage = struct { /// Returns the hint of the serialized length of the message. pub fn hintSerializedLen(self: *const Self) usize { - // 32 bytes for the block header + // 32 bytes for the block hash const fixed_length = 32; const indexes_count_length: usize = CompactSizeUint.new(self.indexes.len).hint_encoded_len(); diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 53509ed..019eb09 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -11,15 +11,14 @@ const std = @import("std"); const protocol = @import("../protocol/lib.zig"); - +const BlockHeader = @import("../../types/block_header.zig"); +const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; const Sha256 = std.crypto.hash.sha2.Sha256; pub const Error = error{ MessageTooLarge, }; -const BlockHeader = @import("../../types/block_header.zig"); -const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; // Return the checksum of a slice /// /// Use it on serialized messages to compute the header's value @@ -583,6 +582,7 @@ test "ok_send_sendcmpct_message" { .sendcmpct => |sendcmpct_message| try std.testing.expect(message.eql(&sendcmpct_message)), else => unreachable, } +} test "ok_send_blocktxn_message" { const Config = @import("../../config/config.zig").Config; @@ -615,6 +615,7 @@ test "ok_send_blocktxn_message" { }, else => unreachable, } +} test "ok_send_cmpctblock_message" { const Transaction = @import("../../types/transaction.zig"); From e5077a378533d4d2373c99f2cba9d7ad2aa88144 Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Mon, 7 Oct 2024 05:03:08 +0530 Subject: [PATCH 3/3] fix --- src/network/protocol/messages/blocktxn.zig | 58 ++++++++-------------- src/network/protocol/messages/lib.zig | 6 +-- src/network/wire/lib.zig | 5 +- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/network/protocol/messages/blocktxn.zig b/src/network/protocol/messages/blocktxn.zig index 226b8da..3a9dbba 100644 --- a/src/network/protocol/messages/blocktxn.zig +++ b/src/network/protocol/messages/blocktxn.zig @@ -3,15 +3,18 @@ const protocol = @import("../lib.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; const BlockHeader = @import("../../../types/block_header.zig"); +const Transaction = @import("../../../types/transaction.zig"); const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint; const genericChecksum = @import("lib.zig").genericChecksum; +const genericDeserializeSlice = @import("lib.zig").genericDeserializeSlice; +const genericSerialize = @import("lib.zig").genericSerialize; /// BlockTxnMessage represents the "BlockTxn" message /// /// https://developer.bitcoin.org/reference/p2p_networking.html#blocktxn pub const BlockTxnMessage = struct { block_hash: [32]u8, - indexes: []CompactSizeUint, + transactions: []Transaction, const Self = @This(); @@ -26,7 +29,7 @@ pub const BlockTxnMessage = struct { /// Free the allocated memory pub fn deinit(self: *const Self, allocator: std.mem.Allocator) void { - allocator.free(self.indexes); + allocator.free(self.transactions); } /// Serialize the message as bytes and write them to the Writer. @@ -36,9 +39,9 @@ pub const BlockTxnMessage = struct { if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'."); } try w.writeAll(&self.block_hash); - const indexes_count = CompactSizeUint.new(self.indexes.len); + const indexes_count = CompactSizeUint.new(self.transactions.len); try indexes_count.encodeToWriter(w); - for (self.indexes) |*index| { + for (self.transactions) |*index| { try index.encodeToWriter(w); } } @@ -50,26 +53,15 @@ pub const BlockTxnMessage = struct { 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(); - const ret = try allocator.alloc(u8, serialized_len); - errdefer allocator.free(ret); - - try self.serializeToSlice(ret); - - return ret; - } - /// Returns the hint of the serialized length of the message. pub fn hintSerializedLen(self: *const Self) usize { // 32 bytes for the block hash const fixed_length = 32; - const indexes_count_length: usize = CompactSizeUint.new(self.indexes.len).hint_encoded_len(); + const indexes_count_length: usize = CompactSizeUint.new(self.transactions.len).hint_encoded_len(); var compact_indexes_length: usize = 0; - for (self.indexes) |index| { + for (self.transactions) |index| { compact_indexes_length += index.hint_encoded_len(); } @@ -82,27 +74,21 @@ pub const BlockTxnMessage = struct { var blocktxn_message: Self = undefined; try r.readNoEof(&blocktxn_message.block_hash); - const indexes_count = try CompactSizeUint.decodeReader(r); - blocktxn_message.indexes = try allocator.alloc(CompactSizeUint, indexes_count.value()); - errdefer allocator.free(blocktxn_message.indexes); + const indexes_count = try Transaction.deserializeReader(allocator, r); + blocktxn_message.transactions = try allocator.alloc(Transaction, indexes_count.value()); + errdefer allocator.free(blocktxn_message.transactions); - for (blocktxn_message.indexes) |*index| { - index.* = try CompactSizeUint.decodeReader(r); + for (blocktxn_message.transactions) |*index| { + index.* = try Transaction.deserializeReader(allocator, r); } return blocktxn_message; } - /// Deserialize bytes into a `BlockTxnMessage` - 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 new(block_hash: [32]u8, indexes: []CompactSizeUint) Self { + pub fn new(block_hash: [32]u8, transactions: []Transaction) Self { return .{ .block_hash = block_hash, - .indexes = indexes, + .transactions = transactions, }; } }; @@ -111,19 +97,19 @@ test "BlockTxnMessage serialization and deserialization" { const test_allocator = std.testing.allocator; const block_hash: [32]u8 = [_]u8{0} ** 32; - const indexes = try test_allocator.alloc(CompactSizeUint, 1); - indexes[0] = CompactSizeUint.new(123); - const msg = BlockTxnMessage.new(block_hash, indexes); + const transaction = try test_allocator.alloc(Transaction, 1); + transaction[0] = Transaction.init(test_allocator); + const msg = BlockTxnMessage.new(block_hash, transaction); defer msg.deinit(test_allocator); - const serialized = try msg.serialize(test_allocator); + const serialized = try msg.genericSerialize(test_allocator); defer test_allocator.free(serialized); - const deserialized = try BlockTxnMessage.deserializeSlice(test_allocator, serialized); + const deserialized = try BlockTxnMessage.genericDeserializeSlice(test_allocator, serialized); defer deserialized.deinit(test_allocator); try std.testing.expectEqual(msg.block_hash, deserialized.block_hash); - try std.testing.expectEqual(msg.indexes[0].value(), msg.indexes[0].value()); + try std.testing.expectEqual(msg.transaction[0].value(), msg.transaction[0].value()); try std.testing.expectEqual(msg.hintSerializedLen(), 32 + 1 + 1); } diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index 7c11dd1..067f90a 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -45,7 +45,6 @@ pub const MessageTypes = enum { cmpctblock, }; - pub const Message = union(MessageTypes) { version: VersionMessage, verack: VerackMessage, @@ -105,9 +104,8 @@ pub const Message = union(MessageTypes) { .sendheaders => {}, .filterload => {}, .blocktxn => |*m| m.deinit(allocator), - .headers => |*m| m.deinit(allocator), } .headers => |*m| m.deinit(allocator), - else => {} + else => {}, } } @@ -204,4 +202,4 @@ pub fn genericDeserializeSlice(comptime T: type, allocator: std.mem.Allocator, b const reader = fbs.reader(); return try T.deserializeReader(allocator, reader); -} \ No newline at end of file +} diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 7d59e0c..7f8e5a6 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -628,13 +628,14 @@ test "ok_send_blocktxn_message" { const ArrayList = std.ArrayList; const test_allocator = std.testing.allocator; const BlockTxnMessage = protocol.messages.BlockTxnMessage; + const Transaction = @import("../../types/transaction.zig"); var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); defer list.deinit(); const block_hash = [_]u8{0} ** 32; - const indexes = try test_allocator.alloc(CompactSizeUint, 1); - indexes[0] = CompactSizeUint.new(1000); + const indexes = try test_allocator.alloc(Transaction, 1); + indexes[0] = Transaction.init(test_allocator); defer test_allocator.free(indexes); const message = BlockTxnMessage.new(block_hash, indexes);