From 784e5d4ad86b3ab5c1b24c0bf3eb4d324eeb7390 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Fri, 28 Oct 2022 11:30:45 +0800 Subject: [PATCH] more efficient ParseChainID implementation --- types/benchmark_test.go | 60 +++++++++++++++++++++++++++++++++++++---- types/chain_id.go | 47 +++++++++++--------------------- 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/types/benchmark_test.go b/types/benchmark_test.go index 23d7790a95..c1fdd38537 100644 --- a/types/benchmark_test.go +++ b/types/benchmark_test.go @@ -1,16 +1,66 @@ package types import ( - "fmt" + fmt "fmt" + "math/big" + "regexp" + "strings" "testing" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + regexChainID = `[a-z]{1,}` + regexEIP155Separator = `_{1}` + regexEIP155 = `[1-9][0-9]*` + regexEpochSeparator = `-{1}` + regexEpoch = `[1-9][0-9]*` + ethermintChainID = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)%s(%s)$`, + regexChainID, + regexEIP155Separator, + regexEIP155, + regexEpochSeparator, + regexEpoch)) ) +// ParseChainID parses a string chain identifier's epoch to an Ethereum-compatible +// chain-id in *big.Int format. The function returns an error if the chain-id has an invalid format +func ParseChainIDLegacy(chainID string) (*big.Int, error) { + chainID = strings.TrimSpace(chainID) + if len(chainID) > 48 { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "chain-id '%s' cannot exceed 48 chars", chainID) + } + + matches := ethermintChainID.FindStringSubmatch(chainID) + if matches == nil || len(matches) != 4 || matches[1] == "" { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "%s: %v", chainID, matches) + } + + // verify that the chain-id entered is a base 10 integer + chainIDInt, ok := new(big.Int).SetString(matches[2], 10) + if !ok { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "epoch %s must be base-10 integer format", matches[2]) + } + + return chainIDInt, nil +} + func BenchmarkParseChainID(b *testing.B) { b.ReportAllocs() - // Start at 1, for valid EIP155, see regexEIP155 variable. - for i := 1; i < b.N; i++ { - chainID := fmt.Sprintf("ethermint_1-%d", i) - if _, err := ParseChainID(chainID); err != nil { + // Parsing a typical ethermint chain id + for i := 0; i < b.N; i++ { + if _, err := ParseChainID("ethermint_9000-1"); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkParseChainIDLegacy(b *testing.B) { + b.ReportAllocs() + // Parsing a typical ethermint chain id + for i := 0; i < b.N; i++ { + if _, err := ParseChainIDLegacy("ethermint_9000-1"); err != nil { b.Fatal(err) } } diff --git a/types/chain_id.go b/types/chain_id.go index 0688423c9f..b5834dac69 100644 --- a/types/chain_id.go +++ b/types/chain_id.go @@ -1,55 +1,40 @@ package types import ( - "fmt" "math/big" - "regexp" + "strconv" "strings" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -var ( - regexChainID = `[a-z]{1,}` - regexEIP155Separator = `_{1}` - regexEIP155 = `[1-9][0-9]*` - regexEpochSeparator = `-{1}` - regexEpoch = `[1-9][0-9]*` - ethermintChainID = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)%s(%s)$`, - regexChainID, - regexEIP155Separator, - regexEIP155, - regexEpochSeparator, - regexEpoch)) -) - // IsValidChainID returns false if the given chain identifier is incorrectly formatted. func IsValidChainID(chainID string) bool { - if len(chainID) > 48 { - return false - } - - return ethermintChainID.MatchString(chainID) + _, err := ParseChainID(chainID) + return err != nil } // ParseChainID parses a string chain identifier's epoch to an Ethereum-compatible // chain-id in *big.Int format. The function returns an error if the chain-id has an invalid format func ParseChainID(chainID string) (*big.Int, error) { chainID = strings.TrimSpace(chainID) - if len(chainID) > 48 { - return nil, sdkerrors.Wrapf(ErrInvalidChainID, "chain-id '%s' cannot exceed 48 chars", chainID) + if len(chainID) == 0 { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "empty chain id") } - matches := ethermintChainID.FindStringSubmatch(chainID) - if matches == nil || len(matches) != 4 || matches[1] == "" { - return nil, sdkerrors.Wrapf(ErrInvalidChainID, "%s: %v", chainID, matches) + idx := strings.LastIndexByte(chainID, '-') + if idx < 0 { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "expect at least 1 epoch seperator, got 0") + } + idx2 := strings.LastIndexByte(chainID[:idx], '_') + if idx2 < 0 { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "expect at least 1 eip155 chain-id seperator, got 0") } // verify that the chain-id entered is a base 10 integer - chainIDInt, ok := new(big.Int).SetString(matches[2], 10) - if !ok { - return nil, sdkerrors.Wrapf(ErrInvalidChainID, "epoch %s must be base-10 integer format", matches[2]) + n, err := strconv.ParseInt(chainID[idx2+1:idx], 10, 64) + if err != nil { + return nil, sdkerrors.Wrapf(ErrInvalidChainID, "eip155 chain-id %s must be base-10 64bit integer format", chainID[idx2+1:idx]) } - - return chainIDInt, nil + return big.NewInt(n), nil }