Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS ?: Interchain account #251

Closed
Thunnini opened this issue Sep 5, 2019 · 9 comments
Closed

ICS ?: Interchain account #251

Thunnini opened this issue Sep 5, 2019 · 9 comments
Labels
app Application layer.

Comments

@Thunnini
Copy link

Thunnini commented Sep 5, 2019

Interchain account (draft)

I made some revisions to the proposal to include more specificities regarding the proposal #211 .

In summary, here are the core changes:

  • Revised the naming from "IBC managed account" to "Interchain account".

  • Added a rough concept for determining interchain account's address.

  • Revised so that the IBC packet includes the transaction raw bytes for the receiving chain. The receiving chain must decode these bytes to a transaction, then check that the sending chain has correct permission. If the sending chain has the correct permission, the receiving chain will execute the transaction. Finally, the receiving chain will return result packet to the sending chain.

    • This process will allow interchain accounts to be able to execute arbitrary transactions without additional consideration for the transaction's corresponding module.
    • After writing some pseudocode for cosmos-sdk, I think that this can be achieved fairly easily without making the Cosmos-sdk's code more complex thanks to Cosmos-SDK's module and router system.
  • Some use cases I can see with the implementation of the interchain account are as follows:

    • The use of interchain staking functions
    • Creation of The DAO-like zone that is able to participate and manages multiple hub/zone's governance and staking position.
    • Possibility of a decentralized CDP position (i.e. Kava) where the CDP position is managed by another zone to create a new derivative DeFi product.
    • A protocol that controls arbitrage in DeFi zones such as Terra / Kava and distributes profits to stakeholders in exchange for their capital.
    • And a host of other decentralized financial use-cases.

ics: (?)
title: (Interchain account)
stage: (Strawman)
category: (ibc-app)
author: (yunjh1994@ellipti.io)
created: (2019-08-21)
modified: (2019-08-21)

Synopsis

This standard document specifies packet data structure, state machine handling logic, and encoding details for the account management system over an IBC channel between separate chains.

Motivation

On Ethereum, there are two types of accounts: externally owned accounts, controlled by private keys, and contract accounts, controlled by their contract code [ref]. Similar to Ethereum's CA(contract accounts), Interchain accounts are managed by another chain(zone) while retaining all the capabilities of a normal account (i.e. stake, send, vote, etc). While an Ethereum CA's contract logic is performed within Ethereum's EVM, Interchain accounts are managed by another chain via IBC in a trustless way.

Definitions

The IBC handler interface & IBC relayer module interface are as defined in ICS 25 and ICS 26, respectively.

Desired Properties

  • Permissionless
  • Fault containment: Interchain account must follow rules of its dwelling chain, even in times of byzantine behavior by the counterparty chain (the chain that manages the account)
  • The chain(that controls the account) requesting a specific transaction from must process the results asynchronously and according to the chain's logic.
  • Sending and receiving transactions will be processed in an ordered manner.

Technical Specification

Data Structures

RegisterIBCAccountPacketData is used for counterparty chain to register an account. Interchain account's address is defined deterministically with channel path and salt. Process of determining address is influenced by EIP-1014. It allows interactions to be made with addresses that do not exist yet on-chain. These specificities will allow more offchain services to build on top of the data structure.

interface RegisterIBCAccountPacketData {
  salt: Uint8Array
}

ResultRegisterPacketData is used to let counterparty chain know if the account was created successfully.

interface ResultRegisterPacketData {
  address: Uint8Array
  success: boolean
}

RunTxPacketData is used to run tx for interchain account. Tx bytes is dependent on app, and it is formed with minimal data set except data for validating signatures.

interface RunTxPacketData {
  txBytes: Uint8Array
}
interface ResultRunTxPacketData{
  hash: Uint8Array // hash of tx
  code: number // response code
  data: Uint8Array // any data returned from the app
}

Subprotocols

The subprotocols described herein should be implemented in a "interchain-account-bridge" module with access to a router and codec (decoder or unmarshaller) for app and to the IBC relayer module.

Port & channel setup

The setup function must be called exactly once when the module is created (perhaps when the blockchain itself is initialized) to bind to the appropriate port and create an escrow address (owned by the module).

function setup() {
  relayerModule.bindPort("interchain-account", ModuleCallbacks{
    onChanOpenInit,
    onChanOpenTry,
    onChanOpenAck,
    onChanOpenConfirm,
    onChanCloseInit,
    onChanCloseConfirm,
    onSendPacket,
    onRecvPacket,
    onTimeoutPacket,
    onAcknowledgePacket,
    onTimeoutPacketClose
  })
}

Once the setup function has been called, channels can be created through the IBC relayer module between instances of the interchain account module on separate chains.

Relayer module callbacks

Channel lifecycle management

Both machines A and B accept new channels from any module on another machine, if and only if:

  • The other module is bound to the "interchain account" port.
  • The channel being created is ordered.
  • The version string is empty.
function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string) {
  // only ordered channels allowed
  assert(order === ORDERED)
  // only allow channels to "interchain-account" port on counterparty chain
  assert(counterpartyPortIdentifier === "interchain-account")
  // version not used at present
  assert(version === "")
}
function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string,
  counterpartyVersion: string) {
  // only ordered channels allowed
  assert(order === ORDERED)
  // version not used at present
  assert(version === "")
  assert(counterpartyVersion === "")
  // only allow channels to "interchain-account" port on counterparty chain
  assert(counterpartyPortIdentifier === "interchain-account")
}
function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  version: string) {
  // version not used at present
  assert(version === "")
  // port has already been validated
}
function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
  // accept channel confirmations, port has already been validated
}
function onChanCloseInit(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
  // no action necessary
}
function onChanCloseConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
  // no action necessary
}

Packet relay

In plain English, between chains A and B. It will describe only the case that chain A wants to register an Interchain account on chain B and control it. Moreover, this system can also be applied the other way around. In these set of examples, we follow Cosmos-SDK's modularity rule and its msg system.

