-
Notifications
You must be signed in to change notification settings - Fork 208
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
refactor: move atomicRearrange into Zcf #7900
Changes from all commits
63ee139
ff54e41
03ac2cd
f54a73b
b9c3ff2
8de8500
5a00ae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { makeScalarMapStore } from '@agoric/vat-data'; | ||
|
||
import { assertRightsConserved } from './rightsConservation.js'; | ||
import { addToAllocation, subtractFromAllocation } from './allocationMath.js'; | ||
|
||
const { Fail } = assert; | ||
|
||
/** @typedef {Array<AmountKeywordRecord>} TransactionList */ | ||
|
||
/** | ||
* Convert from a list of transfer descriptions ([fromSeat, toSeat, fromAmount, | ||
* toAmount], with many parts optional) to a list of resulting allocations for | ||
* each of the seats mentioned. | ||
* | ||
* @param {Array<TransferPart>} transfers | ||
* @returns {[ZCFSeat, AmountKeywordRecord][]} | ||
*/ | ||
export const makeAllocationMap = transfers => { | ||
/** @type {MapStore<ZCFSeat, [TransactionList, TransactionList]>} */ | ||
const allocations = makeScalarMapStore(); | ||
|
||
const getAllocations = seat => { | ||
if (allocations.has(seat)) { | ||
return allocations.get(seat); | ||
} | ||
|
||
/** @type {[TransactionList, TransactionList]} */ | ||
const pair = [[], []]; | ||
allocations.init(seat, pair); | ||
return pair; | ||
}; | ||
|
||
const decrementAllocation = (seat, decrement) => { | ||
const [incr, decr] = getAllocations(seat); | ||
|
||
const newDecr = [...decr, decrement]; | ||
allocations.set(seat, [incr, newDecr]); | ||
}; | ||
|
||
const incrementAllocation = (seat, increment) => { | ||
const [incr, decr] = getAllocations(seat); | ||
|
||
const newIncr = [...incr, increment]; | ||
allocations.set(seat, [newIncr, decr]); | ||
}; | ||
|
||
for (const [fromSeat, toSeat, fromAmounts, toAmounts] of transfers) { | ||
if (fromSeat) { | ||
if (!fromAmounts) { | ||
throw Fail`Transfer from ${fromSeat} must say how much`; | ||
} | ||
decrementAllocation(fromSeat, fromAmounts); | ||
if (toSeat) { | ||
// Conserved transfer between seats | ||
if (toAmounts) { | ||
// distinct amounts, so we check conservation. | ||
assertRightsConserved( | ||
Object.values(fromAmounts), | ||
Object.values(toAmounts), | ||
); | ||
incrementAllocation(toSeat, toAmounts); | ||
} else { | ||
// fromAmounts will be used for toAmounts as well | ||
incrementAllocation(toSeat, fromAmounts); | ||
} | ||
} else { | ||
// Transfer only from fromSeat | ||
!toAmounts || | ||
Fail`Transfer without toSeat cannot have toAmounts ${toAmounts}`; | ||
} | ||
} else { | ||
toSeat || Fail`Transfer must have at least one of fromSeat or toSeat`; | ||
// Transfer only to toSeat | ||
!fromAmounts || | ||
Fail`Transfer without fromSeat cannot have fromAmounts ${fromAmounts}`; | ||
toAmounts || Fail`Transfer to ${toSeat} must say how much`; | ||
incrementAllocation(toSeat, toAmounts); | ||
} | ||
} | ||
|
||
/** @type {[ZCFSeat,AmountKeywordRecord][]} */ | ||
const resultingAllocations = []; | ||
for (const [seat, [incrList, decrList]] of allocations.entries()) { | ||
let newAlloc = seat.getCurrentAllocation(); | ||
for (const incr of incrList) { | ||
newAlloc = addToAllocation(newAlloc, incr); | ||
} | ||
for (const decr of decrList) { | ||
newAlloc = subtractFromAllocation(newAlloc, decr); | ||
} | ||
turadg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
resultingAllocations.push([seat, newAlloc]); | ||
} | ||
return resultingAllocations; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import { | ||
makeScalarBigWeakMapStore, | ||
provideDurableMapStore, | ||
provideDurableWeakMapStore, | ||
prepareExoClass, | ||
prepareExoClassKit, | ||
provide, | ||
prepareExoClass, | ||
provideDurableMapStore, | ||
provideDurableWeakMapStore, | ||
} from '@agoric/vat-data'; | ||
import { E } from '@endo/eventual-send'; | ||
import { AmountMath } from '@agoric/ertp'; | ||
|
@@ -19,6 +19,8 @@ import { | |
SeatDataShape, | ||
SeatShape, | ||
} from '../typeGuards.js'; | ||
import { makeAllocationMap } from './reallocate.js'; | ||
import { TransferPartShape } from '../contractSupport/atomicTransfer.js'; | ||
|
||
const { Fail } = assert; | ||
|
||
|
@@ -214,6 +216,10 @@ export const createSeatManager = ( | |
|
||
return isOfferSafe(state.proposal, reallocation); | ||
}, | ||
/** | ||
* @deprecated switch to zcf.atomicRearrange() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good but note that consumers of this object aren't getting the type written here. They get There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. The comment is mostly for those reading/editing this file. This still seemed the most concise way to express it. |
||
* @param {AmountKeywordRecord} amountKeywordRecord | ||
*/ | ||
incrementBy(amountKeywordRecord) { | ||
const { self } = this; | ||
assertActive(self); | ||
|
@@ -227,6 +233,10 @@ export const createSeatManager = ( | |
); | ||
return amountKeywordRecord; | ||
}, | ||
/** | ||
* @deprecated switch to zcf.atomicRearrange() | ||
* @param {AmountKeywordRecord} amountKeywordRecord | ||
*/ | ||
decrementBy(amountKeywordRecord) { | ||
const { self } = this; | ||
assertActive(self); | ||
|
@@ -265,6 +275,7 @@ export const createSeatManager = ( | |
const ZcfSeatManagerIKit = harden({ | ||
seatManager: M.interface('ZcfSeatManager', { | ||
makeZCFSeat: M.call(SeatDataShape).returns(M.remotable('zcfSeat')), | ||
atomicRearrange: M.call(M.arrayOf(TransferPartShape)).returns(), | ||
reallocate: M.call(M.remotable('zcfSeat'), M.remotable('zcfSeat')) | ||
.rest(M.arrayOf(M.remotable('zcfSeat'))) | ||
.returns(), | ||
|
@@ -289,6 +300,96 @@ export const createSeatManager = ( | |
return zcfSeat; | ||
}, | ||
|
||
/** | ||
turadg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Rearrange the allocations according to the transfer descriptions. | ||
* This is a set of changes to allocations that must satisfy several | ||
* constraints. If these constraints are all met, then the reallocation | ||
* happens atomically. Otherwise, it does not happen at all. | ||
* | ||
* The conditions | ||
* * All the mentioned seats are still live, | ||
* * No outstanding stagings for any of the mentioned seats. Stagings | ||
* have been deprecated in favor or atomicRearrange. To prevent | ||
* confusion, for each reallocation, it can only be expressed in | ||
* the old way or the new way, but not a mixture. | ||
* * Offer safety | ||
* * Overall conservation | ||
* | ||
* The overall transfer is expressed as an array of `TransferPart`. Each | ||
* individual `TransferPart` is one of | ||
* - A transfer from a `fromSeat` to a `toSeat`. Specify both toAmount | ||
* and fromAmount to change keywords, otherwise only fromAmount is required. | ||
* - A taking from a `fromSeat`'s allocation. See the `fromOnly` helper. | ||
* - A giving into a `toSeat`'s allocation. See the `toOnly` helper. | ||
* | ||
* @param {TransferPart[]} transfers | ||
*/ | ||
atomicRearrange(transfers) { | ||
const newAllocations = makeAllocationMap(transfers); | ||
|
||
// ////// All Seats are active ///////////////////////////////// | ||
for (const [seat] of newAllocations) { | ||
assertActive(seat); | ||
!seat.hasStagedAllocation() || | ||
Fail`Cannot mix atomicRearrange with seat stagings: ${seat}`; | ||
zcfSeatToSeatHandle.has(seat) || | ||
Fail`The seat ${seat} was not recognized`; | ||
} | ||
|
||
// ////// Ensure that rights are conserved overall ///////////// | ||
|
||
// convert array of keywordAmountRecords to 1-level array of Amounts | ||
const flattenAmounts = allocations => | ||
allocations.flatMap(Object.values); | ||
const previousAmounts = flattenAmounts( | ||
newAllocations.map(([seat]) => seat.getCurrentAllocation()), | ||
); | ||
const newAmounts = flattenAmounts( | ||
newAllocations.map(([_, allocation]) => allocation), | ||
); | ||
assertRightsConserved(previousAmounts, newAmounts); | ||
|
||
// ////// Ensure that offer safety holds /////////////////////// | ||
for (const [seat, allocation] of newAllocations) { | ||
isOfferSafe(seat.getProposal(), allocation) || | ||
Fail`Offer safety was violated by the proposed allocation: ${allocation}. Proposal was ${seat.getProposal()}`; | ||
} | ||
|
||
const seatHandleAllocations = newAllocations.map( | ||
([seat, allocation]) => { | ||
const seatHandle = zcfSeatToSeatHandle.get(seat); | ||
return { allocation, seatHandle }; | ||
}, | ||
); | ||
try { | ||
// No side effects above. All conditions checked which could have | ||
// caused us to reject this reallocation. Notice that the current | ||
// allocations are captured in seatHandleAllocations, so there must | ||
// be no awaits between that assignment and here. | ||
// | ||
// COMMIT POINT | ||
// | ||
// The effects must succeed atomically. The call to | ||
// replaceAllocations() will be processed in the order of updates | ||
// from ZCF to Zoe. Its effects must occur immediately in Zoe on | ||
// reception, and must not fail. | ||
// | ||
// Commit the new allocations (currentAllocation is replaced | ||
// for each of the seats) and inform Zoe of the new allocation. | ||
|
||
for (const [seat, allocation] of newAllocations) { | ||
activeZCFSeats.set(seat, allocation); | ||
} | ||
|
||
// we don't wait for the results here. As described in | ||
// docs/zoe-zcf.md, The initial allocation to a seat originates with | ||
// Zoe, but *all subsequent updates come from ZCF to Zoe*. | ||
void E(zoeInstanceAdmin).replaceAllocations(seatHandleAllocations); | ||
} catch (err) { | ||
shutdownWithFailure(err); | ||
throw err; | ||
} | ||
}, | ||
reallocate(/** @type {ZCFSeat[]} */ ...seats) { | ||
seats.forEach(assertActive); | ||
seats.forEach(assertStagedAllocation); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resultingAllocations
is a different shape thanallocations
. consider renaming toresultingEntries
orresolvedEntries
.consider a functional style using a transform function:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I looked at that, but
allocations.entries()
isn't an array. UsingArray.from()
would require declaring more types.