Refactor TxState to separate read/write ops

By separating the read operations of the transaction state and the write
operations of the transaction state into separate interfaces we can
allow for API clients that only need to be able to read the state.

By ensuring that there is no overlap between the methods used for
reading and the methods used for writing (including in the internal
implementation - don’t use reader methods to access state for writing)
we can ensure that reading state never initialises lazy data structures.

When reading never initialises state we can allow reader methods to
return read-only objects when there is no state present, allowing us to
concentrate null checks to only a few places, and make the code that
reads the state simpler by having fewer special cases to deal with.
Ok, that seems like a design flaw to me. I'd say it makes sense to repeat a test that is stochastic in some way. In this case the test is based on a random source, and repeated a few times to ensure sufficient exposure to various permutations. Since it didn't add much overhead in terms of time, and since it made to code clearer to use the `RepeatRule` rather than a loop in the test body, that's what I did.

I don't feel very strongly about this though, I'm ok with having the test run just once as well. Although I do feel that the documentation of `RepeatRule` is a bit overly draconian in its dictated use.