@agoric/cosmos 0.34.2-dev-29ef60f.0 → 0.34.2-dev-f84ea2e.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app.go +28 -29
- package/git-revision.txt +1 -1
- package/package.json +2 -2
- package/proto/agoric/vstorage/query.proto +53 -1
- package/x/swingset/module.go +10 -6
- package/x/swingset/types/msgs.pb.go +16 -16
- package/x/vstorage/README.md +52 -0
- package/x/vstorage/capdata/capdata.go +298 -0
- package/x/vstorage/capdata/capdata_test.go +352 -0
- package/x/vstorage/keeper/grpc_query.go +221 -0
- package/x/vstorage/keeper/keeper_grpc_test.go +299 -0
- package/x/vstorage/types/query.pb.go +646 -36
- package/x/vstorage/types/query.pb.gw.go +119 -0
package/app/app.go
CHANGED
|
@@ -598,7 +598,7 @@ func NewAgoricApp(
|
|
|
598
598
|
transferModule,
|
|
599
599
|
icaModule,
|
|
600
600
|
vstorage.NewAppModule(app.VstorageKeeper),
|
|
601
|
-
swingset.NewAppModule(app.SwingSetKeeper, setBootstrapNeeded),
|
|
601
|
+
swingset.NewAppModule(app.SwingSetKeeper, setBootstrapNeeded, app.ensureControllerInited),
|
|
602
602
|
vibcModule,
|
|
603
603
|
vbankModule,
|
|
604
604
|
lienModule,
|
|
@@ -631,6 +631,8 @@ func NewAgoricApp(
|
|
|
631
631
|
paramstypes.ModuleName,
|
|
632
632
|
vestingtypes.ModuleName,
|
|
633
633
|
vstorage.ModuleName,
|
|
634
|
+
// This will cause the swingset controller to init if it hadn't yet, passing
|
|
635
|
+
// any upgrade plan or bootstrap flag when starting at an upgrade height
|
|
634
636
|
swingset.ModuleName,
|
|
635
637
|
vibc.ModuleName,
|
|
636
638
|
vbank.ModuleName,
|
|
@@ -792,6 +794,7 @@ func NewAgoricApp(
|
|
|
792
794
|
// upgrade11Handler performs standard upgrade actions plus custom actions for upgrade-11.
|
|
793
795
|
func upgrade11Handler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) {
|
|
794
796
|
return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) {
|
|
797
|
+
app.CheckControllerInited(false)
|
|
795
798
|
// Record the plan to send to SwingSet
|
|
796
799
|
app.upgradePlan = &plan
|
|
797
800
|
|
|
@@ -825,6 +828,7 @@ func normalizeModuleAccount(ctx sdk.Context, ak authkeeper.AccountKeeper, name s
|
|
|
825
828
|
type cosmosInitAction struct {
|
|
826
829
|
Type string `json:"type"`
|
|
827
830
|
ChainID string `json:"chainID"`
|
|
831
|
+
BlockTime int64 `json:"blockTime,omitempty"`
|
|
828
832
|
IsBootstrap bool `json:"isBootstrap"`
|
|
829
833
|
Params swingset.Params `json:"params"`
|
|
830
834
|
SupplyCoins sdk.Coins `json:"supplyCoins"`
|
|
@@ -849,13 +853,22 @@ func (app *GaiaApp) CheckControllerInited(expected bool) {
|
|
|
849
853
|
|
|
850
854
|
// initController sends the initialization message to the VM.
|
|
851
855
|
// Exits if the controller has already been initialized.
|
|
856
|
+
// The init message will contain any upgrade plan if we're starting after an
|
|
857
|
+
// upgrade, and a flag indicating whether this is a bootstrap of the controller.
|
|
852
858
|
func (app *GaiaApp) initController(ctx sdk.Context, bootstrap bool) {
|
|
853
859
|
app.CheckControllerInited(false)
|
|
854
860
|
app.controllerInited = true
|
|
861
|
+
|
|
862
|
+
var blockTime int64 = 0
|
|
863
|
+
if bootstrap || app.upgradePlan != nil {
|
|
864
|
+
blockTime = ctx.BlockTime().Unix()
|
|
865
|
+
}
|
|
866
|
+
|
|
855
867
|
// Begin initializing the controller here.
|
|
856
868
|
action := &cosmosInitAction{
|
|
857
869
|
Type: "AG_COSMOS_INIT",
|
|
858
870
|
ChainID: ctx.ChainID(),
|
|
871
|
+
BlockTime: blockTime,
|
|
859
872
|
IsBootstrap: bootstrap,
|
|
860
873
|
Params: app.SwingSetKeeper.GetParams(ctx),
|
|
861
874
|
SupplyCoins: sdk.NewCoins(app.BankKeeper.GetSupply(ctx, "uist")),
|
|
@@ -883,33 +896,24 @@ func (app *GaiaApp) initController(ctx sdk.Context, bootstrap bool) {
|
|
|
883
896
|
}
|
|
884
897
|
}
|
|
885
898
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
stdlog.Println("Running SwingSet until bootstrap is ready")
|
|
896
|
-
// Just run the SwingSet kernel to finish bootstrap and get ready to open for
|
|
897
|
-
// business.
|
|
898
|
-
action := &bootstrapBlockAction{
|
|
899
|
-
Type: "BOOTSTRAP_BLOCK",
|
|
900
|
-
BlockTime: ctx.BlockTime().Unix(),
|
|
899
|
+
// ensureControllerInited inits the controller if needed. It's used by the
|
|
900
|
+
// x/swingset module's BeginBlock to lazily start the JS controller.
|
|
901
|
+
// We cannot init early as we don't know when starting the software if this
|
|
902
|
+
// might be a simple restart, or a chain init from genesis or upgrade which
|
|
903
|
+
// require the controller to not be inited yet.
|
|
904
|
+
func (app *GaiaApp) ensureControllerInited(ctx sdk.Context) {
|
|
905
|
+
if app.controllerInited {
|
|
906
|
+
return
|
|
901
907
|
}
|
|
902
908
|
|
|
903
|
-
|
|
904
|
-
|
|
909
|
+
// While we don't expect it anymore, some upgrade may want to throw away
|
|
910
|
+
// the current JS state and bootstrap again (bulldozer). In that case the
|
|
911
|
+
// upgrade handler can just set the bootstrapNeeded flag.
|
|
912
|
+
app.initController(ctx, app.bootstrapNeeded)
|
|
905
913
|
}
|
|
906
914
|
|
|
907
915
|
// BeginBlocker application updates every begin block
|
|
908
916
|
func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
|
909
|
-
if !app.controllerInited {
|
|
910
|
-
app.initController(ctx, false)
|
|
911
|
-
}
|
|
912
|
-
|
|
913
917
|
return app.mm.BeginBlock(ctx, req)
|
|
914
918
|
}
|
|
915
919
|
|
|
@@ -933,14 +937,9 @@ func (app *GaiaApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
|
|
933
937
|
normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ProvisionPoolName)
|
|
934
938
|
normalizeModuleAccount(ctx, app.AccountKeeper, vbanktypes.ReservePoolName)
|
|
935
939
|
|
|
940
|
+
// Init early (before first BeginBlock) to run the potentially lengthy bootstrap
|
|
936
941
|
if app.bootstrapNeeded {
|
|
937
|
-
|
|
938
|
-
// fmt.Fprintf(os.Stderr, "BOOTSTRAP_BLOCK Returned from swingset: %s, %v\n", out, err)
|
|
939
|
-
if err != nil {
|
|
940
|
-
// NOTE: A failed BOOTSTRAP_BLOCK means that the SwingSet state is inconsistent.
|
|
941
|
-
// Panic here, in the hopes that a replay from scratch will fix the problem.
|
|
942
|
-
panic(err)
|
|
943
|
-
}
|
|
942
|
+
app.initController(ctx, true)
|
|
944
943
|
}
|
|
945
944
|
|
|
946
945
|
// Agoric: report the genesis time explicitly.
|
package/git-revision.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f84ea2e
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/cosmos",
|
|
3
|
-
"version": "0.34.2-dev-
|
|
3
|
+
"version": "0.34.2-dev-f84ea2e.0+f84ea2e",
|
|
4
4
|
"description": "Connect JS to the Cosmos blockchain SDK",
|
|
5
5
|
"parsers": {
|
|
6
6
|
"js": "mjs"
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "f84ea2e166856351f9d080a3edc0721aea63d1f9"
|
|
39
39
|
}
|
|
@@ -9,11 +9,18 @@ option go_package = "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types
|
|
|
9
9
|
|
|
10
10
|
// Query defines the gRPC querier service
|
|
11
11
|
service Query {
|
|
12
|
-
// Return an arbitrary vstorage datum.
|
|
12
|
+
// Return the raw string value of an arbitrary vstorage datum.
|
|
13
13
|
rpc Data(QueryDataRequest) returns (QueryDataResponse) {
|
|
14
14
|
option (google.api.http).get = "/agoric/vstorage/data/{path}";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Return a formatted representation of a vstorage datum that must be
|
|
18
|
+
// a valid StreamCell with CapData values, or standalone CapData.
|
|
19
|
+
rpc CapData(QueryCapDataRequest)
|
|
20
|
+
returns (QueryCapDataResponse) {
|
|
21
|
+
option (google.api.http).get = "/agoric/vstorage/capdata/{path}";
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
// Return the children of a given vstorage path.
|
|
18
25
|
rpc Children(QueryChildrenRequest)
|
|
19
26
|
returns (QueryChildrenResponse) {
|
|
@@ -37,6 +44,51 @@ message QueryDataResponse {
|
|
|
37
44
|
];
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
// QueryCapDataRequest contains a path and formatting configuration.
|
|
48
|
+
message QueryCapDataRequest {
|
|
49
|
+
string path = 1 [
|
|
50
|
+
(gogoproto.jsontag) = "path",
|
|
51
|
+
(gogoproto.moretags) = "yaml:\"path\""
|
|
52
|
+
];
|
|
53
|
+
// mediaType must be an actual media type in the registry at
|
|
54
|
+
// https://www.iana.org/assignments/media-types/media-types.xhtml
|
|
55
|
+
// or a special value that does not conflict with the media type syntax.
|
|
56
|
+
// The only valid value is "JSON Lines", which is also the default.
|
|
57
|
+
string media_type = 2 [
|
|
58
|
+
(gogoproto.jsontag) = "mediaType",
|
|
59
|
+
(gogoproto.moretags) = "yaml:\"mediaType\""
|
|
60
|
+
];
|
|
61
|
+
// itemFormat, if present, must be the special value "flat" to indicate that
|
|
62
|
+
// the deep structure of each item should be flattened into a single level
|
|
63
|
+
// with kebab-case keys (e.g., `{ "metrics": { "min": 0, "max": 88 } }` as
|
|
64
|
+
// `{ "metrics-min": 0, "metrics-max": 88 }`).
|
|
65
|
+
string item_format = 3 [
|
|
66
|
+
(gogoproto.jsontag) = "itemFormat",
|
|
67
|
+
(gogoproto.moretags) = "yaml:\"itemFormat\""
|
|
68
|
+
];
|
|
69
|
+
// remotableValueFormat indicates how to transform references to opaque but
|
|
70
|
+
// distinguishable Remotables into readable embedded representations.
|
|
71
|
+
// * "object" represents each Remotable as an `{ id, allegedName }` object.
|
|
72
|
+
// * "string" represents each Remotable as a bracketed string such as `[Alleged: IST brand {}]`.
|
|
73
|
+
string remotable_value_format = 10 [
|
|
74
|
+
(gogoproto.jsontag) = "remotableValueFormat",
|
|
75
|
+
(gogoproto.moretags) = "yaml:\"remotableValueFormat\""
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// QueryCapDataResponse represents the result with the requested formatting,
|
|
80
|
+
// reserving space for future metadata such as media type.
|
|
81
|
+
message QueryCapDataResponse {
|
|
82
|
+
string block_height = 1 [
|
|
83
|
+
(gogoproto.jsontag) = "blockHeight",
|
|
84
|
+
(gogoproto.moretags) = "yaml:\"blockHeight\""
|
|
85
|
+
];
|
|
86
|
+
string value = 10 [
|
|
87
|
+
(gogoproto.jsontag) = "value",
|
|
88
|
+
(gogoproto.moretags) = "yaml:\"value\""
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
40
92
|
// QueryChildrenRequest is the vstorage path children query.
|
|
41
93
|
message QueryChildrenRequest {
|
|
42
94
|
string path = 1 [
|
package/x/swingset/module.go
CHANGED
|
@@ -80,16 +80,18 @@ func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
|
|
80
80
|
|
|
81
81
|
type AppModule struct {
|
|
82
82
|
AppModuleBasic
|
|
83
|
-
keeper
|
|
84
|
-
setBootstrapNeeded
|
|
83
|
+
keeper Keeper
|
|
84
|
+
setBootstrapNeeded func()
|
|
85
|
+
ensureControllerInited func(sdk.Context)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
// NewAppModule creates a new AppModule Object
|
|
88
|
-
func NewAppModule(k Keeper, setBootstrapNeeded func()) AppModule {
|
|
89
|
+
func NewAppModule(k Keeper, setBootstrapNeeded func(), ensureControllerInited func(sdk.Context)) AppModule {
|
|
89
90
|
am := AppModule{
|
|
90
|
-
AppModuleBasic:
|
|
91
|
-
keeper:
|
|
92
|
-
setBootstrapNeeded:
|
|
91
|
+
AppModuleBasic: AppModuleBasic{},
|
|
92
|
+
keeper: k,
|
|
93
|
+
setBootstrapNeeded: setBootstrapNeeded,
|
|
94
|
+
ensureControllerInited: ensureControllerInited,
|
|
93
95
|
}
|
|
94
96
|
return am
|
|
95
97
|
}
|
|
@@ -127,6 +129,8 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {
|
|
|
127
129
|
func (AppModule) ConsensusVersion() uint64 { return 2 }
|
|
128
130
|
|
|
129
131
|
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
|
132
|
+
am.ensureControllerInited(ctx)
|
|
133
|
+
|
|
130
134
|
err := BeginBlock(ctx, req, am.keeper)
|
|
131
135
|
if err != nil {
|
|
132
136
|
fmt.Println("BeginBlock error:", err)
|
|
@@ -433,7 +433,7 @@ type MsgInstallBundle struct {
|
|
|
433
433
|
Submitter github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,2,opt,name=submitter,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"submitter" yaml:"submitter"`
|
|
434
434
|
// Either bundle or compressed_bundle will be set.
|
|
435
435
|
// Default compression algorithm is gzip.
|
|
436
|
-
CompressedBundle []byte `protobuf:"bytes,3,opt,name=compressed_bundle,json=compressedBundle,proto3" json:"compressedBundle" yaml:"
|
|
436
|
+
CompressedBundle []byte `protobuf:"bytes,3,opt,name=compressed_bundle,json=compressedBundle,proto3" json:"compressedBundle" yaml:"compressedBundle"`
|
|
437
437
|
// Size in bytes of uncompression of compressed_bundle.
|
|
438
438
|
UncompressedSize int64 `protobuf:"varint,4,opt,name=uncompressed_size,json=uncompressedSize,proto3" json:"uncompressedSize"`
|
|
439
439
|
}
|
|
@@ -553,7 +553,7 @@ func init() {
|
|
|
553
553
|
func init() { proto.RegisterFile("agoric/swingset/msgs.proto", fileDescriptor_788baa062b181a57) }
|
|
554
554
|
|
|
555
555
|
var fileDescriptor_788baa062b181a57 = []byte{
|
|
556
|
-
//
|
|
556
|
+
// 788 bytes of a gzipped FileDescriptorProto
|
|
557
557
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xcf, 0x6f, 0xe3, 0x44,
|
|
558
558
|
0x14, 0x8e, 0xe3, 0x50, 0x36, 0xaf, 0xd9, 0x6d, 0x63, 0x95, 0xad, 0xd7, 0x0b, 0x99, 0xac, 0xa5,
|
|
559
559
|
0x15, 0x01, 0xd4, 0x44, 0xb0, 0xb7, 0xed, 0x29, 0x16, 0x42, 0x5a, 0xa4, 0xa0, 0xc5, 0x2b, 0x84,
|
|
@@ -590,20 +590,20 @@ var fileDescriptor_788baa062b181a57 = []byte{
|
|
|
590
590
|
0x01, 0x8d, 0x2d, 0xcf, 0x33, 0x66, 0xc1, 0xc4, 0xc3, 0xca, 0x0b, 0xd8, 0x1a, 0xf3, 0x7f, 0xf9,
|
|
591
591
|
0xe9, 0x3c, 0x4d, 0x18, 0xca, 0x91, 0x94, 0xa1, 0x87, 0xc2, 0x9e, 0x88, 0x75, 0x33, 0x27, 0x96,
|
|
592
592
|
0x57, 0x56, 0xbf, 0x87, 0x95, 0x29, 0xdf, 0x40, 0xdb, 0x26, 0x7e, 0x98, 0xc1, 0x78, 0x72, 0x9c,
|
|
593
|
-
0x3b, 0x96, 0x79, 0xe7, 0x41, 0xc2, 0xd0, 0x6e, 0x45, 0x1a, 0x85, 0xf7,
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
593
|
+
0x3b, 0x96, 0x79, 0xe7, 0x41, 0xc2, 0xd0, 0x6e, 0x45, 0x1a, 0x85, 0xf7, 0x7d, 0x61, 0x60, 0x95,
|
|
594
|
+
0xd1, 0xcd, 0x35, 0xb1, 0x32, 0x84, 0xf6, 0x2c, 0x58, 0xa8, 0x4f, 0xdd, 0x4b, 0xcc, 0x4f, 0x4c,
|
|
595
|
+
0x36, 0xf6, 0xb2, 0xea, 0x8b, 0xe4, 0x1b, 0xf7, 0x12, 0x9b, 0x6b, 0x88, 0xae, 0x81, 0xba, 0xba,
|
|
596
|
+
0xb7, 0xc5, 0xc6, 0x7f, 0x72, 0x2d, 0x83, 0x3c, 0xa2, 0x8e, 0xf2, 0x2d, 0x3c, 0x5c, 0xde, 0xfc,
|
|
597
|
+
0x67, 0xfd, 0x95, 0xd7, 0x40, 0x7f, 0xb5, 0x86, 0xf6, 0xc1, 0xad, 0x92, 0xa2, 0x8d, 0x72, 0x02,
|
|
598
|
+
0x8f, 0x56, 0x5e, 0x14, 0xfa, 0xa6, 0xe4, 0x65, 0x8d, 0xf6, 0xe1, 0xed, 0x9a, 0xb2, 0xc3, 0x11,
|
|
599
|
+
0xb4, 0x96, 0x1e, 0xa6, 0xdd, 0x4d, 0xb9, 0x8b, 0x0a, 0xad, 0x77, 0x9b, 0xa2, 0xac, 0xed, 0x42,
|
|
600
|
+
0x7b, 0xfd, 0xc9, 0xf7, 0xfc, 0x9f, 0xd3, 0x17, 0x64, 0xda, 0xc1, 0x7f, 0x92, 0x95, 0xad, 0xbe,
|
|
601
|
+
0x84, 0x66, 0xf5, 0x80, 0x7a, 0x6f, 0x53, 0x6e, 0x49, 0x6b, 0xcf, 0xff, 0x95, 0x2e, 0x4a, 0x1a,
|
|
602
|
+
0x5f, 0xfd, 0x36, 0xef, 0x48, 0x57, 0xf3, 0x8e, 0x74, 0x3d, 0xef, 0x48, 0x3f, 0xde, 0x74, 0x6a,
|
|
603
|
+
0x57, 0x37, 0x9d, 0xda, 0xef, 0x37, 0x9d, 0xda, 0xd1, 0xe1, 0xc2, 0xcc, 0x0f, 0xc5, 0x07, 0x81,
|
|
604
|
+
0xa8, 0xc8, 0x67, 0xde, 0x21, 0x9e, 0x15, 0x38, 0xc5, 0x65, 0xf8, 0xbe, 0xfa, 0x56, 0xe0, 0x97,
|
|
605
|
+
0x61, 0xbc, 0xc5, 0x3f, 0x03, 0x5e, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x51, 0x66, 0x1b, 0xd5,
|
|
606
|
+
0x4b, 0x08, 0x00, 0x00,
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
// Reference imports to suppress errors if they are not otherwise used.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Virtual Storage
|
|
2
|
+
|
|
3
|
+
This module manages "[IAVL](https://github.com/cosmos/iavl)" chain storage data with a hierarchical keyspace in which each key is a "[path](./types/path_keys.go)" composed of zero or more dot-separated nonempty segments in a restricted alphabet. It exposes gRPC endpoints to arbitrary external clients for reading data, and internal read/write interfaces for use by SwingSet (which itself manages further subtree-scoped attenuation).
|
|
4
|
+
|
|
5
|
+
## Internal Go interface
|
|
6
|
+
|
|
7
|
+
[Keeper](./keeper/keeper.go)
|
|
8
|
+
* generic
|
|
9
|
+
* GetChildren
|
|
10
|
+
* GetEntry
|
|
11
|
+
* HasEntry
|
|
12
|
+
* HasStorage
|
|
13
|
+
* SetStorage[AndNotify]
|
|
14
|
+
* StreamCell-oriented (a StreamCell captures a block height and an array of values)
|
|
15
|
+
* AppendStorageValue[AndNotify]
|
|
16
|
+
* queue-oriented (a queue stores items at paths like "$prefix.$n", documenting
|
|
17
|
+
the n for the next item to be consumed at "$prefix.head" and the n for the next
|
|
18
|
+
next item to be pushed at "$prefix.tail" such that the queue is empty when both
|
|
19
|
+
head and tail store the same n)
|
|
20
|
+
* GetQueueLength
|
|
21
|
+
* PushQueueItem
|
|
22
|
+
|
|
23
|
+
## Internal JSON interface
|
|
24
|
+
|
|
25
|
+
This is used by the SwingSet "bridge".
|
|
26
|
+
|
|
27
|
+
[Receive](./vstorage.go) with input `{ "method": "...", "args": [...] }`
|
|
28
|
+
* generic
|
|
29
|
+
* method "entries", args path
|
|
30
|
+
* method "get"/"has", args path
|
|
31
|
+
* method "set"/"setWithoutNotify", args [[path, value?], ...]
|
|
32
|
+
* method "children", args path
|
|
33
|
+
* method "values", args path (returns values for children in the same order as method "children")
|
|
34
|
+
* method "size", args path (returns the count of children)
|
|
35
|
+
* StreamCell-oriented
|
|
36
|
+
* method "append", args [[path, value?], ...]
|
|
37
|
+
|
|
38
|
+
## External protobuf interface
|
|
39
|
+
|
|
40
|
+
gRPC via [Querier](./keeper/keeper.go)
|
|
41
|
+
and CometBFT method "abci_query" with params `{ "path": "/agoric.vstorage.Query/...", "data": "<hexadecimal representation of serialized protobuf>" }` via the same
|
|
42
|
+
* /agoric.vstorage.Query/CapData
|
|
43
|
+
* /agoric.vstorage.Query/Children
|
|
44
|
+
* /agoric.vstorage.Query/Data
|
|
45
|
+
|
|
46
|
+
## Arbitrary-response HTTP interface
|
|
47
|
+
|
|
48
|
+
This depends upon appModule `LegacyQuerierHandler` functionality that is [removed from cosmos-sdk as of v0.47](https://github.com/cosmos/cosmos-sdk/blob/fa4d87ef7e6d87aaccc94c337ffd2fe90fcb7a9d/CHANGELOG.md#api-breaking-changes-3)
|
|
49
|
+
|
|
50
|
+
[legacy querier](./keeper/querier.go)
|
|
51
|
+
* /custom/vstorage/children/$path
|
|
52
|
+
* /custom/vstorage/data/$path
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
package capdata
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"regexp"
|
|
8
|
+
"strconv"
|
|
9
|
+
"strings"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// JsonMarshal returns JSON text representing its input,
|
|
13
|
+
// without special replacement of "<", ">", "&", U+2028, or U+2029.
|
|
14
|
+
func JsonMarshal(val any) ([]byte, error) {
|
|
15
|
+
buf := &bytes.Buffer{}
|
|
16
|
+
encoder := json.NewEncoder(buf)
|
|
17
|
+
encoder.SetEscapeHTML(false)
|
|
18
|
+
if err := encoder.Encode(val); err != nil {
|
|
19
|
+
return nil, err
|
|
20
|
+
}
|
|
21
|
+
// Return without a trailing line feed.
|
|
22
|
+
lineTerminatedJson := buf.Bytes()
|
|
23
|
+
return bytes.TrimSuffix(lineTerminatedJson, []byte("\n")), nil
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// cf. https://github.com/endojs/endo/tree/master/packages/marshal
|
|
27
|
+
|
|
28
|
+
type Capdata struct {
|
|
29
|
+
Body string `json:"body"`
|
|
30
|
+
Slots []interface{} `json:"slots"`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var validBigint = regexp.MustCompile(`^-?(?:0|[1-9][0-9]*)$`)
|
|
34
|
+
|
|
35
|
+
type CapdataBigint struct {
|
|
36
|
+
Normalized string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type CapdataRemotable struct {
|
|
40
|
+
Id interface{}
|
|
41
|
+
Iface *string
|
|
42
|
+
Representation interface{}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func NewCapdataBigint(str string) *CapdataBigint {
|
|
46
|
+
if !validBigint.MatchString(str) {
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
bigint := CapdataBigint{str}
|
|
50
|
+
return &bigint
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func (r *CapdataRemotable) MarshalJSON() ([]byte, error) {
|
|
54
|
+
return JsonMarshal(r.Representation)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type CapdataValueTransformations struct {
|
|
58
|
+
Bigint func(*CapdataBigint) interface{}
|
|
59
|
+
Remotable func(*CapdataRemotable) interface{}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// upsertCapdataRemotable either adds a new CapdataRemotable to `remotables` at the specified
|
|
63
|
+
// slot index or updates the iface of the value that is already there, ensuring lack of iface name
|
|
64
|
+
// inconsistency (iteration order is not guaranteed to correspond with JSON text like it does in
|
|
65
|
+
// JavaScript, so we must accept encountering the "first" reference to a slot late
|
|
66
|
+
// and must therefore also defer transformations).
|
|
67
|
+
func upsertCapdataRemotable(remotables map[uint64]*CapdataRemotable, slotIndex uint64, id interface{}, iface *string) (*CapdataRemotable, error) {
|
|
68
|
+
r := remotables[slotIndex]
|
|
69
|
+
if r == nil {
|
|
70
|
+
r = new(CapdataRemotable)
|
|
71
|
+
r.Id = id
|
|
72
|
+
r.Iface = iface
|
|
73
|
+
remotables[slotIndex] = r
|
|
74
|
+
} else if iface != nil {
|
|
75
|
+
if r.Iface != nil && *iface != *r.Iface {
|
|
76
|
+
return nil, fmt.Errorf("slot iface mismatch: %q", *iface)
|
|
77
|
+
}
|
|
78
|
+
r.Iface = iface
|
|
79
|
+
}
|
|
80
|
+
return r, nil
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// decodeCapdataLegacyValue decodes the non-smallcaps encoding of
|
|
84
|
+
// https://github.com/endojs/endo/blob/master/packages/marshal/src/encodeToCapData.js
|
|
85
|
+
func decodeCapdataLegacyValue(
|
|
86
|
+
encoded interface{},
|
|
87
|
+
slots []interface{},
|
|
88
|
+
remotables map[uint64]*CapdataRemotable,
|
|
89
|
+
transformations CapdataValueTransformations,
|
|
90
|
+
) (interface{}, error) {
|
|
91
|
+
if arr, ok := encoded.([]interface{}); ok {
|
|
92
|
+
for i, v := range arr {
|
|
93
|
+
decoded, err := decodeCapdataLegacyValue(v, slots, remotables, transformations)
|
|
94
|
+
if err != nil {
|
|
95
|
+
return nil, err
|
|
96
|
+
}
|
|
97
|
+
arr[i] = decoded
|
|
98
|
+
}
|
|
99
|
+
return arr, nil
|
|
100
|
+
} else if obj, ok := encoded.(map[string]interface{}); ok {
|
|
101
|
+
if qclassVal, ok := obj["@qclass"]; ok {
|
|
102
|
+
qclass, ok := qclassVal.(string)
|
|
103
|
+
if !ok {
|
|
104
|
+
return nil, fmt.Errorf("invalid @qclass: %q", qclassVal)
|
|
105
|
+
}
|
|
106
|
+
switch qclass {
|
|
107
|
+
case "bigint":
|
|
108
|
+
var bigint *CapdataBigint
|
|
109
|
+
digitsVal := obj["digits"]
|
|
110
|
+
if digitsStr, ok := digitsVal.(string); ok {
|
|
111
|
+
bigint = NewCapdataBigint(digitsStr)
|
|
112
|
+
}
|
|
113
|
+
if bigint == nil {
|
|
114
|
+
return nil, fmt.Errorf("invalid bigint: %q", digitsVal)
|
|
115
|
+
}
|
|
116
|
+
if transformations.Bigint == nil {
|
|
117
|
+
return nil, fmt.Errorf("untransformed bigint")
|
|
118
|
+
}
|
|
119
|
+
return transformations.Bigint(bigint), nil
|
|
120
|
+
case "slot":
|
|
121
|
+
var iface *string
|
|
122
|
+
slotIndexVal, ifaceVal := obj["index"], obj["iface"]
|
|
123
|
+
slotIndexNum, ok := slotIndexVal.(float64)
|
|
124
|
+
slotIndex := uint64(slotIndexNum)
|
|
125
|
+
if !ok || float64(slotIndex) != slotIndexNum || slotIndex >= uint64(len(slots)) {
|
|
126
|
+
return nil, fmt.Errorf("invalid slot index: %q", slotIndexVal)
|
|
127
|
+
}
|
|
128
|
+
if ifaceStr, ok := ifaceVal.(string); ok {
|
|
129
|
+
iface = &ifaceStr
|
|
130
|
+
} else if ifaceVal != nil {
|
|
131
|
+
return nil, fmt.Errorf("invalid slot iface: %q", ifaceVal)
|
|
132
|
+
}
|
|
133
|
+
return upsertCapdataRemotable(remotables, slotIndex, slots[slotIndex], iface)
|
|
134
|
+
case "hilbert":
|
|
135
|
+
fallthrough
|
|
136
|
+
case "undefined":
|
|
137
|
+
fallthrough
|
|
138
|
+
case "NaN":
|
|
139
|
+
fallthrough
|
|
140
|
+
case "Infinity":
|
|
141
|
+
fallthrough
|
|
142
|
+
case "symbol":
|
|
143
|
+
fallthrough
|
|
144
|
+
case "tagged":
|
|
145
|
+
fallthrough
|
|
146
|
+
case "error":
|
|
147
|
+
fallthrough
|
|
148
|
+
case "-Infinity":
|
|
149
|
+
return nil, fmt.Errorf("not implemented: @qclass %q", qclass)
|
|
150
|
+
default:
|
|
151
|
+
return nil, fmt.Errorf("unrecognized @qclass: %q", qclass)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for k, v := range obj {
|
|
155
|
+
decoded, err := decodeCapdataLegacyValue(v, slots, remotables, transformations)
|
|
156
|
+
if err != nil {
|
|
157
|
+
return nil, err
|
|
158
|
+
}
|
|
159
|
+
obj[k] = decoded
|
|
160
|
+
}
|
|
161
|
+
return obj, nil
|
|
162
|
+
} else {
|
|
163
|
+
return encoded, nil
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// decodeCapdataSmallcapsValue decodes the "smallcaps" encoding from
|
|
168
|
+
// https://github.com/endojs/endo/blob/master/packages/marshal/src/encodeToSmallcaps.js
|
|
169
|
+
func decodeCapdataSmallcapsValue(
|
|
170
|
+
encoded interface{},
|
|
171
|
+
slots []interface{},
|
|
172
|
+
remotables map[uint64]*CapdataRemotable,
|
|
173
|
+
transformations CapdataValueTransformations,
|
|
174
|
+
) (interface{}, error) {
|
|
175
|
+
if arr, ok := encoded.([]interface{}); ok {
|
|
176
|
+
for i, v := range arr {
|
|
177
|
+
decoded, err := decodeCapdataSmallcapsValue(v, slots, remotables, transformations)
|
|
178
|
+
if err != nil {
|
|
179
|
+
return nil, err
|
|
180
|
+
}
|
|
181
|
+
arr[i] = decoded
|
|
182
|
+
}
|
|
183
|
+
return arr, nil
|
|
184
|
+
} else if encodedObj, ok := encoded.(map[string]interface{}); ok {
|
|
185
|
+
if _, ok := encodedObj["#tag"]; ok {
|
|
186
|
+
return nil, fmt.Errorf("not implemented: #tag")
|
|
187
|
+
}
|
|
188
|
+
if _, ok := encodedObj["#error"]; ok {
|
|
189
|
+
return nil, fmt.Errorf("not implemented: #error")
|
|
190
|
+
}
|
|
191
|
+
// We need a distinct output map to avoid reprocessing already-decoded keys.
|
|
192
|
+
decodedObj := make(map[string]interface{}, len(encodedObj))
|
|
193
|
+
for encodedK, v := range encodedObj {
|
|
194
|
+
if strings.HasPrefix(encodedK, "#") {
|
|
195
|
+
return nil, fmt.Errorf("unrecognized record type: %q", encodedK)
|
|
196
|
+
}
|
|
197
|
+
decodedK, err := decodeCapdataSmallcapsValue(encodedK, slots, remotables, CapdataValueTransformations{})
|
|
198
|
+
k, ok := decodedK.(string)
|
|
199
|
+
if err != nil || !ok {
|
|
200
|
+
return nil, fmt.Errorf("invalid copyRecord key: %q", encodedK)
|
|
201
|
+
}
|
|
202
|
+
decoded, err := decodeCapdataSmallcapsValue(v, slots, remotables, transformations)
|
|
203
|
+
if err != nil {
|
|
204
|
+
return nil, err
|
|
205
|
+
}
|
|
206
|
+
decodedObj[k] = decoded
|
|
207
|
+
}
|
|
208
|
+
return decodedObj, nil
|
|
209
|
+
} else if str, ok := encoded.(string); ok {
|
|
210
|
+
if len(str) == 0 {
|
|
211
|
+
return str, nil
|
|
212
|
+
}
|
|
213
|
+
switch str[0] {
|
|
214
|
+
case '!':
|
|
215
|
+
return str[1:], nil
|
|
216
|
+
case '+':
|
|
217
|
+
// Normalize to no leading "+".
|
|
218
|
+
str = str[1:]
|
|
219
|
+
fallthrough
|
|
220
|
+
case '-':
|
|
221
|
+
bigint := NewCapdataBigint(str)
|
|
222
|
+
if bigint == nil {
|
|
223
|
+
return nil, fmt.Errorf("invalid bigint: %q", encoded.(string))
|
|
224
|
+
}
|
|
225
|
+
if transformations.Bigint == nil {
|
|
226
|
+
return nil, fmt.Errorf("untransformed bigint")
|
|
227
|
+
}
|
|
228
|
+
return transformations.Bigint(bigint), nil
|
|
229
|
+
case '$':
|
|
230
|
+
var slotIndexStr string
|
|
231
|
+
var iface *string
|
|
232
|
+
if dotIndex := strings.IndexByte(str, '.'); dotIndex >= 0 {
|
|
233
|
+
slotIndexStr = str[1:dotIndex]
|
|
234
|
+
ifaceStr := str[dotIndex+1:]
|
|
235
|
+
iface = &ifaceStr
|
|
236
|
+
} else {
|
|
237
|
+
slotIndexStr = str[1:]
|
|
238
|
+
}
|
|
239
|
+
slotIndex, err := strconv.ParseUint(slotIndexStr, 10, 0)
|
|
240
|
+
if err != nil || slotIndex >= uint64(len(slots)) {
|
|
241
|
+
return nil, fmt.Errorf("invalid slot index: %q", str)
|
|
242
|
+
}
|
|
243
|
+
r, err := upsertCapdataRemotable(remotables, slotIndex, slots[slotIndex], iface)
|
|
244
|
+
if err != nil {
|
|
245
|
+
return nil, fmt.Errorf("slot iface mismatch: %q", str)
|
|
246
|
+
}
|
|
247
|
+
return r, nil
|
|
248
|
+
case '#':
|
|
249
|
+
fallthrough
|
|
250
|
+
case '%':
|
|
251
|
+
fallthrough
|
|
252
|
+
case '&':
|
|
253
|
+
return nil, fmt.Errorf("not implemented: %q", str)
|
|
254
|
+
default:
|
|
255
|
+
if str[0] >= '!' && str[0] <= '-' {
|
|
256
|
+
return nil, fmt.Errorf("invalid smallcaps encoding prefix: %q", str[:1])
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return str, nil
|
|
260
|
+
} else {
|
|
261
|
+
return encoded, nil
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// DecodeSerializedCapdata accepts JSON text representing encoded CapData and
|
|
266
|
+
// decodes it, applying specified transformations for values that otherwise
|
|
267
|
+
// hinder interchange.
|
|
268
|
+
func DecodeSerializedCapdata(
|
|
269
|
+
serializedCapdata string,
|
|
270
|
+
transformations CapdataValueTransformations,
|
|
271
|
+
) (interface{}, error) {
|
|
272
|
+
var capdata Capdata
|
|
273
|
+
if err := json.Unmarshal([]byte(serializedCapdata), &capdata); err != nil {
|
|
274
|
+
return nil, err
|
|
275
|
+
}
|
|
276
|
+
if capdata.Body == "" || capdata.Slots == nil {
|
|
277
|
+
return nil, fmt.Errorf("invalid CapData")
|
|
278
|
+
}
|
|
279
|
+
serializedBody, decodeValue := capdata.Body, decodeCapdataLegacyValue
|
|
280
|
+
if strings.HasPrefix(serializedBody, "#") {
|
|
281
|
+
serializedBody, decodeValue = serializedBody[1:], decodeCapdataSmallcapsValue
|
|
282
|
+
}
|
|
283
|
+
var encoded interface{}
|
|
284
|
+
if err := json.Unmarshal([]byte(serializedBody), &encoded); err != nil {
|
|
285
|
+
return nil, err
|
|
286
|
+
}
|
|
287
|
+
remotables := map[uint64]*CapdataRemotable{}
|
|
288
|
+
decoded, err := decodeValue(encoded, capdata.Slots, remotables, transformations)
|
|
289
|
+
if err == nil && len(remotables) > 0 {
|
|
290
|
+
if transformations.Remotable == nil {
|
|
291
|
+
return nil, fmt.Errorf("untransformed remotable")
|
|
292
|
+
}
|
|
293
|
+
for _, r := range remotables {
|
|
294
|
+
r.Representation = transformations.Remotable(r)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return decoded, err
|
|
298
|
+
}
|