-
Notifications
You must be signed in to change notification settings - Fork 9
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
Refactor Transaction Receipt/Result/State #36
Comments
I'm a bit confused about this issue. A transaction is effective / syntactically valid if it made its way to the mesh and invalid if it was rejected before that as there are validity checks when it is submitted by a client to a node for broadcasting. See TransactionState.REJECTED. I think that as soon as it is TransactionState.PENDING on its journey to the mesh, it means it passed syntactic validation and is in the pool. The Regarding execution failure. The receipt should have all the information regarding the other questions in the issue which are different questions than syntactic validity. So, I don't understand the concern described based on the current design - it is immediately clear exactly what is the transaction status from the receipt, and the TransactionStatus enum should cover all possible cases and questions raised above regarding execution and there's also a field in the receipt for how much gas was consumed. So that is fully addressed. A receipt is generated even if execution 'fails' because gas is consumed and the tx affected the global state in that way, even though the intent of the tx was not I think Iddo and Tal argued we should expose the ordered list of input transactions to the STF in each layer via the global state API, and have an index in the tx receipt that indicates the location of the tx in this ordered list so we can show users what txs were executed before or after it in the layer. As the ordered list is per layer, this gives us all the ordered txs across all layers which Tal mentioned. |
My understanding, based on our conversation, is that it's not this simple. As one example, a transaction may be included in two different layers (#8). It may not be "effective" the first time it's included, because, e.g., the nonce is too low, but it may become "effective" in a later layer, or vice-versa. The same tx in a later layer may not even be visible to/processed by the STF, depending how we design the protocol.
Agree with this, but it's worth pointing out that TransactionState is not canonical because one node might reject a tx that another node admits into its txpool/puts into a block.
See #34 |
I don't understand your examples. Let's go case by case.. So the tl'dr - it can't become effective in a future layer with the current definition of STF and if someone wants to change this definition he needs to come up with an alternative algo for the STF in terms of the input... Regarding your second comment - this is not true - all nodes that run the same protocol can not reject a transaction which is in a verified layer on the mesh. Once a transaction is in the mesh and there's consensus about it being on the mesh by honest nodes then they must execute it according to the STF algo which is all nodes must follow unless they want to fork from the state of all other honest nodes. So, TransactionState is canonical and must always be the same for all honest nodes. They are not free not to change the STF algo at will - otherwise one can never have a functional cryptocurrency. The global state computation and stf algo is what define the currency even if we don't have a way to check the consensus on it. Regarding the last point - yes - we should add to the global state api the canonical list of txs input for each stf and a tx receipt should include the index in this list (similar to the index of a tx in a block in eth tx receipts). |
btw - if a node decides to add this transaction to a block it submits in a future layer then the same transaction should go in unless the pre-filtering per-stf rules will exclude it. In that case, it will be in the list of input txs for the stf of that layer and it will be processed and produce a receipt if it is valid to go into the stf txs input list... e.g. not bad nonce, etc.. This case where the same tx can appear in different layer is a PITA to deal with as it complicates things. e.g. a transaction needs to have a list of one or more mesh layers it appears in and not only one and the apis need to support this. It would be great if we can avoid this but I'm not sure it is possible - heard different opinions about it from diff team members... |
Agree
Disagree: a miner may decide to include the same tx in a block in a later layer - this has nothing to do with the STF
Agree
Disagree: the decision whether and on what basis to admit a transaction to the txpool, or to include it in a block, is not under protocol and different nodes are free to implement different policies around it. You're right that once a tx is in the mesh and confirmed by consensus it becomes canonical, but up to this point transaction state is not canonical and different nodes may have different opinions:
Frankly I don't think we can make any decisions here regarding API or data model until we have a decision on this question! |
Hopefully the following will help answer your questions: Conceptually, I think of Spacemesh as having two layers: the consensus layer and the VM layer. The consensus layer is completely VM agnostic (up to syntactic considerations, see Syntactic Validity of Transactions). Consensus layerThe output of this layer is the transaction ledger: an ordered list of transactions. It has the following properties:
The consensus layer is responsible for ordering and deduplicating transactions. Blocks and layers are implementation artifacts of the consensus layer. They do have some effect on the output---for example, our consensus algorithm can only dedup and order transactions after agreeing on an entire layer, so the output granularity is one layer at a time. However, the VM layer doesn't really have to care about this. Note that if a transaction appears in multiple blocks in a layer, the consensus algorithm will output it only once. Similarly, for a transaction that appears in two different layers, the consensus output will contain only the first occurrence. In particular, this means that once a transaction is in the mesh, the exact same transaction can't be "resubmitted" later --- a new transaction has to be created. Rollbacks (reorgs)In case of self-healing, it's possible that some portion of history is reversed). If this happens, the consensus layer updates the VM layer that there was a reset to some specific transaction in the past, and then outputs the ordered list of transactions starting from the reset point. VM LayerThe VM layer is responsible for maintaining global state (account balances, app code, app variables, etc.) and executing the state transition function (STF). The STF accepts a single transaction, and executes it, potentially modifying the global state. Transaction executionThe VM layer calls the STF on every transaction in the order it receives them from the consensus layer, modifying the global state as it does so. RollbacksIf the VM layer is notified of a rollback, it is responsible for rolling back the global state to whatever it was at the reset point. It can then execute transactions normally. Transaction resultsGiven the above, we can now classify the state of a transaction:
Syntactic Validity of TransactionsTheoretically, we can make the consensus layer totally VM-agnostic, which means it would allow completely invalid transactions in the mesh. However, to prevent DoS attacks, we do want to filter some things at the consensus layer already. So transactions that are syntactically-invalid (i.e., they would be totally ineffective regardless of the global state when they are executed by the VM) can be filtered out at the consensus layer. This means that we would consider blocks containing such transactions as syntactically invalid. Heuristic rejection of transactionsWe may also want to prevent transactions that do depend on state from making into the mempool (such as ones that have conflicting counters). In this case, honest nodes will ignore the transactions, but they could still be included into the consensus layer by malicious nodes (or even by honest nodes, in case our assumptions are not satisfied --- for example during a network split). This kind of transaction can be resubmitted later, since it is filtered before it makes it into the consensus layer, so doesn't violate the uniqueness property of the transaction ledger. |
I agree with most of what you wrote in your comment and I think it is quite close to our proposed STF and tx results / status api but there are few caveats and things to address and be a bit more precise about:
I don't think you can exclude the time element (layer) from this definition. The agreement on the content is always in the context of a layer number. All honest nodes agree on the contents up to a specific layer. e.g. the verified layer.
I find it a bit misleading to call a layer an implementation artifact where it is actually the time ticks that the consensus works in and vital to consensus as my point above explains. So, it is not enough that the consensus creates a prefix list of ordered transactions. Because the consensus verifies tx in chunks (all the tx in a layer) once a layer is verified, it is useful to also describe the input batch to the STF as input list for an input layer. It is not true that the VM layer doesn't care about layers. The way we specify the STF it must care about layer as it always work with input from layer n-1 and outputs state for layer n. Without such knowledge, the STF will not be able to create receipts that describe the deterministic time of execution of the tx which is critical piece of data in a crypto-currency. (e.g. block # for processed txs in bitcoin and ethereum). So it is undesirable from practical considerations to define the vm layer as layer agnostic. The layer is the deterministic tick of the decentralized computer that determines computation time. Without for example layer number in tx receipts there's no way to know when a tx was actually processed as the index in the global ordered lists of tx doesn't give us any notion of time. It is not enough to say that the output granularly is one layer at the time. More exact definition of the STF is required - can you please comment on what is defined in the data and api smip where we attempt to properly define the STF? It will be useful if you can say what is wrong / bad there then needs to be changed. I find the other comments in the consensus layer great and we should add them to spec. To be a bit more specific, I think that what you argue means that a specific transaction can only appear in 1 stf input-list in a layer but not in multiple ones.
This is another reason what it is very useful to define the STF ticks as layers and have the STF define as getting a layer n number as input. In case of rollback, the consensus layer just instructs the vm layers to execute the STF from layer n.
Can you please relate to the proposed states? I think that we pretty much agree on this but you are talking about adding another state for inclusion in the mem-pool.
it is also not entirely accurate that we'll have a receipt if a tx was accepted to the mesh because the STF may have not executed for that layer yet. Transactions which are on the mesh will have a receipt later - this is why we propose the PENDING= 4 and PROCESSED = 5 states which you don't mention in your summary. So considering your feedback and this point, I think the possible states are:
Regarding tx receipt data you mention, can you please review to what we proposed in the api:
I think it captures all possible stf execution results and it is pretty similar to what you describe but more formal. One of these states go into a receipt. BTW, the way we define global-state in smip, each and every transaction that is in the input list of a layer to the STF will affect global state when the STF executes, because it will generate a receipt (side-effect) even if it doesn't change the state of the accounts/apps db. This is why I wanted to define global-state as meta-mash which includes the proper more narrow global-state (state of account and app db) as well as execution side-effects (receipts and app events), but we decided to call all the data and the side-effects of execution global state. So I think that the notion of not-applied, partially-applied, and applied are useful only in the context of the accounts and app db but not in the context of the way global state is currently defined... |
Didn't we review and approved Noam's design for pre-stf filleting for 0.3.5 in the product summit in 12/2019? I believe pre-filtering is currently implemented in the testnet codebase and has been so for a long time. If any pre-filtering as implemented for 0.3.5 then we want to spec it now. |
Agree with Aviv here that this reset should only be defined for an entire layer, not for a specific transaction. The behavior of the STF on an individual transaction, or a portion of a layer, is not well defined due to the way we handle fees and rewards, pooled and on a per-layer basis. In other words, the STF cannot be "reset" to an intermediate point in a layer.
As Aviv pointed out, there should be a "pending" state - added to a block but the block hasn't been approved/added to the mesh yet.
Is this possible in practice? As discussed on the design review call, it's not possible for simple coin transfers as they're designed now, since every signature matches some account's private key - and we cannot know ahead of time whether that account won't have some balance when the STF attempts to execute the tx. As for smart contract/"app" transactions, we need @YaronWittenstein's input on this. This question probably reduces to some version of, "Are there bytecode patterns that we can definitively call invalid pre-execution?" You could attempt to syntactically parse Wasm bytecode and make sure it's valid, but this doesn't preclude the possibility of using other bytecode formats/targeting other VMs in the future.
Thinking about this more, I do see some value in a distinction here between a more narrowly-defined "global state" (basically, state of all accounts) and a broader "meta-mesh" (for lack of a better term) that includes tx receipts, app events, etc. |
I think we're all more or less on the same page about transaction results and receipts. Regarding TransactionState, however, we unfortunately have a confusing mix of concerns the way this is set up right now:
I think it makes sense to have a clean separation of the consensus and VM layers that Tal describes. The PROCESSED state could potentially be dropped, on the grounds that the existence or absence of a receipt for this tx contains this information, which gets us most of the way there.
|
This makes sense. Regarding the precise stages a tx passes once it gets into the mempool:
I'm guessing "mined into a block" means (2) and "added to the mesh" means (3)? Note that while it's true that technically some time passes between (4) and (5), in our current implementation (in which every node runs the STF) I expect this to be a matter of milliseconds at most, so it won't really matter from a user perspective. (In a future version, if/when we support a separate role for state validation, the separation will be more meaningful.) Regarding the layer number, I have no objection to including it as input to the STF. (indeed, as you say, in our protocol we will always process entire layers at a time, and rollbacks will always happen at layer granularity). However, the STF is well defined for executing one transaction at a time. I would think the STF's API would contain (very loosely) something like the following:
The node will call Of course, the "rollback handles" can just be the layer numbers, and if our code can be significantly simplified or optimized by allowing an |
Ok makes sense.
We already defined
Yes, I think we all agree to this. I still believe we should go over with tal over the spec of the STF (including layer being an input) in the smip to confirm that the design is good and finally agree on definitions and the stf. |
The problem with this definition of the STF is that it does not account for fees and rewards. This is why I keep saying the STF is not well-defined for executing one transaction at a time. In the protocol as I understand it, you cannot just execute one transaction in isolation, because fees from all of the transactions in a layer are pooled and divided up as part of the processing of a layer. (This is how it was explained to me, and how it was documented. If this is not the case, let's talk about it.) |
It is all pretty well defined here: spacemeshos/SMIPS#13 -> The STF. e.g
To summarize the way the STF is currently defined:
|
I agree with one caveat:
As Tal pointed out, global state is not really updated after each individual tx is executed. That doesn't really happen until |
It depends what you call the 'global state'. As we said above it includes the accounts and apps state db + all the side-effect of tx execution (receipts and events). So, the global state is updated as each tx is executed and ofcourse if there's an exception that it is rolled back. But it has to be understood that transaction n gets as input the global state after transaction n-1 has been executed as n-1 may have changed an account or app state that is input or ouput of tx n. There is no other way to execute these transactions. So commit, does need to happen after each executed tx and roll-back means not updating app or account state in the db in case of tx exception. So the global state is actually updated after each tx is executed but as Anton mentioned this can be an in-memory representation that is committed to storage only once when the last tx was processed. |
It is impossible to execute correctly n txs that have shared input and some shared output using an initial state s (at least avoiding double-spends in smart contract txs is impossible). They must be executed in steps otherwise execution can't be correct. e.g. tx n-1 deplated an account balance used as input to tx n... tx n should fail as its input is the global state after tx n-1 was executed. |
Maybe it will all be better if we can review the definition in the smip and argue if any specific requirement there needs to be changed and not discuss this generally... |
@lrettig Good catch --- You're right that the fees and rewards do need more information than just the transactions. However, this doesn't mean that transactions aren't executed one at a time --- it just means that the STF needs to get some more information from the consensus layer (in particular, it needs to know the set of blocks in each layer, and the assignment of transactions to blocks, including repeated transactions). The most general way to do this is to have the consensus layer give "auxiliary" information to the STF. Basically, a call such as:
This would be called before processing the first transaction for a layer (with "auxinfo" being the set of blocks in the layer). We might also include in AuxInfo information about published ATXs (e.g., if publishing an ATX gives a reward even without generating a block). Note that in our case the rewards and fees only apply to future layers, so the STF doesn't need the set of blocks for layer i in order execute the transactions in layer i. |
Makes sense - as long as we don't allow the VM to supply a running smart contract with any additional, contextual info based on the block that a tx lives in, e.g., a blockhash (as in ethereum), or the layer (e.g., layer hash, number of blocks in a layer, etc.). |
Right. Although as I wrote it, the STF actually does have this information. Is there a good use-case for letting smart contracts have this additional info? |
I think we all agree that smart contracts need access to global state: reading and writing storage, reading balances, reading code, etc. For mesh data, it's not so immediately obvious how it could be useful. Off the top of my head, a lot of eth smart contracts use block hash as a first order source of randomness. There are also Ewasm host functions for block number, difficulty, block gas limit, etc. - allowing Spacemesh smart contracts to read, say, number of blocks per layer, or number of transactions per block would allow some degree of introspection along these same lines. |
It is super important for smart contract methods implementation to have access to layer number as part of the context as a clock tick and in addition the genesis time-stamp and the network's constant layer duration, so they can do time-based computations. Many use cases that involve time-lock and changes of data over time from creation - e.g. NFT evolution. I bet that every non trivial eth game and NFT is using block number at least once. It is also critical for DEFI apps such as token auctions. So layer # is the abs minimum we know we need in the context. Additional mesh info is NTH but not MH. |
It doesn't really matter for the high-level stf specs - what matters is what the overall set of inputs that the STF needs and it includes the layer blocks - not how they are provided.
Here again Tal - can you please review the specific proposed definition of the STF in the SMIP? It states clearly how rewards are computed and when. The current proposal is for the STF that works on txs from layer n to also compute the rewards for smeshers for layer n. It makes sense because it computes the total TX fees charged in the TX in layer n when it processes the transactions and can assign it as a step after tx processing (as proposed - please read again). If we want to change this then we need clear definitions and specs regarding to what layer does the STF of layer n compute the rewards from in case we want it to be different than what is proposed in the smip. Note, that if for some reason we need to change the rewards definition of the STF then it will need as input all the blocks of the layer != n that it needs to compute the rewards for. We must converge and have a good spec that we all feel good about but it feels like information that is currently written there is ignored or general statements are made that contradict it without an alternative proposal. So, if we need to change something from the current proposal for the stf. e.g. how rewards and tx fee are computed then we need alternative concrete proposals to consider beyond high-level statements. Can we please have that? Here is the current definition of rewards and fees computation of the stf from the smip:
|
Can we take the conversation thread about STF definition to the SMIP thread? spacemeshos/SMIPS#13 |
Sure and it will be helpful if any new comments can propose changes to the current stf high-level definition in the smip so we can converge. |
Is this issue still relevant? What did we agree that needed to be changed if anything? |
Reviewing where we're at right now in the API, we have api/proto/spacemesh/v1/tx_types.proto Lines 40 to 55 in 11c8309
Separately, we have the api/proto/spacemesh/v1/types.proto Lines 45 to 61 in 11c8309
To answer the initial questions I posed, above:
All transactions in spacemesh are syntactically valid. Any transaction with a
Yes iff
See Does this all sound correct? |
I'm taking no response to indicate assent. Closing this as no further action is pending here. Please reopen if I've missed something. |
Since the job of the API will be only to deliver the binary transaction - I think the whole conversation regarding the scheme isn't relevant. The decoding of a Receipt into a JSON will happen inside the |
Per today's design review call, Noam and Iddo both questioned the current design of
TransactionResult
. Maybe what we really want to know is: 1. was the transaction "effective" or not (Iddo's term, i.e., syntactically valid), 2. did the transaction succeed or fail?, and 3. was a fee charged for it or not? These things are not immediately visible from the current design. Do we want to rethink the design?The text was updated successfully, but these errors were encountered: