Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

RFC 006 - Budget Contract Language #1193

Closed
wants to merge 5 commits into from
Closed

Conversation

mvines
Copy link
Contributor

@mvines mvines commented Sep 12, 2018

This RFC defines the external Budget DSL-like language.

@mvines mvines added work in progress This isn't quite right yet noCI Suppress CI on this Pull Request labels Sep 12, 2018
@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

@garious WDYT? This deviates slightly from the current Budget DSL

@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

I just added another commit proposing what a wallet cli for this could look like.

For 0.8 we could also just punt on defining a yaml/json/whatever external representation and instead encode budget transactions as:

  1. solana-wallet ... commands as I showed in the "Add wallet cli proposal" commit
  2. A simple API in https://github.com/solana-labs/solana-web3.js, which the example-webwallet then gets rewritten to use.

Uses all the same stuff under the hook but you just don't get the experience of:

$ vim budget-transfer.yml
... edit edit...
$ solana-wallet compile budget-transfer.yml -o transaction.bin
$ solana-wallet send transaction.bin

until 0.10.

Advantage of this approach too is that it forces Budget to remain very simple. If we define an external representation then it's a short slide down to adding conditionals and loops. But we really ought to be using the generalized contract infra for that in 0.10 and beyond

@garious
Copy link
Contributor

garious commented Sep 12, 2018

I've been thinking of keys as the interpreter's stack, not a heap. So the high-level data structure wouldn't ever reference keys directly. Instead, that AST would contain the keys and would be compiled down into this keys stack and a lower-level AST that assumes the keys will be at the top of the stack whenever it needs them.

@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

Yeah I think of keys as the same (in addition to the transaction userdata).
Perhaps we're trying to turn Budget into something it's simply not.

@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

@garious please check out v0.2. I tried to write the Budget DSL, under a different name to avoid confusion, to actually work in the new world.

It's likely the SystemCall work in #1189 will impact this slightly, I haven't accounted for that here yet.

@garious
Copy link
Contributor

garious commented Sep 12, 2018

The existing low-level, expression-oriented Budget AST seems more elegant to me. The only thing I was expecting to see change is less keys in the low-level AST, and more keys in the high-level one. Specifically, the low-level AST does not contain from keys and assumes those will be passed in via the apply methods. Likewise, the low-level AST does contain to keys even though those are now held in the keys vector.

@garious
Copy link
Contributor

garious commented Sep 12, 2018

I think you can ignore #1189 for now, since it is running multiple experiments at once and offers no new tests in all that I/O-free code. It doesn't look like that'll get merged any time soon. Instead, consider pulling the useful bits out of there, write some tests, and let's get that part merged.

@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

Hmm, there is only one to key. That’s key[1]. This can’t be encoded in Account userdata because that’s where the tokens actually live while the Transfer is pending.

I feel like we aren’t talking the same language yet. What does high and low-level AST mean to you exactly?

@garious
Copy link
Contributor

garious commented Sep 12, 2018

To me, the low-level AST is exactly what's serialized in userdata. It's purpose is to drive state transitions on the server. The high-level AST is as close as possible to what the user writes. Its purpose is make the choice of YAML, JSON, XML, or some custom user-facing syntax a totally uninteresting problem.

@garious
Copy link
Contributor

garious commented Sep 12, 2018

Also, I'm having trouble seeing why we'd add the transfer command to solana-wallet and not incrementally extend the pay command with --after-date, --authorized-by and --cancellable options.

@garious
Copy link
Contributor

garious commented Sep 12, 2018

I see solana-wallet parsing the CLI options to populate the high-level Budget AST, and compile that to a Transaction. The web front-end would do the same, but could (for example) chain JavaScript methods to build up that high-level AST.

var budget = BudgetExpr(from, tokens)
    .afterDate(dt)
    .pay(tokens, to);

Or perhaps hide the smart contracts from the user and write something like:

var tx = Transaction(from, tokens)
    .afterDate(dt)
    .pay(tokens, to)
    .sign(secretKey);

@mvines
Copy link
Contributor Author

mvines commented Sep 12, 2018

Low-level AST (Account userdata): the Or(..) enum of the current Budget AST is a problem as this means the sender of the ApplyTimestamp or ApplySignature instruction must pass the PubKey for each Or branch in their transaction. That is quite clunky and error prone. For example as a witness, I should not have to encode two additional public keys (or1_key[2] and or2_key[3]) in my transaction -- I need exactly two things: my key and the key of the contract I'm witnessing. Instead in v0.2 the funds are always locked up in key[1]'s userdata and the contract takes action based on the key[0] supplied into the next Transaction.

With v0.2 I arrived at the conclusion that the high-level AST should just be wallet cli extensions and a JS object. No YAML/JSON/etc representation as the language is so small it felt like overkill.

I'm having trouble seeing why we'd add the transfer command to solana-wallet and not incrementally extend the pay

Yeah that works too. I kept it separate during this discussion in an attempt at clarity. I certainly see rewriting pay in terms of the new cli commands described. Folding it all into pay is fine too if we are saying that solana-wallet is now really the Budget contract CLI. Keeping it all under solana-wallet transfer/budget reserves cli namespace for other contract types in the future.

@garious
Copy link
Contributor

garious commented Sep 12, 2018

