Skip to content

Commit

Permalink
Merge pull request #9086 from Agoric/ta/localchain-queryMany
Browse files Browse the repository at this point in the history
localchain queryMany and bankManager
  • Loading branch information
mergify[bot] authored Mar 14, 2024
2 parents 65d19fe + 5b789c5 commit accf269
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 52 deletions.
2 changes: 1 addition & 1 deletion golang/cosmos/x/vlocalchain/vlocalchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (h portHandler) Receive(cctx context.Context, str string) (ret string, err
}
ret = string(bz)

case "VLOCALCHAIN_QUERY":
case "VLOCALCHAIN_QUERY_MANY":
// Copy the JSON messages string into a CosmosTx object so we can
// deserialize it with just proto3 JSON.
cosmosTxBz := []byte(`{"messages":` + string(msg.Messages) + `}`)
Expand Down
2 changes: 1 addition & 1 deletion golang/cosmos/x/vlocalchain/vlocalchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func TestQuery(t *testing.T) {
tc := tc
ctx := sdk.UnwrapSDKContext(cctx)
t.Run(tc.name, func(t *testing.T) {
msgGetBalances := `{"type":"VLOCALCHAIN_QUERY","messages":[{"@type":"/cosmos.bank.v1beta1.QueryAllBalancesRequest","address":"` + tc.addr + `"}]}`
msgGetBalances := `{"type":"VLOCALCHAIN_QUERY_MANY","messages":[{"@type":"/cosmos.bank.v1beta1.QueryAllBalancesRequest","address":"` + tc.addr + `"}]}`
t.Logf("query request: %v", msgGetBalances)
ret, err := handler.Receive(cctx, msgGetBalances)
t.Logf("query response: %v", ret)
Expand Down
188 changes: 145 additions & 43 deletions packages/vats/src/localchain.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
// @ts-check
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { AmountShape } from '@agoric/ertp';

const { Fail } = assert;
const { Fail, bare } = assert;

/**
* @typedef {{
* '@type': string;
* [x: string]: unknown;
* }} Proto3Jsonable
*/

/**
* @typedef {{
* system: import('./types.js').ScopedBridgeManager;
* bankManager: import('./vat-bank.js').BankManager;
* }} LocalChainPowers
*
* @typedef {MapStore<
* keyof LocalChainPowers,
* LocalChainPowers[keyof LocalChainPowers]
* >} PowerStore
*/

/**
* @template {keyof LocalChainPowers} K
* @param {PowerStore} powers
* @param {K} name
*/
const getPower = (powers, name) => {
powers.has(name) || Fail`need powers.${bare(name)} for this method`;
return /** @type {LocalChainPowers[K]} */ (powers.get(name));
};

export const LocalChainAccountI = M.interface('LocalChainAccount', {
getAddress: M.callWhen().returns(M.string()),
deposit: M.callWhen(M.remotable('Payment'))
.optional(M.pattern())
.returns(AmountShape),
executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())),
// An example of a high-level query. TODO: Move this functionality to a higher
// layer that implements the Orchestration API.
allBalances: M.callWhen().returns(M.record()),
});

/** @param {import('@agoric/base-zone').Zone} zone */
Expand All @@ -17,32 +48,34 @@ const prepareLocalChainAccount = zone =>
'LocalChainAccount',
LocalChainAccountI,
/**
* @param {import('./types.js').ScopedBridgeManager} system
* @param {string} address
* @param {{ query: (messages: any[]) => Promise<any> }} chain
* @param {PowerStore} powers
*/
(system, address, chain) => ({ system, address, chain }),
(address, powers) => ({ address, powers }),
{
// Information that the account creator needs.
async getAddress() {
return this.state.address;
},
async allBalances() {
// We make a balance request, scoped to our own address.
const { address, chain } = this.state;
const res = await chain.query([
{
'@type': '/cosmos.bank.v1beta1.QueryAllBalancesRequest',
address,
},
]);
if (res[0].error) {
throw Fail`query failed: ${res[0].error}`;
}
return res[0].reply;
/**
* Deposit a payment into the bank purse that matches the alleged brand.
* This is safe, since even if the payment lies about its brand, ERTP will
* reject spoofed payment objects when depositing into a purse.
*
* @param {Payment} payment
*/
async deposit(payment) {
const { address, powers } = this.state;

const bankManager = getPower(powers, 'bankManager');

const allegedBrand = await E(payment).getAllegedBrand();
const bankAcct = E(bankManager).getBankForAddress(address);
const allegedPurse = E(bankAcct).getPurse(allegedBrand);
return E(allegedPurse).deposit(payment);
},
async executeTx(messages) {
const { system, address } = this.state;
const { address, powers } = this.state;
const obj = {
type: 'VLOCALCHAIN_EXECUTE_TX',
// This address is the only one that `VLOCALCHAIN_EXECUTE_TX` will
Expand All @@ -51,44 +84,112 @@ const prepareLocalChainAccount = zone =>
address,
messages,
};
const system = getPower(powers, 'system');
return E(system).toBridge(obj);
},
},
);

export const LocalChainI = M.interface('LocalChain', {
createAccount: M.callWhen().returns(M.remotable('LocalChainAccount')),
query: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())),
query: M.callWhen(M.record()).returns(M.record()),
queryMany: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())),
});

