Usually in such complex situations you aren't looking for the object to have strictly not changed, just that it has behaviourly returned to the state prior to the call.
Configuration is like a mini-language, controlling the function/object being used. Comparing the configuration prior/after a call is a good way to detect many state changes.
Another useful invariant is checking for behaviour despite an earlier failure. The idea being that if the function/object has shifted state then the next behaviour will be unexpected.
If you were in State A, and the error happened that should have left in in State A, then:
- you make a call that requires state A it should pass
- you make a call that requires state B it should fail
Beyond those invariants, you start to dictate implementation.
Which is fine for White Box testing.
But does make very brittle tests that are over fit to a specific implementation.