// Copyright 2015, 2016 Eris Industries (UK) Ltd. // This file is part of Eris-RT // Eris-RT is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Eris-RT is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. package erismint import ( "bytes" "fmt" db "github.com/tendermint/go-db" tendermint_events "github.com/tendermint/go-events" tendermint_types "github.com/tendermint/tendermint/types" wire "github.com/tendermint/go-wire" log "github.com/eris-ltd/eris-logger" account "github.com/eris-ltd/eris-db/account" config "github.com/eris-ltd/eris-db/config" definitions "github.com/eris-ltd/eris-db/definitions" event "github.com/eris-ltd/eris-db/event" manager_types "github.com/eris-ltd/eris-db/manager/types" rpc_tendermint_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types" state "github.com/eris-ltd/eris-db/manager/eris-mint/state" state_types "github.com/eris-ltd/eris-db/manager/eris-mint/state/types" transaction "github.com/eris-ltd/eris-db/txs" ) type ErisMintPipe struct { erisMintState *state.State eventSwitch *tendermint_events.EventSwitch erisMint *ErisMint // Pipe implementations accounts *accounts blockchain *blockchain consensus *consensus events event.EventEmitter namereg *namereg network *network transactor *transactor // Consensus interface consensusEngine definitions.ConsensusEngine // Genesis cache genesisDoc *state_types.GenesisDoc genesisState *state.State } // NOTE [ben] Compiler check to ensure ErisMintPipe successfully implements // eris-db/definitions.Pipe var _ definitions.Pipe = (*ErisMintPipe)(nil) // NOTE [ben] Compiler check to ensure ErisMintPipe successfully implements // eris-db/definitions.erisTendermintPipe var _ definitions.TendermintPipe = (*ErisMintPipe)(nil) func NewErisMintPipe(moduleConfig *config.ModuleConfig, eventSwitch *tendermint_events.EventSwitch) (*ErisMintPipe, error) { startedState, genesisDoc, err := startState(moduleConfig.DataDir, moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile, moduleConfig.ChainId) if err != nil { return nil, fmt.Errorf("Failed to start state: %v", err) } // assert ChainId matches genesis ChainId log.WithFields(log.Fields{ "chainId": startedState.ChainID, "lastBlockHeight": startedState.LastBlockHeight, "lastBlockHash": startedState.LastBlockHash, }).Debug("Loaded state") // start the application erisMint := NewErisMint(startedState, eventSwitch) // NOTE: [ben] Set Host opens an RPC pipe to Tendermint; this is a remnant // of the old Eris-DB / Tendermint and should be considered as an in-process // call when possible tendermintHost := moduleConfig.Config.GetString("tendermint_host") log.Debug(fmt.Sprintf("Starting ErisMint RPC client to Tendermint host on %s", tendermintHost)) erisMint.SetHostAddress(tendermintHost) // initialise the components of the pipe events := newEvents(eventSwitch) accounts := newAccounts(erisMint) namereg := newNameReg(erisMint) transactor := newTransactor(moduleConfig.ChainId, eventSwitch, erisMint, events) // TODO: make interface to tendermint core's rpc for these // blockchain := newBlockchain(chainID, genDocFile, blockStore) // consensus := newConsensus(erisdbApp) // net := newNetwork(erisdbApp) return &ErisMintPipe { erisMintState: startedState, eventSwitch: eventSwitch, erisMint: erisMint, accounts: accounts, events: events, namereg: namereg, transactor: transactor, network: newNetwork(), consensus: nil, // genesis cache genesisDoc: genesisDoc, genesisState: nil, }, nil } //------------------------------------------------------------------------------ // Start state // Start state tries to load the existing state in the data directory; // if an existing database can be loaded, it will validate that the // chainId in the genesis of that loaded state matches the asserted chainId. // If no state can be loaded, the JSON genesis file will be loaded into the // state database as the zero state. func startState(dataDir, backend, genesisFile, chainId string) (*state.State, *state_types.GenesisDoc, error) { // avoid Tendermints PanicSanity and return a clean error if backend != db.DBBackendMemDB && backend != db.DBBackendLevelDB { return nil, nil, fmt.Errorf("Dababase backend %s is not supported by %s", backend, GetErisMintVersion) } stateDB := db.NewDB("erismint", backend, dataDir) newState := state.LoadState(stateDB) var genesisDoc *state_types.GenesisDoc if newState == nil { genesisDoc, newState = state.MakeGenesisStateFromFile(stateDB, genesisFile) newState.Save() buf, n, err := new(bytes.Buffer), new(int), new(error) wire.WriteJSON(genesisDoc, buf, n, err) stateDB.Set(state_types.GenDocKey, buf.Bytes()) if *err != nil { return nil, nil, fmt.Errorf("Unable to write genesisDoc to db: %v", err) } } else { loadedGenesisDocBytes := stateDB.Get(state_types.GenDocKey) err := new(error) wire.ReadJSONPtr(&genesisDoc, loadedGenesisDocBytes, err) if *err != nil { return nil, nil, fmt.Errorf("Unable to read genesisDoc from db on startState: %v", err) } // assert loaded genesis doc has the same chainId as the provided chainId if genesisDoc.ChainID != chainId { return nil, nil, fmt.Errorf("ChainId (%s) loaded from genesis document in existing database does not match configuration chainId (%s).", genesisDoc.ChainID, chainId) } } return newState, genesisDoc, nil } //------------------------------------------------------------------------------ // Implement definitions.Pipe for ErisMintPipe func (pipe *ErisMintPipe) Accounts() definitions.Accounts { return pipe.accounts } func (pipe *ErisMintPipe) Blockchain() definitions.Blockchain { return pipe.blockchain } func (pipe *ErisMintPipe) Consensus() definitions.Consensus { return pipe.consensus } func (pipe *ErisMintPipe) Events() event.EventEmitter { return pipe.events } func (pipe *ErisMintPipe) NameReg() definitions.NameReg { return pipe.namereg } func (pipe *ErisMintPipe) Net() definitions.Net { return pipe.network } func (pipe *ErisMintPipe) Transactor() definitions.Transactor { return pipe.transactor } func (pipe *ErisMintPipe) GetApplication() manager_types.Application { return pipe.erisMint } func (pipe *ErisMintPipe) SetConsensusEngine( consensus definitions.ConsensusEngine) error { if pipe.consensusEngine == nil { pipe.consensusEngine = consensus } else { return fmt.Errorf("Failed to set consensus engine for pipe; already set") } return nil } func (pipe *ErisMintPipe) GetTendermintPipe() (definitions.TendermintPipe, error) { return definitions.TendermintPipe(pipe), nil } //------------------------------------------------------------------------------ // Implement definitions.TendermintPipe for ErisMintPipe func (pipe *ErisMintPipe) Status() (*rpc_tendermint_types.ResultStatus, error) { memoryDatabase := db.NewMemDB() if pipe.genesisState == nil { pipe.genesisState = state.MakeGenesisState(memoryDatabase, pipe.genesisDoc) } genesisHash := pipe.genesisState.Hash() if pipe.consensusEngine == nil { return nil, fmt.Errorf("Consensus Engine is not set in pipe.") } latestHeight := pipe.consensusEngine.Height() var ( latestBlockMeta *tendermint_types.BlockMeta latestBlockHash []byte latestBlockTime int64 ) if latestHeight != 0 { latestBlockMeta = pipe.consensusEngine.LoadBlockMeta(latestHeight) latestBlockHash = latestBlockMeta.Hash latestBlockTime = latestBlockMeta.Header.Time.UnixNano() } return &rpc_tendermint_types.ResultStatus{ NodeInfo: pipe.consensusEngine.NodeInfo(), GenesisHash: genesisHash, PubKey: pipe.consensusEngine.PublicValidatorKey(), LatestBlockHash: latestBlockHash, LatestBlockHeight: latestHeight, LatestBlockTime: latestBlockTime}, nil } func (pipe *ErisMintPipe) NetInfo() (*rpc_tendermint_types.ResultNetInfo, error) { listening := pipe.consensusEngine.IsListening() listeners := []string{} for _, listener := range pipe.consensusEngine.Listeners() { listeners = append(listeners, listener.String()) } peers := pipe.consensusEngine.Peers() return &rpc_tendermint_types.ResultNetInfo{ Listening: listening, Listeners: listeners, Peers: peers, }, nil } func (pipe *ErisMintPipe) Genesis() (*rpc_tendermint_types.ResultGenesis, error) { return &rpc_tendermint_types.ResultGenesis { // TODO: [ben] sharing pointer to unmutated GenesisDoc, but is not immutable Genesis: pipe.genesisDoc, }, nil } // Accounts func (pipe *ErisMintPipe) GetAccount(address []byte) (*rpc_tendermint_types.ResultGetAccount, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) ListAccounts() (*rpc_tendermint_types.ResultListAccounts, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) GetStorage(address, key []byte) (*rpc_tendermint_types.ResultGetStorage, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) DumpStorage(address []byte) (*rpc_tendermint_types.ResultDumpStorage, error) { return nil, fmt.Errorf("Unimplemented.") } // Call func (pipe *ErisMintPipe) Call(fromAddres, toAddress, data []byte) (*rpc_tendermint_types.ResultCall, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tendermint_types.ResultCall, error) { return nil, fmt.Errorf("Unimplemented.") } // TODO: [ben] deprecate as we should not allow unsafe behaviour // where a user is allowed to send a private key over the wire, // especially unencrypted. func (pipe *ErisMintPipe) SignTransaction(transaction transaction.Tx, privAccounts []*account.PrivAccount) (*rpc_tendermint_types.ResultSignTx, error) { return nil, fmt.Errorf("Unimplemented.") } // Name registry func (pipe *ErisMintPipe) GetName(name string) (*rpc_tendermint_types.ResultGetName, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) ListNames() (*rpc_tendermint_types.ResultListNames, error) { return nil, fmt.Errorf("Unimplemented.") } // Memory pool func (pipe *ErisMintPipe) BroadcastTxAsync(transaction transaction.Tx) (*rpc_tendermint_types.ResultBroadcastTx, error) { return nil, fmt.Errorf("Unimplemented.") } func (pipe *ErisMintPipe) BroadcastTxSync(transaction transaction.Tx) (*rpc_tendermint_types.ResultBroadcastTx, error) { return nil, fmt.Errorf("Unimplemented.") }