Skip to content

Commit

Permalink
feat: defer inbound bridge messages
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Jul 24, 2024
1 parent d28e4ec commit bb0683a
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 27 deletions.
20 changes: 14 additions & 6 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,13 @@ test.skip('stakeOsmo - queries', async t => {
});

test.serial('stakeAtom - smart wallet', async t => {
const { buildProposal, evalProposal, agoricNamesRemotes, readLatest } =
t.context;
const {
buildProposal,
evalProposal,
agoricNamesRemotes,
flushInboundQueue,
readLatest,
} = t.context;

await evalProposal(
buildProposal('@agoric/builders/scripts/orchestration/init-stakeAtom.js'),
Expand Down Expand Up @@ -170,7 +175,9 @@ test.serial('stakeAtom - smart wallet', async t => {
const { ATOM } = agoricNamesRemotes.brand;
ATOM || Fail`ATOM missing from agoricNames`;

await wd.executeOffer({
// Cannot await executeOffer because the offer won't resolve until after the bridge's inbound queue is flushed.
// But this test doesn't require that.
await wd.sendOffer({
id: 'request-delegate-success',
invitationSpec: {
source: 'continuing',
Expand All @@ -190,6 +197,7 @@ test.serial('stakeAtom - smart wallet', async t => {
encoding: 'bech32',
};

// This will trigger the immediate ack of the mock bridge
await t.throwsAsync(
wd.executeOffer({
id: 'request-delegate-fail',
Expand Down Expand Up @@ -332,7 +340,7 @@ test.serial('basic-flows - portfolio holder', async t => {
await t.context.walletFactoryDriver.provideSmartWallet('agoric1test2');

// create a cosmos orchestration account
await wd.executeOffer({
await wd.sendOffer({
id: 'request-portfolio-acct',
invitationSpec: {
source: 'agoricContract',
Expand Down Expand Up @@ -369,7 +377,7 @@ test.serial('basic-flows - portfolio holder', async t => {
ATOM || Fail`ATOM missing from agoricNames`;
BLD || Fail`BLD missing from agoricNames`;

await wd.executeOffer({
await wd.sendOffer({
id: 'delegate-cosmoshub',
invitationSpec: {
source: 'continuing',
Expand All @@ -387,7 +395,7 @@ test.serial('basic-flows - portfolio holder', async t => {
status: { id: 'delegate-cosmoshub', numWantsSatisfied: 1 },
});

await wd.executeOffer({
await wd.sendOffer({
id: 'delegate-agoric',
invitationSpec: {
source: 'continuing',
Expand Down
75 changes: 54 additions & 21 deletions packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
NonNullish,
VBankAccount,
makeTracer,
type BridgeIdValue,
type Remote,
} from '@agoric/internal';
import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js';
Expand Down Expand Up @@ -40,7 +41,7 @@ import type { MsgDelegateResponse } from '@agoric/cosmic-proto/cosmos/staking/v1
import type { CoreEvalSDKType } from '@agoric/cosmic-proto/swingset/swingset.js';
import type { EconomyBootstrapPowers } from '@agoric/inter-protocol/src/proposals/econ-behaviors.js';
import type { SwingsetController } from '@agoric/swingset-vat/src/controller/controller.js';
import type { BridgeHandler, IBCMethod } from '@agoric/vats';
import type { BridgeHandler, IBCMethod, IBCPacket } from '@agoric/vats';
import type { BootstrapRootObject } from '@agoric/vats/src/core/lib-boot.js';
import type { EProxy } from '@endo/eventual-send';
import { icaMocks, protoMsgMocks } from './ibc/mocks.js';
Expand Down Expand Up @@ -324,15 +325,26 @@ export const makeSwingsetTestKit = async (

const outboundMessages = new Map();

let inbound;
const inbound: Awaited<ReturnType<typeof buildSwingset>>['bridgeInbound'] = (
...args
) => {
console.log('inbound', ...args);
// eslint-disable-next-line no-use-before-define
bridgeInbound!(...args);
};
let ibcSequenceNonce = 0;

const addSequenceNonce = ({ packet }: IBCMethod<'sendPacket'>): IBCPacket => {
ibcSequenceNonce += 1;
return { ...packet, sequence: ibcSequenceNonce };
};

/**
* Adds the sequence so the bridge knows what response to connect it to.
* Then queue it send it over the bridge over this returns.
* Finally return the packet that will be sent.
*/
const makeAckEvent = (obj: IBCMethod<'sendPacket'>, ack: string) => {
const ackImmediately = (obj: IBCMethod<'sendPacket'>, ack: string) => {
ibcSequenceNonce += 1;
const msg = icaMocks.ackPacketEvent(obj, ibcSequenceNonce, ack);
setTimeout(() => {
Expand All @@ -345,6 +357,18 @@ export const makeSwingsetTestKit = async (
return msg.packet;
};

const inboundQueue: [bridgeId: BridgeIdValue, arg1: unknown][] = [];
/**
* Like ackImmediately but defers in the inbound receiverAck
* until `bridgeQueue()` is awaited.
*/
const ackLater = (obj: IBCMethod<'sendPacket'>, ack: string) => {
ibcSequenceNonce += 1;
const msg = icaMocks.ackPacketEvent(obj, ibcSequenceNonce, ack);
inboundQueue.push([BridgeId.DIBC, msg]);
return msg.packet;
};

/**
* Mock the bridge outbound handler. The real one is implemented in Golang so
* changes there will sometimes require changes here.
Expand Down Expand Up @@ -413,42 +437,34 @@ export const makeSwingsetTestKit = async (
case 'IBC_METHOD':
switch (obj.method) {
case 'startChannelOpenInit':
inbound(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
inbound!(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
return undefined;
case 'sendPacket':
switch (obj.packet.data) {
case protoMsgMocks.delegate.msg: {
return makeAckEvent(obj, protoMsgMocks.delegate.ack);
return ackLater(obj, protoMsgMocks.delegate.ack);
}
case protoMsgMocks.delegateWithOpts.msg: {
return makeAckEvent(
obj,
protoMsgMocks.delegateWithOpts.ack,
);
return ackLater(obj, protoMsgMocks.delegateWithOpts.ack);
}
case protoMsgMocks.queryBalance.msg: {
return makeAckEvent(obj, protoMsgMocks.queryBalance.ack);
return ackLater(obj, protoMsgMocks.queryBalance.ack);
}
case protoMsgMocks.queryUnknownPath.msg: {
return makeAckEvent(
obj,
protoMsgMocks.queryUnknownPath.ack,
);
return ackLater(obj, protoMsgMocks.queryUnknownPath.ack);
}
case protoMsgMocks.queryBalanceMulti.msg: {
return makeAckEvent(
obj,
protoMsgMocks.queryBalanceMulti.ack,
);
return ackLater(obj, protoMsgMocks.queryBalanceMulti.ack);
}
case protoMsgMocks.queryBalanceUnknownDenom.msg: {
return makeAckEvent(
return ackLater(
obj,
protoMsgMocks.queryBalanceUnknownDenom.ack,
);
}
default: {
return makeAckEvent(obj, protoMsgMocks.error.ack);
// An error that would be triggered before reception on another chain
return ackImmediately(obj, protoMsgMocks.error.ack);
}
}
default:
Expand Down Expand Up @@ -521,7 +537,6 @@ export const makeSwingsetTestKit = async (
debugVats,
},
);
inbound = bridgeInbound;

console.timeLog('makeBaseSwingsetTestKit', 'buildSwingset');

Expand Down Expand Up @@ -604,12 +619,30 @@ export const makeSwingsetTestKit = async (
const getOutboundMessages = (bridgeId: string) =>
harden([...outboundMessages.get(bridgeId)]);

/**
* @param {number} max the max number of messages to flush
* @returns {Promise<number>} the number of messages flushed
*/
const flushInboundQueue = async (max: number = Number.POSITIVE_INFINITY) => {
console.log('🚽');
let i = 0;
for (i = 0; i < max; i += 1) {
const args = inboundQueue.shift();
if (!args) break;

await runUtils.queueAndRun(() => inbound(...args), true);
}
console.log('🧻');
return i;
};

return {
advanceTimeBy,
advanceTimeTo,
buildProposal,
bridgeInbound,
controller,
flushInboundQueue,
evalProposal,
getCrankNumber,
getOutboundMessages,
Expand Down

0 comments on commit bb0683a

Please sign in to comment.