Skip to content
Draft
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
30 changes: 30 additions & 0 deletions beacon-chain/blockchain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/db/iface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 58 additions & 0 deletions beacon-chain/db/kv/custody.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
77 changes: 77 additions & 0 deletions beacon-chain/db/kv/custody_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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)
})
}
1 change: 1 addition & 0 deletions beacon-chain/db/kv/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,5 @@ var (
groupCountKey = []byte("group-count")
earliestAvailableSlotKey = []byte("earliest-available-slot")
subscribeAllDataSubnetsKey = []byte("subscribe-all-data-subnets")
liteSupernodeKey = []byte("lite-supernode")
)
3 changes: 3 additions & 0 deletions beacon-chain/sync/custody.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions beacon-chain/sync/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions changelog/james-prysm_lite-supernode.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions cmd/beacon-chain/flags/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion cmd/beacon-chain/flags/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type GlobalFlags struct {
SubscribeToAllSubnets bool
SubscribeAllDataSubnets bool
LiteSupernode bool
MinimumSyncPeers int
MinimumPeersPerSubnet int
MaxConcurrentDials int
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions cmd/beacon-chain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var appFlags = []cli.Flag{
flags.DisableDebugRPCEndpoints,
flags.SubscribeToAllSubnets,
flags.SubscribeAllDataSubnets,
flags.LiteSupernode,
flags.HistoricalSlasherNode,
flags.ChainID,
flags.NetworkID,
Expand Down
1 change: 1 addition & 0 deletions cmd/beacon-chain/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading