diff --git a/docs/architecture/state-sync.md b/docs/architecture/state-sync.md
index 3f7b263ea5b..06b860bea80 100644
--- a/docs/architecture/state-sync.md
+++ b/docs/architecture/state-sync.md
@@ -118,7 +118,7 @@ sequenceDiagram
D-CS-->>-SSEH-CS:
SSEH-CS->>+SSES-CS: OnExportRetrieved()
loop
- SSES-CS->>+SSEH-CS: provider.ReadArtifact()
+ SSES-CS->>+SSEH-CS: provider.ReadNextArtifact()
SSEH-CS->>+D-CS: Read(artifactFile)
D-CS-->>-SSEH-CS:
SSEH-CS-->>-SSES-CS: artifact{name, data}
@@ -246,16 +246,16 @@ sequenceDiagram
SSEH-CS->>SSEH-CS: activeOperation = operationDetails{}
SSEH-CS->>+D-CS: MkDir(exportDir)
D-CS-->>-SSEH-CS:
- SSEH-CS->>+SSES-CS: provider.GetExportData()
+ SSEH-CS->>+SSES-CS: provider.GetExportDataReader()
SSES-CS->>+MS-CS: ExportStorageFromPrefix
("swingStore.")
MS-CS-->>-SSES-CS: vstorage data entries
- SSES-CS-->>-SSEH-CS:
+ SSES-CS--)-SSEH-CS: export data reader
loop each data entry
SSEH-CS->>+D-CS: Append(export-data.jsonl,
"JSON(entry tuple)\n")
D-CS-->>-SSEH-CS:
end
loop extension snapshot items
- SSEH-CS->>+SSES-CS: provider.readArtifact()
+ SSEH-CS->>+SSES-CS: provider.ReadNextArtifact()
SSES-CS->>+SM-CS: payloadReader()
SM-CS->>+SM-M: chunk = <-chunks
SM-M-->>-SM-CS:
diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go
index 44d0d7bda39..07779ef0916 100644
--- a/golang/cosmos/app/app.go
+++ b/golang/cosmos/app/app.go
@@ -102,11 +102,13 @@ import (
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
+ tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
gaiaappparams "github.com/Agoric/agoric-sdk/golang/cosmos/app/params"
appante "github.com/Agoric/agoric-sdk/golang/cosmos/ante"
+ agorictypes "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/lien"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset"
@@ -472,10 +474,19 @@ func NewAgoricApp(
return sendToController(true, string(bz))
},
)
+
+ getSwingStoreExportDataShadowCopyReader := func(height int64) agorictypes.KVEntryReader {
+ ctx := app.NewUncachedContext(false, tmproto.Header{Height: height})
+ exportDataEntries := app.SwingSetKeeper.ExportSwingStore(ctx)
+ if len(exportDataEntries) == 0 {
+ return nil
+ }
+ return agorictypes.NewVstorageDataEntriesReader(exportDataEntries)
+ }
app.SwingSetSnapshotter = *swingsetkeeper.NewExtensionSnapshotter(
bApp,
&app.SwingStoreExportsHandler,
- app.SwingSetKeeper.ExportSwingStore,
+ getSwingStoreExportDataShadowCopyReader,
)
app.VibcKeeper = vibc.NewKeeper(
diff --git a/golang/cosmos/types/kv_entry.go b/golang/cosmos/types/kv_entry.go
new file mode 100644
index 00000000000..44448ad25b6
--- /dev/null
+++ b/golang/cosmos/types/kv_entry.go
@@ -0,0 +1,114 @@
+package types
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+var _ json.Marshaler = &KVEntry{}
+var _ json.Unmarshaler = &KVEntry{}
+
+// KVEntry represents a string key / string value pair, where the value may be
+// missing, which is different from an empty value.
+// The semantics of a missing value are purpose-dependent rather than specified
+// here, but frequently correspond with deletion/incompleteness/etc.
+// A KVEntry with an empty key is considered invalid.
+type KVEntry struct {
+ key string
+ value *string
+}
+
+// NewKVEntry creates a KVEntry with the provided key and value
+func NewKVEntry(key string, value string) KVEntry {
+ return KVEntry{key, &value}
+}
+
+// NewKVEntryWithNoValue creates a KVEntry with the provided key and no value
+func NewKVEntryWithNoValue(key string) KVEntry {
+ return KVEntry{key, nil}
+}
+
+// UnmarshalJSON updates a KVEntry from JSON text corresponding with a
+// [key: string, value?: string | null] shape, or returns an error indicating
+// invalid input.
+// The key must be a non-empty string, and the value (if present) must be a
+// string or null.
+//
+// Implements json.Unmarshaler
+// Note: unlike other methods, this accepts a pointer to satisfy
+// the Unmarshaler semantics.
+func (entry *KVEntry) UnmarshalJSON(input []byte) (err error) {
+ var generic []*string
+ err = json.Unmarshal(input, &generic)
+ if err != nil {
+ return err
+ }
+
+ length := len(generic)
+
+ if generic == nil {
+ return fmt.Errorf("KVEntry cannot be null")
+ }
+ if length != 1 && length != 2 {
+ return fmt.Errorf("KVEntry must be an array of length 1 or 2 (not %d)", length)
+ }
+
+ key := generic[0]
+ if key == nil || *key == "" {
+ return fmt.Errorf("KVEntry key must be a non-empty string: %v", key)
+ }
+
+ var value *string
+ if length == 2 {
+ value = generic[1]
+ }
+
+ entry.key = *key
+ entry.value = value
+
+ return nil
+}
+
+// MarshalJSON encodes the KVEntry into a JSON array of [key: string, value?: string],
+// with the value missing (array length of 1) if the entry has no value.
+//
+// Implements json.Marshaler
+func (entry KVEntry) MarshalJSON() ([]byte, error) {
+ if !entry.IsValidKey() {
+ return nil, fmt.Errorf("cannot marshal invalid KVEntry")
+ }
+ if entry.value != nil {
+ return json.Marshal([2]string{entry.key, *entry.value})
+ } else {
+ return json.Marshal([1]string{entry.key})
+ }
+}
+
+// IsValidKey returns whether the KVEntry has a non-empty key.
+func (entry KVEntry) IsValidKey() bool {
+ return entry.key != ""
+}
+
+// Key returns the string key.
+func (entry KVEntry) Key() string {
+ return entry.key
+}
+
+// HasValue returns whether the KVEntry has a value or not.
+func (entry KVEntry) HasValue() bool {
+ return entry.value != nil
+}
+
+// Value returns a pointer to the string value or nil if the entry has no value.
+func (entry KVEntry) Value() *string {
+ return entry.value
+}
+
+// StringValue returns the string value, or the empty string if the entry has no value.
+// Note that the result therefore does not differentiate an empty string value from no value.
+func (entry KVEntry) StringValue() string {
+ if entry.value != nil {
+ return *entry.value
+ }
+ return ""
+}
diff --git a/golang/cosmos/types/kv_entry_helpers.go b/golang/cosmos/types/kv_entry_helpers.go
new file mode 100644
index 00000000000..d6bd20b8e7a
--- /dev/null
+++ b/golang/cosmos/types/kv_entry_helpers.go
@@ -0,0 +1,220 @@
+package types
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+
+ vstoragetypes "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// These helpers facilitate handling KVEntry streams, in particular for the
+// swing-store "export data" use case. The goal is to avoid passing around
+// large slices of key/value pairs.
+//
+// Handling of these streams is primarily accomplished through a KVEntryReader
+// interface, with multiple implementations for different backing sources, as
+// well as a helper function to consume a reader and write the entries into a
+// byte Writer as line terminated json encoded KVEntry.
+
+// We attempt to pass sdk.Iterator around as much as possible to abstract a
+// stream of Key/Value pairs without requiring the whole slice to be held in
+// memory if possible. Cosmos SDK defines iterators as yielding Key/Value
+// pairs, both as byte slices.
+//
+// More precisely, we define here the following:
+// - A KVEntryReader interface allowing to Read the KVEntry one by one from an
+// underlying source.
+// - Multiple implementations of the KVEntryReader interface:
+// - NewKVIteratorReader constructs a reader which consumes an sdk.Iterator.
+// Keys and values are converted from byte slices to strings, and nil values
+// are preserved as KVEntry instances with no value.
+// - A generic reader which uses a slice of key/value data, and a conversion
+// function from that data type to a KVEntry. The reader does bounds
+// checking and keeps track of the current position. The following data
+// types are available:
+// - NewVstorageDataEntriesReader constructs a reader from a slice of
+// vstorage DataEntry values.
+// - NewJsonRawMessageKVEntriesReader constructs a reader from a slice of
+// [key: string, value?: string | null] JSON array values.
+// - NewJsonlKVEntryDecoderReader constructs a reader from an io.ReadCloser
+// (like a file) containing JSON Lines in which each item is a
+// [key: string, value?: string | null] array.
+// - EncodeKVEntryReaderToJsonl consumes a KVEntryReader and writes its entries
+// into an io.Writer as a sequence of single-line JSON texts. The encoding of
+// each line is [key, value] if the KVEntry has a value, and [key] otherwise.
+// This format terminates each line, but is still compatible with JSON Lines
+// (which is line feed *separated*) for Go and JS decoders.
+
+// KVEntryReader is an abstraction for iteratively reading KVEntry data.
+type KVEntryReader interface {
+ // Read returns the next KVEntry, or an error.
+ // An `io.EOF` error indicates that the previous Read() returned the final KVEntry.
+ Read() (KVEntry, error)
+ // Close frees the underlying resource (such as a slice or file descriptor).
+ Close() error
+}
+
+var _ KVEntryReader = &kvIteratorReader{}
+
+// kvIteratorReader is a KVEntryReader backed by an sdk.Iterator
+type kvIteratorReader struct {
+ iter sdk.Iterator
+}
+
+// NewKVIteratorReader returns a KVEntryReader backed by an sdk.Iterator.
+func NewKVIteratorReader(iter sdk.Iterator) KVEntryReader {
+ return &kvIteratorReader{
+ iter: iter,
+ }
+}
+
+// Read yields the next KVEntry from the source iterator
+// Implements KVEntryReader
+func (ir kvIteratorReader) Read() (next KVEntry, err error) {
+ if !ir.iter.Valid() {
+ // There is unfortunately no way to differentiate completion from iteration
+ // errors with the implementation of Iterators by cosmos-sdk since the
+ // iter.Error() returns an error in both cases
+ return KVEntry{}, io.EOF
+ }
+
+ key := ir.iter.Key()
+ if len(key) == 0 {
+ return KVEntry{}, fmt.Errorf("nil or empty key yielded by iterator")
+ }
+
+ value := ir.iter.Value()
+ ir.iter.Next()
+ if value == nil {
+ return NewKVEntryWithNoValue(string(key)), nil
+ } else {
+ return NewKVEntry(string(key), string(value)), nil
+ }
+}
+
+func (ir kvIteratorReader) Close() error {
+ return ir.iter.Close()
+}
+
+var _ KVEntryReader = &kvEntriesReader[any]{}
+
+// kvEntriesReader is the KVEntryReader using an underlying slice of generic
+// kv entries. It reads from the slice sequentially using a type specific
+// toKVEntry func, performing bounds checks, and tracking the position.
+type kvEntriesReader[T any] struct {
+ entries []T
+ toKVEntry func(T) (KVEntry, error)
+ nextIndex int
+}
+
+// Read yields the next KVEntry from the source
+// Implements KVEntryReader
+func (reader *kvEntriesReader[T]) Read() (next KVEntry, err error) {
+ if reader.entries == nil {
+ return KVEntry{}, fmt.Errorf("reader closed")
+ }
+
+ length := len(reader.entries)
+
+ if reader.nextIndex < length {
+ entry, err := reader.toKVEntry(reader.entries[reader.nextIndex])
+ reader.nextIndex += 1
+ if err != nil {
+ return KVEntry{}, err
+ }
+ if !entry.IsValidKey() {
+ return KVEntry{}, fmt.Errorf("source yielded a KVEntry with an invalid key")
+ }
+ return entry, err
+ } else if reader.nextIndex == length {
+ reader.nextIndex += 1
+ return KVEntry{}, io.EOF
+ } else {
+ return KVEntry{}, fmt.Errorf("index %d is out of source bounds (length %d)", reader.nextIndex, length)
+ }
+}
+
+// Close releases the source slice
+// Implements KVEntryReader
+func (reader *kvEntriesReader[any]) Close() error {
+ reader.entries = nil
+ return nil
+}
+
+// NewVstorageDataEntriesReader creates a KVEntryReader backed by a
+// vstorage DataEntry slice
+func NewVstorageDataEntriesReader(vstorageDataEntries []*vstoragetypes.DataEntry) KVEntryReader {
+ return &kvEntriesReader[*vstoragetypes.DataEntry]{
+ entries: vstorageDataEntries,
+ toKVEntry: func(sourceEntry *vstoragetypes.DataEntry) (KVEntry, error) {
+ return NewKVEntry(sourceEntry.Path, sourceEntry.Value), nil
+ },
+ }
+}
+
+// NewJsonRawMessageKVEntriesReader creates a KVEntryReader backed by
+// a json.RawMessage slice
+func NewJsonRawMessageKVEntriesReader(jsonEntries []json.RawMessage) KVEntryReader {
+ return &kvEntriesReader[json.RawMessage]{
+ entries: jsonEntries,
+ toKVEntry: func(sourceEntry json.RawMessage) (entry KVEntry, err error) {
+ err = json.Unmarshal(sourceEntry, &entry)
+ return entry, err
+ },
+ }
+}
+
+var _ KVEntryReader = &jsonlKVEntryDecoderReader{}
+
+// jsonlKVEntryDecoderReader is the KVEntryReader decoding
+// jsonl-like encoded key/value pairs.
+type jsonlKVEntryDecoderReader struct {
+ closer io.Closer
+ decoder *json.Decoder
+}
+
+// Read yields the next decoded KVEntry
+// Implements KVEntryReader
+func (reader jsonlKVEntryDecoderReader) Read() (next KVEntry, err error) {
+ err = reader.decoder.Decode(&next)
+ return next, err
+}
+
+// Close release the underlying resource backing the decoder
+// Implements KVEntryReader
+func (reader jsonlKVEntryDecoderReader) Close() error {
+ return reader.closer.Close()
+}
+
+// NewJsonlKVEntryDecoderReader creates a KVEntryReader over a byte
+// stream reader that decodes each line as a json encoded KVEntry. The entries
+// are yielded in order they're present in the stream.
+func NewJsonlKVEntryDecoderReader(byteReader io.ReadCloser) KVEntryReader {
+ return &jsonlKVEntryDecoderReader{
+ closer: byteReader,
+ decoder: json.NewDecoder(byteReader),
+ }
+}
+
+// EncodeKVEntryReaderToJsonl consumes a KVEntryReader and JSON encodes each
+// KVEntry, terminating by new lines.
+// It will not Close the Reader when done
+func EncodeKVEntryReaderToJsonl(reader KVEntryReader, bytesWriter io.Writer) (err error) {
+ encoder := json.NewEncoder(bytesWriter)
+ encoder.SetEscapeHTML(false)
+ for {
+ entry, err := reader.Read()
+ if err == io.EOF {
+ return nil
+ } else if err != nil {
+ return err
+ }
+
+ err = encoder.Encode(entry)
+ if err != nil {
+ return err
+ }
+ }
+}
diff --git a/golang/cosmos/types/kv_entry_helpers_test.go b/golang/cosmos/types/kv_entry_helpers_test.go
new file mode 100644
index 00000000000..3037b5f024d
--- /dev/null
+++ b/golang/cosmos/types/kv_entry_helpers_test.go
@@ -0,0 +1,237 @@
+package types
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "strings"
+ "testing"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+func toKVEntryIdentity(entry KVEntry) (KVEntry, error) {
+ return entry, nil
+}
+
+func toKVEntryError(err error) (KVEntry, error) {
+ return KVEntry{}, err
+}
+
+func checkSameKVEntry(t *testing.T, got KVEntry, expected KVEntry) {
+ if got.key != expected.key {
+ t.Errorf("got key %s, expected key %s", got.key, expected.key)
+ }
+ if got.value == nil && expected.value != nil {
+ t.Errorf("got nil value, expected string %s", *expected.value)
+ } else if got.value != nil && expected.value == nil {
+ t.Errorf("got string value %s, expected nil", *got.value)
+ } else if got.value != nil && expected.value != nil {
+ if *got.value != *expected.value {
+ t.Errorf("got string value %s, expected %s", *got.value, *expected.value)
+ }
+ }
+}
+
+func TestKVEntriesReaderNormal(t *testing.T) {
+ source := []KVEntry{NewKVEntry("foo", "bar"), NewKVEntryWithNoValue("baz")}
+ reader := kvEntriesReader[KVEntry]{entries: source, toKVEntry: toKVEntryIdentity}
+
+ got1, err := reader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got1, source[0])
+
+ got2, err := reader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got2, source[1])
+
+ _, err = reader.Read()
+ if err != io.EOF {
+ t.Errorf("expected error io.EOF, got %v", err)
+ }
+
+ _, err = reader.Read()
+ if err == nil || !strings.Contains(err.Error(), "bounds") {
+ t.Errorf("expected out of bounds error, got %v", err)
+ }
+
+ err = reader.Close()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+
+ _, err = reader.Read()
+ if err == nil || !strings.Contains(err.Error(), "reader closed") {
+ t.Errorf("expected reader closed error, got %v", err)
+ }
+}
+
+func TestKVEntriesReaderErrors(t *testing.T) {
+ source := []error{errors.New("foo"), errors.New("bar")}
+ reader := kvEntriesReader[error]{entries: source, toKVEntry: toKVEntryError}
+
+ _, err := reader.Read()
+ if err != source[0] {
+ t.Errorf("got error %v, expected error %v", err, source[0])
+ }
+
+ // Nothing in the reader prevents reading after previous errors
+ _, err = reader.Read()
+ if err != source[1] {
+ t.Errorf("got error %v, expected error %v", err, source[1])
+ }
+
+ _, err = reader.Read()
+ if err != io.EOF {
+ t.Errorf("expected error io.EOF, got %v", err)
+ }
+}
+
+type kvEntryReaderIterator struct {
+ reader KVEntryReader
+ current KVEntry
+ err error
+}
+
+// newKVEntryReaderIterator creates an iterator over a KVEntryReader.
+// KVEntry keys and values are reported as []byte from the reader in order.
+func newKVEntryReaderIterator(reader KVEntryReader) sdk.Iterator {
+ iter := &kvEntryReaderIterator{
+ reader: reader,
+ }
+ iter.Next()
+ return iter
+}
+
+// Domain implements sdk.Iterator
+func (iter *kvEntryReaderIterator) Domain() (start []byte, end []byte) {
+ return nil, nil
+}
+
+// Valid returns whether the current iterator is valid. Once invalid, the
+// Iterator remains invalid forever.
+func (iter *kvEntryReaderIterator) Valid() bool {
+ if iter.err == io.EOF {
+ return false
+ } else if iter.err != nil {
+ panic(iter.err)
+ }
+ return true
+}
+
+// checkValid implements the validity invariants of sdk.Iterator methods.
+func (iter *kvEntryReaderIterator) checkValid() {
+ if !iter.Valid() {
+ panic("invalid iterator")
+ }
+}
+
+// Next moves the iterator to the next entry from the reader.
+// If Valid() returns false, this method will panic.
+func (iter *kvEntryReaderIterator) Next() {
+ iter.checkValid()
+
+ iter.current, iter.err = iter.reader.Read()
+}
+
+// Key returns the key at the current position. Panics if the iterator is invalid.
+// CONTRACT: key readonly []byte
+func (iter *kvEntryReaderIterator) Key() (key []byte) {
+ iter.checkValid()
+
+ return []byte(iter.current.Key())
+}
+
+// Value returns the value at the current position. Panics if the iterator is invalid.
+// CONTRACT: value readonly []byte
+func (iter *kvEntryReaderIterator) Value() (value []byte) {
+ iter.checkValid()
+
+ if !iter.current.HasValue() {
+ return nil
+ } else {
+ return []byte(iter.current.StringValue())
+ }
+}
+
+// Error returns the last error encountered by the iterator, if any.
+func (iter *kvEntryReaderIterator) Error() error {
+ err := iter.err
+ if err == io.EOF {
+ return nil
+ }
+
+ return err
+}
+
+// Close closes the iterator, releasing any allocated resources.
+func (iter *kvEntryReaderIterator) Close() error {
+ return iter.reader.Close()
+}
+
+func TestKVIteratorReader(t *testing.T) {
+ source := []KVEntry{NewKVEntry("foo", "bar"), NewKVEntryWithNoValue("baz")}
+ iterator := newKVEntryReaderIterator(&kvEntriesReader[KVEntry]{entries: source, toKVEntry: toKVEntryIdentity})
+ reader := NewKVIteratorReader(iterator)
+
+ got1, err := reader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got1, source[0])
+
+ got2, err := reader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got2, source[1])
+
+ _, err = reader.Read()
+ if err != io.EOF {
+ t.Errorf("expected error io.EOF, got %v", err)
+ }
+
+ err = reader.Close()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
+
+func TestJsonlEncodeAndReadBack(t *testing.T) {
+ source := []KVEntry{NewKVEntry("foo", "bar"), NewKVEntryWithNoValue("baz")}
+ sourceReader := &kvEntriesReader[KVEntry]{entries: source, toKVEntry: toKVEntryIdentity}
+
+ var encodedKVEntries bytes.Buffer
+ err := EncodeKVEntryReaderToJsonl(sourceReader, &encodedKVEntries)
+ if err != nil {
+ t.Errorf("unexpected encode error %v", err)
+ }
+
+ jsonlReader := NewJsonlKVEntryDecoderReader(io.NopCloser(&encodedKVEntries))
+
+ got1, err := jsonlReader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got1, source[0])
+
+ got2, err := jsonlReader.Read()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ checkSameKVEntry(t, got2, source[1])
+
+ _, err = jsonlReader.Read()
+ if err != io.EOF {
+ t.Errorf("expected error io.EOF, got %v", err)
+ }
+
+ err = jsonlReader.Close()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
diff --git a/golang/cosmos/types/kv_entry_test.go b/golang/cosmos/types/kv_entry_test.go
new file mode 100644
index 00000000000..2a5c5b1e859
--- /dev/null
+++ b/golang/cosmos/types/kv_entry_test.go
@@ -0,0 +1,143 @@
+package types
+
+import (
+ "encoding/json"
+ "errors"
+ "strings"
+ "testing"
+)
+
+func checkEntry(t *testing.T, label string, entry KVEntry, isValidKey bool, expectedKey string, hasValue bool, expectedValue string) {
+ gotValidKey := entry.IsValidKey()
+ if gotValidKey != isValidKey {
+ t.Errorf("%s: valid key is %v, expected %v", label, gotValidKey, isValidKey)
+ }
+
+ gotKey := entry.Key()
+ if gotKey != expectedKey {
+ t.Errorf("%s: got %q, want %q", label, gotKey, expectedKey)
+ }
+
+ if entry.HasValue() {
+ if !hasValue {
+ t.Errorf("%s: expected has no value", label)
+ }
+
+ gotValue := *entry.Value()
+ if gotValue != expectedValue {
+ t.Errorf("%s: got %q, want %q", label, gotValue, expectedValue)
+ }
+ } else {
+ if hasValue {
+ t.Errorf("%s: expected has value", label)
+ }
+
+ gotValuePointer := entry.Value()
+ if gotValuePointer != nil {
+ t.Errorf("%s: got %#v, want nil", label, gotValuePointer)
+ }
+ }
+
+ gotValue := entry.StringValue()
+ if gotValue != expectedValue {
+ t.Errorf("%s: got %q, want %q", label, gotValue, expectedValue)
+ }
+}
+
+func TestKVEntry(t *testing.T) {
+ type testCase struct {
+ label string
+ entry KVEntry
+ isValidKey bool
+ expectedKey string
+ hasValue bool
+ expectedValue string
+ }
+ cases := []testCase{
+ {label: "normal", entry: NewKVEntry("foo", "bar"), isValidKey: true, expectedKey: "foo", hasValue: true, expectedValue: "bar"},
+ {label: "empty string value", entry: NewKVEntry("foo", ""), isValidKey: true, expectedKey: "foo", hasValue: true, expectedValue: ""},
+ {label: "no value", entry: NewKVEntryWithNoValue("foo"), isValidKey: true, expectedKey: "foo", hasValue: false, expectedValue: ""},
+ {label: "empty key", entry: NewKVEntryWithNoValue(""), isValidKey: false, expectedKey: "", hasValue: false, expectedValue: ""},
+ }
+ for _, desc := range cases {
+ checkEntry(t, desc.label, desc.entry, desc.isValidKey, desc.expectedKey, desc.hasValue, desc.expectedValue)
+ }
+}
+
+func TestKVEntryMarshall(t *testing.T) {
+ type testCase struct {
+ label string
+ entry KVEntry
+ expectedError error
+ expectedEncoding string
+ }
+ cases := []testCase{
+ {label: "normal", entry: NewKVEntry("foo", "bar"), expectedEncoding: `["foo","bar"]`},
+ {label: "empty string value", entry: NewKVEntry("foo", ""), expectedEncoding: `["foo",""]`},
+ {label: "no value", entry: NewKVEntryWithNoValue("foo"), expectedEncoding: `["foo"]`},
+ {label: "empty key", entry: NewKVEntryWithNoValue(""), expectedError: errors.New("cannot marshal invalid KVEntry")},
+ }
+ for _, desc := range cases {
+ marshalled, err := json.Marshal(desc.entry)
+ if desc.expectedError != nil && err == nil {
+ t.Errorf("%s: got nil error, expected marshal error: %q", desc.label, desc.expectedError.Error())
+ } else if err != nil {
+ if desc.expectedError == nil {
+ t.Errorf("%s: got error %v, expected no error", desc.label, err)
+ } else if !strings.Contains(err.Error(), desc.expectedError.Error()) {
+ t.Errorf("%s: got error %q, expected error %q", desc.label, err.Error(), desc.expectedError.Error())
+ }
+ continue
+ }
+ if string(marshalled) != desc.expectedEncoding {
+ t.Errorf("%s: got %q, want %q", desc.label, string(marshalled), desc.expectedEncoding)
+ }
+ }
+}
+
+func TestKVEntryUnmarshall(t *testing.T) {
+ type testCase struct {
+ label string
+ encoded string
+ expectedError error
+ expectedKey string
+ hasValue bool
+ expectedValue string
+ }
+ cases := []testCase{
+ {label: "normal", encoded: `["foo","bar"]`, expectedKey: "foo", hasValue: true, expectedValue: "bar"},
+ {label: "empty string value", encoded: `["foo",""]`, expectedKey: "foo", hasValue: true, expectedValue: ""},
+ {label: "no value", encoded: `["foo"]`, expectedKey: "foo", hasValue: false, expectedValue: ""},
+ {label: "null value", encoded: `["foo",null]`, expectedKey: "foo", hasValue: false, expectedValue: ""},
+ {label: "null", encoded: `null`, expectedError: errors.New("KVEntry cannot be null")},
+ {label: "string", encoded: `"foo"`, expectedError: errors.New("json")},
+ {label: "empty array", encoded: `[]`, expectedError: errors.New("KVEntry must be an array of length 1 or 2 (not 0)")},
+ {label: "[null, null] array", encoded: `[null,null]`, expectedError: errors.New("KVEntry key must be a non-empty string")},
+ {label: "invalid key array", encoded: `[42]`, expectedError: errors.New("json")},
+ {label: "empty key", encoded: `["",null]`, expectedError: errors.New("KVEntry key must be a non-empty string")},
+ {label: "too many entries array", encoded: `["foo","bar",null]`, expectedError: errors.New("KVEntry must be an array of length 1 or 2 (not 3)")},
+ {label: "invalid value array", encoded: `["foo",42]`, expectedError: errors.New("json")},
+ }
+ for _, desc := range cases {
+ unmarshalled := NewKVEntry("untouched", "untouched")
+ err := json.Unmarshal([]byte(desc.encoded), &unmarshalled)
+ if desc.expectedError != nil && err == nil {
+ t.Errorf("%s: got nil error, expected unmarshal error: %q", desc.label, desc.expectedError.Error())
+ } else if err != nil {
+ if unmarshalled.Key() != "untouched" {
+ t.Errorf("%s: expected error to not modify target key, got %s", desc.label, unmarshalled.Key())
+ }
+ if unmarshalled.StringValue() != "untouched" {
+ t.Errorf("%s: expected error to not modify target value, got %v", desc.label, unmarshalled.Value())
+ }
+ if desc.expectedError == nil {
+ t.Errorf("%s: got error %v, expected no error", desc.label, err)
+ } else if !strings.Contains(err.Error(), desc.expectedError.Error()) {
+ t.Errorf("%s: got error %q, expected error %q", desc.label, err.Error(), desc.expectedError.Error())
+ }
+ continue
+ }
+
+ checkEntry(t, desc.label, unmarshalled, true, desc.expectedKey, desc.hasValue, desc.expectedValue)
+ }
+}
diff --git a/golang/cosmos/x/swingset/keeper/extension_snapshotter.go b/golang/cosmos/x/swingset/keeper/extension_snapshotter.go
index e6f5e28d666..8e4c1fc0f2e 100644
--- a/golang/cosmos/x/swingset/keeper/extension_snapshotter.go
+++ b/golang/cosmos/x/swingset/keeper/extension_snapshotter.go
@@ -2,19 +2,16 @@ package keeper
import (
"bytes"
- "encoding/json"
"errors"
"fmt"
"io"
"math"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
- vstoragetypes "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
"github.com/cosmos/cosmos-sdk/baseapp"
snapshots "github.com/cosmos/cosmos-sdk/snapshots/types"
- sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/libs/log"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
// This module implements a Cosmos ExtensionSnapshotter to capture and restore
@@ -67,30 +64,26 @@ type snapshotDetails struct {
type ExtensionSnapshotter struct {
isConfigured func() bool
// takeAppSnapshot is called by OnExportStarted when creating a snapshot
- takeAppSnapshot func(height int64)
- newRestoreContext func(height int64) sdk.Context
- swingStoreExportsHandler *SwingStoreExportsHandler
- getSwingStoreExportDataShadowCopy func(ctx sdk.Context) []*vstoragetypes.DataEntry
- logger log.Logger
- activeSnapshot *snapshotDetails
+ takeAppSnapshot func(height int64)
+ swingStoreExportsHandler *SwingStoreExportsHandler
+ getSwingStoreExportDataShadowCopyReader func(height int64) agoric.KVEntryReader
+ logger log.Logger
+ activeSnapshot *snapshotDetails
}
// NewExtensionSnapshotter creates a new swingset ExtensionSnapshotter
func NewExtensionSnapshotter(
app *baseapp.BaseApp,
swingStoreExportsHandler *SwingStoreExportsHandler,
- getSwingStoreExportDataShadowCopy func(ctx sdk.Context) []*vstoragetypes.DataEntry,
+ getSwingStoreExportDataShadowCopyReader func(height int64) agoric.KVEntryReader,
) *ExtensionSnapshotter {
return &ExtensionSnapshotter{
- isConfigured: func() bool { return app.SnapshotManager() != nil },
- takeAppSnapshot: app.Snapshot,
- newRestoreContext: func(height int64) sdk.Context {
- return app.NewUncachedContext(false, tmproto.Header{Height: height})
- },
- logger: app.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName), "submodule", "extension snapshotter"),
- swingStoreExportsHandler: swingStoreExportsHandler,
- getSwingStoreExportDataShadowCopy: getSwingStoreExportDataShadowCopy,
- activeSnapshot: nil,
+ isConfigured: func() bool { return app.SnapshotManager() != nil },
+ takeAppSnapshot: app.Snapshot,
+ logger: app.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName), "submodule", "extension snapshotter"),
+ swingStoreExportsHandler: swingStoreExportsHandler,
+ getSwingStoreExportDataShadowCopyReader: getSwingStoreExportDataShadowCopyReader,
+ activeSnapshot: nil,
}
}
@@ -235,7 +228,7 @@ func (snapshotter *ExtensionSnapshotter) OnExportRetrieved(provider SwingStoreEx
}
for {
- artifact, err := provider.ReadArtifact()
+ artifact, err := provider.ReadNextArtifact()
if err == io.EOF {
break
} else if err != nil {
@@ -248,13 +241,14 @@ func (snapshotter *ExtensionSnapshotter) OnExportRetrieved(provider SwingStoreEx
}
}
- swingStoreExportDataEntries, err := provider.GetExportData()
+ exportDataReader, err := provider.GetExportDataReader()
if err != nil {
return err
}
- if len(swingStoreExportDataEntries) == 0 {
+ if exportDataReader == nil {
return nil
}
+ defer exportDataReader.Close()
// For debugging, write out any retrieved export data as a single untrusted artifact
// which has the same encoding as the internal SwingStore export data representation:
@@ -262,14 +256,9 @@ func (snapshotter *ExtensionSnapshotter) OnExportRetrieved(provider SwingStoreEx
exportDataArtifact := types.SwingStoreArtifact{Name: UntrustedExportDataArtifactName}
var encodedExportData bytes.Buffer
- encoder := json.NewEncoder(&encodedExportData)
- encoder.SetEscapeHTML(false)
- for _, dataEntry := range swingStoreExportDataEntries {
- entry := []string{dataEntry.Path, dataEntry.Value}
- err := encoder.Encode(entry)
- if err != nil {
- return err
- }
+ err = agoric.EncodeKVEntryReaderToJsonl(exportDataReader, &encodedExportData)
+ if err != nil {
+ return err
}
exportDataArtifact.Data = encodedExportData.Bytes()
@@ -298,13 +287,12 @@ func (snapshotter *ExtensionSnapshotter) RestoreExtension(blockHeight uint64, fo
// At this point the content of the cosmos DB has been verified against the
// AppHash, which means the SwingStore data it contains can be used as the
// trusted root against which to validate the artifacts.
- getExportData := func() ([]*vstoragetypes.DataEntry, error) {
- ctx := snapshotter.newRestoreContext(height)
- exportData := snapshotter.getSwingStoreExportDataShadowCopy(ctx)
- return exportData, nil
+ getExportDataReader := func() (agoric.KVEntryReader, error) {
+ exportDataReader := snapshotter.getSwingStoreExportDataShadowCopyReader(height)
+ return exportDataReader, nil
}
- readArtifact := func() (artifact types.SwingStoreArtifact, err error) {
+ readNextArtifact := func() (artifact types.SwingStoreArtifact, err error) {
payloadBytes, err := payloadReader()
if err != nil {
return artifact, err
@@ -315,7 +303,7 @@ func (snapshotter *ExtensionSnapshotter) RestoreExtension(blockHeight uint64, fo
}
return snapshotter.swingStoreExportsHandler.RestoreExport(
- SwingStoreExportProvider{BlockHeight: blockHeight, GetExportData: getExportData, ReadArtifact: readArtifact},
+ SwingStoreExportProvider{BlockHeight: blockHeight, GetExportDataReader: getExportDataReader, ReadNextArtifact: readNextArtifact},
SwingStoreRestoreOptions{IncludeHistorical: false},
)
}
diff --git a/golang/cosmos/x/swingset/keeper/extension_snapshotter_test.go b/golang/cosmos/x/swingset/keeper/extension_snapshotter_test.go
index 85440591c4f..2f20b1662f1 100644
--- a/golang/cosmos/x/swingset/keeper/extension_snapshotter_test.go
+++ b/golang/cosmos/x/swingset/keeper/extension_snapshotter_test.go
@@ -4,7 +4,6 @@ import (
"io"
"testing"
- sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/libs/log"
)
@@ -12,7 +11,6 @@ func newTestExtensionSnapshotter() *ExtensionSnapshotter {
logger := log.NewNopLogger() // log.NewTMLogger(log.NewSyncWriter( /* os.Stdout*/ io.Discard)).With("module", "sdk/app")
return &ExtensionSnapshotter{
isConfigured: func() bool { return true },
- newRestoreContext: func(height int64) sdk.Context { return sdk.Context{} },
logger: logger,
swingStoreExportsHandler: newTestSwingStoreExportsHandler(),
}
diff --git a/golang/cosmos/x/swingset/keeper/keeper.go b/golang/cosmos/x/swingset/keeper/keeper.go
index 2640e7176e0..00f4191a8dd 100644
--- a/golang/cosmos/x/swingset/keeper/keeper.go
+++ b/golang/cosmos/x/swingset/keeper/keeper.go
@@ -16,6 +16,7 @@ import (
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/ante"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
vstoragekeeper "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/keeper"
@@ -261,7 +262,7 @@ func getBeansOwingPathForAddress(addr sdk.AccAddress) string {
func (k Keeper) GetBeansOwing(ctx sdk.Context, addr sdk.AccAddress) sdk.Uint {
path := getBeansOwingPathForAddress(addr)
entry := k.vstorageKeeper.GetEntry(ctx, path)
- if !entry.HasData() {
+ if !entry.HasValue() {
return sdk.ZeroUint()
}
return sdk.NewUintFromString(entry.StringValue())
@@ -271,7 +272,7 @@ func (k Keeper) GetBeansOwing(ctx sdk.Context, addr sdk.AccAddress) sdk.Uint {
// feeCollector but has not yet paid.
func (k Keeper) SetBeansOwing(ctx sdk.Context, addr sdk.AccAddress, beans sdk.Uint) {
path := getBeansOwingPathForAddress(addr)
- k.vstorageKeeper.SetStorage(ctx, vstoragetypes.NewStorageEntry(path, beans.String()))
+ k.vstorageKeeper.SetStorage(ctx, agoric.NewKVEntry(path, beans.String()))
}
// ChargeBeans charges the given address the given number of beans. It divides
@@ -375,7 +376,7 @@ func (k Keeper) ChargeForProvisioning(ctx sdk.Context, submitter, addr sdk.AccAd
func (k Keeper) GetEgress(ctx sdk.Context, addr sdk.AccAddress) types.Egress {
path := StoragePathEgress + "." + addr.String()
entry := k.vstorageKeeper.GetEntry(ctx, path)
- if !entry.HasData() {
+ if !entry.HasValue() {
return types.Egress{}
}
@@ -398,7 +399,7 @@ func (k Keeper) SetEgress(ctx sdk.Context, egress *types.Egress) error {
}
// FIXME: We should use just SetStorageAndNotify here, but solo needs legacy for now.
- k.vstorageKeeper.LegacySetStorageAndNotify(ctx, vstoragetypes.NewStorageEntry(path, string(bz)))
+ k.vstorageKeeper.LegacySetStorageAndNotify(ctx, agoric.NewKVEntry(path, string(bz)))
// Now make sure the corresponding account has been initialised.
if acc := k.accountKeeper.GetAccount(ctx, egress.Peer); acc != nil {
@@ -431,7 +432,7 @@ func (k Keeper) GetMailbox(ctx sdk.Context, peer string) string {
func (k Keeper) SetMailbox(ctx sdk.Context, peer string, mailbox string) {
path := StoragePathMailbox + "." + peer
// FIXME: We should use just SetStorageAndNotify here, but solo needs legacy for now.
- k.vstorageKeeper.LegacySetStorageAndNotify(ctx, vstoragetypes.NewStorageEntry(path, mailbox))
+ k.vstorageKeeper.LegacySetStorageAndNotify(ctx, agoric.NewKVEntry(path, mailbox))
}
func (k Keeper) ExportSwingStore(ctx sdk.Context) []*vstoragetypes.DataEntry {
diff --git a/golang/cosmos/x/swingset/keeper/querier.go b/golang/cosmos/x/swingset/keeper/querier.go
index ca678950371..3195b40885b 100644
--- a/golang/cosmos/x/swingset/keeper/querier.go
+++ b/golang/cosmos/x/swingset/keeper/querier.go
@@ -80,7 +80,7 @@ func queryMailbox(ctx sdk.Context, path []string, req abci.RequestQuery, keeper
// nolint: unparam
func legacyQueryStorage(ctx sdk.Context, path string, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) (res []byte, err error) {
entry := keeper.vstorageKeeper.GetEntry(ctx, path)
- if !entry.HasData() {
+ if !entry.HasValue() {
return []byte{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "could not get swingset %+v", path)
}
diff --git a/golang/cosmos/x/swingset/keeper/swing_store_exports_handler.go b/golang/cosmos/x/swingset/keeper/swing_store_exports_handler.go
index a01bcf35ff3..a0c34268102 100644
--- a/golang/cosmos/x/swingset/keeper/swing_store_exports_handler.go
+++ b/golang/cosmos/x/swingset/keeper/swing_store_exports_handler.go
@@ -9,9 +9,9 @@ import (
"path/filepath"
"regexp"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
- vstoragetypes "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/tendermint/tendermint/libs/log"
)
@@ -177,8 +177,8 @@ type SwingStoreExportOptions struct {
// packages/swing-store/src/swingStore.js makeSwingStoreExporter
ExportMode string `json:"exportMode,omitempty"`
// A flag indicating whether "export data" should be part of the swing-store export
- // If false, the resulting SwingStoreExportProvider's GetExportData will
- // return an empty list of "export data" entries.
+ // If false, the resulting SwingStoreExportProvider's GetExportDataReader
+ // will return nil
IncludeExportData bool `json:"includeExportData,omitempty"`
}
@@ -364,11 +364,12 @@ func checkNotActive() error {
type SwingStoreExportProvider struct {
// BlockHeight is the block height of the SwingStore export.
BlockHeight uint64
- // GetExportData is a function to return the "export data" of the SwingStore export, if any.
- GetExportData func() ([]*vstoragetypes.DataEntry, error)
- // ReadArtifact is a function to return the next unread artifact in the SwingStore export.
- // It errors with io.EOF upon reaching the end of the artifact list.
- ReadArtifact func() (types.SwingStoreArtifact, error)
+ // GetExportDataReader returns a KVEntryReader for the "export data" of the
+ // SwingStore export, or nil if the "export data" is not part of this export.
+ GetExportDataReader func() (agoric.KVEntryReader, error)
+ // ReadNextArtifact is a function to return the next unread artifact in the SwingStore export.
+ // It errors with io.EOF upon reaching the end of the list of available artifacts.
+ ReadNextArtifact func() (types.SwingStoreArtifact, error)
}
// SwingStoreExportEventHandler is used to handle events that occur while generating
@@ -615,41 +616,22 @@ func (exportsHandler SwingStoreExportsHandler) retrieveExport(onExportRetrieved
return fmt.Errorf("export manifest blockHeight (%d) doesn't match (%d)", manifest.BlockHeight, blockHeight)
}
- getExportData := func() ([]*vstoragetypes.DataEntry, error) {
- entries := []*vstoragetypes.DataEntry{}
+ getExportDataReader := func() (agoric.KVEntryReader, error) {
if manifest.Data == "" {
- return entries, nil
+ return nil, nil
}
dataFile, err := os.Open(filepath.Join(exportDir, manifest.Data))
if err != nil {
return nil, err
}
- defer dataFile.Close()
-
- decoder := json.NewDecoder(dataFile)
- for {
- var jsonEntry []string
- err = decoder.Decode(&jsonEntry)
- if err == io.EOF {
- break
- } else if err != nil {
- return nil, err
- }
-
- if len(jsonEntry) != 2 {
- return nil, fmt.Errorf("invalid export data entry (length %d)", len(jsonEntry))
- }
- entry := vstoragetypes.DataEntry{Path: jsonEntry[0], Value: jsonEntry[1]}
- entries = append(entries, &entry)
- }
-
- return entries, nil
+ exportDataReader := agoric.NewJsonlKVEntryDecoderReader(dataFile)
+ return exportDataReader, nil
}
nextArtifact := 0
- readArtifact := func() (artifact types.SwingStoreArtifact, err error) {
+ readNextArtifact := func() (artifact types.SwingStoreArtifact, err error) {
if nextArtifact == len(manifest.Artifacts) {
return artifact, io.EOF
} else if nextArtifact > len(manifest.Artifacts) {
@@ -670,7 +652,7 @@ func (exportsHandler SwingStoreExportsHandler) retrieveExport(onExportRetrieved
return artifact, err
}
- err = onExportRetrieved(SwingStoreExportProvider{BlockHeight: manifest.BlockHeight, GetExportData: getExportData, ReadArtifact: readArtifact})
+ err = onExportRetrieved(SwingStoreExportProvider{BlockHeight: manifest.BlockHeight, GetExportDataReader: getExportDataReader, ReadNextArtifact: readNextArtifact})
if err != nil {
return err
}
@@ -724,12 +706,14 @@ func (exportsHandler SwingStoreExportsHandler) RestoreExport(provider SwingStore
BlockHeight: blockHeight,
}
- exportDataEntries, err := provider.GetExportData()
+ exportDataReader, err := provider.GetExportDataReader()
if err != nil {
return err
}
- if len(exportDataEntries) > 0 {
+ if exportDataReader != nil {
+ defer exportDataReader.Close()
+
manifest.Data = exportDataFilename
exportDataFile, err := os.OpenFile(filepath.Join(exportDir, exportDataFilename), os.O_CREATE|os.O_WRONLY, exportedFilesMode)
if err != nil {
@@ -737,14 +721,9 @@ func (exportsHandler SwingStoreExportsHandler) RestoreExport(provider SwingStore
}
defer exportDataFile.Close()
- encoder := json.NewEncoder(exportDataFile)
- encoder.SetEscapeHTML(false)
- for _, dataEntry := range exportDataEntries {
- entry := []string{dataEntry.Path, dataEntry.Value}
- err := encoder.Encode(entry)
- if err != nil {
- return err
- }
+ err = agoric.EncodeKVEntryReaderToJsonl(exportDataReader, exportDataFile)
+ if err != nil {
+ return err
}
err = exportDataFile.Sync()
@@ -758,7 +737,7 @@ func (exportsHandler SwingStoreExportsHandler) RestoreExport(provider SwingStore
}
for {
- artifact, err := provider.ReadArtifact()
+ artifact, err := provider.ReadNextArtifact()
if err == io.EOF {
break
} else if err != nil {
diff --git a/golang/cosmos/x/swingset/keeper/swing_store_exports_handler_test.go b/golang/cosmos/x/swingset/keeper/swing_store_exports_handler_test.go
index 7396c501157..c13951c9414 100644
--- a/golang/cosmos/x/swingset/keeper/swing_store_exports_handler_test.go
+++ b/golang/cosmos/x/swingset/keeper/swing_store_exports_handler_test.go
@@ -31,7 +31,7 @@ func newTestSwingStoreEventHandler() testSwingStoreEventHandler {
},
onExportRetrieved: func(provider SwingStoreExportProvider) error {
for {
- _, err := provider.ReadArtifact()
+ _, err := provider.ReadNextArtifact()
if err == io.EOF {
return nil
} else if err != nil {
diff --git a/golang/cosmos/x/vstorage/keeper/grpc_query.go b/golang/cosmos/x/vstorage/keeper/grpc_query.go
index 49bc47af39c..0c0c9025492 100644
--- a/golang/cosmos/x/vstorage/keeper/grpc_query.go
+++ b/golang/cosmos/x/vstorage/keeper/grpc_query.go
@@ -200,7 +200,7 @@ func (k Querier) CapData(c context.Context, req *types.QueryCapDataRequest) (*ty
// Read data, auto-upgrading a standalone value to a single-value StreamCell.
entry := k.GetEntry(ctx, req.Path)
- if !entry.HasData() {
+ if !entry.HasValue() {
return nil, status.Error(codes.FailedPrecondition, "no data")
}
value := entry.StringValue()
diff --git a/golang/cosmos/x/vstorage/keeper/keeper.go b/golang/cosmos/x/vstorage/keeper/keeper.go
index 9a91e793b10..cc0e9d3298c 100644
--- a/golang/cosmos/x/vstorage/keeper/keeper.go
+++ b/golang/cosmos/x/vstorage/keeper/keeper.go
@@ -35,7 +35,7 @@ type ProposedChange struct {
}
type ChangeManager interface {
- Track(ctx sdk.Context, k Keeper, entry types.StorageEntry, isLegacy bool)
+ Track(ctx sdk.Context, k Keeper, entry agoric.KVEntry, isLegacy bool)
EmitEvents(ctx sdk.Context, k Keeper)
Rollback(ctx sdk.Context)
}
@@ -65,8 +65,8 @@ type Keeper struct {
storeKey sdk.StoreKey
}
-func (bcm *BatchingChangeManager) Track(ctx sdk.Context, k Keeper, entry types.StorageEntry, isLegacy bool) {
- path := entry.Path()
+func (bcm *BatchingChangeManager) Track(ctx sdk.Context, k Keeper, entry agoric.KVEntry, isLegacy bool) {
+ path := entry.Key()
// TODO: differentiate between deletion and setting empty string?
// Using empty string for deletion for backwards compatibility
value := entry.StringValue()
@@ -177,7 +177,7 @@ func (k Keeper) ImportStorage(ctx sdk.Context, entries []*types.DataEntry) {
for _, entry := range entries {
// This set does the bookkeeping for us in case the entries aren't a
// complete tree.
- k.SetStorage(ctx, types.NewStorageEntry(entry.Path, entry.Value))
+ k.SetStorage(ctx, agoric.NewKVEntry(entry.Path, entry.Value))
}
}
@@ -205,22 +205,22 @@ func (k Keeper) EmitChange(ctx sdk.Context, change *ProposedChange) {
}
// GetEntry gets generic storage. The default value is an empty string.
-func (k Keeper) GetEntry(ctx sdk.Context, path string) types.StorageEntry {
+func (k Keeper) GetEntry(ctx sdk.Context, path string) agoric.KVEntry {
//fmt.Printf("GetEntry(%s)\n", path);
store := ctx.KVStore(k.storeKey)
encodedKey := types.PathToEncodedKey(path)
rawValue := store.Get(encodedKey)
if len(rawValue) == 0 {
- return types.NewStorageEntryWithNoData(path)
+ return agoric.NewKVEntryWithNoValue(path)
}
if bytes.Equal(rawValue, types.EncodedNoDataValue) {
- return types.NewStorageEntryWithNoData(path)
+ return agoric.NewKVEntryWithNoValue(path)
}
value, hasPrefix := cutPrefix(rawValue, types.EncodedDataPrefix)
if !hasPrefix {
panic(fmt.Errorf("value at path %q starts with unexpected prefix", path))
}
- return types.NewStorageEntry(path, string(value))
+ return agoric.NewKVEntry(path, string(value))
}
func (k Keeper) getKeyIterator(ctx sdk.Context, path string) db.Iterator {
@@ -249,7 +249,7 @@ func (k Keeper) GetChildren(ctx sdk.Context, path string) *types.Children {
// (just an empty string) and exist only to provide linkage to subnodes with
// data.
func (k Keeper) HasStorage(ctx sdk.Context, path string) bool {
- return k.GetEntry(ctx, path).HasData()
+ return k.GetEntry(ctx, path).HasValue()
}
// HasEntry tells if a given path has either subnodes or data.
@@ -278,12 +278,12 @@ func (k Keeper) FlushChangeEvents(ctx sdk.Context) {
k.changeManager.Rollback(ctx)
}
-func (k Keeper) SetStorageAndNotify(ctx sdk.Context, entry types.StorageEntry) {
+func (k Keeper) SetStorageAndNotify(ctx sdk.Context, entry agoric.KVEntry) {
k.changeManager.Track(ctx, k, entry, false)
k.SetStorage(ctx, entry)
}
-func (k Keeper) LegacySetStorageAndNotify(ctx sdk.Context, entry types.StorageEntry) {
+func (k Keeper) LegacySetStorageAndNotify(ctx sdk.Context, entry agoric.KVEntry) {
k.changeManager.Track(ctx, k, entry, true)
k.SetStorage(ctx, entry)
}
@@ -308,7 +308,7 @@ func (k Keeper) AppendStorageValueAndNotify(ctx sdk.Context, path, value string)
if err != nil {
return err
}
- k.SetStorageAndNotify(ctx, types.NewStorageEntry(path, string(bz)))
+ k.SetStorageAndNotify(ctx, agoric.NewKVEntry(path, string(bz)))
return nil
}
@@ -320,12 +320,12 @@ func componentsToPath(components []string) string {
//
// Maintains the invariant: path entries exist if and only if self or some
// descendant has non-empty storage
-func (k Keeper) SetStorage(ctx sdk.Context, entry types.StorageEntry) {
+func (k Keeper) SetStorage(ctx sdk.Context, entry agoric.KVEntry) {
store := ctx.KVStore(k.storeKey)
- path := entry.Path()
+ path := entry.Key()
encodedKey := types.PathToEncodedKey(path)
- if !entry.HasData() {
+ if !entry.HasValue() {
if !k.HasChildren(ctx, path) {
// We have no children, can delete.
store.Delete(encodedKey)
@@ -340,7 +340,7 @@ func (k Keeper) SetStorage(ctx sdk.Context, entry types.StorageEntry) {
// Update our other parent children.
pathComponents := strings.Split(path, types.PathSeparator)
- if !entry.HasData() {
+ if !entry.HasValue() {
// delete placeholder ancestors if they're no longer needed
for i := len(pathComponents) - 1; i >= 0; i-- {
ancestor := componentsToPath(pathComponents[0:i])
@@ -381,7 +381,7 @@ func (k Keeper) GetNoDataValue() []byte {
func (k Keeper) getIntValue(ctx sdk.Context, path string) (sdk.Int, error) {
indexEntry := k.GetEntry(ctx, path)
- if !indexEntry.HasData() {
+ if !indexEntry.HasValue() {
return sdk.NewInt(0), nil
}
@@ -420,10 +420,10 @@ func (k Keeper) PushQueueItem(ctx sdk.Context, queuePath string, value string) e
// Set the vstorage corresponding to the queue entry for the current tail.
path := queuePath + "." + tail.String()
- k.SetStorage(ctx, types.NewStorageEntry(path, value))
+ k.SetStorage(ctx, agoric.NewKVEntry(path, value))
// Update the tail to point to the next available entry.
path = queuePath + ".tail"
- k.SetStorage(ctx, types.NewStorageEntry(path, nextTail.String()))
+ k.SetStorage(ctx, agoric.NewKVEntry(path, nextTail.String()))
return nil
}
diff --git a/golang/cosmos/x/vstorage/keeper/keeper_grpc_test.go b/golang/cosmos/x/vstorage/keeper/keeper_grpc_test.go
index b5883d61c3e..26e586e9bb8 100644
--- a/golang/cosmos/x/vstorage/keeper/keeper_grpc_test.go
+++ b/golang/cosmos/x/vstorage/keeper/keeper_grpc_test.go
@@ -9,6 +9,7 @@ import (
grpcCodes "google.golang.org/grpc/codes"
grpcStatus "google.golang.org/grpc/status"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/capdata"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
@@ -271,9 +272,9 @@ func TestCapData(t *testing.T) {
for _, desc := range testCases {
desc.request.Path = "key"
if desc.data == nil {
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData(desc.request.Path))
+ keeper.SetStorage(ctx, agoric.NewKVEntryWithNoValue(desc.request.Path))
} else {
- keeper.SetStorage(ctx, types.NewStorageEntry(desc.request.Path, *desc.data))
+ keeper.SetStorage(ctx, agoric.NewKVEntry(desc.request.Path, *desc.data))
}
resp, err := querier.CapData(sdk.WrapSDKContext(ctx), &desc.request)
if desc.errCode == grpcCodes.OK {
diff --git a/golang/cosmos/x/vstorage/keeper/keeper_test.go b/golang/cosmos/x/vstorage/keeper/keeper_test.go
index 1a2ce5d58ee..120e707b64b 100644
--- a/golang/cosmos/x/vstorage/keeper/keeper_test.go
+++ b/golang/cosmos/x/vstorage/keeper/keeper_test.go
@@ -4,6 +4,7 @@ import (
"reflect"
"testing"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
"github.com/cosmos/cosmos-sdk/store"
@@ -57,19 +58,19 @@ func TestStorage(t *testing.T) {
ctx, keeper := testKit.ctx, testKit.vstorageKeeper
// Test that we can store and retrieve a value.
- keeper.SetStorage(ctx, types.NewStorageEntry("inited", "initValue"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("inited", "initValue"))
if got := keeper.GetEntry(ctx, "inited").StringValue(); got != "initValue" {
t.Errorf("got %q, want %q", got, "initValue")
}
// Test that unknown children return empty string.
- if got := keeper.GetEntry(ctx, "unknown"); got.HasData() || got.StringValue() != "" {
+ if got := keeper.GetEntry(ctx, "unknown"); got.HasValue() || got.StringValue() != "" {
t.Errorf("got %q, want no value", got.StringValue())
}
// Test that we can store and retrieve an empty string value.
- keeper.SetStorage(ctx, types.NewStorageEntry("inited", ""))
- if got := keeper.GetEntry(ctx, "inited"); !got.HasData() || got.StringValue() != "" {
+ keeper.SetStorage(ctx, agoric.NewKVEntry("inited", ""))
+ if got := keeper.GetEntry(ctx, "inited"); !got.HasValue() || got.StringValue() != "" {
t.Errorf("got %q, want empty string", got.StringValue())
}
@@ -78,18 +79,18 @@ func TestStorage(t *testing.T) {
t.Errorf("got %q children, want [inited]", got.Children)
}
- keeper.SetStorage(ctx, types.NewStorageEntry("key1", "value1"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("key1", "value1"))
if got := keeper.GetChildren(ctx, ""); !childrenEqual(got.Children, []string{"inited", "key1"}) {
t.Errorf("got %q children, want [inited,key1]", got.Children)
}
// Check alphabetical.
- keeper.SetStorage(ctx, types.NewStorageEntry("alpha2", "value2"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("alpha2", "value2"))
if got := keeper.GetChildren(ctx, ""); !childrenEqual(got.Children, []string{"alpha2", "inited", "key1"}) {
t.Errorf("got %q children, want [alpha2,inited,key1]", got.Children)
}
- keeper.SetStorage(ctx, types.NewStorageEntry("beta3", "value3"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("beta3", "value3"))
if got := keeper.GetChildren(ctx, ""); !childrenEqual(got.Children, []string{"alpha2", "beta3", "inited", "key1"}) {
t.Errorf("got %q children, want [alpha2,beta3,inited,key1]", got.Children)
}
@@ -99,7 +100,7 @@ func TestStorage(t *testing.T) {
}
// Check adding children.
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1", "value1child"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("key1.child1", "value1child"))
if got := keeper.GetEntry(ctx, "key1.child1").StringValue(); got != "value1child" {
t.Errorf("got %q, want %q", got, "value1child")
}
@@ -109,7 +110,7 @@ func TestStorage(t *testing.T) {
}
// Add a grandchild.
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1.grandchild1", "value1grandchild"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("key1.child1.grandchild1", "value1grandchild"))
if got := keeper.GetEntry(ctx, "key1.child1.grandchild1").StringValue(); got != "value1grandchild" {
t.Errorf("got %q, want %q", got, "value1grandchild")
}
@@ -119,7 +120,7 @@ func TestStorage(t *testing.T) {
}
// Delete the child's contents.
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key1.child1"))
+ keeper.SetStorage(ctx, agoric.NewKVEntryWithNoValue("key1.child1"))
if got := keeper.GetChildren(ctx, "key1"); !childrenEqual(got.Children, []string{"child1"}) {
t.Errorf("got %q children, want [child1]", got.Children)
}
@@ -129,7 +130,7 @@ func TestStorage(t *testing.T) {
}
// Delete the grandchild's contents.
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key1.child1.grandchild1"))
+ keeper.SetStorage(ctx, agoric.NewKVEntryWithNoValue("key1.child1.grandchild1"))
if got := keeper.GetChildren(ctx, "key1.child1"); !childrenEqual(got.Children, []string{}) {
t.Errorf("got %q children, want []", got.Children)
}
@@ -139,13 +140,13 @@ func TestStorage(t *testing.T) {
}
// See about deleting the parent.
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key1"))
+ keeper.SetStorage(ctx, agoric.NewKVEntryWithNoValue("key1"))
if got := keeper.GetChildren(ctx, ""); !childrenEqual(got.Children, []string{"alpha2", "beta3", "inited"}) {
t.Errorf("got %q children, want [alpha2,beta3,inited]", got.Children)
}
// Do a deep set.
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2", "value2grandchild"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("key2.child2.grandchild2", "value2grandchild"))
if got := keeper.GetChildren(ctx, ""); !childrenEqual(got.Children, []string{"alpha2", "beta3", "inited", "key2"}) {
t.Errorf("got %q children, want [alpha2,beta3,inited,key2]", got.Children)
}
@@ -157,7 +158,7 @@ func TestStorage(t *testing.T) {
}
// Do another deep set.
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2a", "value2grandchilda"))
+ keeper.SetStorage(ctx, agoric.NewKVEntry("key2.child2.grandchild2a", "value2grandchilda"))
if got := keeper.GetChildren(ctx, "key2.child2"); !childrenEqual(got.Children, []string{"grandchild2", "grandchild2a"}) {
t.Errorf("got %q children, want [grandchild2,grandchild2a]", got.Children)
}
@@ -191,12 +192,12 @@ func TestStorageNotify(t *testing.T) {
tk := makeTestKit()
ctx, keeper := tk.ctx, tk.vstorageKeeper
- keeper.SetStorageAndNotify(ctx, types.NewStorageEntry("notify.noLegacy", "noLegacyValue"))
- keeper.LegacySetStorageAndNotify(ctx, types.NewStorageEntry("notify.legacy", "legacyValue"))
- keeper.SetStorageAndNotify(ctx, types.NewStorageEntry("notify.noLegacy2", "noLegacyValue2"))
- keeper.SetStorageAndNotify(ctx, types.NewStorageEntry("notify.legacy2", "legacyValue2"))
- keeper.LegacySetStorageAndNotify(ctx, types.NewStorageEntry("notify.legacy2", "legacyValue2b"))
- keeper.SetStorageAndNotify(ctx, types.NewStorageEntry("notify.noLegacy2", "noLegacyValue2b"))
+ keeper.SetStorageAndNotify(ctx, agoric.NewKVEntry("notify.noLegacy", "noLegacyValue"))
+ keeper.LegacySetStorageAndNotify(ctx, agoric.NewKVEntry("notify.legacy", "legacyValue"))
+ keeper.SetStorageAndNotify(ctx, agoric.NewKVEntry("notify.noLegacy2", "noLegacyValue2"))
+ keeper.SetStorageAndNotify(ctx, agoric.NewKVEntry("notify.legacy2", "legacyValue2"))
+ keeper.LegacySetStorageAndNotify(ctx, agoric.NewKVEntry("notify.legacy2", "legacyValue2b"))
+ keeper.SetStorageAndNotify(ctx, agoric.NewKVEntry("notify.noLegacy2", "noLegacyValue2b"))
// Check the batched events.
expectedBeforeFlushEvents := sdk.Events{}
diff --git a/golang/cosmos/x/vstorage/keeper/querier.go b/golang/cosmos/x/vstorage/keeper/querier.go
index 698d61fac3b..44a8a8d40b4 100644
--- a/golang/cosmos/x/vstorage/keeper/querier.go
+++ b/golang/cosmos/x/vstorage/keeper/querier.go
@@ -35,7 +35,7 @@ func NewQuerier(keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier
// nolint: unparam
func queryData(ctx sdk.Context, path string, req abci.RequestQuery, keeper Keeper, legacyQuerierCdc *codec.LegacyAmino) (res []byte, err error) {
entry := keeper.GetEntry(ctx, path)
- if !entry.HasData() {
+ if !entry.HasValue() {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "could not get vstorage path")
}
diff --git a/golang/cosmos/x/vstorage/types/types.go b/golang/cosmos/x/vstorage/types/types.go
index b8cb3174c16..c65fe0948ea 100644
--- a/golang/cosmos/x/vstorage/types/types.go
+++ b/golang/cosmos/x/vstorage/types/types.go
@@ -1,10 +1,5 @@
package types
-import (
- "encoding/json"
- "fmt"
-)
-
func NewData() *Data {
return &Data{}
}
@@ -12,64 +7,3 @@ func NewData() *Data {
func NewChildren() *Children {
return &Children{}
}
-
-type StorageEntry struct {
- path string
- value *string
-}
-
-func NewStorageEntry(path string, value string) StorageEntry {
- return StorageEntry{path, &value}
-}
-
-func NewStorageEntryWithNoData(path string) StorageEntry {
- return StorageEntry{path, nil}
-}
-
-// UnmarshalStorageEntry interprets its argument as a [key: string, value?: string | null]
-// JSON array and returns a corresponding StorageEntry.
-// The key must be a string, and the value (if present) must be a string or null.
-func UnmarshalStorageEntry(msg json.RawMessage) (entry StorageEntry, err error) {
- var generic [2]interface{}
- err = json.Unmarshal(msg, &generic)
-
- if err != nil {
- return
- }
-
- path, ok := generic[0].(string)
- if !ok {
- err = fmt.Errorf("invalid storage entry path: %q", generic[0])
- return
- }
-
- switch generic[1].(type) {
- case string:
- entry = NewStorageEntry(path, generic[1].(string))
- case nil:
- entry = NewStorageEntryWithNoData(path)
- default:
- err = fmt.Errorf("invalid storage entry value: %q", generic[1])
- }
- return
-}
-
-func (se StorageEntry) HasData() bool {
- return se.value != nil
-}
-
-func (se StorageEntry) Path() string {
- return se.path
-}
-
-func (se StorageEntry) Value() *string {
- return se.value
-}
-
-func (se StorageEntry) StringValue() string {
- if se.value != nil {
- return *se.value
- } else {
- return ""
- }
-}
diff --git a/golang/cosmos/x/vstorage/vstorage.go b/golang/cosmos/x/vstorage/vstorage.go
index b2120948a30..3df0da359de 100644
--- a/golang/cosmos/x/vstorage/vstorage.go
+++ b/golang/cosmos/x/vstorage/vstorage.go
@@ -7,8 +7,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
+ agoric "github.com/Agoric/agoric-sdk/golang/cosmos/types"
"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
- "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
)
type vstorageHandler struct {
@@ -69,8 +69,8 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
switch msg.Method {
case "set":
for _, arg := range msg.Args {
- var entry types.StorageEntry
- entry, err = types.UnmarshalStorageEntry(arg)
+ var entry agoric.KVEntry
+ err = json.Unmarshal(arg, &entry)
if err != nil {
return
}
@@ -83,8 +83,8 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
// FIXME: Use just "set" and remove this case.
case "legacySet":
for _, arg := range msg.Args {
- var entry types.StorageEntry
- entry, err = types.UnmarshalStorageEntry(arg)
+ var entry agoric.KVEntry
+ err = json.Unmarshal(arg, &entry)
if err != nil {
return
}
@@ -95,8 +95,8 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
case "setWithoutNotify":
for _, arg := range msg.Args {
- var entry types.StorageEntry
- entry, err = types.UnmarshalStorageEntry(arg)
+ var entry agoric.KVEntry
+ err = json.Unmarshal(arg, &entry)
if err != nil {
return
}
@@ -106,16 +106,16 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
case "append":
for _, arg := range msg.Args {
- var entry types.StorageEntry
- entry, err = types.UnmarshalStorageEntry(arg)
+ var entry agoric.KVEntry
+ err = json.Unmarshal(arg, &entry)
if err != nil {
return
}
- if !entry.HasData() {
- err = fmt.Errorf("no value for append entry with path: %q", entry.Path())
+ if !entry.HasValue() {
+ err = fmt.Errorf("no value for append entry with path: %q", entry.Key())
return
}
- err = keeper.AppendStorageValueAndNotify(cctx.Context, entry.Path(), entry.StringValue())
+ err = keeper.AppendStorageValueAndNotify(cctx.Context, entry.Key(), entry.StringValue())
if err != nil {
return
}
@@ -131,10 +131,7 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
}
entry := keeper.GetEntry(cctx.Context, path)
- if !entry.HasData() {
- return "null", nil
- }
- bz, err := json.Marshal(entry.StringValue())
+ bz, err := json.Marshal(entry.Value())
if err != nil {
return "", err
}
@@ -194,13 +191,13 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
return
}
children := keeper.GetChildren(cctx.Context, path)
- entries := make([][]interface{}, len(children.Children))
+ entries := make([]agoric.KVEntry, len(children.Children))
for i, child := range children.Children {
entry := keeper.GetEntry(cctx.Context, fmt.Sprintf("%s.%s", path, child))
- if !entry.HasData() {
- entries[i] = []interface{}{child}
+ if !entry.HasValue() {
+ entries[i] = agoric.NewKVEntryWithNoValue(child)
} else {
- entries[i] = []interface{}{child, entry.Value()}
+ entries[i] = agoric.NewKVEntry(child, entry.StringValue())
}
}
bytes, err := json.Marshal(entries)
@@ -216,9 +213,9 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
return
}
children := keeper.GetChildren(cctx.Context, path)
- vals := make([]string, len(children.Children))
+ vals := make([]*string, len(children.Children))
for i, child := range children.Children {
- vals[i] = keeper.GetEntry(cctx.Context, fmt.Sprintf("%s.%s", path, child)).StringValue()
+ vals[i] = keeper.GetEntry(cctx.Context, fmt.Sprintf("%s.%s", path, child)).Value()
}
bytes, err := json.Marshal(vals)
if err != nil {
diff --git a/golang/cosmos/x/vstorage/vstorage_test.go b/golang/cosmos/x/vstorage/vstorage_test.go
index 02e478aea09..5817e1ade25 100644
--- a/golang/cosmos/x/vstorage/vstorage_test.go
+++ b/golang/cosmos/x/vstorage/vstorage_test.go
@@ -70,10 +70,10 @@ func TestGetAndHas(t *testing.T) {
kit := makeTestKit()
keeper, handler, ctx, cctx := kit.keeper, kit.handler, kit.ctx, kit.cctx
- keeper.SetStorage(ctx, types.NewStorageEntry("foo", "bar"))
- keeper.SetStorage(ctx, types.NewStorageEntry("empty", ""))
- keeper.SetStorage(ctx, types.NewStorageEntry("top.empty-non-terminal.leaf", ""))
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("top.empty-non-terminal"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("foo", "bar"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("empty", ""))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("top.empty-non-terminal.leaf", ""))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntryWithNoValue("top.empty-non-terminal"))
type testCase struct {
label string
@@ -153,7 +153,7 @@ func doTestSet(t *testing.T, method string, expectNotify bool) {
// TODO: Fully validate input before making changes
// args: []interface{}{[]string{"foo", "X"}, []interface{}{42, "new"}},
args: []interface{}{[]interface{}{42, "new"}},
- errContains: ptr("path"),
+ errContains: ptr("json"),
},
{label: "non-string value",
// TODO: Fully validate input before making changes
@@ -259,15 +259,15 @@ func TestEntries(t *testing.T) {
kit := makeTestKit()
keeper, handler, ctx, cctx := kit.keeper, kit.handler, kit.ctx, kit.cctx
- keeper.SetStorage(ctx, types.NewStorageEntry("key1", "value1"))
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1.grandchild1", "value1grandchild"))
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key1.child1.grandchild2"))
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key1.child1"))
- keeper.SetStorage(ctx, types.NewStorageEntry("key1.child1.empty-non-terminal.leaf", ""))
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key2"))
- keeper.SetStorage(ctx, types.NewStorageEntryWithNoData("key2.child2"))
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2", "value2grandchild"))
- keeper.SetStorage(ctx, types.NewStorageEntry("key2.child2.grandchild2a", "value2grandchilda"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("key1", "value1"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("key1.child1.grandchild1", "value1grandchild"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntryWithNoValue("key1.child1.grandchild2"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntryWithNoValue("key1.child1"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("key1.child1.empty-non-terminal.leaf", ""))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntryWithNoValue("key2"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntryWithNoValue("key2.child2"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("key2.child2.grandchild2", "value2grandchild"))
+ keeper.SetStorage(ctx, agorictypes.NewKVEntry("key2.child2.grandchild2a", "value2grandchilda"))
type testCase struct {
path string