|
@@ -2,6 +2,7 @@ package spy
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"context"
|
|
"context"
|
|
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"net"
|
|
"net"
|
|
|
"net/http"
|
|
"net/http"
|
|
@@ -48,6 +49,9 @@ var (
|
|
|
spyRPC *string
|
|
spyRPC *string
|
|
|
|
|
|
|
|
sendTimeout *time.Duration
|
|
sendTimeout *time.Duration
|
|
|
|
|
+
|
|
|
|
|
+ ethRPC *string
|
|
|
|
|
+ ethContract *string
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
func init() {
|
|
@@ -65,6 +69,9 @@ func init() {
|
|
|
spyRPC = SpyCmd.Flags().String("spyRPC", "", "Listen address for gRPC interface")
|
|
spyRPC = SpyCmd.Flags().String("spyRPC", "", "Listen address for gRPC interface")
|
|
|
|
|
|
|
|
sendTimeout = SpyCmd.Flags().Duration("sendTimeout", 5*time.Second, "Timeout for sending a message to a subscriber")
|
|
sendTimeout = SpyCmd.Flags().Duration("sendTimeout", 5*time.Second, "Timeout for sending a message to a subscriber")
|
|
|
|
|
+
|
|
|
|
|
+ ethRPC = SpyCmd.Flags().String("ethRPC", "", "Ethereum RPC for verifying VAAs (optional)")
|
|
|
|
|
+ ethContract = SpyCmd.Flags().String("ethContract", "", "Ethereum core bridge address for verifying VAAs (required if ethRPC is specified)")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// SpyCmd represents the node command
|
|
// SpyCmd represents the node command
|
|
@@ -79,6 +86,7 @@ type spyServer struct {
|
|
|
logger *zap.Logger
|
|
logger *zap.Logger
|
|
|
subsSignedVaa map[string]*subscriptionSignedVaa
|
|
subsSignedVaa map[string]*subscriptionSignedVaa
|
|
|
subsSignedVaaMu sync.Mutex
|
|
subsSignedVaaMu sync.Mutex
|
|
|
|
|
+ vaaVerifier *VaaVerifier
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type message struct {
|
|
type message struct {
|
|
@@ -103,15 +111,23 @@ func (s *spyServer) PublishSignedVAA(vaaBytes []byte) error {
|
|
|
defer s.subsSignedVaaMu.Unlock()
|
|
defer s.subsSignedVaaMu.Unlock()
|
|
|
|
|
|
|
|
var v *vaa.VAA
|
|
var v *vaa.VAA
|
|
|
|
|
+ var err error
|
|
|
|
|
+ verified := s.vaaVerifier == nil
|
|
|
|
|
|
|
|
for _, sub := range s.subsSignedVaa {
|
|
for _, sub := range s.subsSignedVaa {
|
|
|
if len(sub.filters) == 0 {
|
|
if len(sub.filters) == 0 {
|
|
|
|
|
+ if !verified {
|
|
|
|
|
+ verified = true
|
|
|
|
|
+ v, err = s.verifyVAA(v, vaaBytes)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
sub.ch <- message{vaaBytes: vaaBytes}
|
|
sub.ch <- message{vaaBytes: vaaBytes}
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if v == nil {
|
|
if v == nil {
|
|
|
- var err error
|
|
|
|
|
v, err = vaa.Unmarshal(vaaBytes)
|
|
v, err = vaa.Unmarshal(vaaBytes)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
@@ -120,6 +136,13 @@ func (s *spyServer) PublishSignedVAA(vaaBytes []byte) error {
|
|
|
|
|
|
|
|
for _, fi := range sub.filters {
|
|
for _, fi := range sub.filters {
|
|
|
if fi.chainId == v.EmitterChain && fi.emitterAddr == v.EmitterAddress {
|
|
if fi.chainId == v.EmitterChain && fi.emitterAddr == v.EmitterAddress {
|
|
|
|
|
+ if !verified {
|
|
|
|
|
+ verified = true
|
|
|
|
|
+ v, err = s.verifyVAA(v, vaaBytes)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
sub.ch <- message{vaaBytes: vaaBytes}
|
|
sub.ch <- message{vaaBytes: vaaBytes}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -129,6 +152,31 @@ func (s *spyServer) PublishSignedVAA(vaaBytes []byte) error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (s *spyServer) verifyVAA(v *vaa.VAA, vaaBytes []byte) (*vaa.VAA, error) {
|
|
|
|
|
+ if s.vaaVerifier == nil {
|
|
|
|
|
+ panic("verifier is nil")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if v == nil {
|
|
|
|
|
+ var err error
|
|
|
|
|
+ v, err = vaa.Unmarshal(vaaBytes)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return v, fmt.Errorf(`failed to unmarshal VAA: %w`, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ valid, err := s.vaaVerifier.VerifySignatures(v)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return v, fmt.Errorf(`failed to verify VAA: %w`, err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !valid {
|
|
|
|
|
+ return v, errors.New(`invalid VAA signature`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return v, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (s *spyServer) SubscribeSignedVAA(req *spyv1.SubscribeSignedVAARequest, resp spyv1.SpyRPCService_SubscribeSignedVAAServer) error {
|
|
func (s *spyServer) SubscribeSignedVAA(req *spyv1.SubscribeSignedVAARequest, resp spyv1.SpyRPCService_SubscribeSignedVAAServer) error {
|
|
|
var fi []filterSignedVaa
|
|
var fi []filterSignedVaa
|
|
|
if req.Filters != nil {
|
|
if req.Filters != nil {
|
|
@@ -311,6 +359,17 @@ func runSpy(cmd *cobra.Command, args []string) {
|
|
|
logger.Fatal("failed to start RPC server", zap.Error(err))
|
|
logger.Fatal("failed to start RPC server", zap.Error(err))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // VAA verifier (optional)
|
|
|
|
|
+ if *ethRPC != "" {
|
|
|
|
|
+ if *ethContract == "" {
|
|
|
|
|
+ logger.Fatal(`If "--ethRPC" is specified, "--ethContract" must also be specified`)
|
|
|
|
|
+ }
|
|
|
|
|
+ s.vaaVerifier = NewVaaVerifier(logger, *ethRPC, *ethContract)
|
|
|
|
|
+ if err := s.vaaVerifier.GetInitialGuardianSet(); err != nil {
|
|
|
|
|
+ logger.Fatal(`Failed to read initial guardian set for VAA verification`, zap.Error(err))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Ignore observations
|
|
// Ignore observations
|
|
|
go func() {
|
|
go func() {
|
|
|
for {
|
|
for {
|
|
@@ -344,7 +403,7 @@ func runSpy(cmd *cobra.Command, args []string) {
|
|
|
logger.Info("Received signed VAA",
|
|
logger.Info("Received signed VAA",
|
|
|
zap.Any("vaa", v.Vaa))
|
|
zap.Any("vaa", v.Vaa))
|
|
|
if err := s.PublishSignedVAA(v.Vaa); err != nil {
|
|
if err := s.PublishSignedVAA(v.Vaa); err != nil {
|
|
|
- logger.Error("failed to publish signed VAA", zap.Error(err))
|
|
|
|
|
|
|
+ logger.Error("failed to publish signed VAA", zap.Error(err), zap.Any("vaa", v.Vaa))
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|