Low-level AST (Account userdata): the Or(..) enum of the current Budget AST is a problem as this means the sender of the ApplyTimestamp or ApplySignature instruction must pass the PubKey for each Or branch in their transaction. That is quite clunky and error prone. For example as a witness, I should not have to encode two additional public keys (or1_key[2] and or2_key[3]) in my transaction -- I need exactly two things: my key and the key of the contract I'm witnessing. Instead in v0.2 the funds are always locked up in key[1]'s userdata and the contract takes action based on the key[0] supplied into the next Transaction.

Are we talking about the same thing when I said:

Likewise, the low-level AST does contain to keys even though those are now held in the keys vector.

If so, this is the part I'm referring to when I say we gotta get those Pubkeys out of the low-level AST and turn keys into a stack (disallow random access). As the server is interpreting and reducing the low-level AST, ideally, it'd always be able to find the key its needs at the top of that stack.

A second possibility is to go ahead and leave the keys in the AST and boot the keys data structure. I don't see why that data needs to be separate. Instead, you could add a keys() method to PaymentPlan to generate that list dynamically as-needed.

My preference is the latter, but maybe it's not feasible. @aeyakovenko, thoughts?

@garious
Copy link
Contributor

garious commented Sep 13, 2018

@mvines, I spoke with @aeyakovenko about this. We're basically on the same page now. We're not going to add a keys() method at this time, because we want the GPU to know where to find both Pubkeys and Signatures in the transaction so that it can verify them without needing to figure out what contract language this is and how one should query it. As for the duplication of keys within Budget (and therefore userdata), we're okay with that in the short-term, even though we recognize that it introduces an opportunity for errors. In the long-term, we can consider either updating the keys data structure to index userdata, or updating the userdata to reference the keys data structure. My preference is the former, and if we go that route, you can move forward with Budget without distinguishing a low-level AST from a high-level one. There'd be only one, and we'd change the Transaction format to navigate that serialized userdata for sigverify.


### Transfer (aka, Budget v2) Transaction Format
```rust
pub enum TransferCondition {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was called Condition in budget.rs, which seems like a better name, because it doesn't assume the consequence of satisfying that condition would be to make a payment. Note that Budget is an enum with After(Condition, Payment). The only reason that's not After(Condition, Box<Budget>) is because we didn't write the static analysis code to ensure that's not a really big, expensive expression.

Witness(Pubkey),
}

pub struct Transfer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called Payment in payment_plan.rs, but includes a to pubkey. In the current code, a Budget expression wraps and guards the Payment instead of including those guards in the struct. In this proposed form, you'd need to add cancellable and conditions fields to every type of action, not just payment. Also, if a transaction was not cancellable and had no conditions, you'd have to pay 9 bytes to tell the system its the base case instead of just 1, Budget::Pay.

/// Cancel the `Transfer` identified by `key[1]`. Fails if `key[0]` is not in `Transfer`
/// `cancellable`, or if the `Transfer` is already unlocked. The `tokens` will be returned to
/// `key[0]`.
Cancel,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current code, what you call Witness below aims to handle this case.

/// Declare and instantiate a new `Transfer` into `key[1]` using tokens from `key[0]`.
///
/// `key[1]` must be unallocated before calling `Create`
Create(Transfer),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something like NewContract(Contract(Instruction(Budget))) in current code. I like the idea of moving all Budget-specific constructors out of Instruction.


#### Cancel Transfer
```sh
$ solana-wallet transfer cancel TransferPubKey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange to see this and witness under the transfer command. How about solana-wallet cancel $sig?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this comes down to the question of, do we want solana-wallet to be Budget DSL specific? Doesn't seem like a bad idea necessarily, I just didn't want to jump right to there without discussion first. But sounds like you're already there?


#### Indicate Elapsed Time.
```sh
$ solana-wallet transfer timestamp TransferPubKey 2018-12-24T23:59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about: solana-wallet timestamp and have it submit the system time by default? I wonder if that should also optionally include a the contract signature. Ideally it wouldn't, but it's a pretty expensive operation for the server to go look up every contract that might be waiting on a timestamp instead of just the one you're targeting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitting the system time by default sounds good.

There's no need to include a contact signature. The contact is the public key (denoted by TransferPubKey) that the timestamp instruction is directed at.

@mvines
Copy link
Contributor Author

mvines commented Sep 14, 2018

Ok so we want to reduce all the solana-wallet transfer ... with solana-wallet ..., but otherwise generally keep the functionality that is expressed in these CLI examples.

@CriesofCarrots, with that do you have enough info to start moving the cli design forward using the current contents of this PR as a guide? Internally we'll need to tweak a couple things in Budget DSL/bank.rs to some of the commands actually work, I can work on that in parallel and/or you can help too if I'm too slow.

@CriesofCarrots
Copy link
Contributor

Excellent. Yes, I do, thanks!
I'll touch base with you on the Budget/bank tweaks, as I get into the meat of it.

@mvines
Copy link
Contributor Author

mvines commented Sep 18, 2018

302 #1258

@mvines mvines closed this Sep 18, 2018
@mvines mvines deleted the rfc branch November 28, 2018 18:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
noCI Suppress CI on this Pull Request work in progress This isn't quite right yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants