Skip to content

Commit 605ca17

Browse files
michaelfigkriskowal
andcommitted
feat(cosmos): first cut at install chunking proto
Co-authored-by: Kris Kowal <[email protected]>
1 parent 7b17bb6 commit 605ca17

File tree

37 files changed

+6378
-552
lines changed

37 files changed

+6378
-552
lines changed

golang/cosmos/proto/agoric/swingset/msgs.proto

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import "amino/amino.proto";
55
import "cosmos/msg/v1/msg.proto";
66
import "cosmos_proto/cosmos.proto";
77
import "gogoproto/gogo.proto";
8+
import "agoric/swingset/swingset.proto";
89

910
option go_package = "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types";
1011

1112
// Transactions.
1213
service Msg {
1314
// Install a JavaScript sources bundle on the chain's SwingSet controller.
1415
rpc InstallBundle(MsgInstallBundle) returns (MsgInstallBundleResponse);
16+
// Send a chunk of a bundle to tolerate RPC message size limits.
17+
rpc SendChunk(MsgSendChunk) returns (MsgSendChunkResponse);
1518
// Send inbound messages.
1619
rpc DeliverInbound(MsgDeliverInbound) returns (MsgDeliverInboundResponse);
1720
// Perform a low-privilege wallet action.
@@ -127,6 +130,10 @@ message MsgProvision {
127130
message MsgProvisionResponse {}
128131

129132
// MsgInstallBundle carries a signed bundle to SwingSet.
133+
// Of the fields bundle, compressed_bundle, and chunked_artifact, exactly one
134+
// must be present: bundle if complete and uncompressed, compressed_bundle if
135+
// complete and compressed, or chunked_artifact for a manifest of chunks to be
136+
// submitted in subsequent messages.
130137
message MsgInstallBundle {
131138
// Until agoric-upgrade-22 this message didn't have an amino name
132139
// but no clients actually used amino encoding
@@ -140,26 +147,28 @@ message MsgInstallBundle {
140147
(gogoproto.jsontag) = "submitter",
141148
(gogoproto.moretags) = "yaml:\"submitter\""
142149
];
143-
// Either bundle or compressed_bundle will be set.
144150
// Default compression algorithm is gzip.
145151
bytes compressed_bundle = 3 [
146152
(amino.dont_omitempty) = true,
147153
(amino.field_name) = "compressedBundle",
148154
(gogoproto.jsontag) = "compressedBundle",
149155
(gogoproto.moretags) = "yaml:\"compressedBundle\""
150156
];
151-
// Size in bytes of uncompression of compressed_bundle.
157+
// Total size in bytes of the bundle artifact, before compression and after
158+
// decompression.
152159
int64 uncompressed_size = 4 [
153160
(amino.dont_omitempty) = true,
154161
(amino.field_name) = "uncompressedSize",
155162
(gogoproto.jsontag) = "uncompressedSize"
156163
];
164+
// Declaration of a chunked bundle.
165+
ChunkedArtifact chunked_artifact = 5 [
166+
(amino.field_name) = "chunkedArtifact",
167+
(gogoproto.jsontag) = "chunkedArtifact",
168+
(gogoproto.moretags) = "yaml:\"chunkedArtifact\""
169+
];
157170
}
158171

159-
// MsgInstallBundleResponse is an empty acknowledgement that an install bundle
160-
// message has been queued for the SwingSet kernel's consideration.
161-
message MsgInstallBundleResponse {}
162-
163172
// MsgCoreEval defines an SDK message for a core eval.
164173
message MsgCoreEval {
165174
option (cosmos.msg.v1.signer) = "authority";
@@ -181,3 +190,61 @@ message MsgCoreEvalResponse {
181190
// The result of the core eval.
182191
string result = 1 [(gogoproto.moretags) = "yaml:\"result\""];
183192
}
193+
194+
// MsgInstallBundleResponse is either an empty acknowledgement that a bundle
195+
// installation message has been queued for the SwingSet kernel's
196+
// consideration, or for MsgInstallBundle requests that have a chunked artifact
197+
// manifest instead of a compressed or uncompressed bundle: the identifier
198+
// assigned for the chunked artifact for reference in subsequent MsgSendChunk
199+
// messages.
200+
message MsgInstallBundleResponse {
201+
// The assigned identifier for a chunked artifact, if the caller is expected
202+
// to call back with MsgSendChunk messages.
203+
uint64 chunked_artifact_id = 1 [
204+
(amino.field_name) = "chunkedArtifactId",
205+
(gogoproto.jsontag) = "chunkedArtifactId",
206+
(gogoproto.moretags) = "yaml:\"chunkedArtifactId\""
207+
];
208+
}
209+
210+
// MsgSendChunk carries a chunk of an artifact through RPC to the chain.
211+
// Individual chunks are addressed by the chunked artifact identifier and
212+
// the zero-based index of the chunk among all chunks as mentioned in the
213+
// manifest provided to MsgInstallBundle.
214+
message MsgSendChunk {
215+
uint64 chunked_artifact_id = 1 [
216+
(amino.field_name) = "chunkedArtifactId",
217+
(gogoproto.jsontag) = "chunkedArtifactId",
218+
(gogoproto.moretags) = "yaml:\"chunkedArtifactId\""
219+
];
220+
bytes submitter = 2 [
221+
(amino.encoding) = "legacy_address",
222+
(amino.field_name) = "submitter",
223+
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
224+
(gogoproto.jsontag) = "submitter",
225+
(gogoproto.moretags) = "yaml:\"submitter\""
226+
];
227+
uint64 chunk_index = 3 [
228+
(amino.field_name) = "chunkIndex",
229+
(gogoproto.jsontag) = "chunkIndex",
230+
(gogoproto.moretags) = "yaml:\"chunkIndex\""
231+
];
232+
bytes chunk_data = 4 [
233+
(amino.field_name) = "chunkIndex",
234+
(gogoproto.jsontag) = "chunkData",
235+
(gogoproto.moretags) = "yaml:\"chunkData\""
236+
];
237+
}
238+
239+
// MsgSendChunkResponse is an acknowledgement that a chunk has been received by
240+
// the chain.
241+
message MsgSendChunkResponse {
242+
uint64 chunked_artifact_id = 1 [
243+
(amino.field_name) = "chunkedArtifactId",
244+
(gogoproto.jsontag) = "chunkedArtifactId",
245+
(gogoproto.moretags) = "yaml:\"chunkedArtifactId\""
246+
];
247+
// The current state of the chunk.
248+
ChunkInfo chunk = 2
249+
[(amino.field_name) = "chunk", (gogoproto.jsontag) = "chunk", (gogoproto.moretags) = "yaml:\"chunk\""];
250+
}

golang/cosmos/proto/agoric/swingset/query.proto

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ service Query {
2323
rpc Mailbox(QueryMailboxRequest) returns (QueryMailboxResponse) {
2424
option (google.api.http).get = "/agoric/swingset/mailbox/{peer}";
2525
}
26+
27+
// Return the state of a pending installation.
28+
rpc ChunkedArtifactStatus(QueryChunkedArtifactStatusRequest) returns (QueryChunkedArtifactStatusResponse) {
29+
option (google.api.http).get = "/agoric/swingset/chunked-artifact-status/{chunked_artifact_id}";
30+
}
2631
}
2732

2833
// QueryParamsRequest is the request type for the Query/Params RPC method.
@@ -61,3 +66,24 @@ message QueryMailboxRequest {
6166
message QueryMailboxResponse {
6267
string value = 1 [(gogoproto.jsontag) = "value", (gogoproto.moretags) = "yaml:\"value\""];
6368
}
69+
70+
// QueryChunkedArtifactStatusRequest is the request type for the Query/ChunkedArtifact RPC method.
71+
message QueryChunkedArtifactStatusRequest {
72+
uint64 chunked_artifact_id = 1
73+
[(gogoproto.jsontag) = "chunkedArtifactId", (gogoproto.moretags) = "yaml:\"chunkedArtifactId\""];
74+
}
75+
76+
// QueryChunkedArtifactStatuslResponse is the response type for the Query/ChunkedArtifact RPC method.
77+
message QueryChunkedArtifactStatusResponse {
78+
uint64 chunked_artifact_id = 1
79+
[(gogoproto.jsontag) = "chunkedArtifactId", (gogoproto.moretags) = "yaml:\"chunkedArtifactId\""];
80+
81+
ChunkedArtifact chunked_artifact = 2
82+
[(gogoproto.jsontag) = "chunkedArtifact", (gogoproto.moretags) = "yaml:\"chunkedArtifact\""];
83+
84+
// Start time in UNIX epoch seconds.
85+
int64 start_time_unix = 3 [(gogoproto.jsontag) = "startTimeUnix", (gogoproto.moretags) = "yaml:\"startTimeUnix\""];
86+
87+
int64 start_block_height = 4
88+
[(gogoproto.jsontag) = "startBlockHeight", (gogoproto.moretags) = "yaml:\"startBlockHeight\""];
89+
}

golang/cosmos/proto/agoric/swingset/swingset.proto

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,35 @@ message Params {
8989
// nodes must all serialize and deserialize the existing order without
9090
// permuting it.
9191
repeated UintMapEntry vat_cleanup_budget = 6 [(gogoproto.nullable) = false];
92+
93+
// The maximum number of blocks that an async installation can use. -1 is
94+
// unlimited.
95+
int64 installation_deadline_blocks = 7;
96+
97+
// The maximum number of seconds that an async installation can use. -1 is
98+
// unlimited.
99+
int64 installation_deadline_seconds = 8;
100+
101+
// The maximum size of of a bundle (0 implies default 10MB)
102+
int64 bundle_uncompressed_size_limit_bytes = 9;
103+
104+
// The maximum size of a bundle or artifact chunk (0 implies default 512KB)
105+
int64 chunk_size_limit_bytes = 10;
92106
}
93107

94108
// The current state of the module.
95109
message State {
96110
// The allowed number of items to add to queues, as determined by SwingSet.
97111
// Transactions which attempt to enqueue more should be rejected.
98112
repeated QueueSize queue_allowed = 1 [(gogoproto.nullable) = false];
113+
114+
// Doubly-linked list in order of start block and time.
115+
uint64 first_chunked_artifact_id = 2
116+
[(gogoproto.jsontag) = "first_chunked_artifact_id", (gogoproto.moretags) = "yaml:\"first_chunked_artifact_id\""];
117+
118+
// The last chunked artifact id that has not expired or completed.
119+
uint64 last_chunked_artifact_id = 3
120+
[(gogoproto.jsontag) = "lastChunkedArtifactId", (gogoproto.moretags) = "yaml:\"lastChunkedArtifactId\""];
99121
}
100122

101123
// Map element of a string key to a Nat bean count.
@@ -167,3 +189,66 @@ message SwingStoreArtifact {
167189

168190
bytes data = 2 [(gogoproto.jsontag) = "data", (gogoproto.moretags) = "yaml:\"data\""];
169191
}
192+
193+
// ChunkedArtifact is the manifest for an artifact that is submitted across
194+
// multiple transactions, in chunks, as when using InstallBundle to submit
195+
// chunks.
196+
message ChunkedArtifact {
197+
// The SHA-512 hash of the compartment-map.json file inside the bundle.
198+
string sha512 = 1 [(gogoproto.jsontag) = "sha512", (gogoproto.moretags) = "yaml:\"sha512\""];
199+
200+
// The size of the final bundle artifact in bytes.
201+
uint64 size_bytes = 2 [(gogoproto.jsontag) = "size_bytes", (gogoproto.moretags) = "yaml:\"size_bytes\""];
202+
203+
// Information about the chunks that will be concatenated to form this
204+
// bundle.
205+
repeated ChunkInfo chunks = 3 [(gogoproto.jsontag) = "chunks", (gogoproto.moretags) = "yaml:\"chunks\""];
206+
}
207+
208+
// Current state of this chunk.
209+
enum ChunkState {
210+
// Unknown state.
211+
CHUNK_STATE_UNSPECIFIED = 0;
212+
213+
// The chunk is still in-flight.
214+
CHUNK_STATE_IN_FLIGHT = 1;
215+
216+
// The chunk has been received.
217+
CHUNK_STATE_RECEIVED = 2;
218+
219+
// The chunk has been processed.
220+
CHUNK_STATE_PROCESSED = 3;
221+
};
222+
223+
// Information about a chunk of a bundle.
224+
message ChunkInfo {
225+
// The SHA-512 hash of the chunk contents.
226+
string sha512 = 1 [(gogoproto.jsontag) = "sha512", (gogoproto.moretags) = "yaml:\"sha512\""];
227+
228+
// The chunk size in bytes.
229+
uint64 size_bytes = 2 [(gogoproto.jsontag) = "size_bytes", (gogoproto.moretags) = "yaml:\"size_bytes\""];
230+
231+
// The current state of the chunk.
232+
ChunkState state = 3 [(gogoproto.jsontag) = "state", (gogoproto.moretags) = "yaml:\"state\""];
233+
}
234+
235+
// A node in a doubly-linked-list of chunks of a chunked artifact, as used for
236+
// chunked bundle installation, in order of ascending block time.
237+
// The keeper uses this to expediently expire stale chunks.
238+
message ChunkedArtifactNode {
239+
// The id of the pending bundle installation.
240+
uint64 chunked_artifact_id = 1
241+
[(gogoproto.jsontag) = "chunkedArtifactId", (gogoproto.moretags) = "yaml:\"chunkedArtifactId\""];
242+
243+
// Doubly-linked list.
244+
uint64 next_id = 2 [(gogoproto.jsontag) = "nextId", (gogoproto.moretags) = "yaml:\"nextId\""];
245+
246+
uint64 prev_id = 3 [(gogoproto.jsontag) = "prevId", (gogoproto.moretags) = "yaml:\"prevId\""];
247+
248+
// The time at which the pending installation began, in UNIX epoch seconds.
249+
int64 start_time_unix = 4 [(gogoproto.jsontag) = "startTimeUnix", (gogoproto.moretags) = "yaml:\"startTimeUnix\""];
250+
251+
// The block at which the pending installation began.
252+
int64 start_block_height = 5
253+
[(gogoproto.jsontag) = "startBlockHeight", (gogoproto.moretags) = "yaml:\"startBlockHeight\""];
254+
}

golang/cosmos/x/swingset/abci.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ func EndBlock(ctx sdk.Context, keeper Keeper) ([]abci.ValidatorUpdate, error) {
6666
panic(err)
6767
}
6868

69+
// Remove expired bundle installs.
70+
keeper.PruneExpiredBundleInstalls(ctx)
71+
6972
// Save our EndBlock status.
7073
endBlockHeight = ctx.BlockHeight()
7174
endBlockTime = ctx.BlockTime().Unix()

golang/cosmos/x/swingset/keeper/grpc_query.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,27 @@ func (k Querier) Mailbox(c context.Context, req *types.QueryMailboxRequest) (*ty
6161
Value: value,
6262
}, nil
6363
}
64+
65+
func (k Querier) ChunkedArtifactStatus(c context.Context, req *types.QueryChunkedArtifactStatusRequest) (*types.QueryChunkedArtifactStatusResponse, error) {
66+
if req == nil {
67+
return nil, status.Error(codes.InvalidArgument, "empty request")
68+
}
69+
ctx := sdk.UnwrapSDKContext(c)
70+
71+
msg := k.GetPendingBundleInstall(ctx, req.ChunkedArtifactId)
72+
if msg == nil {
73+
return nil, status.Error(codes.NotFound, "pending chunked artifact not found")
74+
}
75+
76+
can := k.GetChunkedArtifactNode(ctx, req.ChunkedArtifactId)
77+
if can == nil {
78+
return nil, status.Error(codes.NotFound, "pending chunked artifact node not found")
79+
}
80+
81+
return &types.QueryChunkedArtifactStatusResponse{
82+
ChunkedArtifactId: req.ChunkedArtifactId,
83+
ChunkedArtifact: msg.ChunkedArtifact,
84+
StartTimeUnix: can.StartTimeUnix,
85+
StartBlockHeight: can.StartBlockHeight,
86+
}, nil
87+
}

0 commit comments

Comments
 (0)