interface ChainAccountTx {
  msgs: Msg[]
}
function onSendPacket(packet: Packet) {  
  // Sending chain can do preliminary work (i.e. create a log of transactions sent) if needed.
}
function onRecvPacket(packet: Packet): bytes {
  if (packet.data is RunTxPacketData) {
    ChainAccountTx data = {}
    // Decode transaction request
    codec.unmarshal(packet.data.txBytes, &data)
      
    signers = []
    for (const msg of data.msgs) {
      signers.push(msg.getSigners())  
    }
      
    path = "{packet/sourcePort}/{packet.sourceChannel}"
      
    // Check tx has right permissions
    for (const signer of signers){
      assert(path !== store.get(signer)
    }
    
    // Get handler from router
    handler = router(msg.route())
    assert(handler != null)
        
    // Execute handler with msg
    result = handler(msg)
        
    // Return result to sending chain
    sendPacket(ResultRunTxPacketData{
      hash: sha256(packet.data),
      code: result.code,
      data: result.data,    
    })
  }
    
  if (packet.data is RegisterIBCAccountPacketData) {
    RegisterIBCAccountPacketData data = packet.data
      
    path = "{packet/sourcePort}/{packet.sourceChannel}"
    address = sha256(path + packet.salt)
      
    // Should not block even if there is normal account,
    // because attackers can distrupt to create an ibc managed account
    // by sending some assets to estimated address in advance.
    // And IBC managed account has no public key, but its sequence is 1.
    // It can be mark for Interchain account, becuase normal account can't be sequence 1 without publish public key.
    account = accountKeeper.getAccount(account)
    if (account != null) {
      assert(account.sequence === 1 && account.pubKey == null)
    } else {
      accountKeeper.newAccount(address)
    }
      
    store.set(address, path)
        
    // set account's sequence to 1
    accountKeeper.setAccount(address, {
      ...,
      sequence: 1,
      ...,
    })
      
    sendPacket(ResultRegisterPacketData {
      address,
      success: true,
    })
  } 
    
  return 0x
}
function onAcknowledgePacket(
  packet: Packet,
  acknowledgement: bytes) {
  // nothing is necessary, likely this will never be called since it's a no-op
}
function onTimeoutPacket(packet: Packet) {
  // Receiving chain should handle this event as if the tx in packet has failed
}
function onTimeoutPacketClose(packet: Packet) {
  // Receiving chain should handle this event as if the tx in packet failed 
}

Backwards Compatibility

(discussion of compatibility or lack thereof with previous standards)

Forwards Compatibility

(discussion of compatibility or lack thereof with expected future standards)

Example Implementation

(link to or description of concrete example implementation)

Other Implementations

(links to or descriptions of other implementations)

History

(changelog and notable inspirations / references)

Copyright

All content herein is licensed under Apache 2.0.

@mossid mossid added brainstorming Open-ended brainstorming. app Application layer. and removed brainstorming Open-ended brainstorming. labels Sep 13, 2019
@ethanfrey
Copy link
Contributor

ethanfrey commented Sep 17, 2019

TL;DR great idea, but maybe in cosmos-sdk repo?

I agree highly with the motivation. And think it is very important to have a way to have an account controlled by another chain to allow lots of flexibility and many use cases (as mentioned above).

My feeling is that the current ICS spec enables exactly what you are requesting, and the execution of it depends on implementing a module on the relevant chains (which are not all cosmos-sdk based). In particular, it will be a small subset of chains that share the actual same tx data structure encoding (even among sdk-based chains, they only share the core modules, the additional modules are chain-specific).

I don't know where the proper place is for ICS implementation details is. Maybe this is? Maybe in the cosmos-sdk repo?

My question is: what here needs to be specified in the general level so that it is the same in all implementations? and what is a nice feature to add to implementations?

@ethanfrey
Copy link
Contributor

Sorry, just read #211 and you had that same conversation with cwgoes and mossid.
I agree to the theory of this and #44 in any case - great idea, just where is it spec'd

@Thunnini
Copy link
Author

#44 and #211 are similar ideas. And, this idea is different from the previous one. The previous one describes the ways to communicate with other chains sharing the same interfaces. But, this idea describes the ways to communicate with other chains not sharing the same interfaces but having each chain decode their own specified bytes. I think there is a tradeoff between them. The former approach enables each chain to communicate with a more general way, but it might make implementation more complex. In this case, we need to define the encoding/decoding spec and interfaces related to what functions will be executed and how their args will be passed such as function signature. But, it is fairly hard to share the same interfaces with other chains that have different architecture and they should implement these interfaces for each feature. But, the latter approach makes each chain define their interface and have them decode bytes and execute them. It makes the spec more easy, but each chain should know how counterparty chain defines the interface and how to make bytes for its interface. If they are made with the same framework such as cosmos-sdk, they might share some core modules such as bank, staking. So, it can communicate in a general way between them. And if the framework used when the chain was created supports the module and router system like cosmos-sdk, it can be easy to implement such system. In the case that chains that use a different framework, they should know how they define the architecture and how they are decoded and implement the ways counterparty chain defines the interface to communicate.

But in my opinion, the former approach is not different from "ics-020-fungible-token-transfer" spec in primitive point. Even though there is a sharing interface, we should define what function signature and args are needed for each module. Isn't it similar with the ways to implement specs for bank module "ics-?-fungible-token-trasnfor-for-interchain-account" and specs for gov module "ics-?-vote-for-interchain-account" or "ics-?-submit-proposal-for-interchain-account"? I wanted to make the most flexible and minimal way to communicate to manage accounts by other chains. In the latter approach define the primitive interface to send arbitrary bytes to be decoded and executed. If developers use the framework that supports module and router system and implements this spec, they can make their module that is able to be managed by interchain account without defining and implementing specs. I think this is an important part of this specification.

@ethanfrey I wrote the difference between the previous one and this and my thought about why I modified approach. Could you read and respond if you are interested in this topic.

@cwgoes
Copy link
Contributor

cwgoes commented Sep 17, 2019

Notes, by section:

  1. Motivation
    1. What is the entity managing the account - another module, another account, or the validator set as a whole?
  2. Desired Properties
    1. What does "permissionless" mean in this context?
  3. Technical Specification
    1. By "salt", do you mean "nonce" (as far as I understand, "salt" usually refers to passwords)
    2. Why is ResultRegisterPacketData another packet instead of an acknowledgement?
    3. By "it is formed with minimal data set except data for validating signatures", do you mean that the tx bytes include no signatures (which makes sense)? I think this may correspond to the "Msg" type in the SDK, generally, and I wonder if there is a general way to talk about that difference in abstractions.
    4. Why is ResultRunTxPacketData another packet instead of an acknowledgement?
    5. "It will describe only the case that chain A wants to register an Interchain account on chain B and control it. " - isn't this a particular module on chain A? Is there intended to be only one module running the interchain account protocol, or might there be several?
    6. I don't understand this line - assert(path !== store.get(signer) - what is the signer supposed to signify? Why are we checking not-equals? It seems like we should check if the signing addresses are in fact controlled by accounts which have been registered on this channel.

General feedback:

  1. I think this is an excellent start for an application-layer specification and will be quite useful. Feel free to start a PR if you like (a bit easier to comment on), although discussion in the issue is fine as well.
  2. I think the spec should include how this "interchain-account" module sends packets - accounts owned by a user on one chain should be associated with accounts owned by that user on the other chain through the interchain account module.
  3. We should consider whether we want "interchain multisignatures" and how that might work.

@ethanfrey
Copy link
Contributor

@Thunnini Thank you for the explanation. I understand better now. I also now see that it fits in with other such ics app-level specs.

I see this "interchain-account" to basically be some "root account" that has the permission of chainA. Not just saying "someone on my chain wants to send tokens", but "this chain decided to delegate our stake". Is that roguhly correct?

I like this idea. I also wonder about the idea of multiple such accounts (like many investment DAOs on one chain, each with own governance to control their staking on hub, or assets on DEX). However, that is an extension of this topic, and let us get this one clearly designed first.

It seems that you use a nonce/salt on connection to allow a given chain-port combo to have different addresses on each connection. I assume the intention to allow different connections to have different addresses is to allow such a multi-DAO setup, each with their own connection? Is that correct or can you explain better? The one pseudo-code comment is that this salt is in RegisterIBCAccountPacketData but not in RunTxPacketData block, so I don't see how this same address is used on following calls on the connection.

Besides that and the points Chris made (esp. 3(vi) stood out when I read the code), the rest looks quite nice.

@Hyung-bharvest
Copy link

I recently found that there exists a structure called "module account" in sdk. Until now, those accounts are controlled only by internal modules in sdk. I think what @Thunnini is trying to achieve is a generalization of this "module account" so that, the controlling entity can be exterior entity of the hub blockchain. I think this discussion should be well aligned with existing module account design.

@Thunnini
Copy link
Author

Thunnini commented Sep 19, 2019

@cwgoes
Thank for your feedback.

  1. Motivation
    1. What is the entity managing the account - another module, another account, or the validator set as a whole?
    • I think we need to add new module that aims to manage interchain account via IBC. Interchain accounts will be managed by module in code level.
  2. Desired Properties
    1. What does "permissionless" mean in this context?
    • I intended that it means that any chains can connect the port for interchain account module and open channel and register interchain accounts and send transactions for interchain account via IBC. I think that this system can’t spoil the logic on its dwelling chain, because interchain accounts follow rules of its dwelling chain. So, I think this can work with permissionless way.
  3. Technical Specification
    1. By "salt", do you mean "nonce" (as far as I understand, "salt" usually refers to passwords)
    • It is inspired by EIP-1014. I think the salt is not for security but just an additive for determining unique address.
    1. Why is ResultRegisterPacketData another packet instead of an acknowledgement?
    • I didn't fully understand ICS spec. So, I missed making use of acknowledgement packet. In next version of this proposal, I will use acknowledgement packet instead.
    1. By "it is formed with minimal data set except data for validating signatures", do you mean that the tx bytes include no signatures (which makes sense)? I think this may correspond to the "Msg" type in the SDK, generally, and I wonder if there is a general way to talk about that difference in abstractions.
    • I think that signatures are unnecessary to validate the permissions of interchain accounts because with explicit process for registering interchain account application can know which chains made the interchain account. And, interchain account's assets are managed by code in counterpary chain, it is valid that anyone can't send transactions by interchain accounts.
    1. Why is ResultRunTxPacketData another packet instead of an acknowledgement?
    • I didn't fully understand ICS spec. So, I missed making use of acknowledgement packet. In next version of this proposal, I will use acknowledgement packet instead.
    1. "It will describe only the case that chain A wants to register an Interchain account on chain B and control it. " - isn't this a particular module on chain A? Is there intended to be only one module running the interchain account protocol, or might there be several?
    • I think it is better that there is only one module that manages many interchain accounts in one place.
    1. I don't understand this line - assert(path !== store.get(signer) - what is the signer supposed to signify? Why are we checking not-equals? It seems like we should check if the signing addresses are in fact controlled by accounts which have been registered on this channel.
    • It is my mistake. It should be "assert(path === store.get(signer))". This validating process is identical to standard ante handler except for replacing the signature validation by validating that the signers are the interchain accounts made by counterparty chain on this channel. First, it iterates msgs in tx sent. And, it gets signers for msgs and collects these these signers. Finally, it checks the collected signers are have been registered on this channel.
      I think that it is very rough and have more rooms to improve. I'll try to keep on for further improvements.

@Thunnini
Copy link
Author

@ethanfrey
Yes, I think we are on the same page regarding interchain accounts and its use cases.

I think that Chain B can know the address to be made prior to sending RegisterIBCAccountPacketData by calculating the hash of channel path + salt.

So, basic process might be that Chain B calculates expected address to be created on Chain A and stores the information. Then, Chain B waits for the result (acknowledgement) packet from chain A, then stores the information that the same address has been successfully created on Chain A from the result packet that is sent back. If result packet says the account creation failed, Chain B may retry by registering an interchain account address with a different salt. And, chain B will use the stored address as signer to request transaction via IBC according to their protocol.

On 3(vi), looks like I've made a mistake 😅

@cwgoes
Copy link
Contributor

cwgoes commented Jan 4, 2020

Closing as the initial version of this has been merged. Thanks again!

@cwgoes cwgoes closed this as completed Jan 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
app Application layer.
Projects
None yet
Development

No branches or pull requests

5 participants