statechannels / go-nitro

Implementation of nitro-protocol in Go
Other
37 stars 18 forks source link

Implement `watchtower` functionality #1520

Open geoknee opened 11 months ago

geoknee commented 11 months ago

From the grant proposal:

The on-chain challenge method (and associated go-nitro module in Milestone 3) gives power to participants to “take each other to court”.

This is an important protection, but one that invites malicious / false claims. Although the protocol will swiftly reject any challenge that does not have sufficient “support” — for example, a unanimous round of signatures from all parties — there is still a risk. A malicious or faulty peer can submit stale challenges (meaning for a “supported state” which has been superseded by more recent agreements) and thereby lay claim to funds “already spent” off-chain.

We will enhance the go-nitro client to detect challenges on chain and respond with a proof of the newer agreement. This will lead to the stale challenge being cancelled (”thrown out of court”).

Deliverables:

A new protocol defend which is spawned automatically by go-nitro when: a challenge goes on chain it was not generated by this client it risks an unfavourable outcome finalizing (that is to say, I have the ability to challenge with a more favourable outcome). An integration test running in testground which shows a go-nitro client responding appropriately to a malicious challenge


An extension to this would be to extract this module out into a sidecar / process with a very simple RPC API.:

RegisterSupportedState(channelId, candidate, proof) error

Regardless of the execution environment, the go-nitro node needs to call this method whenever a new state becomes supported in its store. Then, the watchtower will automatically watch the chain and respond to challenges.

geoknee commented 10 months ago

The watchtower can be smart:

The decision about whether a counterparty is "bad" should arguably not be decided per channel. If 0xDave is bad, let's act accordingly in all channels we have with them.

geoknee commented 10 months ago

Initially, we will build this into the node (maybe it lives inside the chain service).

Longer term, we may want to extract it into a separate service that could be run remotely by a 3rd party.

See suggestion above about the API which might make this easier (the watchtower never needs to contact the node, the node just keeps the watchtower up to date all the time).

geoknee commented 10 months ago

Prior art: https://discovery.ucl.ac.uk/id/eprint/10090374/1/main.pdf

lalexgap commented 10 months ago

it can decide if it is economically sensible to spend gas on responding to a given challenge

Even if it's not economically feasible I think there may also be motivation to "punish" the counterparty. Let's say we have the following two states between Alice and Bob:

Let's say Bob sees a challenge from Alice with s0. If Bob responds with s1 he prevents himself from losing 10 and Alice from holding onto 10. Bob might be willing to pay 12 in gas to "punish" Alice and force her to pay the 10 she promised.

Economic feasibility will also vary over time due to fluctuations in gas prices. If the network is busy now it might not make economic sense to respond to a challenge now. However it's possible that in the future (before the challenge timeout) gas prices drop and it becomes economically sensible to respond. It can also depend on how long we'd be willing to wait for our tx to be confirmed. We may be able to use a lower gas price and just wait longer (slightly related to https://github.com/statechannels/go-nitro/issues/1502) to keep things feasible.

However these are kind of edge cases, so for the initial implementation it's probably OK for the watchtower to perform a simple calculation based on whatever the gas oracle returns(when transactions are submitted to geth without a gas price, it uses the gas oracle to determine them). Alternatively we could just always respond ignoring whether or not it's economically feasible for now. Or have both options available controlled by a config value.

lalexgap commented 10 months ago

it risks an unfavourable outcome finalizing (that is to say, I have the ability to challenge with a more favourable outcome).

Right now with just the ConsensusApp and VirtualPaymentApp I think we're OK to do this, since the Outcome is all we care about. However I don't think this would work for any arbitrary IForceMoveAppsince their rules may mean it makes sense to respond even if the Outcome hasn't changed in our favour. IE: There could be a tic-tac-toe IForceMoveApp where the outcome doesn't change until the game is completed, but we still want to respond with our winning move.

Not something we'll run into soon, but it's probably worth designing the watchtower so it'll be easy to expand or add to these checks for different IForceMoveApp implementations.

lalexgap commented 10 months ago

or challenge in response and also "cancel running objectives" to bring the channel safely to a close

Could the node handle cancelling objectives when it detects a challenge event from it's own SC address? Otherwise in a future where the watchtower is separate, it's going to have to call anode API to cancel the objectives.

I also just realized that calling challenge means the watchtower must have a copy of the SC key, so it can sign the challenger sig. With checkpoint we don't need to sign anything special, we just submit the signed states that the watchtower has been given.

geoknee commented 10 months ago

I also just realized that calling challenge means the watchtower must have a copy of the SC key, so it can sign the challenger sig. With checkpoint we don't need to sign anything special, we just submit the signed states that the watchtower has been given.

Good point!

I think probably out of scope for now but maybe we could allow participants to nominate a 3rd party watchtower account which can sign challenge messages on its behalf. Or, the node could provide the relevant signature to the watchtower in anticipation of it needing to send a challenge.

https://docs.statechannels.org/protocol-tutorial/0070-finalizing-a-channel/#call-challenge

lalexgap commented 10 months ago

Or, the node could provide the relevant signature to the watchtower in anticipation of it needing to send a challenge.

That could be a nice solution! Where you provide a supported state to the watchtower you could provide the challenge signature for that supported state, meaning the watchtower is always capable of challenging but doesn't have to own the SC key.

lalexgap commented 10 months ago

Regardless of the execution environment, the go-nitro node needs to call this method whenever a new state becomes supported in its store. Then, the watchtower will automatically watch the chain and respond to challenges.

One thing I just realized is currently a go-nitro node doesn't really know when a state becomes supported; we only check if state is supported within our protocols. So we'll need to add some kind of mechanism to catch this. We'll also need to trigger RegisterSupportedState whenever we add a voucher to our voucher manager.

I think if we:

This should let the engine catch any change to a supported state and call RegisterSupportedState appropriately.