Skip to content

Commit

Permalink
feat(vats): Add support for configuring chainStorage nodes as sequences
Browse files Browse the repository at this point in the history
  • Loading branch information
gibson042 committed Aug 12, 2022
1 parent 0aca52b commit 5b76010
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 7 deletions.
40 changes: 40 additions & 0 deletions golang/cosmos/x/vstorage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package keeper

import (
"bytes"
"encoding/json"
"strconv"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -11,6 +13,19 @@ import (
"github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
)

// StreamCell is an envelope representing a sequence of values written at a path in a single block.
// It is persisted to storage as a { "height": "<digits>", "values": ["...", ...] } JSON text
// that off-chain consumers rely upon.
type StreamCell struct {
Height string `json:"height"`
// XXX Should Values be []string or []interface{}?
// The latter would remove a layer of JSON encoding (e.g., `[{…}]` rather than `["{…}"]`,
// but would add a requirement exclusive to AppendStorageValueAndNotify that its input be JSON.
// On the other hand, we could always extend this format in the future to include an indication
// that values are subject to a different encoding, e.g. `"valueEncoding":"base64"`.
Values []string `json:"values"`
}

// Keeper maintains the link to data storage and exposes getter/setter methods
// for the various parts of the state machine
type Keeper struct {
Expand Down Expand Up @@ -127,6 +142,31 @@ func (k Keeper) SetStorageAndNotify(ctx sdk.Context, path, value string) {
)
}

func (k Keeper) AppendStorageValueAndNotify(ctx sdk.Context, path, value string) error {
height := strconv.FormatInt(ctx.BlockHeight(), 10)

// Preserve correctly-formatted data within the current block,
// otherwise initialize a blank cell.
currentData := k.GetData(ctx, path)
var cell StreamCell
_ = json.Unmarshal([]byte(currentData), &cell)
if cell.Height != height {
cell.Height = height
cell.Values = make([]string, 0, 1)
}

// Append the new value.
cell.Values = append(cell.Values, value)

// Perform the write.
bz, err := json.Marshal(cell)
if err != nil {
return err
}
k.SetStorageAndNotify(ctx, path, string(bz))
return nil
}

func componentsToPath(components []string) string {
return strings.Join(components, types.PathSeparator)
}
Expand Down
8 changes: 8 additions & 0 deletions golang/cosmos/x/vstorage/vstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ func (sh vstorageHandler) Receive(cctx *vm.ControllerContext, str string) (ret s
keeper.SetStorageAndNotify(cctx.Context, msg.Path, msg.Value)
return "true", nil

case "append":
err = keeper.AppendStorageValueAndNotify(cctx.Context, msg.Path, msg.Value)
if err != nil {
return "", err
}
return "true", nil

case "get":
// Note that "get" does not (currently) unwrap a StreamCell.
value := keeper.GetData(cctx.Context, msg.Path)
if value == "" {
return "null", nil
Expand Down
19 changes: 14 additions & 5 deletions packages/vats/src/lib-chainStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ harden(sanitizePathSegment);
* @param {(message: StorageMessage) => any} handleStorageMessage a function for sending a storageMessage object to the storage implementation (cf. golang/cosmos/x/vstorage/vstorage.go)
* @param {string} storeName currently limited to "swingset"
* @param {string} rootPath
* @param {object} [rootOptions]
* @param {boolean} [rootOptions.sequence] employ a wrapping structure that preserves each value set within a single block, and default child nodes to do the same
*/
export function makeChainStorageRoot(
handleStorageMessage,
storeName,
rootPath,
rootOptions = {},
) {
assert.equal(
storeName,
Expand All @@ -51,22 +54,28 @@ export function makeChainStorageRoot(
);
assert.typeof(rootPath, 'string');

function makeChainStorageNode(path) {
function makeChainStorageNode(path, options = {}) {
const { sequence = false } = options;
const node = {
getStoreKey() {
return handleStorageMessage({ key: path, method: 'getStoreKey' });
},
getChildNode(name) {
getChildNode(name, childNodeOptions = {}) {
assert.typeof(name, 'string');
assert(
pathSegmentPattern.test(name),
X`Path segment names must consist of 1 to 100 characters limited to ASCII alphanumerics, underscores, and/or dashes: ${name}`,
);
return makeChainStorageNode(`${path}.${name}`);
const mergedOptions = { sequence, ...childNodeOptions };
return makeChainStorageNode(`${path}.${name}`, mergedOptions);
},
setValue(value) {
assert.typeof(value, 'string');
handleStorageMessage({ key: path, method: 'set', value });
handleStorageMessage({
key: path,
method: sequence ? 'append' : 'set',
value,
});
},
clearValue() {
handleStorageMessage({ key: path, method: 'set' });
Expand All @@ -82,7 +91,7 @@ export function makeChainStorageRoot(
return Far('chainStorageNode', node);
}

const rootNode = makeChainStorageNode(rootPath);
const rootNode = makeChainStorageNode(rootPath, rootOptions);
return rootNode;
}
/** @typedef {ReturnType<typeof makeChainStorageRoot>} ChainStorageNode */
Expand Down
15 changes: 13 additions & 2 deletions packages/vats/src/vat-chainStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@ export function buildRootObject(_vatPowers) {
* @param {ERef<BridgeManager>} bridgeManager
* @param {string} bridgeId
* @param {string} rootPath must be unique (caller responsibility to ensure)
* @param {object} [options]
*/
function makeBridgedChainStorageRoot(bridgeManager, bridgeId, rootPath) {
function makeBridgedChainStorageRoot(
bridgeManager,
bridgeId,
rootPath,
options,
) {
// Note that the uniqueness of rootPath is not validated here,
// and is instead the responsibility of callers.
const toStorage = message => E(bridgeManager).toBridge(bridgeId, message);
const rootNode = makeChainStorageRoot(toStorage, 'swingset', rootPath);
const rootNode = makeChainStorageRoot(
toStorage,
'swingset',
rootPath,
options,
);
return rootNode;
}

Expand Down

0 comments on commit 5b76010

Please sign in to comment.