The roles of state root:
In #385, we have discussed the details about the advantages and disadvantages of putting the state root in the block header and p2p messages. Considering that the upgrade of neo-vm will cause state changes, it has been finally decided to put the state root in the p2p message to complete decoupling, so that during the upgrade, the state root is updated while preserving the blocks.
Thanks to @erikzhang @igormcoelho @lock9 @jsolman @shargon for the discussion in #385 #901 and #302. Now we can discuss the specific solutions.
State may change during neo upgrade. (It implies that the latest state is more reliable than before)
State validators can be different from block validators. When the validator changes, the signature verification of the old state will not be able to be verified by the nearest validator.
Nodes may send fault state root messages.
If the state consensus is coupled with block consensus, when upgrading neo-vm, will lead to block consensus waiting for state consensus to complete.
One-to-one correspondence between state root and block

When there are multiple state roots of the same block, the state root signed by subsequent validators is more effective. In case of the validator is the same, the state root with the higher version number has the higher effectiveness.
roles:
We propose three solutions here.
Solution 1: Extend the existing consensus message to support three types of consensus content: block, state root, both block and state root
When state and block both have synchronized, the state root and block will be approved in the same consensus message. Otherwise, state root and block will be in different consensus processes.
a) Adds state root part in PrepareRequest

b) Adds signature of state root in Commit message

Solution 2: Using another dbft process for state root consensus
Changes of consensus
a) Add state consensus message

b) message body

Upgrade
If the upgrade will cause state changes, then upgrade the version number of the state root. During the process of resynchronizing block, if the consensus nodes find the state root inconsistent with the previous one, it will trigger the state root consensus and broadcast the latest state root.
Solution 3: The consensus node signs the current state root and broadcasts it after the block generated.

Nodes can determine the state root after they have collected enough signatures.
Comparison of the three solutions

Risks
Principles when a node obtains a official state root:
The node uses state_height to indicate that it confirms the height of the state. After synchronizing the block, it will calculate the state root by itself and compare it with the state root signed by the consensus node. If the state root is consistent, the state_height increases.
Otherwise, set state_height to the index where state root starts to be unconsistent with the official state root. But node can still broadcast the official state root.

For a new node when synchronizing the block and state, it is necessary to first synchronize the block and calculate the local state root information, and then synchronize the state information signed by consensus nodes. At the same time,
Add LastStateIndex in ping payload

Add state type in invmessage and add GetStateRoots and StateRoot payload
GetStateRoots payload
StateRoot payload
If you support solution 1, vote 馃憤 and give your reasons.
If you support solution 2, vote 鉂わ笍 and give your reasons.
If you support solution 3, vote 馃帀 and give your reasons.
If you have other solutions, you can comment in this issue and open a new issue addressing the design of your solution if necessary. We will update your solution in the issue if the design is quite complete.
1 - because is easier, simpler and produce less overload.
Some projects are waiting for this StateRoot eagerly. And it is a hug blocker for Neo NOT providing stateRoot. So it is awesome to see the progress on it ..
Please, please move forward with the solution and implementation. 馃挴 :)
Vote for solution 3. Simply to implement and do not bring too much burden to CN.
I vote for solution 3, however, the description and proposal can still be improved. Perhaps we do not even need a new message. We could add in the PrepReq as a non-signed part of it, then, a normal flow as today can be kept.
@erikzhang What's your opinion, dalao?
Maybe we can combine Solution1 and Solution3. Consensus nodes confirm block and state root together of new block. At the same time, if there are previously unconfirmed state root, consensus nodes send their signature.
There are two issues that have not been fully resolved, if the state root has changed after the upgrade.
1. How to generate a new state root chain with signatures?
2. How to sync to other nodes?
Because each full node will generate an local StateRoot chain, using the latest StateRoot signed by the latest consensus to verify with the latest stateRoot of the local can ensure the consistency of the local state and the state of the consensus node. Therefore, the node only needs to store the latest StateRoot , and does not need to store the historical StateRoot.
Since it is no longer necessary to re-sign the previous StateRoot, we recommend Solution 1, impovement and details as following.

The StateRoot and block will be confirmed in the same consensus message.
PrepareRequest
Commit message
Block and StateRoot
When receiving the latest StateRoot message, the node will first verify its legitimacy, and then compare the local PreHash and StateRoot. If they are equal, it means that the current state of the node is consistent with the consensus node, and the StateHeight is setted to the current block height.
Background: When upgrade consensus, a new node is first deployed to synchronize the block to the latest height, then the old node is closed, and a validator wallet is opened in the new node to start consensus.
Consensus nodes always treat their local state as correct. They use their local StateRoot to proposal or check other validator's proposal. If more than (n-1)/3 consensus nodes' StateRoot are different from others, consensus won't be reached.
I will complete this feature in 4 individual steps (pull request)
@erikzhang @igormcoelho @vncoelho @shargon
I think this is my final solution. Can you guys please take a look and show your opinions?
@erikzhang @igormcoelho @vncoelho @shargon
Can you please take a look and share your opinions?
Can you please take a look and share your opinions?
I am not very fan of send the state root by p2p, I prefer to write the previous state in the next block.
@KickSeason, NeoResearch has been studying the situation with care, in particular, because it is a critical decision that implicates in many other important features for the NEO ecosystem.
We have been conducting some studies on the topics aligned with the idea and vision @erikzhang provided.
@KickSeason Sorry for the long time waiting, complicated month(s) here in Brazil.
Just to quickly highlight: we need at least 15 secs (~1 block time) to validate state, otherwise we severely harm scalability. I think that's what you're proposing, right? To use dbft to validate state on next consensus round, regarding current state (prevBlock) that should be the same on all CN.
Besides that, the Block message itself can carry the statehash, just not in its header, may as an appendix. Distributing only statehash on p2p is more complicated than just sending the block with the latest validated hash (at that height). Is it more or less correct?
Regarding the code, I'll try to review asap, to see which structure you've decided for MPT.
Linking #1526 discussion there as it's directly related and IMO has a better option of storing state root for the previous block in the header.
Most helpful comment
Maybe we can combine Solution1 and Solution3. Consensus nodes confirm block and state root together of new block. At the same time, if there are previously unconfirmed state root, consensus nodes send their signature.