@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 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
- type bootstrapBlockAction struct {
887
- Type string `json:"type"`
888
- BlockTime int64 `json:"blockTime"`
889
- }
890
-
891
- // BootstrapController initializes the controller (with the bootstrap flag) and sends a bootstrap action.
892
- func (app *GaiaApp) BootstrapController(ctx sdk.Context) error {
893
- app.initController(ctx, true)
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
- _, err := app.SwingSetKeeper.BlockingSend(ctx, action)
904
- return err
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
- err := app.BootstrapController(ctx)
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
- 29ef60f
1
+ f84ea2e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmos",
3
- "version": "0.34.2-dev-29ef60f.0+29ef60f",
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": "29ef60f589cf63d033b5d0e3e2cc060c087781ad"
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 [
@@ -80,16 +80,18 @@ func (AppModuleBasic) GetTxCmd() *cobra.Command {
80
80
 
81
81
  type AppModule struct {
82
82
  AppModuleBasic
83
- keeper Keeper
84
- setBootstrapNeeded func()
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: AppModuleBasic{},
91
- keeper: k,
92
- setBootstrapNeeded: 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:"bcompressedBndle"`
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
- // 789 bytes of a gzipped FileDescriptorProto
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, 0xfd, 0xdc, 0xfb, 0x02,
594
- 0x25, 0x56, 0xb1, 0x26, 0x56, 0x86, 0xd0, 0x9e, 0x05, 0x0b, 0xf5, 0xa9, 0x7b, 0x89, 0xf9, 0x89,
595
- 0xc9, 0xc6, 0x5e, 0x56, 0x7d, 0x91, 0x7c, 0xe3, 0x5e, 0x62, 0x73, 0x0d, 0xd1, 0x35, 0x50, 0x57,
596
- 0xf7, 0xb6, 0xd8, 0xf8, 0x4f, 0xae, 0x65, 0x90, 0x47, 0xd4, 0x51, 0xbe, 0x85, 0x87, 0xcb, 0x9b,
597
- 0xff, 0xac, 0xbf, 0xf2, 0x1a, 0xe8, 0xaf, 0xd6, 0xd0, 0x3e, 0xb8, 0x55, 0x52, 0xb4, 0x51, 0x4e,
598
- 0xe0, 0xd1, 0xca, 0x8b, 0x42, 0xdf, 0x94, 0xbc, 0xac, 0xd1, 0x3e, 0xbc, 0x5d, 0x53, 0x76, 0x38,
599
- 0x82, 0xd6, 0xd2, 0xc3, 0xb4, 0xbb, 0x29, 0x77, 0x51, 0xa1, 0xf5, 0x6e, 0x53, 0x94, 0xb5, 0x5d,
600
- 0x68, 0xaf, 0x3f, 0xf9, 0x9e, 0xff, 0x73, 0xfa, 0x82, 0x4c, 0x3b, 0xf8, 0x4f, 0xb2, 0xb2, 0xd5,
601
- 0x97, 0xd0, 0xac, 0x1e, 0x50, 0xef, 0x6d, 0xca, 0x2d, 0x69, 0xed, 0xf9, 0xbf, 0xd2, 0x45, 0x49,
602
- 0xe3, 0xab, 0xdf, 0xe6, 0x1d, 0xe9, 0x6a, 0xde, 0x91, 0xae, 0xe7, 0x1d, 0xe9, 0xc7, 0x9b, 0x4e,
603
- 0xed, 0xea, 0xa6, 0x53, 0xfb, 0xfd, 0xa6, 0x53, 0x3b, 0x3a, 0x5c, 0x98, 0xf9, 0xa1, 0xf8, 0x20,
604
- 0x10, 0x15, 0xf9, 0xcc, 0x3b, 0xc4, 0xb3, 0x02, 0xa7, 0xb8, 0x0c, 0xdf, 0x57, 0xdf, 0x0a, 0xfc,
605
- 0x32, 0x8c, 0xb7, 0xf8, 0x67, 0xc0, 0x8b, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x99, 0x11, 0x18,
606
- 0xbe, 0x4b, 0x08, 0x00, 0x00,
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
+ }