cosmos-sdk/plugins/ibc/ibc.go

577 lines
15 KiB
Go
Raw Normal View History

2017-01-28 21:26:43 -08:00
package ibc
import (
2017-02-13 20:22:31 -08:00
"bytes"
2017-05-13 17:04:53 -07:00
"encoding/json"
2017-01-28 21:26:43 -08:00
"errors"
2017-05-21 08:08:34 -07:00
"fmt"
2017-01-28 21:26:43 -08:00
"net/url"
2017-05-21 12:36:46 -07:00
"strconv"
2017-01-28 21:26:43 -08:00
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire"
2017-05-13 17:04:53 -07:00
merkle "github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/types"
2017-01-28 21:26:43 -08:00
tm "github.com/tendermint/tendermint/types"
)
const (
// Key parts
_IBC = "ibc"
_BLOCKCHAIN = "blockchain"
_GENESIS = "genesis"
_STATE = "state"
_HEADER = "header"
_EGRESS = "egress"
2017-01-29 19:54:38 -08:00
_INGRESS = "ingress"
2017-01-28 21:26:43 -08:00
_CONNECTION = "connection"
)
type IBCPluginState struct {
// @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis
// @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState
// @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header
// @[:ibc, :egress, Src, Dst, Sequence] <~ Packet
2017-01-29 19:54:38 -08:00
// @[:ibc, :ingress, Dst, Src, Sequence] <~ Packet
2017-01-28 21:26:43 -08:00
// @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state
}
type BlockchainGenesis struct {
ChainID string
Genesis string
}
type BlockchainState struct {
ChainID string
Validators []*tm.Validator
LastBlockHash []byte
LastBlockHeight uint64
}
type Packet struct {
SrcChainID string
DstChainID string
Sequence uint64
2017-05-21 12:36:46 -07:00
Type string // redundant now that Type() is a method on Payload ?
2017-05-21 08:08:34 -07:00
Payload Payload
}
2017-05-21 12:36:46 -07:00
func NewPacket(src, dst string, seq uint64, payload Payload) Packet {
2017-05-21 08:08:46 -07:00
return Packet{
SrcChainID: src,
DstChainID: dst,
Sequence: seq,
2017-05-21 12:36:46 -07:00
Type: payload.Type(),
2017-05-21 08:08:46 -07:00
Payload: payload,
}
}
2017-05-21 12:36:46 -07:00
// GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain
func GetSequenceNumber(store types.KVStore, src, dst string) uint64 {
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
seqBytes := store.Get(sequenceKey)
if seqBytes == nil {
return 0
}
seq, err := strconv.ParseUint(string(seqBytes), 10, 64)
if err != nil {
cmn.PanicSanity(err.Error())
}
return seq
}
// SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain
func SetSequenceNumber(store types.KVStore, src, dst string, seq uint64) {
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10)))
}
// SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain
// using the correct sequence number. It also increments the sequence number by 1
func SaveNewIBCPacket(state types.KVStore, src, dst string, payload Payload) {
// fetch sequence number and increment by 1
seq := GetSequenceNumber(state, src, dst)
SetSequenceNumber(state, src, dst, seq+1)
// save ibc packet
packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
packet := NewPacket(src, dst, uint64(seq), payload)
save(state, packetKey, packet)
}
2017-05-21 08:08:34 -07:00
//--------------------------------------------------------------------------------
const (
PayloadTypeBytes = byte(0x01)
PayloadTypeCoins = byte(0x02)
)
var _ = wire.RegisterInterface(
struct{ Payload }{},
2017-05-21 12:36:46 -07:00
wire.ConcreteType{DataPayload{}, PayloadTypeBytes},
2017-05-21 08:08:34 -07:00
wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins},
)
type Payload interface {
AssertIsPayload()
2017-05-21 12:36:46 -07:00
Type() string
2017-05-21 08:08:34 -07:00
ValidateBasic() abci.Result
}
2017-05-21 12:36:46 -07:00
func (DataPayload) AssertIsPayload() {}
2017-05-21 08:08:34 -07:00
func (CoinsPayload) AssertIsPayload() {}
2017-05-21 12:36:46 -07:00
type DataPayload []byte
2017-05-21 08:08:34 -07:00
2017-05-21 12:36:46 -07:00
func (p DataPayload) Type() string {
return "data"
}
func (p DataPayload) ValidateBasic() abci.Result {
2017-05-21 08:08:34 -07:00
return abci.OK
}
type CoinsPayload struct {
Address []byte
Coins types.Coins
}
2017-05-21 12:36:46 -07:00
func (p CoinsPayload) Type() string {
return "coin"
}
2017-05-21 08:08:34 -07:00
func (p CoinsPayload) ValidateBasic() abci.Result {
// TODO: validate
return abci.OK
2017-01-28 21:26:43 -08:00
}
//--------------------------------------------------------------------------------
const (
IBCTxTypeRegisterChain = byte(0x01)
IBCTxTypeUpdateChain = byte(0x02)
2017-01-29 19:14:58 -08:00
IBCTxTypePacketCreate = byte(0x03)
IBCTxTypePacketPost = byte(0x04)
2017-01-29 18:42:25 -08:00
2017-01-29 19:54:38 -08:00
IBCCodeEncodingError = abci.CodeType(1001)
IBCCodeChainAlreadyExists = abci.CodeType(1002)
IBCCodePacketAlreadyExists = abci.CodeType(1003)
IBCCodeUnknownHeight = abci.CodeType(1004)
2017-01-29 21:16:51 -08:00
IBCCodeInvalidCommit = abci.CodeType(1005)
IBCCodeInvalidProof = abci.CodeType(1006)
2017-01-28 21:26:43 -08:00
)
var _ = wire.RegisterInterface(
struct{ IBCTx }{},
wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain},
wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain},
2017-01-29 19:14:58 -08:00
wire.ConcreteType{IBCPacketCreateTx{}, IBCTxTypePacketCreate},
wire.ConcreteType{IBCPacketPostTx{}, IBCTxTypePacketPost},
2017-01-28 21:26:43 -08:00
)
type IBCTx interface {
AssertIsIBCTx()
ValidateBasic() abci.Result
}
func (IBCRegisterChainTx) AssertIsIBCTx() {}
func (IBCUpdateChainTx) AssertIsIBCTx() {}
2017-01-29 19:14:58 -08:00
func (IBCPacketCreateTx) AssertIsIBCTx() {}
func (IBCPacketPostTx) AssertIsIBCTx() {}
2017-01-28 21:26:43 -08:00
type IBCRegisterChainTx struct {
BlockchainGenesis
}
func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
type IBCUpdateChainTx struct {
Header tm.Header
Commit tm.Commit
// TODO: NextValidators
}
func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
2017-01-29 19:14:58 -08:00
type IBCPacketCreateTx struct {
Packet
}
func (IBCPacketCreateTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
type IBCPacketPostTx struct {
2017-01-28 21:26:43 -08:00
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof
Packet
2017-01-29 22:31:05 -08:00
Proof *merkle.IAVLProof
2017-01-28 21:26:43 -08:00
}
2017-01-29 19:14:58 -08:00
func (IBCPacketPostTx) ValidateBasic() (res abci.Result) {
2017-01-28 21:26:43 -08:00
// TODO - validate
return
}
//--------------------------------------------------------------------------------
type IBCPlugin struct {
}
func (ibc *IBCPlugin) Name() string {
return "IBC"
}
func (ibc *IBCPlugin) StateKey() []byte {
return []byte("IBCPlugin.State")
}
func New() *IBCPlugin {
return &IBCPlugin{}
}
func (ibc *IBCPlugin) SetOption(store types.KVStore, key string, value string) (log string) {
return ""
}
func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
var tx IBCTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
2017-01-29 21:16:51 -08:00
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
2017-01-28 21:26:43 -08:00
}
// Validate tx
res = tx.ValidateBasic()
if res.IsErr() {
return res.PrependLog("ValidateBasic Failed: ")
}
// TODO - Check whether sufficient funds
defer func() {
// TODO - Refund any remaining funds left over
// e.g. !ctx.Coins.Minus(tx.Fee).IsZero()
// ctx.CallerAccount is synced w/ store, so just modify that and store it.
// NOTE: We should use the CallContext to store fund/refund information.
}()
sm := &IBCStateMachine{store, ctx, abci.OK}
switch tx := tx.(type) {
case IBCRegisterChainTx:
sm.runRegisterChainTx(tx)
case IBCUpdateChainTx:
sm.runUpdateChainTx(tx)
2017-01-29 19:14:58 -08:00
case IBCPacketCreateTx:
sm.runPacketCreateTx(tx)
case IBCPacketPostTx:
sm.runPacketPostTx(tx)
2017-01-28 21:26:43 -08:00
}
return sm.res
}
type IBCStateMachine struct {
store types.KVStore
ctx types.CallContext
res abci.Result
}
func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) {
chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID)
chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID)
chainGen := tx.BlockchainGenesis
// Parse genesis
2017-05-13 17:04:53 -07:00
chainGenDoc := new(tm.GenesisDoc)
err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc)
2017-01-28 21:26:43 -08:00
if err != nil {
2017-01-29 18:42:25 -08:00
sm.res.Code = IBCCodeEncodingError
2017-01-29 20:46:01 -08:00
sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error()
2017-01-28 21:26:43 -08:00
return
}
// Make sure chainGen doesn't already exist
if exists(sm.store, chainGenKey) {
2017-01-29 18:42:25 -08:00
sm.res.Code = IBCCodeChainAlreadyExists
2017-01-29 20:46:01 -08:00
sm.res.Log = "Already exists"
2017-01-28 21:26:43 -08:00
return
}
// Save new BlockchainGenesis
save(sm.store, chainGenKey, chainGen)
// Create new BlockchainState
chainState := BlockchainState{
ChainID: chainGenDoc.ChainID,
Validators: make([]*tm.Validator, len(chainGenDoc.Validators)),
LastBlockHash: nil,
LastBlockHeight: 0,
}
// Make validators slice
for i, val := range chainGenDoc.Validators {
pubKey := val.PubKey
address := pubKey.Address()
chainState.Validators[i] = &tm.Validator{
Address: address,
PubKey: pubKey,
VotingPower: val.Amount,
}
}
// Save new BlockchainState
save(sm.store, chainStateKey, chainState)
}
func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) {
chainID := tx.Header.ChainID
chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chainID)
// Make sure chainState exists
if !exists(sm.store, chainStateKey) {
return // Chain does not exist, do nothing
}
// Load latest chainState
var chainState BlockchainState
exists, err := load(sm.store, chainStateKey, &chainState)
if err != nil {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading ChainState: %v", err.Error()))
return
}
if !exists {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Missing ChainState"))
return
}
// Check commit against last known state & validators
err = verifyCommit(chainState, &tx.Header, &tx.Commit)
if err != nil {
2017-01-29 21:16:51 -08:00
sm.res.Code = IBCCodeInvalidCommit
sm.res.Log = cmn.Fmt("Invalid Commit: %v", err.Error())
2017-01-28 21:26:43 -08:00
return
}
// Store header
headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, chainID, cmn.Fmt("%v", tx.Header.Height))
save(sm.store, headerKey, tx.Header)
// Update chainState
chainState.LastBlockHash = tx.Header.Hash()
chainState.LastBlockHeight = uint64(tx.Header.Height)
// Store chainState
save(sm.store, chainStateKey, chainState)
}
2017-01-29 19:14:58 -08:00
func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
2017-01-29 19:54:38 -08:00
packet := tx.Packet
packetKey := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
// Make sure packet doesn't already exist
if exists(sm.store, packetKey) {
sm.res.Code = IBCCodePacketAlreadyExists
2017-01-29 20:46:01 -08:00
// TODO: .AppendLog() does not update sm.res
sm.res.Log = "Already exists"
2017-01-29 19:54:38 -08:00
return
}
2017-05-21 08:08:34 -07:00
// Execute the payload
switch payload := tx.Packet.Payload.(type) {
2017-05-21 12:36:46 -07:00
case DataPayload:
2017-05-21 08:08:34 -07:00
// do nothing
case CoinsPayload:
// ensure enough coins were sent in tx to cover the payload coins
if !sm.ctx.Coins.IsGTE(payload.Coins) {
sm.res.Code = abci.CodeType_InsufficientFunds
sm.res.Log = fmt.Sprintf("Not enough funds sent in tx (%v) to send %v via IBC", sm.ctx.Coins, payload.Coins)
return
}
// deduct coins from context
sm.ctx.Coins = sm.ctx.Coins.Minus(payload.Coins)
}
2017-01-29 19:54:38 -08:00
// Save new Packet
2017-01-29 22:31:05 -08:00
save(sm.store, packetKey, packet)
2017-01-29 19:14:58 -08:00
}
func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
2017-01-29 19:54:38 -08:00
packet := tx.Packet
packetKeyEgress := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
packetKeyIngress := toKey(_IBC, _INGRESS,
packet.DstChainID,
packet.SrcChainID,
cmn.Fmt("%v", packet.Sequence),
)
headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER,
tx.FromChainID,
cmn.Fmt("%v", tx.FromChainHeight),
)
// Make sure packet doesn't already exist
if exists(sm.store, packetKeyIngress) {
sm.res.Code = IBCCodePacketAlreadyExists
2017-01-29 20:46:01 -08:00
sm.res.Log = "Already exists"
2017-01-29 19:54:38 -08:00
return
}
2017-05-21 08:08:34 -07:00
// Save new Packet (just for fun)
2017-01-29 22:31:05 -08:00
save(sm.store, packetKeyIngress, packet)
2017-01-29 19:54:38 -08:00
// Load Header and make sure it exists
2017-02-13 18:29:11 -08:00
// If it exists, we already checked a valid commit for it in UpdateChainTx
2017-01-29 19:54:38 -08:00
var header tm.Header
exists, err := load(sm.store, headerKey, &header)
if err != nil {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading Header: %v", err.Error()))
return
}
if !exists {
sm.res.Code = IBCCodeUnknownHeight
2017-01-29 22:43:07 -08:00
sm.res.Log = cmn.Fmt("Loading Header: Unknown height")
2017-01-29 19:54:38 -08:00
return
}
proof := tx.Proof
2017-01-29 22:31:05 -08:00
if proof == nil {
sm.res.Code = IBCCodeInvalidProof
sm.res.Log = "Proof is nil"
return
}
2017-01-29 19:54:38 -08:00
packetBytes := wire.BinaryBytes(packet)
// Make sure packet's proof matches given (packet, key, blockhash)
ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
if !ok {
sm.res.Code = IBCCodeInvalidProof
2017-01-29 20:46:01 -08:00
sm.res.Log = "Proof is invalid"
2017-01-29 19:54:38 -08:00
return
}
2017-05-21 08:08:34 -07:00
// Execute payload
switch payload := packet.Payload.(type) {
2017-05-21 12:36:46 -07:00
case DataPayload:
2017-05-21 08:08:34 -07:00
// do nothing
case CoinsPayload:
// Add coins to destination account
2017-05-21 12:36:46 -07:00
acc := types.GetAccount(sm.store, payload.Address)
2017-05-21 11:30:41 -07:00
if acc == nil {
acc = &types.Account{}
}
2017-05-21 08:08:34 -07:00
acc.Balance = acc.Balance.Plus(payload.Coins)
2017-05-21 12:36:46 -07:00
types.SetAccount(sm.store, payload.Address, acc)
2017-05-21 08:08:34 -07:00
}
2017-01-29 19:54:38 -08:00
return
2017-01-28 21:26:43 -08:00
}
func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
}
2017-02-04 14:47:51 -08:00
func (cp *IBCPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
2017-01-28 21:26:43 -08:00
}
2017-02-04 14:47:51 -08:00
func (cp *IBCPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) {
return
2017-01-28 21:26:43 -08:00
}
//--------------------------------------------------------------------------------
// TODO: move to utils
// Returns true if exists, false if nil.
func exists(store types.KVStore, key []byte) (exists bool) {
value := store.Get(key)
return len(value) > 0
}
// Load bytes from store by reading value for key and read into ptr.
// Returns true if exists, false if nil.
// Returns err if decoding error.
func load(store types.KVStore, key []byte, ptr interface{}) (exists bool, err error) {
value := store.Get(key)
if len(value) > 0 {
err = wire.ReadBinaryBytes(value, ptr)
if err != nil {
return true, errors.New(
cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()),
)
}
return true, nil
} else {
return false, nil
}
}
// Save bytes to store by writing obj's go-wire binary bytes.
func save(store types.KVStore, key []byte, obj interface{}) {
store.Set(key, wire.BinaryBytes(obj))
}
// Key parts are URL escaped and joined with ','
func toKey(parts ...string) []byte {
escParts := make([]string, len(parts))
for i, part := range parts {
escParts[i] = url.QueryEscape(part)
}
return []byte(strings.Join(escParts, ","))
}
// NOTE: Commit's votes include ValidatorAddress, so can be matched up
// against chainState.Validators, even if the validator set had changed.
// For the purpose of the demo, we assume that the validator set hadn't changed,
// though we should check that explicitly.
func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Commit) error {
// Ensure that chainState and header ChainID match.
if chainState.ChainID != header.ChainID {
return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID))
}
2017-02-13 18:29:11 -08:00
// Ensure things aren't empty
2017-01-28 21:26:43 -08:00
if len(chainState.Validators) == 0 {
return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen?
}
if len(commit.Precommits) == 0 {
return errors.New(cmn.Fmt("Commit has no signatures"))
}
chainID := chainState.ChainID
vals := chainState.Validators
valSet := tm.NewValidatorSet(vals)
2017-02-14 14:21:21 -08:00
blockID := commit.Precommits[0].BlockID // XXX: incorrect
2017-01-28 21:26:43 -08:00
// NOTE: Currently this only works with the exact same validator set.
// Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose
// the functionality to verify commits even after validator changes.
2017-02-14 14:21:21 -08:00
err := valSet.VerifyCommit(chainID, blockID, header.Height, commit)
2017-01-28 21:26:43 -08:00
if err != nil {
return err
}
2017-02-13 22:34:24 -08:00
// Ensure the committed blockID matches the header
if !bytes.Equal(header.Hash(), blockID.Hash) {
return errors.New(cmn.Fmt("blockID.Hash (%X) does not match header.Hash (%X)", blockID.Hash, header.Hash()))
}
2017-01-28 21:26:43 -08:00
// All ok!
return nil
}