2021-11-04 02:00:52 -07:00
package p
import (
2021-11-10 04:35:28 -08:00
"bytes"
2021-11-04 02:00:52 -07:00
"context"
2021-11-10 04:35:28 -08:00
"encoding/binary"
"encoding/hex"
2021-11-04 02:00:52 -07:00
"fmt"
"log"
2021-11-10 04:35:28 -08:00
"cloud.google.com/go/bigtable"
2021-11-10 04:56:13 -08:00
"cloud.google.com/go/pubsub"
2021-11-04 02:00:52 -07:00
"github.com/certusone/wormhole/node/pkg/vaa"
2021-11-10 04:35:28 -08:00
"github.com/holiman/uint256"
2021-11-04 02:00:52 -07:00
)
type PubSubMessage struct {
Data [ ] byte ` json:"data" `
}
2021-11-10 04:35:28 -08:00
// The keys are emitterAddress hex values, so that we can quickly check a message against the index to see if it
// meets the criteria for saving payload info: if it is a token transfer, or an NFT transfer.
var NFTEmitters = map [ string ] string {
// mainnet
"0def15a24423e1edd1a5ab16f557b9060303ddbab8c803d2ee48f4b78a1cfd6b" : "WnFt12ZrnzZrFZkt2xsNsaNWoQribnuQ5B5FrDbwDhD" , // solana
"0000000000000000000000006ffd7ede62328b3af38fcd61461bbfc52f5651fe" : "0x6FFd7EdE62328b3Af38FCD61461Bbfc52F5651fE" , // ethereum
"0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" : "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE" , // bsc
"00000000000000000000000090bbd86a6fe93d3bc3ed6335935447e75fab7fcf" : "0x90bbd86a6fe93d3bc3ed6335935447e75fab7fcf" , // polygon
// devnet
"96ee982293251b48729804c8e8b24b553eb6b887867024948d2236fd37a577ab" : "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA" , // solana
"00000000000000000000000026b4afb60d6c903165150c6f0aa14f8016be4aec" : "0x26b4afb60d6c903165150c6f0aa14f8016be4aec" , // ethereum
}
var TokenTransferEmitters = map [ string ] string {
// mainnet
"ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5" : "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb" , // solana
"0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585" : "0x3ee18B2214AFF97000D974cf647E7C347E8fa585" , // ethereum
"0000000000000000000000007cf7b764e38a0a5e967972c1df77d432510564e2" : "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf" , // terra
"000000000000000000000000b6f6d86a8f9879a9c87f643768d9efc38c1da6e7" : "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7" , // bsc
"0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" : "0x5a58505a96d1dbf8df91cb21b54419fc36e93fde" , // polygon
// devnet
"c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f" : "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE" , // solana
"0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16" : "0x0290fb167208af455bb137780163b7b7a9a10c16" , // EVM chains
"000000000000000000000000784999135aaa8a3ca5914468852fdddbddd8789d" : "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4" , // terra
}
2021-11-16 10:45:15 -08:00
// this address is an emitter for BSC and Polygon.
var sharedEmitterAddress = "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde"
2021-11-10 04:35:28 -08:00
type (
TokenTransfer struct {
PayloadId uint8
Amount uint256 . Int
OriginAddress [ 32 ] byte
OriginChain uint16
TargetAddress [ 32 ] byte
TargetChain uint16
}
NFTTransfer struct {
PayloadId uint8
OriginAddress [ 32 ] byte
OriginChain uint16
Symbol [ 32 ] byte
Name [ 32 ] byte
TokenId uint256 . Int
URI [ ] byte
TargetAddress [ 32 ] byte
TargetChain uint16
}
AssetMeta struct {
PayloadId uint8
TokenAddress [ 32 ] byte
TokenChain uint16
Decimals uint8
Symbol [ 32 ] byte
Name [ 32 ] byte
}
)
func DecodeTokenTransfer ( data [ ] byte ) ( * TokenTransfer , error ) {
tt := & TokenTransfer { }
tt . PayloadId = data [ 0 ]
reader := bytes . NewReader ( data [ 1 : ] )
if err := binary . Read ( reader , binary . BigEndian , & tt . Amount ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Amount: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & tt . OriginAddress ) ; err != nil {
return nil , fmt . Errorf ( "failed to read OriginAddress: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & tt . OriginChain ) ; err != nil {
return nil , fmt . Errorf ( "failed to read OriginChain: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & tt . TargetAddress ) ; err != nil {
return nil , fmt . Errorf ( "failed to read TargetAddress: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & tt . TargetChain ) ; err != nil {
return nil , fmt . Errorf ( "failed to read TargetChain: %w" , err )
}
return tt , nil
}
func DecodeNFTTransfer ( data [ ] byte ) ( * NFTTransfer , error ) {
nt := & NFTTransfer { }
nt . PayloadId = data [ 0 ]
reader := bytes . NewReader ( data [ 1 : ] )
if err := binary . Read ( reader , binary . BigEndian , & nt . OriginAddress ) ; err != nil {
return nil , fmt . Errorf ( "failed to read OriginAddress: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & nt . OriginChain ) ; err != nil {
return nil , fmt . Errorf ( "failed to read OriginChain: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & nt . Symbol ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Symbol: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & nt . Name ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Name: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & nt . TokenId ) ; err != nil {
return nil , fmt . Errorf ( "failed to read TokenId: %w" , err )
}
// uri len
uriLen , er := reader . ReadByte ( )
if er != nil {
return nil , fmt . Errorf ( "failed to read URI length" )
}
// uri
uri := make ( [ ] byte , int ( uriLen ) )
n , err := reader . Read ( uri )
if err != nil || n == 0 {
return nil , fmt . Errorf ( "failed to read uri [%d]: %w" , n , err )
}
nt . URI = uri [ : n ]
if err := binary . Read ( reader , binary . BigEndian , & nt . TargetAddress ) ; err != nil {
return nil , fmt . Errorf ( "failed to read : %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & nt . TargetChain ) ; err != nil {
return nil , fmt . Errorf ( "failed to read : %w" , err )
}
return nt , nil
}
func DecodeAssetMeta ( data [ ] byte ) ( * AssetMeta , error ) {
am := & AssetMeta { }
am . PayloadId = data [ 0 ]
reader := bytes . NewReader ( data [ 1 : ] )
tokenAddress := [ 32 ] byte { }
if n , err := reader . Read ( tokenAddress [ : ] ) ; err != nil || n != 32 {
return nil , fmt . Errorf ( "failed to read TokenAddress [%d]: %w" , n , err )
}
am . TokenAddress = tokenAddress
if err := binary . Read ( reader , binary . BigEndian , & am . TokenChain ) ; err != nil {
return nil , fmt . Errorf ( "failed to read TokenChain: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & am . Decimals ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Decimals: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & am . Symbol ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Symbol: %w" , err )
}
if err := binary . Read ( reader , binary . BigEndian , & am . Name ) ; err != nil {
return nil , fmt . Errorf ( "failed to read Name: %w" , err )
}
return am , nil
}
// TEMP: until this https://forge.certus.one/c/wormhole/+/1850 lands
func makeRowKey ( emitterChain vaa . ChainID , emitterAddress vaa . Address , sequence uint64 ) string {
// 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-12-13 10:33:23 -08:00
func writePayloadToBigTable ( ctx context . Context , rowKey string , colFam string , mutation * bigtable . Mutation , forceWrite bool ) error {
mut := mutation
if ! forceWrite {
filter := bigtable . ChainFilters (
bigtable . FamilyFilter ( colFam ) ,
bigtable . ColumnFilter ( "PayloadId" ) )
mut = bigtable . NewCondMutation ( filter , nil , mutation )
}
2021-11-10 04:35:28 -08:00
2021-12-13 10:33:23 -08:00
err := tbl . Apply ( ctx , rowKey , mut )
2021-11-10 04:35:28 -08:00
if err != nil {
log . Printf ( "Failed to write payload for %v to BigTable. err: %v" , rowKey , err )
return err
}
return nil
}
2021-11-16 10:45:15 -08:00
func TrimUnicodeFromByteArray ( b [ ] byte ) [ ] byte {
// Escaped Unicode that has been observed in payload's token names and symbol:
null := "\u0000"
start := "\u0002"
ack := "\u0006"
tab := "\u0009"
control := "\u0012"
return bytes . Trim ( b , null + start + ack + tab + control )
}
2021-11-10 04:35:28 -08:00
2021-12-13 10:33:23 -08:00
func addReceiverAddressToMutation ( mut * bigtable . Mutation , ts bigtable . Timestamp , chainID uint16 , hexAddress string ) {
nativeAddress := transformHexAddressToNative ( vaa . ChainID ( chainID ) , hexAddress )
if vaa . ChainID ( chainID ) == vaa . ChainIDSolana {
ownerAddress := fetchSolanaAccountOwner ( nativeAddress )
if ownerAddress == "" {
// exit with a failure code so the pubsub message is retried.
log . Fatalf ( "failed to find owner address for Solana account." )
}
nativeAddress = ownerAddress
}
mut . Set ( columnFamilies [ 6 ] , "ReceiverAddress" , ts , [ ] byte ( nativeAddress ) )
}
2021-11-10 04:35:28 -08:00
// ProcessVAA is triggered by a PubSub message, emitted after row is saved to BigTable by guardiand
2021-11-04 02:00:52 -07:00
func ProcessVAA ( ctx context . Context , m PubSubMessage ) error {
data := string ( m . Data )
if data == "" {
2021-11-10 04:35:28 -08:00
return fmt . Errorf ( "no data to process in message" )
}
2021-11-04 02:00:52 -07:00
2021-11-10 04:35:28 -08:00
signedVaa , err := vaa . Unmarshal ( m . Data )
if err != nil {
log . Println ( "failed Unmarshaling VAA" )
return err
}
// create the bigtable identifier from the VAA data
rowKey := makeRowKey ( signedVaa . EmitterChain , signedVaa . EmitterAddress , signedVaa . Sequence )
emitterHex := signedVaa . EmitterAddress . String ( )
payloadId := int ( signedVaa . Payload [ 0 ] )
2021-11-16 10:45:15 -08:00
// BSC and Polygon have the same contract address: "0x5a58505a96d1dbf8df91cb21b54419fc36e93fde".
// The BSC contract is the NFT emitter address.
// The Polygon contract is the token transfer emitter address.
// Due to that, ensure that the block below only runs for token transfers by checking for chain == 4 and emitter addaress.
if _ , ok := TokenTransferEmitters [ emitterHex ] ; ok && ! ( signedVaa . EmitterChain == 4 && signedVaa . EmitterAddress . String ( ) == sharedEmitterAddress ) {
2021-11-10 04:35:28 -08:00
// figure out if it's a transfer or asset metadata
if payloadId == 1 {
// token transfer
payload , decodeErr := DecodeTokenTransfer ( signedVaa . Payload )
if decodeErr != nil {
log . Println ( "failed decoding payload for row " , rowKey )
return decodeErr
}
2021-12-13 10:33:23 -08:00
log . Printf ( "Processing Transfer: Amount %v\n" , fmt . Sprint ( payload . Amount [ 3 ] ) )
2021-11-10 04:56:13 -08:00
// save payload to bigtable, then publish a new PubSub message for further processing
2021-11-10 04:35:28 -08:00
colFam := columnFamilies [ 2 ]
mutation := bigtable . NewMutation ( )
ts := bigtable . Now ( )
mutation . Set ( colFam , "PayloadId" , ts , [ ] byte ( fmt . Sprint ( payload . PayloadId ) ) )
// TODO: find a better way of representing amount as a string
2021-11-16 10:45:15 -08:00
amount := [ ] byte ( fmt . Sprint ( payload . Amount [ 3 ] ) )
if payload . Amount [ 2 ] != 0 {
log . Printf ( "payload.Amount is larger than uint64 for row %v" , rowKey )
amount = payload . Amount . Bytes ( )
}
2021-12-13 10:33:23 -08:00
targetAddressHex := hex . EncodeToString ( payload . TargetAddress [ : ] )
2021-11-16 10:45:15 -08:00
mutation . Set ( colFam , "Amount" , ts , amount )
2021-11-10 04:35:28 -08:00
mutation . Set ( colFam , "OriginAddress" , ts , [ ] byte ( hex . EncodeToString ( payload . OriginAddress [ : ] ) ) )
mutation . Set ( colFam , "OriginChain" , ts , [ ] byte ( fmt . Sprint ( payload . OriginChain ) ) )
2021-12-13 10:33:23 -08:00
mutation . Set ( colFam , "TargetAddress" , ts , [ ] byte ( targetAddressHex ) )
2021-11-10 04:35:28 -08:00
mutation . Set ( colFam , "TargetChain" , ts , [ ] byte ( fmt . Sprint ( payload . TargetChain ) ) )
2021-12-13 10:33:23 -08:00
addReceiverAddressToMutation ( mutation , ts , payload . TargetChain , targetAddressHex )
writeErr := writePayloadToBigTable ( ctx , rowKey , colFam , mutation , false )
2021-11-10 04:56:13 -08:00
if writeErr != nil {
return writeErr
}
// now that the payload is saved to BigTable,
// pass along the message to the topic that will calculate TokenTransferDetails
pubSubTokenTransferDetailsTopic . Publish ( ctx , & pubsub . Message { Data : m . Data } )
2021-11-10 04:35:28 -08:00
} else if payloadId == 2 {
// asset meta
payload , decodeErr := DecodeAssetMeta ( signedVaa . Payload )
if decodeErr != nil {
log . Println ( "failed decoding payload for row " , rowKey )
return decodeErr
}
2021-11-19 11:09:39 -08:00
addressHex := hex . EncodeToString ( payload . TokenAddress [ : ] )
chainID := vaa . ChainID ( payload . TokenChain )
nativeAddress := transformHexAddressToNative ( chainID , addressHex )
name := string ( TrimUnicodeFromByteArray ( payload . Name [ : ] ) )
symbol := string ( TrimUnicodeFromByteArray ( payload . Symbol [ : ] ) )
// find the CoinGecko id of this token
coinGeckoCoinId , foundSymbol , foundName := fetchCoinGeckoCoinId ( chainID , nativeAddress , symbol , name )
// populate the symbol & name if they were blank, and we found values
if symbol == "" && foundSymbol != "" {
symbol = foundSymbol
}
if name == "" && foundName != "" {
name = foundName
}
2021-12-13 10:33:23 -08:00
log . Printf ( "Processing AssetMeta: Name %v, Symbol %v, coingeckoId %v\n" , name , symbol , coinGeckoCoinId )
2021-11-10 04:35:28 -08:00
// save payload to bigtable
colFam := columnFamilies [ 3 ]
mutation := bigtable . NewMutation ( )
ts := bigtable . Now ( )
mutation . Set ( colFam , "PayloadId" , ts , [ ] byte ( fmt . Sprint ( payload . PayloadId ) ) )
2021-11-19 11:09:39 -08:00
mutation . Set ( colFam , "TokenAddress" , ts , [ ] byte ( addressHex ) )
2021-11-10 04:35:28 -08:00
mutation . Set ( colFam , "TokenChain" , ts , [ ] byte ( fmt . Sprint ( payload . TokenChain ) ) )
mutation . Set ( colFam , "Decimals" , ts , [ ] byte ( fmt . Sprint ( payload . Decimals ) ) )
2021-11-19 11:09:39 -08:00
mutation . Set ( colFam , "Name" , ts , [ ] byte ( name ) )
mutation . Set ( colFam , "Symbol" , ts , [ ] byte ( symbol ) )
mutation . Set ( colFam , "CoinGeckoCoinId" , ts , [ ] byte ( coinGeckoCoinId ) )
mutation . Set ( colFam , "NativeAddress" , ts , [ ] byte ( nativeAddress ) )
2021-12-13 10:33:23 -08:00
writeErr := writePayloadToBigTable ( ctx , rowKey , colFam , mutation , false )
2021-11-10 04:35:28 -08:00
return writeErr
} else {
// unknown payload type
log . Println ( "encountered unknown payload type for row " , rowKey )
return nil
2021-11-04 02:00:52 -07:00
}
2021-11-10 04:35:28 -08:00
} else if _ , ok := NFTEmitters [ emitterHex ] ; ok {
if payloadId == 1 {
// NFT transfer
payload , decodeErr := DecodeNFTTransfer ( signedVaa . Payload )
if decodeErr != nil {
log . Println ( "failed decoding payload for row " , rowKey )
return decodeErr
}
2021-12-13 10:33:23 -08:00
log . Printf ( "Processing NTF: Name %v, Symbol %v\n" , string ( TrimUnicodeFromByteArray ( payload . Name [ : ] ) ) , string ( TrimUnicodeFromByteArray ( payload . Symbol [ : ] ) ) )
2021-11-10 04:35:28 -08:00
// save payload to bigtable
colFam := columnFamilies [ 4 ]
mutation := bigtable . NewMutation ( )
ts := bigtable . Now ( )
2021-12-13 10:33:23 -08:00
targetAddressHex := hex . EncodeToString ( payload . TargetAddress [ : ] )
2021-11-10 04:35:28 -08:00
mutation . Set ( colFam , "PayloadId" , ts , [ ] byte ( fmt . Sprint ( payload . PayloadId ) ) )
mutation . Set ( colFam , "OriginAddress" , ts , [ ] byte ( hex . EncodeToString ( payload . OriginAddress [ : ] ) ) )
mutation . Set ( colFam , "OriginChain" , ts , [ ] byte ( fmt . Sprint ( payload . OriginChain ) ) )
2021-11-16 10:45:15 -08:00
mutation . Set ( colFam , "Symbol" , ts , TrimUnicodeFromByteArray ( payload . Symbol [ : ] ) )
mutation . Set ( colFam , "Name" , ts , TrimUnicodeFromByteArray ( payload . Name [ : ] ) )
mutation . Set ( colFam , "TokenId" , ts , payload . TokenId . Bytes ( ) )
mutation . Set ( colFam , "URI" , ts , TrimUnicodeFromByteArray ( payload . URI ) )
2021-12-13 10:33:23 -08:00
mutation . Set ( colFam , "TargetAddress" , ts , [ ] byte ( targetAddressHex ) )
2021-11-10 04:35:28 -08:00
mutation . Set ( colFam , "TargetChain" , ts , [ ] byte ( fmt . Sprint ( payload . TargetChain ) ) )
2021-11-04 02:00:52 -07:00
2021-12-13 10:33:23 -08:00
addReceiverAddressToMutation ( mutation , ts , payload . TargetChain , targetAddressHex )
writeErr := writePayloadToBigTable ( ctx , rowKey , colFam , mutation , false )
2021-11-10 04:35:28 -08:00
return writeErr
} else {
// unknown payload type
log . Println ( "encountered unknown payload type for row " , rowKey )
return nil
}
2021-11-04 02:00:52 -07:00
}
2021-11-10 04:35:28 -08:00
// this is not a payload we are ready to decode & save. return success
2021-11-04 02:00:52 -07:00
return nil
}