From 9263a612befcf4ea2b3e1c69bc0fb325fde143c8 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 21:26:43 -0800 Subject: [PATCH 1/4] Intermediate --- plugins/ibc/ibc.go | 272 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 plugins/ibc/ibc.go diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go new file mode 100644 index 000000000000..f1f81a6dc4ce --- /dev/null +++ b/plugins/ibc/ibc.go @@ -0,0 +1,272 @@ +package ibc + +import ( + "fmt" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/types" + "github.com/tendermint/go-wire" + tm "github.com/tendermint/tendermint/types" +) + +const ( + // Key parts + _IBC = "ibc" + _BLOCKCHAIN = "blockchain" + _GENESIS = "genesis" + _STATE = "state" + _HASHES = "hashes" + _EGRESS = "egress" + _CONNECTION = "connection" +) + +type IBCPluginState struct { + // @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis + // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState + // @[:ibc, :blockchain, :hashes, ChainID, Height] <~ some blockhash []byte + // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet + // @[: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 + Type string + Payload []byte +} + +//-------------------------------------------------------------------------------- + +const ( + IBCTxTypeRegisterChain = byte(0x01) + IBCTxTypeUpdateChain = byte(0x02) + IBCTxTypePacket = byte(0x03) +) + +var _ = wire.RegisterInterface( + struct{ IBCTx }{}, + wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain}, + wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain}, + wire.ConcreteType{IBCPacketTx{}, IBCTxTypePacket}, +) + +type IBCTx interface { + AssertIsIBCTx() + ValidateBasic() abci.Result +} + +func (IBCRegisterChainTx) AssertIsIBCTx() {} +func (IBCUpdateChainTx) AssertIsIBCTx() {} +func (IBCPacketTx) AssertIsIBCTx() {} + +type IBCRegisterChainTx struct { + BlockchainGenesis +} + +func (IBCRegisterChainTx) ValidateBasic() abci.Result { + // TODO - validate + return +} + +type IBCUpdateChainTx struct { + Header tm.Header + Commit tm.Commit + // TODO: NextValidators +} + +func (IBCUpdateChainTx) ValidateBasic() abci.Result { + // TODO - validate + return +} + +type IBCPacketTx struct { + 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 + Proof merkle.IAVLProof +} + +func (IBCPacketTx) ValidateBasic() abci.Result { + // TODO - validate + return +} + +//-------------------------------------------------------------------------------- + +type IBCPlugin struct { +} + +func (ibc *IBCPlugin) Name() string { + "IBC" +} + +func (ibc *IBCPlugin) StateKey() []byte { + return []byte(fmt.Sprintf("IBCPlugin.State", ibc.name)) +} + +func New(name string) *IBCPlugin { + return &IBCPlugin{ + name: name, + } +} + +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 { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // 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) + case IBCPacketTx: + sm.runPacketTx(tx) + } + + 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, chain.ChainID) + chainGen := tx.BlockchainGenesis + + // Make sure chainGen doesn't already exist + if exists(sm.store, chainGenKey) { + return // Already exists, do nothing + } + + // Save new BlockchainGenesis + save(sm.store, chainGenKey, chainGen) + + // TODO - Create and save new BlockchainState +} + +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("Loading ChainState: %v", err.Error()) + return + } + + // TODO Compute blockHash from Header + // TODO Check commit against validators + // NOTE: Commit's votes include ValidatorAddress, so can be matched up against chainState.Validators + // for the demo we could assume that the validator set hadn't changed, + // though we should check that explicitly. + // TODO Store blockhash + // TODO Update chainState + // TODO Store chainState +} + +func (sm *IBCStateMachine) runPacketTx(tx IBCPacketTx) { + // TODO Make sure packat doesn't already exist + // TODO Load associated blockHash and make sure it exists + // TODO compute packet key + // TODO Make sure packet's proof matches given (packet, key, blockhash) + // TODO Store packet +} + +func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { +} + +func (ibc *IBCPlugin) BeginBlock(store types.KVStore, height uint64) { +} + +func (ibc *IBCPlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { + return nil +} + +//-------------------------------------------------------------------------------- +// 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( + 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, ",")) +} From 01b5588b8ec3c40a3b9c9afd7fe379f8cf556c0e Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 07:43:51 -0800 Subject: [PATCH 2/4] Intermediate - finished runRegisterChainTx --- plugins/ibc/ibc.go | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index f1f81a6dc4ce..eb500ed52068 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -172,17 +172,47 @@ type IBCStateMachine struct { func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, chain.ChainID) + chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chain.ChainID) chainGen := tx.BlockchainGenesis + // Parse genesis + var chainGenDoc = &tm.GenesisDoc{} + var err error + wire.ReadJSONPtr(&chainGenDoc, []byte(chianGen), &err) + if err != nil { + sm.res.AppendLog("Genesis doc couldn't be parsed: " + err.Error()) + return + } + // Make sure chainGen doesn't already exist if exists(sm.store, chainGenKey) { - return // Already exists, do nothing + sm.res.AppendLog("Already exists") + return } // Save new BlockchainGenesis save(sm.store, chainGenKey, chainGen) - // TODO - Create and save new BlockchainState + // Create new BlockchainState + chainState := BlockchainState{ + ChainID: chain.ChainID, + Validators: make([]*tm.Validator, len(chainGen.Validators)), + LastBlockHash: nil, + LastBlockHeight: 0, + } + // Make validators slice + for i, val := range chainGenDoc.Validators { + pubKey := val.PubKey + address := pubKey.Address() + chainState.Validators[i] = &types.Validator{ + Address: address, + PubKey: pubKey, + VotingPower: val.Amount, + } + } + + // Save new BlockchainState + save(sm.store, chainStateKey, chainState) } func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { From a8a1ae5382ac3af64f818f79efadbd0c4107593d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 08:10:23 -0800 Subject: [PATCH 3/4] Store headers, not just the block hash --- plugins/ibc/ibc.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index eb500ed52068..52ee33785ff1 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -15,7 +15,7 @@ const ( _BLOCKCHAIN = "blockchain" _GENESIS = "genesis" _STATE = "state" - _HASHES = "hashes" + _HEADER = "header" _EGRESS = "egress" _CONNECTION = "connection" ) @@ -23,7 +23,7 @@ const ( type IBCPluginState struct { // @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState - // @[:ibc, :blockchain, :hashes, ChainID, Height] <~ some blockhash []byte + // @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet // @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state } @@ -232,7 +232,8 @@ func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { return } - // TODO Compute blockHash from Header + // Compute blockHash from Header + blockHash := tx.Header.Hash() // TODO Check commit against validators // NOTE: Commit's votes include ValidatorAddress, so can be matched up against chainState.Validators // for the demo we could assume that the validator set hadn't changed, From 50e242b1ec77e5683dbf4947abddaf7c57d9b45a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 15:06:51 -0800 Subject: [PATCH 4/4] make it compile at least --- plugins/ibc/ibc.go | 105 ++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 52ee33785ff1..8a8d4d9fcb8f 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -1,10 +1,14 @@ package ibc import ( - "fmt" + "errors" + "net/url" + "strings" abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + merkle "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" tm "github.com/tendermint/tendermint/types" ) @@ -35,7 +39,7 @@ type BlockchainGenesis struct { type BlockchainState struct { ChainID string - Validators []tm.Validator + Validators []*tm.Validator LastBlockHash []byte LastBlockHeight uint64 } @@ -76,7 +80,7 @@ type IBCRegisterChainTx struct { BlockchainGenesis } -func (IBCRegisterChainTx) ValidateBasic() abci.Result { +func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) { // TODO - validate return } @@ -87,7 +91,7 @@ type IBCUpdateChainTx struct { // TODO: NextValidators } -func (IBCUpdateChainTx) ValidateBasic() abci.Result { +func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) { // TODO - validate return } @@ -99,7 +103,7 @@ type IBCPacketTx struct { Proof merkle.IAVLProof } -func (IBCPacketTx) ValidateBasic() abci.Result { +func (IBCPacketTx) ValidateBasic() (res abci.Result) { // TODO - validate return } @@ -110,17 +114,15 @@ type IBCPlugin struct { } func (ibc *IBCPlugin) Name() string { - "IBC" + return "IBC" } func (ibc *IBCPlugin) StateKey() []byte { - return []byte(fmt.Sprintf("IBCPlugin.State", ibc.name)) + return []byte("IBCPlugin.State") } -func New(name string) *IBCPlugin { - return &IBCPlugin{ - name: name, - } +func New() *IBCPlugin { + return &IBCPlugin{} } func (ibc *IBCPlugin) SetOption(store types.KVStore, key string, value string) (log string) { @@ -136,7 +138,7 @@ func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes } // Validate tx - res := tx.ValidateBasic() + res = tx.ValidateBasic() if res.IsErr() { return res.PrependLog("ValidateBasic Failed: ") } @@ -171,14 +173,14 @@ type IBCStateMachine struct { } func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { - chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, chain.ChainID) - chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chain.ChainID) + chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID) + chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID) chainGen := tx.BlockchainGenesis // Parse genesis var chainGenDoc = &tm.GenesisDoc{} var err error - wire.ReadJSONPtr(&chainGenDoc, []byte(chianGen), &err) + wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err) if err != nil { sm.res.AppendLog("Genesis doc couldn't be parsed: " + err.Error()) return @@ -195,8 +197,8 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { // Create new BlockchainState chainState := BlockchainState{ - ChainID: chain.ChainID, - Validators: make([]*tm.Validator, len(chainGen.Validators)), + ChainID: chainGenDoc.ChainID, + Validators: make([]*tm.Validator, len(chainGenDoc.Validators)), LastBlockHash: nil, LastBlockHeight: 0, } @@ -204,7 +206,7 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { for i, val := range chainGenDoc.Validators { pubKey := val.PubKey address := pubKey.Address() - chainState.Validators[i] = &types.Validator{ + chainState.Validators[i] = &tm.Validator{ Address: address, PubKey: pubKey, VotingPower: val.Amount, @@ -228,19 +230,31 @@ func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { var chainState BlockchainState exists, err := load(sm.store, chainStateKey, &chainState) if err != nil { - sm.res = abci.ErrInternalError.AppendLog("Loading ChainState: %v", err.Error()) + 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 { + sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Invalid Commit: %v", err.Error())) return } - // Compute blockHash from Header - blockHash := tx.Header.Hash() - // TODO Check commit against validators - // NOTE: Commit's votes include ValidatorAddress, so can be matched up against chainState.Validators - // for the demo we could assume that the validator set hadn't changed, - // though we should check that explicitly. - // TODO Store blockhash - // TODO Update chainState - // TODO Store chainState + // 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) } func (sm *IBCStateMachine) runPacketTx(tx IBCPacketTx) { @@ -279,7 +293,7 @@ func load(store types.KVStore, key []byte, ptr interface{}) (exists bool, err er err = wire.ReadBinaryBytes(value, ptr) if err != nil { return true, errors.New( - Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()), + cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()), ) } return true, nil @@ -301,3 +315,36 @@ func toKey(parts ...string) []byte { } 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)) + } + 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 + vote0 := commit.Precommits[0] + vals := chainState.Validators + valSet := tm.NewValidatorSet(vals) + + // 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. + err := valSet.VerifyCommit(chainID, vote0.BlockID, vote0.Height, commit) + if err != nil { + return err + } + + // All ok! + return nil +}