Skip to content

Commit

Permalink
improve proto3 JSON types and utils (#9219)
Browse files Browse the repository at this point in the history
## Description

Spurred by some recent reviews. 
- clarify the Any protobuf encoding
- use it where we can, to align with external types.
- move the vat-safe encoding concern lower than orchestration
(applicable to all inter-vat comms)

### Security Considerations

none

### Scaling Considerations

none

### Documentation Considerations

Less to document

### Testing Considerations

CI

### Upgrade Considerations

not yet in production
  • Loading branch information
mergify[bot] authored Apr 22, 2024
2 parents c0e811e + c77c1be commit 4c65fc2
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 123 deletions.
8 changes: 4 additions & 4 deletions packages/boot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
"license": "Apache-2.0",
"dependencies": {
"@agoric/assert": "^0.6.0",
"@agoric/cosmic-proto": "^0.4.0",
"@agoric/builders": "^0.1.0",
"@agoric/cosmic-proto": "^0.4.0",
"@agoric/cosmic-swingset": "^0.41.3",
"@agoric/ertp": "^0.16.2",
"@agoric/internal": "^0.3.2",
"@agoric/inter-protocol": "^0.16.1",
"@agoric/internal": "^0.3.2",
"@agoric/kmarshal": "^0.1.0",
"@agoric/notifier": "^0.6.2",
"@agoric/orchestration": "^0.1.0",
Expand All @@ -37,12 +37,12 @@
"@agoric/vm-config": "^0.1.0",
"@agoric/zoe": "^0.26.2",
"@agoric/zone": "^0.2.2",
"@endo/marshal": "^1.4.1",
"@endo/bundle-source": "^3.2.2",
"@endo/captp": "^4.1.1",
"@endo/eventual-send": "^1.2.1",
"@endo/init": "^1.1.1",
"@endo/far": "^1.1.1",
"@endo/init": "^1.1.1",
"@endo/marshal": "^1.4.1",
"@endo/promise-kit": "^1.1.1",
"@endo/stream": "^1.2.1",
"import-meta-resolve": "^2.2.1"
Expand Down
22 changes: 14 additions & 8 deletions packages/boot/test/bootstrapTests/test-vat-orchestration.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import type { ExecutionContext, TestFn } from 'ava';

import type { AnyJson } from '@agoric/cosmic-proto';
import {
MsgDelegate,
MsgDelegateResponse,
} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js';
import { Any } from '@agoric/cosmic-proto/google/protobuf/any';
import type { ChainAccount, Orchestration } from '@agoric/orchestration';
import { decodeBase64 } from '@endo/base64';
import { M, matches } from '@endo/patterns';
import { txToBase64 } from '@agoric/orchestration';
import { makeWalletFactoryContext } from './walletFactory.ts';

const makeTestContext = async (t: ExecutionContext) =>
Expand All @@ -22,13 +25,13 @@ const test: TestFn<DefaultTestContext> = anyTest;
* If adding a new msg, reference the mock in the `sendPacket` switch statement
* in [supports.ts](../../tools/supports.ts).
*/
const delegateMsgSuccess = txToBase64(
const delegateMsgSuccess = Any.toJSON(
MsgDelegate.toProtoMsg({
delegatorAddress: 'cosmos1test',
validatorAddress: 'cosmosvaloper1test',
amount: { denom: 'uatom', amount: '10' },
}),
);
) as AnyJson;

test.before(async t => {
t.context = await makeTestContext(t);
Expand Down Expand Up @@ -97,7 +100,8 @@ test('ICA connection can be closed', async t => {
runUtils: { EV },
} = t.context;

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');
const orchestration: Orchestration =
await EV.vat('bootstrap').consumeItem('orchestration');

const account = await EV(orchestration).createAccount(
'connection-0',
Expand All @@ -120,13 +124,13 @@ test('ICA connection can send msg with proto3', async t => {

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');

/** @type {ChainAccount} */
const account = await EV(orchestration).createAccount(
const account: ChainAccount = await EV(orchestration).createAccount(
'connection-0',
'connection-0',
);
t.truthy(account, 'createAccount returns an account');

// @ts-expect-error intentional
await t.throwsAsync(EV(account).executeEncodedTx('malformed'), {
message:
'In "executeEncodedTx" method of (ChainAccount account): arg 0: string "malformed" - Must be a copyArray',
Expand All @@ -146,6 +150,7 @@ test('ICA connection can send msg with proto3', async t => {

const txWithOptions = await EV(account).executeEncodedTx(
[delegateMsgSuccess],
// @ts-expect-error XXX TxBody interface
{
memo: 'TESTING',
timeoutHeight: 1_000_000_000n,
Expand All @@ -157,13 +162,14 @@ test('ICA connection can send msg with proto3', async t => {
'txWithOptions',
);

const delegateMsgFailure = txToBase64(
const delegateMsgFailure = Any.toJSON(
MsgDelegate.toProtoMsg({
delegatorAddress: 'cosmos1fail',
validatorAddress: 'cosmosvaloper1fail',
amount: { denom: 'uatom', amount: '10' },
}),
);
) as AnyJson;

await t.throwsAsync(EV(account).executeEncodedTx([delegateMsgFailure]), {
message: 'ABCI code: 5: error handling packet: see events for details',
});
Expand Down
4 changes: 4 additions & 0 deletions packages/cosmic-proto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"types": "./dist/codegen/cosmos/staking/v1beta1/tx.d.ts",
"default": "./dist/codegen/cosmos/staking/v1beta1/tx.js"
},
"./google/protobuf/any": {
"types": "./dist/codegen/google/protobuf/any.d.ts",
"default": "./dist/codegen/google/protobuf/any.js"
},
"./swingset/msgs.js": {
"types": "./dist/codegen/agoric/swingset/msgs.d.ts",
"default": "./dist/codegen/agoric/swingset/msgs.js"
Expand Down
15 changes: 15 additions & 0 deletions packages/cosmic-proto/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import type { QueryAllBalancesRequest } from './codegen/cosmos/bank/v1beta1/quer
import type { MsgSend } from './codegen/cosmos/bank/v1beta1/tx.js';
import type { MsgDelegate } from './codegen/cosmos/staking/v1beta1/tx.js';

/**
* The result of Any.toJSON(). The type in cosms-types says it returns
* `unknown` but it's actually this. The `value` string is a base64 encoding of
* the bytes array.
*/
export type AnyJson = { typeUrl: string; value: string };

// TODO codegen this by modifying Telescope
export type Proto3Shape = {
'/cosmos.bank.v1beta1.MsgSend': MsgSend;
Expand Down Expand Up @@ -34,3 +41,11 @@ export const typedJson = <T extends keyof Proto3Shape>(
...obj,
} as TypedJson<T>;
};

// TODO make codegen toJSON() return these instead of unknown
/**
* Proto Any with arrays encoded as base64
*/
export type Base64Any<T> = {
[Prop in keyof T]: T[Prop] extends Uint8Array ? string : T[Prop];
};
7 changes: 3 additions & 4 deletions packages/orchestration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"homepage": "https://github.com/Agoric/agoric-sdk#readme",
"dependencies": {
"@agoric/assert": "^0.6.0",
"@agoric/ertp": "^0.16.2",
"@agoric/cosmic-proto": "^0.4.0",
"@agoric/ertp": "^0.16.2",
"@agoric/internal": "^0.3.2",
"@agoric/network": "^0.1.0",
"@agoric/notifier": "^0.6.2",
Expand All @@ -46,11 +46,10 @@
"@endo/patterns": "^1.3.1"
},
"devDependencies": {
"@endo/ses-ava": "^1.2.1",
"@cosmjs/amino": "^0.32.3",
"@cosmjs/proto-signing": "^0.32.3",
"ava": "^5.3.1",
"cosmjs-types": "^0.9.0"
"@endo/ses-ava": "^1.2.1",
"ava": "^5.3.1"
},
"ava": {
"extensions": {
Expand Down
17 changes: 10 additions & 7 deletions packages/orchestration/src/contracts/stakingAccountHolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import { M, prepareExoClassKit } from '@agoric/vat-data';
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js';
import { decodeBase64 } from '@endo/base64';
import { E } from '@endo/far';
import { txToBase64 } from '../utils/tx.js';
import { Any } from '@agoric/cosmic-proto/google/protobuf/any';

/**
* @import { ChainAccount, ChainAddress } from '../types.js';
* @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js';
* @import { Baggage } from '@agoric/swingset-liveslots';
* @import {AnyJson} from '@agoric/cosmic-proto';
*/

const trace = makeTracer('StakingAccountHolder');
Expand Down Expand Up @@ -109,12 +110,14 @@ export const prepareStakingAccountHolder = (baggage, makeRecorderKit, zcf) => {
const delegatorAddress = this.state.chainAddress;

const result = await E(account).executeEncodedTx([
txToBase64(
MsgDelegate.toProtoMsg({
delegatorAddress,
validatorAddress,
amount,
}),
/** @type {AnyJson} */ (
Any.toJSON(
MsgDelegate.toProtoMsg({
delegatorAddress,
validatorAddress,
amount,
}),
)
),
]);

Expand Down
4 changes: 2 additions & 2 deletions packages/orchestration/src/orchestration.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import '@agoric/network/exported.js';
const { Fail, bare } = assert;
const trace = makeTracer('Orchestration');

/** @import {Proto3Msg} from './utils/tx.js'; */
/** @import {AnyJson} from '@agoric/cosmic-proto'; */

// TODO improve me
/** @typedef {string} ChainAddress */
Expand Down Expand Up @@ -124,7 +124,7 @@ const prepareChainAccount = zone =>
return this.state.port;
},
/**
* @param {Proto3Msg[]} msgs
* @param {AnyJson[]} msgs
* @param {Omit<TxBody, 'messages'>} [opts]
* @returns {Promise<string>} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object.
* @throws {Error} if packet fails to send or an error is returned
Expand Down
25 changes: 4 additions & 21 deletions packages/orchestration/src/utils/tx.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
// @ts-check
import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
import { decodeBase64, encodeBase64 } from '@endo/base64';

/** @typedef {{ typeUrl: string; value: string; }} Proto3Msg */
import { encodeBase64 } from '@endo/base64';
import { Any } from '@agoric/cosmic-proto/google/protobuf/any';

/**
* Makes an IBC packet from an array of messages. Expects the `value` of each message
* to be base64 encoded bytes.
* Skips checks for malformed messages in favor of interface guards.
* @param {Proto3Msg[]} msgs
* @param {import('@agoric/cosmic-proto').AnyJson[]} msgs
* // XXX intellisense does not seem to infer well here
* @param {Omit<TxBody, 'messages'>} [opts]
* @returns {string} - IBC TX packet
* @throws {Error} if malformed messages are provided
*/
export function makeTxPacket(msgs, opts) {
const messages = msgs.map(msg => ({
typeUrl: msg.typeUrl,
value: decodeBase64(msg.value),
}));
const messages = msgs.map(Any.fromJSON);
const bytes = TxBody.encode(
TxBody.fromPartial({
messages,
Expand All @@ -34,19 +30,6 @@ export function makeTxPacket(msgs, opts) {
}
harden(makeTxPacket);

/**
* base64 encode an EncodeObject for cross-vat communication
* @param {{ typeUrl: string, value: Uint8Array }} tx
* @returns {Proto3Msg}
*/
export function txToBase64(tx) {
return {
typeUrl: tx.typeUrl,
value: encodeBase64(tx.value),
};
}
harden(txToBase64);

/**
* Looks for a result or error key in the response string, and returns
* a Base64Bytes string. This string can be decoded using the corresponding
Expand Down
12 changes: 5 additions & 7 deletions packages/orchestration/test/utils/tx.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import test from '@endo/ses-ava/prepare-endo.js';
import {
makeTxPacket,
txToBase64,
parsePacketAck,
} from '../../src/utils/tx.js';
import { Any } from '@agoric/cosmic-proto/google/protobuf/any';
import { makeTxPacket, parsePacketAck } from '../../src/utils/tx.js';

test('makeTxPacket', t => {
const mockMsg = {
Expand All @@ -21,12 +18,13 @@ test('makeTxPacket', t => {
'{"type":1,"data":"ChcKCy9mb28uYmFyLnYxEggKBgoEc2VudBIFaGVsbG8YkE4=","memo":""}',
'accepts options for TxBody',
);
t.throws(() =>
t.deepEqual(
makeTxPacket([
{
typeUrl: mockMsg.typeUrl,
},
]),
'{"type":1,"data":"Cg0KCy9mb28uYmFyLnYx","memo":""}',
);
t.throws(() =>
makeTxPacket([
Expand All @@ -40,7 +38,7 @@ test('makeTxPacket', t => {

test('txToBase64', t => {
t.deepEqual(
txToBase64({
Any.toJSON({
typeUrl: '/foo.bar.v1',
value: new Uint8Array([1, 2, 3]),
}),
Expand Down
1 change: 1 addition & 0 deletions packages/vats/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@agoric/vat-data": "^0.5.2",
"@agoric/zoe": "^0.26.2",
"@agoric/zone": "^0.2.2",
"@endo/base64": "^1.0.4",
"@endo/far": "^1.1.1",
"@endo/import-bundle": "^1.1.1",
"@endo/marshal": "^1.4.1",
Expand Down
5 changes: 1 addition & 4 deletions packages/zoe/src/contractFacet/types-ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,7 @@ type ZcfSeatKit = {
zcfSeat: ZCFSeat;
userSeat: ERef<UserSeat>;
};
type HandleOffer<OR extends unknown, OA> = (
seat: ZCFSeat,
offerArgs?: OA,
) => OR;
type HandleOffer<OR extends unknown, OA> = (seat: ZCFSeat, offerArgs: OA) => OR;
type OfferHandler<OR extends unknown = unknown, OA = never> =
| HandleOffer<OR, OA>
| {
Expand Down
Loading

0 comments on commit 4c65fc2

Please sign in to comment.