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.