-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Smart contracts design #72
Comments
Wadler discussing Plutus Core and IELE: |
Here's a smart contract language that looks more in line with the way we evaluate smart contracts concurrently: https://github.com/rchain/Rholang |
In v0.5.0, we'll have the same contract language as v.0.4.0. Going forward, we'll create an interface into a safe execution environment. We need to come up with a deterministic way to charge contracts for the use of network resources. EVM's gas-per-instruction is too arbitrary and unnecessarily low-level. Anything that can be computed within the duration of a single sig-verify is effectively the same price in our pipeline. |
Simplicity: A New Language for Blockchains - by Russel O'Conner is worth a read during research on this. It would still likely need a more natural language ontop of it, such as eg. Lua. Also, the devlist (captured here) has feedback from several bitcoin-core developers on this proposal, which highlights many of the challenges associated with building smart contracting language: https://www.reddit.com/r/bitcoin_devlist/comments/79r1ma/simplicity_an_alternative_to_script_russell/ |
Race makes it sound like the language itself induces race conditions -- not the sort of connotation you want for an unbiased Or operator, no? |
@FishmanL, |
A smart contract is a process that is waiting on a set of information. In response to receiving any information in that set, it may optionally send information to other processes, and then move into a new state where it is waiting on a new set of information. Once the process no longer requires information, it is complete. Below is a Rust implementation of a reusable M-N Multisig combinator. struct Multisig {
m: u64,
sigs: Vec<Signature>,
}
impl Multisig {
fn new(m: usize, sigs: Vec<Signature>) -> Result<Self> {
if m == 0 || sigs.len() < m {
Err(ContractError::Invalid);
}
Ok(Multsig{m, sigs})
}
}
impl Step for Multisig {
fn step(&mut self, sig: &Signature) -> Result<State> {
if !self.sigs.contains(sig) {
return Ok(State::NeedsMore);
}
if self.m == 1 {
return Ok(State::Done);
}
self.m -= 1;
self.sigs.remove(sig);
Ok(State::NeedsMore)
}
} As a result of receiving M signatures, the combinator doesn't do anything in particular, like make a payment. You'd instead, write something such as: Multisig::new(2, sigs).then(
Pay::new(42, to)
) The role of frontend languages then, would be to stitch together combinators in some more intuitive way. For example, in Rust, you'd add some macro magic to write that as: do!(
mutisig(2, sigs);
pay(42, to);
) |
A little cleaner, if a do!(
sigs.len() <= 1;
pay(42, to);
) This might, however, require an do!(
until(sigs.len() <= 1);
pay(42, to);
) |
The smart contract engine allows operations like M-N Mulisig to be written in a general purpose programming language. Here's a Lua implementation that executes on the new -- M-N Multisig. Pass in a table "{m=M, n=N, tokens=T}" where M is the number
-- of signatures required, and N is a list of the pubkeys identifying
-- those signatures. Once M of len(N) signatures are collected, tokens T
-- are subtracted from account 1 and given to account 4. Note that unlike
-- Rust, Lua is one-based and that account 1 is the first account.
function find(t, x)
for i, v in pairs(t) do
if v == x then
return i
end
end
end
function deserialize(bytes)
return load("return" .. bytes)()
end
local from_account,
serialize_account,
state_account,
to_account = table.unpack(accounts)
local serialize = load(serialize_account.userdata)().serialize
if #state_account.userdata == 0 then
local cfg = deserialize(data)
state_account.userdata = serialize(cfg, nil, "s")
return
end
local cfg = deserialize(state_account.userdata)
local key = deserialize(data)
local i = find(cfg.n, key)
if i == nil then
return
end
table.remove(cfg.n, i)
cfg.m = cfg.m - 1
state_account.userdata = serialize(cfg, nil, "s")
if cfg.m == 0 then
from_account.tokens = from_account.tokens - cfg.tokens
to_account.tokens = to_account.tokens + cfg.tokens
-- End of game.
state_account.tokens = 0
end |
Closing this. Our goal of creating a new concurrent smart contracts language is no more. Instead, we created a concurrency-friendly smart contracts engine, and will provide various safe execution environments, such as a BPF interpreter for C programs and a safe subset of the Lua interpreter for Lua programs. |
#### Problem AccountsFile currently doesn't have an implementation for TieredStorage. To enable AccountsDB tests for the TieredStorage, we need AccountsFile to support TieredStorage. #### Summary of Changes This PR implements a AccountsFile::TieredStorage, a thin wrapper between AccountsFile and TieredStorage.
…ed-storage (solana-labs#418) #### Problem As solana-labs#72 introduced AccountsFile::TieredStorage, it also performs file-type check when opening an accounts-file to determine whether it is a tiered-storage or an append-vec. But before tiered-storage is enabled, this opening check is unnecessary. #### Summary of Changes Remove the accounts-file type check code and simply assume everything is append-vec on AccountsFile::new_from_file().
Some thoughts on the current state of the smart contract language. We currently have a minimal language suitable for little more than today's needs. As we move towards supporting atomic swaps, we'll need to choose whether to incrementally extend this language or make a bigger jump to a more general solution. Note that this decision is independent of the language's interpreter targeting BPF, as described in the Loom whitepaper. Below is a walk down the path of incrementally extending today's contract language into a general purpose one:
Here's an example spending plan we can write today:
where the parameters to Payment are of type i64 and PublicKey and Or is of type Plan. In Haskell DSLs, Plan would traditionally be called Expr and Or is the unbiased choice operator sometimes denoted with Alternative's
<|>
. Likewise, instead of the Condition/Payment tuple, Haskellers would probably use Applicative's*>
to say "Timestamp then Payment". In Haskell, we'd look to express the same spending plan as:where each function returns an Expr, or maybe a nicely typed Expr GADT. In this form, we see the duplication of
Payment tokens
, and in fact, can rewrite the same expression as:Translated back to Rust, we'd want something like:
But unlike Haskell, Rust isn't going to heap-allocate each of those nodes, so it'd actually be more like:
Next, we can remove all those Boxes with a top-level
[Plan]
instead of aPlan
, and replace everyBox<Plan>
with a&Plan
pointing to a slot in the top-level array. Effectively, we'd be translating our heap-allocated recursive expression into a stack of expressions. We'd end up with:That last line looks so similar to Reverse Polish Notation, it begs the question, what would that look like? Maybe:
tldr; The walk above shows us that incrementally taking our existing contract language to a general-purpose one takes us down a long, deep rabbit hole. Instead, we'll want to choose between:
I'm leaning toward option 3 today and then moving toward option 2 after we demonstrate our minimal, custom language meets our performance targets.
The text was updated successfully, but these errors were encountered: