Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create chain account (ica) #9114

Merged
merged 4 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion packages/boot/test/bootstrapTests/test-orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { TestFn } from 'ava';
import { Fail } from '@agoric/assert';
import type { start as stakeBldStart } from '@agoric/orchestration/src/contracts/stakeBld.contract.js';
import type { Instance } from '@agoric/zoe/src/zoeService/utils.js';
import { M, matches } from '@endo/patterns';
import { makeWalletFactoryContext } from './walletFactory.ts';

type DefaultTestContext = Awaited<ReturnType<typeof makeWalletFactoryContext>>;
Expand All @@ -14,7 +15,7 @@ const test: TestFn<DefaultTestContext> = anyTest;
test.before(async t => (t.context = await makeWalletFactoryContext(t)));
test.after.always(t => t.context.shutdown?.());

test('stakeBld', async t => {
test.serial('stakeBld', async t => {
const {
agoricNamesRemotes,
buildProposal,
Expand Down Expand Up @@ -86,3 +87,37 @@ test('stakeBld', async t => {
},
});
});

test.serial('stakeAtom', async t => {
const {
buildProposal,
evalProposal,
runUtils: { EV },
} = t.context;
// TODO move into a vm-config for u15
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-network.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-orchestration.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/orchestration/init-stakeAtom.js'),
);

const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames');
const instance = await EV(agoricNames).lookup('instance', 'stakeAtom');
t.truthy(instance, 'stakeAtom instance is available');

const zoe = await EV.vat('bootstrap').consumeItem('zoe');
const publicFacet = await EV(zoe).getPublicFacet(instance);
t.truthy(publicFacet, 'stakeAtom publicFacet is available');

const account = await EV(publicFacet).createAccount();
t.log('account', account);
t.truthy(account, 'createAccount returns an account on ATOM connection');
t.truthy(
matches(account, M.remotable('ChainAccount')),
'account is a remotable',
);
});
89 changes: 89 additions & 0 deletions packages/boot/test/bootstrapTests/test-vat-orchestration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import type { ExecutionContext, TestFn } from 'ava';
import { M, matches } from '@endo/patterns';
import { makeWalletFactoryContext } from './walletFactory.ts';

const makeTestContext = async (t: ExecutionContext) =>
makeWalletFactoryContext(t);

type DefaultTestContext = Awaited<ReturnType<typeof makeTestContext>>;
const test: TestFn<DefaultTestContext> = anyTest;

test.before(async t => {
t.context = await makeTestContext(t);

async function setupDeps() {
const {
buildProposal,
evalProposal,
runUtils: { EV },
} = t.context;
/** ensure network, ibc, and orchestration are available */
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-network.js'),
);
await evalProposal(
buildProposal('@agoric/builders/scripts/vats/init-orchestration.js'),
);
const vatStore = await EV.vat('bootstrap').consumeItem('vatStore');
t.true(await EV(vatStore).has('ibc'), 'ibc');
t.true(await EV(vatStore).has('network'), 'network');
t.true(await EV(vatStore).has('orchestration'), 'orchestration');
}

await setupDeps();
});

test.after.always(t => t.context.shutdown?.());

test('createAccount returns an ICA connection', async t => {
const {
runUtils: { EV },
} = t.context;

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

const account = await EV(orchestration).createAccount(
'connection-0',
'connection-0',
);
t.truthy(account, 'createAccount returns an account');
t.truthy(
matches(account, M.remotable('ChainAccount')),
'account is a remotable',
);
const [remoteAddress, localAddress, accountAddress, port] = await Promise.all(
[
EV(account).getRemoteAddress(),
EV(account).getLocalAddress(),
EV(account).getAccountAddress(),
EV(account).getPort(),
],
);
t.regex(remoteAddress, /icahost/);
t.regex(localAddress, /icacontroller/);
t.regex(accountAddress, /osmo1/);
t.truthy(matches(port, M.remotable('Port')));
t.log('ICA Account Addresses', {
remoteAddress,
localAddress,
accountAddress,
});
});

test('ICA connection can be closed', async t => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important that closing a connection frees up resources? If so, is it feasible to check that?

I can imagine it might not be feasible... that any relevant collections are weak / non-enumerable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are good questions. I created #9192 to track

const {
runUtils: { EV },
} = t.context;

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

const account = await EV(orchestration).createAccount(
'connection-0',
'connection-0',
);
t.truthy(account, 'createAccount returns an account');

const res = await EV(account).close();
t.is(res, 'Connection closed');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about trying to use it after it's closed and checking that it throws?

});
113 changes: 113 additions & 0 deletions packages/boot/tools/ibc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

