Compare commits

...

8 Commits

Author SHA1 Message Date
james-prysm
93514608b0 changelog 2025-11-14 11:27:20 -06:00
james-prysm
f999173ccd Merge branch 'develop' into lite-supernode 2025-11-14 07:20:31 -08:00
james-prysm
9c074acf59 fixing test 2025-11-13 15:05:27 -06:00
james-prysm
430bb09a74 Merge branch 'develop' into lite-supernode 2025-11-13 12:58:33 -08:00
james-prysm
d72ce136b8 Merge branch 'develop' into lite-supernode 2025-11-13 07:00:47 -08:00
james-prysm
2937b62a10 Rename semi-super-node to lite-supernode 2025-11-12 21:27:36 -06:00
james-prysm
2fb2fecd92 changing attempt 2025-11-12 20:58:20 -06:00
james-prysm
f93a77aef9 wip 2025-11-12 20:15:58 -06:00
12 changed files with 194 additions and 1 deletions

View File

@@ -471,10 +471,20 @@ func (s *Service) removeStartupState() {
// It returns the (potentially updated) custody group count and the earliest available slot.
func (s *Service) updateCustodyInfoInDB(slot primitives.Slot) (primitives.Slot, uint64, error) {
isSubscribedToAllDataSubnets := flags.Get().SubscribeAllDataSubnets
isLiteSupernode := flags.Get().LiteSupernode
cfg := params.BeaconConfig()
custodyRequirement := cfg.CustodyRequirement
// Warn if both flags are set (supernode takes precedence).
if isSubscribedToAllDataSubnets && isLiteSupernode {
log.Warnf(
"Both `--%s` and `--%s` flags are set. The supernode flag takes precedence.",
flags.SubscribeAllDataSubnets.Name,
flags.LiteSupernode.Name,
)
}
// Check if the node was previously subscribed to all data subnets, and if so,
// store the new status accordingly.
wasSubscribedToAllDataSubnets, err := s.cfg.BeaconDB.UpdateSubscribedToAllDataSubnets(s.ctx, isSubscribedToAllDataSubnets)
@@ -490,11 +500,31 @@ func (s *Service) updateCustodyInfoInDB(slot primitives.Slot) (primitives.Slot,
)
}
// Check if the node was previously in lite-supernode mode, and if so,
// store the new status accordingly.
wasLiteSupernode, err := s.cfg.BeaconDB.UpdateLiteSupernode(s.ctx, isLiteSupernode)
if err != nil {
log.WithError(err).Error("Could not update lite-supernode status")
}
// Warn the user if the node was previously in lite-supernode mode and is not any more.
if wasLiteSupernode && !isLiteSupernode {
log.Warnf(
"Because the flag `--%s` was previously used, the node will remain in lite-supernode mode (subscribe to 64 subnets).",
flags.LiteSupernode.Name,
)
}
// Compute the custody group count.
// Note: Lite-supernode subscribes to 64 subnets (enough to reconstruct) but only custodies the minimum groups (4).
// Only supernode increases custody to all 128 groups.
// Persistence (preventing downgrades) is handled by UpdateCustodyInfo() which
// refuses to decrease the stored custody count.
custodyGroupCount := custodyRequirement
if isSubscribedToAllDataSubnets {
custodyGroupCount = cfg.NumberOfCustodyGroups
}
// Lite-supernode does NOT increase custody count - it keeps the minimum (4 or validator requirement).
// Safely compute the fulu fork slot.
fuluForkSlot, err := fuluForkSlot()

View File

@@ -129,6 +129,7 @@ type NoHeadAccessDatabase interface {
// Custody operations.
UpdateSubscribedToAllDataSubnets(ctx context.Context, subscribed bool) (bool, error)
UpdateLiteSupernode(ctx context.Context, enabled bool) (bool, error)
UpdateCustodyInfo(ctx context.Context, earliestAvailableSlot primitives.Slot, custodyGroupCount uint64) (primitives.Slot, uint64, error)
UpdateEarliestAvailableSlot(ctx context.Context, earliestAvailableSlot primitives.Slot) error

View File

@@ -203,3 +203,61 @@ func (s *Store) UpdateSubscribedToAllDataSubnets(ctx context.Context, subscribed
return result, nil
}
// UpdateLiteSupernode updates the "lite-supernode" status in the database
// only if `enabled` is `true`.
// It returns the previous lite-supernode status.
func (s *Store) UpdateLiteSupernode(ctx context.Context, enabled bool) (bool, error) {
_, span := trace.StartSpan(ctx, "BeaconDB.UpdateLiteSupernode")
defer span.End()
result := false
if !enabled {
if err := s.db.View(func(tx *bolt.Tx) error {
// Retrieve the custody bucket.
bucket := tx.Bucket(custodyBucket)
if bucket == nil {
return nil
}
// Retrieve the lite-supernode flag.
bytes := bucket.Get(liteSupernodeKey)
if len(bytes) == 0 {
return nil
}
if bytes[0] == 1 {
result = true
}
return nil
}); err != nil {
return false, err
}
return result, nil
}
if err := s.db.Update(func(tx *bolt.Tx) error {
// Retrieve the custody bucket.
bucket, err := tx.CreateBucketIfNotExists(custodyBucket)
if err != nil {
return errors.Wrap(err, "create custody bucket")
}
bytes := bucket.Get(liteSupernodeKey)
if len(bytes) != 0 && bytes[0] == 1 {
result = true
}
if err := bucket.Put(liteSupernodeKey, []byte{1}); err != nil {
return errors.Wrap(err, "put lite-supernode")
}
return nil
}); err != nil {
return false, err
}
return result, nil
}

View File

@@ -67,6 +67,29 @@ func getSubscriptionStatusFromDB(t *testing.T, db *Store) bool {
return subscribed
}
// getLiteSupernodeStatusFromDB reads the lite-supernode status directly from the database for testing purposes.
func getLiteSupernodeStatusFromDB(t *testing.T, db *Store) bool {
t.Helper()
var enabled bool
err := db.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(custodyBucket)
if bucket == nil {
return nil
}
bytes := bucket.Get(liteSupernodeKey)
if len(bytes) != 0 && bytes[0] == 1 {
enabled = true
}
return nil
})
require.NoError(t, err)
return enabled
}
func TestUpdateCustodyInfo(t *testing.T) {
ctx := t.Context()
@@ -302,3 +325,57 @@ func TestUpdateSubscribedToAllDataSubnets(t *testing.T) {
require.Equal(t, true, stored)
})
}
func TestUpdateLiteSupernode(t *testing.T) {
ctx := context.Background()
t.Run("initial update with empty database - set to false", func(t *testing.T) {
db := setupDB(t)
prev, err := db.UpdateLiteSupernode(ctx, false)
require.NoError(t, err)
require.Equal(t, false, prev)
stored := getLiteSupernodeStatusFromDB(t, db)
require.Equal(t, false, stored)
})
t.Run("initial update with empty database - set to true", func(t *testing.T) {
db := setupDB(t)
prev, err := db.UpdateLiteSupernode(ctx, true)
require.NoError(t, err)
require.Equal(t, false, prev)
stored := getLiteSupernodeStatusFromDB(t, db)
require.Equal(t, true, stored)
})
t.Run("attempt to update from true to false (should not change)", func(t *testing.T) {
db := setupDB(t)
_, err := db.UpdateLiteSupernode(ctx, true)
require.NoError(t, err)
prev, err := db.UpdateLiteSupernode(ctx, false)
require.NoError(t, err)
require.Equal(t, true, prev)
stored := getLiteSupernodeStatusFromDB(t, db)
require.Equal(t, true, stored)
})
t.Run("update from true to true (no change)", func(t *testing.T) {
db := setupDB(t)
_, err := db.UpdateLiteSupernode(ctx, true)
require.NoError(t, err)
prev, err := db.UpdateLiteSupernode(ctx, true)
require.NoError(t, err)
require.Equal(t, true, prev)
stored := getLiteSupernodeStatusFromDB(t, db)
require.Equal(t, true, stored)
})
}

