diff --git a/schema/indexer/indexer.go b/schema/indexer/indexer.go index ba3d0db704fa..57ada8bb80c1 100644 --- a/schema/indexer/indexer.go +++ b/schema/indexer/indexer.go @@ -5,6 +5,7 @@ import ( "cosmossdk.io/schema/appdata" "cosmossdk.io/schema/logutil" + "cosmossdk.io/schema/view" ) // Config species the configuration passed to an indexer initialization function. @@ -55,10 +56,11 @@ type InitParams struct { Config Config // Context is the context that the indexer should use to listen for a shutdown signal via Context.Done(). Other - // parameters may also be passed through context from the app if necessary. + // parameters may also be passed through context from the app if necessary. It is expected to be non-nil. Context context.Context - // Logger is a logger the indexer can use to write log messages. + // Logger is a logger the indexer can use to write log messages. It may be nil if the indexer does not need + // to write logs. Logger logutil.Logger } @@ -67,12 +69,13 @@ type InitResult struct { // Listener is the indexer's app data listener. Listener appdata.Listener - // LastBlockPersisted indicates the last block that the indexer persisted (if it is persisting data). It - // should be 0 if the indexer has no data stored and wants to start syncing state. It should be -1 if the indexer - // does not care to persist state at all and is just listening for some other streaming purpose. If the indexer - // has persisted state and has missed some blocks, a runtime error will occur to prevent the indexer from continuing - // in an invalid state. If an indexer starts indexing after a chain's genesis (returning 0), the indexer manager - // will attempt to perform a catch-up sync of state. Historical events will not be replayed, but an accurate + // View is a view of indexed data that indexers can provide. It is optional and may be nil. + // If it is provided it can be used for automated testing and other purposes. + // At indexer start-up, the block number returned by the view will be used to determine the + // starting block for the indexer. If the block number is 0, the indexer manager will attempt + // to perform a catch-up sync of state. Historical events will not be replayed, but an accurate // representation of the current state at the height at which indexing began can be reproduced. - LastBlockPersisted int64 + // If the block number is non-zero but does not match the current chain height, a runtime error + // will occur because this is an unsafe condition that indicates lost data. + View view.AppData } diff --git a/schema/testing/appdatasim/app_data.go b/schema/testing/appdatasim/app_data.go index db82d285de5a..2f6138bfe824 100644 --- a/schema/testing/appdatasim/app_data.go +++ b/schema/testing/appdatasim/app_data.go @@ -9,6 +9,7 @@ import ( "cosmossdk.io/schema" "cosmossdk.io/schema/appdata" "cosmossdk.io/schema/testing/statesim" + "cosmossdk.io/schema/view" ) // Options are the options for creating an app data simulator. @@ -151,11 +152,11 @@ func (a *Simulator) ProcessPacket(packet appdata.Packet) error { } // AppState returns the current app state backing the simulator. -func (a *Simulator) AppState() statesim.AppState { +func (a *Simulator) AppState() view.AppState { return a.state } // BlockNum returns the current block number of the simulator. -func (a *Simulator) BlockNum() uint64 { - return a.blockNum +func (a *Simulator) BlockNum() (uint64, error) { + return a.blockNum, nil } diff --git a/schema/testing/appdatasim/diff.go b/schema/testing/appdatasim/diff.go index 5896331d542a..339419e7d9b8 100644 --- a/schema/testing/appdatasim/diff.go +++ b/schema/testing/appdatasim/diff.go @@ -4,25 +4,15 @@ import ( "fmt" "cosmossdk.io/schema/testing/statesim" + "cosmossdk.io/schema/view" ) -// HasAppData defines an interface for things that hold app data include app state. -// If an indexer implements this then DiffAppData can be used to compare it with -// the Simulator state which also implements this. -type HasAppData interface { - // AppState returns the app state. - AppState() statesim.AppState - - // BlockNum returns the latest block number. - BlockNum() uint64 -} - // DiffAppData compares the app data of two objects that implement HasAppData. // This can be used by indexer to compare their state with the Simulator state // if the indexer implements HasAppData. // It returns a human-readable diff if the app data differs and the empty string // if they are the same. -func DiffAppData(expected, actual HasAppData) string { +func DiffAppData(expected, actual view.AppData) string { res := "" if stateDiff := statesim.DiffAppStates(expected.AppState(), actual.AppState()); stateDiff != "" { @@ -30,8 +20,20 @@ func DiffAppData(expected, actual HasAppData) string { res += stateDiff } - if expected.BlockNum() != actual.BlockNum() { - res += fmt.Sprintf("BlockNum: expected %d, got %d\n", expected.BlockNum(), actual.BlockNum()) + expectedBlock, err := expected.BlockNum() + if err != nil { + res += fmt.Sprintf("ERROR getting expected block num: %s\n", err) + return res + } + + actualBlock, err := actual.BlockNum() + if err != nil { + res += fmt.Sprintf("ERROR getting actual block num: %s\n", err) + return res + } + + if expectedBlock != actualBlock { + res += fmt.Sprintf("BlockNum: expected %d, got %d\n", expectedBlock, actualBlock) } return res diff --git a/schema/testing/appdatasim/testdata/app_sim_example_schema.txt b/schema/testing/appdatasim/testdata/app_sim_example_schema.txt index 32aa613514a2..117d557ba5a4 100644 --- a/schema/testing/appdatasim/testdata/app_sim_example_schema.txt +++ b/schema/testing/appdatasim/testdata/app_sim_example_schema.txt @@ -78,7 +78,7 @@ StartBlock: {6 } OnObjectUpdate: {"ModuleName":"test_cases","Updates":[{"TypeName":"TwoKeys","Key":["A𞥟",981],"Value":null,"Delete":false},{"TypeName":"ManyValues","Key":"ᵕ؏­􏿽A","Value":{"Value1":-317,"Value2":"AA==","Value3":-37.62890625,"Value4":232},"Delete":false}]} OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_address","Key":"AACHBAjyAgFHOQAABo+PGAK3Bj7TwwBb/wAB3gE=","Value":{"valNotNull":"HBwBHAY6AAKO+UwDKRICAT0lgRRvCRvHFFoNAigBAUEDHoQUfB2qApRB/z41AAubARsBATQg3gCppQMAAQwHAQ=="},"Delete":false}]} OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_decimal","Key":"9.5E+8","Value":["-2","88111430.0122412446"],"Delete":false}]} -OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_uint8","Key":7,"Value":null,"Delete":true},{"TypeName":"test_time","Key":"1970-01-01T00:59:59.999999999+01:00","Value":["1970-01-01T01:00:00.000000001+01:00",null],"Delete":false}]} +OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_uint8","Key":7,"Value":null,"Delete":true},{"TypeName":"test_time","Key":"1969-12-31T18:59:59.999999999-05:00","Value":["1969-12-31T19:00:00.000000001-05:00",null],"Delete":false}]} OnObjectUpdate: {"ModuleName":"test_cases","Updates":[{"TypeName":"RetainDeletions","Key":"൴~𝔶ٞ蹯a_ ᛮ!؋aض©-?","Value":{"Value2":""},"Delete":false}]} OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_uint8","Key":14,"Value":{"valNotNull":116},"Delete":false},{"TypeName":"test_duration","Key":100403021838,"Value":[1547,null],"Delete":false}]} OnObjectUpdate: {"ModuleName":"all_kinds","Updates":[{"TypeName":"test_int64","Key":-34196421,"Value":[56,224549431],"Delete":false},{"TypeName":"test_bool","Key":true,"Value":{"valNotNull":true,"valNullable":null},"Delete":false}]} diff --git a/schema/testing/appdatasim/testdata/diff_example.txt b/schema/testing/appdatasim/testdata/diff_example.txt index 636e388937ee..fcdd45729de3 100644 --- a/schema/testing/appdatasim/testdata/diff_example.txt +++ b/schema/testing/appdatasim/testdata/diff_example.txt @@ -60,10 +60,10 @@ Module all_kinds Object key=š℠¼々¢~;-Ⱥ!˃a[ʰᾌ?{ᪧ৵%ᾯ¦〈: NOT FOUND Object Collection test_time OBJECT COUNT ERROR: expected 4, got 3 - Object key=1970-01-01 01:00:00.000000005 +0100 CET: NOT FOUND - Object key=1970-01-01 01:00:00.001598687 +0100 CET - valNotNull: expected 1970-01-01 01:00:00.007727197 +0100 CET, got 1970-01-01 01:00:00.034531678 +0100 CET - valNullable: expected 1970-01-01 01:00:00.000000484 +0100 CET, got 1970-01-01 01:00:00.000000033 +0100 CET + Object key=1969-12-31 19:00:00.000000005 -0500 EST: NOT FOUND + Object key=1969-12-31 19:00:00.001598687 -0500 EST + valNotNull: expected 1969-12-31 19:00:00.007727197 -0500 EST, got 1969-12-31 19:00:00.034531678 -0500 EST + valNullable: expected 1969-12-31 19:00:00.000000484 -0500 EST, got 1969-12-31 19:00:00.000000033 -0500 EST Object Collection test_uint16 OBJECT COUNT ERROR: expected 4, got 3 Object key=23712: NOT FOUND @@ -75,5 +75,5 @@ Module all_kinds Object Collection test_uint8 OBJECT COUNT ERROR: expected 3, got 2 Object key=1: NOT FOUND - Module test_cases: NOT FOUND + Module test_cases: actual module NOT FOUND BlockNum: expected 2, got 1 diff --git a/schema/testing/statesim/app.go b/schema/testing/statesim/app.go index 127af5431b9d..c3c7399acc2b 100644 --- a/schema/testing/statesim/app.go +++ b/schema/testing/statesim/app.go @@ -9,6 +9,7 @@ import ( "cosmossdk.io/schema" "cosmossdk.io/schema/appdata" + "cosmossdk.io/schema/view" ) // App is a collection of simulated module states corresponding to an app's schema for testing purposes. @@ -27,7 +28,7 @@ func NewApp(appSchema map[string]schema.ModuleSchema, options Options) *App { } for moduleName, moduleSchema := range appSchema { - moduleState := NewModule(moduleSchema, options) + moduleState := NewModule(moduleName, moduleSchema, options) app.moduleStates.Set(moduleName, moduleState) } @@ -62,7 +63,7 @@ func (a *App) InitializeModule(data appdata.ModuleInitializationData) error { return fmt.Errorf("module %s already initialized", data.ModuleName) } - a.moduleStates.Set(data.ModuleName, NewModule(data.Schema, a.options)) + a.moduleStates.Set(data.ModuleName, NewModule(data.ModuleName, data.Schema, a.options)) return nil } @@ -91,18 +92,22 @@ func (a *App) UpdateGen() *rapid.Generator[appdata.ObjectUpdateData] { } // GetModule returns the module state for the given module name. -func (a *App) GetModule(moduleName string) (ModuleState, bool) { - return a.moduleStates.Get(moduleName) +func (a *App) GetModule(moduleName string) (view.ModuleState, error) { + mod, ok := a.moduleStates.Get(moduleName) + if !ok { + return nil, nil + } + return mod, nil } // Modules iterates over all the module state instances in the app. -func (a *App) Modules(f func(moduleName string, modState ModuleState) bool) { +func (a *App) Modules(f func(modState view.ModuleState, err error) bool) { a.moduleStates.Scan(func(key string, value *Module) bool { - return f(key, value) + return f(value, nil) }) } // NumModules returns the number of modules in the app. -func (a *App) NumModules() int { - return a.moduleStates.Len() +func (a *App) NumModules() (int, error) { + return a.moduleStates.Len(), nil } diff --git a/schema/testing/statesim/app_diff.go b/schema/testing/statesim/app_diff.go index 6dfa222d066a..93735093a7db 100644 --- a/schema/testing/statesim/app_diff.go +++ b/schema/testing/statesim/app_diff.go @@ -1,32 +1,46 @@ package statesim -import "fmt" +import ( + "fmt" -// AppState defines an interface for things that represent application state in schema format. -type AppState interface { - // GetModule returns the module state for the given module name. - GetModule(moduleName string) (ModuleState, bool) - - // Modules iterates over all the module state instances in the app. - Modules(f func(moduleName string, modState ModuleState) bool) - - // NumModules returns the number of modules in the app. - NumModules() int -} + "cosmossdk.io/schema/view" +) // DiffAppStates compares the app state of two objects that implement AppState and returns a string with a diff if they // are different or the empty string if they are the same. -func DiffAppStates(expected, actual AppState) string { +func DiffAppStates(expected, actual view.AppState) string { res := "" - if expected.NumModules() != actual.NumModules() { - res += fmt.Sprintf("MODULE COUNT ERROR: expected %d, got %d\n", expected.NumModules(), actual.NumModules()) + expectNumModules, err := expected.NumModules() + if err != nil { + res += fmt.Sprintf("ERROR getting expected num modules: %s\n", err) + return res + } + + actualNumModules, err := actual.NumModules() + if err != nil { + res += fmt.Sprintf("ERROR getting actual num modules: %s\n", err) + return res + } + + if expectNumModules != actualNumModules { + res += fmt.Sprintf("MODULE COUNT ERROR: expected %d, got %d\n", expectNumModules, actualNumModules) } - expected.Modules(func(moduleName string, expectedMod ModuleState) bool { - actualMod, found := actual.GetModule(moduleName) - if !found { - res += fmt.Sprintf("Module %s: NOT FOUND\n", moduleName) + expected.Modules(func(expectedMod view.ModuleState, err error) bool { + if err != nil { + res += fmt.Sprintf("ERROR getting expected module: %s\n", err) + return true + } + + moduleName := expectedMod.ModuleName() + actualMod, err := actual.GetModule(moduleName) + if err != nil { + res += fmt.Sprintf("ERROR getting actual module: %s\n", err) + return true + } + if actualMod == nil { + res += fmt.Sprintf("Module %s: actual module NOT FOUND\n", moduleName) return true } diff --git a/schema/testing/statesim/module.go b/schema/testing/statesim/module.go index 6a33f19d5817..fd3f70b0fc61 100644 --- a/schema/testing/statesim/module.go +++ b/schema/testing/statesim/module.go @@ -8,17 +8,19 @@ import ( "pgregory.net/rapid" "cosmossdk.io/schema" + "cosmossdk.io/schema/view" ) // Module is a collection of object collections corresponding to a module's schema for testing purposes. type Module struct { + name string moduleSchema schema.ModuleSchema objectCollections *btree.Map[string, *ObjectCollection] updateGen *rapid.Generator[schema.ObjectUpdate] } // NewModule creates a new Module for the given module schema. -func NewModule(moduleSchema schema.ModuleSchema, options Options) *Module { +func NewModule(name string, moduleSchema schema.ModuleSchema, options Options) *Module { objectCollections := &btree.Map[string, *ObjectCollection]{} var objectTypeNames []string @@ -39,6 +41,7 @@ func NewModule(moduleSchema schema.ModuleSchema, options Options) *Module { }) return &Module{ + name: name, moduleSchema: moduleSchema, updateGen: updateGen, objectCollections: objectCollections, @@ -61,24 +64,33 @@ func (o *Module) UpdateGen() *rapid.Generator[schema.ObjectUpdate] { return o.updateGen } +// ModuleName returns the name of the module. +func (o *Module) ModuleName() string { + return o.name +} + // ModuleSchema returns the module schema for the module. func (o *Module) ModuleSchema() schema.ModuleSchema { return o.moduleSchema } // GetObjectCollection returns the object collection for the given object type. -func (o *Module) GetObjectCollection(objectType string) (ObjectCollectionState, bool) { - return o.objectCollections.Get(objectType) +func (o *Module) GetObjectCollection(objectType string) (view.ObjectCollection, error) { + obj, ok := o.objectCollections.Get(objectType) + if !ok { + return nil, nil + } + return obj, nil } // ObjectCollections iterates over all object collections in the module. -func (o *Module) ObjectCollections(f func(value ObjectCollectionState) bool) { +func (o *Module) ObjectCollections(f func(value view.ObjectCollection, err error) bool) { o.objectCollections.Scan(func(key string, value *ObjectCollection) bool { - return f(value) + return f(value, nil) }) } // NumObjectCollections returns the number of object collections in the module. -func (o *Module) NumObjectCollections() int { - return o.objectCollections.Len() +func (o *Module) NumObjectCollections() (int, error) { + return o.objectCollections.Len(), nil } diff --git a/schema/testing/statesim/module_diff.go b/schema/testing/statesim/module_diff.go index 0907f974dfd8..29ee19fb01a9 100644 --- a/schema/testing/statesim/module_diff.go +++ b/schema/testing/statesim/module_diff.go @@ -3,38 +3,44 @@ package statesim import ( "fmt" - "cosmossdk.io/schema" + "cosmossdk.io/schema/view" ) -// ModuleState defines an interface for things that represent module state in schema format. -type ModuleState interface { - // ModuleSchema returns the schema for the module. - ModuleSchema() schema.ModuleSchema - - // GetObjectCollection returns the object collection state for the given object type. - GetObjectCollection(objectType string) (ObjectCollectionState, bool) - - // ObjectCollections iterates over all the object collection states in the module. - ObjectCollections(f func(value ObjectCollectionState) bool) - - // NumObjectCollections returns the number of object collections in the module. - NumObjectCollections() int -} - // DiffModuleStates compares the module state of two objects that implement ModuleState and returns a string with a diff if they // are different or the empty string if they are the same. -func DiffModuleStates(expected, actual ModuleState) string { +func DiffModuleStates(expected, actual view.ModuleState) string { res := "" - if expected.NumObjectCollections() != actual.NumObjectCollections() { - res += fmt.Sprintf("OBJECT COLLECTION COUNT ERROR: expected %d, got %d\n", expected.NumObjectCollections(), actual.NumObjectCollections()) + expectedNumObjectCollections, err := expected.NumObjectCollections() + if err != nil { + res += fmt.Sprintf("ERROR getting expected num object collections: %s\n", err) + return res + } + + actualNumObjectCollections, err := actual.NumObjectCollections() + if err != nil { + res += fmt.Sprintf("ERROR getting actual num object collections: %s\n", err) + return res } - expected.ObjectCollections(func(expectedColl ObjectCollectionState) bool { + if expectedNumObjectCollections != actualNumObjectCollections { + res += fmt.Sprintf("OBJECT COLLECTION COUNT ERROR: expected %d, got %d\n", expectedNumObjectCollections, actualNumObjectCollections) + } + + expected.ObjectCollections(func(expectedColl view.ObjectCollection, err error) bool { + if err != nil { + res += fmt.Sprintf("ERROR getting expected object collection: %s\n", err) + return true + } + objTypeName := expectedColl.ObjectType().Name - actualColl, found := actual.GetObjectCollection(objTypeName) - if !found { - res += fmt.Sprintf("Object Collection %s: NOT FOUND\n", objTypeName) + actualColl, err := actual.GetObjectCollection(objTypeName) + if err != nil { + res += fmt.Sprintf("ERROR getting actual object collection: %s\n", err) + return true + } + if actualColl == nil { + res += fmt.Sprintf("Object Collection %s: actuall collection NOT FOUND\n", objTypeName) return true } diff --git a/schema/testing/statesim/object_coll.go b/schema/testing/statesim/object_coll.go index 56b09fe758b5..8531811de3c3 100644 --- a/schema/testing/statesim/object_coll.go +++ b/schema/testing/statesim/object_coll.go @@ -110,16 +110,17 @@ func (o *ObjectCollection) UpdateGen() *rapid.Generator[schema.ObjectUpdate] { // AllState iterates over the state of the collection by calling the given function with each item in // state represented as an object update. -func (o *ObjectCollection) AllState(f func(schema.ObjectUpdate) bool) { +func (o *ObjectCollection) AllState(f func(schema.ObjectUpdate, error) bool) { o.objects.Scan(func(_ string, v schema.ObjectUpdate) bool { - return f(v) + return f(v, nil) }) } // GetObject returns the object with the given key from the collection represented as an ObjectUpdate // itself. Deletions that are retained are returned as ObjectUpdate's with delete set to true. -func (o *ObjectCollection) GetObject(key any) (update schema.ObjectUpdate, found bool) { - return o.objects.Get(fmtObjectKey(o.objectType, key)) +func (o *ObjectCollection) GetObject(key interface{}) (update schema.ObjectUpdate, found bool, err error) { + update, ok := o.objects.Get(fmtObjectKey(o.objectType, key)) + return update, ok, nil } // ObjectType returns the object type of the collection. @@ -128,8 +129,8 @@ func (o *ObjectCollection) ObjectType() schema.ObjectType { } // Len returns the number of objects in the collection. -func (o *ObjectCollection) Len() int { - return o.objects.Len() +func (o *ObjectCollection) Len() (int, error) { + return o.objects.Len(), nil } func fmtObjectKey(objectType schema.ObjectType, key any) string { diff --git a/schema/testing/statesim/object_coll_diff.go b/schema/testing/statesim/object_coll_diff.go index 0a9be9f222d6..cfd33b0b2a40 100644 --- a/schema/testing/statesim/object_coll_diff.go +++ b/schema/testing/statesim/object_coll_diff.go @@ -6,46 +6,49 @@ import ( "cosmossdk.io/schema" schematesting "cosmossdk.io/schema/testing" + "cosmossdk.io/schema/view" ) -// ObjectCollectionState is the interface for the state of an object collection -// represented by ObjectUpdate's for an ObjectType. ObjectUpdates must not include -// ValueUpdates in the Value field. When ValueUpdates are applied they must be -// converted to individual value or array format depending on the number of fields in -// the value. For collections which retain deletions, ObjectUpdate's with the Delete -// field set to true should be returned with the latest Value still intact. -type ObjectCollectionState interface { - // ObjectType returns the object type for the collection. - ObjectType() schema.ObjectType - - // GetObject returns the object update for the given key if it exists. - GetObject(key any) (update schema.ObjectUpdate, found bool) - - // AllState iterates over the state of the collection by calling the given function with each item in - // state represented as an object update. - AllState(f func(schema.ObjectUpdate) bool) - - // Len returns the number of objects in the collection. - Len() int -} - // DiffObjectCollections compares the object collection state of two objects that implement ObjectCollectionState and returns a string with a diff if they // are different or the empty string if they are the same. -func DiffObjectCollections(expected, actual ObjectCollectionState) string { +func DiffObjectCollections(expected, actual view.ObjectCollection) string { res := "" - if expected.Len() != actual.Len() { - res += fmt.Sprintf("OBJECT COUNT ERROR: expected %d, got %d\n", expected.Len(), actual.Len()) + + expectedNumObjects, err := expected.Len() + if err != nil { + res += fmt.Sprintf("ERROR getting expected num objects: %s\n", err) + return res + } + + actualNumObjects, err := actual.Len() + if err != nil { + res += fmt.Sprintf("ERROR getting actual num objects: %s\n", err) + return res } - expected.AllState(func(expectedUpdate schema.ObjectUpdate) bool { - actualUpdate, found := actual.GetObject(expectedUpdate.Key) + if expectedNumObjects != actualNumObjects { + res += fmt.Sprintf("OBJECT COUNT ERROR: expected %d, got %d\n", expectedNumObjects, actualNumObjects) + } + + expected.AllState(func(expectedUpdate schema.ObjectUpdate, err error) bool { + if err != nil { + res += fmt.Sprintf("ERROR getting expected object: %s\n", err) + return true + } + + keyStr := fmtObjectKey(expected.ObjectType(), expectedUpdate.Key) + actualUpdate, found, err := actual.GetObject(expectedUpdate.Key) + if err != nil { + res += fmt.Sprintf("Object %s: ERROR: %v\n", keyStr, err) + return true + } if !found { - res += fmt.Sprintf("Object %s: NOT FOUND\n", fmtObjectKey(expected.ObjectType(), expectedUpdate.Key)) + res += fmt.Sprintf("Object %s: NOT FOUND\n", keyStr) return true } if expectedUpdate.Delete != actualUpdate.Delete { - res += fmt.Sprintf("Object %s: Deleted mismatch, expected %v, got %v\n", fmtObjectKey(expected.ObjectType(), expectedUpdate.Key), expectedUpdate.Delete, actualUpdate.Delete) + res += fmt.Sprintf("Object %s: Deleted mismatch, expected %v, got %v\n", keyStr, expectedUpdate.Delete, actualUpdate.Delete) } if expectedUpdate.Delete { @@ -55,7 +58,7 @@ func DiffObjectCollections(expected, actual ObjectCollectionState) string { valueDiff := schematesting.DiffObjectValues(expected.ObjectType().ValueFields, expectedUpdate.Value, actualUpdate.Value) if valueDiff != "" { res += "Object " - res += fmtObjectKey(expected.ObjectType(), expectedUpdate.Key) + res += keyStr res += "\n" res += indentAllLines(valueDiff) } diff --git a/schema/view/app.go b/schema/view/app.go new file mode 100644 index 000000000000..80f43b0bd701 --- /dev/null +++ b/schema/view/app.go @@ -0,0 +1,15 @@ +package view + +// AppState defines an interface for things that represent application state in schema format. +type AppState interface { + // GetModule returns the module state for the given module name. If the module does not exist, nil and no error + // should be returned. + GetModule(moduleName string) (ModuleState, error) + + // Modules iterates over all the module state instances in the app. If there is an error getting a module state, + // modState may be nil and err will be non-nil. + Modules(f func(modState ModuleState, err error) bool) + + // NumModules returns the number of modules in the app. + NumModules() (int, error) +} diff --git a/schema/view/app_data.go b/schema/view/app_data.go new file mode 100644 index 000000000000..3de86e421cad --- /dev/null +++ b/schema/view/app_data.go @@ -0,0 +1,16 @@ +package view + +// AppData is an interface which indexer data targets can implement to allow their app data including +// state, blocks, transactions and events to be queried. An app's state and event store can also implement +// this interface to provide an authoritative source of data for comparing with indexed data. +type AppData interface { + // BlockNum indicates the last block that was persisted. It should be 0 if the target has no data + // stored and wants to start syncing state. + // If an indexer starts indexing after a chain's genesis (returning 0), the indexer manager + // will attempt to perform a catch-up sync of state. Historical events will not be replayed, but an accurate + // representation of the current state at the height at which indexing began can be reproduced. + BlockNum() (uint64, error) + + // AppState returns the app state. If the view doesn't persist app state, nil should be returned. + AppState() AppState +} diff --git a/schema/view/doc.go b/schema/view/doc.go new file mode 100644 index 000000000000..e58cbf49eab5 --- /dev/null +++ b/schema/view/doc.go @@ -0,0 +1,2 @@ +// Package view defines interfaces for viewing the data stored in an indexer target or an app's original data store. +package view diff --git a/schema/view/module.go b/schema/view/module.go new file mode 100644 index 000000000000..41893d45b11c --- /dev/null +++ b/schema/view/module.go @@ -0,0 +1,23 @@ +package view + +import "cosmossdk.io/schema" + +// ModuleState defines an interface for things that represent module state in schema format. +type ModuleState interface { + // ModuleName returns the name of the module. + ModuleName() string + + // ModuleSchema returns the schema for the module. + ModuleSchema() schema.ModuleSchema + + // GetObjectCollection returns the object collection for the given object type. If the object collection + // does not exist, nil and no error should be returned + GetObjectCollection(objectType string) (ObjectCollection, error) + + // ObjectCollections iterates over all the object collections in the module. If there is an error getting an object + // collection, objColl may be nil and err will be non-nil. + ObjectCollections(f func(value ObjectCollection, err error) bool) + + // NumObjectCollections returns the number of object collections in the module. + NumObjectCollections() (int, error) +} diff --git a/schema/view/object.go b/schema/view/object.go new file mode 100644 index 000000000000..ac19edd6abb5 --- /dev/null +++ b/schema/view/object.go @@ -0,0 +1,27 @@ +package view + +import "cosmossdk.io/schema" + +// ObjectCollection is the interface for viewing the state of a collection of objects in a module +// represented by ObjectUpdate's for an ObjectType. ObjectUpdates must not include +// ValueUpdates in the Value field. When ValueUpdates are applied they must be +// converted to individual value or array format depending on the number of fields in +// the value. For collections which retain deletions, ObjectUpdate's with the Delete +// field set to true should be returned with the latest Value still intact. +type ObjectCollection interface { + // ObjectType returns the object type for the collection. + ObjectType() schema.ObjectType + + // GetObject returns the object update for the given key if it exists. And error should only be returned + // if there was an error getting the object update. If the object does not exist but there was no error, + // then found should be false and the error should be nil. + GetObject(key interface{}) (update schema.ObjectUpdate, found bool, err error) + + // AllState iterates over the state of the collection by calling the given function with each item in + // state represented as an object update. If there is an error getting an object update, the error will be + // non-nil and the object update should be empty. + AllState(f func(schema.ObjectUpdate, error) bool) + + // Len returns the number of objects in the collection. + Len() (int, error) +}