diff --git a/src/core/hash.zig b/src/core/hash.zig index b6c89ac60..c33b65c97 100644 --- a/src/core/hash.zig +++ b/src/core/hash.zig @@ -3,6 +3,12 @@ const Sha256 = std.crypto.hash.sha2.Sha256; pub const HASH_SIZE: usize = 32; +pub const CompareResult = enum { + Greater, + Less, + Equal, +}; + pub const Hash = struct { data: [HASH_SIZE]u8, @@ -15,4 +21,15 @@ pub const Hash = struct { Sha256.hash(bytes, &hash.data, .{}); return hash; } + + pub fn cmp(a: *const Self, b: *const Self) CompareResult { + for (0..HASH_SIZE) |i| { + if (a.data[i] > b.data[i]) { + return CompareResult.Greater; + } else if (a.data[i] < b.data[i]) { + return CompareResult.Less; + } + } + return CompareResult.Equal; + } }; diff --git a/src/core/transaction.zig b/src/core/transaction.zig index 008add5f9..912d0fe55 100644 --- a/src/core/transaction.zig +++ b/src/core/transaction.zig @@ -9,6 +9,14 @@ pub const Transaction = struct { message: Message, pub const @"!bincode-config:signatures" = shortvec_config; + + // used in tests + pub fn default() Transaction { + return Transaction{ + .signatures = &[_]Signature{}, + .message = Message.default(), + }; + } }; pub const Message = struct { @@ -19,6 +27,19 @@ pub const Message = struct { pub const @"!bincode-config:account_keys" = shortvec_config; pub const @"!bincode-config:instructions" = shortvec_config; + + pub fn default() Message { + return Message{ + .header = MessageHeader{ + .num_required_signatures = 0, + .num_readonly_signed_accounts = 0, + .num_readonly_unsigned_accounts = 0, + }, + .account_keys = &[_]Pubkey{}, + .recent_blockhash = Hash.generateSha256Hash(&[_]u8{0}), + .instructions = &[_]CompiledInstruction{}, + }; + } }; pub const MessageHeader = struct { @@ -48,3 +69,8 @@ pub const CompiledInstruction = struct { pub const @"!bincode-config:accounts" = shortvec_config; pub const @"!bincode-config:data" = shortvec_config; }; + +test "core.transaction: tmp" { + const msg = Message.default(); + try std.testing.expect(msg.account_keys.len == 0); +} diff --git a/src/gossip/crds.zig b/src/gossip/crds.zig index 804748ff0..9d0e717d0 100644 --- a/src/gossip/crds.zig +++ b/src/gossip/crds.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const Pubkey = @import("../core/pubkey.zig").Pubkey; const SocketAddr = @import("net.zig").SocketAddr; const Tuple = std.meta.Tuple; const Hash = @import("../core/hash.zig").Hash; @@ -9,26 +8,11 @@ const Slot = @import("../core/slot.zig").Slot; const Option = @import("../option.zig").Option; const ContactInfo = @import("node.zig").ContactInfo; const bincode = @import("bincode-zig"); -const AutoArrayHashMap = std.AutoArrayHashMap; const ArrayList = std.ArrayList; const ArrayListConfig = @import("../utils/arraylist.zig").ArrayListConfig; const Bloom = @import("../bloom/bloom.zig").Bloom; const KeyPair = std.crypto.sign.Ed25519.KeyPair; - -/// Cluster Replicated Data Store -pub const Crds = struct { - store: AutoArrayHashMap(CrdsValueLabel, CrdsVersionedValue), - - const Self = @This(); - - pub fn init(allocator: std.mem.Allocator) Self { - return Self{ .store = AutoArrayHashMap(CrdsValueLabel, CrdsVersionedValue).init(allocator) }; - } - - pub fn deinit(self: *Self) void { - self.store.deinit(); - } -}; +const Pubkey = @import("../core/pubkey.zig").Pubkey; pub const CrdsFilter = struct { filter: Bloom, @@ -51,6 +35,7 @@ pub const CrdsFilter = struct { }; pub const CrdsVersionedValue = struct { + ordinal: u64, value: CrdsValue, local_timestamp: u64, value_hash: Hash, @@ -92,6 +77,129 @@ pub const CrdsValue = struct { var msg = try bincode.writeToSlice(buf[0..], self.data, bincode.Params.standard); return self.signature.verify(pubkey, msg); } + + pub fn id(self: *const Self) Pubkey { + return switch (self.data) { + .LegacyContactInfo => |*v| { + return v.id; + }, + .Vote => |*v| { + return v[1].from; + }, + .LowestSlot => |*v| { + return v[1].from; + }, + .LegacySnapshotHashes => |*v| { + return v.from; + }, + .AccountsHashes => |*v| { + return v.from; + }, + .EpochSlots => |*v| { + return v[1].from; + }, + .LegacyVersion => |*v| { + return v.from; + }, + .Version => |*v| { + return v.from; + }, + .NodeInstance => |*v| { + return v.from; + }, + .DuplicateShred => |*v| { + return v[1].from; + }, + .SnapshotHashes => |*v| { + return v.from; + }, + .ContactInfo => |*v| { + return v.pubkey; + }, + }; + } + + pub fn wallclock(self: *const Self) u64 { + return switch (self.data) { + .LegacyContactInfo => |*v| { + return v.wallclock; + }, + .Vote => |*v| { + return v[1].wallclock; + }, + .LowestSlot => |*v| { + return v[1].wallclock; + }, + .LegacySnapshotHashes => |*v| { + return v.wallclock; + }, + .AccountsHashes => |*v| { + return v.wallclock; + }, + .EpochSlots => |*v| { + return v[1].wallclock; + }, + .LegacyVersion => |*v| { + return v.wallclock; + }, + .Version => |*v| { + return v.wallclock; + }, + .NodeInstance => |*v| { + return v.wallclock; + }, + .DuplicateShred => |*v| { + return v[1].wallclock; + }, + .SnapshotHashes => |*v| { + return v.wallclock; + }, + .ContactInfo => |*v| { + return v.wallclock; + }, + }; + } + + pub fn label(self: *const Self) CrdsValueLabel { + return switch (self.data) { + .LegacyContactInfo => { + return CrdsValueLabel{ .LegacyContactInfo = self.id() }; + }, + .Vote => |*v| { + return CrdsValueLabel{ .Vote = .{ v[0], self.id() } }; + }, + .LowestSlot => { + return CrdsValueLabel{ .LowestSlot = self.id() }; + }, + .LegacySnapshotHashes => { + return CrdsValueLabel{ .LegacySnapshotHashes = self.id() }; + }, + .AccountsHashes => { + return CrdsValueLabel{ .AccountsHashes = self.id() }; + }, + .EpochSlots => |*v| { + return CrdsValueLabel{ .EpochSlots = .{ v[0], self.id() } }; + }, + .LegacyVersion => { + return CrdsValueLabel{ .LegacyVersion = self.id() }; + }, + .Version => { + return CrdsValueLabel{ .Version = self.id() }; + }, + .NodeInstance => { + return CrdsValueLabel{ .NodeInstance = self.id() }; + }, + .DuplicateShred => |*v| { + return CrdsValueLabel{ .DuplicateShred = .{ v[0], self.id() } }; + }, + .SnapshotHashes => { + return CrdsValueLabel{ .SnapshotHashes = self.id() }; + }, + .ContactInfo => { + return CrdsValueLabel{ .ContactInfo = self.id() }; + }, + }; + } }; pub const LegacyContactInfo = struct { @@ -331,6 +439,36 @@ pub const SnapshotHashes = struct { wallclock: u64, }; +test "gossip.crds: test CrdsValue label() and id() methods" { + var kp_bytes = [_]u8{1} ** 32; + var kp = try KeyPair.create(kp_bytes); + const pk = kp.public_key; + var id = Pubkey.fromPublicKey(&pk, true); + const unspecified_addr = SocketAddr.unspecified(); + var legacy_contact_info = LegacyContactInfo{ + .id = id, + .gossip = unspecified_addr, + .tvu = unspecified_addr, + .tvu_forwards = unspecified_addr, + .repair = unspecified_addr, + .tpu = unspecified_addr, + .tpu_forwards = unspecified_addr, + .tpu_vote = unspecified_addr, + .rpc = unspecified_addr, + .rpc_pubsub = unspecified_addr, + .serve_repair = unspecified_addr, + .wallclock = 0, + .shred_version = 0, + }; + + var crds_value = try CrdsValue.initSigned(CrdsData{ + .LegacyContactInfo = legacy_contact_info, + }, kp); + + try std.testing.expect(crds_value.id().equals(&id)); + try std.testing.expect(crds_value.label().LegacyContactInfo.equals(&id)); +} + test "gossip.crds: default crds filter matches rust bytes" { const rust_bytes = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0 }; var filter = CrdsFilter.init(std.testing.allocator); diff --git a/src/gossip/crds_table.zig b/src/gossip/crds_table.zig new file mode 100644 index 000000000..ba33f883a --- /dev/null +++ b/src/gossip/crds_table.zig @@ -0,0 +1,331 @@ +const std = @import("std"); +const AutoArrayHashMap = std.AutoArrayHashMap; +const AutoHashMap = std.AutoHashMap; + +const bincode = @import("bincode-zig"); + +const hash = @import("../core/hash.zig"); +const Hash = hash.Hash; +const CompareResult = hash.CompareResult; + +const SocketAddr = @import("net.zig").SocketAddr; + +const crds = @import("./crds.zig"); +const CrdsValue = crds.CrdsValue; +const CrdsData = crds.CrdsData; +const CrdsVersionedValue = crds.CrdsVersionedValue; +const CrdsValueLabel = crds.CrdsValueLabel; +const LegacyContactInfo = crds.LegacyContactInfo; + +const Transaction = @import("../core/transaction.zig").Transaction; +const Pubkey = @import("../core/pubkey.zig").Pubkey; +const KeyPair = std.crypto.sign.Ed25519.KeyPair; + +// tmp upperbound on number for `get_nodes`/`get_votes`/... +// enables stack allocations for buffers in the getter functions +const MAX_N_CONTACT_INFOS = 100; +const MAX_N_VOTES = 20; +const MAX_N_EPOCH_SLOTS = 20; +const MAX_N_DUP_SHREDS = 20; + +pub const CrdsError = error{ + OldValue, +}; + +/// Cluster Replicated Data Store: stores gossip data +/// the self.store uses an AutoArrayHashMap which is a HashMap that also allows for +/// indexing values (value = arrayhashmap[0]). This allows us to insert data +/// into the store and track the indexs of different types for +/// retrieval. We use the 'cursor' value to track what index is the head of the +/// store. +/// Other functions include getters with a cursor +/// (`get_votes_with_cursor`) which allows you to retrieve values which are +/// past a certain cursor index. A listener would use their own cursor to +/// retrieve new values inserted in the store. +/// insertion of values is all based on the CRDSLabel type -- when duplicates +/// are found, the entry with the largest wallclock time (newest) is stored. +pub const CrdsTable = struct { + store: AutoArrayHashMap(CrdsValueLabel, CrdsVersionedValue), + contact_infos: AutoArrayHashMap(usize, void), // hashset for O(1) insertion/removal + shred_versions: AutoHashMap(Pubkey, u16), + votes: AutoArrayHashMap(usize, usize), + epoch_slots: AutoArrayHashMap(usize, usize), + duplicate_shreds: AutoArrayHashMap(usize, usize), + cursor: usize, + + const Self = @This(); + + pub fn init(allocator: std.mem.Allocator) Self { + return Self{ + .store = AutoArrayHashMap(CrdsValueLabel, CrdsVersionedValue).init(allocator), + .contact_infos = AutoArrayHashMap(usize, void).init(allocator), + .shred_versions = AutoHashMap(Pubkey, u16).init(allocator), + .votes = AutoArrayHashMap(usize, usize).init(allocator), + .epoch_slots = AutoArrayHashMap(usize, usize).init(allocator), + .duplicate_shreds = AutoArrayHashMap(usize, usize).init(allocator), + .cursor = 0, + }; + } + + pub fn deinit(self: *Self) void { + self.store.deinit(); + self.contact_infos.deinit(); + self.shred_versions.deinit(); + self.votes.deinit(); + self.epoch_slots.deinit(); + self.duplicate_shreds.deinit(); + } + + pub fn insert(self: *Self, value: CrdsValue, now: u64) !void { + // TODO: check to make sure this sizing is correct or use heap + var buf = [_]u8{0} ** 2048; + var bytes = try bincode.writeToSlice(&buf, value, bincode.Params.standard); + const value_hash = Hash.generateSha256Hash(bytes); + const versioned_value = CrdsVersionedValue{ + .value = value, + .value_hash = value_hash, + .local_timestamp = now, + .num_push_dups = 0, + .ordinal = self.cursor, + }; + + const label = value.label(); + var result = try self.store.getOrPut(label); + + // entry doesnt exist + if (!result.found_existing) { + switch (value.data) { + .LegacyContactInfo => |*info| { + try self.contact_infos.put(result.index, {}); + try self.shred_versions.put(info.id, info.shred_version); + }, + .Vote => { + try self.votes.put(self.cursor, result.index); + }, + .EpochSlots => { + try self.epoch_slots.put(self.cursor, result.index); + }, + .DuplicateShred => { + try self.duplicate_shreds.put(self.cursor, result.index); + }, + else => {}, + } + + self.cursor += 1; + result.value_ptr.* = versioned_value; + + // should overwrite existing entry + } else if (crds_overwrites(&versioned_value, result.value_ptr)) { + const old_entry = result.value_ptr.*; + + switch (value.data) { + .LegacyContactInfo => |*info| { + try self.shred_versions.put(info.id, info.shred_version); + }, + .Vote => { + var did_remove = self.votes.swapRemove(old_entry.ordinal); + std.debug.assert(did_remove); + try self.votes.put(self.cursor, result.index); + }, + .EpochSlots => { + var did_remove = self.epoch_slots.swapRemove(old_entry.ordinal); + std.debug.assert(did_remove); + try self.epoch_slots.put(self.cursor, result.index); + }, + .DuplicateShred => { + var did_remove = self.duplicate_shreds.swapRemove(old_entry.ordinal); + std.debug.assert(did_remove); + try self.duplicate_shreds.put(self.cursor, result.index); + }, + else => {}, + } + + self.cursor += 1; + result.value_ptr.* = versioned_value; + + // do nothing + } else { + return CrdsError.OldValue; + } + } + + pub fn get_votes_with_cursor(self: *Self, caller_cursor: *usize) ![]*CrdsVersionedValue { + const keys = self.votes.keys(); + // initialize this buffer once in struct and re-use on each call? + var buf: [MAX_N_VOTES]*CrdsVersionedValue = undefined; // max N votes per query (20) + var index: usize = 0; + for (keys) |key| { + if (key < caller_cursor.*) { + continue; + } + const entry_index = self.votes.get(key).?; + var entry = self.store.iterator().values[entry_index]; + buf[index] = &entry; + index += 1; + + if (index == MAX_N_VOTES) { + break; + } + } + // move up the caller_cursor + caller_cursor.* += index; + return buf[0..index]; + } + + pub fn get_epoch_slots_with_cursor(self: *Self, caller_cursor: *usize) ![]*CrdsVersionedValue { + const keys = self.epoch_slots.keys(); + var buf: [MAX_N_EPOCH_SLOTS]*CrdsVersionedValue = undefined; + var index: usize = 0; + for (keys) |key| { + if (key < caller_cursor.*) { + continue; + } + const entry_index = self.epoch_slots.get(key).?; + var entry = self.store.iterator().values[entry_index]; + buf[index] = &entry; + index += 1; + + if (index == MAX_N_EPOCH_SLOTS) { + break; + } + } + // move up the caller_cursor + caller_cursor.* += index; + return buf[0..index]; + } + + pub fn get_duplicate_shreds_with_cursor(self: *Self, caller_cursor: *usize) ![]*CrdsVersionedValue { + const keys = self.duplicate_shreds.keys(); + var buf: [MAX_N_DUP_SHREDS]*CrdsVersionedValue = undefined; + var index: usize = 0; + for (keys) |key| { + if (key < caller_cursor.*) { + continue; + } + const entry_index = self.duplicate_shreds.get(key).?; + var entry = self.store.iterator().values[entry_index]; + buf[index] = &entry; + index += 1; + + if (index == MAX_N_DUP_SHREDS) { + break; + } + } + // move up the caller_cursor + caller_cursor.* += index; + return buf[0..index]; + } + + pub fn get_contact_infos(self: *const Self) ![]*CrdsVersionedValue { + var entry_ptrs: [MAX_N_CONTACT_INFOS]*CrdsVersionedValue = undefined; + const size = @min(self.contact_infos.count(), MAX_N_CONTACT_INFOS); + const store_values = self.store.iterator().values; + const contact_indexs = self.contact_infos.iterator().keys; + for (0..size) |i| { + const index = contact_indexs[i]; + const entry = &store_values[index]; + entry_ptrs[i] = entry; + } + return entry_ptrs[0..size]; + } +}; + +pub fn crds_overwrites(new_value: *const CrdsVersionedValue, old_value: *const CrdsVersionedValue) bool { + // labels must match + std.debug.assert(@intFromEnum(new_value.value.label()) == @intFromEnum(old_value.value.label())); + + const new_ts = new_value.value.wallclock(); + const old_ts = old_value.value.wallclock(); + + if (new_ts > old_ts) { + return true; + } else if (new_ts < old_ts) { + return false; + } else { + return old_value.value_hash.cmp(&new_value.value_hash) == CompareResult.Less; + } +} + +test "gossip.crds_table: insert and get votes" { + var kp_bytes = [_]u8{1} ** 32; + const kp = try KeyPair.create(kp_bytes); + const pk = kp.public_key; + var id = Pubkey.fromPublicKey(&pk, true); + + var vote = crds.Vote{ .from = id, .transaction = Transaction.default(), .wallclock = 10 }; + var crds_value = try CrdsValue.initSigned(CrdsData{ + .Vote = .{ 0, vote }, + }, kp); + + var crds_table = CrdsTable.init(std.testing.allocator); + defer crds_table.deinit(); + try crds_table.insert(crds_value, 0); + + var cursor: usize = 0; + var votes = try crds_table.get_votes_with_cursor(&cursor); + + try std.testing.expect(votes.len == 1); + try std.testing.expect(cursor == 1); + + // try inserting another vote + id = Pubkey.random(.{}); + vote = crds.Vote{ .from = id, .transaction = Transaction.default(), .wallclock = 10 }; + crds_value = try CrdsValue.initSigned(CrdsData{ + .Vote = .{ 0, vote }, + }, kp); + try crds_table.insert(crds_value, 1); + + votes = try crds_table.get_votes_with_cursor(&cursor); + try std.testing.expect(votes.len == 1); + try std.testing.expect(cursor == 2); +} + +test "gossip.crds_table: insert and get contact_info" { + var kp_bytes = [_]u8{1} ** 32; + const kp = try KeyPair.create(kp_bytes); + const pk = kp.public_key; + var id = Pubkey.fromPublicKey(&pk, true); + const unspecified_addr = SocketAddr.unspecified(); + var legacy_contact_info = crds.LegacyContactInfo{ + .id = id, + .gossip = unspecified_addr, + .tvu = unspecified_addr, + .tvu_forwards = unspecified_addr, + .repair = unspecified_addr, + .tpu = unspecified_addr, + .tpu_forwards = unspecified_addr, + .tpu_vote = unspecified_addr, + .rpc = unspecified_addr, + .rpc_pubsub = unspecified_addr, + .serve_repair = unspecified_addr, + .wallclock = 0, + .shred_version = 0, + }; + var crds_value = try CrdsValue.initSigned(CrdsData{ + .LegacyContactInfo = legacy_contact_info, + }, kp); + + var crds_table = CrdsTable.init(std.testing.allocator); + defer crds_table.deinit(); + + // test insertion + try crds_table.insert(crds_value, 0); + + // test retrieval + var nodes = try crds_table.get_contact_infos(); + try std.testing.expect(nodes.len == 1); + try std.testing.expect(nodes[0].value.data.LegacyContactInfo.id.equals(&id)); + + // test re-insertion + const result = crds_table.insert(crds_value, 0); + try std.testing.expectError(CrdsError.OldValue, result); + + // test re-insertion with greater wallclock + crds_value.data.LegacyContactInfo.wallclock = 2; + try crds_table.insert(crds_value, 0); + + // check retrieval + nodes = try crds_table.get_contact_infos(); + try std.testing.expect(nodes.len == 1); + try std.testing.expect(nodes[0].value.data.LegacyContactInfo.wallclock == 2); +} diff --git a/src/gossip/gossip_service.zig b/src/gossip/gossip_service.zig index 45d17878f..112bf200d 100644 --- a/src/gossip/gossip_service.zig +++ b/src/gossip/gossip_service.zig @@ -14,6 +14,10 @@ const Protocol = @import("protocol.zig").Protocol; const Ping = @import("protocol.zig").Ping; const bincode = @import("bincode-zig"); const crds = @import("../gossip/crds.zig"); + +const _crds_table = @import("../gossip/crds_table.zig"); +const CrdsTable = _crds_table.CrdsTable; +const CrdsError = _crds_table.CrdsError; const Logger = @import("../trace/log.zig").Logger; var gpa_allocator = std.heap.GeneralPurposeAllocator(.{}){}; @@ -22,12 +26,19 @@ var gpa = gpa_allocator.allocator(); const PacketChannel = Channel(Packet); // const ProtocolChannel = Channel(Protocol); +const CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS: u64 = 30000; + +pub fn get_wallclock() u64 { + return @intCast(std.time.milliTimestamp()); +} + pub const GossipService = struct { cluster_info: *ClusterInfo, gossip_socket: UdpSocket, exit_sig: AtomicBool, packet_channel: PacketChannel, responder_channel: PacketChannel, + crds_table: CrdsTable, const Self = @This(); @@ -39,6 +50,7 @@ pub const GossipService = struct { ) Self { var packet_channel = PacketChannel.init(allocator, 10000); var responder_channel = PacketChannel.init(allocator, 10000); + var crds_table = CrdsTable.init(allocator); return Self{ .cluster_info = cluster_info, @@ -46,12 +58,14 @@ pub const GossipService = struct { .exit_sig = exit, .packet_channel = packet_channel, .responder_channel = responder_channel, + .crds_table = crds_table, }; } pub fn deinit(self: *Self) void { self.packet_channel.deinit(); self.responder_channel.deinit(); + self.crds_table.deinit(); } pub fn run(self: *Self, logger: *Logger) !void { @@ -105,7 +119,7 @@ pub const GossipService = struct { const gossip_endpoint = try self.gossip_socket.getLocalEndPoint(); const gossip_addr = SocketAddr.init_ipv4(gossip_endpoint.address.ipv4.value, gossip_endpoint.port); const unspecified_addr = SocketAddr.init_ipv4(.{ 0, 0, 0, 0 }, 0); - const wallclock = @as(u64, @intCast(std.time.milliTimestamp())); + const wallclock = get_wallclock(); var legacy_contact_info = crds.LegacyContactInfo{ .id = id, @@ -192,10 +206,37 @@ pub const GossipService = struct { logger.debugf("ping message verification failed...", .{}); } }, + .PushMessage => |*push| { + logger.debugf("got a push message: {any}", .{protocol_message}); + const values = push[1]; + handle_push_message(&self.crds_table, values, logger); + }, else => { logger.debugf("got a protocol message: {any}", .{protocol_message}); }, } } } + + pub fn handle_push_message(crds_table: *CrdsTable, values: []crds.CrdsValue, logger: *Logger) void { + var now = get_wallclock(); + + for (values) |value| { + const value_time = value.wallclock(); + const is_too_new = value_time > now +| CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS; + const is_too_old = value_time < now -| CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS; + if (is_too_new or is_too_old) { + continue; + } + + crds_table.insert(value, now) catch |err| switch (err) { + CrdsError.OldValue => { + logger.debugf("failed to insert into crds: {any}", .{value}); + }, + else => { + logger.debugf("failed to insert into crds with unkown error: {any}", .{err}); + }, + }; + } + } }; diff --git a/src/lib.zig b/src/lib.zig index 03621dfda..41f71a869 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -18,6 +18,7 @@ pub const gossip = struct { pub usingnamespace @import("gossip/cluster_info.zig"); pub usingnamespace @import("gossip/cmd.zig"); pub usingnamespace @import("gossip/crds.zig"); + pub usingnamespace @import("gossip/crds_table.zig"); pub usingnamespace @import("gossip/gossip_service.zig"); pub usingnamespace @import("gossip/net.zig"); pub usingnamespace @import("gossip/node.zig"); diff --git a/src/tests.zig b/src/tests.zig index 5f9104a30..a11880b6e 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2,6 +2,6 @@ const std = @import("std"); const lib = @import("lib.zig"); test { - std.testing.log_level = std.log.Level.debug; + std.testing.log_level = std.log.Level.err; std.testing.refAllDecls(lib); }