Skip to content

Commit

Permalink
feat(p2p/messages): add sendcmpct message (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
guha-rahul authored Sep 27, 2024
1 parent 437f295 commit e169cc9
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/network/protocol/messages/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub const GetblocksMessage = @import("getblocks.zig").GetblocksMessage;
pub const PingMessage = @import("ping.zig").PingMessage;
pub const PongMessage = @import("pong.zig").PongMessage;
pub const FeeFilterMessage = @import("feefilter.zig").FeeFilterMessage;
pub const SendCmpctMessage = @import("sendcmpct.zig").SendCmpctMessage;
pub const FilterClearMessage = @import("filterclear.zig").FilterClearMessage;

pub const MessageTypes = enum {
Expand All @@ -17,6 +18,7 @@ pub const MessageTypes = enum {
getblocks,
ping,
pong,
sendcmpct,
feefilter,
filterclear,
};
Expand All @@ -29,6 +31,7 @@ pub const Message = union(MessageTypes) {
getblocks: GetblocksMessage,
ping: PingMessage,
pong: PongMessage,
sendcmpct: SendCmpctMessage,
feefilter: FeeFilterMessage,
filterclear: FilterClearMessage,

Expand All @@ -41,6 +44,7 @@ pub const Message = union(MessageTypes) {
.getblocks => |m| @TypeOf(m).name(),
.ping => |m| @TypeOf(m).name(),
.pong => |m| @TypeOf(m).name(),
.sendcmpct => |m| @TypeOf(m).name(),
.feefilter => |m| @TypeOf(m).name(),
.filterclear => |m| @TypeOf(m).name(),
};
Expand All @@ -55,6 +59,7 @@ pub const Message = union(MessageTypes) {
.getblocks => |m| m.deinit(allocator),
.ping => {},
.pong => {},
.sendcmpct => {},
.feefilter => {},
.filterclear => {},
}
Expand All @@ -69,6 +74,7 @@ pub const Message = union(MessageTypes) {
.getblocks => |m| m.checksum(),
.ping => |m| m.checksum(),
.pong => |m| m.checksum(),
.sendcmpct => |m| m.checksum(),
.feefilter => |m| m.checksum(),
.filterclear => |m| m.checksum(),
};
Expand All @@ -83,6 +89,7 @@ pub const Message = union(MessageTypes) {
.getblocks => |m| m.hintSerializedLen(),
.ping => |m| m.hintSerializedLen(),
.pong => |m| m.hintSerializedLen(),
.sendcmpct => |m| m.hintSerializedLen(),
.feefilter => |m| m.hintSerializedLen(),
.filterclear => |m| m.hintSerializedLen(),
};
Expand Down
104 changes: 104 additions & 0 deletions src/network/protocol/messages/sendcmpct.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const std = @import("std");
const protocol = @import("../lib.zig");

const Sha256 = std.crypto.hash.sha2.Sha256;

const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint;

/// SendCmpctMessage represents the "sendcmpct" message
///
/// https://developer.bitcoin.org/reference/p2p_networking.html#sendcmpct
pub const SendCmpctMessage = struct {
announce: bool,
version: u64,

pub fn name() *const [12]u8 {
return protocol.CommandNames.SENDCMPCT ++ [_]u8{0} ** 3;
}

/// Returns the message checksum
///
/// Computed as `Sha256(Sha256(self.serialize()))[0..4]`
pub fn checksum(self: *const SendCmpctMessage) [4]u8 {
var digest: [32]u8 = undefined;
var hasher = Sha256.init(.{});
const writer = hasher.writer();
self.serializeToWriter(writer) catch unreachable; // Sha256.write is infallible
hasher.final(&digest);

Sha256.hash(&digest, &digest, .{});

return digest[0..4].*;
}
/// Serialize the message as bytes and write them to the Writer.
pub fn serializeToWriter(self: *const SendCmpctMessage, w: anytype) !void {
comptime {
if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects writer to have 'writeInt'.");
}
// Write announce (1 byte)
try w.writeInt(u8, if (self.announce) 0x01 else 0x00, .little);
// Write version (8 bytes, little-endian)
try w.writeInt(u64, self.version, .little);
}

/// Serialize a message as bytes and return them.
pub fn serialize(self: *const SendCmpctMessage, allocator: std.mem.Allocator) ![]u8 {
const serialized_len = self.hintSerializedLen();

const buffer = try allocator.alloc(u8, serialized_len);
errdefer allocator.free(buffer);

var fbs = std.io.fixedBufferStream(buffer);
try self.serializeToWriter(fbs.writer());

return buffer;
}
pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !SendCmpctMessage {
_ = allocator;
comptime {
if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects reader to have 'readInt'.");
}

var msg: SendCmpctMessage = undefined;

// Read announce (1 byte)
const announce_byte = try r.readByte();

msg.announce = announce_byte != 0x00;

// Read version (8 bytes, little-endian)
msg.version = try r.readInt(u64, .little);

return msg;
}

pub fn hintSerializedLen(self: *const SendCmpctMessage) usize {
_ = self;
return 1 + 8;
}
// Equality check
pub fn eql(self: *const SendCmpctMessage, other: *const SendCmpctMessage) bool {
return self.announce == other.announce and self.version == other.version;
}
};
// TESTS

test "ok_full_flow_SendCmpctMessage" {
const allocator = std.testing.allocator;

const msg = SendCmpctMessage{
.announce = true,
.version = 1,
};

// Serialize the message
const payload = try msg.serialize(allocator);
defer allocator.free(payload);

// Deserialize the message
var fbs = std.io.fixedBufferStream(payload);
const reader = fbs.reader();
const deserialized_msg = try SendCmpctMessage.deserializeReader(allocator, reader);

try std.testing.expect(msg.eql(&deserialized_msg));
}
31 changes: 31 additions & 0 deletions src/network/wire/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ pub fn receiveMessage(
protocol.messages.Message{ .ping = try protocol.messages.PingMessage.deserializeReader(allocator, r) }
else if (std.mem.eql(u8, &command, protocol.messages.PongMessage.name()))
protocol.messages.Message{ .pong = try protocol.messages.PongMessage.deserializeReader(allocator, r) }
else if (std.mem.eql(u8, &command, protocol.messages.SendCmpctMessage.name()))
protocol.messages.Message{ .sendcmpct = try protocol.messages.SendCmpctMessage.deserializeReader(allocator, r) }
else if (std.mem.eql(u8, &command, protocol.messages.FilterClearMessage.name()))
protocol.messages.Message{ .filterclear = try protocol.messages.FilterClearMessage.deserializeReader(allocator, r) }
else {
Expand Down Expand Up @@ -455,3 +457,32 @@ test "ko_receive_invalid_command" {

try std.testing.expectError(error.UnknownMessage, receiveMessage(test_allocator, reader, network_id));
}

test "ok_send_sendcmpct_message" {
const Config = @import("../../config/config.zig").Config;
const ArrayList = std.ArrayList;
const test_allocator = std.testing.allocator;
const SendCmpctMessage = protocol.messages.SendCmpctMessage;

var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator);
defer list.deinit();

const message = SendCmpctMessage{
.announce = true,
.version = 1,
};

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) {
.sendcmpct => |sendcmpct_message| try std.testing.expect(message.eql(&sendcmpct_message)),
else => unreachable,
}
}

0 comments on commit e169cc9

Please sign in to comment.