-
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.
feat: marshal based on user's petnames (#1092)
* feat: add a petname system that hydrates and dehydrates based on petnames
- Loading branch information
1 parent
b1607f9
commit 5e1945c
Showing
3 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
packages/cosmic-swingset/lib/ag-solo/vats/lib-dehydrate.js
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,87 @@ | ||
import harden from '@agoric/harden'; | ||
import { makeMarshal } from '@agoric/marshal'; | ||
import makeStore from '@agoric/store'; | ||
import { assert, details } from '@agoric/assert'; | ||
|
||
// Marshalling for the UI should only use the user's petnames. We will | ||
// call marshalling for the UI "dehydration" and "hydration" to distinguish it from | ||
// other marshalling. | ||
export const makeDehydrator = (initialUnnamedCount = 0) => { | ||
let unnamedCount = initialUnnamedCount; | ||
|
||
const petnameKindToMapping = makeStore(); | ||
|
||
const searchOrder = []; | ||
|
||
const makeMapping = kind => { | ||
assert.typeof(kind, 'string', details`kind ${kind} must be a string`); | ||
const valToPetname = makeStore(); | ||
const petnameToVal = makeStore(); | ||
const addPetname = (petname, val) => { | ||
assert( | ||
!petnameToVal.has(petname), | ||
details`petname ${petname} is already in use`, | ||
); | ||
assert(!valToPetname.has(val), details`val ${val} already has a petname`); | ||
petnameToVal.init(petname, val); | ||
valToPetname.init(val, petname); | ||
}; | ||
const mapping = harden({ | ||
valToPetname, | ||
petnameToVal, | ||
addPetname, | ||
kind, | ||
}); | ||
petnameKindToMapping.init(kind, mapping); | ||
return mapping; | ||
}; | ||
|
||
const unnamedMapping = makeMapping('unnamed'); | ||
|
||
const addToUnnamed = val => { | ||
unnamedCount += 1; | ||
const placeholder = `unnamed-${unnamedCount}`; | ||
const placeholderName = harden({ | ||
kind: 'unnamed', | ||
petname: placeholder, | ||
}); | ||
unnamedMapping.addPetname(placeholder, val); | ||
return placeholderName; | ||
}; | ||
|
||
// look through the petname stores in order and create a new | ||
// unnamed record if not found. | ||
const convertValToName = val => { | ||
for (let i = 0; i < searchOrder.length; i += 1) { | ||
const kind = searchOrder[i]; | ||
const { valToPetname } = petnameKindToMapping.get(kind); | ||
if (valToPetname.has(val)) { | ||
return harden({ | ||
kind, | ||
petname: valToPetname.get(val), | ||
}); | ||
} | ||
} | ||
// not found in any map | ||
const placeholderName = addToUnnamed(val); | ||
return placeholderName; | ||
}; | ||
|
||
const convertNameToVal = ({ kind, petname }) => { | ||
const { petnameToVal } = petnameKindToMapping.get(kind); | ||
return petnameToVal.get(petname); | ||
}; | ||
const { serialize: dehydrate, unserialize: hydrate } = makeMarshal( | ||
convertValToName, | ||
convertNameToVal, | ||
); | ||
return harden({ | ||
hydrate, | ||
dehydrate, | ||
makeMapping: kind => { | ||
const mapping = makeMapping(kind); | ||
searchOrder.push(kind); | ||
return mapping; | ||
}, | ||
}); | ||
}; |
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
191 changes: 191 additions & 0 deletions
191
packages/cosmic-swingset/test/unitTests/test-lib-dehydrate.js
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,191 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import { test } from 'tape-promise/tape'; | ||
import harden from '@agoric/harden'; | ||
|
||
import { makeDehydrator } from '../../lib/ag-solo/vats/lib-dehydrate'; | ||
|
||
test('makeDehydrator', async t => { | ||
try { | ||
const { hydrate, dehydrate, makeMapping } = makeDehydrator(); | ||
|
||
const instanceHandleMapping = makeMapping('instanceHandle'); | ||
const brandMapping = makeMapping('brand'); | ||
|
||
const handle1 = harden({}); | ||
const handle2 = harden({}); | ||
const handle3 = harden({}); | ||
instanceHandleMapping.addPetname('simpleExchange', handle1); | ||
instanceHandleMapping.addPetname('atomicSwap', handle2); | ||
instanceHandleMapping.addPetname('automaticRefund', handle3); | ||
t.throws( | ||
() => instanceHandleMapping.addPetname('simpleExchange2', handle1), | ||
`cannot add a second petname for the same value`, | ||
); | ||
t.throws( | ||
() => instanceHandleMapping.addPetname('simpleExchange', harden({})), | ||
`cannot add another value for the same petname`, | ||
); | ||
|
||
const makeMockBrand = () => | ||
harden({ | ||
isMyIssuer: _allegedIssuer => {}, | ||
getAllegedName: () => {}, | ||
}); | ||
|
||
const brand1 = makeMockBrand(); | ||
const brand2 = makeMockBrand(); | ||
const brand3 = makeMockBrand(); | ||
brandMapping.addPetname('moola', brand1); | ||
brandMapping.addPetname('simolean', brand2); | ||
brandMapping.addPetname('zoeInvite', brand3); | ||
|
||
t.deepEquals( | ||
dehydrate(harden({ handle: handle1 })), | ||
{ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'instanceHandle', petname: 'simpleExchange' }], | ||
}, | ||
`serialize val with petname`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'instanceHandle', petname: 'simpleExchange' }], | ||
}), | ||
), | ||
harden({ handle: handle1 }), | ||
`deserialize val with petname`, | ||
); | ||
t.deepEquals( | ||
dehydrate(harden({ brand: brand1, extent: 40 })), | ||
harden({ | ||
body: '{"brand":{"@qclass":"slot","index":0},"extent":40}', | ||
slots: [{ kind: 'brand', petname: 'moola' }], | ||
}), | ||
`serialize brand with petname`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: '{"brand":{"@qclass":"slot","index":0},"extent":40}', | ||
slots: [{ kind: 'brand', petname: 'moola' }], | ||
}), | ||
), | ||
harden({ brand: brand1, extent: 40 }), | ||
`deserialize brand with petname`, | ||
); | ||
const proposal = harden({ | ||
want: { | ||
Asset1: { brand: brand1, extent: 60 }, | ||
Asset2: { brand: brand3, extent: { instanceHandle: handle3 } }, | ||
}, | ||
give: { | ||
Price: { brand: brand2, extent: 3 }, | ||
}, | ||
exit: { | ||
afterDeadline: { | ||
timer: {}, | ||
deadline: 55, | ||
}, | ||
}, | ||
}); | ||
t.deepEquals( | ||
dehydrate(proposal), | ||
{ | ||
body: | ||
'{"want":{"Asset1":{"brand":{"@qclass":"slot","index":0},"extent":60},"Asset2":{"brand":{"@qclass":"slot","index":1},"extent":{"instanceHandle":{"@qclass":"slot","index":2}}}},"give":{"Price":{"brand":{"@qclass":"slot","index":3},"extent":3}},"exit":{"afterDeadline":{"timer":{"@qclass":"slot","index":4},"deadline":55}}}', | ||
slots: [ | ||
{ kind: 'brand', petname: 'moola' }, | ||
{ kind: 'brand', petname: 'zoeInvite' }, | ||
{ kind: 'instanceHandle', petname: 'automaticRefund' }, | ||
{ kind: 'brand', petname: 'simolean' }, | ||
{ kind: 'unnamed', petname: 'unnamed-1' }, | ||
], | ||
}, | ||
`dehydrated proposal`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: | ||
'{"want":{"Asset1":{"brand":{"@qclass":"slot","index":0},"extent":60},"Asset2":{"brand":{"@qclass":"slot","index":1},"extent":{"instanceHandle":{"@qclass":"slot","index":2}}}},"give":{"Price":{"brand":{"@qclass":"slot","index":3},"extent":3}},"exit":{"afterDeadline":{"timer":{"@qclass":"slot","index":4},"deadline":55}}}', | ||
slots: [ | ||
{ kind: 'brand', petname: 'moola' }, | ||
{ kind: 'brand', petname: 'zoeInvite' }, | ||
{ kind: 'instanceHandle', petname: 'automaticRefund' }, | ||
{ kind: 'brand', petname: 'simolean' }, | ||
{ kind: 'unnamed', petname: 'unnamed-1' }, | ||
], | ||
}), | ||
), | ||
proposal, | ||
`hydrated proposal`, | ||
); | ||
const handle4 = harden({}); | ||
t.deepEquals( | ||
dehydrate(harden({ handle: handle4 })), | ||
{ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'unnamed', petname: 'unnamed-2' }], | ||
}, | ||
`serialize val with no petname`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'unnamed', petname: 'unnamed-2' }], | ||
}), | ||
), | ||
{ handle: handle4 }, | ||
`deserialize same val with no petname`, | ||
); | ||
// Name a previously unnamed handle | ||
instanceHandleMapping.addPetname('autoswap', handle4); | ||
t.deepEquals( | ||
dehydrate(harden({ handle: handle4 })), | ||
{ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'instanceHandle', petname: 'autoswap' }], | ||
}, | ||
`serialize val with new petname`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: '{"handle":{"@qclass":"slot","index":0}}', | ||
slots: [{ kind: 'instanceHandle', petname: 'autoswap' }], | ||
}), | ||
), | ||
{ handle: handle4 }, | ||
`deserialize same val with new petname`, | ||
); | ||
|
||
// Test spoofing | ||
t.notDeepEqual( | ||
hydrate( | ||
harden({ | ||
body: '{"handle":{"kind":"instanceHandle","petname":"autoswap"}}', | ||
slots: [], | ||
}), | ||
), | ||
{ handle: handle4 }, | ||
`deserialize with no slots does not produce the real object`, | ||
); | ||
t.deepEquals( | ||
hydrate( | ||
harden({ | ||
body: '{"handle":{"kind":"instanceHandle","petname":"autoswap"}}', | ||
slots: [], | ||
}), | ||
), | ||
{ handle: { kind: 'instanceHandle', petname: 'autoswap' } }, | ||
`deserialize with no slots does not produce the real object`, | ||
); | ||
} catch (e) { | ||
t.isNot(e, e, 'unexpected exception'); | ||
} finally { | ||
t.end(); | ||
} | ||
}); |