View File

@@ -77,4 +77,5 @@ var (
groupCountKey = []byte("group-count")
earliestAvailableSlotKey = []byte("earliest-available-slot")
subscribeAllDataSubnetsKey = []byte("subscribe-all-data-subnets")
liteSupernodeKey = []byte("lite-supernode")
)

View File

@@ -96,9 +96,12 @@ func (s *Service) updateCustodyInfoIfNeeded() error {
// custodyGroupCount computes the custody group count based on the custody requirement,
// the validators custody requirement, and whether the node is subscribed to all data subnets.
// Note: Lite-supernode subscribes to 64 subnets (enough to reconstruct) but only custodies the minimum groups.
func (s *Service) custodyGroupCount(context.Context) (uint64, error) {
cfg := params.BeaconConfig()
// Only supernode mode increases custody to all 128 groups.
// Lite-supernode subscribes to 64 subnets but custodies the minimum.
if flags.Get().SubscribeAllDataSubnets {
return cfg.NumberOfCustodyGroups, nil
}

View File

@@ -697,10 +697,16 @@ func (s *Service) dataColumnSubnetIndices(primitives.Slot) map[uint64]bool {
func (s *Service) samplingSize() (uint64, error) {
cfg := params.BeaconConfig()
// Supernode subscribes to all subnets.
if flags.Get().SubscribeAllDataSubnets {
return cfg.DataColumnSidecarSubnetCount, nil
}
// Lite-supernode subscribes to half (64 subnets - minimum needed to reconstruct).
if flags.Get().LiteSupernode {
return cfg.DataColumnSidecarSubnetCount / 2, nil
}
// Compute the validators custody requirement.
validatorsCustodyRequirement, err := s.validatorsCustodyRequirement()
if err != nil {

View File

@@ -0,0 +1,2 @@
### Added
- New flag `--lite-supernode` is an option to run the node with the minimum custody based on number of validators, but will subscribe to 64 subnets to obtain 64 data columns required to reconstruct all blobs.

View File

@@ -339,6 +339,13 @@ var (
Aliases: []string{"subscribe-all-data-subnets"},
Usage: "Enable subscription to all data subnets and store all blob columns, serving them over RPC. Required post-Fusaka for full blob reconstruction. This is effectively one-way: once enabled, the node keeps storing and serving all columns even if the flag is later unset.",
}
// LiteSupernode enables lite-supernode mode: subscribe to 64 subnets and store 64 columns (minimum for reconstruction),
// but only custody/serve the minimum 4 groups to peers.
LiteSupernode = &cli.BoolFlag{
Name: "lite-supernode",
Usage: "Enable lite-supernode mode: subscribe to 64 data column subnets (enough to reconstruct), " +
"but only custody and serve the minimum 4 groups to peers. Once set, you can only upgrade to full supernode, not downgrade.",
}
// BatchVerifierLimit sets the maximum number of signatures to batch verify at once.
BatchVerifierLimit = &cli.IntFlag{
Name: "batch-verifier-limit",

View File

@@ -10,6 +10,7 @@ import (
type GlobalFlags struct {
SubscribeToAllSubnets bool
SubscribeAllDataSubnets bool
LiteSupernode bool
MinimumSyncPeers int
MinimumPeersPerSubnet int
MaxConcurrentDials int
@@ -47,10 +48,15 @@ func ConfigureGlobalFlags(ctx *cli.Context) {
}
if ctx.Bool(SubscribeAllDataSubnets.Name) {
log.Warning("Subscribing to all data subnets")
log.Warning("Subscribing to all data subnets (super-node mode: 128 custody groups)")
cfg.SubscribeAllDataSubnets = true
}
if ctx.Bool(LiteSupernode.Name) {
log.Warning("Enabling lite-supernode mode (subscribe to 64 subnets, custody 4 groups)")
cfg.LiteSupernode = true
}
cfg.BlockBatchLimit = ctx.Int(BlockBatchLimit.Name)
cfg.BlockBatchLimitBurstFactor = ctx.Int(BlockBatchLimitBurstFactor.Name)
cfg.BlobBatchLimit = ctx.Int(BlobBatchLimit.Name)

View File

@@ -66,6 +66,7 @@ var appFlags = []cli.Flag{
flags.DisableDebugRPCEndpoints,
flags.SubscribeToAllSubnets,
flags.SubscribeAllDataSubnets,
flags.LiteSupernode,
flags.HistoricalSlasherNode,
flags.ChainID,
flags.NetworkID,

View File

@@ -108,6 +108,7 @@ var appHelpFlagGroups = []flagGroup{
flags.MinSyncPeers,
flags.SubscribeToAllSubnets,
flags.SubscribeAllDataSubnets,
flags.LiteSupernode,
},
},
{ // Flags relevant to storing data on disk and configuring the beacon chain database.