export const LocalChainAdminI = M.interface('LocalChainAdmin', {
setPower: M.callWhen(M.string(), M.await(M.any())).returns(),
});

/**
* @param {import('@agoric/base-zone').Zone} zone
* @param {ReturnType<typeof prepareLocalChainAccount>} createAccount
*/
const prepareLocalChain = (zone, createAccount) =>
zone.exoClass(
zone.exoClassKit(
'LocalChain',
LocalChainI,
/** @param {import('./types.js').ScopedBridgeManager} system */
system => ({ system }),
{ public: LocalChainI, admin: LocalChainAdminI },
/** @param {Partial<LocalChainPowers>} [initialPowers] */
initialPowers => {
/** @type {PowerStore} */
const powers = zone.detached().mapStore('PowerStore');
if (initialPowers) {
for (const [name, power] of Object.entries(initialPowers)) {
powers.init(/** @type {keyof LocalChainPowers} */ (name), power);
}
}
return { powers };
},
{
async createAccount() {
const { system } = this.state;
// Allocate a fresh address that doesn't correspond with a public key,
// and follows the ICA guidelines to help reduce collisions. See
// x/vlocalchain/keeper/keeper.go AllocateAddress for the use of the app
// hash and block data hash.
const address = await E(system).toBridge({
type: 'VLOCALCHAIN_ALLOCATE_ADDRESS',
});
return createAccount(system, address, this.self);
admin: {
/**
* @template {keyof LocalChainPowers} K
* @param {K} name
* @param {LocalChainPowers[K]} [power]
*/
setPower(name, power) {
const { powers } = this.state;
if (power === undefined) {
// Remove from powers.
powers.delete(name);
} else if (powers.has(name)) {
// Replace an existing power.
powers.set(name, power);
} else {
// Add a new power.
powers.init(name, power);
}
},
},
async query(messages) {
const { system } = this.state;
return E(system).toBridge({
type: 'VLOCALCHAIN_QUERY',
messages,
});
public: {
/**
* Allocate a fresh address that doesn't correspond with a public key,
* and follows the ICA guidelines to help reduce collisions. See
* x/vlocalchain/keeper/keeper.go AllocateAddress for the use of the app
* hash and block data hash.
*/
async createAccount() {
const { powers } = this.state;
const system = getPower(powers, 'system');
const address = await E(system).toBridge({
type: 'VLOCALCHAIN_ALLOCATE_ADDRESS',
});
return createAccount(address, powers);
},
/**
* Make a single query to the local chain. Will reject with an error if
* the query fails. Otherwise, return the response as a JSON-compatible
* object.
*
* @param {Proto3Jsonable} request
* @returns {Promise<Proto3Jsonable>}
*/
async query(request) {
const requests = harden([request]);
const results = await E(this.facets.public).queryMany(requests);
results.length === 1 ||
Fail`expected exactly one result; got ${results}`;
const { error, reply } = results[0];
if (error) {
throw Fail`query failed: ${error}`;
}
return reply;
},
/**
* Send a batch of query requests to the local chain. Unless there is a
* system error, will return all results to indicate their success or
* failure.
*
* @param {Proto3Jsonable[]} requests
* @returns {Promise<{ error?: string; reply: Proto3Jsonable }[]>}
*/
async queryMany(requests) {
const { powers } = this.state;
const system = getPower(powers, 'system');
return E(system).toBridge({
type: 'VLOCALCHAIN_QUERY_MANY',
messages: requests,
});
},
},
},
);
Expand All @@ -103,4 +204,5 @@ export const prepareLocalChainTools = zone => {
harden(prepareLocalChainTools);

/** @typedef {ReturnType<typeof prepareLocalChainTools>} LocalChainTools */
/** @typedef {ReturnType<LocalChainTools['makeLocalChain']>} LocalChain */
/** @typedef {ReturnType<LocalChainTools['makeLocalChain']>} LocalChainKit */
/** @typedef {LocalChainKit['public']} LocalChain */
40 changes: 38 additions & 2 deletions packages/vats/src/proposals/localchain-proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { BridgeId as BRIDGE_ID } from '@agoric/internal';
* loadCriticalVat: VatLoader<any>;
* bridgeManager: import('../types').BridgeManager;
* localchainBridgeManager: import('../types').ScopedBridgeManager;
* bankManager: Promise<import('../vat-bank.js').BankManager>;
* };
* produce: {
* localchain: Producer<any>;
* localchainAdmin: Producer<any>;
* localchainVat: Producer<any>;
* localchainBridgeManager: Producer<any>;
* };
Expand All @@ -28,8 +30,14 @@ export const setupLocalChainVat = async (
loadCriticalVat,
bridgeManager: bridgeManagerP,
localchainBridgeManager: localchainBridgeManagerP,
bankManager,
},
produce: {
localchainVat,
localchain,
localchainAdmin: localchainAdminP,
localchainBridgeManager,
},
produce: { localchainVat, localchain, localchainBridgeManager },
},
options,
) => {
Expand Down Expand Up @@ -66,9 +74,34 @@ export const setupLocalChainVat = async (
);
}

const newLocalChain = await E(vats.localchain).makeLocalChain(scopedManager);
const { admin: localChainAdmin, public: newLocalChain } = await E(
vats.localchain,
).makeLocalChain({
system: scopedManager,
});

localchain.reset();
localchain.resolve(newLocalChain);
localchainAdminP.reset();
localchainAdminP.resolve(localChainAdmin);

/** @type {Record<string, Promise<void>>} */
const descToPromise = {
'bank manager power': bankManager.then(bm =>
E(localChainAdmin).setPower('bankManager', bm),
),
};
void Promise.all(
Object.entries(descToPromise).map(([desc, p]) =>
p
.then(() =>
console.info(`Completed configuration of localchain with ${desc}`),
)
.catch(e =>
console.error(`Failed to configure localchain with ${desc}:`, e),
),
),
);
};

