162 lines
3.7 KiB
Go
162 lines
3.7 KiB
Go
package address
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/golang-lru/simplelru"
|
|
|
|
"cosmossdk.io/core/address"
|
|
errorsmod "cosmossdk.io/errors"
|
|
|
|
"github.com/cosmos/cosmos-sdk/internal/conv"
|
|
sdkAddress "github.com/cosmos/cosmos-sdk/types/address"
|
|
"github.com/cosmos/cosmos-sdk/types/bech32"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
var errEmptyAddress = errors.New("empty address string is not allowed")
|
|
|
|
var (
|
|
_ address.Codec = &Bech32Codec{}
|
|
_ address.Codec = &cachedBech32Codec{}
|
|
)
|
|
|
|
type Bech32Codec struct {
|
|
Bech32Prefix string
|
|
}
|
|
|
|
type cachedBech32Codec struct {
|
|
codec Bech32Codec
|
|
mu *sync.Mutex
|
|
cache *simplelru.LRU
|
|
}
|
|
|
|
type CachedCodecOptions struct {
|
|
Mu *sync.Mutex
|
|
Lru *simplelru.LRU
|
|
}
|
|
|
|
func NewBech32Codec(prefix string) address.Codec {
|
|
return &Bech32Codec{Bech32Prefix: prefix}
|
|
}
|
|
|
|
func NewCachedBech32Codec(prefix string, opts CachedCodecOptions) (address.Codec, error) {
|
|
var err error
|
|
ac := Bech32Codec{prefix}
|
|
if opts.Mu == nil && opts.Lru == nil {
|
|
opts.Mu = new(sync.Mutex)
|
|
opts.Lru, err = simplelru.NewLRU(256, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create LRU cache: %w", err)
|
|
}
|
|
} else if opts.Mu == nil && opts.Lru != nil {
|
|
// The LRU cache uses a map internally. Without a mutex, concurrent access to this map can lead to race conditions.
|
|
// Therefore, a mutex is required to ensure thread-safe operations on the LRU cache.
|
|
return nil, errors.New("mutex must be provided alongside the LRU cache")
|
|
} else if opts.Mu != nil && opts.Lru == nil {
|
|
opts.Lru, err = simplelru.NewLRU(256, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create LRU cache: %w", err)
|
|
}
|
|
}
|
|
|
|
return cachedBech32Codec{
|
|
codec: ac,
|
|
cache: opts.Lru,
|
|
mu: opts.Mu,
|
|
}, nil
|
|
}
|
|
|
|
// StringToBytes encodes text to bytes
|
|
func (bc Bech32Codec) StringToBytes(text string) ([]byte, error) {
|
|
if len(strings.TrimSpace(text)) == 0 {
|
|
return []byte{}, errEmptyAddress
|
|
}
|
|
|
|
hrp, bz, err := bech32.DecodeAndConvert(text)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(bz) > sdkAddress.MaxAddrLen {
|
|
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", sdkAddress.MaxAddrLen, len(bz))
|
|
}
|
|
|
|
if hrp != bc.Bech32Prefix {
|
|
return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "hrp does not match bech32 prefix: expected '%s' got '%s'", bc.Bech32Prefix, hrp)
|
|
}
|
|
|
|
return bz, nil
|
|
}
|
|
|
|
// BytesToString decodes bytes to text
|
|
func (bc Bech32Codec) BytesToString(bz []byte) (string, error) {
|
|
if len(bz) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
text, err := bech32.ConvertAndEncode(bc.Bech32Prefix, bz)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(bz) > sdkAddress.MaxAddrLen {
|
|
return "", errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", sdkAddress.MaxAddrLen, len(bz))
|
|
}
|
|
|
|
return text, nil
|
|
}
|
|
|
|
func (cbc cachedBech32Codec) BytesToString(bz []byte) (string, error) {
|
|
if len(bz) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
key := conv.UnsafeBytesToStr(bz)
|
|
cbc.mu.Lock()
|
|
defer cbc.mu.Unlock()
|
|
|
|
addrs, ok := cbc.cache.Get(key)
|
|
if !ok {
|
|
addrs = make(map[string]string)
|
|
cbc.cache.Add(key, addrs)
|
|
}
|
|
|
|
addrMap, ok := addrs.(map[string]string)
|
|
if !ok {
|
|
return "", fmt.Errorf("cache contains non-map[string]string value for key %s", key)
|
|
}
|
|
|
|
addr, ok := addrMap[cbc.codec.Bech32Prefix]
|
|
if !ok {
|
|
var err error
|
|
addr, err = cbc.codec.BytesToString(bz)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
addrMap[cbc.codec.Bech32Prefix] = addr
|
|
}
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
func (cbc cachedBech32Codec) StringToBytes(text string) ([]byte, error) {
|
|
cbc.mu.Lock()
|
|
defer cbc.mu.Unlock()
|
|
|
|
if addr, ok := cbc.cache.Get(text); ok {
|
|
return addr.([]byte), nil
|
|
}
|
|
|
|
addr, err := cbc.codec.StringToBytes(text)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cbc.cache.Add(text, addr)
|
|
|
|
return addr, nil
|
|
}
|