mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-07 20:43:57 -05:00
p2p: batch publish data column sidecars (#16183)
**What type of PR is this?** Feature What does this PR do? Why is it needed? This PR takes @MarcoPolo 's PR at https://github.com/OffchainLabs/prysm/pull/16130 to completion with tests. The description on his PR: """ a relatively small change to optimize network send order. Without this, network writes tend to prioritize sending data for one column to all peers before sending data for later columns (e.g for two columns and 4 peers per column it would send A,A,A,A,B,B,B,B). With batch publishing we can change the write order to round robin across columns (e.g. A,B,A,B,A,B,A,B). In cases where the process is sending at a rate over the network limit, this approach allows at least some copies of the column to propagate through the network. In early simulations with bandwidth limits of 50mbps for the publisher, this improved dissemination by ~20-30%. """ See the issue for some more context. **Which issues(s) does this PR fix?** Fixes https://github.com/OffchainLabs/prysm/issues/16129 Other notes for review Acknowledgements - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [ ] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description with sufficient context for reviewers to understand this PR. - [ ] I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable). --------- Co-authored-by: Marco Munizaga <git@marcopolo.io> Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com> Co-authored-by: kasey <489222+kasey@users.noreply.github.com> Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com> Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,3 +44,6 @@ tmp
|
||||
|
||||
# spectest coverage reports
|
||||
report.txt
|
||||
|
||||
# execution client data
|
||||
execution/
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/pkg/errors"
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -357,58 +358,67 @@ func (s *Service) BroadcastDataColumnSidecars(ctx context.Context, sidecars []bl
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcastDataColumnSidecars broadcasts multiple data column sidecars to the p2p network, after ensuring
|
||||
// there is at least one peer in each needed subnet. If not, it will attempt to find one before broadcasting.
|
||||
// It returns when all broadcasts are complete, or the context is cancelled (whichever comes first).
|
||||
// broadcastDataColumnSidecars broadcasts multiple data column sidecars to the p2p network.
|
||||
// For sidecars with available peers, it uses batch publishing.
|
||||
// For sidecars without peers, it finds peers first and then publishes individually.
|
||||
// Both paths run in parallel. It returns when all broadcasts are complete, or the context is cancelled.
|
||||
func (s *Service) broadcastDataColumnSidecars(ctx context.Context, forkDigest [fieldparams.VersionLength]byte, sidecars []blocks.VerifiedRODataColumn) {
|
||||
type rootAndIndex struct {
|
||||
root [fieldparams.RootLength]byte
|
||||
index uint64
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
timings sync.Map
|
||||
)
|
||||
|
||||
var timings sync.Map
|
||||
logLevel := logrus.GetLevel()
|
||||
|
||||
slotPerRoot := make(map[[fieldparams.RootLength]byte]primitives.Slot, 1)
|
||||
|
||||
topicFunc := func(sidecar blocks.VerifiedRODataColumn) (topic string, wrappedSubIdx uint64, subnet uint64) {
|
||||
subnet = peerdas.ComputeSubnetForDataColumnSidecar(sidecar.Index)
|
||||
topic = dataColumnSubnetToTopic(subnet, forkDigest)
|
||||
wrappedSubIdx = subnet + dataColumnSubnetVal
|
||||
return
|
||||
}
|
||||
|
||||
sidecarsWithPeers := make([]blocks.VerifiedRODataColumn, 0, len(sidecars))
|
||||
var sidecarsWithoutPeers []blocks.VerifiedRODataColumn
|
||||
|
||||
// Categorize sidecars by peer availability.
|
||||
for _, sidecar := range sidecars {
|
||||
slotPerRoot[sidecar.BlockRoot()] = sidecar.Slot()
|
||||
|
||||
wg.Go(func() {
|
||||
// Add tracing to the function.
|
||||
ctx, span := trace.StartSpan(s.ctx, "p2p.broadcastDataColumnSidecars")
|
||||
topic, wrappedSubIdx, _ := topicFunc(sidecar)
|
||||
// Check if we have a peer for this subnet (use RLock for read-only check).
|
||||
mu := s.subnetLocker(wrappedSubIdx)
|
||||
mu.RLock()
|
||||
hasPeer := s.hasPeerWithSubnet(topic)
|
||||
mu.RUnlock()
|
||||
|
||||
if hasPeer {
|
||||
sidecarsWithPeers = append(sidecarsWithPeers, sidecar)
|
||||
continue
|
||||
}
|
||||
|
||||
sidecarsWithoutPeers = append(sidecarsWithoutPeers, sidecar)
|
||||
}
|
||||
|
||||
var batchWg, individualWg sync.WaitGroup
|
||||
|
||||
// Batch publish sidecars that already have peers
|
||||
var messageBatch pubsub.MessageBatch
|
||||
for _, sidecar := range sidecarsWithPeers {
|
||||
batchWg.Go(func() {
|
||||
_, span := trace.StartSpan(ctx, "p2p.broadcastDataColumnSidecars")
|
||||
ctx := trace.NewContext(s.ctx, span)
|
||||
defer span.End()
|
||||
|
||||
// Compute the subnet for this data column sidecar.
|
||||
subnet := peerdas.ComputeSubnetForDataColumnSidecar(sidecar.Index)
|
||||
topic, _, _ := topicFunc(sidecar)
|
||||
|
||||
// Build the topic corresponding to subnet column subnet and this fork digest.
|
||||
topic := dataColumnSubnetToTopic(subnet, forkDigest)
|
||||
|
||||
// Compute the wrapped subnet index.
|
||||
wrappedSubIdx := subnet + dataColumnSubnetVal
|
||||
|
||||
// Find peers if needed.
|
||||
if err := s.findPeersIfNeeded(ctx, wrappedSubIdx, DataColumnSubnetTopicFormat, forkDigest, subnet); err != nil {
|
||||
if err := s.batchObject(ctx, &messageBatch, sidecar, topic); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
log.WithError(err).Error("Cannot find peers if needed")
|
||||
log.WithError(err).Error("Cannot batch data column sidecar")
|
||||
return
|
||||
}
|
||||
|
||||
// Broadcast the data column sidecar to the network.
|
||||
if err := s.broadcastObject(ctx, sidecar, topic); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
log.WithError(err).Error("Cannot broadcast data column sidecar")
|
||||
return
|
||||
}
|
||||
|
||||
// Increase the number of successful broadcasts.
|
||||
dataColumnSidecarBroadcasts.Inc()
|
||||
|
||||
// Record the timing for log purposes.
|
||||
if logLevel >= logrus.DebugLevel {
|
||||
root := sidecar.BlockRoot()
|
||||
timings.Store(rootAndIndex{root: root, index: sidecar.Index}, time.Now())
|
||||
@@ -416,8 +426,50 @@ func (s *Service) broadcastDataColumnSidecars(ctx context.Context, forkDigest [f
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for all broadcasts to finish.
|
||||
wg.Wait()
|
||||
// For sidecars without peers, find peers and publish individually (no batching).
|
||||
for _, sidecar := range sidecarsWithoutPeers {
|
||||
individualWg.Go(func() {
|
||||
_, span := trace.StartSpan(ctx, "p2p.broadcastDataColumnSidecars")
|
||||
ctx := trace.NewContext(s.ctx, span)
|
||||
defer span.End()
|
||||
|
||||
topic, wrappedSubIdx, subnet := topicFunc(sidecar)
|
||||
|
||||
// Find peers for this sidecar's subnet.
|
||||
if err := s.findPeersIfNeeded(ctx, wrappedSubIdx, DataColumnSubnetTopicFormat, forkDigest, subnet); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
log.WithError(err).Error("Cannot find peers if needed")
|
||||
return
|
||||
}
|
||||
|
||||
// Publish individually (not batched) since we just found peers.
|
||||
if err := s.broadcastObject(ctx, sidecar, topic); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
log.WithError(err).Error("Cannot broadcast data column sidecar")
|
||||
return
|
||||
}
|
||||
|
||||
dataColumnSidecarBroadcasts.Inc()
|
||||
|
||||
if logLevel >= logrus.DebugLevel {
|
||||
root := sidecar.BlockRoot()
|
||||
timings.Store(rootAndIndex{root: root, index: sidecar.Index}, time.Now())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for batch to be populated, then publish.
|
||||
batchWg.Wait()
|
||||
if len(sidecarsWithPeers) > 0 {
|
||||
if err := s.pubsub.PublishBatch(&messageBatch); err != nil {
|
||||
log.WithError(err).Error("Cannot publish batch for data column sidecars")
|
||||
} else {
|
||||
dataColumnSidecarBroadcasts.Add(float64(len(sidecarsWithPeers)))
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all individual publishes to complete.
|
||||
individualWg.Wait()
|
||||
|
||||
// The rest of this function is only for debug logging purposes.
|
||||
if logLevel < logrus.DebugLevel {
|
||||
@@ -504,28 +556,68 @@ func (s *Service) findPeersIfNeeded(
|
||||
return nil
|
||||
}
|
||||
|
||||
// method to broadcast messages to other peers in our gossip mesh.
|
||||
// encodeGossipMessage encodes an object for gossip transmission.
|
||||
// It returns the encoded bytes and the full topic with protocol suffix.
|
||||
func (s *Service) encodeGossipMessage(obj ssz.Marshaler, topic string) ([]byte, string, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := s.Encoding().EncodeGossip(buf, obj); err != nil {
|
||||
return nil, "", fmt.Errorf("could not encode message: %w", err)
|
||||
}
|
||||
return buf.Bytes(), topic + s.Encoding().ProtocolSuffix(), nil
|
||||
}
|
||||
|
||||
// broadcastObject broadcasts a message to other peers in our gossip mesh.
|
||||
func (s *Service) broadcastObject(ctx context.Context, obj ssz.Marshaler, topic string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "p2p.broadcastObject")
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(trace.StringAttribute("topic", topic))
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := s.Encoding().EncodeGossip(buf, obj); err != nil {
|
||||
err := errors.Wrap(err, "could not encode message")
|
||||
data, fullTopic, err := s.encodeGossipMessage(obj, topic)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if span.IsRecording() {
|
||||
id := hash.FastSum64(buf.Bytes())
|
||||
messageLen := int64(buf.Len())
|
||||
id := hash.FastSum64(data)
|
||||
messageLen := int64(len(data))
|
||||
// lint:ignore uintcast -- It's safe to do this for tracing.
|
||||
iid := int64(id)
|
||||
span = trace.AddMessageSendEvent(span, iid, messageLen /*uncompressed*/, messageLen /*compressed*/)
|
||||
}
|
||||
if err := s.PublishToTopic(ctx, topic+s.Encoding().ProtocolSuffix(), buf.Bytes()); err != nil {
|
||||
|
||||
if err := s.PublishToTopic(ctx, fullTopic, data); err != nil {
|
||||
err := errors.Wrap(err, "could not publish message")
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// batchObject adds an object to a message batch for a future broadcast.
|
||||
// The caller MUST publish the batch after all messages have been added.
|
||||
func (s *Service) batchObject(ctx context.Context, batch *pubsub.MessageBatch, obj ssz.Marshaler, topic string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "p2p.batchObject")
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(trace.StringAttribute("topic", topic))
|
||||
|
||||
data, fullTopic, err := s.encodeGossipMessage(obj, topic)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if span.IsRecording() {
|
||||
id := hash.FastSum64(data)
|
||||
messageLen := int64(len(data))
|
||||
// lint:ignore uintcast -- It's safe to do this for tracing.
|
||||
iid := int64(id)
|
||||
span = trace.AddMessageSendEvent(span, iid, messageLen /*uncompressed*/, messageLen /*compressed*/)
|
||||
}
|
||||
|
||||
if err := s.addToBatch(ctx, batch, fullTopic, data); err != nil {
|
||||
err := errors.Wrap(err, "could not publish message")
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -787,3 +789,190 @@ func TestService_BroadcastDataColumn(t *testing.T) {
|
||||
require.NoError(t, service.Encoding().DecodeGossip(msg.Data, &result))
|
||||
require.DeepEqual(t, &result, verifiedRoSidecar)
|
||||
}
|
||||
|
||||
type topicInvoked struct {
|
||||
topic string
|
||||
pid peer.ID
|
||||
}
|
||||
|
||||
// rpcOrderTracer is a RawTracer implementation that captures the order of SendRPC calls.
|
||||
// It records the topics of messages sent via pubsub to verify round-robin ordering.
|
||||
type rpcOrderTracer struct {
|
||||
mu sync.Mutex
|
||||
invoked []*topicInvoked
|
||||
byTopic map[string][]peer.ID
|
||||
}
|
||||
|
||||
func (t *rpcOrderTracer) SendRPC(rpc *pubsub.RPC, pid peer.ID) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for _, msg := range rpc.GetPublish() {
|
||||
invoked := &topicInvoked{topic: msg.GetTopic(), pid: pid}
|
||||
t.invoked = append(t.invoked, invoked)
|
||||
t.byTopic[invoked.topic] = append(t.byTopic[invoked.topic], invoked.pid)
|
||||
}
|
||||
}
|
||||
|
||||
func newRpcOrderTracer() *rpcOrderTracer {
|
||||
return &rpcOrderTracer{byTopic: make(map[string][]peer.ID)}
|
||||
}
|
||||
|
||||
func (t *rpcOrderTracer) getTopics() []string {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
result := make([]string, len(t.invoked))
|
||||
for i := range t.invoked {
|
||||
result[i] = t.invoked[i].topic
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// No-op implementations for other RawTracer methods.
|
||||
func (*rpcOrderTracer) AddPeer(peer.ID, protocol.ID) {}
|
||||
func (*rpcOrderTracer) RemovePeer(peer.ID) {}
|
||||
func (*rpcOrderTracer) Join(string) {}
|
||||
func (*rpcOrderTracer) Leave(string) {}
|
||||
func (*rpcOrderTracer) Graft(peer.ID, string) {}
|
||||
func (*rpcOrderTracer) Prune(peer.ID, string) {}
|
||||
func (*rpcOrderTracer) ValidateMessage(*pubsub.Message) {}
|
||||
func (*rpcOrderTracer) DeliverMessage(*pubsub.Message) {}
|
||||
func (*rpcOrderTracer) RejectMessage(*pubsub.Message, string) {}
|
||||
func (*rpcOrderTracer) DuplicateMessage(*pubsub.Message) {}
|
||||
func (*rpcOrderTracer) ThrottlePeer(peer.ID) {}
|
||||
func (*rpcOrderTracer) RecvRPC(*pubsub.RPC) {}
|
||||
func (*rpcOrderTracer) DropRPC(*pubsub.RPC, peer.ID) {}
|
||||
func (*rpcOrderTracer) UndeliverableMessage(*pubsub.Message) {}
|
||||
|
||||
// TestService_BroadcastDataColumnRoundRobin verifies that when broadcasting multiple
|
||||
// data column sidecars, messages are interleaved in round-robin order by column index
|
||||
// rather than sending all copies of one column before the next.
|
||||
//
|
||||
// Without batch publishing: A,A,A,A,B,B,B,B (all peers for column A, then all for column B)
|
||||
// With batch publishing: A,B,A,B,A,B,A,B (interleaved by message ID)
|
||||
func TestService_BroadcastDataColumnRoundRobin(t *testing.T) {
|
||||
const (
|
||||
port = 2100
|
||||
topicFormat = DataColumnSubnetTopicFormat
|
||||
)
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
// Load the KZG trust setup.
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
gFlags := new(flags.GlobalFlags)
|
||||
gFlags.MinimumPeersPerSubnet = 1
|
||||
flags.Init(gFlags)
|
||||
defer flags.Init(new(flags.GlobalFlags))
|
||||
|
||||
// Create a tracer to capture the order of SendRPC calls.
|
||||
tracer := newRpcOrderTracer()
|
||||
|
||||
// Create the publisher node with the tracer injected.
|
||||
p1 := p2ptest.NewTestP2PWithPubsubOptions(t, []pubsub.Option{pubsub.WithRawTracer(tracer)})
|
||||
|
||||
// Create subscriber peers.
|
||||
expectedPeers := []*p2ptest.TestP2P{
|
||||
p2ptest.NewTestP2P(t),
|
||||
p2ptest.NewTestP2P(t),
|
||||
}
|
||||
|
||||
// Connect peers.
|
||||
for _, p := range expectedPeers {
|
||||
p1.Connect(p)
|
||||
}
|
||||
require.NotEqual(t, 0, len(p1.BHost.Network().Peers()), "No peers")
|
||||
|
||||
// Create a host for discovery.
|
||||
_, pkey, ipAddr := createHost(t, port)
|
||||
|
||||
// Create a shared DB for the service.
|
||||
db := testDB.SetupDB(t)
|
||||
|
||||
// Create and close the custody info channel immediately since custodyInfo is already set.
|
||||
custodyInfoSet := make(chan struct{})
|
||||
close(custodyInfoSet)
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
host: p1.BHost,
|
||||
pubsub: p1.PubSub(),
|
||||
joinedTopics: map[string]*pubsub.Topic{},
|
||||
cfg: &Config{DB: db},
|
||||
genesisTime: time.Now(),
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
subnetsLock: make(map[uint64]*sync.RWMutex),
|
||||
subnetsLockLock: sync.Mutex{},
|
||||
peers: peers.NewStatus(ctx, &peers.StatusConfig{ScorerParams: &scorers.Config{}}),
|
||||
custodyInfo: &custodyInfo{},
|
||||
custodyInfoSet: custodyInfoSet,
|
||||
}
|
||||
|
||||
// Create a listener for discovery.
|
||||
listener, err := service.startDiscoveryV5(ipAddr, pkey)
|
||||
require.NoError(t, err)
|
||||
service.dv5Listener = listener
|
||||
|
||||
digest, err := service.currentForkDigest()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create multiple data column sidecars with different column indices.
|
||||
// Use indices that map to different subnets: 0, 32, 64 (assuming 128 columns and 64 subnets).
|
||||
columnIndices := []uint64{0, 32, 64}
|
||||
params := make([]util.DataColumnParam, len(columnIndices))
|
||||
for i, idx := range columnIndices {
|
||||
params[i] = util.DataColumnParam{Index: idx}
|
||||
}
|
||||
_, verifiedRoSidecars := util.CreateTestVerifiedRoDataColumnSidecars(t, params)
|
||||
|
||||
expectedTopics := make(map[string]bool)
|
||||
// Subscribe peers to the relevant topics.
|
||||
for _, idx := range columnIndices {
|
||||
subnet := peerdas.ComputeSubnetForDataColumnSidecar(idx)
|
||||
topic := fmt.Sprintf(topicFormat, digest, subnet) + service.Encoding().ProtocolSuffix()
|
||||
for _, p := range expectedPeers {
|
||||
_, err = p.SubscribeToTopic(topic)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
expectedTopics[topic] = true
|
||||
}
|
||||
// libp2p needs some time to establish mesh connections.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Broadcast all sidecars.
|
||||
err = service.BroadcastDataColumnSidecars(ctx, verifiedRoSidecars)
|
||||
require.NoError(t, err)
|
||||
// Give some time for messages to be sent.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
topics := tracer.getTopics()
|
||||
if len(topics) == 0 {
|
||||
t.Fatal("Expected at least one message for each topic to be sent to each peer")
|
||||
}
|
||||
|
||||
unseen := make(map[string]bool)
|
||||
for k := range expectedTopics {
|
||||
unseen[k] = true
|
||||
}
|
||||
// Verify round-robin invariant: before all message IDs are seen, no message ID may be repeated.
|
||||
// In round-robin order, we should see each topic once before any topic repeats.
|
||||
for _, topic := range topics {
|
||||
if !expectedTopics[topic] {
|
||||
continue
|
||||
}
|
||||
if !unseen[topic] {
|
||||
t.Errorf("Topic %s repeated before all topics were seen once. This violates round-robin ordering.", topic)
|
||||
}
|
||||
delete(unseen, topic)
|
||||
if len(unseen) == 0 {
|
||||
break // all have been seen
|
||||
}
|
||||
}
|
||||
require.Equal(t, 0, len(unseen))
|
||||
|
||||
// Verify that we actually saw all expected topics.
|
||||
for topic := range expectedTopics {
|
||||
require.Equal(t, len(expectedPeers), len(tracer.byTopic[topic]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,27 @@ func (s *Service) PublishToTopic(ctx context.Context, topic string, data []byte,
|
||||
}
|
||||
}
|
||||
|
||||
// addToBatch joins (if necessary) a topic and adds the message to a message batch.
|
||||
func (s *Service) addToBatch(ctx context.Context, batch *pubsub.MessageBatch, topic string, data []byte, opts ...pubsub.PubOpt) error {
|
||||
topicHandle, err := s.JoinTopic(topic)
|
||||
if err != nil {
|
||||
return fmt.Errorf("joining topic: %w", err)
|
||||
}
|
||||
|
||||
// Wait for at least 1 peer to be available to receive the published message.
|
||||
for {
|
||||
if flags.Get().MinimumSyncPeers == 0 || len(topicHandle.ListPeers()) > 0 {
|
||||
return topicHandle.AddToBatch(ctx, batch, data, opts...)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Wrapf(ctx.Err(), "unable to find requisite number of peers for topic %s, 0 peers found to publish to", topic)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// reenter the for loop after 100ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeToTopic joins (if necessary) and subscribes to PubSub topic.
|
||||
func (s *Service) SubscribeToTopic(topic string, opts ...pubsub.SubOpt) (*pubsub.Subscription, error) {
|
||||
s.awaitStateInitialized() // Genesis time and genesis validators root are required to subscribe.
|
||||
|
||||
@@ -70,6 +70,11 @@ type TestP2P struct {
|
||||
|
||||
// NewTestP2P initializes a new p2p test service.
|
||||
func NewTestP2P(t *testing.T, userOptions ...config.Option) *TestP2P {
|
||||
return NewTestP2PWithPubsubOptions(t, nil, userOptions...)
|
||||
}
|
||||
|
||||
// NewTestP2PWithPubsubOptions initializes a new p2p test service with custom pubsub options.
|
||||
func NewTestP2PWithPubsubOptions(t *testing.T, pubsubOpts []pubsub.Option, userOptions ...config.Option) *TestP2P {
|
||||
ctx := context.Background()
|
||||
options := []config.Option{
|
||||
libp2p.ResourceManager(&network.NullResourceManager{}),
|
||||
@@ -84,10 +89,14 @@ func NewTestP2P(t *testing.T, userOptions ...config.Option) *TestP2P {
|
||||
|
||||
h, err := libp2p.New(options...)
|
||||
require.NoError(t, err)
|
||||
ps, err := pubsub.NewFloodSub(ctx, h,
|
||||
|
||||
defaultPubsubOpts := []pubsub.Option{
|
||||
pubsub.WithMessageSigning(false),
|
||||
pubsub.WithStrictSignatureVerification(false),
|
||||
)
|
||||
}
|
||||
allPubsubOpts := append(defaultPubsubOpts, pubsubOpts...)
|
||||
|
||||
ps, err := pubsub.NewGossipSub(ctx, h, allPubsubOpts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Batch publish data columns for faster data propogation.
|
||||
Reference in New Issue
Block a user