Skip to content

Commit

Permalink
feat(orch): packet affordances for LocalOrchestrationAccount (#9820)
Browse files Browse the repository at this point in the history
closes: #9783

## Description

Introduce a per-LOA `packetTools` to grant powers used by IBC-specific `makeIBCReplyKit` and `makeIBCTransferSender` which enables a `localOrchestrationAccount` to send a packet and wait for a corresponding reply packet (such as IBC acknowledgement or IBC timeout) in the context of a single localChainAccount address.

The most important files are `agoric-sdk/packages/orchestration/src/exos/packet-tools.js` and `ibc-packet.js` in the same directory.

### Security Considerations

This change is the next layer wrapping localchain and vtransfer powers, but in an attenuated fashion.  They don't introduce more powers than are already available via `LOA.monitorTransfers(...)`.

### Scaling Considerations

More message exchanges, but most of those are caused by inbound Cosmos transactions, so they should not consume much CPU per block.

### Documentation Considerations

New API surface.  Existing data and deployments will not be affected, especially since the orchestration vat+API has not yet landed on chain.

### Testing Considerations

Unit and bootstrap tests have been implemented.  The `agoric-sdk/packages/boot/test/supports.ts` fake bridge has been reorganised to greater mimic the actual chain and make tests pass.

### Upgrade Considerations

Some more Exos shipped with the Orch API, with the usual considerations of adding new Exo singletons and classKits and maintaining them.
  • Loading branch information
mergify[bot] authored Aug 5, 2024
2 parents 369fa7c + 0df9f82 commit ddf71d7
Show file tree
Hide file tree
Showing 18 changed files with 969 additions and 210 deletions.
7 changes: 4 additions & 3 deletions packages/boot/test/orchestration/restart-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ test.before(async t => {
});
test.after.always(t => t.context.shutdown?.());

// Not interesting because it doesn't wait on other chains. Leaving here because maybe it will before it's done.
test.serial('sendAnywhere', async t => {
// FIXME the test needs to be able to send the acknowledgementPacket ack
// so that the transfer vow resolves.
test.serial.failing('sendAnywhere', async t => {
const {
walletFactoryDriver,
buildProposal,
Expand Down Expand Up @@ -109,7 +110,7 @@ const hasResult = (r: UpdateRecord) => {
};

// Tests restart but not of an orchestration() flow
test('stakeAtom', async t => {
test.serial('stakeAtom', async t => {
const {
buildProposal,
evalProposal,
Expand Down
214 changes: 109 additions & 105 deletions packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,130 +382,134 @@ export const makeSwingsetTestKit = async (
switch (bridgeId) {
case BridgeId.BANK: {
trace(
'bridgeOutbound BANK',
'bridgeOutbound bank',
obj.type,
obj.recipient,
obj.amount,
obj.denom,
);
break;
}
case BridgeId.STORAGE:
return storage.toStorage(obj);
case BridgeId.PROVISION:
case BridgeId.PROVISION_SMART_WALLET:
case BridgeId.WALLET:
console.warn('Bridge returning undefined for', bridgeId, ':', obj);
return undefined;
default:
break;
}

const bridgeTargetRegistered = new Set();
const bridgeType = `${bridgeId}:${obj.type}`;
switch (bridgeType) {
case `${BridgeId.BANK}:VBANK_GET_MODULE_ACCOUNT_ADDRESS`: {
// bridgeOutbound bank : {
// moduleName: 'vbank/reserve',
// type: 'VBANK_GET_MODULE_ACCOUNT_ADDRESS'
// }
switch (obj.type) {
case 'VBANK_GET_MODULE_ACCOUNT_ADDRESS': {
const { moduleName } = obj;
const moduleDescriptor = Object.values(VBankAccount).find(
({ module }) => module === moduleName,
);
if (!moduleDescriptor) {
return 'undefined';
}
return moduleDescriptor.address;
}

// Observed message:
// address: 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346',
// denom: 'ibc/toyatom',
// type: 'VBANK_GET_BALANCE'
case 'VBANK_GET_BALANCE': {
// TODO consider letting config specify vbank assets
// empty balances for test.
return '0';
}
const { moduleName } = obj;
const moduleDescriptor = Object.values(VBankAccount).find(
({ module }) => module === moduleName,
);
if (!moduleDescriptor) {
return 'undefined';
}
return moduleDescriptor.address;
}

case 'VBANK_GRAB':
case 'VBANK_GIVE': {
lastNonce += 1n;
// Also empty balances.
return harden({
type: 'VBANK_BALANCE_UPDATE',
nonce: `${lastNonce}`,
updated: [],
});
}
// Observed message:
// address: 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346',
// denom: 'ibc/toyatom',
// type: 'VBANK_GET_BALANCE'
case `${BridgeId.BANK}:VBANK_GET_BALANCE`: {
// TODO consider letting config specify vbank assets
// empty balances for test.
return '0';
}

default: {
return 'undefined';
}
}
case `${BridgeId.BANK}:VBANK_GRAB`:
case `${BridgeId.BANK}:VBANK_GIVE`: {
lastNonce += 1n;
// Also empty balances.
return harden({
type: 'VBANK_BALANCE_UPDATE',
nonce: `${lastNonce}`,
updated: [],
});
}
case BridgeId.CORE:
case BridgeId.DIBC:
switch (obj.type) {
case 'IBC_METHOD':
switch (obj.method) {
case 'startChannelOpenInit':
pushInbound(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
return undefined;
case 'sendPacket':
switch (obj.packet.data) {
case protoMsgMocks.delegate.msg: {
return ackLater(obj, protoMsgMocks.delegate.ack);
}
case protoMsgMocks.delegateWithOpts.msg: {
return ackLater(obj, protoMsgMocks.delegateWithOpts.ack);
}
case protoMsgMocks.queryBalance.msg: {
return ackLater(obj, protoMsgMocks.queryBalance.ack);
}
case protoMsgMocks.queryUnknownPath.msg: {
return ackLater(obj, protoMsgMocks.queryUnknownPath.ack);
}
case protoMsgMocks.queryBalanceMulti.msg: {
return ackLater(obj, protoMsgMocks.queryBalanceMulti.ack);
}
case protoMsgMocks.queryBalanceUnknownDenom.msg: {
return ackLater(
obj,
protoMsgMocks.queryBalanceUnknownDenom.ack,
);
}
default: {
// An error that would be triggered before reception on another chain
return ackImmediately(obj, protoMsgMocks.error.ack);
}
}
default:
return undefined;

case `${BridgeId.CORE}:IBC_METHOD`:
case `${BridgeId.DIBC}:IBC_METHOD`:
case `${BridgeId.VTRANSFER}:IBC_METHOD`: {
switch (obj.method) {
case 'startChannelOpenInit':
pushInbound(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
return undefined;
case 'sendPacket':
switch (obj.packet.data) {
case protoMsgMocks.delegate.msg: {
return ackLater(obj, protoMsgMocks.delegate.ack);
}
case protoMsgMocks.delegateWithOpts.msg: {
return ackLater(obj, protoMsgMocks.delegateWithOpts.ack);
}
case protoMsgMocks.queryBalance.msg: {
return ackLater(obj, protoMsgMocks.queryBalance.ack);
}
case protoMsgMocks.queryUnknownPath.msg: {
return ackLater(obj, protoMsgMocks.queryUnknownPath.ack);
}
case protoMsgMocks.queryBalanceMulti.msg: {
return ackLater(obj, protoMsgMocks.queryBalanceMulti.ack);
}
case protoMsgMocks.queryBalanceUnknownDenom.msg: {
return ackLater(
obj,
protoMsgMocks.queryBalanceUnknownDenom.ack,
);
}
default: {
// An error that would be triggered before reception on another chain
return ackImmediately(obj, protoMsgMocks.error.ack);
}
}
default:
return undefined;
}
case BridgeId.PROVISION:
case BridgeId.PROVISION_SMART_WALLET:
case BridgeId.VTRANSFER:
case BridgeId.WALLET:
console.warn('Bridge returning undefined for', bridgeId, ':', obj);
}
case `${BridgeId.VTRANSFER}:BRIDGE_TARGET_REGISTER`: {
bridgeTargetRegistered.add(obj.target);
return undefined;
case BridgeId.STORAGE:
return storage.toStorage(obj);
case BridgeId.VLOCALCHAIN:
switch (obj.type) {
case 'VLOCALCHAIN_ALLOCATE_ADDRESS':
return 'agoric1mockVlocalchainAddress';
case 'VLOCALCHAIN_EXECUTE_TX': {
return obj.messages.map(message => {
switch (message['@type']) {
case '/cosmos.staking.v1beta1.MsgDelegate': {
if (message.amount.amount === '504') {
// FIXME - how can we propagate the error?
// this results in `syscall.callNow failed: device.invoke failed, see logs for details`
throw Error('simulated packet timeout');
}
return {} as JsonSafe<MsgDelegateResponse>;
}
// returns one empty object per message unless specified
default:
return {};
}
case `${BridgeId.VTRANSFER}:BRIDGE_TARGET_UNREGISTER`: {
bridgeTargetRegistered.delete(obj.target);
return undefined;
}
case `${BridgeId.VLOCALCHAIN}:VLOCALCHAIN_ALLOCATE_ADDRESS`: {
return 'agoric1mockVlocalchainAddress';
}
case `${BridgeId.VLOCALCHAIN}:VLOCALCHAIN_EXECUTE_TX`: {
return obj.messages.map(message => {
switch (message['@type']) {
case '/cosmos.staking.v1beta1.MsgDelegate': {
if (message.amount.amount === '504') {
// FIXME - how can we propagate the error?
// this results in `syscall.callNow failed: device.invoke failed, see logs for details`
throw Error('simulated packet timeout');
}
});
return {} as JsonSafe<MsgDelegateResponse>;
}
// returns one empty object per message unless specified
default:
return {};
}
default:
throw Error(`VLOCALCHAIN message of unknown type ${obj.type}`);
}
default:
throw Error(`unknown bridgeId ${bridgeId}`);
});
}
default: {
throw Error(`FIXME missing support for ${bridgeId}: ${obj.type}`);
}
}
};

Expand Down
Loading

0 comments on commit ddf71d7

Please sign in to comment.