# ICS-27 Interchain Accounts

Sequence diagrams for Interchain Accounts based on [ics-027-interchain-accounts/README.md#9ffb1d2](https://github.com/cosmos/ibc/blob/9ffb1d26d3018b6efda546189ec7e43d56d23da3/spec/app/ics-027-interchain-accounts/README.md).

### IBC Connection Creation

_Prerequisite to creating a channel._

```mermaid
sequenceDiagram
participant CC as Chain A
participant R as Relayer
participant HC as Chain B

CC->>R: ConnOpenInit(ClientId, CounterpartyClientId, Version)
R->>HC: ConnOpenTry(ClientId, CounterpartyClientId, Version, CounterpartyVersion)
HC-->>R: ConnOpenTry(Version)
R->>CC: ConnOpenAck(ConnectionId, Version)
CC-->>R: ConnOpenAck
R->>HC: ConnOpenConfirm(ConnectionId)
```

Mock Testing:
- on `ConnOpenInit`, return `ConnOpenAck`

### ICA Channel Creation

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain

CC->>CC: RegisterInterchainAccount()
CC->>R: ChannelOpenInit(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
R->>HC: ChannelOpenTry(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
HC->>HC: RegisterInterchainAccount(CounterpartyPortIdentifier)
HC-->>R: ChannelOpenTry(Version)
R->>CC: ChannelOpenAck(PortIdentifier, ChannelIdentifier, CounterpartyVersion)
CC->>CC: SetInterchainAccountAddress(PortID, Address)
CC->>CC: SetActiveChannelID(PortIdentifier, ConnectionID, ChannelIdentifier)
CC-->>R: ChannelOpenAck
R->>HC: ChannelOpenConfirm(PortIdentifier, ChannelIdentifier)
HC->>HC: SetActiveChannelID(CounterpartyPortIdentifier, ConnectionID, ChannelIdentifier)
```

Mock Testing:
- on `ChannelOpenInit`, return `ChannelOpenAck`

### ICA Transaction

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain

CC->>R: SendPacket(PacketData)
R->>HC: onRecvPacket(Packet)
HC->>HC: Deserialize and validate packet data
alt Successful deserialization and validation
HC->>HC: AuthenticateTx(msgs)
HC->>HC: ExecuteTx(msgs)
HC-->>R: Acknowledgement(result)
else Error
HC-->>R: ErrorAcknowledgement(error)
end
R->>CC: onAcknowledgePacket(Packet, Acknowledgement)
CC->>CC: Handle acknowledgement
```

Mock Testing:
- on `SendPacket`, return `onAcknowledgePacket`


### ICA Channel Reactivation

```mermaid
sequenceDiagram
participant CC as Controller Chain
participant R as Relayer
participant HC as Host Chain

Note over CC,HC: Existing ICA channel expires or closes
CC->>CC: Channel closes or times out
CC->>R: ChannelOpenInit(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
Note right of CC: Reusing the same PortIdentifier and ConnectionID
R->>HC: ChannelOpenTry(Order, ConnectionHops, PortIdentifier, ChannelIdentifier, Version)
HC->>HC: Verify PortIdentifier and ConnectionID match the previous active channel
HC->>HC: Verify the channel is in CLOSED state
HC->>HC: Verify the new channel has the same order and version as the previous channel
HC-->>R: ChannelOpenTry(Version)
R->>CC: ChannelOpenAck(PortIdentifier, ChannelIdentifier, CounterpartyVersion)
CC->>CC: Verify the CounterpartyVersion matches the previous active channel
CC->>CC: SetActiveChannelID(PortIdentifier, ConnectionID, ChannelIdentifier)
CC-->>R: ChannelOpenAck
R->>HC: ChannelOpenConfirm(PortIdentifier, ChannelIdentifier)
HC->>HC: SetActiveChannelID(CounterpartyPortIdentifier, ConnectionID, ChannelIdentifier)
```

Mock Testing:
- on `ChannelOpenInit`, return `ChannelOpenAck`
- n.b. testing should verify `SetActiveChannelID` flow on CC side


### Testing Mocks Summary

- IBC Connection Creation: on ConnOpenInit, return ConnOpenAck
- ICA Channel Creation: on ChannelOpenInit, return ChannelOpenAck
- ICA Transaction: on SendPacket, return onAcknowledgePacket
- ICA Channel Reactivation: on ChannelOpenInit, return ChannelOpenAck
- testing should verify SetActiveChannelID flow on CC side
28 changes: 28 additions & 0 deletions packages/boot/tools/ibc/mocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* mock bridgeInbound events for ICA (ICS-27) flow
* see [./ics27-1.md](./ics27-1.md) for more details
*
* sourced from [@agoric/vats/test/test-network.js](https://github.com/Agoric/agoric-sdk/blob/4601a1ab65a0c36fbfbfcc1fa59e83ee10a1c996/packages/vats/test/test-network.js)
* and end e2e testing with sim chains (v16: IBC fromBridge logs)
*/
export const icaMocks = {
startChannelOpenInit: {
// ICA Channel Creation
channelOpenAck: obj => ({
type: 'IBC_EVENT',
blockHeight: 99,
blockTime: 1711571357,
event: 'channelOpenAck',
portID: obj.packet.source_port,
channelID: 'channel-0',
counterparty: {
port_id: obj.packet.destination_port,
channel_id: 'channel-1',
},
counterpartyVersion:
'{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-0","address":"osmo1234","encoding":"proto3","txType":"sdk_multi_msg"}',
connectionHops: obj.hops,
}),
// XXX channelOpenAckFailure
},
};
22 changes: 21 additions & 1 deletion packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { ExecutionContext as AvaT } from 'ava';
import { makeRunUtils } from '@agoric/swingset-vat/tools/run-utils.js';
import type { CoreEvalSDKType } from '@agoric/cosmic-proto/swingset/swingset.js';
import type { BridgeHandler } from '@agoric/vats';
import { icaMocks } from './ibc/mocks.js';

const trace = makeTracer('BSTSupport', false);

Expand Down Expand Up @@ -289,6 +290,7 @@ export const makeSwingsetTestKit = async (

const outboundMessages = new Map();

let inbound;
/**
* Mock the bridge outbound handler. The real one is implemented in Golang so
* changes there will sometimes require changes here.
Expand Down Expand Up @@ -356,6 +358,21 @@ export const makeSwingsetTestKit = async (
}
case BridgeId.CORE:
case BridgeId.DIBC:
switch (obj.type) {
case 'IBC_METHOD':
switch (obj.method) {
case 'startChannelOpenInit':
inbound(
BridgeId.DIBC,
icaMocks.startChannelOpenInit.channelOpenAck(obj),
);
return undefined;
default:
return undefined;
}
default:
return undefined;
}
case BridgeId.PROVISION:
case BridgeId.PROVISION_SMART_WALLET:
case BridgeId.VTRANSFER:
Expand Down Expand Up @@ -390,7 +407,7 @@ export const makeSwingsetTestKit = async (
},
});
}
const { controller, timer } = await buildSwingset(
const { controller, timer, bridgeInbound } = await buildSwingset(
new Map(),
bridgeOutbound,
kernelStorage,
Expand All @@ -406,6 +423,8 @@ export const makeSwingsetTestKit = async (
debugVats,
},
);
inbound = bridgeInbound;

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

const runUtils = makeRunUtils(controller);
Expand Down Expand Up @@ -491,6 +510,7 @@ export const makeSwingsetTestKit = async (
advanceTimeBy,
advanceTimeTo,
buildProposal,
bridgeInbound,
controller,
evalProposal,
getCrankNumber,
Expand Down
34 changes: 34 additions & 0 deletions packages/builders/scripts/orchestration/init-stakeAtom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async (
{ publishRef, install },
options = {},
) => {
const {
hostConnectionId = 'connection-1',
controllerConnectionId = 'connection-0',
} = options;
return harden({
sourceSpec: '@agoric/orchestration/src/proposals/start-stakeAtom.js',
getManifestCall: [
'getManifestForStakeAtom',
{
installKeys: {
stakeAtom: publishRef(
install(
'@agoric/orchestration/src/contracts/stakeAtom.contract.js',
),
),
},
hostConnectionId,
controllerConnectionId,
},
],
});
};

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('start-stakeAtom', defaultProposalBuilder);
};
Loading
Loading