Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions src/cmd.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,12 @@ fn validator(
);
defer loaded_snapshot.deinit();

const static_rpc_ctx: sig.rpc.methods.StaticHookContext = .{
.genesis_hash = loaded_snapshot.genesis_config.hash,
};

try app_base.rpc_hooks.set(allocator, &static_rpc_ctx);

var unrooted_tracy: tracy.TracingAllocator = .{
.name = "AccountsDB Unrooted",
.parent = allocator,
Expand Down
30 changes: 29 additions & 1 deletion src/core/genesis_config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,28 @@ pub const GenesisConfig = struct {
epoch_schedule: EpochSchedule,
/// network runlevel
cluster_type: ClusterType,
/// hash of the serialized genesis config, computed after deserialization
hash: sig.core.Hash = sig.core.Hash.ZEROES,

pub const @"!bincode-config:hash" = bincode.FieldConfig(sig.core.Hash){ .skip = true };

pub fn init(allocator: Allocator, genesis_path: []const u8) !GenesisConfig {
var file = try std.fs.cwd().openFile(genesis_path, .{});
defer file.close();

return try bincode.read(allocator, GenesisConfig, file.reader(), .{});
// Read the entire file to compute hash from raw bytes
const file_bytes = try file.readToEndAlloc(allocator, 100 * 1024 * 1024); // 100 MB max
defer allocator.free(file_bytes);

// Compute hash from original file bytes
// [agave] https://github.com/anza-xyz/solana-sdk/blob/f2d15de6f7a1715ff806f0c39bba8f64bf6a587d/genesis-config/src/lib.rs#L144
var hash_bytes: [32]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash(file_bytes, &hash_bytes, .{});

// Parse the genesis config from the bytes
var config = try bincode.readFromSlice(allocator, GenesisConfig, file_bytes, .{});
config.hash.data = hash_bytes;
return config;
}

pub fn default(allocator: Allocator) GenesisConfig {
Expand Down Expand Up @@ -363,6 +379,10 @@ test "genesis_config deserialize testnet config" {
defer config.deinit(allocator);

try std.testing.expectEqual(ClusterType.Testnet, config.cluster_type);
try std.testing.expectEqualStrings(
"4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY",
config.hash.base58String().constSlice(),
);
}

test "genesis_config deserialize devnet config" {
Expand All @@ -373,6 +393,10 @@ test "genesis_config deserialize devnet config" {
defer config.deinit(allocator);

try std.testing.expectEqual(ClusterType.Devnet, config.cluster_type);
try std.testing.expectEqualStrings(
"EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
config.hash.base58String().constSlice(),
);
}

test "genesis_config deserialize mainnet config" {
Expand All @@ -383,6 +407,10 @@ test "genesis_config deserialize mainnet config" {
defer config.deinit(allocator);

try std.testing.expectEqual(ClusterType.MainnetBeta, config.cluster_type);
try std.testing.expectEqualStrings(
"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
config.hash.base58String().constSlice(),
);
}

test "inflation" {
Expand Down
6 changes: 5 additions & 1 deletion src/rpc/hooks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ pub const Hooks = struct {
const cref = try ContextRef.init(allocator, context);
defer cref.ctx_ref.dec(allocator);

const Context = @TypeOf(context);
const RealContext = @TypeOf(context);
const Context = switch (@typeInfo(RealContext)) {
.pointer => |ty| ty.child,
else => RealContext,
};
inline for (comptime std.meta.declarations(Context)) |decl| {
const method = if (@hasField(Method, decl.name))
@field(Method, decl.name)
Expand Down
49 changes: 47 additions & 2 deletions src/rpc/methods.zig
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub const MethodAndParams = union(enum) {
getFirstAvailableBlock: noreturn,

/// https://github.com/Syndica/sig/issues/557
getGenesisHash: noreturn,
getGenesisHash: GetGenesisHash,
/// https://github.com/Syndica/sig/issues/558
getHealth: GetHealth,
/// Custom (not standardized) RPC method for "GET /*snapshot*.tar.bz2"
Expand Down Expand Up @@ -373,7 +373,40 @@ pub const GetEpochSchedule = struct {

// TODO: getFeeForMessage
// TODO: getFirstAvailableBlock
// TODO: getGenesisHash

pub const GetGenesisHash = struct {
pub const Response = struct {
hash: sig.core.Hash,

pub fn jsonStringify(
self: Response,
/// `*std.json.WriteStream(...)`
jw: anytype,
) !void {
try jw.write(self.hash.base58String().slice());
}

test "jsonStringify outputs base58 string" {
const allocator = std.testing.allocator;

const slice = "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn";
const expected_slice = try std.fmt.allocPrint(allocator, "\"{s}\"", .{slice});
defer allocator.free(expected_slice);

// Use a known hash value for testing
const hash = sig.core.Hash.parse(slice);
const response: GetGenesisHash.Response = .{ .hash = hash };

// Serialize the response to JSON
const json = try std.json.stringifyAlloc(allocator, response, .{});
defer allocator.free(json);

// The jsonStringify should output the hash as a base58 string (without wrapping object)
try std.testing.expectEqualStrings(expected_slice, json);
}
};
};

// TODO: getHealth
// TODO: getHighestSnapshotSlot
// TODO: getIdentity
Expand Down Expand Up @@ -686,3 +719,15 @@ pub const common = struct {
shredVersion: ?u16 = null,
};
};

pub const StaticHookContext = struct {
genesis_hash: sig.core.Hash,

pub fn getGenesisHash(
self: *const @This(),
_: std.mem.Allocator,
_: GetGenesisHash,
) !GetGenesisHash.Response {
return .{ .hash = self.genesis_hash };
}
};