go-perun
go-perun copied to clipboard
GOB Encoding of core types
The stdlib gob
package can be used to encode Go objects over the wire. While trying to use it for encoding some core types, like channel.State
or channel.Params
, it didn't work out of the box.
I identified two problems:
- If interfaces are to be sent over the wire,
gob
needs to be made aware of all possible implementations of those interfaces in advance usinggob.Register
. This is not the case for our implementations ofwallet.Address
,channel.Asset
,channel.App
,channel.Data
orchannel.Action
, possibly others. -
gob
gets confused by interfaces that includegob.GobDecoder, gob.GobEncoder
orencoding.BinaryMarshaler, encoding.BinaryUnmarshaler
. Ifgob
sees an object that implements those interfaces, it takes those implementations for en/decoding, which makes sense. However, it then doesn't transport the type information with the encoding, so the decoding side doesn't know which object to create. I found a Google groups discussion on the topic and a SO post.
I made the following observations that, if implemented, would hopefully make it work:
- All concrete interface implementations in backends need to be registered using
gob.Register
, e.g. adding
func init() {
gob.Register((*Address)(nil))
}
to packages backend/{sim,ethereum}/wallet
. Same for Asset
s, App
s etc. Make sure to use the correct type (pointer or no pointer) depending on what actually is used as the interface implementation (e.g. noApp
is used as a non-pointer type).
2. All interfaces used in structs that are to be encoded with gob
need to remove the embeddings of gob.GobDecoder, gob.GobEncoder
or encoding.BinaryMarshaler, encoding.BinaryUnmarshaler
. Of course, the concrete implementations can still implement those interfaces, but it is important that the struct field interface types don't reference those, or gob
gets confused and uses the methods before encoding the type information. One solution could be to have two interfaces, like
type interface AssetGOB {
// all functions except Binary(Unm|M)arshaler
}
type interface Asset {
AssetGOB
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}
and then require backends to implement Asset
but use AssetGOB
as the field type of assets in Allocation
etc. The constructors like NewAllocation
would still require Asset
to make sure full Asset
implementations are used but would then store the assets as AssetGOB
s so that it can be used with gob
encoding.
This Go Playground example shows what happens if gob.GobDecoder, gob.GobEncoder
is part of an interface (rename Duck
to DuckS
in the two lines mentioned in main
to see the problem).