-
Notifications
You must be signed in to change notification settings - Fork 228
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
new device model: read/writeLater #55
Comments
One other note: the motivation for synchronous-read on devices is to find a way to connect an ERTP Issuer (in a swingset vat, within a cosmos-sdk/tendermint chain) with a cosmos-sdk Bank module (which has a table of balances, indexed by a public key), probably for a "native" token like the one used for staking/delegation, or for paying gas fees on txns before swingset even has a chance to see the message. We expect to want to manipulate the account balance table with ERTP Purses. The Issuer's balance table normally maps from a Purse object to a balance (an integer). We might change that to map to both a balance and a public key. From the ocap side, if you have a purse, you can ask for its public key. If you create a new purse ( So the thought is that ocap/swingset messages can pretend that the Issuer vat is the sole source of truth, at least for the duration of a single message processing turn. But the "native" token balances must be correct in the cosmos-sdk Bank table, so "native" messages (Bank.transfer, or gas fees) can deduct them. So the Issuer's But the Bank-balance writes can be deferred until the end of the Crank, which is not observably different than being synchronous, without losing the transactionality of the updates (squelching the writes if the turn aborts). |
Right now, it's a purse or payment object to an amount. It would be interesting to see if we could only store the quantity, which would be a Nat in the default configuration, but could be other things, including data including other amounts. |
There were a couple of other issues we considered, and I'm going to record some of the outcomes to save us some of the work of thinking them through again. Devices are a lot like other vats, and (under the rubric that similar things should either be made the same or be clearly distinct) we talked about whether they should continue to be a distinct thing, with a similar interface, or if we would be better off providing access to non-vat functionality by allowing particular vats to receive endowments. Devices get access to endowments, which give them private access to functionality that can't be implemented in a vat. They implement objects which are accessible to vat code as clist entries supported by the kernel, and can similarly access other objects known to the kernel. Objects provided by devices are accessed using D(), which is like E(), except that its calls can be synchronous. So far, we've made all those calls send-only, so the device code can't tell that the calls are synchronous. We mostly agreed that devices should have exclusive access to some endowment, but they should be closely held by a "wrapper" vat. The wrapper vat handles asynchronous requests from other vats, and implements them in terms of calls on the device objects. The mailbox device follows this model, and the timer device probably will, but we haven't concluded that it's a pattern we should enforce. If we were to remove the distinction between devices and vats, this would have implications for how endowments are provided, and how we achieve orthogonal persistence. We might support persistence by having devices record incremental changes to their state, which they could then query during checkWrite, or we could have the device always be reading from the frozen state as of the beginning of the turn, while writes are queued up and sent at turn end. |
Dean and I talked more today about the synchronous endowments question. I think we settled on not needing synchronous access, which is great because for #54 I want to make all syscalls async: basically the userspace Vat code will issue syscalls as it runs, but the kernel merely queues them up, then after control returns to kernelspace, the kernel can make async DB requests out to the host to collect all the state it needs to execute those syscalls, as lazily as it wants. If any syscall has to return a synchronous value, I can't defer their execution. We focused on Meters, and how to manage them with normal ERTP primitives. Specifically we thought about the execution-fee layer, which exists to protect nodes/validators against spam and DoS attacks. The idea is that every message must include a deposit, and if we can bound the amount of time/CPU/etc we spend on the message before having enough information to claim the deposit, then at least there's an economic argument against unbounded junk messages. To keep this early-verification cost low, we can't be doing a lot of work (no kernel invocations, not Vat messages yet): just a simple signature check and deduction of a ledger entry indexed by the public key. If that ledger entry goes negative, the message is rejected. If the message is accepted, and passes subsequent (more expensive) checks, then maybe the deposit is refunded, or maybe it's just transferred to the validator as payment, or something. This pay-something-for-execution model looks sufficiently like the Meters and Keepers that we plan to use in the #23 escalator scheduler that we're just calling them (gas) Meters. So each Meter is associated with a specific public key and a matching entry in the ledger (which lives outside the SwingSet kernel). Now the trick is that we want to be able to refresh these Meters using ERTP We figure that we'll have an The Meter also supports a Finally the Meter supports a Likewise, We think this is sufficient to do what we need w.r.t. gas balances, and that probably means it's sufficient for other cosmos-sdk We thought a lot about gas and Meters/Keepers too. We can afford an async lookup of a Meter state before calling a Vat's So I'm planning to go ahead with the async-ification of the SwingSet syscall API. |
Implement version command
* update @agoric/evaluate * update @agoric/swingset-vat
update documentation and code in preparation for ERTP 0.1.0 (released)
I added some notes to a new ticket before remembering this one, here are those notes: The current swingset device model defines "devices" as containers very much like vats: the kernel dispatches some messages into them, they can make some syscalls back out through the kernel, there is a c-list between the two. Where vats export "objects", devices export "device nodes". But unlike vats, which are completely isolated from the outside world and can only communicate through the kernel, devices are given some collection of Vats interact with each other by calling Vats interact with device nodes through a special syscall named The idea was to provide a general-purpose (but capability-friendly) way for vats to interact with the outside world, mediated by devices. This makes read-old/write-later APIDean has recommended a more structured API. Vats would be limited to invoking devices with either an explicit devicesDevices aren't just passive: the device itself gets to push messages onto the run-queue. We need this for any sort of inbound events:
However these events only happen in the spaces between cranks/blocks: they do not (should not / must not) occur while a crank is running. So in a sense the inbound events are updating the "state of the world" before the block, and any device reads happening during the block should sample that updated state. |
We (me, dean, mark, chris) had a long discussion today about device drivers in SwingSet. Not sure we came to any clear conclusions, but here's what I remember:
We need three properties:
We have three devices in mind: Mailbox (exists), Timer/Clock (#148), and something to help integrate the cosmos-sdk Bank module's account entries with an Issuer Vat's purses.
Dean's experience suggests that the React model is the best one to follow: handler functions get a snapshot of the current state, plus a function that lets them queue changes to be applied at the end of the operation (iff i doesn't get cancelled or aborted for some reason).
We defined three levels of turns:
kernel.step()
, and is achieved by usingsetImmediate()
to wait on the IO queue, which is strictly lower-priority than the promise queue. Swingset Vats cannot share JS Promises or resolver functions (they can only share data and reference slots, through the kernel), therefore nothing in a Vat can get control until the kernel next invokes thedispatch()
function, even if other Vats resolve their own promises (i.e. the object graphs of separate Vats are disjoint except for their link through the kernel syscall/dispatch, and those calls don't accept JS promises)We were converging on a device design that exposes two functions to the calling vat:
syscall.read(devnode, args) -> results
, andsyscall.writeLater(devnode, args) -> error
. The first causes the device to be invoked withdispatch.read(devnode, args)
and should return synchronous data, but not modify any state. The second would cause two device invocations. The first is acheckWrite
that should compare theargs
against a shadow state object that it builds up, to see if the writes would succeed later (e.g. does a balance transfer underflow, or does an argument refer to a missing timer object).checkWrite
is allowed to return an error, but it is not allowed to commit any state changes to its endowments. If it returns success, the kernel pushes a call todispatch.write
to a special queue that does not run until the end of the current Crank (so it will be abandoned if any other Turn causes a vat-killing error). At the end of the Crank, all device writes are delivered, giving the device an opportunity to commit the changes to the endowments.Rather than
dispatch
-style calls, another possible device API could be:read(devnode, args) -> data
write(devnode, args, S) -> error or function commit(S2) -> newS2
Where both close over the device's endowments.
write()
gets anS
object that is an emptyMap
for the first call during the Crank, and a copy of the previous one for subsequent calls. Eachsyscall.writeLater
causes an immediate devicewrite()
call, andS
lets it accumulate both changes that need to be committed at the end of the crank, and a "shadow table" of data that helps figure out if the arguments are legal.At the end of the Crank, the kernel invokes the
commit
function with a separateS2
state object.commit
should pull the queued writes fromS
and apply them to the endowment. It can also return a modified S2 object to retain non-endowment state from one Crank to the next. E.g. the Timer device would use S2 to record the set of callback handler objects for each timer, and the Mailbox would use it to record the inbound handler object.The text was updated successfully, but these errors were encountered: