On Tue, Apr 26, 2022 at 7:05 PM Nicolas Barry <
nic...@stellar.org> wrote:
> Entry points
> I do not see anything on that front. How are those exposed?
In the section on the interface between host and guest environments,
the CAP mentions that WASM modules carry a list of exported guest
functions, which the host can call. The CAP does not specify which if
any are considered "entrypoints" as that's a higher-level concept
(i.e. the interface or mediation between any transactions that invoke
smart contracts and the host's decision to call a particular guest
function).
> Host objects
>
> Limits
> I know this is a "TBD" section for the most part.
> Are there limits on the size of the linear memory available? Are parts of it reserved? What about stack limits?
It says TBD :)
Speculating-ahead though: we must certainly impose a limit on the
linear memory, as with any memory resources allocated by a contract
implicitly through its use of host functions. The linear memory is in
many ways the easiest memory resource to bound: there's a single "grow
memory" WASM operation and it gives an exact size. A lot of the other
memory-metering will be more subtle due to allocations implicit in
host function calls.
> Limits on host objects.
> When it comes to limits, like "total memory allocated by host objects", I imagine that this limit would be enforced at the transaction level, regardless of which VM allocates the object. So basically the limit would be enforced regardless of who allocates it?
Yes, that is my assumption.
> Do you think there would also be limits at the VM level? For example, you could imagine having a global limit of 4MB, but each VM would only be able to allocate 1MB (basically, it's like having 4 levels of recursion). This may help with making it easier for contracts to predict when they will hit those limits.
Possibly. At present the design doesn't assign a VM owner to an
allocated object, and tracking the movement of objects shared between
them might be challenging (eg. if contract A calls contract B passing
object X, and A is finished using X, does B now own it?) but it's not
beyond the possibility of trying.
> If it was just one global limit, the "memory spike" caused by a contract invocation may cause the contract to fail depending on the calling contract, which is not great.
This is true of other aspects of the transaction environment. For
example if a caller is not authorized to perform some action via the
signatures it carries, or has not declared its requirement to read or
write a given ledger entry from a concurrency control perspective,
then a contract may fail or not-fail depending on calling context.
Even with execution step-limits, a contract called "right near the
end" of its caller's step-limit exhaustion should fail where it would
not when called from a more generous budget context. So I think the
memory accounting context issue is not different.
> related to limits. As objects are immutable, I imagine that the policy for "freeing up" unused objects (at least from a limit enforcement point of view), would respect the rule of "not used, not counted"? There are different strategies for resource management (like only freeing up data in "batches" like in GC runtimes). Maybe it doesn't matter, but this should probably be specified.
There is no mechanism for freeing objects. I could clarify in the CAP
that the intent is to only free all objects allocated by a transaction
at the end of the transaction, but at present the CAP says nothing
about transactions. I should also note that there is no way to trace
the memory graph of a WASM contract to know which objects it's "not
using". And besides, the execution budget for a smart contract is so
brief that it would fit within a typical GC period -- at which point
one might as well just wait until completion and free everything.
> Host value and host object types:
> I find it weird that the doc uses raw integrals instead of enum names from the XDR (that are defined later in the doc, maybe that's the bug: start with the xdr).
If you're referring to the bit-packing tags used on the 64 bit host
value representation, I should emphasize that they are not equal to
the SCValType discriminant values used on the multi-word SCVal XDR
datatype. They are similar (there are 8 tags in the host value
representation, vs. 9 cases in the XDR discriminant) and they are not
the same numbers, due to the u63 / not-u63 case being modeled as a
single low bit in the host value type. So for example "symbol" is tag
4 but in XDR it is denoted by SCV_SYMBOL which has value 5.
The SCObjectType discriminant values are the same as the host object
type codes, and so I could recycle them there (I do in the most recent
iteration of the implementation).
> Some of the types would benefit for some explanation, and in general what is the goal of the representation exposed to the contracts.
This is a fair point and where much of the casual feedback I've got so
far rests. I only have comparatively weak justification for inclusion
or exclusion of many of the XDR types -- as I noted in the CAP the
criteria are fairly subjective, based on "foreseen use in a lot of
contracts". I'm happy to adjust the list of object types, and if you
think the best approach is to err on the side of fewer at first /
leaving things out, that's fine by me.
> Why are there so many integer types?
> I am counting U63, U32, I32 and then U64, I64, BigInt.
> I imagine that the more types we have the more conversions need to be supported.
Well, all of u32, u64, i32 and i64 are present in every source
programming language we're dealing with and also occur freely in a
variety of XDR datatypes in the existing protocol. So I don't know
that it makes sense to exclude them as values in the communication
repertoire. And BigInt is completely different, it's not a standard
part of XDR or almost any PL, and it's always provided as a software
library with memory allocation and arbitrary storage requirements and
so forth. But it's useful for high resolution calculations and
cryptography.
The u63 is a size optimization: fitting a positive int64 inside a Val
without allocating an Object. The same issue/question occurs with
Symbol (a small string with a limited 64-character repertoire) vs.
String, or as you mention below BitSet (a small 60-bit set) vs.
overflowing to some putative arbitrary-size-bitset Object. In all
cases the small packed-in-a-Val type is intended to allow small /
common values to traverse the host/guest interface and have guest code
perform basic operations (insert/extract/compare) on the Val form
without allocating an Object and interacting with it at-a-distance
through host function calls.
u63 is visible in the _low level interface_ between guest and host (in
this CAP), but we've been experimenting with hiding it in the
_user-facing SDK_ bindings, transparently switching to an Object
holding int64 in the negative case. It's not completely clear to me
when such transparent-type-changing is good or bad to do: it's
convenient for users in some cases (fewer types and distinctions to
think about) but also obscures a cost difference (some values suddenly
cost more) where users might well be optimizing for cost. It's also
plausible to (say) have the SDK transparently overflow from intNN
types to BigInt, but .. again I think this is something one can
credibly argue that users would prefer _not_ to have done behind their
backs.
Whether to hide or reveal such representation distinctions to users is
more of an SDK issue, though, less a question for this CAP. The
question with respect to this CAP is whether it's _useless_ to provide
this set of options to the SDK -- if there's no point including
something in the interface because no SDK will ever use it at all
(either as a visible case or a hidden optimization).
If you think it'd help with the rationale, I can add a section
discussing the motivation to provide partially-redundant small-value
types as common special cases.
> Why is there a 64 bit bitset type?
> I would expect instead an arbitrary size bitset object (and maybe not even for v1).
Small-value optimization as mentioned above.
> `BigInt` for example: is the goal to expose the actual underlying data so that people can write arbitrary code to manipulate it?
If you mean "underlying data" in the sense of access to bytes making
up host-side BigInt Objects, no, I don't expect guests will want to be
(or be allowed to be) poking at the bytes inside them. I suspect
though you're asking about the SCBigInt and SCBigRat XDR types, which
are not types the user _operates_ on, they're transport encoding for
import/export of data from the host. They are necessary because XDR
has no native big integer type -- if we want users to be able to
import/export big integers at all, we have to define some projection
into XDR. When held in a host representation, though, the bytes will
be inaccessible (they're typically rearranged into arrays of
host-native-word-sized "limbs").
> I was expecting this to be completely opaque, and people would just have to rely on host functions to manipulate or access those properties.
For performing arithmetic or doing any non-IO operations on BigInt /
BigRat host Objects, this is correct. They're just for IO in the XDR
representation.
> "string" vs "opaque"
> Are strings utf8 strings? xdr strings are not great from an interop point of view with other systems and languages (in the existing protocol, we have additional constraints on strings), but if we want those strings to play nice with existing assets, we'll have to use the same rules.
Yeah, good point. I intend them to be UTF-8 only.
> "Price" seems redundant with "BigRat"
> I think the only place where "Price" exists in the current protocol is when we deal with the DEX. I would leave it out for now, and just have a way (if we need to) for people to round a fraction that corresponds to a Price if needed via a host function.
Ok. I don't feel strongly, I just put it in (along with other XDR
types) because it seemed likely ubiquitous in contracts.
> How do we think of "BigRat" vs "double/float"?
> Maybe "BigRat" should not be part of the first version of this and instead be added later depending on what people use in the standard library?
I left it out at first, but then Leigh suggested putting it in. There
is also an open question about whether to allow FP instructions at all
in user code; many people find FP worrying from a predictability
perspective, and there is some work to do in any given WASM
interpreter to ensure determinism. I don't feel strongly, can see
arguments either way.
> "Operation", "OperationResult" and "Transaction" seem quite arbitrary at this stage as well.
These were based on the assumption that contracts would likely be
constructing operations / transactions to submit back to core. If this
is an incorrect assumption, we can leave them out, I don't feel
strongly.
> "AccountID"
> What is the purpose of this one? Is this supposed to be a "StrKey" type of thing or something used to identify the execution context? This may not be the right type in either case.
This was included based on the assumption that contracts would very
likely receive (as invocation information) and sub-key their storage
by account IDs. If not, I am happy to change this also.
-Graydon