2021-08-09 22:29:21 -07:00
|
|
|
package reporter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
2021-08-26 01:35:09 -07:00
|
|
|
"github.com/certusone/wormhole/node/pkg/supervisor"
|
|
|
|
"github.com/certusone/wormhole/node/pkg/vaa"
|
2021-08-09 22:29:21 -07:00
|
|
|
"go.uber.org/zap"
|
|
|
|
"google.golang.org/api/option"
|
|
|
|
|
|
|
|
"cloud.google.com/go/bigtable"
|
2021-11-04 02:00:52 -07:00
|
|
|
"cloud.google.com/go/pubsub"
|
2021-08-09 22:29:21 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type BigTableConnectionConfig struct {
|
|
|
|
GcpProjectID string
|
|
|
|
GcpInstanceName string
|
|
|
|
GcpKeyFilePath string
|
|
|
|
TableName string
|
2021-11-04 02:00:52 -07:00
|
|
|
TopicName string
|
2021-08-09 22:29:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type bigTableWriter struct {
|
|
|
|
connectionConfig *BigTableConnectionConfig
|
|
|
|
events *AttestationEventReporter
|
|
|
|
}
|
|
|
|
|
|
|
|
// rowKey returns a string with the input vales delimited by colons.
|
2021-12-27 10:21:47 -08:00
|
|
|
func MakeRowKey(emitterChain vaa.ChainID, emitterAddress vaa.Address, sequence uint64) string {
|
2021-09-21 09:08:07 -07:00
|
|
|
// left-pad the sequence with zeros to 16 characters, because bigtable keys are stored lexicographically
|
|
|
|
return fmt.Sprintf("%d:%s:%016d", emitterChain, emitterAddress, sequence)
|
2021-08-09 22:29:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func BigTableWriter(events *AttestationEventReporter, connectionConfig *BigTableConnectionConfig) func(ctx context.Context) error {
|
|
|
|
return func(ctx context.Context) error {
|
|
|
|
|
|
|
|
e := &bigTableWriter{events: events, connectionConfig: connectionConfig}
|
|
|
|
|
|
|
|
hostname, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
errC := make(chan error)
|
|
|
|
logger := supervisor.Logger(ctx)
|
|
|
|
|
|
|
|
client, err := bigtable.NewClient(ctx,
|
|
|
|
e.connectionConfig.GcpProjectID,
|
|
|
|
e.connectionConfig.GcpInstanceName,
|
|
|
|
option.WithCredentialsFile(e.connectionConfig.GcpKeyFilePath))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create BigTable client: %w", err)
|
|
|
|
}
|
|
|
|
tbl := client.Open(e.connectionConfig.TableName)
|
|
|
|
|
2022-02-01 00:56:36 -08:00
|
|
|
pubsubClient, err := pubsub.NewClient(ctx,
|
|
|
|
e.connectionConfig.GcpProjectID,
|
|
|
|
option.WithCredentialsFile(e.connectionConfig.GcpKeyFilePath))
|
2021-11-04 02:00:52 -07:00
|
|
|
if err != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("failed to create GCP PubSub client", zap.Error(err))
|
|
|
|
return fmt.Errorf("failed to create GCP PubSub client: %w", err)
|
2021-11-04 02:00:52 -07:00
|
|
|
}
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Info("GCP PubSub.NewClient initialized")
|
|
|
|
|
2021-11-04 02:00:52 -07:00
|
|
|
pubsubTopic := pubsubClient.Topic(e.connectionConfig.TopicName)
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Info("GCP PubSub.Topic initialized",
|
|
|
|
zap.String("Topic", e.connectionConfig.TopicName))
|
2021-08-09 22:29:21 -07:00
|
|
|
// call to subscribe to event channels
|
|
|
|
sub := e.events.Subscribe()
|
|
|
|
logger.Info("subscribed to AttestationEvents")
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case msg := <-sub.Channels.MessagePublicationC:
|
|
|
|
colFam := "MessagePublication"
|
|
|
|
mutation := bigtable.NewMutation()
|
|
|
|
ts := bigtable.Now()
|
|
|
|
|
|
|
|
mutation.Set(colFam, "Version", ts, []byte(fmt.Sprint(msg.VAA.Version)))
|
|
|
|
mutation.Set(colFam, "GuardianSetIndex", ts, []byte(fmt.Sprint(msg.VAA.GuardianSetIndex)))
|
|
|
|
mutation.Set(colFam, "Timestamp", ts, []byte(ts.Time().String()))
|
|
|
|
mutation.Set(colFam, "Nonce", ts, []byte(fmt.Sprint(msg.VAA.Nonce)))
|
|
|
|
mutation.Set(colFam, "EmitterChain", ts, []byte(msg.VAA.EmitterChain.String()))
|
|
|
|
mutation.Set(colFam, "EmitterAddress", ts, []byte(msg.VAA.EmitterAddress.String()))
|
|
|
|
mutation.Set(colFam, "Sequence", ts, []byte(fmt.Sprint(msg.VAA.Sequence)))
|
|
|
|
mutation.Set(colFam, "InitiatingTxID", ts, []byte(msg.InitiatingTxID.Hex()))
|
|
|
|
mutation.Set(colFam, "Payload", ts, msg.VAA.Payload)
|
|
|
|
|
|
|
|
mutation.Set(colFam, "ReporterHostname", ts, []byte(hostname))
|
|
|
|
|
|
|
|
// filter to see if there is a row with this rowKey, and has a value for EmitterAddress
|
|
|
|
filter := bigtable.ChainFilters(
|
|
|
|
bigtable.FamilyFilter(colFam),
|
|
|
|
bigtable.ColumnFilter("EmitterAddress"))
|
|
|
|
conditionalMutation := bigtable.NewCondMutation(filter, nil, mutation)
|
|
|
|
|
2021-12-27 10:21:47 -08:00
|
|
|
rowKey := MakeRowKey(msg.VAA.EmitterChain, msg.VAA.EmitterAddress, msg.VAA.Sequence)
|
2021-08-09 22:29:21 -07:00
|
|
|
err := tbl.Apply(ctx, rowKey, conditionalMutation)
|
|
|
|
if err != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("Failed to write message publication to BigTable",
|
2021-08-09 22:29:21 -07:00
|
|
|
zap.String("rowKey", rowKey),
|
|
|
|
zap.String("columnFamily", colFam),
|
|
|
|
zap.Error(err))
|
|
|
|
errC <- err
|
|
|
|
}
|
|
|
|
case msg := <-sub.Channels.VAAQuorumC:
|
|
|
|
colFam := "QuorumState"
|
|
|
|
mutation := bigtable.NewMutation()
|
|
|
|
ts := bigtable.Now()
|
|
|
|
|
|
|
|
b, marshalErr := msg.Marshal()
|
|
|
|
if marshalErr != nil {
|
|
|
|
logger.Error("failed to marshal VAAQuorum VAA.")
|
|
|
|
}
|
|
|
|
mutation.Set(colFam, "SignedVAA", ts, b)
|
|
|
|
|
|
|
|
// filter to see if this row already has the signature.
|
|
|
|
filter := bigtable.ChainFilters(
|
|
|
|
bigtable.FamilyFilter(colFam),
|
|
|
|
bigtable.ColumnFilter("SignedVAA"))
|
|
|
|
conditionalMutation := bigtable.NewCondMutation(filter, nil, mutation)
|
|
|
|
|
2021-12-27 10:21:47 -08:00
|
|
|
rowKey := MakeRowKey(msg.EmitterChain, msg.EmitterAddress, msg.Sequence)
|
2021-08-09 22:29:21 -07:00
|
|
|
err := tbl.Apply(ctx, rowKey, conditionalMutation)
|
|
|
|
if err != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("Failed to write persistence info to BigTable",
|
2021-08-09 22:29:21 -07:00
|
|
|
zap.String("rowKey", rowKey),
|
|
|
|
zap.String("columnFamily", colFam),
|
|
|
|
zap.Error(err))
|
|
|
|
errC <- err
|
|
|
|
}
|
2022-01-25 18:41:38 -08:00
|
|
|
publishResult := pubsubTopic.Publish(ctx, &pubsub.Message{
|
2021-11-04 02:00:52 -07:00
|
|
|
Data: []byte(b),
|
|
|
|
})
|
2022-01-25 22:35:56 -08:00
|
|
|
if _, err = publishResult.Get(ctx); err != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("Failed getting GCP PubSub publish reciept",
|
|
|
|
zap.String("rowKey", rowKey),
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
2021-08-09 22:29:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
e.events.Unsubscribe(sub.ClientId)
|
|
|
|
if err = client.Close(); err != nil {
|
|
|
|
logger.Error("Could not close BigTable client", zap.Error(err))
|
|
|
|
}
|
2021-11-04 02:00:52 -07:00
|
|
|
if pubsubErr := pubsubClient.Close(); pubsubErr != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("Could not close GCP PubSub client", zap.Error(pubsubErr))
|
2021-11-04 02:00:52 -07:00
|
|
|
}
|
2021-08-09 22:29:21 -07:00
|
|
|
return ctx.Err()
|
|
|
|
case err := <-errC:
|
|
|
|
logger.Error("bigtablewriter encountered an error", zap.Error(err))
|
|
|
|
|
|
|
|
e.events.Unsubscribe(sub.ClientId)
|
|
|
|
|
|
|
|
// try to close the connection before returning
|
|
|
|
if closeErr := client.Close(); closeErr != nil {
|
|
|
|
logger.Error("Could not close BigTable client", zap.Error(closeErr))
|
|
|
|
}
|
2021-11-04 02:00:52 -07:00
|
|
|
if pubsubErr := pubsubClient.Close(); pubsubErr != nil {
|
2022-01-25 18:41:38 -08:00
|
|
|
logger.Error("Could not close GCP PubSub client", zap.Error(pubsubErr))
|
2021-11-04 02:00:52 -07:00
|
|
|
}
|
2021-08-09 22:29:21 -07:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|