[API] Accept additional address formats (#170)

### Summary

Context: https://github.com/wormhole-foundation/wormhole-explorer/issues/154

This PR modifies all endpoints that receive an emitter/guardian address to accept a wider range of formats.
After this pull request, all of these are equivalent:
* `0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7`
* `000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7`
* `0xf890982f9310df57d00f659cf4fd87e65aded8d7`
* `f890982f9310df57d00f659cf4fd87e65aded8d7`

### Testing plan
* Added unit tests for the parsing code.
* Tested manually a few of the affected endpoints.
This commit is contained in:
agodnic 2023-02-28 17:50:23 -03:00 committed by GitHub
parent b1583d5e21
commit 533b83ad28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 209 additions and 47 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
@ -1603,7 +1604,7 @@ type EnqueuedResponse struct {
func (r *Repository) IsVaaEnqueued(
ctx context.Context,
chainID vaa.ChainID,
emitter vaa.Address,
emitter *types.Address,
sequence string,
) (bool, error) {
@ -1636,7 +1637,7 @@ func (r *Repository) IsVaaEnqueued(
matchStage6 := bson.D{
{"$match", bson.D{
{"chainid", chainID},
{"emitters.emitteraddress", fmt.Sprintf("0x%s", emitter.String())},
{"emitters.emitteraddress", fmt.Sprintf("0x%s", emitter.ShortHex())},
{"emitters.enqueuedvaas.sequence", sequence},
}},
}

View File

@ -7,6 +7,7 @@ import (
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/api/response"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
@ -62,9 +63,16 @@ func (s *Service) FindGovernorStatus(ctx context.Context, p *pagination.Paginati
}
// FindGovernorStatusByGuardianAddress get a governor status by guardianAddress.
func (s *Service) FindGovernorStatusByGuardianAddress(ctx context.Context, guardianAddress string, p *pagination.Pagination) (*response.Response[*GovStatus], error) {
func (s *Service) FindGovernorStatusByGuardianAddress(
ctx context.Context,
guardianAddress string,
p *pagination.Pagination,
) (*response.Response[*GovStatus], error) {
query := QueryGovernor().SetID(guardianAddress).SetPagination(p)
govStatus, err := s.repo.FindOneGovernorStatus(ctx, query)
res := response.Response[*GovStatus]{Data: govStatus}
return &res, err
}
@ -182,7 +190,7 @@ func (s *Service) GetEnqueuedVaas(ctx context.Context) ([]*EnqueuedVaaItem, erro
// IsVaaEnqueued check vaa is enqueued.
// Guardian api migration.
func (s *Service) IsVaaEnqueued(ctx context.Context, chainID vaa.ChainID, emitter vaa.Address, seq string) (bool, error) {
func (s *Service) IsVaaEnqueued(ctx context.Context, chainID vaa.ChainID, emitter *types.Address, seq string) (bool, error) {
isEnqueued, err := s.repo.IsVaaEnqueued(ctx, chainID, emitter, seq)
return isEnqueued, err
}

View File

@ -5,6 +5,7 @@ import (
"context"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
@ -32,19 +33,55 @@ func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *paginat
}
// FindByEmitter get all the observations by chainID and emitter address.
func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter *vaa.Address, p *pagination.Pagination) ([]*ObservationDoc, error) {
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetPagination(p)
func (s *Service) FindByEmitter(
ctx context.Context,
chain vaa.ChainID,
emitter *types.Address,
p *pagination.Pagination,
) ([]*ObservationDoc, error) {
query := Query().
SetChain(chain).
SetEmitter(emitter.ShortHex()).
SetPagination(p)
return s.repo.Find(ctx, query)
}
// FindByVAA get all the observations for a VAA (chainID, emitter addrress and sequence number).
func (s *Service) FindByVAA(ctx context.Context, chain vaa.ChainID, emitter *vaa.Address, seq string, p *pagination.Pagination) ([]*ObservationDoc, error) {
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq).SetPagination(p)
func (s *Service) FindByVAA(
ctx context.Context,
chain vaa.ChainID,
emitter *types.Address,
seq string,
p *pagination.Pagination,
) ([]*ObservationDoc, error) {
query := Query().
SetChain(chain).
SetEmitter(emitter.ShortHex()).
SetSequence(seq).
SetPagination(p)
return s.repo.Find(ctx, query)
}
// FindOne get a observation by chainID, emitter address, sequence, signer address and hash.
func (s *Service) FindOne(ctx context.Context, chainID vaa.ChainID, emitterAddr *vaa.Address, seq string, signerAddr *vaa.Address, hash []byte) (*ObservationDoc, error) {
query := Query().SetChain(chainID).SetEmitter(emitterAddr.String()).SetSequence(seq).SetGuardianAddr(signerAddr.String()).SetHash(hash)
func (s *Service) FindOne(
ctx context.Context,
chainID vaa.ChainID,
emitterAddr *types.Address,
seq string,
signerAddr *vaa.Address,
hash []byte,
) (*ObservationDoc, error) {
query := Query().
SetChain(chainID).
SetEmitter(emitterAddr.ShortHex()).
SetSequence(seq).
SetGuardianAddr(signerAddr.String()).
SetHash(hash)
return s.repo.FindOne(ctx, query)
}

View File

@ -10,6 +10,7 @@ import (
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/api/response"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
@ -90,13 +91,13 @@ func (s *Service) FindByChain(
func (s *Service) FindByEmitter(
ctx context.Context,
chain vaa.ChainID,
emitter vaa.Address,
emitter *types.Address,
p *pagination.Pagination,
) (*response.Response[[]*VaaDoc], error) {
query := Query().
SetChain(chain).
SetEmitter(emitter.String()).
SetEmitter(emitter.ShortHex()).
SetPagination(p)
vaas, err := s.repo.Find(ctx, query)
@ -109,7 +110,7 @@ func (s *Service) FindByEmitter(
func (s *Service) FindById(
ctx context.Context,
chain vaa.ChainID,
emitter vaa.Address,
emitter *types.Address,
seq string,
includeParsedPayload bool,
) (*response.Response[*VaaDoc], error) {
@ -141,24 +142,24 @@ func (s *Service) FindById(
func (s *Service) findById(
ctx context.Context,
chain vaa.ChainID,
emitter vaa.Address,
emitter *types.Address,
seq string,
) (*VaaDoc, error) {
query := Query().
SetChain(chain).
SetEmitter(emitter.String()).
SetEmitter(emitter.ShortHex()).
SetSequence(seq)
return s.repo.FindOne(ctx, query)
}
// findByIdWithPayload get a vaa with payload data by chainID, emitter address and sequence number.
func (s *Service) findByIdWithPayload(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq string) (*VaaDoc, error) {
func (s *Service) findByIdWithPayload(ctx context.Context, chain vaa.ChainID, emitter *types.Address, seq string) (*VaaDoc, error) {
query := Query().
SetChain(chain).
SetEmitter(emitter.String()).
SetEmitter(emitter.ShortHex()).
SetSequence(seq)
vaas, err := s.repo.FindVaasWithPayload(ctx, query)
@ -192,8 +193,8 @@ func (s *Service) GetVaaCount(ctx context.Context, p *pagination.Pagination) (*r
// discardVaaNotIndexed discard a vaa request if the input sequence for a chainID, address is greatter than or equals
// the cached value of the sequence for this chainID, address.
// If the sequence does not exist we can not discard the request.
func (s *Service) discardVaaNotIndexed(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq string) bool {
key := fmt.Sprintf("%s:%d:%s", "wormscan:vaa-max-sequence", chain, emitter.String())
func (s *Service) discardVaaNotIndexed(ctx context.Context, chain vaa.ChainID, emitter *types.Address, seq string) bool {
key := fmt.Sprintf("%s:%d:%s", "wormscan:vaa-max-sequence", chain, emitter.ShortHex())
sequence, err := s.getCacheFunc(ctx, key)
if err != nil {
if errors.Is(err, errs.ErrInternalError) {

View File

@ -8,6 +8,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/pkg/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/response"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
@ -31,11 +32,11 @@ func ExtractChainID(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, error) {
}
// ExtractEmitterAddr get emitter parameter from route path.
func ExtractEmitterAddr(c *fiber.Ctx, l *zap.Logger) (*vaa.Address, error) {
func ExtractEmitterAddr(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
emitterStr := c.Params("emitter")
emitter, err := vaa.StringToAddress(emitterStr)
emitter, err := types.StringToAddress(emitterStr)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to convert emitter to address",
@ -46,7 +47,7 @@ func ExtractEmitterAddr(c *fiber.Ctx, l *zap.Logger) (*vaa.Address, error) {
return nil, response.NewInvalidParamError(c, "MALFORMED EMITTER_ADDR", errors.WithStack(err))
}
return &emitter, nil
return emitter, nil
}
// ExtractSequence get sequence parameter from route path.
@ -69,37 +70,30 @@ func ExtractSequence(c *fiber.Ctx, l *zap.Logger) (uint64, error) {
}
// ExtractGuardianAddress get guardian address from route path.
func ExtractGuardianAddress(c *fiber.Ctx, l *zap.Logger) (string, error) {
func ExtractGuardianAddress(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
// read the address from query params
tmp := c.Params("guardian_address")
if tmp == "" {
return "", response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", nil)
return nil, response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", nil)
}
// validate the address using the SDK
guardianAddress, err := vaa.StringToAddress(tmp)
// validate the address
guardianAddress, err := types.StringToAddress(tmp)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to decode guardian address",
zap.Error(err),
zap.String("requestID", requestID),
)
return "", response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", errors.WithStack(err))
return nil, response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", errors.WithStack(err))
}
// make sure the address length is the expected
addr := guardianAddress.String()
if len(addr) != 64 {
return "", response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", nil)
}
// the address returned by the SDK has 24 leading zeroes
return addr[24:], nil
return guardianAddress, nil
}
// ExtractVAAParams get VAA chain, address from route path.
func ExtractVAAChainIDEmitter(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *vaa.Address, error) {
func ExtractVAAChainIDEmitter(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *types.Address, error) {
chainID, err := ExtractChainID(c, l)
if err != nil {
@ -115,7 +109,7 @@ func ExtractVAAChainIDEmitter(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *vaa.Ad
}
// ExtractVAAParams get VAAA chain, address and sequence from route path.
func ExtractVAAParams(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *vaa.Address, uint64, error) {
func ExtractVAAParams(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *types.Address, uint64, error) {
chainID, err := ExtractChainID(c, l)
if err != nil {

View File

@ -139,7 +139,7 @@ func (c *Controller) IsVaaEnqueued(ctx *fiber.Ctx) error {
if err != nil {
return err
}
isEnqueued, err := c.srv.IsVaaEnqueued(ctx.Context(), chainID, *emitter, strconv.FormatUint(seq, 10))
isEnqueued, err := c.srv.IsVaaEnqueued(ctx.Context(), chainID, emitter, strconv.FormatUint(seq, 10))
if err != nil {
return err
}

View File

@ -50,7 +50,7 @@ func (c *Controller) FindSignedVAAByID(ctx *fiber.Ctx) error {
vaa, err := c.srv.FindById(
ctx.Context(),
chainID,
*emitter,
emitter,
strconv.FormatUint(seq, 10),
false, /*includeParsedPayload*/
)

View File

@ -65,7 +65,7 @@ func (c *Controller) FindGovernorConfigurationByGuardianAddress(ctx *fiber.Ctx)
}
// query the database
govConfigs, err := c.srv.FindGovernorConfigByGuardianAddress(ctx.Context(), guardianAddress)
govConfigs, err := c.srv.FindGovernorConfigByGuardianAddress(ctx.Context(), guardianAddress.ShortHex())
if err != nil {
return err
} else if len(govConfigs) == 0 {
@ -127,7 +127,7 @@ func (c *Controller) FindGovernorStatusByGuardianAddress(ctx *fiber.Ctx) error {
return err
}
govStatus, err := c.srv.FindGovernorStatusByGuardianAddress(ctx.Context(), guardianAddress, p)
govStatus, err := c.srv.FindGovernorStatusByGuardianAddress(ctx.Context(), guardianAddress.ShortHex(), p)
if err != nil {
return err
}

View File

@ -128,7 +128,7 @@ func (c *Controller) FindByEmitter(ctx *fiber.Ctx) error {
return err
}
vaas, err := c.srv.FindByEmitter(ctx.Context(), chainID, *emitter, p)
vaas, err := c.srv.FindByEmitter(ctx.Context(), chainID, emitter, p)
if err != nil {
return err
}
@ -163,7 +163,7 @@ func (c *Controller) FindById(ctx *fiber.Ctx) error {
vaa, err := c.srv.FindById(
ctx.Context(),
chainID,
*emitter,
emitter,
strconv.FormatUint(seq, 10),
includeParsedPayload,
)

View File

@ -14,6 +14,7 @@ import (
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/heartbeats"
vaaservice "github.com/wormhole-foundation/wormhole-explorer/api/handlers/vaa"
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
@ -51,14 +52,16 @@ func (h *Handler) GetSignedVAA(ctx context.Context, request *publicrpcv1.GetSign
address, err := hex.DecodeString(request.MessageId.EmitterAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address: %v", err))
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address from hex: %v", err))
}
if len(address) != 32 {
return nil, status.Error(codes.InvalidArgument, "address must be 32 bytes")
}
addr := vaa.Address{}
copy(addr[:], address)
addr, err := types.BytesToAddress(address)
if err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address from bytes: %v", err))
}
sequence := strconv.FormatUint(request.MessageId.Sequence, 10)
@ -222,7 +225,7 @@ func (h *Handler) GovernorIsVAAEnqueued(ctx context.Context, request *publicrpcv
return nil, status.Error(codes.InvalidArgument, "Parameters are required")
}
chainID := vaa.ChainID(request.MessageId.EmitterChain)
emitterAddress, err := vaa.StringToAddress(request.MessageId.EmitterAddress)
emitterAddress, err := types.StringToAddress(request.MessageId.EmitterAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "Invalid emitter address")
}

56
api/types/address.go Normal file
View File

@ -0,0 +1,56 @@
package types
import (
"fmt"
"strings"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
)
type Address struct {
address vaa.Address
}
func BytesToAddress(b []byte) (*Address, error) {
var a Address
if len(b) != len(a.address) {
return nil, fmt.Errorf("expected byte slice to have len=%d, but got %d instead", len(a.address), len(b))
}
copy(a.address[:], b)
return &a, nil
}
// StringToAddress converts a hex-encoded address string into an *Address.
func StringToAddress(s string) (*Address, error) {
a, err := vaa.StringToAddress(s)
if err != nil {
return nil, err
}
return &Address{address: a}, nil
}
// Hex returns the full 32-byte address, encoded as hex.
func (addr *Address) Hex() string {
return addr.address.String()
}
// ShortHex returns a hex-encoded address that is usually shorted than Hex().
//
// If the full address returned by Hex() is prefixed 12 bytes set to zero,
// this function will trim those bytes.
func (addr *Address) ShortHex() string {
full := addr.Hex()
if len(full) == 64 && strings.HasPrefix(full, "000000000000000000000000") {
return full[24:]
}
return full
}

62
api/types/address_test.go Normal file
View File

@ -0,0 +1,62 @@
package types
import "testing"
// Test_Address_ShortString runs several test cases on the method `Address.ShortString()`.
func Test_Address_ShortString(t *testing.T) {
testCases := []struct {
Input string
Hex string
ShortHex string
}{
{
Input: "0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
Hex: "000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
ShortHex: "f890982f9310df57d00f659cf4fd87e65aded8d7",
},
{
Input: "000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
Hex: "000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
ShortHex: "f890982f9310df57d00f659cf4fd87e65aded8d7",
},
{
Input: "0xf890982f9310df57d00f659cf4fd87e65aded8d7",
Hex: "000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
ShortHex: "f890982f9310df57d00f659cf4fd87e65aded8d7",
},
{
Input: "f890982f9310df57d00f659cf4fd87e65aded8d7",
Hex: "000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7",
ShortHex: "f890982f9310df57d00f659cf4fd87e65aded8d7",
},
{
Input: "ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
Hex: "ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
ShortHex: "ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
},
{
Input: "0xec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
Hex: "ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
ShortHex: "ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
},
}
for i := range testCases {
tc := &testCases[i]
addr, err := StringToAddress(tc.Input)
if err != nil {
t.Fatalf("failed to parse address %s: %v", tc.Input, err)
}
if addr.Hex() != tc.Hex {
t.Fatalf("expected Address.Hex()=%s, but got %s", tc.Hex, addr.Hex())
}
if addr.ShortHex() != tc.ShortHex {
t.Fatalf("expected Address.ShortHex()=%s, but got %s", tc.ShortHex, addr.ShortHex())
}
}
}