From 2727c14183d05f5c31ca11f2dcd52b0aa683fadf Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 24 Nov 2020 06:15:19 +0000 Subject: [PATCH] types/Coin: compile and reuse Regexps to reduce massive RAM+CPU burn (#8001) types/Coin: compile and reuse Regexps to reduce massive RAM+CPU burn (#7989) From: #7989 Closes: #7986 Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Emmanuel T Odeke --- CHANGELOG.md | 2 +- RELEASE_NOTES.md | 4 ++-- types/bench_test.go | 30 ++++++++++++++++++++++++++++++ types/coin.go | 34 +++++++++++++++++++--------------- types/coin_test.go | 6 +++--- types/dec_coin.go | 2 +- 6 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 types/bench_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d23048413aca..b154ff55d589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features -* (types/coin.go) [\#6755](https://github.com/cosmos/cosmos-sdk/pull/6755) Add custom regex validation for `Coin` denom by overwriting `CoinDenomRegex` when using `/types/coin.go`. +* (types/coin.go) [\#6755](https://github.com/cosmos/cosmos-sdk/pull/6755) [\#8001](https://github.com/cosmos/cosmos-sdk/pull/8001) Allow custom regex validation for `Coin` denom through `SetCoinDenomRegex()`. * (version) [\#7835](https://github.com/cosmos/cosmos-sdk/issues/7835) [\#7940](https://github.com/cosmos/cosmos-sdk/issues/7940) The version --long command now shows the list of build dependencies and their versioning information. ### Improvements diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 95077e74bef4..6c816d050fe9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,8 +6,8 @@ See the [Cosmos SDK 0.39.2 milestone](https://github.com/cosmos/cosmos-sdk/miles ## Allow ValidateDenom() to be customised per application -Applications can now customise `types.Coin` denomination validation by -replacing `types.CoinDenomRegex` with their application-specific validation function. +Applications can now customise `types.Coin` denomination validation by passing +their application-specific validation function to `types.SetCoinDenomRegex()`. ## Upgrade queries don't work after upgrade diff --git a/types/bench_test.go b/types/bench_test.go new file mode 100644 index 000000000000..be7e0c7f4b7e --- /dev/null +++ b/types/bench_test.go @@ -0,0 +1,30 @@ +package types_test + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/types" +) + +var coinStrs = []string{ + "2000ATM", + "5000AMX", + "192XXX", + "1e9BTC", +} + +func BenchmarkParseCoin(b *testing.B) { + var blankCoin types.Coin + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, coinStr := range coinStrs { + coin, err := types.ParseCoin(coinStr) + if err != nil { + b.Fatal(err) + } + if coin == blankCoin { + b.Fatal("Unexpectedly returned a blank coin") + } + } + } +} diff --git a/types/coin.go b/types/coin.go index 927312475b33..24782a6a9684 100644 --- a/types/coin.go +++ b/types/coin.go @@ -599,19 +599,13 @@ var ( reAmt = `[[:digit:]]+` reDecAmt = `[[:digit:]]*\.[[:digit:]]+` reSpc = `[[:space:]]*` - reDnm = returnReDnm - reCoin = returnReCoin - reDecCoin = returnDecCoin + reDnm *regexp.Regexp + reCoin *regexp.Regexp + reDecCoin *regexp.Regexp ) -func returnDecCoin() *regexp.Regexp { - return regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, CoinDenomRegex())) -} -func returnReCoin() *regexp.Regexp { - return regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, CoinDenomRegex())) -} -func returnReDnm() *regexp.Regexp { - return regexp.MustCompile(fmt.Sprintf(`^%s$`, CoinDenomRegex())) +func init() { + SetCoinDenomRegex(DefaultCoinDenomRegex) } // DefaultCoinDenomRegex returns the default regex string @@ -619,13 +613,23 @@ func DefaultCoinDenomRegex() string { return reDnmString } -// CoinDenomRegex returns the current regex string and can be overwritten for custom validation -var CoinDenomRegex = DefaultCoinDenomRegex +// coinDenomRegex returns the current regex string and can be overwritten through the SetCoinDenomRegex accessor. +var coinDenomRegex = DefaultCoinDenomRegex + +// SetCoinDenomRegex allows for coin's custom validation by overriding the regular +// expression string used for denom validation. +func SetCoinDenomRegex(reFn func() string) { + coinDenomRegex = reFn + + reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, coinDenomRegex())) + reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, coinDenomRegex())) + reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, coinDenomRegex())) +} // ValidateDenom validates a denomination string returning an error if it is // invalid. func ValidateDenom(denom string) error { - if !reDnm().MatchString(denom) { + if !reDnm.MatchString(denom) { return fmt.Errorf("invalid denom: %s", denom) } return nil @@ -642,7 +646,7 @@ func mustValidateDenom(denom string) { func ParseCoin(coinStr string) (coin Coin, err error) { coinStr = strings.TrimSpace(coinStr) - matches := reCoin().FindStringSubmatch(coinStr) + matches := reCoin.FindStringSubmatch(coinStr) if matches == nil { return Coin{}, fmt.Errorf("invalid coin expression: %s", coinStr) } diff --git a/types/coin_test.go b/types/coin_test.go index 4877b3332e83..d73b46cda327 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -73,9 +73,9 @@ func TestCoinIsValid(t *testing.T) { func TestCustomValidation(t *testing.T) { newDnmRegex := `[\x{1F600}-\x{1F6FF}]` - CoinDenomRegex = func() string { + SetCoinDenomRegex(func() string { return newDnmRegex - } + }) cases := []struct { coin Coin @@ -92,7 +92,7 @@ func TestCustomValidation(t *testing.T) { for i, tc := range cases { require.Equal(t, tc.expectPass, tc.coin.IsValid(), "unexpected result for IsValid, tc #%d", i) } - CoinDenomRegex = DefaultCoinDenomRegex + SetCoinDenomRegex(DefaultCoinDenomRegex) } func TestAddCoin(t *testing.T) { diff --git a/types/dec_coin.go b/types/dec_coin.go index 62e35bffc98f..9b6e700104e7 100644 --- a/types/dec_coin.go +++ b/types/dec_coin.go @@ -600,7 +600,7 @@ func (coins DecCoins) Sort() DecCoins { func ParseDecCoin(coinStr string) (coin DecCoin, err error) { coinStr = strings.TrimSpace(coinStr) - matches := reDecCoin().FindStringSubmatch(coinStr) + matches := reDecCoin.FindStringSubmatch(coinStr) if matches == nil { return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr) }