wormhole/node/pkg/processor/processor.go

306 lines
8.7 KiB
Go

package processor
import (
"context"
"crypto/ecdsa"
"fmt"
"time"
"github.com/certusone/wormhole/node/pkg/notify/discord"
"github.com/certusone/wormhole/node/pkg/db"
"github.com/certusone/wormhole/node/pkg/governor"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"go.uber.org/zap"
"github.com/certusone/wormhole/node/pkg/accounting"
"github.com/certusone/wormhole/node/pkg/common"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/reporter"
"github.com/certusone/wormhole/node/pkg/supervisor"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
)
type (
// Observation defines the interface for any events observed by the guardian.
Observation interface {
// GetEmitterChain returns the id of the chain where this event was observed.
GetEmitterChain() vaa.ChainID
// MessageID returns a human-readable emitter_chain/emitter_address/sequence tuple.
MessageID() string
// SigningMsg returns the hash of the signing body of the observation. This is used
// for signature generation and verification.
SigningMsg() ethcommon.Hash
// IsReliable returns whether this message is considered reliable meaning it can be reobserved.
IsReliable() bool
// HandleQuorum finishes processing the observation once a quorum of signatures have
// been received for it.
HandleQuorum(sigs []*vaa.Signature, hash string, p *Processor)
}
// state represents the local view of a given observation
state struct {
// First time this digest was seen (possibly even before we observed it ourselves).
firstObserved time.Time
// The most recent time that a re-observation request was sent to the guardian network.
lastRetry time.Time
// Copy of our observation.
ourObservation Observation
// Map of signatures seen by guardian. During guardian set updates, this may contain signatures belonging
// to either the old or new guardian set.
signatures map[ethcommon.Address][]byte
// Flag set after reaching quorum and submitting the VAA.
submitted bool
// Flag set by the cleanup service after the settlement timeout has expired and misses were counted.
settled bool
// Human-readable description of the VAA's source, used for metrics.
source string
// Number of times the cleanup service has attempted to retransmit this VAA.
retryCount uint
// Copy of the bytes we submitted (ourObservation, but signed and serialized). Used for retransmissions.
ourMsg []byte
// The hash of the transaction in which the observation was made. Used for re-observation requests.
txHash []byte
// Copy of the guardian set valid at observation/injection time.
gs *common.GuardianSet
}
observationMap map[string]*state
// aggregationState represents the node's aggregation of guardian signatures.
aggregationState struct {
signatures observationMap
}
)
type PythNetVaaEntry struct {
v *vaa.VAA
updateTime time.Time // Used for determining when to delete entries
}
type Processor struct {
// lockC is a channel of observed emitted messages
lockC chan *common.MessagePublication
// setC is a channel of guardian set updates
setC chan *common.GuardianSet
// sendC is a channel of outbound messages to broadcast on p2p
sendC chan []byte
// obsvC is a channel of inbound decoded observations from p2p
obsvC chan *gossipv1.SignedObservation
// obsvReqSendC is a send-only channel of outbound re-observation requests to broadcast on p2p
obsvReqSendC chan<- *gossipv1.ObservationRequest
// signedInC is a channel of inbound signed VAA observations from p2p
signedInC chan *gossipv1.SignedVAAWithQuorum
// injectC is a channel of VAAs injected locally.
injectC chan *vaa.VAA
// gk is the node's guardian private key
gk *ecdsa.PrivateKey
// devnetMode specified whether to submit transactions to the hardcoded Ethereum devnet
devnetMode bool
devnetNumGuardians uint
devnetEthRPC string
wormchainLCD string
attestationEvents *reporter.AttestationEventReporter
logger *zap.Logger
db *db.Database
// Runtime state
// gs is the currently valid guardian set
gs *common.GuardianSet
// gst is managed by the processor and allows concurrent access to the
// guardian set by other components.
gst *common.GuardianSetState
// state is the current runtime VAA view
state *aggregationState
// gk pk as eth address
ourAddr ethcommon.Address
// cleanup triggers periodic state cleanup
cleanup *time.Ticker
notifier *discord.DiscordNotifier
governor *governor.ChainGovernor
acct *accounting.Accounting
acctReadC <-chan *common.MessagePublication
pythnetVaas map[string]PythNetVaaEntry
}
func NewProcessor(
ctx context.Context,
db *db.Database,
lockC chan *common.MessagePublication,
setC chan *common.GuardianSet,
sendC chan []byte,
obsvC chan *gossipv1.SignedObservation,
obsvReqSendC chan<- *gossipv1.ObservationRequest,
injectC chan *vaa.VAA,
signedInC chan *gossipv1.SignedVAAWithQuorum,
gk *ecdsa.PrivateKey,
gst *common.GuardianSetState,
devnetMode bool,
devnetNumGuardians uint,
devnetEthRPC string,
wormchainLCD string,
attestationEvents *reporter.AttestationEventReporter,
notifier *discord.DiscordNotifier,
g *governor.ChainGovernor,
acct *accounting.Accounting,
acctReadC <-chan *common.MessagePublication,
) *Processor {
return &Processor{
lockC: lockC,
setC: setC,
sendC: sendC,
obsvC: obsvC,
obsvReqSendC: obsvReqSendC,
signedInC: signedInC,
injectC: injectC,
gk: gk,
gst: gst,
devnetMode: devnetMode,
devnetNumGuardians: devnetNumGuardians,
devnetEthRPC: devnetEthRPC,
db: db,
wormchainLCD: wormchainLCD,
attestationEvents: attestationEvents,
notifier: notifier,
logger: supervisor.Logger(ctx),
state: &aggregationState{observationMap{}},
ourAddr: crypto.PubkeyToAddress(gk.PublicKey),
governor: g,
acct: acct,
acctReadC: acctReadC,
pythnetVaas: make(map[string]PythNetVaaEntry),
}
}
func (p *Processor) Run(ctx context.Context) error {
p.cleanup = time.NewTicker(30 * time.Second)
// Always initialize the timer so don't have a nil pointer in the case below. It won't get rearmed after that.
govTimer := time.NewTimer(time.Minute)
for {
select {
case <-ctx.Done():
if p.acct != nil {
p.acct.Close()
}
return ctx.Err()
case p.gs = <-p.setC:
p.logger.Info("guardian set updated",
zap.Strings("set", p.gs.KeysAsHexStrings()),
zap.Uint32("index", p.gs.Index))
p.gst.Set(p.gs)
case k := <-p.lockC:
if p.governor != nil {
if !p.governor.ProcessMsg(k) {
continue
}
}
if p.acct != nil {
shouldPub, err := p.acct.SubmitObservation(k)
if err != nil {
return fmt.Errorf("acct: failed to process message `%s`: %w", k.MessageIDString(), err)
}
if !shouldPub {
continue
}
}
p.handleMessage(ctx, k)
case k := <-p.acctReadC:
if p.acct == nil {
return fmt.Errorf("acct: received an accounting event when accounting is not configured")
}
p.handleMessage(ctx, k)
case v := <-p.injectC:
p.handleInjection(ctx, v)
case m := <-p.obsvC:
p.handleObservation(ctx, m)
case m := <-p.signedInC:
p.handleInboundSignedVAAWithQuorum(ctx, m)
case <-p.cleanup.C:
p.handleCleanup(ctx)
case <-govTimer.C:
if p.governor != nil {
toBePublished, err := p.governor.CheckPending()
if err != nil {
return err
}
if len(toBePublished) != 0 {
for _, k := range toBePublished {
if p.acct != nil {
shouldPub, err := p.acct.SubmitObservation(k)
if err != nil {
return fmt.Errorf("acct: failed to process message released by governor `%s`: %w", k.MessageIDString(), err)
}
if !shouldPub {
continue
}
}
p.handleMessage(ctx, k)
}
}
}
if p.acct != nil {
p.acct.AuditPendingTransfers()
}
if (p.governor != nil) || (p.acct != nil) {
govTimer = time.NewTimer(time.Minute)
}
}
}
}
func (p *Processor) storeSignedVAA(v *vaa.VAA) error {
if v.EmitterChain == vaa.ChainIDPythNet {
key := fmt.Sprintf("%v/%v", v.EmitterAddress, v.Sequence)
p.pythnetVaas[key] = PythNetVaaEntry{v: v, updateTime: time.Now()}
return nil
}
return p.db.StoreSignedVAA(v)
}
func (p *Processor) getSignedVAA(id db.VAAID) (*vaa.VAA, error) {
if id.EmitterChain == vaa.ChainIDPythNet {
key := fmt.Sprintf("%v/%v", id.EmitterAddress, id.Sequence)
ret, exists := p.pythnetVaas[key]
if exists {
return ret.v, nil
}
return nil, db.ErrVAANotFound
}
vb, err := p.db.GetSignedVAABytes(id)
if err != nil {
return nil, err
}
vaa, err := vaa.Unmarshal(vb)
if err != nil {
panic("failed to unmarshal VAA from db")
}
return vaa, err
}