Can anyone solo close a channel with a contract inside?


#1

I’ve been trying out channels with the Epoch Websocket Server, Python SDK and the Swagger API but I ran into a problem.

I manage to open a channel, create a contract inside, and call its functions. But if I try to solo close this channel, the solo close transaction does not get mined. Epoch sends it, but miners reject it. The miner’s log says that the tx is invalid, the error is “invalid POI hash”.

This is with solo close transactions constructed using the debug endpoint where the POI is obtained from the WS API using the getPoi function and the payload is the latest mutually signed channel state.

Did anyone else get this to work? Maybe it’s not implemented yet?

Solo closing a channel without a contract in it works fine.


#2

Here’s an example:

Channel State: tx_+QEhCwH4hLhAqid+trhVao1ZsVdCZuSkKly5+UAsq6mu7/M2TZExLV5Vsh41KOo+ZxLdkOWQtj7ctITLlpc71Ul/McF5rX5WBbhArqlZLhaLvVyW1ornAklw2L1u0oovlJYWCrYgfBGJ31taE/icOpemINKDTyanlgdupqSTOtODOW7XLJ/sl1JgB7iX+JU5AaEGyTmbByXRIausrhCOofDHfPFkBYkMAcMPjiMU6CbxGXYH+E24S/hJggI6AaEBhQ28rSi+fAwdzrAiB4k1FxYD2gF7EOg5DLwZ+fTnFJ2hAexHBYw0cRKr7MkjpAYyigrfnm3zk9lEoMqpTCU0cMG/AKBNDBvwhhFBK1BBE3fI7UJlvn4hTew4QomawrwTgAXeCtTxMRI=

POI: pi_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

Channel Close Solo Tx: tx_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

I can decode the state using aeternity.hashing.decode_rlp() and the serialization docs and get this, which seems right (I recognize the addresses, channel id, and the last operation was a null transfer):

channel id ... ch_2Xd2LjrMn2EtLd6p5EjqFaSDP4HKZRdD2gzwisYBp8y8X1NEpL
round ... 7
updates ... [ChannelOffchainUpdateTransfer(from='ak_21bgH1gJT53UQ459gZyfvFSyY9KDu2dWx3EaJhLv3468fc8QUp', to='ak_2o4PcFqLCXLiYXBqvHnoHbgTk1VsMQUKCQsJLwvwyi7NCWquZP', amount=0)]
state_hash ... 'st_TQwb8IYRQStQQRN3yO1CZb5+IU3sOEKJmsK8E4AF3gqXdbsM'

But the POI beats me, it’s opaque to me. The error message sounds like the root hash of the POI (merkle proof) is different from the state_hash. Or that the POI is just invalid as a merkle proof. To be honest I’m not even sure what we want to prove to the blockchain in the channel close transaction. The balances of the participants, sure, but what are we proving about the contract? Its state, balance, and code? Or just the balance and owner?


#3

Hi @bakkhos
The state you’ve provided has the following tx:

{
  "channel_id": "ch_2Xd2LjrMn2EtLd6p5EjqFaSDP4HKZRdD2gzwisYBp8y8X1NEpL",
  "round": 7,
  "state_hash": "st_TQwb8IYRQStQQRN3yO1CZb5+IU3sOEKJmsK8E4AF3gqXdbsM",
  "type": "ChannelOffchainTx",
  "updates": [
    {
      "am": 0,
      "from": "ak_21bgH1gJT53UQ459gZyfvFSyY9KDu2dWx3EaJhLv3468fc8QUp",
      "op": "OffChainTransfer",
      "to": "ak_2o4PcFqLCXLiYXBqvHnoHbgTk1VsMQUKCQsJLwvwyi7NCWquZP"
    }
  ],
  "version": 1
}

Note that the state_hash part - this is the roof of the channel’s off-chain state tree at this round. In order for the proof of inclusion to be correct, it must have the same root hash as the one provided with the co-signed state. The proof of inclusion provided has a root has of st_anpkp5JjiBqaY9BIhhze0JWvRMYgoWrDl+3EUcOGcPWSlCkS so those mismatch. This is the cause of the issue and thus - the error invalid POI hash.

The idea behind a co-signed state is that a both participants had agreed upon it, at least at some point of time. The state provided could as well be an old one and there are dispute mechanisms to tackle this and this is not the point now. Enter the proof of inclusion - it is supposed to be a subset of the channel’s off-chain state tree. That’s why the state_hash and the PoI must have the exact same root hash: the former provides a proof that the latter indeed is a subtree of a state valid at some point of time. Without this proof, on-chain validation can not validate that the proof of inclusion indeed is a valid one.

What I supposed happened is that the proof of inclusion was extracted from a different round - either before round 7 or after it. In this case it represents the channel’s off-chain state tree of either an earlier or later state than the one provided (round 7). Sadly, this is something I can not determine from the proof of inclusion itself.

By the way I failed deserializing the close solo transaction provided.


#4

Actually I did deserialize it, it is an unsigned transaction :slight_smile:


#5

Thanks for the pointer.

What tool did you use to convert the state to JSON? Would it work on the POI?


#6

I was using plain basic aeternity node, but this requires some inside knowledge for the API as well as some Erlang knowledge. Currently there is no API to transform the PoI into a JSON.


#7

I got it to work. I used an older POI… one round older than the one I posted.

It seems like the state_hash contains the hash of the state tree before the updates are applied. I had thought it was the one after.