-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(pegasus): more POLA and less global state
- Loading branch information
1 parent
c703d07
commit e7ea320
Showing
5 changed files
with
470 additions
and
399 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { details as X } from '@agoric/assert'; | ||
|
||
import { AmountMath } from '@agoric/ertp'; | ||
import { E } from '@agoric/eventual-send'; | ||
import { Far } from '@agoric/marshal'; | ||
import { makeOncePromiseKit } from './once-promise-kit.js'; | ||
|
||
/** | ||
* Create or return an existing courier promise kit. | ||
* | ||
* @template K | ||
* @param {K} key | ||
* @param {Store<K, PromiseRecord<Courier>>} keyToCourierPK | ||
*/ | ||
export const getCourierPK = (key, keyToCourierPK) => { | ||
if (keyToCourierPK.has(key)) { | ||
return keyToCourierPK.get(key); | ||
} | ||
|
||
// This is the first packet for this denomination. | ||
// Create a new Courier promise kit for it. | ||
const courierPK = makeOncePromiseKit(() => X`${key} already pegged`); | ||
|
||
keyToCourierPK.init(key, courierPK); | ||
return courierPK; | ||
}; | ||
|
||
/** | ||
* Create the [send, receive] pair. | ||
* | ||
* @typedef {Object} CourierArgs | ||
* @property {ContractFacet} zcf | ||
* @property {ERef<BoardDepositFacet>} board | ||
* @property {ERef<NameHub>} namesByAddress | ||
* @property {Denom} remoteDenom | ||
* @property {Brand} localBrand | ||
* @property {(zcfSeat: ZCFSeat, amounts: AmountKeywordRecord) => void} retain | ||
* @property {(zcfSeat: ZCFSeat, amounts: AmountKeywordRecord) => void} redeem | ||
* @property {ERef<TransferProtocol>} transferProtocol | ||
* @param {ERef<Connection>} connection | ||
* @returns {(args: CourierArgs) => Courier} | ||
*/ | ||
export const makeCourierMaker = connection => ({ | ||
zcf, | ||
board, | ||
namesByAddress, | ||
remoteDenom, | ||
localBrand, | ||
retain, | ||
redeem, | ||
transferProtocol, | ||
}) => { | ||
/** @type {Sender} */ | ||
const send = async (zcfSeat, depositAddress) => { | ||
const tryToSend = async () => { | ||
const amount = zcfSeat.getAmountAllocated('Transfer', localBrand); | ||
const transferPacket = await E(transferProtocol).makeTransferPacket({ | ||
value: amount.value, | ||
remoteDenom, | ||
depositAddress, | ||
}); | ||
|
||
// Retain the payment. We must not proceed on failure. | ||
retain(zcfSeat, { Transfer: amount }); | ||
|
||
// The payment is already escrowed, and proposed to retain, so try sending. | ||
return E(connection) | ||
.send(transferPacket) | ||
.then(ack => E(transferProtocol).assertTransferPacketAck(ack)) | ||
.then( | ||
_ => zcfSeat.exit(), | ||
reason => { | ||
// Return the payment to the seat, if possible. | ||
redeem(zcfSeat, { Transfer: amount }); | ||
throw reason; | ||
}, | ||
); | ||
}; | ||
|
||
// Reflect any error back to the seat. | ||
return tryToSend().catch(reason => { | ||
zcfSeat.fail(reason); | ||
}); | ||
}; | ||
|
||
/** @type {Receiver} */ | ||
const receive = async ({ value, depositAddress }) => { | ||
const localAmount = AmountMath.make(localBrand, value); | ||
|
||
// Look up the deposit facet for this board address, if there is one. | ||
/** @type {DepositFacet} */ | ||
const depositFacet = await E(board) | ||
.getValue(depositAddress) | ||
.catch(_ => E(namesByAddress).lookup(depositAddress, 'depositFacet')); | ||
|
||
const { userSeat, zcfSeat } = zcf.makeEmptySeatKit(); | ||
|
||
// Redeem the backing payment. | ||
try { | ||
redeem(zcfSeat, { Transfer: localAmount }); | ||
zcfSeat.exit(); | ||
} catch (e) { | ||
zcfSeat.fail(e); | ||
throw e; | ||
} | ||
|
||
// Once we've gotten to this point, their payment is committed and | ||
// won't be refunded on a failed receive. | ||
const payout = await E(userSeat).getPayout('Transfer'); | ||
|
||
// Send the payout promise to the deposit facet. | ||
// | ||
// We don't want to wait for the depositFacet to return, so that | ||
// it can't hang up (i.e. DoS) an ordered channel, which relies on | ||
// us returning promptly. | ||
E(depositFacet) | ||
.receive(payout) | ||
.catch(_ => {}); | ||
|
||
return E(transferProtocol).makeTransferPacketAck(true); | ||
}; | ||
|
||
return Far('courier', { send, receive }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { assert } from '@agoric/assert'; | ||
import { makePromiseKit } from '@agoric/promise-kit'; | ||
|
||
/** | ||
* Create a promise kit that will throw an exception if it is resolved or | ||
* rejected more than once. | ||
* | ||
* @param {() => Details} makeReinitDetails | ||
*/ | ||
export const makeOncePromiseKit = makeReinitDetails => { | ||
const { promise, resolve, reject } = makePromiseKit(); | ||
|
||
let initialized = false; | ||
/** | ||
* @template {any[]} A | ||
* @template R | ||
* @param {(...args: A) => R} fn | ||
* @returns {(...args: A) => R} | ||
*/ | ||
const onceOnly = fn => (...args) => { | ||
assert(!initialized, makeReinitDetails()); | ||
initialized = true; | ||
return fn(...args); | ||
}; | ||
|
||
/** @type {PromiseRecord<any>} */ | ||
const oncePK = harden({ | ||
promise, | ||
resolve: onceOnly(resolve), | ||
reject: onceOnly(reject), | ||
}); | ||
return oncePK; | ||
}; |
Oops, something went wrong.