2023-01-17 05:30:50 -08:00
package accountant
2023-01-16 04:33:01 -08:00
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/certusone/wormhole/node/pkg/common"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
ethCommon "github.com/ethereum/go-ethereum/common"
tmAbci "github.com/tendermint/tendermint/abci/types"
tmHttp "github.com/tendermint/tendermint/rpc/client/http"
tmCoreTypes "github.com/tendermint/tendermint/rpc/core/types"
tmTypes "github.com/tendermint/tendermint/types"
"go.uber.org/zap"
)
// watcher reads transaction events from the smart contract and publishes them.
2023-01-17 05:30:50 -08:00
func ( acct * Accountant ) watcher ( ctx context . Context ) error {
2023-01-16 04:33:01 -08:00
errC := make ( chan error )
acct . logger . Info ( "acctwatch: creating watcher" , zap . String ( "url" , acct . wsUrl ) , zap . String ( "contract" , acct . contract ) )
tmConn , err := tmHttp . New ( acct . wsUrl , "/websocket" )
if err != nil {
connectionErrors . Inc ( )
return fmt . Errorf ( "failed to establish tendermint connection: %w" , err )
}
if err := tmConn . Start ( ) ; err != nil {
connectionErrors . Inc ( )
return fmt . Errorf ( "failed to start tendermint connection: %w" , err )
}
defer func ( ) {
if err := tmConn . Stop ( ) ; err != nil {
connectionErrors . Inc ( )
acct . logger . Error ( "acctwatch: failed to stop tendermint connection" , zap . Error ( err ) )
}
} ( )
query := fmt . Sprintf ( "execute._contract_address='%s'" , acct . contract )
events , err := tmConn . Subscribe (
ctx ,
"guardiand" ,
query ,
64 , // channel capacity
)
if err != nil {
2023-01-17 05:30:50 -08:00
return fmt . Errorf ( "failed to subscribe to accountant events: %w" , err )
2023-01-16 04:33:01 -08:00
}
defer func ( ) {
if err := tmConn . UnsubscribeAll ( ctx , "guardiand" ) ; err != nil {
acct . logger . Error ( "acctwatch: failed to unsubscribe from events" , zap . Error ( err ) )
}
} ( )
go acct . handleEvents ( ctx , events , errC )
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
case err := <- errC :
return err
}
}
// handleEvents handles events from the tendermint client library.
2023-01-17 05:30:50 -08:00
func ( acct * Accountant ) handleEvents ( ctx context . Context , evts <- chan tmCoreTypes . ResultEvent , errC chan error ) {
2023-01-16 04:33:01 -08:00
defer close ( errC )
for {
select {
case <- ctx . Done ( ) :
return
case e := <- evts :
tx , ok := e . Data . ( tmTypes . EventDataTx )
if ! ok {
acct . logger . Error ( "acctwatcher: unknown data from event subscription" , zap . Stringer ( "e.Data" , reflect . TypeOf ( e . Data ) ) , zap . Any ( "event" , e ) )
continue
}
for _ , event := range tx . Result . Events {
2023-01-20 06:15:36 -08:00
if event . Type == "wasm-Observation" {
evt , err := parseEvent [ WasmObservation ] ( acct . logger , event , "wasm-Observation" , acct . contract )
2023-01-16 04:33:01 -08:00
if err != nil {
2023-01-20 06:15:36 -08:00
acct . logger . Error ( "acctwatcher: failed to parse wasm transfer event" , zap . Error ( err ) , zap . Stringer ( "e.Data" , reflect . TypeOf ( e . Data ) ) , zap . Any ( "event" , event ) )
2023-01-16 04:33:01 -08:00
continue
}
eventsReceived . Inc ( )
2023-01-20 06:15:36 -08:00
acct . processPendingTransfer ( evt )
} else if event . Type == "wasm-ObservationError" {
evt , err := parseEvent [ WasmObservationError ] ( acct . logger , event , "wasm-ObservationError" , acct . contract )
if err != nil {
acct . logger . Error ( "acctwatcher: failed to parse wasm observation error event" , zap . Error ( err ) , zap . Stringer ( "e.Data" , reflect . TypeOf ( e . Data ) ) , zap . Any ( "event" , event ) )
continue
}
errorEventsReceived . Inc ( )
acct . handleTransferError ( evt . Key . String ( ) , evt . Error , "acct: transfer error event received" )
2023-01-16 04:33:01 -08:00
} else {
2023-01-20 06:15:36 -08:00
acct . logger . Debug ( "acctwatcher: ignoring uninteresting event" , zap . String ( "eventType" , event . Type ) )
2023-01-16 04:33:01 -08:00
}
}
}
}
}
2023-01-20 06:15:36 -08:00
type (
// WasmObservation represents a transfer event from the smart contract.
WasmObservation Observation
2023-01-16 04:33:01 -08:00
2023-01-20 06:15:36 -08:00
// WasmObservationError represents an error event from the smart contract.
WasmObservationError struct {
Key ObservationKey ` json:"key" `
Error string ` json:"error" `
2023-01-16 04:33:01 -08:00
}
2023-01-20 06:15:36 -08:00
)
2023-01-16 04:33:01 -08:00
2023-01-20 06:15:36 -08:00
func parseEvent [ T any ] ( logger * zap . Logger , event tmAbci . Event , name string , contractAddress string ) ( * T , error ) {
2023-01-16 04:33:01 -08:00
attrs := make ( map [ string ] json . RawMessage )
for _ , attr := range event . Attributes {
if string ( attr . Key ) == "_contract_address" {
if string ( attr . Value ) != contractAddress {
2023-01-20 06:15:36 -08:00
return nil , fmt . Errorf ( "wasm-Observation event from unexpected contract: %s" , string ( attr . Value ) )
2023-01-16 04:33:01 -08:00
}
} else {
2023-01-20 06:15:36 -08:00
logger . Debug ( "acctwatcher: event attribute" , zap . String ( "event" , name ) , zap . String ( "key" , string ( attr . Key ) ) , zap . String ( "value" , string ( attr . Value ) ) )
2023-01-16 04:33:01 -08:00
attrs [ string ( attr . Key ) ] = attr . Value
}
}
attrBytes , err := json . Marshal ( attrs )
if err != nil {
2023-01-20 06:15:36 -08:00
return nil , fmt . Errorf ( "failed to marshal wasm-Observation event attributes: %w" , err )
2023-01-16 04:33:01 -08:00
}
2023-01-20 06:15:36 -08:00
evt := new ( T )
2023-01-16 04:33:01 -08:00
if err := json . Unmarshal ( attrBytes , evt ) ; err != nil {
2023-01-20 06:15:36 -08:00
return nil , fmt . Errorf ( "failed to unmarshal wasm-Observation event: %w" , err )
2023-01-16 04:33:01 -08:00
}
return evt , nil
}
2023-01-20 06:15:36 -08:00
// processPendingTransfer takes a WasmObservation event, determines if we are expecting it, and if so, publishes it.
func ( acct * Accountant ) processPendingTransfer ( xfer * WasmObservation ) {
acct . pendingTransfersLock . Lock ( )
defer acct . pendingTransfersLock . Unlock ( )
2023-01-16 04:33:01 -08:00
acct . logger . Info ( "acctwatch: transfer event detected" ,
2023-01-20 06:15:36 -08:00
zap . String ( "tx_hash" , hex . EncodeToString ( xfer . TxHash ) ) ,
2023-01-16 04:33:01 -08:00
zap . Uint32 ( "timestamp" , xfer . Timestamp ) ,
zap . Uint32 ( "nonce" , xfer . Nonce ) ,
zap . Stringer ( "emitter_chain" , vaa . ChainID ( xfer . EmitterChain ) ) ,
zap . Stringer ( "emitter_address" , xfer . EmitterAddress ) ,
zap . Uint64 ( "sequence" , xfer . Sequence ) ,
zap . Uint8 ( "consistency_level" , xfer . ConsistencyLevel ) ,
zap . String ( "payload" , hex . EncodeToString ( xfer . Payload ) ) ,
)
msg := & common . MessagePublication {
2023-01-20 06:15:36 -08:00
TxHash : ethCommon . BytesToHash ( xfer . TxHash ) ,
2023-01-16 04:33:01 -08:00
Timestamp : time . Unix ( int64 ( xfer . Timestamp ) , 0 ) ,
Nonce : xfer . Nonce ,
Sequence : xfer . Sequence ,
EmitterChain : vaa . ChainID ( xfer . EmitterChain ) ,
EmitterAddress : xfer . EmitterAddress ,
Payload : xfer . Payload ,
ConsistencyLevel : xfer . ConsistencyLevel ,
}
msgId := msg . MessageIDString ( )
pe , exists := acct . pendingTransfers [ msgId ]
if exists {
digest := msg . CreateDigest ( )
if pe . digest != digest {
digestMismatches . Inc ( )
acct . logger . Error ( "acctwatch: digest mismatch, dropping transfer" ,
zap . String ( "msgID" , msgId ) ,
zap . String ( "oldDigest" , pe . digest ) ,
zap . String ( "newDigest" , digest ) ,
)
2023-01-20 06:15:36 -08:00
acct . deletePendingTransferAlreadyLocked ( msgId )
2023-01-16 04:33:01 -08:00
return
}
acct . logger . Info ( "acctwatch: pending transfer has been approved" , zap . String ( "msgId" , msgId ) )
2023-01-20 06:15:36 -08:00
acct . publishTransferAlreadyLocked ( pe )
2023-01-16 04:33:01 -08:00
transfersApproved . Inc ( )
} else {
// TODO: We could issue a reobservation request here since it looks like other guardians have seen this transfer but we haven't.
acct . logger . Info ( "acctwatch: unknown transfer has been approved, ignoring it" , zap . String ( "msgId" , msgId ) )
}
}