From 7c9379ee79725e33008b39ed0922bb78d746e6a4 Mon Sep 17 00:00:00 2001 From: Neeraj Godiyal Date: Mon, 14 Sep 2026 17:26:00 +0530 Subject: [PATCH 1/2] fix(rpc): derive epoch info from the cluster schedule --- pkg/rpcserver/get_epoch_info.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/rpcserver/get_epoch_info.go b/pkg/rpcserver/get_epoch_info.go index 2ee4a9cdc..75ff31d93 100644 --- a/pkg/rpcserver/get_epoch_info.go +++ b/pkg/rpcserver/get_epoch_info.go @@ -25,17 +25,15 @@ func (rpcServer *RpcServer) GetEpochInfo(ctx context.Context, p jsonrpc.RawParam _ = params - epoch := global.Epoch() slot := global.Slot() - firstSlotInEpoch := rpcServer.epochSchedule.FirstSlotInEpoch(epoch) - slotIndex := slot - firstSlotInEpoch + epoch, slotIndex := rpcServer.epochSchedule.GetEpochAndSlotIndex(slot) resp := GetEpochInfoResp{ - AbsoluteSlot: global.Slot(), + AbsoluteSlot: slot, BlockHeight: global.BlockHeight(), - Epoch: global.Epoch(), + Epoch: epoch, SlotIndex: slotIndex, - SlotsInEpoch: 432000, + SlotsInEpoch: rpcServer.epochSchedule.SlotsInEpoch(epoch), TransactionCount: global.TransactionCount(), } From e7c50c436ba807836f4a8b400f387aed84672d75 Mon Sep 17 00:00:00 2001 From: Neeraj Godiyal Date: Mon, 14 Sep 2026 17:26:00 +0530 Subject: [PATCH 2/2] test(rpc): cover custom and warmup epoch schedules --- pkg/rpcserver/get_epoch_info_test.go | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pkg/rpcserver/get_epoch_info_test.go diff --git a/pkg/rpcserver/get_epoch_info_test.go b/pkg/rpcserver/get_epoch_info_test.go new file mode 100644 index 000000000..7a28d932c --- /dev/null +++ b/pkg/rpcserver/get_epoch_info_test.go @@ -0,0 +1,79 @@ +package rpcserver + +import ( + "net/http/httptest" + "testing" + + "github.com/Overclock-Validator/mithril/pkg/global" + "github.com/Overclock-Validator/mithril/pkg/sealevel" + "github.com/gagliardetto/solana-go" + solanarpc "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/require" +) + +func TestGetEpochInfoSchedule(t *testing.T) { + savedSlot, savedEpoch := global.Slot(), global.Epoch() + t.Cleanup(func() { + global.SetSlot(savedSlot) + global.SetEpoch(savedEpoch) + }) + + for _, schedule := range []struct { + name string + value sealevel.SysvarEpochSchedule + cases []struct{ slot, epoch, index, length uint64 } + }{ + { + name: "short epochs", + value: sealevel.SysvarEpochSchedule{SlotsPerEpoch: 32, LeaderScheduleSlotOffset: 32}, + cases: []struct{ slot, epoch, index, length uint64 }{ + {0, 0, 0, 32}, {31, 0, 31, 32}, {32, 1, 0, 32}, {79, 2, 15, 32}, + }, + }, + { + name: "normal epochs", + value: sealevel.SysvarEpochSchedule{SlotsPerEpoch: 432000, LeaderScheduleSlotOffset: 432000}, + cases: []struct{ slot, epoch, index, length uint64 }{ + {431999, 0, 431999, 432000}, {432000, 1, 0, 432000}, + }, + }, + { + name: "warmup epochs", + value: sealevel.SysvarEpochSchedule{ + SlotsPerEpoch: 432000, LeaderScheduleSlotOffset: 432000, + Warmup: true, FirstNormalEpoch: 14, FirstNormalSlot: 524256, + }, + cases: []struct{ slot, epoch, index, length uint64 }{ + {0, 0, 0, 32}, {32, 1, 0, 64}, {95, 1, 63, 64}, + {524255, 13, 262143, 262144}, {524256, 14, 0, 432000}, + }, + }, + } { + t.Run(schedule.name, func(t *testing.T) { + server := NewRpcServer(nil, 0, &schedule.value, solana.Hash{1}) + require.NoError(t, server.listener.Close()) + endpoint := httptest.NewServer(server) + t.Cleanup(endpoint.Close) + client := solanarpc.New(endpoint.URL) + for _, test := range schedule.cases { + global.SetSlot(test.slot) + global.SetEpoch(test.epoch) + got, err := client.GetEpochInfo(t.Context(), solanarpc.CommitmentProcessed) + require.NoError(t, err, "slot %d", test.slot) + require.Equal(t, test.slot, got.AbsoluteSlot) + require.Equal(t, test.epoch, got.Epoch, "slot %d", test.slot) + require.Equal(t, test.index, got.SlotIndex, "slot %d", test.slot) + require.Equal(t, test.length, got.SlotsInEpoch, "slot %d", test.slot) + require.Equal(t, global.BlockHeight(), got.BlockHeight) + require.NotNil(t, got.TransactionCount) + require.Equal(t, global.TransactionCount(), *got.TransactionCount) + + // Replay publishes slot and epoch separately; keep the response tied to its slot. + global.SetEpoch(test.epoch + 1) + repeated, err := client.GetEpochInfo(t.Context(), solanarpc.CommitmentProcessed) + require.NoError(t, err) + require.Equal(t, got, repeated) + } + }) + } +}