Skip to content

Commit

Permalink
feat: implemented block message (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
gdnathan authored Oct 1, 2024
1 parent 0e87d6b commit 110cbde
Show file tree
Hide file tree
Showing 20 changed files with 694 additions and 204 deletions.
16 changes: 8 additions & 8 deletions src/core/mempool.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const std = @import("std");
const Config = @import("../config/config.zig").Config;
const tx = @import("../types/transaction.zig");
const tx = @import("../types/lib.zig");

/// Transaction descriptor containing a transaction in the mempool along with additional metadata.
const TxDesc = struct {
Expand Down Expand Up @@ -74,15 +74,15 @@ pub const Mempool = struct {
.added_time = std.time.milliTimestamp(),
.height = height,
.fee = fee,
.fee_per_kb = @divTrunc(fee * 1000, @as(i64, @intCast(transaction.virtual_size()))),
.fee_per_kb = @divTrunc(fee * 1000, @as(i64, @intCast(transaction.hintEncodedLen()))),
.starting_priority = try self.calculatePriority(transaction, height),
};

// Add the transaction to the pool
try self.pool.put(hash, tx_desc);

// Add the transaction outpoints to the outpoints map
for (transaction.inputs.items) |input| {
for (transaction.inputs) |input| {
try self.outpoints.put(input.previous_outpoint, transaction);
}

Expand All @@ -102,7 +102,7 @@ pub const Mempool = struct {

if (remove_redeemers) {
// Remove any transactions which rely on this one
for (tx_desc.tx.outputs.items, 0..) |_, i| {
for (tx_desc.tx.outputs, 0..) |_, i| {
const outpoint = tx.OutPoint{ .hash = hash, .index = @as(u32, @intCast(i)) };
if (self.outpoints.get(outpoint)) |redeemer| {
self.removeTransaction(redeemer.hash(), true);
Expand All @@ -114,7 +114,7 @@ pub const Mempool = struct {
_ = self.pool.remove(hash);

// Remove the outpoints from the outpoints map
for (tx_desc.tx.inputs.items) |input| {
for (tx_desc.tx.inputs) |input| {
_ = self.outpoints.remove(input.previous_outpoint);
}

Expand All @@ -136,7 +136,7 @@ pub const Mempool = struct {
fn calculatePriority(self: *Mempool, transaction: *tx.Transaction, height: i32) !f64 {
_ = self;
var priority: f64 = 0;
for (transaction.inputs.items) |input| {
for (transaction.inputs) |input| {
// TODO: Fetch the UTXO from the chain
_ = input;
const utxo = .{ .value = 1000, .height = 100 };
Expand All @@ -145,7 +145,7 @@ pub const Mempool = struct {
priority += @as(f64, @floatFromInt(input_value)) * input_age;
}

priority /= @as(f64, @floatFromInt(transaction.virtual_size()));
priority /= @as(f64, @floatFromInt(transaction.hintEncodedLen()));

return priority;
}
Expand Down Expand Up @@ -197,7 +197,7 @@ test "Mempool" {
// Create a mock transaction
var transaction = try tx.Transaction.init(allocator);
defer transaction.deinit();
try transaction.addInput(tx.OutPoint{ .hash = tx.Hash.zero(), .index = 0 });
try transaction.addInput(tx.OutPoint{ .hash = tx.Hash.newZeroed(), .index = 0 });
try transaction.addOutput(50000, try tx.Script.init(allocator));

// Add the transaction to the mempool
Expand Down
2 changes: 1 addition & 1 deletion src/network/peer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pub const Peer = struct {
// TODO: Implement logic to filter transactions based on the received feerate
},
// TODO: handle other messages correctly
else => |m| {
else => |*m| {
std.log.info("Peer {any} sent a `{s}` message", .{ self.address, m.name() });
continue;
},
Expand Down
1 change: 0 additions & 1 deletion src/network/protocol/lib.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub const messages = @import("./messages/lib.zig");
pub const NetworkAddress = @import("NetworkAddress.zig");
pub const BlockHeader = @import("../../types/BlockHeader.zig").BlockHeader;
/// Network services
pub const ServiceFlags = struct {
pub const NODE_NETWORK: u64 = 0x1;
Expand Down
185 changes: 185 additions & 0 deletions src/network/protocol/messages/block.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
const std = @import("std");
const native_endian = @import("builtin").target.cpu.arch.endian();
const protocol = @import("../lib.zig");

const ServiceFlags = protocol.ServiceFlags;

const readBytesExact = @import("../../../util/mem/read.zig").readBytesExact;

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

const CompactSizeUint = @import("bitcoin-primitives").types.CompatSizeUint;
const Types = @import("../../../types/lib.zig");
const Transaction = Types.Transaction;
const BlockHeader = Types.BlockHeader;

/// BlockMessage represents the "block" message
///
/// https://developer.bitcoin.org/reference/p2p_networking.html#block
pub const BlockMessage = struct {
block_header: BlockHeader,
txns: []Transaction,

const Self = @This();

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

pub fn checksum(self: BlockMessage) [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].*;
}

pub fn deinit(self: *BlockMessage, allocator: std.mem.Allocator) void {
for (self.txns) |*txn| {
txn.deinit();
}
allocator.free(self.txns);
}

/// 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), "writeInt")) @compileError("Expects r to have fn 'writeInt'.");
if (!std.meta.hasFn(@TypeOf(w), "writeAll")) @compileError("Expects r to have fn 'writeAll'.");
}

try self.block_header.serializeToWriter(w);

try CompactSizeUint.new(self.txns.len).encodeToWriter(w);

for (self.txns) |txn| {
try txn.serializeToWriter(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 BlockMessage, 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;
}

pub fn deserializeReader(allocator: std.mem.Allocator, r: anytype) !BlockMessage {
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'.");
if (!std.meta.hasFn(@TypeOf(r), "readAll")) @compileError("Expects r to have fn 'readAll'.");
if (!std.meta.hasFn(@TypeOf(r), "readByte")) @compileError("Expects r to have fn 'readByte'.");
}

var block_message: Self = undefined;

block_message.block_header = try BlockHeader.deserializeReader(r);

const txns_count = try CompactSizeUint.decodeReader(r);

block_message.txns = try allocator.alloc(Transaction, txns_count.value());
errdefer allocator.free(block_message.txns);

var i: usize = 0;
while (i < txns_count.value()) : (i += 1) {
const tx = try Transaction.deserializeReader(allocator, r);
block_message.txns[i] = tx;
}

return block_message;
}

/// Deserialize bytes into a `VersionMessage`
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: BlockMessage) usize {
const header_length = BlockHeader.serializedLen();
const txs_number_length = CompactSizeUint.new(self.txns.len).hint_encoded_len();
var txs_length: usize = 0;
for (self.txns) |txn| {
txs_length += txn.hintEncodedLen();
}
return header_length + txs_number_length + txs_length;
}
};

// TESTS

test "ok_full_flow_BlockMessage" {
const OpCode = @import("../../../script/opcodes/constant.zig").Opcode;
const allocator = std.testing.allocator;
const OutPoint = Types.OutPoint;
const Hash = Types.Hash;
const Script = Types.Script;

{
var tx = try Transaction.init(allocator);

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_0.toBytes(), OpCode.OP_1.toBytes() });
try tx.addOutput(50000, script_pubkey);
}

var txns = try allocator.alloc(Transaction, 1);
// errdefer allocator.free(txns);
txns[0] = tx;

var msg = BlockMessage{
.block_header = BlockHeader{
.version = 1,
.prev_block = [_]u8{0} ** 32,
.merkle_root = [_]u8{0} ** 32,
.timestamp = 1,
.nbits = 1,
.nonce = 1,
},
.txns = txns,
};
defer msg.deinit(allocator);

const payload = try msg.serialize(allocator);
defer allocator.free(payload);
var deserialized_msg = try BlockMessage.deserializeSlice(allocator, payload);
defer deserialized_msg.deinit(allocator);

try std.testing.expectEqual(msg.block_header.version, deserialized_msg.block_header.version);
try std.testing.expect(std.mem.eql(u8, &msg.block_header.prev_block, &deserialized_msg.block_header.prev_block));
try std.testing.expect(std.mem.eql(u8, &msg.block_header.merkle_root, &deserialized_msg.block_header.merkle_root));
try std.testing.expect(msg.block_header.timestamp == deserialized_msg.block_header.timestamp);
try std.testing.expect(msg.block_header.nbits == deserialized_msg.block_header.nbits);
try std.testing.expect(msg.block_header.nonce == deserialized_msg.block_header.nonce);

for (msg.txns, 0..) |txn, i| {
const deserialized_txn = deserialized_msg.txns[i];
try std.testing.expect(txn.eql(deserialized_txn));
}
}
}
2 changes: 1 addition & 1 deletion src/network/protocol/messages/getblocks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub const GetblocksMessage = struct {
}

/// Free the `header_hashes`
pub fn deinit(self: GetblocksMessage, allocator: std.mem.Allocator) void {
pub fn deinit(self: *GetblocksMessage, allocator: std.mem.Allocator) void {
allocator.free(self.header_hashes);
}

Expand Down
Loading

0 comments on commit 110cbde

Please sign in to comment.