diff --git a/go.mod b/go.mod index e32b9028a44d..236d13f29745 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/ava-labs/firewood-go-ethhash/ffi v0.0.8 // indirect - github.com/ava-labs/simplex v0.0.0-20250626192006-220e6aeacdc1 + github.com/ava-labs/simplex v0.0.0-20250715173145-e4fe035cb9b2 github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index e0b7f2b3c8fa..2c8ecfb0a901 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo= github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 h1:vBMYo+Iazw0rGTr+cwjkBdh5eadLPlv4ywI4lKye3CA= github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= -github.com/ava-labs/simplex v0.0.0-20250626192006-220e6aeacdc1 h1:ipeWExRrhYF7DZ/bcigoQrzo3vZWNZrFx8W+Yg2sJ2Q= -github.com/ava-labs/simplex v0.0.0-20250626192006-220e6aeacdc1/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= +github.com/ava-labs/simplex v0.0.0-20250715173145-e4fe035cb9b2 h1:PZ5PMEDkTbd6NLNiwKWV8nz7QvAM+QC9Rj3/NrL9ICA= +github.com/ava-labs/simplex v0.0.0-20250715173145-e4fe035cb9b2/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/message/messagemock/outbound_message_builder.go b/message/messagemock/outbound_message_builder.go index 89794619de73..c4bedbae9f92 100644 --- a/message/messagemock/outbound_message_builder.go +++ b/message/messagemock/outbound_message_builder.go @@ -390,6 +390,21 @@ func (mr *OutboundMsgBuilderMockRecorder) Put(chainID, requestID, container any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*OutboundMsgBuilder)(nil).Put), chainID, requestID, container) } +// SimplexMessage mocks base method. +func (m *OutboundMsgBuilder) SimplexMessage(msg *p2p.Simplex) (message.OutboundMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SimplexMessage", msg) + ret0, _ := ret[0].(message.OutboundMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SimplexMessage indicates an expected call of SimplexMessage. +func (mr *OutboundMsgBuilderMockRecorder) SimplexMessage(msg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimplexMessage", reflect.TypeOf((*OutboundMsgBuilder)(nil).SimplexMessage), msg) +} + // StateSummaryFrontier mocks base method. func (m *OutboundMsgBuilder) StateSummaryFrontier(chainID ids.ID, requestID uint32, summary []byte) (message.OutboundMessage, error) { m.ctrl.T.Helper() diff --git a/message/outbound_msg_builder.go b/message/outbound_msg_builder.go index 042236c81482..a323044b1953 100644 --- a/message/outbound_msg_builder.go +++ b/message/outbound_msg_builder.go @@ -181,6 +181,10 @@ type OutboundMsgBuilder interface { chainID ids.ID, msg []byte, ) (OutboundMessage, error) + + SimplexMessage( + msg *p2p.Simplex, + ) (OutboundMessage, error) } type outMsgBuilder struct { @@ -725,3 +729,15 @@ func (b *outMsgBuilder) AppGossip(chainID ids.ID, msg []byte) (OutboundMessage, false, ) } + +func (b *outMsgBuilder) SimplexMessage(msg *p2p.Simplex) (OutboundMessage, error) { + return b.builder.createOutbound( + &p2p.Message{ + Message: &p2p.Message_Simplex{ + Simplex: msg, + }, + }, + b.compressionType, + false, + ) +} diff --git a/simplex/block.go b/simplex/block.go index 7cf75093ad46..3d6d9abdc205 100644 --- a/simplex/block.go +++ b/simplex/block.go @@ -67,7 +67,7 @@ type blockDeserializer struct { parser block.Parser } -func (d *blockDeserializer) DeserializeBlock(bytes []byte) (simplex.Block, error) { +func (d *blockDeserializer) DeserializeBlock(ctx context.Context, bytes []byte) (simplex.Block, error) { var canotoBlock canotoSimplexBlock if err := canotoBlock.UnmarshalCanoto(bytes); err != nil { @@ -79,7 +79,7 @@ func (d *blockDeserializer) DeserializeBlock(bytes []byte) (simplex.Block, error return nil, fmt.Errorf("failed to parse protocol metadata: %w", err) } - vmblock, err := d.parser.ParseBlock(context.TODO(), canotoBlock.InnerBlock) + vmblock, err := d.parser.ParseBlock(ctx, canotoBlock.InnerBlock) if err != nil { return nil, err } diff --git a/simplex/block_test.go b/simplex/block_test.go index a57c614b6c9f..6b200d97d8be 100644 --- a/simplex/block_test.go +++ b/simplex/block_test.go @@ -21,7 +21,7 @@ import ( func TestBlockSerialization(t *testing.T) { unexpectedBlockBytes := errors.New("unexpected block bytes") - + ctx := context.Background() testBlock := snowmantest.BuildChild(snowmantest.Genesis) b := &Block{ @@ -88,7 +88,7 @@ func TestBlockSerialization(t *testing.T) { } // Deserialize the block - deserializedBlock, err := deserializer.DeserializeBlock(tt.blockBytes) + deserializedBlock, err := deserializer.DeserializeBlock(ctx, tt.blockBytes) require.ErrorIs(t, err, tt.expectedError) if tt.expectedError == nil { diff --git a/simplex/bls_test.go b/simplex/bls_test.go index cc38dd6d918d..9222e86912c0 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -13,8 +13,7 @@ import ( ) func TestBLSVerifier(t *testing.T) { - config, err := newEngineConfig() - require.NoError(t, err) + config := newEngineConfig(t, 1) signer, verifier := NewBLSAuth(config) otherNodeID := ids.GenerateTestNodeID() @@ -81,7 +80,7 @@ func TestBLSVerifier(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err = verifier.Verify(msg, tt.sig, tt.nodeID) + err := verifier.Verify(msg, tt.sig, tt.nodeID) require.ErrorIs(t, err, tt.expectErr) }) } diff --git a/simplex/comm.go b/simplex/comm.go new file mode 100644 index 000000000000..7abecd408e33 --- /dev/null +++ b/simplex/comm.go @@ -0,0 +1,138 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "errors" + "fmt" + + "github.com/ava-labs/simplex" + "go.uber.org/zap" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/message" + "github.com/ava-labs/avalanchego/proto/pb/p2p" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/networking/sender" + "github.com/ava-labs/avalanchego/subnets" + "github.com/ava-labs/avalanchego/utils/set" +) + +var ( + _ simplex.Communication = (*Comm)(nil) + errNodeNotFound = errors.New("node not found in the validator list") +) + +type Comm struct { + logger simplex.Logger + subnetID ids.ID + chainID ids.ID + // broadcastNodes are the nodes that should receive broadcast messages + broadcastNodes set.Set[ids.NodeID] + // allNodes are the IDs of all the nodes in the subnet + allNodes []simplex.NodeID + + // sender is used to send messages to other nodes + sender sender.ExternalSender + msgBuilder message.OutboundMsgBuilder +} + +func NewComm(config *Config) (*Comm, error) { + if _, ok := config.Validators[config.Ctx.NodeID]; !ok { + config.Log.Warn("Node is not a validator for the subnet", + zap.Stringer("nodeID", config.Ctx.NodeID), + zap.Stringer("chainID", config.Ctx.ChainID), + zap.Stringer("subnetID", config.Ctx.SubnetID), + ) + return nil, fmt.Errorf("our %w: %s", errNodeNotFound, config.Ctx.NodeID) + } + + broadcastNodes := set.NewSet[ids.NodeID](len(config.Validators) - 1) + allNodes := make([]simplex.NodeID, 0, len(config.Validators)) + // grab all the nodes that are validators for the subnet + for _, vd := range config.Validators { + allNodes = append(allNodes, vd.NodeID[:]) + if vd.NodeID == config.Ctx.NodeID { + continue // skip our own node ID + } + + broadcastNodes.Add(vd.NodeID) + } + + return &Comm{ + subnetID: config.Ctx.SubnetID, + broadcastNodes: broadcastNodes, + allNodes: allNodes, + logger: config.Log, + sender: config.Sender, + msgBuilder: config.OutboundMsgBuilder, + chainID: config.Ctx.ChainID, + }, nil +} + +func (c *Comm) Nodes() []simplex.NodeID { + return c.allNodes +} + +func (c *Comm) Send(msg *simplex.Message, destination simplex.NodeID) { + outboundMsg, err := c.simplexMessageToOutboundMessage(msg) + if err != nil { + c.logger.Error("Failed creating message", zap.Error(err)) + return + } + + dest, err := ids.ToNodeID(destination) + if err != nil { + c.logger.Error("Failed to convert destination NodeID", zap.Error(err)) + return + } + + c.sender.Send(outboundMsg, common.SendConfig{NodeIDs: set.Of(dest)}, c.subnetID, subnets.NoOpAllower) +} + +func (c *Comm) Broadcast(msg *simplex.Message) { + outboundMsg, err := c.simplexMessageToOutboundMessage(msg) + if err != nil { + c.logger.Error("Failed creating message", zap.Error(err)) + return + } + + c.sender.Send(outboundMsg, common.SendConfig{NodeIDs: c.broadcastNodes}, c.subnetID, subnets.NoOpAllower) +} + +func (c *Comm) simplexMessageToOutboundMessage(msg *simplex.Message) (message.OutboundMessage, error) { + var simplexMsg *p2p.Simplex + switch { + case msg.VerifiedBlockMessage != nil: + bytes, err := msg.VerifiedBlockMessage.VerifiedBlock.Bytes() + if err != nil { + return nil, fmt.Errorf("failed to serialize block: %w", err) + } + simplexMsg = newBlockProposal(c.chainID, bytes, msg.VerifiedBlockMessage.Vote) + case msg.VoteMessage != nil: + simplexMsg = newVote(c.chainID, msg.VoteMessage) + case msg.EmptyVoteMessage != nil: + simplexMsg = newEmptyVote(c.chainID, msg.EmptyVoteMessage) + case msg.FinalizeVote != nil: + simplexMsg = newFinalizeVote(c.chainID, msg.FinalizeVote) + case msg.Notarization != nil: + simplexMsg = newNotarization(c.chainID, msg.Notarization) + case msg.EmptyNotarization != nil: + simplexMsg = newEmptyNotarization(c.chainID, msg.EmptyNotarization) + case msg.Finalization != nil: + simplexMsg = newFinalization(c.chainID, msg.Finalization) + case msg.ReplicationRequest != nil: + simplexMsg = newReplicationRequest(c.chainID, msg.ReplicationRequest) + case msg.VerifiedReplicationResponse != nil: + msg, err := newReplicationResponse(c.chainID, msg.VerifiedReplicationResponse) + if err != nil { + return nil, fmt.Errorf("failed to create replication response: %w", err) + } + simplexMsg = msg + default: + return nil, fmt.Errorf("unknown message type: %+v", msg) + } + + return c.msgBuilder.SimplexMessage(simplexMsg) +} diff --git a/simplex/comm_test.go b/simplex/comm_test.go new file mode 100644 index 000000000000..40535069ed39 --- /dev/null +++ b/simplex/comm_test.go @@ -0,0 +1,130 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "testing" + "time" + + "github.com/ava-labs/simplex" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/message" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/networking/sender/sendermock" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/set" +) + +var testSimplexMessage = simplex.Message{ + VoteMessage: &simplex.Vote{ + Vote: simplex.ToBeSignedVote{ + BlockHeader: simplex.BlockHeader{ + ProtocolMetadata: simplex.ProtocolMetadata{ + Version: 1, + Epoch: 1, + Round: 1, + Seq: 1, + }, + }, + }, + Signature: simplex.Signature{ + Signer: []byte("dummy_node_id"), + Value: []byte("dummy_signature"), + }, + }, +} + +func TestCommSendMessage(t *testing.T) { + config := newEngineConfig(t, 1) + + destinationNodeID := ids.GenerateTestNodeID() + ctrl := gomock.NewController(t) + sender := sendermock.NewExternalSender(ctrl) + mc, err := message.NewCreator( + prometheus.NewRegistry(), + constants.DefaultNetworkCompressionType, + 10*time.Second, + ) + require.NoError(t, err) + + config.OutboundMsgBuilder = mc + config.Sender = sender + + comm, err := NewComm(config) + require.NoError(t, err) + + outboundMsg, err := mc.SimplexMessage(newVote(config.Ctx.ChainID, testSimplexMessage.VoteMessage)) + require.NoError(t, err) + expectedSendConfig := common.SendConfig{ + NodeIDs: set.Of(destinationNodeID), + } + sender.EXPECT().Send(outboundMsg, expectedSendConfig, comm.subnetID, gomock.Any()) + + comm.Send(&testSimplexMessage, destinationNodeID[:]) +} + +// TestCommBroadcast tests the Broadcast method sends to all nodes in the subnet +// not including the sending node. +func TestCommBroadcast(t *testing.T) { + config := newEngineConfig(t, 3) + + ctrl := gomock.NewController(t) + sender := sendermock.NewExternalSender(ctrl) + mc, err := message.NewCreator( + prometheus.NewRegistry(), + constants.DefaultNetworkCompressionType, + 10*time.Second, + ) + require.NoError(t, err) + + config.OutboundMsgBuilder = mc + config.Sender = sender + + comm, err := NewComm(config) + require.NoError(t, err) + outboundMsg, err := mc.SimplexMessage(newVote(config.Ctx.ChainID, testSimplexMessage.VoteMessage)) + require.NoError(t, err) + nodes := make([]ids.NodeID, 0, len(comm.Nodes())) + for _, node := range comm.Nodes() { + if node.Equals(config.Ctx.NodeID[:]) { + continue // skip the sending node + } + nodes = append(nodes, ids.NodeID(node)) + } + + expectedSendConfig := common.SendConfig{ + NodeIDs: set.Of(nodes...), + } + + sender.EXPECT().Send(outboundMsg, expectedSendConfig, comm.subnetID, gomock.Any()) + + comm.Broadcast(&testSimplexMessage) +} + +func TestCommFailsWithoutCurrentNode(t *testing.T) { + config := newEngineConfig(t, 3) + + ctrl := gomock.NewController(t) + mc, err := message.NewCreator( + prometheus.NewRegistry(), + constants.DefaultNetworkCompressionType, + 10*time.Second, + ) + require.NoError(t, err) + sender := sendermock.NewExternalSender(ctrl) + + config.OutboundMsgBuilder = mc + config.Sender = sender + + // set the curNode to a different nodeID than the one in the config + vdrs := generateTestNodes(t, 3) + config.Validators = newTestValidatorInfo(vdrs) + + _, err = NewComm(config) + require.ErrorIs(t, err, errNodeNotFound) +} diff --git a/simplex/config.go b/simplex/config.go index e95f080e2249..df86202b4309 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -5,6 +5,8 @@ package simplex import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/message" + "github.com/ava-labs/avalanchego/snow/networking/sender" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -14,6 +16,9 @@ type Config struct { Ctx SimplexChainContext Log logging.Logger + Sender sender.ExternalSender + OutboundMsgBuilder message.OutboundMsgBuilder + // Validators is a map of node IDs to their validator information. // This tells the node about the current membership set, and should be consistent // across all nodes in the subnet. @@ -31,6 +36,9 @@ type SimplexChainContext struct { // ChainID is the ID of the chain this context exists within. ChainID ids.ID + // SubnetID is the ID of the subnet this context exists within. + SubnetID ids.ID + // NodeID is the ID of this node NetworkID uint32 } diff --git a/simplex/messages.go b/simplex/messages.go new file mode 100644 index 000000000000..338db81771dc --- /dev/null +++ b/simplex/messages.go @@ -0,0 +1,228 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "github.com/ava-labs/simplex" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/proto/pb/p2p" +) + +func newBlockProposal( + chainID ids.ID, + block []byte, + vote simplex.Vote, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_BlockProposal{ + BlockProposal: &p2p.BlockProposal{ + Block: block, + Vote: &p2p.Vote{ + BlockHeader: blockHeaderToP2P(vote.Vote.BlockHeader), + Signature: &p2p.Signature{ + Signer: vote.Signature.Signer, + Value: vote.Signature.Value, + }, + }, + }, + }, + } +} + +func newVote( + chainID ids.ID, + vote *simplex.Vote, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_Vote{ + Vote: &p2p.Vote{ + BlockHeader: blockHeaderToP2P(vote.Vote.BlockHeader), + Signature: &p2p.Signature{ + Signer: vote.Signature.Signer, + Value: vote.Signature.Value, + }, + }, + }, + } +} + +func newEmptyVote( + chainID ids.ID, + emptyVote *simplex.EmptyVote, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_EmptyVote{ + EmptyVote: &p2p.EmptyVote{ + Metadata: protocolMetadataToP2P(emptyVote.Vote.ProtocolMetadata), + Signature: &p2p.Signature{ + Signer: emptyVote.Signature.Signer, + Value: emptyVote.Signature.Value, + }, + }, + }, + } +} + +func newFinalizeVote( + chainID ids.ID, + finalizeVote *simplex.FinalizeVote, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_FinalizeVote{ + FinalizeVote: &p2p.Vote{ + BlockHeader: blockHeaderToP2P(finalizeVote.Finalization.BlockHeader), + Signature: &p2p.Signature{ + Signer: finalizeVote.Signature.Signer, + Value: finalizeVote.Signature.Value, + }, + }, + }, + } +} + +func newNotarization( + chainID ids.ID, + notarization *simplex.Notarization, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_Notarization{ + Notarization: &p2p.QuorumCertificate{ + BlockHeader: blockHeaderToP2P(notarization.Vote.BlockHeader), + QuorumCertificate: notarization.QC.Bytes(), + }, + }, + } +} + +func newEmptyNotarization( + chainID ids.ID, + emptyNotarization *simplex.EmptyNotarization, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_EmptyNotarization{ + EmptyNotarization: &p2p.EmptyNotarization{ + Metadata: protocolMetadataToP2P(emptyNotarization.Vote.ProtocolMetadata), + QuorumCertificate: emptyNotarization.QC.Bytes(), + }, + }, + } +} + +func newFinalization( + chainID ids.ID, + finalization *simplex.Finalization, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_Finalization{ + Finalization: &p2p.QuorumCertificate{ + BlockHeader: blockHeaderToP2P(finalization.Finalization.BlockHeader), + QuorumCertificate: finalization.QC.Bytes(), + }, + }, + } +} + +func newReplicationRequest( + chainID ids.ID, + replicationRequest *simplex.ReplicationRequest, +) *p2p.Simplex { + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_ReplicationRequest{ + ReplicationRequest: &p2p.ReplicationRequest{ + Seqs: replicationRequest.Seqs, + LatestRound: replicationRequest.LatestRound, + }, + }, + } +} + +func newReplicationResponse( + chainID ids.ID, + replicationResponse *simplex.VerifiedReplicationResponse, +) (*p2p.Simplex, error) { + data := replicationResponse.Data + latestRound := replicationResponse.LatestRound + + qrs := make([]*p2p.QuorumRound, 0, len(data)) + for _, qr := range data { + p2pQR, err := quorumRoundToP2P(&qr) + if err != nil { + return nil, err + } + qrs = append(qrs, p2pQR) + } + + latestQR, err := quorumRoundToP2P(latestRound) + if err != nil { + return nil, err + } + + return &p2p.Simplex{ + ChainId: chainID[:], + Message: &p2p.Simplex_ReplicationResponse{ + ReplicationResponse: &p2p.ReplicationResponse{ + Data: qrs, + LatestRound: latestQR, + }, + }, + }, nil +} + +func blockHeaderToP2P(bh simplex.BlockHeader) *p2p.BlockHeader { + return &p2p.BlockHeader{ + Metadata: protocolMetadataToP2P(bh.ProtocolMetadata), + Digest: bh.Digest[:], + } +} + +func protocolMetadataToP2P(md simplex.ProtocolMetadata) *p2p.ProtocolMetadata { + return &p2p.ProtocolMetadata{ + Version: uint32(md.Version), + Epoch: md.Epoch, + Round: md.Round, + Seq: md.Seq, + Prev: md.Prev[:], + } +} + +func quorumRoundToP2P(qr *simplex.VerifiedQuorumRound) (*p2p.QuorumRound, error) { + p2pQR := &p2p.QuorumRound{} + + if qr.VerifiedBlock != nil { + bytes, err := qr.VerifiedBlock.Bytes() + if err != nil { + return nil, err + } + + p2pQR.Block = bytes + } + if qr.Notarization != nil { + p2pQR.Notarization = &p2p.QuorumCertificate{ + BlockHeader: blockHeaderToP2P(qr.Notarization.Vote.BlockHeader), + QuorumCertificate: qr.Notarization.QC.Bytes(), + } + } + if qr.Finalization != nil { + p2pQR.Finalization = &p2p.QuorumCertificate{ + BlockHeader: blockHeaderToP2P(qr.Finalization.Finalization.BlockHeader), + QuorumCertificate: qr.Finalization.QC.Bytes(), + } + } + if qr.EmptyNotarization != nil { + p2pQR.EmptyNotarization = &p2p.EmptyNotarization{ + Metadata: protocolMetadataToP2P(qr.EmptyNotarization.Vote.ProtocolMetadata), + QuorumCertificate: qr.EmptyNotarization.QC.Bytes(), + } + } + return p2pQR, nil +} diff --git a/simplex/util_test.go b/simplex/util_test.go index 5b167bd17e8d..99d0b6d8cd8c 100644 --- a/simplex/util_test.go +++ b/simplex/util_test.go @@ -4,43 +4,76 @@ package simplex import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" + "github.com/ava-labs/avalanchego/utils/logging" ) -func newTestValidatorInfo(allVds []validators.GetValidatorOutput) map[ids.NodeID]*validators.GetValidatorOutput { - vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(allVds)) - for _, vd := range allVds { - vds[vd.NodeID] = &vd +func newTestValidatorInfo(allNodes []*testNode) map[ids.NodeID]*validators.GetValidatorOutput { + vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(allNodes)) + for _, node := range allNodes { + vds[node.validator.NodeID] = &node.validator } return vds } -func newEngineConfig() (*Config, error) { - ls, err := localsigner.New() - if err != nil { - return nil, err - } +func newEngineConfig(t *testing.T, numNodes uint64) *Config { + return newNetworkConfigs(t, numNodes)[0] +} - nodeID := ids.GenerateTestNodeID() +type testNode struct { + validator validators.GetValidatorOutput + signFunc SignFunc +} - simplexChainContext := SimplexChainContext{ - NodeID: nodeID, - ChainID: ids.GenerateTestID(), - NetworkID: constants.UnitTestID, - } +// newNetworkConfigs creates a slice of Configs for testing purposes. +// they are initialized with a common chainID and a set of validators. +func newNetworkConfigs(t *testing.T, numNodes uint64) []*Config { + require.Positive(t, numNodes) - nodeInfo := validators.GetValidatorOutput{ - NodeID: nodeID, - PublicKey: ls.PublicKey(), + chainID := ids.GenerateTestID() + + testNodes := generateTestNodes(t, numNodes) + + configs := make([]*Config, 0, numNodes) + for _, node := range testNodes { + config := &Config{ + Ctx: SimplexChainContext{ + NodeID: node.validator.NodeID, + ChainID: chainID, + NetworkID: constants.UnitTestID, + }, + Log: logging.NoLog{}, + Validators: newTestValidatorInfo(testNodes), + SignBLS: node.signFunc, + } + configs = append(configs, config) } - return &Config{ - Ctx: simplexChainContext, - Validators: newTestValidatorInfo([]validators.GetValidatorOutput{nodeInfo}), - SignBLS: ls.Sign, - }, nil + return configs +} + +func generateTestNodes(t *testing.T, num uint64) []*testNode { + nodes := make([]*testNode, num) + for i := uint64(0); i < num; i++ { + ls, err := localsigner.New() + require.NoError(t, err) + + nodeID := ids.GenerateTestNodeID() + nodes[i] = &testNode{ + validator: validators.GetValidatorOutput{ + NodeID: nodeID, + PublicKey: ls.PublicKey(), + }, + signFunc: ls.Sign, + } + } + return nodes }