Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PING message #120

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 55 additions & 24 deletions src/config/config.zig
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
const std = @import("std");

const dns_seed = [:0]const u8;

const DNS_SEEDS = [3]dns_seed{
"seed.bitcoin.sipa.be",
"seed.bitcoin.sprovoost.nl",
"seed.btc.petertodd.net",
};
const DnsSeed = struct { inner: [:0]const u8 };

/// Global configuration for the node
///
/// This is loaded from the `bitcoin.conf` file
/// Must be loaded before any other modules are used.
/// Must be compatible with Bitcoin Core's `bitcoin.conf` format.
pub const Config = struct {
allocator: std.mem.Allocator,
const Self = @This();
/// Protocol version
pub const PROTOCOL_VERSION: i32 = 70015;

/// RPC port
rpc_port: u16,

/// P2P port
p2p_port: u16,
/// Known network ids
pub const BitcoinNetworkId = struct {
pub const MAINNET: [4]u8 = .{ 0xf9, 0xbe, 0xb4, 0xd9 };
pub const REGTEST: [4]u8 = .{ 0xfa, 0xbf, 0xd5, 0xda };
pub const TESTNET3: [4]u8 = .{ 0x0b, 0x11, 0x09, 0x07 };
pub const SIGNET: [4]u8 = .{ 0x0a, 0x03, 0xcf, 0x40 };
};

/// Testnet flag
testnet: bool,
const DNS_SEEDS = [1]DnsSeed{
.{ .inner = "seed.bitcoin.sipa.be" },
// Those are two other seeds that we will keep here for later.
// We are still building and I don't want to spam the whole network everytime I reboot.
// "seed.bitcoin.sprovoost.nl",
// "seed.btc.petertodd.net",
};

allocator: std.mem.Allocator,
/// RPC port
rpc_port: u16 = 8332,
/// P2P port
p2p_port: u16 = 8333,
/// Data directory
datadir: [:0]const u8,
datadir: [:0]const u8 = ".bitcoin",
/// Services supported
services: u64 = 0,
/// Protocol version supported
protocol_version: i32 = PROTOCOL_VERSION,
/// Network Id
network_id: [4]u8 = BitcoinNetworkId.MAINNET,

/// Load the configuration from a file
///
Expand All @@ -48,10 +62,6 @@ pub const Config = struct {

var config = Config{
.allocator = allocator,
.rpc_port = 8332,
.p2p_port = 8333,
.testnet = false,
.datadir = ".bitcoin",
};

var buf: [1024]u8 = undefined;
Expand All @@ -64,21 +74,42 @@ pub const Config = struct {
config.rpc_port = try std.fmt.parseInt(u16, value, 10);
} else if (std.mem.eql(u8, key, "port")) {
config.p2p_port = try std.fmt.parseInt(u16, value, 10);
} else if (std.mem.eql(u8, key, "testnet")) {
config.testnet = std.mem.eql(u8, value, "1");
} else if (std.mem.eql(u8, key, "network")) {
if (std.mem.eql(u8, value, &BitcoinNetworkId.MAINNET)) {
config.network_id = BitcoinNetworkId.MAINNET;
} else if (std.mem.eql(u8, value, &BitcoinNetworkId.REGTEST)) {
config.network_id = BitcoinNetworkId.REGTEST;
} else if (std.mem.eql(u8, value, &BitcoinNetworkId.TESTNET3)) {
config.network_id = BitcoinNetworkId.TESTNET3;
} else if (std.mem.eql(u8, value, &BitcoinNetworkId.SIGNET)) {
config.network_id = BitcoinNetworkId.SIGNET;
} else {
return error.UnknownNetworkId;
}
} else if (std.mem.eql(u8, key, "datadir")) {
config.datadir = try allocator.dupeZ(u8, value);
} else if (std.mem.eql(u8, key, "services")) {
config.services = try std.fmt.parseInt(u64, value, 10);
} else if (std.mem.eql(u8, key, "protocol")) {
config.protocol_version = try std.fmt.parseInt(i32, value, 10);
}
}

return config;
}

