Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
more efficient ParseChainID implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
yihuang committed Oct 28, 2022
1 parent 9b71c37 commit 9bd1720
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 40 deletions.
60 changes: 55 additions & 5 deletions types/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Expand Down
51 changes: 22 additions & 29 deletions types/chain_id.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
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))
const (
epochSeparator = '-'
eip155Separator = '_'
)

// 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, epochSeparator)
if idx < 0 {
return nil, sdkerrors.Wrapf(ErrInvalidChainID, "expect at least 1 epoch separator, got 0")
}
idx2 := strings.LastIndexByte(chainID[:idx], eip155Separator)
if idx2 < 0 {
return nil, sdkerrors.Wrapf(ErrInvalidChainID, "expect at least 1 eip155 chain-id separator, got 0")
}
if idx2 == 0 {
return nil, sdkerrors.Wrapf(ErrInvalidChainID, "empty chain identifier")
}

// 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
}
12 changes: 6 additions & 6 deletions types/chain_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ func TestParseChainID(t *testing.T) {
"invalid chain-id, undefined identifier", "_1-1", true, nil,
},
{
"invalid chain-id, uppercases", "ETHERMINT_1-1", true, nil,
"valid chain-id, uppercases", "ETHERMINT_1-1", false, big.NewInt(1),
},
{
"invalid chain-id, mixed cases", "Ethermint_1-1", true, nil,
"valid chain-id, mixed cases", "Ethermint_1-1", false, big.NewInt(1),
},
{
"invalid chain-id, special chars", "$&*#!_1-1", true, nil,
"valid chain-id, special chars", "$&*#!_1-1", false, big.NewInt(1),
},
{
"invalid eip155 chain-id, cannot start with 0", "ethermint_001-1", true, nil,
"valid eip155 chain-id, start with 0", "ethermint_001-1", false, big.NewInt(1),
},
{
"invalid eip155 chain-id, cannot invalid base", "ethermint_0x212-1", true, nil,
},
{
"invalid eip155 chain-id, non-integer", "ethermint_ethermint_9000-1", true, nil,
"valid eip155 chain-id, underscore in identifier", "ethermint_ethermint_9000-1", false, big.NewInt(9000),
},
{
"invalid epoch, undefined", "ethermint_-", true, nil,
Expand Down Expand Up @@ -81,7 +81,7 @@ func TestParseChainID(t *testing.T) {
} else {
require.NoError(t, err, tc.name)
require.Equal(t, tc.expInt, chainIDEpoch, tc.name)
require.True(t, IsValidChainID(tc.chainID))
require.True(t, IsValidChainID(tc.chainID), tc.name)
}
}
}

0 comments on commit 9bd1720

Please sign in to comment.