Co-authored-by: yihuang <huang@crypto.com> Co-authored-by: marbar3778 <marbar3778@yahoo.com>
This commit is contained in:
parent
9c6c648436
commit
d8957530a5
@ -50,6 +50,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* (x/staking) [#18035](https://github.com/cosmos/cosmos-sdk/pull/18035) Hoisted out of the redelegation loop, the non-changing validator and delegator addresses parsing.
|
||||
* (keyring) [#17913](https://github.com/cosmos/cosmos-sdk/pull/17913) Add `NewAutoCLIKeyring` for creating an AutoCLI keyring from a SDK keyring.
|
||||
* (x/consensus) [#18041](https://github.com/cosmos/cosmos-sdk/pull/18041) Let `ToProtoConsensusParams()` return an error.
|
||||
* [#18204](https://github.com/cosmos/cosmos-sdk/pull/18204) Use streaming json parser to parse chain-id from genesis file.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
||||
@ -475,12 +475,16 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) {
|
||||
chainID := cast.ToString(appOpts.Get(flags.FlagChainID))
|
||||
if chainID == "" {
|
||||
// fallback to genesis chain-id
|
||||
appGenesis, err := genutiltypes.AppGenesisFromFile(filepath.Join(homeDir, "config", "genesis.json"))
|
||||
reader, err := os.Open(filepath.Join(homeDir, "config", "genesis.json"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
chainID = appGenesis.ChainID
|
||||
chainID, err = genutiltypes.ParseChainIDFromGenesis(reader)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse chain-id from genesis file: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
snapshotStore, err := GetSnapshotStore(appOpts)
|
||||
|
||||
69
x/genutil/types/chain_id.go
Normal file
69
x/genutil/types/chain_id.go
Normal file
@ -0,0 +1,69 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cometbft/cometbft/types"
|
||||
)
|
||||
|
||||
const ChainIDFieldName = "chain_id"
|
||||
|
||||
// ParseChainIDFromGenesis parses the `chain_id` from a genesis JSON file, aborting early after finding the `chain_id`.
|
||||
// For efficiency, it's recommended to place the `chain_id` field before any large entries in the JSON file.
|
||||
// Returns an error if the `chain_id` field is not found.
|
||||
func ParseChainIDFromGenesis(r io.Reader) (string, error) {
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if t != json.Delim('{') {
|
||||
return "", fmt.Errorf("expected {, got %s", t)
|
||||
}
|
||||
|
||||
for dec.More() {
|
||||
t, err = dec.Token()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key, ok := t.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("expected string for the key type, got %s", t)
|
||||
}
|
||||
|
||||
if key == ChainIDFieldName {
|
||||
var chainID string
|
||||
if err := dec.Decode(&chainID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := validateChainID(chainID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return chainID, nil
|
||||
}
|
||||
|
||||
// skip the value
|
||||
var value json.RawMessage
|
||||
if err := dec.Decode(&value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("missing chain-id in genesis file")
|
||||
}
|
||||
|
||||
func validateChainID(chainID string) error {
|
||||
if strings.TrimSpace(chainID) == "" {
|
||||
return errors.New("genesis doc must include non-empty chain_id")
|
||||
}
|
||||
if len(chainID) > types.MaxChainIDLen {
|
||||
return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", types.MaxChainIDLen)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
130
x/genutil/types/chain_id_test.go
Normal file
130
x/genutil/types/chain_id_test.go
Normal file
@ -0,0 +1,130 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
)
|
||||
|
||||
//go:embed testdata/parse_chain_id.json
|
||||
var BenchmarkGenesis string
|
||||
|
||||
func TestParseChainIDFromGenesis(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
json string
|
||||
expChainID string
|
||||
expError string
|
||||
}{
|
||||
{
|
||||
"success",
|
||||
`{
|
||||
"state": {
|
||||
"accounts": {
|
||||
"a": {}
|
||||
}
|
||||
},
|
||||
"chain_id": "test-chain-id"
|
||||
}`,
|
||||
"test-chain-id",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"nested",
|
||||
`{
|
||||
"state": {
|
||||
"accounts": {
|
||||
"a": {}
|
||||
},
|
||||
"chain_id": "test-chain-id"
|
||||
}
|
||||
}`,
|
||||
"",
|
||||
"missing chain-id in genesis file",
|
||||
},
|
||||
{
|
||||
"not exist",
|
||||
`{
|
||||
"state": {
|
||||
"accounts": {
|
||||
"a": {}
|
||||
}
|
||||
},
|
||||
"chain-id": "test-chain-id"
|
||||
}`,
|
||||
"",
|
||||
"missing chain-id in genesis file",
|
||||
},
|
||||
{
|
||||
"invalid type",
|
||||
`{
|
||||
"chain-id": 1,
|
||||
}`,
|
||||
"",
|
||||
"invalid character '}' looking for beginning of object key string",
|
||||
},
|
||||
{
|
||||
"invalid json",
|
||||
`[ " ': }`,
|
||||
"",
|
||||
"expected {, got [",
|
||||
},
|
||||
{
|
||||
"empty chain_id",
|
||||
`{"chain_id": ""}`,
|
||||
"",
|
||||
"genesis doc must include non-empty chain_id",
|
||||
},
|
||||
{
|
||||
"whitespace chain_id",
|
||||
`{"chain_id": " "}`,
|
||||
"",
|
||||
"genesis doc must include non-empty chain_id",
|
||||
},
|
||||
{
|
||||
"chain_id too long",
|
||||
`{"chain_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}`,
|
||||
"",
|
||||
"chain_id in genesis doc is too long",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
chainID, err := types.ParseChainIDFromGenesis(strings.NewReader(tc.json))
|
||||
if tc.expChainID == "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expChainID, chainID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseChainID(b *testing.B) {
|
||||
expChainID := "cronosmainnet_25-1"
|
||||
b.ReportAllocs()
|
||||
b.Run("new", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
chainID, err := types.ParseChainIDFromGenesis(strings.NewReader(BenchmarkGenesis))
|
||||
require.NoError(b, err)
|
||||
require.Equal(b, expChainID, chainID)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("old", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
doc, err := types.AppGenesisFromReader(strings.NewReader(BenchmarkGenesis))
|
||||
require.NoError(b, err)
|
||||
require.Equal(b, expChainID, doc.ChainID)
|
||||
}
|
||||
})
|
||||
}
|
||||
1
x/genutil/types/testdata/parse_chain_id.json
vendored
Normal file
1
x/genutil/types/testdata/parse_chain_id.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user