-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(shred-network): minimize dependencies (#462)
This is an alternative implementation for #423. It accomplishes the same goals, except that it implements the interface between `shred-network` and an epoch context provider with shared memory instead of a vtable. The static context of an epoch is defined by EpochContext, which contains staked nodes and a leader schedule. The storage of relevant EpochContexts is managed by EpochContextManager. This specialized struct is needed because it must be aware of the epoch schedule. Depending on the run mode, the initial EpochContext may be created from the bank or from rpc. There is currently only one way to update the EpochContextManager during the runtime of the validator, which is via rpc. There is a limitation where RPC cannot be used to access epoch staked nodes. This can only be acquired from the bank. So the bank is used when available, otherwise the data is not populated. ## commits * refactor(leader_schedule): SlotLeaderProvider from PointerClosure to direct SlotLeaders interface * refactor(sync): adapt RcSlice for single-item pointers * feat: add Window and SharedPointerWindow data structures * feat(shred-network): minimize dependencies * fix(collections): window integer overflow when realigning from 0. add test to reproduce issue and fix bug * fix(sync): shared pointer window defer lock should be unlock * fix(rpc epoch context): wrong epoch calculation
- Loading branch information
Showing
8 changed files
with
265 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//! Links dependencies with dependents. Connects components from distant regions of the code. | ||
|
||
const std = @import("std"); | ||
const sig = @import("sig.zig"); | ||
|
||
const leader_schedule = sig.core.leader_schedule; | ||
|
||
const Allocator = std.mem.Allocator; | ||
|
||
const Epoch = sig.core.Epoch; | ||
const EpochContext = sig.core.EpochContext; | ||
const EpochSchedule = sig.core.EpochSchedule; | ||
const Slot = sig.core.Slot; | ||
|
||
pub const EpochContextManager = struct { | ||
schedule: sig.core.EpochSchedule, | ||
contexts: ContextWindow, | ||
|
||
const ContextWindow = sig.sync.SharedPointerWindow( | ||
sig.core.EpochContext, | ||
sig.core.EpochContext.deinit, | ||
std.mem.Allocator, | ||
); | ||
|
||
const Self = @This(); | ||
|
||
/// all contexts that are `put` into this context manager must be | ||
/// allocated using the same allocator passed here. | ||
pub fn init(allocator: Allocator, schedule: EpochSchedule) Allocator.Error!Self { | ||
return .{ | ||
.schedule = schedule, | ||
.contexts = try ContextWindow.init(allocator, 3, 0, allocator), | ||
}; | ||
} | ||
|
||
pub fn deinit(self: Self) void { | ||
self.contexts.deinit(); | ||
} | ||
|
||
pub fn put(self: *Self, epoch: Epoch, context: sig.core.EpochContext) !void { | ||
try self.contexts.put(epoch, context); | ||
} | ||
|
||
/// call `release` when done with pointer | ||
pub fn get(self: *Self, epoch: Epoch) ?*const sig.core.EpochContext { | ||
return self.contexts.get(@intCast(epoch)); | ||
} | ||
|
||
pub fn contains(self: *Self, epoch: Epoch) bool { | ||
return self.contexts.contains(@intCast(epoch)); | ||
} | ||
|
||
pub fn setEpoch(self: *Self, epoch: Epoch) void { | ||
self.contexts.realign(@intCast(epoch)); | ||
} | ||
|
||
pub fn setSlot(self: *Self, slot: Slot) void { | ||
self.contexts.realign(@intCast(self.schedule.getEpoch(slot))); | ||
} | ||
|
||
pub fn release(self: *Self, context: *const sig.core.EpochContext) void { | ||
self.contexts.release(context); | ||
} | ||
|
||
pub fn getLeader(self: *Self, slot: Slot) ?sig.core.Pubkey { | ||
const epoch, const slot_index = self.schedule.getEpochAndSlotIndex(slot); | ||
const context = self.contexts.get(epoch) orelse return null; | ||
defer self.contexts.release(context); | ||
return context.leader_schedule[slot_index]; | ||
} | ||
|
||
pub fn slotLeaders(self: *Self) sig.core.leader_schedule.SlotLeaders { | ||
return sig.core.leader_schedule.SlotLeaders.init(self, getLeader); | ||
} | ||
}; | ||
|
||
pub const RpcEpochContextService = struct { | ||
allocator: std.mem.Allocator, | ||
logger: sig.trace.ScopedLogger(@typeName(Self)), | ||
rpc_client: sig.rpc.Client, | ||
state: *EpochContextManager, | ||
|
||
const Self = @This(); | ||
|
||
pub fn init( | ||
allocator: Allocator, | ||
logger: sig.trace.Logger, | ||
state: *EpochContextManager, | ||
rpc_client: sig.rpc.Client, | ||
) Self { | ||
return .{ | ||
.allocator = allocator, | ||
.logger = logger.withScope(@typeName(Self)), | ||
.rpc_client = rpc_client, | ||
.state = state, | ||
}; | ||
} | ||
|
||
pub fn run(self: *Self, exit: *std.atomic.Value(bool)) void { | ||
var i: usize = 0; | ||
while (!exit.load(.monotonic)) { | ||
if (i % 1000 == 0) { | ||
self.refresh() catch |e| { | ||
self.logger.err().logf("failed to refresh epoch context via rpc: {}", .{e}); | ||
}; | ||
} | ||
std.time.sleep(100 * std.time.ns_per_ms); | ||
i += 1; | ||
} | ||
} | ||
|
||
fn refresh(self: *Self) !void { | ||
const response = try self.rpc_client.getSlot(self.allocator, .{}); | ||
defer response.deinit(); | ||
const old_slot = try response.result() - self.state.schedule.slots_per_epoch; | ||
const last_epoch = self.state.schedule.getEpoch(old_slot); | ||
|
||
self.state.setEpoch(last_epoch); | ||
|
||
const ls1 = try self.getLeaderSchedule(old_slot); | ||
const ctx1 = EpochContext{ .staked_nodes = .{}, .leader_schedule = ls1 }; | ||
try self.state.put(last_epoch, ctx1); | ||
|
||
for (0..3) |epoch_offset| { | ||
const selected_slot = old_slot + epoch_offset * self.state.schedule.slots_per_epoch; | ||
const selected_epoch = last_epoch + epoch_offset; | ||
std.debug.assert(selected_epoch == self.state.schedule.getEpoch(selected_slot)); | ||
|
||
if (self.state.contains(selected_epoch)) { | ||
continue; | ||
} | ||
|
||
if (self.getLeaderSchedule(selected_slot)) |ls2| { | ||
const ctx2 = EpochContext{ .staked_nodes = .{}, .leader_schedule = ls2 }; | ||
try self.state.put(selected_epoch, ctx2); | ||
} else |e| if (selected_epoch == last_epoch) { | ||
return e; | ||
} | ||
} | ||
} | ||
|
||
fn getLeaderSchedule(self: *Self, slot: sig.core.Slot) ![]const sig.core.Pubkey { | ||
const response = try self.rpc_client.getLeaderSchedule(self.allocator, slot, .{}); | ||
defer response.deinit(); | ||
const rpc_schedule = try response.result(); | ||
const schedule = try leader_schedule.LeaderSchedule.fromMap(self.allocator, rpc_schedule); | ||
return schedule.slot_leaders; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const std = @import("std"); | ||
const core = @import("lib.zig"); | ||
|
||
/// constant data about a particular epoch. | ||
/// this can be computed before the epoch begins, and does not change during the epoch | ||
pub const EpochContext = struct { | ||
/// the staked nodes for this particular cluster to use for the leader schedule and turbine tree | ||
staked_nodes: std.AutoArrayHashMapUnmanaged(core.Pubkey, u64), | ||
/// the leader schedule for this epoch | ||
leader_schedule: []const core.Pubkey, | ||
|
||
pub fn deinit(self: *EpochContext, allocator: std.mem.Allocator) void { | ||
self.staked_nodes.deinit(allocator); | ||
allocator.free(self.leader_schedule); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.