From 8b5819618138b3b5b59b2a1e85a9f1df00613dd9 Mon Sep 17 00:00:00 2001 From: agodnic Date: Thu, 22 Jun 2023 10:32:17 -0300 Subject: [PATCH] Fix transaction hash search for Sui (#448) ### Description Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/447 For Sui transaction hashes, calling `GET /api/v1/vaas?txHash={hash}` returned an HTTP status code of 400 and a message of `"MALFORMED TX HASH"`. This pull request fixes the problem. --- api/types/tx_hash.go | 34 +++++++++++++++++++++++++++--- api/types/tx_hash_test.go | 44 +++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/api/types/tx_hash.go b/api/types/tx_hash.go index 6e2c17ee..a4c78a37 100644 --- a/api/types/tx_hash.go +++ b/api/types/tx_hash.go @@ -10,6 +10,8 @@ import ( ) const ( + suiMinTxHashLen = 43 + suiMaxTxHashLen = 44 algorandTxHashLen = 52 wormholeMinTxHashLen = 64 wormholeMaxTxHashLen = 66 @@ -39,11 +41,16 @@ func ParseTxHash(value string) (*TxHash, error) { return parseSolanaTxHash(value) } - // Algorand txHashes are 52 bytes long, encoded as base32. + // Algorand txHashes are 32 bytes long, encoded as base32. if len(value) == algorandTxHashLen { return parseAlgorandTxHash(value) } + // Sui txHashes are 32 bytes long, encoded as base32. + if len(value) >= suiMinTxHashLen && len(value) <= suiMaxTxHashLen { + return parseSuiTxHash(value) + } + // Wormhole txHashes are 32 bytes long, encoded as hex. // Optionally, they can be prefixed with "0x" or "0X". if len(value) >= wormholeMinTxHashLen && len(value) <= wormholeMaxTxHashLen { @@ -58,7 +65,7 @@ func parseSolanaTxHash(value string) (*TxHash, error) { // Decode the string from base58 to binary bytes, err := base58.Decode(value) if err != nil { - return nil, fmt.Errorf("failed to decode txHash from base58: %w", err) + return nil, fmt.Errorf("failed to decode solana txHash from base58: %w", err) } // Make sure we have the expected amount of bytes @@ -78,7 +85,7 @@ func parseAlgorandTxHash(value string) (*TxHash, error) { // Decode the string from base32 to binary bytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(value) if err != nil { - return nil, fmt.Errorf("failed to decode txHash from base32: %w", err) + return nil, fmt.Errorf("failed to decode algorand txHash from base32: %w", err) } // Make sure we have the expected amount of bytes @@ -94,6 +101,27 @@ func parseAlgorandTxHash(value string) (*TxHash, error) { return &result, nil } +func parseSuiTxHash(value string) (*TxHash, error) { + + // Decode the string from base58 to binary + bytes, err := base58.Decode(value) + if err != nil { + return nil, fmt.Errorf("failed to decode sui txHash from base58: %w", err) + } + + // Make sure we have the expected amount of bytes + if len(bytes) != 32 { + return nil, fmt.Errorf("sui txHash must be exactly 32 bytes, but got %d bytes", len(bytes)) + } + + // Populate the result struct and return + result := TxHash{ + hash: base58.Encode(bytes), + isWormhole: true, + } + return &result, nil +} + func parseWormholeTxHash(value string) (*TxHash, error) { // Trim any preceding "0x" to the address diff --git a/api/types/tx_hash_test.go b/api/types/tx_hash_test.go index 5f80f061..32320a93 100644 --- a/api/types/tx_hash_test.go +++ b/api/types/tx_hash_test.go @@ -13,24 +13,48 @@ func TestParseTxHash(t *testing.T) { isWormholeTxHash bool }{ { - // Solana hash - 88 characters + // Invalid Solana hash - 86 characters (too short) + input: "VKrJx5ak3amnpY5EXiqfu6pnrzxHTLU95m9vfbYnGSSLQrkRzb4tm4NztCGeLcJxieXQYnqddUwoaEsDRTRh57", + }, + { + // Valid Solana hash - 88 characters input: "2maR6uDZzroV7JFF76rp5QR4CFP1PFUe76VRE8gF8QtWRifpGAKJQo4SQDBNs3TAM9RrchJhnJ644jUL2yfagZco", output: "2maR6uDZzroV7JFF76rp5QR4CFP1PFUe76VRE8gF8QtWRifpGAKJQo4SQDBNs3TAM9RrchJhnJ644jUL2yfagZco", isSolanaTxHash: true, }, { - // Solana hash - 87 characters + // Valid Solana hash - 87 characters input: "VKrJx5ak3amnpY5EXiqfu6pnrzxHTLU95m9vfbYnGSSLQrkRzb4tm4NztCGeLcJxieXQYnqddUwoaEsDRTRh57R", output: "VKrJx5ak3amnpY5EXiqfu6pnrzxHTLU95m9vfbYnGSSLQrkRzb4tm4NztCGeLcJxieXQYnqddUwoaEsDRTRh57R", isSolanaTxHash: true, }, { - // Solana hash w/ invalid length (86 characters) - input: "VKrJx5ak3amnpY5EXiqfu6pnrzxHTLU95m9vfbYnGSSLQrkRzb4tm4NztCGeLcJxieXQYnqddUwoaEsDRTRh57", + // Invalid Solana hash - 89 characters (too long) + input: "2maR6uDZzroV7JFF76rp5QR4CFP1PFUe76VRE8gF8QtWRifpGAKJQo4SQDBNs3TAM9RrchJhnJ644jUL2yfagZco2", }, { - // Solana hash w/ invalid length (89 characters) - input: "2maR6uDZzroV7JFF76rp5QR4CFP1PFUe76VRE8gF8QtWRifpGAKJQo4SQDBNs3TAM9RrchJhnJ644jUL2yfagZco2", + // Invalid Sui hash - 42 characters (too short) + input: "cVWa8xZtWbTxXQGLQaYquwmChE2sQYxFNGnHmp6oXX", + }, + { + // Valid Sui hash - 43 characters + input: "cVWa8xZtWbTxXQGLQaYquwmChE2sQYxFNGnHmp6oXXL", + output: "cVWa8xZtWbTxXQGLQaYquwmChE2sQYxFNGnHmp6oXXL", + isWormholeTxHash: true, + }, + { + // Valid Sui hash - 44 characters + input: "9yQWLTNmFkwZ6CdK3QXhk8utKr42n3Eh1CFFBWcdCeJC", + output: "9yQWLTNmFkwZ6CdK3QXhk8utKr42n3Eh1CFFBWcdCeJC", + isWormholeTxHash: true, + }, + { + // Invalid Sui hash - 45 characters (too long) + input: "9yQWLTNmFkwZ6CdK3QXhk8utKr42n3Eh1CFFBWcdCeJC9", + }, + { + // Invalid Wormhole hash - 63 characters (too short) + input: "f77f8b44f35ff047a74ee8235ce007afbab357d4e30010d51b6f6990f921637", }, { // Wormhole hash with 0x prefix @@ -51,14 +75,12 @@ func TestParseTxHash(t *testing.T) { isWormholeTxHash: true, }, { - // Wormhole hash w/ indalid length - input: "33f77f8b44f35ff047a74ee8235ce007afbab357d4e30010d51b6f6990f921637", - output: "", + // Invalid Wormhole hash - 65 characters (too long) + input: "33f77f8b44f35ff047a74ee8235ce007afbab357d4e30010d51b6f6990f921637", }, { // A bunch of random characters - input: "434234i32042oiu08d8sauf0suif", - output: "", + input: "434234i32042oiu08d8sauf0suif", }, }