package proxy import ( "bytes" "fmt" "sync" . "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" "github.com/tendermint/tendermint/types" // ... tmspcli "github.com/tendermint/tmsp/client" "github.com/tendermint/tmsp/example/dummy" nilapp "github.com/tendermint/tmsp/example/nil" ) //----------------------------- // Tendermint's interface to the application consists of multiple connections type AppConns interface { Service Mempool() AppConnMempool Consensus() AppConnConsensus Query() AppConnQuery } func NewAppConns(config cfg.Config, clientCreator ClientCreator, state State, blockStore BlockStore) AppConns { return NewMultiAppConn(config, clientCreator, state, blockStore) } // a multiAppConn is made of a few appConns (mempool, consensus, query) // and manages their underlying tmsp clients, including the handshake // which ensures the app and tendermint are synced. // TODO: on app restart, clients must reboot together type multiAppConn struct { BaseService config cfg.Config state State blockStore BlockStore mempoolConn *appConnMempool consensusConn *appConnConsensus queryConn *appConnQuery clientCreator ClientCreator } // Make all necessary tmsp connections to the application func NewMultiAppConn(config cfg.Config, clientCreator ClientCreator, state State, blockStore BlockStore) *multiAppConn { multiAppConn := &multiAppConn{ config: config, state: state, blockStore: blockStore, clientCreator: clientCreator, } multiAppConn.BaseService = *NewBaseService(log, "multiAppConn", multiAppConn) return multiAppConn } // Returns the mempool connection func (app *multiAppConn) Mempool() AppConnMempool { return app.mempoolConn } // Returns the consensus Connection func (app *multiAppConn) Consensus() AppConnConsensus { return app.consensusConn } // Returns the query Connection func (app *multiAppConn) Query() AppConnQuery { return app.queryConn } func (app *multiAppConn) OnStart() error { app.BaseService.OnStart() // query connection querycli, err := app.clientCreator.NewTMSPClient() if err != nil { return err } app.queryConn = NewAppConnQuery(querycli) // mempool connection memcli, err := app.clientCreator.NewTMSPClient() if err != nil { return err } app.mempoolConn = NewAppConnMempool(memcli) // consensus connection concli, err := app.clientCreator.NewTMSPClient() if err != nil { return err } app.consensusConn = NewAppConnConsensus(concli) // ensure app is synced to the latest state return app.Handshake() } // TODO: retry the handshake once if it fails the first time func (app *multiAppConn) Handshake() error { // handshake is done on the query conn res, tmspInfo, blockInfo, configInfo := app.queryConn.InfoSync() if res.IsErr() { return fmt.Errorf("Error calling Info. Code: %v; Data: %X; Log: %s", res.Code, res.Data, res.Log) } if blockInfo == nil { log.Warn("blockInfo is nil, aborting handshake") return nil } log.Notice("TMSP Handshake", "height", blockInfo.BlockHeight, "block_hash", blockInfo.BlockHash, "app_hash", blockInfo.AppHash) // TODO: check overflow or change pb to int32 blockHeight := int(blockInfo.BlockHeight) blockHash := blockInfo.BlockHash appHash := blockInfo.AppHash if tmspInfo != nil { // TODO: check tmsp version (or do this in the tmspcli?) _ = tmspInfo } // of the last block (nil if we starting from 0) var header *types.Header var partsHeader types.PartSetHeader // check block // if the blockHeight == 0, we will replay everything if blockHeight != 0 { blockMeta := app.blockStore.LoadBlockMeta(blockHeight) if blockMeta == nil { return fmt.Errorf("Handshake error. Could not find block #%d", blockHeight) } // check block hash if !bytes.Equal(blockMeta.Hash, blockHash) { return fmt.Errorf("Handshake error. Block hash at height %d does not match. Got %X, expected %X", blockHeight, blockHash, blockMeta.Hash) } // check app hash if !bytes.Equal(blockMeta.Header.AppHash, appHash) { return fmt.Errorf("Handshake error. App hash at height %d does not match. Got %X, expected %X", blockHeight, appHash, blockMeta.Header.AppHash) } header = blockMeta.Header partsHeader = blockMeta.PartsHeader } if configInfo != nil { // TODO: set config info _ = configInfo } // replay blocks up to the latest in the blockstore err := app.state.ReplayBlocks(header, partsHeader, app.consensusConn, app.blockStore) if err != nil { return fmt.Errorf("Error on replay: %v", err) } // TODO: (on restart) replay mempool return nil } //-------------------------------- // Get a connected tmsp client func NewTMSPClient(addr, transport string) (tmspcli.Client, error) { var client tmspcli.Client // use local app (for testing) // TODO: local proxy app conn switch addr { case "nilapp": app := nilapp.NewNilApplication() mtx := new(sync.Mutex) // TODO client = tmspcli.NewLocalClient(mtx, app) case "dummy": app := dummy.NewDummyApplication() mtx := new(sync.Mutex) // TODO client = tmspcli.NewLocalClient(mtx, app) default: // Run forever in a loop mustConnect := false remoteApp, err := tmspcli.NewClient(addr, transport, mustConnect) if err != nil { return nil, fmt.Errorf("Failed to connect to proxy for mempool: %v", err) } client = remoteApp } return client, nil }