statechannels / go-nitro

Implementation of nitro-protocol in Go
Other
39 stars 19 forks source link

Allow for the consumer to sign their own chain transactions #1416

Open geoknee opened 1 year ago

geoknee commented 1 year ago

Context

Currently, go-nitro accepts a chainPk (chain private key) through its CLI entrypoint. This is used to construct an EthChainService which is (in turn) injected into the node constructor.. The EthChainService will bind an internal txSigner variable to that chainPk, so has the ability to sign transactions with that key.

This way, when go-nitro decides it is safe to launch an on-chain transaction, a side effect will be generated by a protocol / objective "crank", routed to the chain service, signed and launched to the blockchain by the chain service.

Problem

Users may not want to trust go-nitro to sign and send transactions with their layer 1 blockchain private key.

Solution

An alternative pattern we could support is having go-nitro prepare a transaction and send on or return to the user to sign and submit.

We would like to be able to preserve the current behaviour, and switch depending on a command line parameter / toml config.

Detail

The existing ChainService interface implies a blend of:

https://github.com/statechannels/go-nitro/blob/2b0bc7c5998abe021970704f448d030844a05928/node/engine/chainservice/chainservice.go#L85-L98

I propose that will split up the interface to have prepare transaction and send transaction:

PrepareTransaction(protocols.ChainTransaction) (types.Transaction, error) // has nil signature
SendTransaction(types.Transaction) error // requires non-nil signature, returns an error otherwise

Happily, we already have some abstract transaction types which are "without signatures": https://github.com/statechannels/go-nitro/blob/2b0bc7c5998abe021970704f448d030844a05928/protocols/interfaces.go#L29-L32

Unknowns

Should go-nitro accept signed transactions so it can send them? Or do we assume the caller can send them (e.g. through metamask).

Proof

We should do a manual integration test between two go-nitro nodes. One can run headless and manage its own chainPk. The other is run headful using the go-nitro GUI, which should be wired up to route transactions to metamask through the browser (like so). If we can open and fund a ledger channel between those two nodes, we will have achieved our aim.

geoknee commented 1 year ago

From standup today:

  1. Instead of rewriting the code which is currently autogenerated by abigen, we could instead pass a different tx.Opts.Signer function. This would encapsulate sending the raw transaction back to the user and having the user return a signed transaction. This would mean go-nitro is still responsible for sending the transaction. This might be a bit messy when it comes to managing concurrency / not blocking
  2. We could fork abigen so we can get under the hood and make it do what we want. See this https://github.com/ethereum/go-ethereum/pull/26782
geoknee commented 1 year ago

For the abigen v2 route see the sketch code here: https://github.com/statechannels/go-nitro/compare/abigenv2?expand=1#diff-108f221519228166f68fe014cb4428e8cb8cb434a564a008109ae9c611d60a2cR165-R177

The real advantage is that breaking changes to our solidity code API will flag immediately in the go-nitro codebase as a compiler error.

The alternative is to hand-roll transaction preparation. It's not difficult, and in fact we are already doing something like this when parsing events (of course it has the same downsides #1417).

geoknee commented 1 year ago

If the transaction is to be prepared by go-nitro and then signed by the consumer, it is not obvious which party is responsible for setting gas costs and account nonces. If the consumer ultimately is using some wallet like metamask, then metamask will overwrite these values anyway. I think probably go-nitro should leave these fields null / blank for the consumer to fill in.