refactor(orm)!: support core genesis (#14822)

This commit is contained in:
Aaron Craelius 2023-02-07 10:24:55 -05:00 committed by GitHub
parent 364d3af5e7
commit 85e5896e78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 141 deletions

41
orm/CHANGELOG.md Normal file
View File

@ -0,0 +1,41 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) \#<issue-number> message
The issue numbers will later be link-ified during the release process so you do
not have to worry about including a link manually, but you can if you wish.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
"CLI Breaking" for breaking CLI commands.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]
### API-Breaking Changes
- [14822](https://github.com/cosmos/cosmos-sdk/pull/14822) Migrate to cosmossdk.io/core genesis API

View File

@ -5,15 +5,24 @@ import (
"fmt"
"sort"
"cosmossdk.io/core/appmodule"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
)
func (m moduleDB) DefaultJSON(target ormjson.WriteTarget) error {
type appModuleGenesisWrapper struct {
moduleDB
}
func (m appModuleGenesisWrapper) IsOnePerModuleType() {}
func (m appModuleGenesisWrapper) IsAppModule() {}
func (m appModuleGenesisWrapper) DefaultGenesis(target appmodule.GenesisTarget) error {
tableNames := maps.Keys(m.tablesByName)
sort.Slice(tableNames, func(i, j int) bool {
ti, tj := tableNames[i], tableNames[j]
@ -22,7 +31,7 @@ func (m moduleDB) DefaultJSON(target ormjson.WriteTarget) error {
for _, name := range tableNames {
table := m.tablesByName[name]
w, err := target.OpenWriter(name)
w, err := target(string(name))
if err != nil {
return err
}
@ -40,7 +49,7 @@ func (m moduleDB) DefaultJSON(target ormjson.WriteTarget) error {
return nil
}
func (m moduleDB) ValidateJSON(source ormjson.ReadSource) error {
func (m appModuleGenesisWrapper) ValidateGenesis(source appmodule.GenesisSource) error {
errMap := map[protoreflect.FullName]error{}
names := maps.Keys(m.tablesByName)
sort.Slice(names, func(i, j int) bool {
@ -48,7 +57,7 @@ func (m moduleDB) ValidateJSON(source ormjson.ReadSource) error {
return ti.Name() < tj.Name()
})
for _, name := range names {
r, err := source.OpenReader(name)
r, err := source(string(name))
if err != nil {
return err
}
@ -80,7 +89,7 @@ func (m moduleDB) ValidateJSON(source ormjson.ReadSource) error {
return nil
}
func (m moduleDB) ImportJSON(ctx context.Context, source ormjson.ReadSource) error {
func (m appModuleGenesisWrapper) InitGenesis(ctx context.Context, source appmodule.GenesisSource) error {
var names []string
for name := range m.tablesByName {
names = append(names, string(name))
@ -91,7 +100,7 @@ func (m moduleDB) ImportJSON(ctx context.Context, source ormjson.ReadSource) err
fullName := protoreflect.FullName(name)
table := m.tablesByName[fullName]
r, err := source.OpenReader(fullName)
r, err := source(string(fullName))
if err != nil {
return errors.Wrapf(err, "table %s", fullName)
}
@ -114,7 +123,7 @@ func (m moduleDB) ImportJSON(ctx context.Context, source ormjson.ReadSource) err
return nil
}
func (m moduleDB) ExportJSON(ctx context.Context, sink ormjson.WriteTarget) error {
func (m appModuleGenesisWrapper) ExportGenesis(ctx context.Context, sink appmodule.GenesisTarget) error {
// Ensure that we export the tables in a deterministic order.
tableNames := maps.Keys(m.tablesByName)
sort.Slice(tableNames, func(i, j int) bool {
@ -123,7 +132,7 @@ func (m moduleDB) ExportJSON(ctx context.Context, sink ormjson.WriteTarget) erro
})
for _, name := range tableNames {
w, err := sink.OpenWriter(name)
w, err := sink(string(name))
if err != nil {
return err
}

View File

@ -7,13 +7,12 @@ import (
"fmt"
"math"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"google.golang.org/protobuf/reflect/protoregistry"
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
"google.golang.org/protobuf/reflect/protodesc"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
@ -31,18 +30,19 @@ import (
type ModuleDB interface {
ormtable.Schema
// DefaultJSON writes default JSON for each table in the module to the target.
DefaultJSON(ormjson.WriteTarget) error
// GenesisHandler returns an implementation of appmodule.HasGenesis
// to be embedded in or called from app module implementations.
// Ex:
// type Keeper struct {
// appmodule.HasGenesis
// }
//
// func NewKeeper(db ModuleDB) *Keeper {
// return &Keeper{HasGenesis: db.GenesisHandler()}
// }
GenesisHandler() appmodule.HasGenesis
// ValidateJSON validates JSON for each table in the module.
ValidateJSON(ormjson.ReadSource) error
// ImportJSON imports JSON for each table in the module which has JSON
// defined in the read source.
ImportJSON(context.Context, ormjson.ReadSource) error
// ExportJSON exports JSON for each table in the module.
ExportJSON(context.Context, ormjson.WriteTarget) error
private()
}
type moduleDB struct {
@ -212,3 +212,8 @@ func (m moduleDB) EncodeEntry(entry ormkv.Entry) (k, v []byte, err error) {
func (m moduleDB) GetTable(message proto.Message) ormtable.Table {
return m.tablesByName[message.ProtoReflect().Descriptor().FullName()]
}
func (m moduleDB) GenesisHandler() appmodule.HasGenesis {
return appModuleGenesisWrapper{m}
}
func (moduleDB) private() {}

View File

@ -13,6 +13,7 @@ import (
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"cosmossdk.io/core/appconfig"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/genesis"
"cosmossdk.io/core/store"
dbm "github.com/cosmos/cosmos-db"
@ -32,7 +33,6 @@ import (
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
"github.com/cosmos/cosmos-sdk/orm/testing/ormtest"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
)
// These tests use a simulated bank keeper. Addresses and balances use
@ -225,40 +225,40 @@ func TestModuleDB(t *testing.T) {
}
// check JSON
target := ormjson.NewRawMessageTarget()
assert.NilError(t, db.DefaultJSON(target))
target := genesis.RawJSONTarget{}
assert.NilError(t, db.GenesisHandler().DefaultGenesis(target.Target()))
rawJson, err := target.JSON()
assert.NilError(t, err)
golden.Assert(t, string(rawJson), "default_json.golden")
target = ormjson.NewRawMessageTarget()
assert.NilError(t, db.ExportJSON(ctx, target))
target = genesis.RawJSONTarget{}
assert.NilError(t, db.GenesisHandler().ExportGenesis(ctx, target.Target()))
rawJson, err = target.JSON()
assert.NilError(t, err)
goodJSON := `{
"testpb.Supply": []
}`
source, err := ormjson.NewRawMessageSource(json.RawMessage(goodJSON))
source, err := genesis.SourceFromRawJSON(json.RawMessage(goodJSON))
assert.NilError(t, err)
assert.NilError(t, db.ValidateJSON(source))
assert.NilError(t, db.ImportJSON(ormtable.WrapContextDefault(ormtest.NewMemoryBackend()), source))
assert.NilError(t, db.GenesisHandler().ValidateGenesis(source))
assert.NilError(t, db.GenesisHandler().InitGenesis(ormtable.WrapContextDefault(ormtest.NewMemoryBackend()), source))
badJSON := `{
"testpb.Balance": 5,
"testpb.Supply": {}
}
`
source, err = ormjson.NewRawMessageSource(json.RawMessage(badJSON))
source, err = genesis.SourceFromRawJSON(json.RawMessage(badJSON))
assert.NilError(t, err)
assert.ErrorIs(t, db.ValidateJSON(source), ormerrors.JSONValidationError)
assert.ErrorIs(t, db.GenesisHandler().ValidateGenesis(source), ormerrors.JSONValidationError)
backend2 := ormtest.NewMemoryBackend()
ctx2 := ormtable.WrapContextDefault(backend2)
source, err = ormjson.NewRawMessageSource(rawJson)
source, err = genesis.SourceFromRawJSON(rawJson)
assert.NilError(t, err)
assert.NilError(t, db.ValidateJSON(source))
assert.NilError(t, db.ImportJSON(ctx2, source))
assert.NilError(t, db.GenesisHandler().ValidateGenesis(source))
assert.NilError(t, db.GenesisHandler().InitGenesis(ctx2, source))
testkv.AssertBackendsEqual(t, backend, backend2)
}

View File

@ -1,27 +0,0 @@
package ormjson
import (
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// ReadSource is a source for reading tables in JSON format. It
// may abstract over a single JSON object or JSON in separate files that
// can be streamed over.
type ReadSource interface {
// OpenReader returns an io.ReadCloser for the named table. If there
// is no JSON for this table, this method will return nil. It is
// important the caller closes the reader when done with it.
OpenReader(tableName protoreflect.FullName) (io.ReadCloser, error)
}
// WriteTarget is a target for writing tables in JSON format. It
// may abstract over a single JSON object or JSON in separate files that
// can be written incrementally.
type WriteTarget interface {
// OpenWriter returns an io.WriteCloser for the named table. It is
// important the caller closers the writer AND checks the error
// when done with it.
OpenWriter(tableName protoreflect.FullName) (io.WriteCloser, error)
}

View File

@ -1,79 +0,0 @@
package ormjson
import (
"bytes"
"encoding/json"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
type rawMessageSource struct {
m map[string]json.RawMessage
}
// NewRawMessageSource returns a new ReadSource for the provided
// json.RawMessage where it is assumed that the raw message is a JSON
// map where each table's JSON referenced by the map key corresponding
// to the tables full protobuf name.
func NewRawMessageSource(message json.RawMessage) (ReadSource, error) {
var m map[string]json.RawMessage
err := json.Unmarshal(message, &m)
if err != nil {
return nil, err
}
return &rawMessageSource{m}, err
}
func (r rawMessageSource) OpenReader(tableName protoreflect.FullName) (io.ReadCloser, error) {
j, ok := r.m[string(tableName)]
if !ok {
return nil, nil
}
return readCloserWrapper{bytes.NewReader(j)}, nil
}
type readCloserWrapper struct {
io.Reader
}
func (r readCloserWrapper) Close() error { return nil }
var _ ReadSource = rawMessageSource{}
// RawMessageTarget is a WriteTarget wrapping a raw JSON map.
type RawMessageTarget struct {
m map[string]json.RawMessage
}
// NewRawMessageTarget returns a new WriteTarget where each table's JSON
// is written to a map key corresponding to the table's full protobuf name.
func NewRawMessageTarget() *RawMessageTarget {
return &RawMessageTarget{}
}
func (r *RawMessageTarget) OpenWriter(tableName protoreflect.FullName) (io.WriteCloser, error) {
if r.m == nil {
r.m = map[string]json.RawMessage{}
}
return &rawWriter{Buffer: &bytes.Buffer{}, sink: r, table: tableName}, nil
}
// JSON returns the JSON map that was written as a json.RawMessage.
func (r *RawMessageTarget) JSON() (json.RawMessage, error) {
return json.MarshalIndent(r.m, "", " ")
}
type rawWriter struct {
*bytes.Buffer
table protoreflect.FullName
sink *RawMessageTarget
}
func (r rawWriter) Close() error {
r.sink.m[string(r.table)] = r.Buffer.Bytes()
return nil
}
var _ WriteTarget = &RawMessageTarget{}