2020-04-14 20:27:03 +00:00
|
|
|
package p2p
|
|
|
|
|
|
|
|
import (
|
2020-08-10 15:27:50 +00:00
|
|
|
"context"
|
|
|
|
"sync"
|
|
|
|
|
2020-04-14 20:27:03 +00:00
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
|
|
"github.com/prysmaticlabs/go-bitfield"
|
2020-08-10 15:27:50 +00:00
|
|
|
"go.opencensus.io/trace"
|
|
|
|
|
2020-07-13 04:16:24 +00:00
|
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
2020-04-16 22:21:44 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
2020-04-14 20:27:03 +00:00
|
|
|
)
|
|
|
|
|
2020-04-16 22:21:44 +00:00
|
|
|
var attestationSubnetCount = params.BeaconNetworkConfig().AttestationSubnetCount
|
|
|
|
|
|
|
|
var attSubnetEnrKey = params.BeaconNetworkConfig().AttSubnetKey
|
2020-04-14 20:27:03 +00:00
|
|
|
|
2020-07-13 04:16:24 +00:00
|
|
|
// FindPeersWithSubnet performs a network search for peers
|
|
|
|
// subscribed to a particular subnet. Then we try to connect
|
2020-12-12 02:40:56 +00:00
|
|
|
// with those peers. This method will block until the required amount of
|
|
|
|
// peers are found, the method only exits in the event of context timeouts.
|
|
|
|
func (s *Service) FindPeersWithSubnet(ctx context.Context, topic string,
|
|
|
|
index, threshold uint64) (bool, error) {
|
2020-08-10 15:27:50 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "p2p.FindPeersWithSubnet")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
span.AddAttributes(trace.Int64Attribute("index", int64(index)))
|
|
|
|
|
2020-07-13 04:16:24 +00:00
|
|
|
if s.dv5Listener == nil {
|
|
|
|
// return if discovery isn't set
|
|
|
|
return false, nil
|
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
|
|
|
|
topic += s.Encoding().ProtocolSuffix()
|
2020-07-13 04:16:24 +00:00
|
|
|
iterator := s.dv5Listener.RandomNodes()
|
2020-12-12 02:40:56 +00:00
|
|
|
iterator = filterNodes(ctx, iterator, s.filterPeerForSubnet(index))
|
|
|
|
|
|
|
|
currNum := uint64(len(s.pubsub.ListPeers(topic)))
|
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
for {
|
2020-08-10 15:27:50 +00:00
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
if currNum >= threshold {
|
|
|
|
break
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
nodes := enode.ReadNodes(iterator, int(params.BeaconNetworkConfig().MinimumPeersInSubnetSearch))
|
|
|
|
for _, node := range nodes {
|
|
|
|
info, _, err := convertToAddrInfo(node)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
if err := s.connectWithPeer(ctx, *info); err != nil {
|
|
|
|
log.WithError(err).Tracef("Could not connect with peer %s", info.String())
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
// Wait for all dials to be completed.
|
|
|
|
wg.Wait()
|
|
|
|
currNum = uint64(len(s.pubsub.ListPeers(topic)))
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns a method with filters peers specifically for a particular attestation subnet.
|
|
|
|
func (s *Service) filterPeerForSubnet(index uint64) func(node *enode.Node) bool {
|
|
|
|
return func(node *enode.Node) bool {
|
|
|
|
if !s.filterPeer(node) {
|
|
|
|
return false
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
|
|
|
subnets, err := retrieveAttSubnets(node.Record())
|
|
|
|
if err != nil {
|
2020-12-12 02:40:56 +00:00
|
|
|
return false
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
indExists := false
|
2020-07-13 04:16:24 +00:00
|
|
|
for _, comIdx := range subnets {
|
|
|
|
if comIdx == index {
|
2020-12-12 02:40:56 +00:00
|
|
|
indExists = true
|
|
|
|
break
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-12 02:40:56 +00:00
|
|
|
return indExists
|
2020-07-13 04:16:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 02:40:56 +00:00
|
|
|
// lower threshold to broadcast object compared to searching
|
|
|
|
// for a subnet. So that even in the event of poor peer
|
|
|
|
// connectivity, we can still broadcast an attestation.
|
|
|
|
func (s *Service) hasPeerWithSubnet(topic string) bool {
|
|
|
|
return len(s.pubsub.ListPeers(topic+s.Encoding().ProtocolSuffix())) >= 1
|
2020-08-10 15:27:50 +00:00
|
|
|
}
|
|
|
|
|
2020-07-13 04:16:24 +00:00
|
|
|
// Updates the service's discv5 listener record's attestation subnet
|
|
|
|
// with a new value for a bitfield of subnets tracked. It also updates
|
|
|
|
// the node's metadata by increasing the sequence number and the
|
|
|
|
// subnets tracked by the node.
|
|
|
|
func (s *Service) updateSubnetRecordWithMetadata(bitV bitfield.Bitvector64) {
|
|
|
|
entry := enr.WithEntry(attSubnetEnrKey, &bitV)
|
|
|
|
s.dv5Listener.LocalNode().Set(entry)
|
|
|
|
s.metaData = &pb.MetaData{
|
|
|
|
SeqNumber: s.metaData.SeqNumber + 1,
|
|
|
|
Attnets: bitV,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:11:20 +00:00
|
|
|
// Initializes a bitvector of attestation subnets beacon nodes is subscribed to
|
|
|
|
// and creates a new ENR entry with its default value.
|
2020-04-14 20:27:03 +00:00
|
|
|
func intializeAttSubnets(node *enode.LocalNode) *enode.LocalNode {
|
|
|
|
bitV := bitfield.NewBitvector64()
|
|
|
|
entry := enr.WithEntry(attSubnetEnrKey, bitV.Bytes())
|
|
|
|
node.Set(entry)
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:11:20 +00:00
|
|
|
// Reads the attestation subnets entry from a node's ENR and determines
|
|
|
|
// the committee indices of the attestation subnets the node is subscribed to.
|
2020-04-14 20:27:03 +00:00
|
|
|
func retrieveAttSubnets(record *enr.Record) ([]uint64, error) {
|
|
|
|
bitV, err := retrieveBitvector(record)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-12 08:11:05 +00:00
|
|
|
var committeeIdxs []uint64
|
2020-04-14 20:27:03 +00:00
|
|
|
for i := uint64(0); i < attestationSubnetCount; i++ {
|
|
|
|
if bitV.BitAt(i) {
|
|
|
|
committeeIdxs = append(committeeIdxs, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return committeeIdxs, nil
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:11:20 +00:00
|
|
|
// Parses the attestation subnets ENR entry in a node and extracts its value
|
|
|
|
// as a bitvector for further manipulation.
|
2020-04-14 20:27:03 +00:00
|
|
|
func retrieveBitvector(record *enr.Record) (bitfield.Bitvector64, error) {
|
|
|
|
bitV := bitfield.NewBitvector64()
|
|
|
|
entry := enr.WithEntry(attSubnetEnrKey, &bitV)
|
|
|
|
err := record.Load(entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return bitV, nil
|
|
|
|
}
|
2020-08-10 15:27:50 +00:00
|
|
|
|
|
|
|
func (s *Service) subnetLocker(i uint64) *sync.RWMutex {
|
|
|
|
s.subnetsLockLock.Lock()
|
|
|
|
defer s.subnetsLockLock.Unlock()
|
|
|
|
l, ok := s.subnetsLock[i]
|
|
|
|
if !ok {
|
|
|
|
l = &sync.RWMutex{}
|
|
|
|
s.subnetsLock[i] = l
|
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|