mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Regular Sync - First Pass (#3201)
* checkpoint * checkpoint * varint prefix for ssz * move the encoding API around a little bit to support reader writer * add a simple test for the happy path subscribe * move wait timeout to testutil * Add inverted topic mapping * Add varint prefixing to ssz network encoder * fix spacing * fix comments * fix comments * make anon methods more clear * clean up log fields * move topic mapping, reformat TODOs, get ready for brutal team review * lint * lint * lint * Update beacon-chain/p2p/gossip_topic_mappings.go Co-Authored-By: Nishant Das <nishdas93@gmail.com> * PR feedback * PR feedback * PR feedback * PR feedback * PR feedback * Update WORKSPACE * Update WORKSPACE
This commit is contained in:
@@ -205,7 +205,7 @@ go_repository(
|
||||
|
||||
go_repository(
|
||||
name = "com_github_prysmaticlabs_go_ssz",
|
||||
commit = "4c975a4b9dc1575778ccd3d02740fc81b977b540",
|
||||
commit = "de7d8d169d83bf8ca5ac66739fc49685fd6b05e0",
|
||||
importpath = "github.com/prysmaticlabs/go-ssz",
|
||||
)
|
||||
|
||||
|
||||
@@ -3,17 +3,25 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"broadcaster.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"gossip_topic_mappings.go",
|
||||
"interfaces.go",
|
||||
"log.go",
|
||||
"service.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/p2p",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/p2p/encoder:go_default_library",
|
||||
"//proto/eth/v1alpha1:go_default_library",
|
||||
"//shared:go_default_library",
|
||||
"@com_github_gogo_protobuf//proto:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//host:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//protocol:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
|
||||
8
beacon-chain/p2p/broadcaster.go
Normal file
8
beacon-chain/p2p/broadcaster.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package p2p
|
||||
|
||||
import "github.com/gogo/protobuf/proto"
|
||||
|
||||
// Broadcast a message to the p2p network.
|
||||
func (s *Service) Broadcast(msg proto.Message) {
|
||||
// TODO(3147): implement
|
||||
}
|
||||
28
beacon-chain/p2p/gossip_topic_mappings.go
Normal file
28
beacon-chain/p2p/gossip_topic_mappings.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
|
||||
)
|
||||
|
||||
// GossipTopicMappings represent the protocol ID to protobuf message type map for easy
|
||||
// lookup.
|
||||
var GossipTopicMappings = map[string]proto.Message{
|
||||
"/eth2/beacon_block": &pb.BeaconBlock{},
|
||||
"/eth2/beacon_attestation": &pb.Attestation{},
|
||||
"/eth2/voluntary_exit": &pb.VoluntaryExit{},
|
||||
"/eth2/proposer_slashing": &pb.ProposerSlashing{},
|
||||
"/eth2/attester_slashing": &pb.AttesterSlashing{},
|
||||
}
|
||||
|
||||
// GossipTypeMapping is the inverse of GossipTopicMappings so that an arbitrary protobuf message
|
||||
// can be mapped to a protocol ID string.
|
||||
var GossipTypeMapping = make(map[reflect.Type]string)
|
||||
|
||||
func init() {
|
||||
for k, v := range GossipTopicMappings {
|
||||
GossipTypeMapping[reflect.TypeOf(v)] = k
|
||||
}
|
||||
}
|
||||
36
beacon-chain/p2p/interfaces.go
Normal file
36
beacon-chain/p2p/interfaces.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder"
|
||||
)
|
||||
|
||||
// P2P represents the full p2p interface composed of all of the sub-interfaces.
|
||||
type P2P interface {
|
||||
Broadcaster
|
||||
SetStreamHandler
|
||||
EncodingProvider
|
||||
PubSubProvider
|
||||
}
|
||||
|
||||
// Broadcaster broadcasts messages to peers over the p2p pubsub protocol.
|
||||
type Broadcaster interface {
|
||||
Broadcast(proto.Message)
|
||||
}
|
||||
|
||||
// SetStreamHandler configures p2p to handle streams of a certain topic ID.
|
||||
type SetStreamHandler interface {
|
||||
SetStreamHandler(topic string, handler network.StreamHandler)
|
||||
}
|
||||
|
||||
// EncodingProvider provides p2p network encoding.
|
||||
type EncodingProvider interface {
|
||||
Encoding() encoder.NetworkEncoding
|
||||
}
|
||||
|
||||
// PubSubProvider provides the p2p pubsub protocol.
|
||||
type PubSubProvider interface {
|
||||
PubSub() *pubsub.PubSub
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import (
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
network "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder"
|
||||
"github.com/prysmaticlabs/prysm/shared"
|
||||
)
|
||||
|
||||
@@ -75,3 +78,19 @@ func (s *Service) Status() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encoding returns the configured networking encoding.
|
||||
func (s *Service) Encoding() encoder.NetworkEncoding {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PubSub returns the p2p pubsub framework.
|
||||
func (s *Service) PubSub() *pubsub.PubSub {
|
||||
return s.pubsub
|
||||
}
|
||||
|
||||
// SetStreamHandler sets the protocol handler on the p2p host multiplexer.
|
||||
// This method is a pass through to libp2pcore.Host.SetStreamHandler.
|
||||
func (s *Service) SetStreamHandler(topic string, handler network.StreamHandler) {
|
||||
s.host.SetStreamHandler(protocol.ID(topic), handler)
|
||||
}
|
||||
|
||||
20
beacon-chain/p2p/testing/BUILD.bazel
Normal file
20
beacon-chain/p2p/testing/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = ["p2p.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/p2p/encoder:go_default_library",
|
||||
"@com_github_gogo_protobuf//proto:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_blankhost//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//host:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//protocol:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_swarm//testing:go_default_library",
|
||||
],
|
||||
)
|
||||
128
beacon-chain/p2p/testing/p2p.go
Normal file
128
beacon-chain/p2p/testing/p2p.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
bhost "github.com/libp2p/go-libp2p-blankhost"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
swarmt "github.com/libp2p/go-libp2p-swarm/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder"
|
||||
)
|
||||
|
||||
var _ = p2p.P2P(&TestP2P{})
|
||||
|
||||
// TestP2P represents a p2p implementation that can be used for testing.
|
||||
type TestP2P struct {
|
||||
t *testing.T
|
||||
host host.Host
|
||||
pubsub *pubsub.PubSub
|
||||
}
|
||||
|
||||
// NewTestP2P initializes a new p2p test service.
|
||||
func NewTestP2P(t *testing.T) *TestP2P {
|
||||
ctx := context.Background()
|
||||
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
|
||||
ps, err := pubsub.NewFloodSub(ctx, h,
|
||||
pubsub.WithMessageSigning(false),
|
||||
pubsub.WithStrictSignatureVerification(false),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return &TestP2P{
|
||||
t: t,
|
||||
host: h,
|
||||
pubsub: ps,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect two test peers together.
|
||||
func (p *TestP2P) Connect(b *TestP2P) {
|
||||
if err := connect(p.host, b.host); err != nil {
|
||||
p.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func connect(a, b host.Host) error {
|
||||
pinfo := a.Peerstore().PeerInfo(a.ID())
|
||||
return b.Connect(context.Background(), pinfo)
|
||||
}
|
||||
|
||||
// ReceiveRPC simulates an incoming RPC.
|
||||
func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t, context.Background()))
|
||||
if err := connect(h, p.host); err != nil {
|
||||
p.t.Fatalf("Failed to connect two peers for RPC: %v", err)
|
||||
}
|
||||
s, err := h.NewStream(context.Background(), p.host.ID(), protocol.ID(topic+p.Encoding().ProtocolSuffix()))
|
||||
if err != nil {
|
||||
p.t.Fatalf("Failed to open stream %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
n, err := p.Encoding().Encode(s, msg)
|
||||
if err != nil {
|
||||
p.t.Fatalf("Failed to encode message: %v", err)
|
||||
}
|
||||
|
||||
p.t.Logf("Wrote %d bytes", n)
|
||||
}
|
||||
|
||||
// ReceivePubSub simulates an incoming message over pubsub on a given topic.
|
||||
func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) {
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t, context.Background()))
|
||||
ps, err := pubsub.NewFloodSub(context.Background(), h,
|
||||
pubsub.WithMessageSigning(false),
|
||||
pubsub.WithStrictSignatureVerification(false),
|
||||
)
|
||||
if err != nil {
|
||||
p.t.Fatalf("Failed to create flood sub: %v", err)
|
||||
}
|
||||
if err := connect(h, p.host); err != nil {
|
||||
p.t.Fatalf("Failed to connect two peers for RPC: %v", err)
|
||||
}
|
||||
|
||||
// PubSub requires some delay after connecting for the (*PubSub).processLoop method to
|
||||
// pick up the newly connected peer.
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := p.Encoding().Encode(buf, msg); err != nil {
|
||||
p.t.Fatalf("Failed to encode message: %v", err)
|
||||
}
|
||||
|
||||
if err := ps.Publish(topic+p.Encoding().ProtocolSuffix(), buf.Bytes()); err != nil {
|
||||
p.t.Fatalf("Failed to publish message; %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast a message.
|
||||
func (p *TestP2P) Broadcast(msg proto.Message) {
|
||||
// TODO(3147): implement
|
||||
}
|
||||
|
||||
// SetStreamHandler for RPC.
|
||||
func (p *TestP2P) SetStreamHandler(topic string, handler network.StreamHandler) {
|
||||
p.host.SetStreamHandler(protocol.ID(topic), handler)
|
||||
}
|
||||
|
||||
// Encoding returns ssz encoding.
|
||||
func (p *TestP2P) Encoding() encoder.NetworkEncoding {
|
||||
return &encoder.SszNetworkEncoder{}
|
||||
}
|
||||
|
||||
// PubSub returns reference underlying floodsub. This test library uses floodsub
|
||||
// to ensure all connected peers receive the message.
|
||||
func (p *TestP2P) PubSub() *pubsub.PubSub {
|
||||
return p.pubsub
|
||||
}
|
||||
44
beacon-chain/sync/BUILD.bazel
Normal file
44
beacon-chain/sync/BUILD.bazel
Normal file
@@ -0,0 +1,44 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
"rpc.go",
|
||||
"rpc_hello.go",
|
||||
"service.go",
|
||||
"subscriber.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//proto/beacon/p2p/v1:go_default_library",
|
||||
"//shared:go_default_library",
|
||||
"//shared/roughtime:go_default_library",
|
||||
"@com_github_gogo_protobuf//proto:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"rpc_test.go",
|
||||
"subscriber_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/p2p/testing:go_default_library",
|
||||
"//proto/eth/v1alpha1:go_default_library",
|
||||
"//proto/testing:go_default_library",
|
||||
"//shared/testutil:go_default_library",
|
||||
"@com_github_gogo_protobuf//proto:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_core//:go_default_library",
|
||||
],
|
||||
)
|
||||
4
beacon-chain/sync/doc.go
Normal file
4
beacon-chain/sync/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package sync TODO(3147): Add details on how sync works.
|
||||
*/
|
||||
package sync
|
||||
5
beacon-chain/sync/log.go
Normal file
5
beacon-chain/sync/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package sync
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "sync")
|
||||
3
beacon-chain/sync/metrics.go
Normal file
3
beacon-chain/sync/metrics.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package sync
|
||||
|
||||
// TODO(3147): Add metrics for RPC & subscription success/error.
|
||||
80
beacon-chain/sync/rpc.go
Normal file
80
beacon-chain/sync/rpc.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
libp2pcore "github.com/libp2p/go-libp2p-core"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
||||
"github.com/prysmaticlabs/prysm/shared/roughtime"
|
||||
)
|
||||
|
||||
// Time to first byte timeout. The maximum time to wait for first byte of
|
||||
// request response (time-to-first-byte). The client is expected to give up if
|
||||
// they don't receive the first byte within 5 seconds.
|
||||
var ttfbTimeout = 5 * time.Second
|
||||
|
||||
// rpcHandler is responsible for handling and responding to any incoming message.
|
||||
// This method may return an error to internal monitoring, but the error will
|
||||
// not be relayed to the peer.
|
||||
type rpcHandler func(context.Context, proto.Message, libp2pcore.Stream) error
|
||||
|
||||
// TODO(3147): Delete after all handlers implemented.
|
||||
func notImplementedRPCHandler(_ context.Context, _ proto.Message, _ libp2pcore.Stream) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// registerRPCHandlers for p2p RPC.
|
||||
func (r *RegularSync) registerRPCHandlers() {
|
||||
r.registerRPC(
|
||||
"/eth2/beacon_chain/req/hello/1",
|
||||
&pb.Hello{},
|
||||
r.helloRPCHandler,
|
||||
)
|
||||
r.registerRPC(
|
||||
"/eth2/beacon_chain/req/goodbye/1",
|
||||
nil,
|
||||
notImplementedRPCHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.registerRPC(
|
||||
"/eth2/beacon_chain/req/recent_beacon_blocks/1",
|
||||
nil,
|
||||
notImplementedRPCHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.registerRPC(
|
||||
"/eth2/beacon_chain/req/beacon_blocks/1",
|
||||
nil,
|
||||
notImplementedRPCHandler, // TODO(3147): Implement.
|
||||
)
|
||||
}
|
||||
|
||||
// registerRPC for a given topic with an expected protobuf message type.
|
||||
func (r *RegularSync) registerRPC(topic string, base proto.Message, handle rpcHandler) {
|
||||
topic += r.p2p.Encoding().ProtocolSuffix()
|
||||
log := log.WithField("topic", topic)
|
||||
r.p2p.SetStreamHandler(topic, func(stream network.Stream) {
|
||||
ctx, cancel := context.WithTimeout(r.ctx, ttfbTimeout)
|
||||
defer cancel()
|
||||
defer stream.Close()
|
||||
|
||||
if err := stream.SetReadDeadline(roughtime.Now().Add(ttfbTimeout)); err != nil {
|
||||
log.WithError(err).Error("Could not set stream read deadline")
|
||||
return
|
||||
}
|
||||
|
||||
// Clone the base message type so we have a newly initialized message as the decoding
|
||||
// destination.
|
||||
msg := proto.Clone(base)
|
||||
if err := r.p2p.Encoding().Decode(stream, msg); err != nil {
|
||||
log.WithError(err).Error("Failed to decode stream message")
|
||||
return
|
||||
}
|
||||
if err := handle(ctx, msg, stream); err != nil {
|
||||
// TODO(3147): Update metrics
|
||||
log.WithError(err).Error("Failed to handle p2p RPC")
|
||||
}
|
||||
})
|
||||
}
|
||||
13
beacon-chain/sync/rpc_hello.go
Normal file
13
beacon-chain/sync/rpc_hello.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
libp2pcore "github.com/libp2p/go-libp2p-core"
|
||||
)
|
||||
|
||||
func (r *RegularSync) helloRPCHandler(ctx context.Context, msg proto.Message, stream libp2pcore.Stream) error {
|
||||
// TODO(3147): Implement
|
||||
return nil
|
||||
}
|
||||
43
beacon-chain/sync/rpc_test.go
Normal file
43
beacon-chain/sync/rpc_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
libp2pcore "github.com/libp2p/go-libp2p-core"
|
||||
p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/testing"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
)
|
||||
|
||||
func TestRegisterRPC_ReceivesValidMessage(t *testing.T) {
|
||||
p2p := p2ptest.NewTestP2P(t)
|
||||
r := &RegularSync{
|
||||
ctx: context.Background(),
|
||||
p2p: p2p,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
topic := "/testing/foobar/1"
|
||||
handler := func(ctx context.Context, msg proto.Message, stream libp2pcore.Stream) error {
|
||||
m := msg.(*pb.TestSimpleMessage)
|
||||
if !bytes.Equal(m.Foo, []byte("foo")) {
|
||||
t.Errorf("Unexpected incoming message: %+v", m)
|
||||
}
|
||||
wg.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
r.registerRPC(topic, &pb.TestSimpleMessage{}, handler)
|
||||
|
||||
p2p.ReceiveRPC(topic, &pb.TestSimpleMessage{Foo: []byte("foo")})
|
||||
|
||||
if testutil.WaitTimeout(&wg, time.Second) {
|
||||
t.Fatal("Did not receive RPC in 1 second")
|
||||
}
|
||||
}
|
||||
33
beacon-chain/sync/service.go
Normal file
33
beacon-chain/sync/service.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/shared"
|
||||
)
|
||||
|
||||
var _ = shared.Service(&RegularSync{})
|
||||
|
||||
// RegularSync service is responsible for handling all run time p2p related operations as the
|
||||
// main entry point for network messages.
|
||||
type RegularSync struct {
|
||||
ctx context.Context
|
||||
p2p p2p.P2P
|
||||
}
|
||||
|
||||
// Start the regular sync service by initializing all of the p2p sync handlers.
|
||||
func (r *RegularSync) Start() {
|
||||
r.registerRPCHandlers()
|
||||
r.registerSubscribers()
|
||||
}
|
||||
|
||||
// Stop the regular sync service.
|
||||
func (r *RegularSync) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status of the currently running regular sync service.
|
||||
func (r *RegularSync) Status() error {
|
||||
return nil
|
||||
}
|
||||
122
beacon-chain/sync/subscriber.go
Normal file
122
beacon-chain/sync/subscriber.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
|
||||
)
|
||||
|
||||
// subHandler represents handler for a given subscription.
|
||||
type subHandler func(context.Context, proto.Message) error
|
||||
|
||||
func notImplementedSubHandler(_ context.Context, _ proto.Message) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// validator should verify the contents of the message, propagate the message
|
||||
// as expected, and return true or false to continue the message processing
|
||||
// pipeline.
|
||||
type validator func(context.Context, proto.Message, p2p.Broadcaster) bool
|
||||
|
||||
// noopValidator is a no-op that always returns true and does not propagate any
|
||||
// message.
|
||||
func noopValidator(_ context.Context, _ proto.Message, _ p2p.Broadcaster) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Register PubSub subscribers
|
||||
func (r *RegularSync) registerSubscribers() {
|
||||
r.subscribe(
|
||||
"/eth2/beacon_block",
|
||||
noopValidator,
|
||||
notImplementedSubHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.subscribe(
|
||||
"/eth2/beacon_attestation",
|
||||
noopValidator,
|
||||
notImplementedSubHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.subscribe(
|
||||
"/eth2/voluntary_exit",
|
||||
noopValidator,
|
||||
notImplementedSubHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.subscribe(
|
||||
"/eth2/proposer_slashing",
|
||||
noopValidator,
|
||||
notImplementedSubHandler, // TODO(3147): Implement.
|
||||
)
|
||||
r.subscribe(
|
||||
"/eth2/attester_slashing",
|
||||
noopValidator,
|
||||
notImplementedSubHandler, // TODO(3147): Implement.
|
||||
)
|
||||
}
|
||||
|
||||
// subscribe to a given topic with a given validator and subscription handler.
|
||||
// The base protobuf message is used to initialize new messages for decoding.
|
||||
func (r *RegularSync) subscribe(topic string, validate validator, handle subHandler) {
|
||||
base := p2p.GossipTopicMappings[topic]
|
||||
if base == nil {
|
||||
panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topic))
|
||||
}
|
||||
|
||||
topic += r.p2p.Encoding().ProtocolSuffix()
|
||||
log := log.WithField("topic", topic)
|
||||
|
||||
sub, err := r.p2p.PubSub().Subscribe(topic)
|
||||
if err != nil {
|
||||
// Any error subscribing to a PubSub topic would be the result of a misconfiguration of
|
||||
// libp2p PubSub library. This should not happen at normal runtime, unless the config
|
||||
// changes to a fatal configuration.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pipeline decodes the incoming subscription data, runs the validation, and handles the
|
||||
// message.
|
||||
pipeline := func(data []byte) {
|
||||
if data == nil {
|
||||
log.Warn("Received nil message on pubsub")
|
||||
return
|
||||
}
|
||||
|
||||
msg := proto.Clone(base)
|
||||
if err := r.p2p.Encoding().Decode(bytes.NewBuffer(data), msg); err != nil {
|
||||
log.WithError(err).Warn("Failed to decode pubsub message")
|
||||
return
|
||||
}
|
||||
|
||||
if !validate(r.ctx, msg, r.p2p) {
|
||||
log.WithField("message", msg.String()).Debug("Message did not verify")
|
||||
|
||||
// TODO(3147): Increment metrics.
|
||||
return
|
||||
}
|
||||
|
||||
if err := handle(r.ctx, msg); err != nil {
|
||||
// TODO(3147): Increment metrics.
|
||||
log.WithError(err).Error("Failed to handle p2p pubsub")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The main message loop for receiving incoming messages from this subscription.
|
||||
messageLoop := func() {
|
||||
for {
|
||||
msg, err := sub.Next(r.ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Subscription next failed")
|
||||
// TODO(3147): Mark status unhealthy.
|
||||
return
|
||||
}
|
||||
|
||||
go pipeline(msg.Data)
|
||||
}
|
||||
}
|
||||
|
||||
go messageLoop()
|
||||
}
|
||||
40
beacon-chain/sync/subscriber_test.go
Normal file
40
beacon-chain/sync/subscriber_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
|
||||
pb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
)
|
||||
|
||||
func TestSubscribe_ReceivesValidMessage(t *testing.T) {
|
||||
p2p := p2ptest.NewTestP2P(t)
|
||||
r := RegularSync{
|
||||
ctx: context.Background(),
|
||||
p2p: p2p,
|
||||
}
|
||||
|
||||
topic := "/eth2/voluntary_exit"
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
r.subscribe(topic, noopValidator, func(_ context.Context, msg proto.Message) error {
|
||||
m := msg.(*pb.VoluntaryExit)
|
||||
if m.Epoch != 55 {
|
||||
t.Errorf("Unexpected incoming message: %+v", m)
|
||||
}
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
|
||||
p2p.ReceivePubSub(topic, &pb.VoluntaryExit{Epoch: 55})
|
||||
|
||||
if testutil.WaitTimeout(&wg, time.Second) {
|
||||
t.Fatal("Did not receive PubSub in 1 second")
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,21 @@ func TestBlockHeaderSigningRoot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// See: https://github.com/prysmaticlabs/go-ssz/pull/69
|
||||
func TestBeaconBlock(t *testing.T) {
|
||||
block := ðpb.BeaconBlock{
|
||||
Slot: 55,
|
||||
}
|
||||
enc, err := ssz.Marshal(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dec := ðpb.BeaconBlock{}
|
||||
if err := ssz.Unmarshal(enc, dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func hexDecodeOrDie(t *testing.T, h string) []byte {
|
||||
b, err := hex.DecodeString(h)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ go_library(
|
||||
"json_to_pb_converter.go",
|
||||
"log.go",
|
||||
"tempdir.go",
|
||||
"wait_timeout.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/shared/testutil",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
22
shared/testutil/wait_timeout.go
Normal file
22
shared/testutil/wait_timeout.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WaitTimeout will wait for a WaitGroup to resolve within a timeout interval.
|
||||
// Returns true if the waitgroup exceeded the timeout.
|
||||
func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
wg.Wait()
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
return false
|
||||
case <-time.After(timeout):
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user