diff --git a/go.mod b/go.mod index 6846982002..17753c9984 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/confio/ics23/go v0.6.3 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ledger-cosmos-go v0.11.1 + github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25 github.com/go-kit/kit v0.10.0 diff --git a/server/config/config.go b/server/config/config.go index 94472ef424..8e9cb68cf4 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -74,6 +74,9 @@ type BaseConfig struct { // IAVL cache size; bytes size unit IAVLCacheSize int `mapstructure:"iavl-cache-size"` + // Bech32CacheSize is the maximum bytes size of bech32 cache (Default : 1GB) + Bech32CacheSize int `mapstructure:"bech32-cache-size"` + // When true, Prometheus metrics are served under /metrics on prometheus_listen_addr in config.toml. // It works when tendermint's prometheus option (config.toml) is set to true. Prometheus bool `mapstructure:"prometheus"` @@ -179,6 +182,7 @@ func DefaultConfig() *Config { InterBlockCache: true, InterBlockCacheSize: cache.DefaultCommitKVStoreCacheSize, IAVLCacheSize: iavl.DefaultIAVLCacheSize, + Bech32CacheSize: sdk.DefaultBech32CacheSize, Pruning: storetypes.PruningOptionDefault, PruningKeepRecent: "0", PruningKeepEvery: "0", diff --git a/server/config/toml.go b/server/config/toml.go index d1fa97e0a7..de057ebdfa 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -69,6 +69,9 @@ inter-block-cache-size = {{ .BaseConfig.InterBlockCacheSize }} # IAVLCacheSize is the maximum bytes size of iavl node cache iavl-cache-size = {{ .BaseConfig.IAVLCacheSize }} +# Bech32CacheSize is the maximum bytes size of bech32 cache (Default : 1GB) +bech32-cache-size = {{ .BaseConfig.Bech32CacheSize }} + # IndexEvents defines the set of events in the form {eventType}.{attributeKey}, # which informs Tendermint what to index. If empty, all events will be indexed. # diff --git a/server/start.go b/server/start.go index 45826b313d..af59d72f96 100644 --- a/server/start.go +++ b/server/start.go @@ -43,6 +43,7 @@ const ( FlagInterBlockCache = "inter-block-cache" FlagInterBlockCacheSize = "inter-block-cache-size" FlagIAVLCacheSize = "iavl-cache-size" + FlagBech32CacheSize = "bech32-cache-size" FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades" FlagTrace = "trace" FlagInvCheckPeriod = "inv-check-period" diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index b763b9a2d1..3de911106c 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -165,6 +165,11 @@ func (a appCreator) newApp(logger log.Logger, db tmdb.DB, traceStore io.Writer, cast.ToInt(appOpts.Get(server.FlagInterBlockCacheSize)), ibCacheMetricsProvider) } + bech32CacheSize := cast.ToInt(appOpts.Get(server.FlagBech32CacheSize)) + if bech32CacheSize > 0 { + sdk.SetBech32Cache(int64(bech32CacheSize)) + } + skipUpgradeHeights := make(map[int64]bool) for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { skipUpgradeHeights[int64(h)] = true diff --git a/types/address.go b/types/address.go index 6b1a11c32f..35a8982465 100644 --- a/types/address.go +++ b/types/address.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/dgraph-io/ristretto" yaml "gopkg.in/yaml.v2" "github.com/line/lbm-sdk/v2/codec/legacy" @@ -92,6 +93,59 @@ var _ yaml.Marshaler = ConsAddress{} // account // ---------------------------------------------------------------------------- +// TODO We should add a layer to choose whether to access the cache or to run actual conversion +// bech32 encoding and decoding takes a lot of time, so memoize it +var bech32Cache Bech32Cache + +type Bech32Cache struct { + bech32ToAddrCache *ristretto.Cache + addrToBech32Cache *ristretto.Cache +} + +func SetBech32Cache(size int64) { + var err error + config := &ristretto.Config{ + NumCounters: 1e7, // number of keys to track frequency of (10M). + MaxCost: size, + BufferItems: 64, // number of keys per Get buffer. + } + bech32Cache.bech32ToAddrCache, err = ristretto.NewCache(config) + if err != nil { + panic(err) + } + bech32Cache.addrToBech32Cache, err = ristretto.NewCache(config) + if err != nil { + panic(err) + } +} + +func (cache *Bech32Cache) GetAddr(bech32Addr string) ([]byte, bool) { + if cache.bech32ToAddrCache != nil { + rawAddr, ok := cache.bech32ToAddrCache.Get(bech32Addr) + return rawAddr.([]byte), ok + } + return nil, false +} + +func (cache *Bech32Cache) GetBech32(rawAddr []byte) (string, bool) { + if cache.bech32ToAddrCache != nil { + bech32Addr, ok := cache.bech32ToAddrCache.Get(rawAddr) + return bech32Addr.(string), ok + } + return "", false +} + +func (cache *Bech32Cache) Set(bech32Addr string, rawAddr []byte) { + if cache.bech32ToAddrCache != nil { + cache.bech32ToAddrCache.Set(bech32Addr, rawAddr, int64(len(rawAddr))) + } + if cache.addrToBech32Cache != nil { + cache.addrToBech32Cache.Set(string(rawAddr), bech32Addr, int64(len(bech32Addr))) + } +} + +const DefaultBech32CacheSize = 1 << 30 // maximum size of cache (1GB). + // AccAddress a wrapper around bytes meant to represent an account address. // When marshaled to a string or JSON, it uses Bech32. type AccAddress []byte @@ -117,14 +171,19 @@ func VerifyAddressFormat(bz []byte) error { } // AccAddressFromBech32 creates an AccAddress from a Bech32 string. -func AccAddressFromBech32(address string) (addr AccAddress, err error) { - if len(strings.TrimSpace(address)) == 0 { +func AccAddressFromBech32(bech32Addr string) (AccAddress, error) { + addr, ok := bech32Cache.GetAddr(bech32Addr) + if ok { + return addr, nil + } + + if len(strings.TrimSpace(bech32Addr)) == 0 { return AccAddress{}, errors.New("empty address string is not allowed") } bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() - bz, err := GetFromBech32(address, bech32PrefixAccAddr) + bz, err := GetFromBech32(bech32Addr, bech32PrefixAccAddr) if err != nil { return nil, err } @@ -133,8 +192,8 @@ func AccAddressFromBech32(address string) (addr AccAddress, err error) { if err != nil { return nil, err } - - return AccAddress(bz), nil + bech32Cache.Set(bech32Addr, bz) + return bz, nil } // Returns boolean for whether two AccAddresses are Equal @@ -229,6 +288,11 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { + bech32Addr, ok := bech32Cache.GetBech32(aa) + if ok { + return bech32Addr + } + if aa.Empty() { return "" } @@ -239,7 +303,7 @@ func (aa AccAddress) String() string { if err != nil { panic(err) } - + bech32Cache.Set(bech32Addr, aa) return bech32Addr }