/**
Expand All @@ -93,9 +126,12 @@ export const getManifestForLocalChain = (_powers, { localchainRef }) => ({
loadCriticalVat: true,
bridgeManager: 'bridge',
localchainBridgeManager: 'localchain',
bankManager: 'bank',
transferMiddleware: 'transfer',
},
produce: {
localchain: 'localchain',
localchainAdmin: 'localchain',
localchainVat: 'localchain',
localchainBridgeManager: 'localchain',
},
Expand Down
7 changes: 5 additions & 2 deletions packages/vats/src/proposals/localchain-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export const testLocalChain = async (
console.info('created account', lca);
const address = await E(lca).getAddress();
console.info('address', address);
const balances = await E(lca).allBalances();
const balances = await E(localchain).query({
'@type': '/cosmos.bank.v1beta1.QueryAllBalancesRequest',
address,
});
console.info('balances', balances);
await E(lca)
.executeTx([
Expand All @@ -53,7 +56,7 @@ export const testLocalChain = async (
},
);

const emptyQuery = await E(localchain).query([]);
const emptyQuery = await E(localchain).queryMany([]);
console.info('emptyQuery', emptyQuery);

result = { success: true };
Expand Down
1 change: 1 addition & 0 deletions packages/vats/src/vat-bank.js
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ const prepareBankManager = (
);
return makeBankManager;
};
/** @typedef {ReturnType<ReturnType<typeof prepareBankManager>>} BankManager */

/** @param {MapStore<string, any>} baggage */
const prepareFromBaggage = baggage => {
Expand Down
6 changes: 3 additions & 3 deletions packages/vats/src/vat-localchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export const buildRootObject = (_vatPowers, _args, baggage) => {
* Create a local chain that allows permissionlessly making fresh local
* chain accounts, then using them to send chain queries and transactions.
*
* @param {import('./types').ScopedBridgeManager} system
* @param {Partial<import('./localchain.js').LocalChainPowers>} [initialPowers]
*/
makeLocalChain(system) {
return makeLocalChain(system);
makeLocalChain(initialPowers = {}) {
return makeLocalChain(initialPowers);
},
});
};
Expand Down

0 comments on commit accf269

Please sign in to comment.