feat(core): Reject client-initiated updates that build on stale state
Builds on https://github.com/ceramicnetwork/js-ceramic/pull/2395
The goal of this change is to better handle the case where the client application authors a new update to a stream based on stale state. Imagine that an application loads a stream and the stream handle stays open in the user's browser tab for a while, possibly hours or days. In the meantime, new updates are made to the stream, possibly via a different Ceramic node powering a different app, or even possibly via the same Ceramic node and app, just in a different session. Then the user tries to do an update via the original stream handle, which has an old, out of date version of the stream state in it's client-side view. In this case, one of two things will happen: either the write will be rejected by conflict resolution, or it will succeed by winning the conflict resolution against the existing state on the node, and thus overriding an existing write.
With this change, if the user tries to initiate a write via the http-client but that write is built off of state the Ceramic node already knows is stale, the Ceramic node will proactively reject that write to preserve the data that it already knows about that has been around longer, over the new write that was authored based on a stale view of the data.
Note that this change has no affect to the conflict resolution rules applied when learning about a commit via pubsub. The arbitrary-but-deterministic tie-break based on CID lexographic ordering still applies for commits learned via the network. This is a special case to prefer existing data over new data when we know the new data is stale, as a way to minimize the number of writes that successfully get applied to a node and only to be later rejected.
NET-1587 Throw an error if a write is generated using out of date state
If the client has outdated state and then authors a write pointing to a prev commit that is no longer the current tip of the stream, the application may or may not get an error depending on whether the commit is rejected by conflict resolution. If there has been an anchor since the outdated state that is present in the client (and the node knows about it), then applying the commit will always fail and throw an error. If there wasn't an anchor yet but just a different signed commit, then it's a random 50/50 chance whether or not the client will get an error depending on which branch of history wins the arbitrary conflict resolution tie break rule.
While we have to follow that arbitrary-but-deterministic tie-break rule for commits we hear about via pubsub, for commits that are authored directly against the local node we could always reject them with an error if they are created against a state that is not the current tip that the node knows about. This would force the application to re-sync the client state with the node state and retry applying the commit. This could significantly decrease the likelihood of applying commits that override previous writes.
Not yet ready for review, needs cleanups to the tests that depend on https://github.com/ceramicnetwork/js-ceramic/pull/2358
Closing in favor of https://github.com/ceramicnetwork/js-ceramic/pull/2579