-
Notifications
You must be signed in to change notification settings - Fork 632
[CO-354] Fix SafeCopy instance for AddrAttributes
#3685
[CO-354] Fix SafeCopy instance for AddrAttributes
#3685
Conversation
Until now, the instance was derived, and thus was broken when we modified `AddrAttributes` in earlier release/1.3.1 commits by adding the `aaNetworkMagic` field. In order to make future modifications to the instance easier in the future, we simply de/serialize a ByteString from the `Bi` instance for `Attributes AddrAttributes`. Since there is a very low probability of collisions between the old SafeCopy binary format and the new CBOR/Bi binary format, we attempt to decode via `Bi` first, and if that fails (either an invalid length ByteString, or a CBOR failure), then we do a "legacy" decode, where we only look for 2 fields (`AddrAttributes` had 2 before we added `aaNetworkMagic`) and fill in `NMNothing` so that the existing mainnet/staging databases are correctly read.
1fc6a43
to
69b0d7c
Compare
In order to ensure we have backwards compatibility with the old Address and Address' SafeCopy binary formats, we add some decode-only tests, which exercise the "migration" part of our new `SafeCopy AddrAttributes` instance. We also add golden tests which ensure the new CBOR format is maintained going forwards. We also add roundtrip testing for Address' (which exercises the new CBOR code). There was already a roundtrip test for Address, so we leave that.
69b0d7c
to
44fbef4
Compare
We want to work with mainnet-style addresses when testing asset locking. This reverts commit a2d569a.
The log file which is checked after running the tests got renamed from nodeN.json to coreN.json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM -- as a matter of personal preference I would have attempted immediately the CBOR decode in getCopy
as this will serve us well for the future, but that's marginal and probably debatable. Nothing that should stop this PR from being merged anyway!
I would be curious to hear what Duncan has to say, though 😉
@adinapoli-iohk I think you're preference lines up with my intention when I was writing the code, can you elaborate on the differences between the implementation and what you were imagining? In |
@mhuesch As said it's a minor (and probably debatable) alternative implementation, which would have been along the lines of: getCopy = contain $ do
resE <- Bi.deserialise' ...
case resE of
Left e -> ... getLegacy ..
Right x -> return x I.e. attempting the deserialisation first thing, skipping the look-ahead check, and then only if we fail we try the legacy one. But let me repeat that I was just playing the devil's advocate here, I think your implementation is an excellent one. 😉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the SafeCopy instance we need to be a bit more careful with the use of the cereal
package primitives.
The uncheckedLookAhead
is actually a bit dangerous and rather misleading. It only does what you expect if all the input is supplied in one chunk, since it only looks up to the end of the chunk. If acid-state uses the incremental interface to deserialise in e.g. 4k chunks then this can fail at a chunk boundary.
Instead we can use lookaheadM
and ensure
, something like:
-- ByteStrings are prefixed with a Int64 length. We cheat here and read the length as
-- thought it were a safePut-encoded Int64, so we know how long the ByteString will be.
--
-- 4[version] + 8[Int64] + <bytesLen>
-- bytesLen == length of AddrAttributes bytestring
tryNonLegacy <- Cereal.lookAheadE $ do
bytesLen <- safeGet
bytes <- Cereal.ensure bytesLen
pure (Bi.decodeFull bytes)
label $ case tryNonLegacy of
Left _ -> getLegacy
Right attrAddrAttrs -> pure (attrData attrAddrAttrs)
The point is, we use the lookaheadE
so that we don't have to mess with uncheckedSkip
etc, since it'll automatically go back if it fails, and consume if it succeeds. And then it means we can use ensure
rather than uncheckedLookAhead
, since if the ensure
fails then the surrounding lookAheadE
fails. And finally the Bi.decodeFull
returns the Either
that lookAheadE
expects to indicate failure or success.
Otherwise, looks good. |
@dcoutts After digging into your suggestion a bit, my conclusion is that it's better/safer, but |
I think we can actually go simpler still. We don't need to parse the encoding of the bytestring manually. We can just use a normal
|
From @dcoutts: > The uncheckedLookAhead is actually a bit dangerous > and rather misleading. It only does what you expect > if all the input is supplied in one chunk, since it > only looks up to the end of the chunk. If acid-state > uses the incremental interface to deserialise in e.g. > 4k chunks then this can fail at a chunk boundary. I switch to using `Alternative` to run the non-legacy parser first, and if it fails, to run the legacy parser. This seems to leverage Cereal's `fail` mechanism the way it is intended - when the first parser fails, no input is consumed, and the second parser is run.
@dcoutts I'm still mystified by I figured out how to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Description
Linked issue
https://iohk.myjetbrains.com/youtrack/issue/CO-354
Type of change
Developer checklist
Testing checklist
QA Steps