pub inline fn dnsSeeds() [3]dns_seed {
pub inline fn dnsSeeds(self: *const Self) [1]DnsSeed {
_ = self;
return DNS_SEEDS;
}

pub fn deinit(self: *Config) void {
pub inline fn bestBlock(self: *const Self) i32 {
_ = self;
// Should probably read it from db in the future
return 0;
}

pub fn deinit(self: *Self) void {
self.allocator.free(self.datadir);
}
};
4 changes: 3 additions & 1 deletion src/core/mempool.zig
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ test "Mempool" {
.allocator = allocator,
.rpc_port = 8332,
.p2p_port = 8333,
.testnet = false,
.protocol_version = Config.PROTOCOL_VERSION,
.network_id = Config.BitcoinNetworkId.MAINNET,
.services = 1,
.datadir = "/tmp/btczee",
};
var mempool = try Mempool.init(allocator, &config);
Expand Down
30 changes: 13 additions & 17 deletions src/network/p2p.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
//! It can receive and send messages to other nodes, based on the Bitcoin protocol.
const std = @import("std");
const net = std.net;
const posix = std.posix;
const Config = @import("../config/config.zig").Config;
const Peer = @import("peer.zig").Peer;
const Logger = @import("../util/trace/log.zig").Logger;
const wire = @import("wire/lib.zig");
const protocol = @import("protocol/lib.zig");
const VersionMessage = protocol.messages.VersionMessage;
const NetworkAddress = protocol.NetworkAddress;

/// P2P network handler.
pub const P2P = struct {
Expand Down Expand Up @@ -48,24 +53,15 @@ pub const P2P = struct {
pub fn start(self: *P2P) !void {
self.logger.infof("Starting P2P network on port {}", .{self.config.p2p_port});

// TODO: Implement the P2P network handler
// Initialize the listener
// const address = try net.Address.parseIp4("0.0.0.0", self.config.p2p_port);
// std.debug.panic("{any}", .{address});
// const stream = try net.tcpConnectToAddress(address);

// self.listener = net.Server{
// .listen_address = address,
// .stream = stream,
// };

// // Start accepting connections
// try self.acceptConnections();

// // Connect to seed nodes
// try self.connectToSeedNodes();
for (self.config.dnsSeeds()) |seed| {
const address_list = try std.net.getAddressList(self.allocator, seed.inner, 8333);
for (address_list.addrs[0..5]) |address| {
const peer = Peer.init(self.allocator, self.config, address) catch continue;
try self.peers.append(peer);
peer.start(true) catch continue;
}
}
}

/// Accept incoming connections.
/// The P2P network handler will accept incoming connections and handle them in a separate thread.
fn acceptConnections(self: *P2P) !void {
Expand Down
145 changes: 61 additions & 84 deletions src/network/peer.zig
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
const std = @import("std");
const net = std.net;
const protocol = @import("./protocol/lib.zig");
const wire = @import("./wire/lib.zig");
const Config = @import("../config/config.zig").Config;

const PeerError = error{
WeOnlySupportIPV6ForNow,
};

/// Represents a peer connection in the Bitcoin network
pub const Peer = struct {
allocator: std.mem.Allocator,
config: *const Config,
stream: net.Stream,
address: net.Address,
version: ?protocol.messages.VersionMessage,
protocol_version: ?i32 = null,
services: ?u64 = null,
last_seen: i64,
is_outbound: bool,

/// Initialize a new peer
pub fn init(allocator: std.mem.Allocator, connection: net.Server.Connection) !*Peer {
pub fn init(allocator: std.mem.Allocator, config: *const Config, address: std.net.Address) !*Peer {
if (address.any.family != std.posix.AF.INET6) {
return error.WeOnlySupportIPV6ForNow;
}

const stream = try std.net.tcpConnectToAddress(address);
const peer = try allocator.create(Peer);

peer.* = .{
.allocator = allocator,
.stream = connection.stream,
.address = connection.address,
.version = null,
.config = config,
.stream = stream,
.address = address,
.last_seen = std.time.timestamp(),
.is_outbound = false,
};
return peer;
}
Expand All @@ -32,90 +44,55 @@ pub const Peer = struct {
}

/// Start peer operations
pub fn start(self: *Peer) !void {
pub fn start(self: *Peer, is_outbound: bool) !void {
std.log.info("Starting peer connection with {}", .{self.address});

try self.sendVersionMessage();
try self.handleMessages();
}

/// Send version message to peer
fn sendVersionMessage(self: *Peer) !void {
const version_msg = protocol.VersionMessage{
.version = 70015,
.services = 1,
.timestamp = @intCast(std.time.timestamp()),
.addr_recv = protocol.NetworkAddress.init(self.address),
};

try self.sendMessage("version", version_msg);
}

/// Handle incoming messages from peer
fn handleMessages(self: *Peer) !void {
var buffer: [1024]u8 = undefined;

while (true) {
const bytes_read = try self.stream.read(&buffer);
if (bytes_read == 0) break; // Connection closed

// Mock message parsing
const message_type = self.parseMessageType(buffer[0..bytes_read]);
try self.handleMessage(message_type, buffer[0..bytes_read]);

self.last_seen = std.time.timestamp();
}
}

/// Mock function to parse message type
fn parseMessageType(self: *Peer, data: []const u8) []const u8 {
_ = self;
if (std.mem.startsWith(u8, data, "version")) {
return "version";
} else if (std.mem.startsWith(u8, data, "verack")) {
return "verack";
if (is_outbound) {
try self.negociateProtocolOutboundConnection();
} else {
return "unknown";
// Not implemented yet
unreachable;
}
}

/// Handle a specific message type
fn handleMessage(self: *Peer, message_type: []const u8, data: []const u8) !void {
if (std.mem.eql(u8, message_type, "version")) {
try self.handleVersionMessage(data);
} else if (std.mem.eql(u8, message_type, "verack")) {
try self.handleVerackMessage();
} else {
std.log.warn("Received unknown message type from peer", .{});
}
}

/// Handle version message
fn handleVersionMessage(self: *Peer, data: []const u8) !void {
_ = data; // In a real implementation, parse the version message

// Mock version message handling
self.version = protocol.VersionMessage{
.version = 70015,
.services = 1,
.timestamp = @intCast(std.time.timestamp()),
.addr_recv = protocol.NetworkAddress.init(self.address),
// ... other fields ...
};

try self.sendMessage("verack", {});
}
fn negociateProtocolOutboundConnection(self: *Peer) !void {
try self.sendVersionMessage();

/// Handle verack message
fn handleVerackMessage(self: *Peer) !void {
std.log.info("Received verack from peer {}", .{self.address});
// In a real implementation, mark the connection as established
while (true) {
const received_message = wire.receiveMessage(self.allocator, self.stream.reader()) catch |e| {
switch (e) {
error.EndOfStream, error.UnknownMessage => continue,
else => return e,
}
};

switch (received_message) {
.Version => {
self.protocol_version = @min(self.config.protocol_version, received_message.Version.version);
self.services = received_message.Version.trans_services;
},

.Verack => return,
else => return error.InvalidHandshake,
}
}
}

/// Send a message to the peer
fn sendMessage(self: *Peer, command: []const u8, message: anytype) !void {
_ = message;
// In a real implementation, serialize the message and send it
try self.stream.writer().print("{s}\n", .{command});
/// Send version message to peer
fn sendVersionMessage(self: *Peer) !void {
const message = protocol.messages.VersionMessage.new(
self.config.protocol_version,
.{ .ip = std.mem.zeroes([16]u8), .port = 0, .services = self.config.services },
.{ .ip = self.address.in6.sa.addr, .port = self.address.in6.getPort(), .services = 0 },
std.crypto.random.int(u64),
self.config.bestBlock(),
);

try wire.sendMessage(
self.allocator,
self.stream.writer(),
self.config.protocol_version,
self.config.network_id,
message,
);
}
};
3 changes: 3 additions & 0 deletions src/network/protocol/NetworkAddress.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ip: [16]u8,
port: u16,
services: u64,
12 changes: 1 addition & 11 deletions src/network/protocol/lib.zig
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
pub const messages = @import("./messages/lib.zig");

/// Known network ids
pub const BitcoinNetworkId = struct {
pub const MAINNET: [4]u8 = .{ 0xd9, 0xb4, 0xbe, 0xf9 };
pub const REGTEST: [4]u8 = 0xdab5bffa;
pub const TESTNET3: [4]u8 = 0x0709110b;
pub const SIGNET: [4]u8 = 0x40cf030a;
};

/// Protocol version
pub const PROTOCOL_VERSION: i32 = 70015;
pub const NetworkAddress = @import("NetworkAddress.zig");

/// Network services
pub const ServiceFlags = struct {
Expand Down
Loading
Loading