2023-10-12 11:20:42 -07:00
package p2p
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"strings"
"github.com/certusone/wormhole/node/pkg/common"
"github.com/certusone/wormhole/node/pkg/query"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"google.golang.org/protobuf/proto"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"go.uber.org/zap"
)
var (
ccqP2pMessagesSent = promauto . NewCounter (
prometheus . CounterOpts {
Name : "wormhole_ccqp2p_broadcast_messages_sent_total" ,
Help : "Total number of ccq p2p pubsub broadcast messages sent" ,
} )
ccqP2pMessagesReceived = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "wormhole_ccqp2p_broadcast_messages_received_total" ,
Help : "Total number of ccq p2p pubsub broadcast messages received" ,
} , [ ] string { "type" } )
)
type ccqP2p struct {
logger * zap . Logger
2023-11-14 11:52:33 -08:00
h host . Host
th_req * pubsub . Topic
th_resp * pubsub . Topic
sub * pubsub . Subscription
allowedPeers map [ string ] struct { }
p2pComponents * Components
2023-10-12 11:20:42 -07:00
}
func newCcqRunP2p (
logger * zap . Logger ,
allowedPeersStr string ,
2023-11-14 11:52:33 -08:00
components * Components ,
2023-10-12 11:20:42 -07:00
) * ccqP2p {
l := logger . With ( zap . String ( "component" , "ccqp2p" ) )
allowedPeers := make ( map [ string ] struct { } )
for _ , peerID := range strings . Split ( allowedPeersStr , "," ) {
if peerID != "" {
l . Info ( "will allow requests from peer" , zap . String ( "peerID" , peerID ) )
allowedPeers [ peerID ] = struct { } { }
}
}
return & ccqP2p {
2023-11-14 11:52:33 -08:00
logger : l ,
allowedPeers : allowedPeers ,
p2pComponents : components ,
2023-10-12 11:20:42 -07:00
}
}
func ( ccq * ccqP2p ) run (
ctx context . Context ,
priv crypto . PrivKey ,
gk * ecdsa . PrivateKey ,
2023-11-14 08:46:38 -08:00
p2pNetworkID string ,
2023-10-12 11:20:42 -07:00
bootstrapPeers string ,
port uint ,
signedQueryReqC chan <- * gossipv1 . SignedQueryRequest ,
queryResponseReadC <- chan * query . QueryResponsePublication ,
errC chan error ,
) error {
2023-11-14 08:46:38 -08:00
networkID := p2pNetworkID + "/ccq"
2023-10-12 11:20:42 -07:00
var err error
components := DefaultComponents ( )
if components == nil {
return fmt . Errorf ( "components is not initialized" )
}
components . Port = port
ccq . h , err = NewHost ( ccq . logger , ctx , networkID , bootstrapPeers , components , priv )
if err != nil {
return fmt . Errorf ( "failed to create p2p: %w" , err )
}
2023-12-14 11:24:31 -08:00
// Build a map of bootstrap peers so we can always allow subscribe requests from them.
bootstrapPeersMap := map [ string ] struct { } { }
bootstrappers , _ := BootstrapAddrs ( ccq . logger , bootstrapPeers , ccq . h . ID ( ) )
for _ , peer := range bootstrappers {
bootstrapPeersMap [ peer . ID . String ( ) ] = struct { } { }
}
2023-10-12 11:20:42 -07:00
topic_req := fmt . Sprintf ( "%s/%s" , networkID , "ccq_req" )
topic_resp := fmt . Sprintf ( "%s/%s" , networkID , "ccq_resp" )
ccq . logger . Info ( "Creating pubsub topics" , zap . String ( "request_topic" , topic_req ) , zap . String ( "response_topic" , topic_resp ) )
ps , err := pubsub . NewGossipSub ( ctx , ccq . h ,
// We only want to accept subscribes from peers in the allow list.
pubsub . WithPeerFilter ( func ( peerID peer . ID , topic string ) bool {
if len ( ccq . allowedPeers ) == 0 {
return true
}
if _ , found := ccq . allowedPeers [ peerID . String ( ) ] ; found {
return true
}
2023-11-14 11:52:33 -08:00
ccq . p2pComponents . ProtectedHostByGuardianKeyLock . Lock ( )
defer ccq . p2pComponents . ProtectedHostByGuardianKeyLock . Unlock ( )
for _ , guardianPeerID := range ccq . p2pComponents . ProtectedHostByGuardianKey {
if peerID == guardianPeerID {
return true
}
}
2023-12-14 11:24:31 -08:00
if _ , found := bootstrapPeersMap [ peerID . String ( ) ] ; found {
return true
}
2023-11-21 15:59:32 -08:00
ccq . logger . Debug ( "Dropping subscribe attempt from unknown peer" , zap . String ( "peerID" , peerID . String ( ) ) )
2023-10-12 11:20:42 -07:00
return false
} ) )
if err != nil {
return fmt . Errorf ( "failed to create new gossip sub for req: %w" , err )
}
// We want to join and subscribe to the request topic. We will receive messages from there, but never write to it.
ccq . th_req , err = ps . Join ( topic_req )
if err != nil {
return fmt . Errorf ( "failed to join topic_req: %w" , err )
}
// We only want to join the response topic. We will only write to it.
ccq . th_resp , err = ps . Join ( topic_resp )
if err != nil {
return fmt . Errorf ( "failed to join topic_resp: %w" , err )
}
// We only want to accept messages from peers in the allow list.
err = ps . RegisterTopicValidator ( topic_req , func ( ctx context . Context , from peer . ID , msg * pubsub . Message ) bool {
if len ( ccq . allowedPeers ) == 0 {
return true
}
2023-11-17 08:59:06 -08:00
if _ , found := ccq . allowedPeers [ msg . GetFrom ( ) . String ( ) ] ; found {
2023-10-12 11:20:42 -07:00
return true
}
2023-11-21 15:59:32 -08:00
ccq . logger . Debug ( "Dropping message from unknown peer" , zap . String ( "fromPeerID" , from . String ( ) ) , zap . String ( "msgPeerID" , msg . ReceivedFrom . String ( ) ) , zap . String ( "msgFrom" , msg . GetFrom ( ) . String ( ) ) )
2023-10-12 11:20:42 -07:00
return false
} )
if err != nil {
return fmt . Errorf ( "failed to register message filter: %w" , err )
}
// Increase the buffer size to prevent failed delivery to slower subscribers
ccq . sub , err = ccq . th_req . Subscribe ( pubsub . WithBufferSize ( 1024 ) )
if err != nil {
return fmt . Errorf ( "failed to subscribe topic_req: %w" , err )
}
common . StartRunnable ( ctx , errC , false , "ccqp2p_listener" , func ( ctx context . Context ) error {
return ccq . listener ( ctx , signedQueryReqC )
} )
common . StartRunnable ( ctx , errC , false , "ccqp2p_publisher" , func ( ctx context . Context ) error {
return ccq . publisher ( ctx , gk , queryResponseReadC )
} )
ccq . logger . Info ( "Node has been started" , zap . String ( "peer_id" , ccq . h . ID ( ) . String ( ) ) , zap . String ( "addrs" , fmt . Sprintf ( "%v" , ccq . h . Addrs ( ) ) ) )
return nil
}
func ( ccq * ccqP2p ) close ( ) {
ccq . logger . Info ( "entering close" )
if err := ccq . th_req . Close ( ) ; err != nil && ! errors . Is ( err , context . Canceled ) {
ccq . logger . Error ( "Error closing the topic_req" , zap . Error ( err ) )
}
if err := ccq . th_resp . Close ( ) ; err != nil && ! errors . Is ( err , context . Canceled ) {
ccq . logger . Error ( "Error closing the topic_req" , zap . Error ( err ) )
}
ccq . sub . Cancel ( )
if err := ccq . h . Close ( ) ; err != nil {
ccq . logger . Error ( "error closing the host" , zap . Error ( err ) )
}
}
func ( ccq * ccqP2p ) listener ( ctx context . Context , signedQueryReqC chan <- * gossipv1 . SignedQueryRequest ) error {
for {
envelope , err := ccq . sub . Next ( ctx ) // Note: sub.Next(ctx) will return an error once ctx is canceled
if err != nil {
return fmt . Errorf ( "failed to receive pubsub message: %w" , err )
}
var msg gossipv1 . GossipMessage
err = proto . Unmarshal ( envelope . Data , & msg )
if err != nil {
ccq . logger . Info ( "received invalid message" ,
zap . Binary ( "data" , envelope . Data ) ,
zap . String ( "from" , envelope . GetFrom ( ) . String ( ) ) )
ccqP2pMessagesReceived . WithLabelValues ( "invalid" ) . Inc ( )
continue
}
ccq . logger . Info ( "received message" , //TODO: Change to Debug
zap . Any ( "payload" , msg . Message ) ,
zap . Binary ( "raw" , envelope . Data ) ,
zap . String ( "from" , envelope . GetFrom ( ) . String ( ) ) )
switch m := msg . Message . ( type ) {
case * gossipv1 . GossipMessage_SignedQueryRequest :
if err := query . PostSignedQueryRequest ( signedQueryReqC , m . SignedQueryRequest ) ; err != nil {
ccq . logger . Warn ( "failed to handle query request" , zap . Error ( err ) )
}
default :
ccqP2pMessagesReceived . WithLabelValues ( "unknown" ) . Inc ( )
ccq . logger . Warn ( "received unknown message type (running outdated software?)" ,
zap . Any ( "payload" , msg . Message ) ,
zap . Binary ( "raw" , envelope . Data ) ,
zap . String ( "from" , envelope . GetFrom ( ) . String ( ) ) )
}
}
}
func ( ccq * ccqP2p ) publisher ( ctx context . Context , gk * ecdsa . PrivateKey , queryResponseReadC <- chan * query . QueryResponsePublication ) error {
for {
select {
case <- ctx . Done ( ) :
return nil
case msg := <- queryResponseReadC :
msgBytes , err := msg . Marshal ( )
if err != nil {
ccq . logger . Error ( "failed to marshal query response" , zap . Error ( err ) )
continue
}
digest := query . GetQueryResponseDigestFromBytes ( msgBytes )
sig , err := ethcrypto . Sign ( digest . Bytes ( ) , gk )
if err != nil {
panic ( err )
}
envelope := & gossipv1 . GossipMessage {
Message : & gossipv1 . GossipMessage_SignedQueryResponse {
SignedQueryResponse : & gossipv1 . SignedQueryResponse {
QueryResponse : msgBytes ,
Signature : sig ,
} ,
} ,
}
b , err := proto . Marshal ( envelope )
if err != nil {
panic ( err )
}
err = ccq . th_resp . Publish ( ctx , b )
ccqP2pMessagesSent . Inc ( )
if err != nil {
ccq . logger . Error ( "failed to publish query response" ,
2023-11-28 06:51:30 -08:00
zap . String ( "requestSignature" , msg . Signature ( ) ) ,
2023-10-12 11:20:42 -07:00
zap . Any ( "query_response" , msg ) ,
zap . Any ( "signature" , sig ) ,
zap . Error ( err ) ,
)
} else {
ccq . logger . Info ( "published signed query response" , //TODO: Change to Debug
2023-11-28 06:51:30 -08:00
zap . String ( "requestSignature" , msg . Signature ( ) ) ,
2023-10-12 11:20:42 -07:00
zap . Any ( "query_response" , msg ) ,
zap . Any ( "signature" , sig ) ,
)
}
}
}
}