node: add GetSignedVAA endpoint
Works: $ curl 'http://localhost:7071/v1/signed_vaa/1/1268b2bf4a[...]/0' {"vaaBytes":"AQAAAAABACbK50nrmgWPtTmRlYf/[...]"} Bug: certusone/wormhole#282 Change-Id: I09eade00c4649c550f06a2efe350d6d9ff9da3ae
This commit is contained in:
parent
8da1eaa6b1
commit
723cf5fe95
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/certusone/wormhole/bridge/pkg/db"
|
||||||
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
||||||
"github.com/certusone/wormhole/bridge/pkg/publicrpc"
|
"github.com/certusone/wormhole/bridge/pkg/publicrpc"
|
||||||
"math"
|
"math"
|
||||||
|
@ -131,7 +132,7 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no
|
||||||
return &nodev1.InjectGovernanceVAAResponse{Digest: digest.Bytes()}, nil
|
return &nodev1.InjectGovernanceVAAResponse{Digest: digest.Bytes()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<- *vaa.VAA, hl *publicrpc.RawHeartbeatConns) (supervisor.Runnable, error) {
|
func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<- *vaa.VAA, hl *publicrpc.RawHeartbeatConns, db *db.Database) (supervisor.Runnable, error) {
|
||||||
// Delete existing UNIX socket, if present.
|
// Delete existing UNIX socket, if present.
|
||||||
fi, err := os.Stat(socketPath)
|
fi, err := os.Stat(socketPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -165,7 +166,7 @@ func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<-
|
||||||
logger: logger.Named("adminservice"),
|
logger: logger.Named("adminservice"),
|
||||||
}
|
}
|
||||||
|
|
||||||
publicrpcService := publicrpc.NewPublicrpcServer(logger, hl)
|
publicrpcService := publicrpc.NewPublicrpcServer(logger, hl, db)
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
grpcServer := grpc.NewServer()
|
||||||
nodev1.RegisterNodePrivilegedServer(grpcServer, nodeService)
|
nodev1.RegisterNodePrivilegedServer(grpcServer, nodeService)
|
||||||
|
|
|
@ -385,13 +385,13 @@ func runBridge(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
// subscriber channel multiplexing for public gPRC streams
|
// subscriber channel multiplexing for public gPRC streams
|
||||||
rawHeartbeatListeners := publicrpc.HeartbeatStreamMultiplexer(logger)
|
rawHeartbeatListeners := publicrpc.HeartbeatStreamMultiplexer(logger)
|
||||||
publicrpcService, err := publicrpcServiceRunnable(logger, *publicRPC, rawHeartbeatListeners)
|
publicrpcService, err := publicrpcServiceRunnable(logger, *publicRPC, rawHeartbeatListeners, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to create publicrpc service socket", zap.Error(err))
|
log.Fatal("failed to create publicrpc service socket", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// local admin service socket
|
// local admin service socket
|
||||||
adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC, rawHeartbeatListeners)
|
adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC, rawHeartbeatListeners, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("failed to create admin service socket", zap.Error(err))
|
logger.Fatal("failed to create admin service socket", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package guardiand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/certusone/wormhole/bridge/pkg/db"
|
||||||
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
||||||
"github.com/certusone/wormhole/bridge/pkg/publicrpc"
|
"github.com/certusone/wormhole/bridge/pkg/publicrpc"
|
||||||
"github.com/certusone/wormhole/bridge/pkg/supervisor"
|
"github.com/certusone/wormhole/bridge/pkg/supervisor"
|
||||||
|
@ -10,7 +11,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func publicrpcServiceRunnable(logger *zap.Logger, listenAddr string, hl *publicrpc.RawHeartbeatConns) (supervisor.Runnable, error) {
|
func publicrpcServiceRunnable(logger *zap.Logger, listenAddr string, hl *publicrpc.RawHeartbeatConns, db *db.Database) (supervisor.Runnable, error) {
|
||||||
l, err := net.Listen("tcp", listenAddr)
|
l, err := net.Listen("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to listen: %w", err)
|
return nil, fmt.Errorf("failed to listen: %w", err)
|
||||||
|
@ -18,7 +19,7 @@ func publicrpcServiceRunnable(logger *zap.Logger, listenAddr string, hl *publicr
|
||||||
|
|
||||||
logger.Info("publicrpc server listening", zap.String("addr", l.Addr().String()))
|
logger.Info("publicrpc server listening", zap.String("addr", l.Addr().String()))
|
||||||
|
|
||||||
rpcServer := publicrpc.NewPublicrpcServer(logger, hl)
|
rpcServer := publicrpc.NewPublicrpcServer(logger, hl, db)
|
||||||
grpcServer := grpc.NewServer()
|
grpcServer := grpc.NewServer()
|
||||||
publicrpcv1.RegisterPublicrpcServer(grpcServer, rpcServer)
|
publicrpcv1.RegisterPublicrpcServer(grpcServer, rpcServer)
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ func (d *Database) StoreSignedVAA(v *vaa.VAA) error {
|
||||||
|
|
||||||
b, _ := v.Marshal()
|
b, _ := v.Marshal()
|
||||||
|
|
||||||
|
// TODO: panic if same VAA is stored with different value
|
||||||
|
|
||||||
err := d.db.Update(func(txn *badger.Txn) error {
|
err := d.db.Update(func(txn *badger.Txn) error {
|
||||||
if err := txn.Set(vaaIDFromVAA(v).Bytes(), b); err != nil {
|
if err := txn.Set(vaaIDFromVAA(v).Bytes(), b); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -74,11 +76,16 @@ func (d *Database) GetSignedVAABytes(id VAAID) (b []byte, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := item.ValueCopy(b); err != nil {
|
if val, err := item.ValueCopy(nil); err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
b = val
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
if err == badger.ErrKeyNotFound {
|
||||||
|
return nil, ErrVAANotFound
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -230,7 +230,8 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
|
||||||
p.logger.Info("signed VAA with quorum",
|
p.logger.Info("signed VAA with quorum",
|
||||||
zap.String("digest", hash),
|
zap.String("digest", hash),
|
||||||
zap.Any("vaa", signed),
|
zap.Any("vaa", signed),
|
||||||
zap.String("bytes", hex.EncodeToString(vaaBytes)))
|
zap.String("bytes", hex.EncodeToString(vaaBytes)),
|
||||||
|
zap.String("message_id", signed.MessageID()))
|
||||||
|
|
||||||
if err := p.db.StoreSignedVAA(signed); err != nil {
|
if err := p.db.StoreSignedVAA(signed); err != nil {
|
||||||
p.logger.Error("failed to store signed VAA", zap.Error(err))
|
p.logger.Error("failed to store signed VAA", zap.Error(err))
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
package publicrpc
|
package publicrpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/certusone/wormhole/bridge/pkg/db"
|
||||||
gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1"
|
gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1"
|
||||||
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1"
|
||||||
|
"github.com/certusone/wormhole/bridge/pkg/vaa"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicrpcServer implements the publicrpc gRPC service.
|
// PublicrpcServer implements the publicrpc gRPC service.
|
||||||
|
@ -11,12 +18,14 @@ type PublicrpcServer struct {
|
||||||
publicrpcv1.UnimplementedPublicrpcServer
|
publicrpcv1.UnimplementedPublicrpcServer
|
||||||
rawHeartbeatListeners *RawHeartbeatConns
|
rawHeartbeatListeners *RawHeartbeatConns
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
db *db.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPublicrpcServer(logger *zap.Logger, rawHeartbeatListeners *RawHeartbeatConns) *PublicrpcServer {
|
func NewPublicrpcServer(logger *zap.Logger, rawHeartbeatListeners *RawHeartbeatConns, db *db.Database) *PublicrpcServer {
|
||||||
return &PublicrpcServer{
|
return &PublicrpcServer{
|
||||||
rawHeartbeatListeners: rawHeartbeatListeners,
|
rawHeartbeatListeners: rawHeartbeatListeners,
|
||||||
logger: logger.Named("publicrpcserver"),
|
logger: logger.Named("publicrpcserver"),
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,3 +49,34 @@ func (s *PublicrpcServer) GetRawHeartbeats(req *publicrpcv1.GetRawHeartbeatsRequ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicrpcServer) GetSignedVAA(ctx context.Context, req *publicrpcv1.GetSignedVAARequest) (*publicrpcv1.GetSignedVAAResponse, error) {
|
||||||
|
address, err := hex.DecodeString(req.MessageId.EmitterAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address: %v", err))
|
||||||
|
}
|
||||||
|
if len(address) != 32 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "address must be 32 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := vaa.Address{}
|
||||||
|
copy(addr[:], address)
|
||||||
|
|
||||||
|
b, err := s.db.GetSignedVAABytes(db.VAAID{
|
||||||
|
EmitterChain: vaa.ChainID(req.MessageId.EmitterChain.Number()),
|
||||||
|
EmitterAddress: addr,
|
||||||
|
Sequence: uint64(req.MessageId.Sequence),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == db.ErrVAANotFound {
|
||||||
|
return nil, status.Error(codes.NotFound, err.Error())
|
||||||
|
}
|
||||||
|
s.logger.Error("failed to fetch VAA", zap.Error(err), zap.Any("request", req))
|
||||||
|
return nil, status.Error(codes.Internal, "internal server error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &publicrpcv1.GetSignedVAAResponse{
|
||||||
|
VaaBytes: b,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -241,6 +241,11 @@ func (v *VAA) Marshal() ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageID returns a human-readable emitter_chain/emitter_address/sequence tuple.
|
||||||
|
func (v *VAA) MessageID() string {
|
||||||
|
return fmt.Sprintf("%d/%s/%d", v.EmitterChain, v.EmitterAddress, v.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VAA) serializeBody() ([]byte, error) {
|
func (v *VAA) serializeBody() ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
MustWrite(buf, binary.BigEndian, uint32(v.Timestamp.Unix()))
|
MustWrite(buf, binary.BigEndian, uint32(v.Timestamp.Unix()))
|
||||||
|
|
|
@ -7,6 +7,24 @@ option go_package = "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1
|
||||||
import "gossip/v1/gossip.proto";
|
import "gossip/v1/gossip.proto";
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
|
|
||||||
|
enum EmitterChain {
|
||||||
|
CHAIN_ID_UNKNOWN = 0;
|
||||||
|
CHAIN_ID_SOLANA = 1;
|
||||||
|
CHAIN_ID_ETHEREUM = 2;
|
||||||
|
CHAIN_ID_TERRA = 3;
|
||||||
|
CHAIN_ID_BSC = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageID is a VAA's globally unique identifier (see data availability design document).
|
||||||
|
message MessageID {
|
||||||
|
// Emitter chain ID.
|
||||||
|
EmitterChain emitter_chain = 1;
|
||||||
|
// Hex-encoded emitter address.
|
||||||
|
string emitter_address = 2;
|
||||||
|
// Sequence number for (emitter_chain, emitter_address).
|
||||||
|
int64 sequence = 3;
|
||||||
|
}
|
||||||
|
|
||||||
// Publicrpc service exposes endpoints to be consumed externally; GUIs, historical record keeping, etc.
|
// Publicrpc service exposes endpoints to be consumed externally; GUIs, historical record keeping, etc.
|
||||||
service Publicrpc {
|
service Publicrpc {
|
||||||
// GetRawHeartbeats rpc endpoint returns a stream of the p2p heartbeat messages received.
|
// GetRawHeartbeats rpc endpoint returns a stream of the p2p heartbeat messages received.
|
||||||
|
@ -17,7 +35,21 @@ service Publicrpc {
|
||||||
get: "/v1/heartbeats:stream_raw"
|
get: "/v1/heartbeats:stream_raw"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rpc GetSignedVAA (GetSignedVAARequest) returns (GetSignedVAAResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v1/signed_vaa/{message_id.emitter_chain}/{message_id.emitter_address}/{message_id.sequence}"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetRawHeartbeatsRequest {
|
message GetRawHeartbeatsRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetSignedVAARequest {
|
||||||
|
MessageID message_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSignedVAAResponse {
|
||||||
|
bytes vaa_bytes = 1;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue