Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: file base genesis source/target #13724

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f2fb040
implement file genesis source/target to support genesis state import/…
JayT106 Nov 1, 2022
aacb158
remove unrelated file
JayT106 Nov 1, 2022
5cef410
fix gosec error
JayT106 Nov 8, 2022
0aa3faf
fix gosec
JayT106 Nov 8, 2022
0271aa2
fix function returns
JayT106 Nov 8, 2022
9793747
fix function returns
JayT106 Nov 8, 2022
6338347
Update core/genesis/file.go
JayT106 Nov 8, 2022
61704a6
Update core/genesis/file.go
JayT106 Nov 8, 2022
a7b4c7a
Update core/genesis/file.go
JayT106 Nov 9, 2022
885b92f
Update core/genesis/file.go
JayT106 Nov 9, 2022
17fdaf5
Update core/genesis/file.go
JayT106 Nov 9, 2022
cde16b7
Update core/genesis/file.go
JayT106 Nov 9, 2022
c41e73e
Update core/genesis/file.go
JayT106 Nov 9, 2022
e26e2ed
update struct field
JayT106 Nov 9, 2022
224468b
revise OpenReader for reading field data
JayT106 Nov 9, 2022
b8b0d9a
assign ReadRawJSON() result to moduleRootJson and update unit tests
JayT106 Nov 11, 2022
274e49b
fix dependency
JayT106 Nov 14, 2022
194bf29
remove readCloserWrapper and use io.NopCloser instead
JayT106 Nov 28, 2022
087e8a1
fix wording
JayT106 Nov 28, 2022
c98dbc7
propagate the error details
JayT106 Nov 28, 2022
fa6e0fa
nit: reduce nested block
JayT106 Nov 28, 2022
8d4047d
propagate the error details
JayT106 Nov 28, 2022
e65f2fd
update code comment
JayT106 Nov 28, 2022
7471cc1
explicit file not IsNotExist error
JayT106 Dec 2, 2022
be088da
nit
JayT106 Dec 2, 2022
5fec7b8
update module dependency
JayT106 Dec 2, 2022
b8d7e4e
nit
JayT106 Dec 2, 2022
416df26
nit
JayT106 Dec 2, 2022
32e5ae6
Update core/genesis/file.go
JayT106 Dec 6, 2022
119c31d
Update core/genesis/file.go
JayT106 Dec 6, 2022
2ffc074
revise FileGenesisSource logic with moduleRawState passing
JayT106 Dec 6, 2022
51ce71e
return default rawJson if fileNotExist error in ReadRawJSON
JayT106 Dec 7, 2022
e15e2f1
merge module state when calling ReadRawJSON
JayT106 Dec 7, 2022
d350663
add fast path in ReadRawJSON
JayT106 Dec 7, 2022
e7b5dce
fix unused import during merge
JayT106 Jan 3, 2023
ad0193b
implement new fileGenesis functions with new genesis API
JayT106 Jan 3, 2023
f316228
simplify the function argument
JayT106 Jan 23, 2023
fafd034
Merge branch 'main' into core-genesis
tac0turtle Feb 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 328 additions & 0 deletions core/genesis/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package genesis

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"

"cosmossdk.io/core/appmodule"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

type FileGenesisSource struct {
sourceDir string
moduleName string

// the RawMessage from the genesis.json app_state.<module> that got passed into InitChain
moduleRootJson json.RawMessage
// flag for loading merged module states
mergedStates bool
}
JayT106 marked this conversation as resolved.
Show resolved Hide resolved

const (
fileOpenflag = os.O_CREATE | os.O_WRONLY
flieOpenMode = fs.FileMode(0o600)
dirCreateMode = fs.FileMode(0o700)
)

// NewFileGenesisSource returns a new GenesisSource for the provided
// source directory and the provided module name where it is assumed
// that it contains encoded JSON data in the file or in the moduleState
// of the appState be passed from RequestInitChain.
func NewFileGenesisSource(sourceDir, moduleName string, rawModuleState json.RawMessage) GenesisSource {
return &FileGenesisSource{
sourceDir: filepath.Clean(sourceDir),
moduleName: moduleName,
moduleRootJson: rawModuleState,
}
}

// OpenReader opens the source field reading from the given parameters,
// and returns a ReadCloser.
// It will try to open the field in order following by:
// <sourceDir>/<module>/<field>.json
// <field> key inside <sourceDir>/<module>.json
// app_state.<module>.<field> key from moduleRootJson
func (f *FileGenesisSource) OpenReader(field string) (io.ReadCloser, error) {
// try reading genesis data from <sourceDir>/<module>/<field>.json
fName := fmt.Sprintf("%s.json", field)
fPath := filepath.Join(f.sourceDir, f.moduleName)

fp, err := os.Open(filepath.Clean(filepath.Join(fPath, fName)))
if err == nil {
return fp, nil
}

if !os.IsNotExist(err) {
return nil, fmt.Errorf("unexpected error: %w", err)
}

if f.mergedStates {
return f.unmarshalRawModuleWithField(f.moduleRootJson, field)
}

// try reading from <sourceDir>/<module>.json
rawBz, err := f.ReadRawJSON()
if err != nil {
return nil, err
}

return f.unmarshalRawModuleWithField(rawBz, field)
}

func (f *FileGenesisSource) unmarshalRawModuleWithField(rawBz []byte, field string) (io.ReadCloser, error) {
fieldState := make(map[string]json.RawMessage)
err := json.Unmarshal(rawBz, &fieldState)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal fields from module state %s, err: %w", f.moduleName, err)
}

fieldRawData := fieldState[field]
if fieldRawData == nil {
return nil, fmt.Errorf("failed to retrieve module field %s/%s from genesis.json", f.moduleName, field)
}

// wrap raw field data to a ReadCloser
return io.NopCloser(bytes.NewReader(fieldRawData)), nil
}

// ReadMessage reads rawJSON data from source file or moduleRawData,
// and then unmarshal it into proto.Message
func (f *FileGenesisSource) ReadMessage(msg proto.Message) error {
bz, err := f.ReadRawJSON()
if err != nil {
return fmt.Errorf("unexpected error: %w", err)
}

return protojson.Unmarshal(bz, msg)
}

// ReadRawJSON returns a json.RawMessage read from the source file given by the
// source directory and the module name.
// Return the rawModuleJson coming from Initchain if the err is equal to ErrNotExist
func (f *FileGenesisSource) ReadRawJSON() (rawBz json.RawMessage, rerr error) {
fName := fmt.Sprintf("%s.json", f.moduleName)
fPath := filepath.Join(f.sourceDir, fName)

fp, err := os.Open(filepath.Clean(fPath))
if err != nil {
if os.IsNotExist(err) {
f.mergedStates = true
return f.moduleRootJson, nil
}
return nil, err
}

defer func() {
if err := fp.Close(); err != nil {
if rerr != nil {
rerr = fmt.Errorf("failed to close file %s: %s, %w", fp.Name(), err.Error(), rerr)
return
}
rerr = fmt.Errorf("failed to close file %s: %w", fp.Name(), err)
}
}()

fi, err := fp.Stat()
if err != nil {
rerr = fmt.Errorf("failed to stat file %s: %w", fp.Name(), rerr)
return nil, rerr
}

buf, err := io.ReadAll(fp)
if err != nil {
rerr = fmt.Errorf("failed to read file %s: %w", fp.Name(), err)
return nil, rerr
}
if int64(len(buf)) != fi.Size() {
rerr = fmt.Errorf("couldn't read entire file: %s, read: %d, file size: %d", fp.Name(), len(buf), fi.Size())
return nil, rerr
}

// if the file is empty, return moduleRootJson instead
if len(buf) == 0 {
f.mergedStates = true
return f.moduleRootJson, nil
}

// if moduleRootJson is empty, no data combined needed
if len(f.moduleRootJson) == 0 {
f.moduleRootJson = buf
f.mergedStates = true
return f.moduleRootJson, nil
}

// else, combine data with moduleRootJson
moduleStates := make(map[string]interface{})
if err := json.Unmarshal(f.moduleRootJson, &moduleStates); err != nil {
rerr = fmt.Errorf("failed to unmarshal moduleRootJson: %w", err)
return nil, rerr
}

if err := json.Unmarshal(buf, &moduleStates); err != nil {
rerr = fmt.Errorf("failed to unmarshal the source module file: %w", err)
return nil, rerr
}

bz, err := json.Marshal(moduleStates)
if err != nil {
rerr = fmt.Errorf("failed to marshal the combined module states: %w", err)
return nil, rerr
}

// overwrite moduleRootJson with updated moduleStates
f.moduleRootJson = bz
f.mergedStates = true

return f.moduleRootJson, nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should default to the json coming from initchain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I updated it to return the default rawJson when the file doesn't exist. If we have an error during the file reading, we should return the error directly I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense


// SourceFromFile opens the source field reading from the given parameters,
// and returns appmodule.GenesisSource.
// It will try to open the field in order following by:
// <sourceDir>/<field>.json
func SourceFromFile(sourceDir string) (appmodule.GenesisSource, error) {
return func(field string) (io.ReadCloser, error) {
fName := fmt.Sprintf("%s.json", field)
fPath := filepath.Clean(filepath.Join(sourceDir, fName))
fp, err := os.Open(fPath)
if err != nil {
return nil, fmt.Errorf("failed to open the source file: %s, err: %w", fPath, err)
}

return fp, nil
}, nil
}

type FileGenesisTarget struct {
targetDir string
moduleName string
indent bool
}

// NewFileGenesisTarget returns GenesisTarget implementation with given target directory
// and the given module name.
func NewFileGenesisTarget(targetDir, moduleName string) GenesisTarget {
return &FileGenesisTarget{
targetDir: filepath.Clean(targetDir),
moduleName: moduleName,
}
}

// NewFileGenesisTargetWithIndent returns GenesisTarget implementation with given target directory,
// the given module name, and enabled the indent option for JSON raw data.
func NewFileGenesisTargetWithIndent(targetDir, moduleName string) GenesisTarget {
return &FileGenesisTarget{
targetDir: filepath.Clean(targetDir),
moduleName: moduleName,
indent: true,
}
}

// OpenWriter create a file for writing the genesus state to the file.
// It will try to create a file in order following by:
// <targetDir>/<module>/<field>.json
// <targetDir>/<module>.json when field is empty
// <targetDir>/genesis.json when both module name and field are empty
func (f *FileGenesisTarget) OpenWriter(field string) (io.WriteCloser, error) {
// try to create open/create a file to <targetDir>/<module>/<field>.json
if len(field) > 0 {
if len(f.moduleName) == 0 {
return nil, fmt.Errorf("failed to open writer, the module name must be specified when field is assigned")
}

fPath := filepath.Join(f.targetDir, f.moduleName)
if err := os.MkdirAll(fPath, dirCreateMode); err != nil {
return nil, fmt.Errorf("failed to create target directory %s: %w", fPath, err)
}

fileName := fmt.Sprintf("%s.json", field)
return os.OpenFile(filepath.Clean(filepath.Join(f.targetDir, f.moduleName, fileName)), fileOpenflag, flieOpenMode)
}

if err := os.MkdirAll(f.targetDir, dirCreateMode); err != nil {
return nil, fmt.Errorf("failed to create target directory: %w", err)
}

// if there is empty field, try to open/create a file to <targetDir>/<module>.json
if len(f.moduleName) > 0 {
fName := fmt.Sprintf("%s.json", f.moduleName)
return os.OpenFile(filepath.Clean(filepath.Join(f.targetDir, fName)), fileOpenflag, flieOpenMode)
}

// else if there is empty module and field name try to open/create a file to <targetDir>/genesis.json
return os.OpenFile(filepath.Clean(filepath.Join(f.targetDir, "genesis.json")), fileOpenflag, flieOpenMode)
}

// WriteRawJSON wtites the encoded JSON data to desinated target directory and the
// file.
func (f *FileGenesisTarget) WriteRawJSON(rawBz json.RawMessage) (rerr error) {
if err := os.MkdirAll(f.targetDir, dirCreateMode); err != nil {
return fmt.Errorf("failed to create target directory %s: %w", f.targetDir, err)
}

if len(f.moduleName) == 0 {
return fmt.Errorf("failed to write RawJSON: empty module name")
}

fName := fmt.Sprintf("%s.json", f.moduleName)
fPath := filepath.Join(f.targetDir, fName)
fp, err := os.OpenFile(filepath.Clean(fPath), fileOpenflag, flieOpenMode)
Fixed Show fixed Hide fixed
if err != nil {
return fmt.Errorf("failed to create file, %s: %w", fPath, err)
}

defer func() {
if err := fp.Close(); err != nil {
if rerr != nil {
rerr = fmt.Errorf("failed to close file %s: %s, %w", fName, err.Error(), rerr)
return
}

rerr = fmt.Errorf("failed to close file %s: %w", fName, err)
}
}()

if f.indent {
rawBz, err = json.MarshalIndent(rawBz, "", " ")
if err != nil {
return fmt.Errorf("failed to format the raw JSON data: %w", err)
}
}

n, err := fp.Write(rawBz)
if err != nil {
return fmt.Errorf("failed to write genesis file %s: %w", fName, err)
}

if n != len(rawBz) {
return fmt.Errorf("failed to written %s, expect:%d, actual: %d", fName, len(rawBz), n)
}

return nil
}

// WriteMessage is an unsupported op.
func (f *FileGenesisTarget) WriteMessage(proto.Message) error {
return errors.New("unsupported op")
}

// TargetToFile create a file for writing the genesus state to the file.
// It will try to create a file <targetDir>/<field>.json
func TargetToFile(targetDir string) appmodule.GenesisTarget {
return func(field string) (io.WriteCloser, error) {
if err := os.MkdirAll(targetDir, dirCreateMode); err != nil {
return nil, fmt.Errorf("failed to create target directory %s: %w", targetDir, err)
}

fName := fmt.Sprintf("%s.json", field)
fPath := filepath.Clean(filepath.Join(targetDir, fName))
return os.OpenFile(fPath, fileOpenflag, flieOpenMode)
}
}
Loading