Closed JimLarson closed 3 months ago
@michaelfig please correct or elaborate as necessary.
The
Receive()
equivalent isn't straightforward, as the caller must handle the unmarshaling - what's the message type?
Ah, okay, after looking at the code I see what you mean.
We could either add another method to the
vm.PortHandler
interface:NewMsg() interface{}
which would return a new, blank message suitable for marshaling into, or pass an unmarshaling function in:Receive(sdk.Context, func(interface{}) error) (interface{}, error)
.
I think these two approaches break down when we consider that a given PortHandler may receive more than one message type.
So, I think my preferred mechanism (though maybe it should be spelt differently) is:
type PortHandler interface {
// Receive and type-dispatch a message from the controller.
Receive(sdk.Context, interface{}) (interface{}, error)
// Marshal converts a Go type to a byte slice.
Marshal(interface{}) ([]byte, error)
// Unmarshal converts a byte slice to a Go type.
Unmarshal([]byte) (interface{}, error)
}
I think it's fine for the port handler to be in control of the serialisation for that port. I think the Unmarshal
method would use an internal registry to demux []byte
to different types, like it would if we moved to protobuf for these messages.
Also, switching from string
to []byte
and encapsulating that in the PortHandler
is probably the idiomatic thing to do. We should update our JS controller for []byte
as well.
Begin rant. Grumble. I think the Golang encoding/json
package makes a fundamental semantic error by marshaling to []byte
instead of string
, thereby hard-wiring the utf8 character encoding into the translation, whereas RFC 7159 makes it clear that utf16 and utf32 are also supported.
See this history to see similar confusion in a base64 library and the resulting simplification when text is represented as string
and character encoding is specified by explicit composition.
In any case, I think it's fair to sidestep this issue and just say that our encoding isn't "JSON" but "JSON∘utf8". End rant.
So by making a convenient entry point that reacts to Go data, it's worth being clear about how this feature should and shouldn't be used, relative to other options like:
Like an ABCI message, the controller messages may update state and must be deterministic, and there is a routing mechanism to locate the right PortHandler
. Unlike ABCI messages, there is no "check" operation.
It might be a best practice to have read-only operations have an equivalent query, both mapping to the same underlying keeper method.
Question: would we ever want to do a module-to-module call to the Receive()
method instead of a normal call to a keeper?
Question: would we ever want to do a module-to-module call to the
Receive()
method instead of a normal call to a keeper?
I don't think so.
Discussion today with @michaelfig
[]bytes
for unknown structures.Notes from discussion with @michaelfig
x/controller
module to replace vm
x/controller
<--> x/vbank
vbankKeeper.SetController()
and controllerKeeper.SetVBank()
. This would make an extensible API, vs piling more arguments into the keeper constructors.x/swingset
into a new x/storage
.Looks like a plan!
Btw, I'd suggest other keepers could receive the controller keeper in their constructor for upcalls. Just the downcall targets would use controllerKeeper.Set*
.
Thanks.
No longer plan of record.
What is the Problem Being Solved?
The current Agoric-specific Cosmos modules (
x/lien
,x/swingset
,x/vbank
,x/vibc
) are endowed with a "controller" reference for communication to/from Javascript. The controller API accepts and receives strings which hold marshaled JSON text, tying these modules to JSON as an interface.We'd like to refactor the JSON marshaling to allow these modules to be more JSON-agnostic and Golang-idiomatic, and thus potentially more efficient and widely useful.
Description of the Design
The controller API that a modules sees for sending to JS is
callToController func(sdk.Context, string) (string, error)
, where thestring
is JSON, typically marshaled from a JSON-annotated structure suitable forencoding/json.Marshal()
.Similarly, the API for receiving from JS is
Receive(*vm.ControllerContext, string) (string, error)
, again where the strings are JSON. (The*vm.ControllerContext
is a no-op wrapper around asdk.Context
and could be simply refactored away.)The proposal is to replace the use of
string
withinterface{}
, where theinterface{}
dynamically matches the types specified inencoding/json.Marshal()
orUnmarshal()
as appropriate. The actual marshaling and unmarshaling will be handled in the controller implementation.The proposal for
callToController
is a usage likeerr := keeper.callToController(ctx, &reqMsg, &replMsg)
, where the called function handles the marshaling of the request and unmarshaling of the reply.The
Receive()
equivalent isn't straightforward, as the caller must handle the unmarshaling - what's the message type? We could either add another method to thevm.PortHandler
interface:NewMsg() interface{}
which would return a new, blank message suitable for marshaling into, or pass an unmarshaling function in:Receive(sdk.Context, func(interface{}) error) (interface{}, error)
.Security Considerations
None foreseen.
Test Plan
Refactor of existing code, so