diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..074c48c94 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +# Everything goes through Bucky. For now. +* @ebuchman + diff --git a/.gitignore b/.gitignore index cc6e1d679..23ae616ee 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ test/p2p/data/ test/logs .glide coverage.txt +docs/_build +docs/tools diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ccc7e0a7..c48d0850b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,17 +28,32 @@ BUG FIXES: - Graceful handling/recovery for apps that have non-determinism or fail to halt - Graceful handling/recovery for violations of safety, or liveness +## 0.11.1 (October 10, 2017) + +IMPROVEMENTS: + - blockchain/reactor: respondWithNoResponseMessage for missing height + +BUG FIXES: + - rpc: fixed client WebSocket timeout + - rpc: client now resubscribes on reconnection + - rpc: fix panics on missing params + - rpc: fix `/dump_consensus_state` to have normal json output (NOTE: technically breaking, but worth a bug fix label) + - types: fixed out of range error in VoteSet.addVote + - consensus: fix wal autofile via https://github.com/tendermint/tmlibs/blob/master/CHANGELOG.md#032-october-2-2017 + ## 0.11.0 (September 22, 2017) BREAKING: - - state: every validator set change is persisted to disk, which required some changes to the `State` structure. - - cmd: if there is no genesis, exit immediately instead of waiting around for one to show. - - p2p: new `p2p.Peer` interface used for all reactor methods (instead of `*p2p.Peer` struct). - - types: `Signer.Sign` returns an error. + - genesis file: validator `amount` is now `power` + - abci: Info, BeginBlock, InitChain all take structs - rpc: various changes to match JSONRPC spec (http://www.jsonrpc.org/specification), including breaking ones: - requests that previously returned HTTP code 4XX now return 200 with an error code in the JSONRPC. - `rpctypes.RPCResponse` uses new `RPCError` type instead of `string`. - - abci: Info, BeginBlock, InitChain all take structs + + - cmd: if there is no genesis, exit immediately instead of waiting around for one to show. + - types: `Signer.Sign` returns an error. + - state: every validator set change is persisted to disk, which required some changes to the `State` structure. + - p2p: new `p2p.Peer` interface used for all reactor methods (instead of `*p2p.Peer` struct). FEATURES: - rpc: `/validators?height=X` allows querying of validators at previous heights. diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index 55f2e14ab..019e1f30d 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,8 +1,8 @@ FROM alpine:3.6 # This is the release of tendermint to pull in. -ENV TM_VERSION 0.10.0 -ENV TM_SHA256SUM a29852b8d51c00db93c87c3d148fa419a047abd38f32b2507a905805131acc19 +ENV TM_VERSION 0.11.0 +ENV TM_SHA256SUM 7e443bac4d42f12e7beaf9cee63b4a565dad8c58895291fdedde8057088b70c5 # Tendermint will be looking for genesis file in /tendermint (unless you change # `genesis_file` in config.toml). You can put your config.toml and private diff --git a/DOCKER/README.md b/DOCKER/README.md index e191abc39..e5c6fee35 100644 --- a/DOCKER/README.md +++ b/DOCKER/README.md @@ -1,6 +1,7 @@ # Supported tags and respective `Dockerfile` links -- `0.10.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/e5342f4054ab784b2cd6150e14f01053d7c8deb2/DOCKER/Dockerfile) +- `0.11.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/9177cc1f64ca88a4a0243c5d1773d10fba67e201/DOCKER/Dockerfile) +- `0.10.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/e5342f4054ab784b2cd6150e14f01053d7c8deb2/DOCKER/Dockerfile) - `0.9.1`, `0.9`, [(Dockerfile)](https://github.com/tendermint/tendermint/blob/809e0e8c5933604ba8b2d096803ada7c5ec4dfd3/DOCKER/Dockerfile) - `0.9.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/d474baeeea6c22b289e7402449572f7c89ee21da/DOCKER/Dockerfile) - `0.8.0`, `0.8` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/bf64dd21fdb193e54d8addaaaa2ecf7ac371de8c/DOCKER/Dockerfile) @@ -8,13 +9,24 @@ `develop` tag points to the [develop](https://github.com/tendermint/tendermint/tree/develop) branch. +# Quick reference + +* **Where to get help:** + [Chat on Rocket](https://cosmos.rocket.chat/) + +* **Where to file issues:** + https://github.com/tendermint/tendermint/issues + +* **Supported Docker versions:** + [the latest release](https://github.com/moby/moby/releases) (down to 1.6 on a best-effort basis) + # Tendermint Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine, written in any programming language, and securely replicates it on many machines. -For more background, see the [introduction](https://tendermint.com/intro). +For more background, see the [introduction](https://tendermint.readthedocs.io/en/master/introduction.html). -To get started developing applications, see the [application developers guide](https://tendermint.com/docs/guides/app-development). +To get started developing applications, see the [application developers guide](https://tendermint.readthedocs.io/en/master/getting-started.html). # How to use this image @@ -31,26 +43,12 @@ docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app If you want to see many containers talking to each other, consider using [mintnet-kubernetes](https://github.com/tendermint/tools/tree/master/mintnet-kubernetes), which is a tool for running Tendermint-based applications on a Kubernetes cluster. -# Supported Docker versions - -This image is officially supported on Docker version 1.13.1. - -Support for older versions (down to 1.6) is provided on a best-effort basis. - -Please see [the Docker installation documentation](https://docs.docker.com/installation/) for details on how to upgrade your Docker daemon. - # License View [license information](https://raw.githubusercontent.com/tendermint/tendermint/master/LICENSE) for the software contained in this image. # User Feedback -## Issues - -If you have any problems with or questions about this image, please contact us through a [GitHub](https://github.com/tendermint/tendermint/issues) issue. If the issue is related to a CVE, please check for [a `cve-tracker` issue on the `official-images` repository](https://github.com/docker-library/official-images/issues?q=label%3Acve-tracker) first. - -You can also reach the image maintainers via [Slack](http://forum.tendermint.com:3000/). - ## Contributing You are invited to contribute new features, fixes, or updates, large or small; we are always thrilled to receive pull requests, and do our best to process them as fast as we can. diff --git a/Makefile b/Makefile index 8c9c5214d..dfb0dc3a9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GOTOOLS = \ github.com/mitchellh/gox \ + github.com/tcnksm/ghr \ github.com/Masterminds/glide \ - honnef.co/go/tools/cmd/megacheck PACKAGES=$(shell go list ./... | grep -v '/vendor/') BUILD_TAGS?=tendermint diff --git a/README.md b/README.md index 420057976..6aa4d8789 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/6874 [![](https://tokei.rs/b1/github/tendermint/tendermint?category=lines)](https://github.com/tendermint/tendermint) -Branch | Tests | Coverage +Branch | Tests | Coverage ----------|-------|---------- master | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/master.svg?style=shield)](https://circleci.com/gh/tendermint/tendermint/tree/master) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/master/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) develop | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/develop.svg?style=shield)](https://circleci.com/gh/tendermint/tendermint/tree/develop) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/develop/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) @@ -56,6 +56,7 @@ All resources involving the use of, building application on, or developing for, * [Ethermint](http://github.com/tendermint/ethermint); Ethereum on Tendermint * [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework +* [Many more](https://tendermint.readthedocs.io/en/master/ecosystem.html#abci-applications) ### More diff --git a/Vagrantfile b/Vagrantfile index c465ed73a..0f69feed2 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -17,11 +17,11 @@ Vagrant.configure("2") do |config| usermod -a -G docker vagrant apt-get autoremove -y - curl -O https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz - tar -xvf go1.8.linux-amd64.tar.gz + curl -O https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz + tar -xvf go1.9.linux-amd64.tar.gz rm -rf /usr/local/go mv go /usr/local - rm -f go1.8.linux-amd64.tar.gz + rm -f go1.9.linux-amd64.tar.gz mkdir -p /home/vagrant/go/bin echo 'export PATH=$PATH:/usr/local/go/bin:/home/vagrant/go/bin' >> /home/vagrant/.bash_profile echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile diff --git a/benchmarks/map_test.go b/benchmarks/map_test.go index 80edaff7c..2d9789026 100644 --- a/benchmarks/map_test.go +++ b/benchmarks/map_test.go @@ -1,8 +1,9 @@ package benchmarks import ( - . "github.com/tendermint/tmlibs/common" "testing" + + cmn "github.com/tendermint/tmlibs/common" ) func BenchmarkSomething(b *testing.B) { @@ -11,11 +12,11 @@ func BenchmarkSomething(b *testing.B) { numChecks := 100000 keys := make([]string, numItems) for i := 0; i < numItems; i++ { - keys[i] = RandStr(100) + keys[i] = cmn.RandStr(100) } txs := make([]string, numChecks) for i := 0; i < numChecks; i++ { - txs[i] = RandStr(100) + txs[i] = cmn.RandStr(100) } b.StartTimer() diff --git a/benchmarks/os_test.go b/benchmarks/os_test.go index 2c4611c84..9c8fae656 100644 --- a/benchmarks/os_test.go +++ b/benchmarks/os_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) func BenchmarkFileWrite(b *testing.B) { @@ -14,7 +14,7 @@ func BenchmarkFileWrite(b *testing.B) { if err != nil { b.Error(err) } - testString := RandStr(200) + "\n" + testString := cmn.RandStr(200) + "\n" b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/benchmarks/simu/counter.go b/benchmarks/simu/counter.go index d26f5e6f7..ff5b14c0d 100644 --- a/benchmarks/simu/counter.go +++ b/benchmarks/simu/counter.go @@ -3,9 +3,8 @@ package main import ( "context" "encoding/binary" - "time" - //"encoding/hex" "fmt" + "time" rpcclient "github.com/tendermint/tendermint/rpc/lib/client" cmn "github.com/tendermint/tmlibs/common" diff --git a/blockchain/pool.go b/blockchain/pool.go index 924880c08..bd52e280f 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -6,7 +6,7 @@ import ( "time" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" flow "github.com/tendermint/tmlibs/flowrate" "github.com/tendermint/tmlibs/log" ) @@ -33,7 +33,7 @@ var peerTimeoutSeconds = time.Duration(15) // not const so we can override with */ type BlockPool struct { - BaseService + cmn.BaseService startTime time.Time mtx sync.Mutex @@ -59,7 +59,7 @@ func NewBlockPool(start int, requestsCh chan<- BlockRequest, timeoutsCh chan<- s requestsCh: requestsCh, timeoutsCh: timeoutsCh, } - bp.BaseService = *NewBaseService(nil, "BlockPool", bp) + bp.BaseService = *cmn.NewBaseService(nil, "BlockPool", bp) return bp } @@ -137,14 +137,14 @@ func (pool *BlockPool) IsCaughtUp() bool { maxPeerHeight := 0 for _, peer := range pool.peers { - maxPeerHeight = MaxInt(maxPeerHeight, peer.height) + maxPeerHeight = cmn.MaxInt(maxPeerHeight, peer.height) } // some conditions to determine if we're caught up receivedBlockOrTimedOut := (pool.height > 0 || time.Since(pool.startTime) > 5*time.Second) ourChainIsLongestAmongPeers := maxPeerHeight == 0 || pool.height >= maxPeerHeight isCaughtUp := receivedBlockOrTimedOut && ourChainIsLongestAmongPeers - pool.Logger.Info(Fmt("IsCaughtUp: %v", isCaughtUp), "height", pool.height, "maxPeerHeight", maxPeerHeight) + pool.Logger.Info(cmn.Fmt("IsCaughtUp: %v", isCaughtUp), "height", pool.height, "maxPeerHeight", maxPeerHeight) return isCaughtUp } @@ -180,7 +180,7 @@ func (pool *BlockPool) PopRequest() { delete(pool.requesters, pool.height) pool.height++ } else { - PanicSanity(Fmt("Expected requester to pop, got nothing at height %v", pool.height)) + cmn.PanicSanity(cmn.Fmt("Expected requester to pop, got nothing at height %v", pool.height)) } } @@ -192,7 +192,7 @@ func (pool *BlockPool) RedoRequest(height int) { pool.mtx.Unlock() if request.block == nil { - PanicSanity("Expected block to be non-nil") + cmn.PanicSanity("Expected block to be non-nil") } // RemovePeer will redo all requesters associated with this peer. // TODO: record this malfeasance @@ -311,10 +311,10 @@ func (pool *BlockPool) debug() string { str := "" for h := pool.height; h < pool.height+len(pool.requesters); h++ { if pool.requesters[h] == nil { - str += Fmt("H(%v):X ", h) + str += cmn.Fmt("H(%v):X ", h) } else { - str += Fmt("H(%v):", h) - str += Fmt("B?(%v) ", pool.requesters[h].block != nil) + str += cmn.Fmt("H(%v):", h) + str += cmn.Fmt("B?(%v) ", pool.requesters[h].block != nil) } } return str @@ -394,7 +394,7 @@ func (peer *bpPeer) onTimeout() { //------------------------------------- type bpRequester struct { - BaseService + cmn.BaseService pool *BlockPool height int gotBlockCh chan struct{} @@ -415,7 +415,7 @@ func newBPRequester(pool *BlockPool, height int) *bpRequester { peerID: "", block: nil, } - bpr.BaseService = *NewBaseService(nil, "bpRequester", bpr) + bpr.BaseService = *cmn.NewBaseService(nil, "bpRequester", bpr) return bpr } diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 43ddbaddf..a1fce2da8 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) @@ -22,7 +22,7 @@ type testPeer struct { func makePeers(numPeers int, minHeight, maxHeight int) map[string]testPeer { peers := make(map[string]testPeer, numPeers) for i := 0; i < numPeers; i++ { - peerID := RandStr(12) + peerID := cmn.RandStr(12) height := minHeight + rand.Intn(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height} } diff --git a/blockchain/reactor.go b/blockchain/reactor.go index fb68aadda..91f0adede 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -121,6 +121,24 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { bcR.pool.RemovePeer(peer.Key()) } +// respondToPeer loads a block and sends it to the requesting peer, +// if we have it. Otherwise, we'll respond saying we don't have it. +// According to the Tendermint spec, if all nodes are honest, +// no node should be requesting for a block that's non-existent. +func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, src p2p.Peer) (queued bool) { + block := bcR.store.LoadBlock(msg.Height) + if block != nil { + msg := &bcBlockResponseMessage{Block: block} + return src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{msg}) + } + + bcR.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height) + + return src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{ + &bcNoBlockResponseMessage{Height: msg.Height}, + }) +} + // Receive implements Reactor by handling 4 types of messages (look below). func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { _, msg, err := DecodeMessage(msgBytes, bcR.maxMsgSize()) @@ -134,16 +152,8 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) // TODO: improve logic to satisfy megacheck switch msg := msg.(type) { case *bcBlockRequestMessage: - // Got a request for a block. Respond with block if we have it. - block := bcR.store.LoadBlock(msg.Height) - if block != nil { - msg := &bcBlockResponseMessage{Block: block} - queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{msg}) - if !queued { - // queue is full, just ignore. - } - } else { - // TODO peer is asking for things we don't have. + if queued := bcR.respondToPeer(msg, src); !queued { + // Unfortunately not queued since the queue is full. } case *bcBlockResponseMessage: // Got a block. @@ -235,7 +245,7 @@ FOR_LOOP: err := bcR.state.Validators.VerifyCommit( bcR.state.ChainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit) if err != nil { - bcR.Logger.Info("error in validation", "err", err) + bcR.Logger.Error("Error in validation", "err", err) bcR.pool.RedoRequest(first.Height) break SYNC_LOOP } else { @@ -276,10 +286,11 @@ func (bcR *BlockchainReactor) SetEventSwitch(evsw types.EventSwitch) { // Messages const ( - msgTypeBlockRequest = byte(0x10) - msgTypeBlockResponse = byte(0x11) - msgTypeStatusResponse = byte(0x20) - msgTypeStatusRequest = byte(0x21) + msgTypeBlockRequest = byte(0x10) + msgTypeBlockResponse = byte(0x11) + msgTypeNoBlockResponse = byte(0x12) + msgTypeStatusResponse = byte(0x20) + msgTypeStatusRequest = byte(0x21) ) // BlockchainMessage is a generic message for this reactor. @@ -289,6 +300,7 @@ var _ = wire.RegisterInterface( struct{ BlockchainMessage }{}, wire.ConcreteType{&bcBlockRequestMessage{}, msgTypeBlockRequest}, wire.ConcreteType{&bcBlockResponseMessage{}, msgTypeBlockResponse}, + wire.ConcreteType{&bcNoBlockResponseMessage{}, msgTypeNoBlockResponse}, wire.ConcreteType{&bcStatusResponseMessage{}, msgTypeStatusResponse}, wire.ConcreteType{&bcStatusRequestMessage{}, msgTypeStatusRequest}, ) @@ -316,6 +328,14 @@ func (m *bcBlockRequestMessage) String() string { return cmn.Fmt("[bcBlockRequestMessage %v]", m.Height) } +type bcNoBlockResponseMessage struct { + Height int +} + +func (brm *bcNoBlockResponseMessage) String() string { + return cmn.Fmt("[bcNoBlockResponseMessage %d]", brm.Height) +} + //------------------------------------- // NOTE: keep up-to-date with maxBlockchainResponseSize diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go new file mode 100644 index 000000000..492ea7a80 --- /dev/null +++ b/blockchain/reactor_test.go @@ -0,0 +1,158 @@ +package blockchain + +import ( + "bytes" + "testing" + + wire "github.com/tendermint/go-wire" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +func newBlockchainReactor(logger log.Logger, maxBlockHeight int) *BlockchainReactor { + config := cfg.ResetTestRoot("node_node_test") + + blockStoreDB := db.NewDB("blockstore", config.DBBackend, config.DBDir()) + blockStore := NewBlockStore(blockStoreDB) + + stateLogger := logger.With("module", "state") + + // Get State + stateDB := db.NewDB("state", config.DBBackend, config.DBDir()) + state, _ := sm.GetState(stateDB, config.GenesisFile()) + + state.SetLogger(stateLogger) + state.Save() + + // Make the blockchainReactor itself + fastSync := true + bcReactor := NewBlockchainReactor(state.Copy(), nil, blockStore, fastSync) + + // Next: we need to set a switch in order for peers to be added in + bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig()) + bcReactor.SetLogger(logger.With("module", "blockchain")) + + // Lastly: let's add some blocks in + for blockHeight := 1; blockHeight <= maxBlockHeight; blockHeight++ { + firstBlock := makeBlock(blockHeight, state) + secondBlock := makeBlock(blockHeight+1, state) + firstParts := firstBlock.MakePartSet(state.Params().BlockGossipParams.BlockPartSizeBytes) + blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit) + } + + return bcReactor +} + +func TestNoBlockMessageResponse(t *testing.T) { + logBuf := new(bytes.Buffer) + logger := log.NewTMLogger(logBuf) + maxBlockHeight := 20 + + bcr := newBlockchainReactor(logger, maxBlockHeight) + go bcr.OnStart() + defer bcr.Stop() + + // Add some peers in + peer := newbcrTestPeer(cmn.RandStr(12)) + bcr.AddPeer(peer) + + chID := byte(0x01) + + tests := []struct { + height int + existent bool + }{ + {maxBlockHeight + 2, false}, + {10, true}, + {1, true}, + {100, false}, + } + + for _, tt := range tests { + reqBlockMsg := &bcBlockRequestMessage{tt.height} + reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg}) + bcr.Receive(chID, peer, reqBlockBytes) + value := peer.lastValue() + msg := value.(struct{ BlockchainMessage }).BlockchainMessage + + if tt.existent { + if blockMsg, ok := msg.(*bcBlockResponseMessage); !ok { + t.Fatalf("Expected to receive a block response for height %d", tt.height) + } else if blockMsg.Block.Height != tt.height { + t.Fatalf("Expected response to be for height %d, got %d", tt.height, blockMsg.Block.Height) + } + } else { + if noBlockMsg, ok := msg.(*bcNoBlockResponseMessage); !ok { + t.Fatalf("Expected to receive a no block response for height %d", tt.height) + } else if noBlockMsg.Height != tt.height { + t.Fatalf("Expected response to be for height %d, got %d", tt.height, noBlockMsg.Height) + } + } + } +} + +//---------------------------------------------- +// utility funcs + +func makeTxs(blockNumber int) (txs []types.Tx) { + for i := 0; i < 10; i++ { + txs = append(txs, types.Tx([]byte{byte(blockNumber), byte(i)})) + } + return txs +} + +func makeBlock(blockNumber int, state *sm.State) *types.Block { + prevHash := state.LastBlockID.Hash + prevParts := types.PartSetHeader{} + valHash := state.Validators.Hash() + prevBlockID := types.BlockID{prevHash, prevParts} + block, _ := types.MakeBlock(blockNumber, "test_chain", makeTxs(blockNumber), + new(types.Commit), prevBlockID, valHash, state.AppHash, state.Params().BlockGossipParams.BlockPartSizeBytes) + return block +} + +// The Test peer +type bcrTestPeer struct { + cmn.Service + key string + ch chan interface{} +} + +var _ p2p.Peer = (*bcrTestPeer)(nil) + +func newbcrTestPeer(key string) *bcrTestPeer { + return &bcrTestPeer{ + Service: cmn.NewBaseService(nil, "bcrTestPeer", nil), + key: key, + ch: make(chan interface{}, 2), + } +} + +func (tp *bcrTestPeer) lastValue() interface{} { return <-tp.ch } + +func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { + if _, ok := value.(struct{ BlockchainMessage }).BlockchainMessage.(*bcStatusResponseMessage); ok { + // Discard status response messages since they skew our results + // We only want to deal with: + // + bcBlockResponseMessage + // + bcNoBlockResponseMessage + } else { + tp.ch <- value + } + return true +} + +func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } +func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } +func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } +func (tp *bcrTestPeer) Key() string { return tp.key } +func (tp *bcrTestPeer) IsOutbound() bool { return false } +func (tp *bcrTestPeer) IsPersistent() bool { return true } +func (tp *bcrTestPeer) Get(s string) interface{} { return s } +func (tp *bcrTestPeer) Set(string, interface{}) {} diff --git a/cmd/tendermint/commands/root_test.go b/cmd/tendermint/commands/root_test.go index ae0f38275..b89e7a199 100644 --- a/cmd/tendermint/commands/root_test.go +++ b/cmd/tendermint/commands/root_test.go @@ -3,15 +3,15 @@ package commands import ( "os" "strconv" + "testing" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tmlibs/cli" - - "testing" ) var ( diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 0f2d7b040..38dff4064 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -9,7 +9,7 @@ import ( data "github.com/tendermint/go-wire/data" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/events" ) @@ -149,8 +149,8 @@ func TestByzantine(t *testing.T) { case <-done: case <-tick.C: for i, reactor := range reactors { - t.Log(Fmt("Consensus Reactor %v", i)) - t.Log(Fmt("%v", reactor)) + t.Log(cmn.Fmt("Consensus Reactor %v", i)) + t.Log(cmn.Fmt("%v", reactor)) } t.Fatalf("Timed out waiting for all validators to commit first block") } @@ -220,7 +220,7 @@ func sendProposalAndParts(height, round int, cs *ConsensusState, peer p2p.Peer, // byzantine consensus reactor type ByzantineReactor struct { - Service + cmn.Service reactor *ConsensusReactor } @@ -296,5 +296,5 @@ func (privVal *ByzantinePrivValidator) SignHeartbeat(chainID string, heartbeat * } func (privVal *ByzantinePrivValidator) String() string { - return Fmt("PrivValidator{%X}", privVal.GetAddress()) + return cmn.Fmt("PrivValidator{%X}", privVal.GetAddress()) } diff --git a/consensus/common_test.go b/consensus/common_test.go index b16afc3d0..33b613a0c 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -15,11 +15,12 @@ import ( abci "github.com/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" + cstypes "github.com/tendermint/tendermint/consensus/types" mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -34,7 +35,7 @@ var config *cfg.Config // NOTE: must be reset for each _test.go file var ensureTimeout = time.Second * 2 func ensureDir(dir string, mode os.FileMode) { - if err := EnsureDir(dir, mode); err != nil { + if err := cmn.EnsureDir(dir, mode); err != nil { panic(err) } } @@ -341,7 +342,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou state, _ := sm.MakeGenesisState(db, genDoc) state.SetLogger(logger.With("module", "state", "validator", i)) state.Save() - thisConfig := ResetConfig(Fmt("%s_%d", testName, i)) + thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) for _, opt := range configOpts { opt(thisConfig) } @@ -362,13 +363,13 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF state, _ := sm.MakeGenesisState(db, genDoc) state.SetLogger(log.TestingLogger().With("module", "state")) state.Save() - thisConfig := ResetConfig(Fmt("%s_%d", testName, i)) + thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i)) ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal var privVal types.PrivValidator if i < nValidators { privVal = privVals[i] } else { - _, tempFilePath := Tempfile("priv_validator_") + _, tempFilePath := cmn.Tempfile("priv_validator_") privVal = types.GenPrivValidatorFS(tempFilePath) } @@ -456,7 +457,7 @@ func (m *mockTicker) ScheduleTimeout(ti timeoutInfo) { if m.onlyOnce && m.fired { return } - if ti.Step == RoundStepNewHeight { + if ti.Step == cstypes.RoundStepNewHeight { m.c <- ti m.fired = true } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index f17d19f52..3a430ef26 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -8,7 +8,7 @@ import ( abci "github.com/tendermint/abci/types" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) func init() { @@ -86,7 +86,7 @@ func deliverTxsRange(cs *ConsensusState, start, end int) { binary.BigEndian.PutUint64(txBytes, uint64(i)) err := cs.mempool.CheckTx(txBytes, nil) if err != nil { - panic(Fmt("Error after CheckTx: %v", err)) + panic(cmn.Fmt("Error after CheckTx: %v", err)) } } } @@ -184,7 +184,7 @@ func NewCounterApplication() *CounterApplication { } func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo { - return abci.ResponseInfo{Data: Fmt("txs:%v", app.txCount)} + return abci.ResponseInfo{Data: cmn.Fmt("txs:%v", app.txCount)} } func (app *CounterApplication) DeliverTx(tx []byte) abci.Result { @@ -201,7 +201,7 @@ func runTx(tx []byte, countPtr *int) abci.Result { copy(tx8[len(tx8)-len(tx):], tx) txValue := binary.BigEndian.Uint64(tx8) if txValue != uint64(count) { - return abci.ErrBadNonce.AppendLog(Fmt("Invalid nonce. Expected %v, got %v", count, txValue)) + return abci.ErrBadNonce.AppendLog(cmn.Fmt("Invalid nonce. Expected %v, got %v", count, txValue)) } *countPtr += 1 return abci.OK diff --git a/consensus/reactor.go b/consensus/reactor.go index 59cbfea76..48041e2fb 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -12,6 +12,7 @@ import ( cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + cstypes "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -321,7 +322,7 @@ func (conR *ConsensusReactor) FastSync() bool { func (conR *ConsensusReactor) registerEventCallbacks() { types.AddListenerForEvent(conR.evsw, "conR", types.EventStringNewRoundStep(), func(data types.TMEventData) { - rs := data.Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := data.Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) conR.broadcastNewRoundStep(rs) }) @@ -344,7 +345,7 @@ func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(heartbeat types. conR.Switch.Broadcast(StateChannel, struct{ ConsensusMessage }{msg}) } -func (conR *ConsensusReactor) broadcastNewRoundStep(rs *RoundState) { +func (conR *ConsensusReactor) broadcastNewRoundStep(rs *cstypes.RoundState) { nrsMsg, csMsg := makeRoundStepMessages(rs) if nrsMsg != nil { @@ -381,7 +382,7 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { */ } -func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { +func makeRoundStepMessages(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { nrsMsg = &NewRoundStepMessage{ Height: rs.Height, Round: rs.Round, @@ -389,7 +390,7 @@ func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg * SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } - if rs.Step == RoundStepCommit { + if rs.Step == cstypes.RoundStepCommit { csMsg = &CommitStepMessage{ Height: rs.Height, BlockPartsHeader: rs.ProposalBlockParts.Header(), @@ -491,8 +492,8 @@ OUTER_LOOP: } } -func (conR *ConsensusReactor) gossipDataForCatchup(logger log.Logger, rs *RoundState, - prs *PeerRoundState, ps *PeerState, peer p2p.Peer) { +func (conR *ConsensusReactor) gossipDataForCatchup(logger log.Logger, rs *cstypes.RoundState, + prs *cstypes.PeerRoundState, ps *PeerState, peer p2p.Peer) { if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { // Ensure that the peer's PartSetHeader is correct @@ -606,24 +607,24 @@ OUTER_LOOP: } } -func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *RoundState, prs *PeerRoundState, ps *PeerState) bool { +func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState) bool { // If there are lastCommits to send... - if prs.Step == RoundStepNewHeight { + if prs.Step == cstypes.RoundStepNewHeight { if ps.PickSendVote(rs.LastCommit) { logger.Debug("Picked rs.LastCommit to send") return true } } // If there are prevotes to send... - if prs.Step <= RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round { + if prs.Step <= cstypes.RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round) return true } } // If there are precommits to send... - if prs.Step <= RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round { + if prs.Step <= cstypes.RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) { logger.Debug("Picked rs.Precommits(prs.Round) to send", "round", prs.Round) return true @@ -752,54 +753,6 @@ func (conR *ConsensusReactor) StringIndented(indent string) string { //----------------------------------------------------------------------------- -// PeerRoundState contains the known state of a peer. -// NOTE: Read-only when returned by PeerState.GetRoundState(). -type PeerRoundState struct { - Height int // Height peer is at - Round int // Round peer is at, -1 if unknown. - Step RoundStepType // Step peer is at - StartTime time.Time // Estimated start of round 0 at this height - Proposal bool // True if peer has proposal for this round - ProposalBlockPartsHeader types.PartSetHeader // - ProposalBlockParts *cmn.BitArray // - ProposalPOLRound int // Proposal's POL round. -1 if none. - ProposalPOL *cmn.BitArray // nil until ProposalPOLMessage received. - Prevotes *cmn.BitArray // All votes peer has for this round - Precommits *cmn.BitArray // All precommits peer has for this round - LastCommitRound int // Round of commit for last height. -1 if none. - LastCommit *cmn.BitArray // All commit precommits of commit for last height. - CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. - CatchupCommit *cmn.BitArray // All commit precommits peer has for this height & CatchupCommitRound -} - -// String returns a string representation of the PeerRoundState -func (prs PeerRoundState) String() string { - return prs.StringIndented("") -} - -// StringIndented returns a string representation of the PeerRoundState -func (prs PeerRoundState) StringIndented(indent string) string { - return fmt.Sprintf(`PeerRoundState{ -%s %v/%v/%v @%v -%s Proposal %v -> %v -%s POL %v (round %v) -%s Prevotes %v -%s Precommits %v -%s LastCommit %v (round %v) -%s Catchup %v (round %v) -%s}`, - indent, prs.Height, prs.Round, prs.Step, prs.StartTime, - indent, prs.ProposalBlockPartsHeader, prs.ProposalBlockParts, - indent, prs.ProposalPOL, prs.ProposalPOLRound, - indent, prs.Prevotes, - indent, prs.Precommits, - indent, prs.LastCommit, prs.LastCommitRound, - indent, prs.CatchupCommit, prs.CatchupCommitRound, - indent) -} - -//----------------------------------------------------------------------------- - var ( ErrPeerStateHeightRegression = errors.New("Error peer state height regression") ErrPeerStateInvalidStartTime = errors.New("Error peer state invalid startTime") @@ -812,7 +765,7 @@ type PeerState struct { logger log.Logger mtx sync.Mutex - PeerRoundState + cstypes.PeerRoundState } // NewPeerState returns a new PeerState for the given Peer @@ -820,7 +773,7 @@ func NewPeerState(peer p2p.Peer) *PeerState { return &PeerState{ Peer: peer, logger: log.NewNopLogger(), - PeerRoundState: PeerRoundState{ + PeerRoundState: cstypes.PeerRoundState{ Round: -1, ProposalPOLRound: -1, LastCommitRound: -1, @@ -836,7 +789,7 @@ func (ps *PeerState) SetLogger(logger log.Logger) *PeerState { // GetRoundState returns an atomic snapshot of the PeerRoundState. // There's no point in mutating it since it won't change PeerState. -func (ps *PeerState) GetRoundState() *PeerRoundState { +func (ps *PeerState) GetRoundState() *cstypes.PeerRoundState { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -1227,7 +1180,7 @@ func DecodeMessage(bz []byte) (msgType byte, msg ConsensusMessage, err error) { type NewRoundStepMessage struct { Height int Round int - Step RoundStepType + Step cstypes.RoundStepType SecondsSinceStartTime int LastCommitRound int } diff --git a/consensus/state.go b/consensus/state.go index 648fc0559..f0fbad811 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" "reflect" + "runtime/debug" "sync" "time" @@ -16,6 +17,7 @@ import ( "github.com/tendermint/tmlibs/log" cfg "github.com/tendermint/tendermint/config" + cstypes "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -38,124 +40,6 @@ var ( ErrVoteHeightMismatch = errors.New("Error vote height mismatch") ) -//----------------------------------------------------------------------------- -// RoundStepType enum type - -// RoundStepType enumerates the state of the consensus state machine -type RoundStepType uint8 // These must be numeric, ordered. - -const ( - RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit - RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose - RoundStepPropose = RoundStepType(0x03) // Did propose, gossip proposal - RoundStepPrevote = RoundStepType(0x04) // Did prevote, gossip prevotes - RoundStepPrevoteWait = RoundStepType(0x05) // Did receive any +2/3 prevotes, start timeout - RoundStepPrecommit = RoundStepType(0x06) // Did precommit, gossip precommits - RoundStepPrecommitWait = RoundStepType(0x07) // Did receive any +2/3 precommits, start timeout - RoundStepCommit = RoundStepType(0x08) // Entered commit state machine - // NOTE: RoundStepNewHeight acts as RoundStepCommitWait. -) - -// String returns a string -func (rs RoundStepType) String() string { - switch rs { - case RoundStepNewHeight: - return "RoundStepNewHeight" - case RoundStepNewRound: - return "RoundStepNewRound" - case RoundStepPropose: - return "RoundStepPropose" - case RoundStepPrevote: - return "RoundStepPrevote" - case RoundStepPrevoteWait: - return "RoundStepPrevoteWait" - case RoundStepPrecommit: - return "RoundStepPrecommit" - case RoundStepPrecommitWait: - return "RoundStepPrecommitWait" - case RoundStepCommit: - return "RoundStepCommit" - default: - return "RoundStepUnknown" // Cannot panic. - } -} - -//----------------------------------------------------------------------------- - -// RoundState defines the internal consensus state. -// It is Immutable when returned from ConsensusState.GetRoundState() -// TODO: Actually, only the top pointer is copied, -// so access to field pointers is still racey -type RoundState struct { - Height int // Height we are working on - Round int - Step RoundStepType - StartTime time.Time - CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found - Validators *types.ValidatorSet - Proposal *types.Proposal - ProposalBlock *types.Block - ProposalBlockParts *types.PartSet - LockedRound int - LockedBlock *types.Block - LockedBlockParts *types.PartSet - Votes *HeightVoteSet - CommitRound int // - LastCommit *types.VoteSet // Last precommits at Height-1 - LastValidators *types.ValidatorSet -} - -// RoundStateEvent returns the H/R/S of the RoundState as an event. -func (rs *RoundState) RoundStateEvent() types.EventDataRoundState { - edrs := types.EventDataRoundState{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step.String(), - RoundState: rs, - } - return edrs -} - -// String returns a string -func (rs *RoundState) String() string { - return rs.StringIndented("") -} - -// StringIndented returns a string -func (rs *RoundState) StringIndented(indent string) string { - return fmt.Sprintf(`RoundState{ -%s H:%v R:%v S:%v -%s StartTime: %v -%s CommitTime: %v -%s Validators: %v -%s Proposal: %v -%s ProposalBlock: %v %v -%s LockedRound: %v -%s LockedBlock: %v %v -%s Votes: %v -%s LastCommit: %v -%s LastValidators: %v -%s}`, - indent, rs.Height, rs.Round, rs.Step, - indent, rs.StartTime, - indent, rs.CommitTime, - indent, rs.Validators.StringIndented(indent+" "), - indent, rs.Proposal, - indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), - indent, rs.LockedRound, - indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), - indent, rs.Votes.StringIndented(indent+" "), - indent, rs.LastCommit.StringShort(), - indent, rs.LastValidators.StringIndented(indent+" "), - indent) -} - -// StringShort returns a string -func (rs *RoundState) StringShort() string { - return fmt.Sprintf(`RoundState{H:%v R:%v S:%v ST:%v}`, - rs.Height, rs.Round, rs.Step, rs.StartTime) -} - //----------------------------------------------------------------------------- var ( @@ -170,10 +54,10 @@ type msgInfo struct { // internally generated messages which may update the state type timeoutInfo struct { - Duration time.Duration `json:"duration"` - Height int `json:"height"` - Round int `json:"round"` - Step RoundStepType `json:"step"` + Duration time.Duration `json:"duration"` + Height int `json:"height"` + Round int `json:"round"` + Step cstypes.RoundStepType `json:"step"` } func (ti *timeoutInfo) String() string { @@ -198,7 +82,7 @@ type ConsensusState struct { // internal state mtx sync.Mutex - RoundState + cstypes.RoundState state *sm.State // State until height-1. // state changes may be triggered by msgs from peers, @@ -281,13 +165,13 @@ func (cs *ConsensusState) GetState() *sm.State { } // GetRoundState returns a copy of the internal consensus state. -func (cs *ConsensusState) GetRoundState() *RoundState { +func (cs *ConsensusState) GetRoundState() *cstypes.RoundState { cs.mtx.Lock() defer cs.mtx.Unlock() return cs.getRoundState() } -func (cs *ConsensusState) getRoundState() *RoundState { +func (cs *ConsensusState) getRoundState() *cstypes.RoundState { rs := cs.RoundState // copy return &rs } @@ -468,20 +352,20 @@ func (cs *ConsensusState) updateHeight(height int) { cs.Height = height } -func (cs *ConsensusState) updateRoundStep(round int, step RoundStepType) { +func (cs *ConsensusState) updateRoundStep(round int, step cstypes.RoundStepType) { cs.Round = round cs.Step = step } // enterNewRound(height, 0) at cs.StartTime. -func (cs *ConsensusState) scheduleRound0(rs *RoundState) { +func (cs *ConsensusState) scheduleRound0(rs *cstypes.RoundState) { //cs.Logger.Info("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) sleepDuration := rs.StartTime.Sub(time.Now()) - cs.scheduleTimeout(sleepDuration, rs.Height, 0, RoundStepNewHeight) + cs.scheduleTimeout(sleepDuration, rs.Height, 0, cstypes.RoundStepNewHeight) } // Attempt to schedule a timeout (by sending timeoutInfo on the tickChan) -func (cs *ConsensusState) scheduleTimeout(duration time.Duration, height, round int, step RoundStepType) { +func (cs *ConsensusState) scheduleTimeout(duration time.Duration, height, round int, step cstypes.RoundStepType) { cs.timeoutTicker.ScheduleTimeout(timeoutInfo{duration, height, round, step}) } @@ -523,7 +407,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { } // Updates ConsensusState and increments height to match that of state. -// The round becomes 0 and cs.Step becomes RoundStepNewHeight. +// The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight. func (cs *ConsensusState) updateToState(state *sm.State) { if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { cmn.PanicSanity(cmn.Fmt("updateToState() expected state height of %v but found %v", @@ -559,7 +443,7 @@ func (cs *ConsensusState) updateToState(state *sm.State) { // RoundState fields cs.updateHeight(height) - cs.updateRoundStep(0, RoundStepNewHeight) + cs.updateRoundStep(0, cstypes.RoundStepNewHeight) if cs.CommitTime.IsZero() { // "Now" makes it easier to sync up dev nodes. // We add timeoutCommit to allow transactions @@ -577,7 +461,7 @@ func (cs *ConsensusState) updateToState(state *sm.State) { cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil - cs.Votes = NewHeightVoteSet(state.ChainID, height, validators) + cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators) cs.CommitRound = -1 cs.LastCommit = lastPrecommits cs.LastValidators = state.LastValidators @@ -609,7 +493,7 @@ func (cs *ConsensusState) newStep() { func (cs *ConsensusState) receiveRoutine(maxSteps int) { defer func() { if r := recover(); r != nil { - cs.Logger.Error("CONSENSUS FAILURE!!!", "err", r) + cs.Logger.Error("CONSENSUS FAILURE!!!", "err", r, "stack", string(debug.Stack())) } }() @@ -698,7 +582,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { } } -func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs RoundState) { +func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs cstypes.RoundState) { cs.Logger.Debug("Received tock", "timeout", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) // timeouts must be for current height, round, step @@ -712,19 +596,19 @@ func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs RoundState) { defer cs.mtx.Unlock() switch ti.Step { - case RoundStepNewHeight: + case cstypes.RoundStepNewHeight: // NewRound event fired from enterNewRound. // XXX: should we fire timeout here (for timeout commit)? cs.enterNewRound(ti.Height, 0) - case RoundStepNewRound: + case cstypes.RoundStepNewRound: cs.enterPropose(ti.Height, 0) - case RoundStepPropose: + case cstypes.RoundStepPropose: types.FireEventTimeoutPropose(cs.evsw, cs.RoundStateEvent()) cs.enterPrevote(ti.Height, ti.Round) - case RoundStepPrevoteWait: + case cstypes.RoundStepPrevoteWait: types.FireEventTimeoutWait(cs.evsw, cs.RoundStateEvent()) cs.enterPrecommit(ti.Height, ti.Round) - case RoundStepPrecommitWait: + case cstypes.RoundStepPrecommitWait: types.FireEventTimeoutWait(cs.evsw, cs.RoundStateEvent()) cs.enterNewRound(ti.Height, ti.Round+1) default: @@ -751,7 +635,7 @@ func (cs *ConsensusState) handleTxsAvailable(height int) { // Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round) // NOTE: cs.StartTime was already set for height. func (cs *ConsensusState) enterNewRound(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != RoundStepNewHeight) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != cstypes.RoundStepNewHeight) { cs.Logger.Debug(cmn.Fmt("enterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -772,7 +656,7 @@ func (cs *ConsensusState) enterNewRound(height int, round int) { // Setup new round // we don't fire newStep for this step, // but we fire an event, so update the round step first - cs.updateRoundStep(round, RoundStepNewRound) + cs.updateRoundStep(round, cstypes.RoundStepNewRound) cs.Validators = validators if round == 0 { // We've already reset these upon new height, @@ -793,7 +677,7 @@ func (cs *ConsensusState) enterNewRound(height int, round int) { waitForTxs := cs.config.WaitForTxs() && round == 0 && !cs.needProofBlock(height) if waitForTxs { if cs.config.CreateEmptyBlocksInterval > 0 { - cs.scheduleTimeout(cs.config.EmptyBlocksInterval(), height, round, RoundStepNewRound) + cs.scheduleTimeout(cs.config.EmptyBlocksInterval(), height, round, cstypes.RoundStepNewRound) } go cs.proposalHeartbeat(height, round) } else { @@ -826,7 +710,7 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { for { rs := cs.GetRoundState() // if we've already moved on, no need to send more heartbeats - if rs.Step > RoundStepNewRound || rs.Round > round || rs.Height > height { + if rs.Step > cstypes.RoundStepNewRound || rs.Round > round || rs.Height > height { return } heartbeat := &types.Heartbeat{ @@ -848,7 +732,7 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { // Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval // Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool func (cs *ConsensusState) enterPropose(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -856,7 +740,7 @@ func (cs *ConsensusState) enterPropose(height int, round int) { defer func() { // Done enterPropose: - cs.updateRoundStep(round, RoundStepPropose) + cs.updateRoundStep(round, cstypes.RoundStepPropose) cs.newStep() // If we have the whole proposal + POL, then goto Prevote now. @@ -868,7 +752,7 @@ func (cs *ConsensusState) enterPropose(height int, round int) { }() // If we don't get the proposal and all block parts quick enough, enterPrevote - cs.scheduleTimeout(cs.config.Propose(round), height, round, RoundStepPropose) + cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose) // Nothing more to do if we're not a validator if cs.privValidator == nil { @@ -985,14 +869,14 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Prevote for LockedBlock if we're locked, or ProposalBlock if valid. // Otherwise vote nil. func (cs *ConsensusState) enterPrevote(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevote <= cs.Step) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevote <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } defer func() { // Done enterPrevote: - cs.updateRoundStep(round, RoundStepPrevote) + cs.updateRoundStep(round, cstypes.RoundStepPrevote) cs.newStep() }() @@ -1047,7 +931,7 @@ func (cs *ConsensusState) defaultDoPrevote(height int, round int) { // Enter: any +2/3 prevotes at next round. func (cs *ConsensusState) enterPrevoteWait(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevoteWait <= cs.Step) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -1058,12 +942,12 @@ func (cs *ConsensusState) enterPrevoteWait(height int, round int) { defer func() { // Done enterPrevoteWait: - cs.updateRoundStep(round, RoundStepPrevoteWait) + cs.updateRoundStep(round, cstypes.RoundStepPrevoteWait) cs.newStep() }() // Wait for some more prevotes; enterPrecommit - cs.scheduleTimeout(cs.config.Prevote(round), height, round, RoundStepPrevoteWait) + cs.scheduleTimeout(cs.config.Prevote(round), height, round, cstypes.RoundStepPrevoteWait) } // Enter: `timeoutPrevote` after any +2/3 prevotes. @@ -1073,7 +957,7 @@ func (cs *ConsensusState) enterPrevoteWait(height int, round int) { // else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, // else, precommit nil otherwise. func (cs *ConsensusState) enterPrecommit(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommit <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -1082,7 +966,7 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) { defer func() { // Done enterPrecommit: - cs.updateRoundStep(round, RoundStepPrecommit) + cs.updateRoundStep(round, cstypes.RoundStepPrecommit) cs.newStep() }() @@ -1166,7 +1050,7 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) { // Enter: any +2/3 precommits for next round. func (cs *ConsensusState) enterPrecommitWait(height int, round int) { - if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommitWait <= cs.Step) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommitWait <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -1177,18 +1061,18 @@ func (cs *ConsensusState) enterPrecommitWait(height int, round int) { defer func() { // Done enterPrecommitWait: - cs.updateRoundStep(round, RoundStepPrecommitWait) + cs.updateRoundStep(round, cstypes.RoundStepPrecommitWait) cs.newStep() }() // Wait for some more precommits; enterNewRound - cs.scheduleTimeout(cs.config.Precommit(round), height, round, RoundStepPrecommitWait) + cs.scheduleTimeout(cs.config.Precommit(round), height, round, cstypes.RoundStepPrecommitWait) } // Enter: +2/3 precommits for block func (cs *ConsensusState) enterCommit(height int, commitRound int) { - if cs.Height != height || RoundStepCommit <= cs.Step { + if cs.Height != height || cstypes.RoundStepCommit <= cs.Step { cs.Logger.Debug(cmn.Fmt("enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step)) return } @@ -1197,7 +1081,7 @@ func (cs *ConsensusState) enterCommit(height int, commitRound int) { defer func() { // Done enterCommit: // keep cs.Round the same, commitRound points to the right Precommits set. - cs.updateRoundStep(cs.Round, RoundStepCommit) + cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) cs.CommitRound = commitRound cs.CommitTime = time.Now() cs.newStep() @@ -1254,9 +1138,9 @@ func (cs *ConsensusState) tryFinalizeCommit(height int) { cs.finalizeCommit(height) } -// Increment height and goto RoundStepNewHeight +// Increment height and goto cstypes.RoundStepNewHeight func (cs *ConsensusState) finalizeCommit(height int) { - if cs.Height != height || cs.Step != RoundStepCommit { + if cs.Height != height || cs.Step != cstypes.RoundStepCommit { cs.Logger.Debug(cmn.Fmt("finalizeCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step)) return } @@ -1350,7 +1234,7 @@ func (cs *ConsensusState) finalizeCommit(height int) { // By here, // * cs.Height has been increment to height+1 - // * cs.Step is now RoundStepNewHeight + // * cs.Step is now cstypes.RoundStepNewHeight // * cs.StartTime is set to when we will start round0. } @@ -1368,8 +1252,8 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return nil } - // We don't care about the proposal if we're already in RoundStepCommit. - if RoundStepCommit <= cs.Step { + // We don't care about the proposal if we're already in cstypes.RoundStepCommit. + if cstypes.RoundStepCommit <= cs.Step { return nil } @@ -1414,10 +1298,10 @@ func (cs *ConsensusState) addProposalBlockPart(height int, part *types.Part, ver cs.state.Params().BlockSizeParams.MaxBytes, &n, &err).(*types.Block) // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) - if cs.Step == RoundStepPropose && cs.isProposalComplete() { + if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() { // Move onto the next step cs.enterPrevote(height, cs.Round) - } else if cs.Step == RoundStepCommit { + } else if cs.Step == cstypes.RoundStepCommit { // If we're waiting on the proposal block... cs.tryFinalizeCommit(height) } @@ -1462,7 +1346,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, // A precommit for the previous height? // These come in while we wait timeoutCommit if vote.Height+1 == cs.Height { - if !(cs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { + if !(cs.Step == cstypes.RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { // TODO: give the reason .. // fmt.Errorf("tryAddVote: Wrong height, not a LastCommit straggler commit.") return added, ErrVoteHeightMismatch @@ -1475,7 +1359,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, // if we can skip timeoutCommit and have all the votes now, if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { // go straight to new round (skip timeout commit) - // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, RoundStepNewHeight) + // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) cs.enterNewRound(cs.Height, 0) } } @@ -1539,7 +1423,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, if cs.config.SkipTimeoutCommit && precommits.HasAll() { // if we have all the votes now, // go straight to new round (skip timeout commit) - // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, RoundStepNewHeight) + // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) cs.enterNewRound(cs.Height, 0) } @@ -1600,7 +1484,7 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.Part //--------------------------------------------------------- -func CompareHRS(h1, r1 int, s1 RoundStepType, h2, r2 int, s2 RoundStepType) int { +func CompareHRS(h1, r1 int, s1 cstypes.RoundStepType, h2, r2 int, s2 cstypes.RoundStepType) int { if h1 < h2 { return -1 } else if h1 > h2 { diff --git a/consensus/state_test.go b/consensus/state_test.go index c4a6769e9..69b6d53ce 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -6,8 +6,9 @@ import ( "testing" "time" + cstypes "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) func init() { @@ -80,7 +81,7 @@ func TestProposerSelection0(t *testing.T) { prop = cs1.GetRoundState().Validators.GetProposer() if !bytes.Equal(prop.Address, vss[1].GetAddress()) { - panic(Fmt("expected proposer to be validator %d. Got %X", 1, prop.Address)) + panic(cmn.Fmt("expected proposer to be validator %d. Got %X", 1, prop.Address)) } } @@ -101,7 +102,7 @@ func TestProposerSelection2(t *testing.T) { for i := 0; i < len(vss); i++ { prop := cs1.GetRoundState().Validators.GetProposer() if !bytes.Equal(prop.Address, vss[(i+2)%len(vss)].GetAddress()) { - panic(Fmt("expected proposer to be validator %d. Got %X", (i+2)%len(vss), prop.Address)) + panic(cmn.Fmt("expected proposer to be validator %d. Got %X", (i+2)%len(vss), prop.Address)) } rs := cs1.GetRoundState() @@ -247,7 +248,7 @@ func TestFullRound1(t *testing.T) { // grab proposal re := <-propCh - propBlockHash := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState).ProposalBlock.Hash() + propBlockHash := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState).ProposalBlock.Hash() <-voteCh // wait for prevote // NOTE: voteChan cap of 0 ensures we can complete this @@ -344,7 +345,7 @@ func TestLockNoPOL(t *testing.T) { cs1.startRoutines(0) re := <-proposalCh - rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() <-voteCh // prevote @@ -384,7 +385,7 @@ func TestLockNoPOL(t *testing.T) { // now we're on a new round and not the proposer, so wait for timeout re = <-timeoutProposeCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) if rs.ProposalBlock != nil { panic("Expected proposal block to be nil") @@ -428,11 +429,11 @@ func TestLockNoPOL(t *testing.T) { incrementRound(vs2) re = <-proposalCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) // now we're on a new round and are the proposer if !bytes.Equal(rs.ProposalBlock.Hash(), rs.LockedBlock.Hash()) { - panic(Fmt("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) + panic(cmn.Fmt("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } <-voteCh // prevote @@ -515,7 +516,7 @@ func TestLockPOLRelock(t *testing.T) { <-newRoundCh re := <-proposalCh - rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() <-voteCh // prevote @@ -591,7 +592,7 @@ func TestLockPOLRelock(t *testing.T) { be := <-newBlockCh b := be.(types.TMEventData).Unwrap().(types.EventDataNewBlockHeader) re = <-newRoundCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) if rs.Height != 2 { panic("Expected height to increment") } @@ -627,7 +628,7 @@ func TestLockPOLUnlock(t *testing.T) { startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh - rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) theBlockHash := rs.ProposalBlock.Hash() <-voteCh // prevote @@ -653,7 +654,7 @@ func TestLockPOLUnlock(t *testing.T) { // timeout to new round re = <-timeoutWaitCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) lockedBlockHash := rs.LockedBlock.Hash() //XXX: this isnt guaranteed to get there before the timeoutPropose ... @@ -713,7 +714,7 @@ func TestLockPOLSafety1(t *testing.T) { startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh - rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) propBlock := rs.ProposalBlock <-voteCh // prevote @@ -761,7 +762,7 @@ func TestLockPOLSafety1(t *testing.T) { re = <-proposalCh } - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) if rs.LockedBlock != nil { panic("we should not be locked!") @@ -848,7 +849,7 @@ func TestLockPOLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) - cs1.updateRoundStep(0, RoundStepPrecommitWait) + cs1.updateRoundStep(0, cstypes.RoundStepPrecommitWait) t.Log("### ONTO Round 1") // jump in at round 1 @@ -929,7 +930,7 @@ func TestSlashingPrevotes(t *testing.T) { re := <-proposalCh <-voteCh // prevote - rs := re.(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) // we should now be stuck in limbo forever, waiting for more prevotes // add one for a different block should cause us to go into prevote wait @@ -1009,7 +1010,7 @@ func TestHalt1(t *testing.T) { startTestRound(cs1, cs1.Height, 0) <-newRoundCh re := <-proposalCh - rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet(partSize) @@ -1032,7 +1033,7 @@ func TestHalt1(t *testing.T) { // timeout to new round <-timeoutWaitCh re = <-newRoundCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) t.Log("### ONTO ROUND 1") /*Round2 @@ -1050,7 +1051,7 @@ func TestHalt1(t *testing.T) { // receiving that precommit should take us straight to commit <-newBlockCh re = <-newRoundCh - rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*RoundState) + rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState) if rs.Height != 2 { panic("expected height to increment") diff --git a/consensus/height_vote_set.go b/consensus/types/height_vote_set.go similarity index 95% rename from consensus/height_vote_set.go rename to consensus/types/height_vote_set.go index 455004f92..18c1c78a6 100644 --- a/consensus/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -1,11 +1,11 @@ -package consensus +package types import ( "strings" "sync" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) type RoundVoteSet struct { @@ -76,7 +76,7 @@ func (hvs *HeightVoteSet) SetRound(round int) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if hvs.round != 0 && (round < hvs.round+1) { - PanicSanity("SetRound() must increment hvs.round") + cmn.PanicSanity("SetRound() must increment hvs.round") } for r := hvs.round + 1; r <= round; r++ { if _, ok := hvs.roundVoteSets[r]; ok { @@ -89,7 +89,7 @@ func (hvs *HeightVoteSet) SetRound(round int) { func (hvs *HeightVoteSet) addRound(round int) { if _, ok := hvs.roundVoteSets[round]; ok { - PanicSanity("addRound() for an existing round") + cmn.PanicSanity("addRound() for an existing round") } // log.Debug("addRound(round)", "round", round) prevotes := types.NewVoteSet(hvs.chainID, hvs.height, round, types.VoteTypePrevote, hvs.valSet) @@ -164,7 +164,7 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet { case types.VoteTypePrecommit: return rvs.Precommits default: - PanicSanity(Fmt("Unexpected vote type %X", type_)) + cmn.PanicSanity(cmn.Fmt("Unexpected vote type %X", type_)) return nil } } @@ -194,7 +194,7 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { voteSetString = roundVoteSet.Precommits.StringShort() vsStrings = append(vsStrings, voteSetString) } - return Fmt(`HeightVoteSet{H:%v R:0~%v + return cmn.Fmt(`HeightVoteSet{H:%v R:0~%v %s %v %s}`, hvs.height, hvs.round, diff --git a/consensus/height_vote_set_test.go b/consensus/types/height_vote_set_test.go similarity index 84% rename from consensus/height_vote_set_test.go rename to consensus/types/height_vote_set_test.go index 7e03e40f5..d57973687 100644 --- a/consensus/height_vote_set_test.go +++ b/consensus/types/height_vote_set_test.go @@ -1,14 +1,17 @@ -package consensus +package types import ( "testing" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) +var config *cfg.Config // NOTE: must be reset for each _test.go file + func init() { - config = ResetConfig("consensus_height_vote_set_test") + config = cfg.ResetTestRoot("consensus_height_vote_set_test") } func TestPeerCatchupRounds(t *testing.T) { @@ -57,7 +60,7 @@ func makeVoteHR(t *testing.T, height, round int, privVals []*types.PrivValidator chainID := config.ChainID err := privVal.SignVote(chainID, vote) if err != nil { - panic(Fmt("Error signing vote: %v", err)) + panic(cmn.Fmt("Error signing vote: %v", err)) return nil } return vote diff --git a/consensus/types/reactor.go b/consensus/types/reactor.go new file mode 100644 index 000000000..2306ee389 --- /dev/null +++ b/consensus/types/reactor.go @@ -0,0 +1,57 @@ +package types + +import ( + "fmt" + "time" + + "github.com/tendermint/tendermint/types" + cmn "github.com/tendermint/tmlibs/common" +) + +//----------------------------------------------------------------------------- + +// PeerRoundState contains the known state of a peer. +// NOTE: Read-only when returned by PeerState.GetRoundState(). +type PeerRoundState struct { + Height int // Height peer is at + Round int // Round peer is at, -1 if unknown. + Step RoundStepType // Step peer is at + StartTime time.Time // Estimated start of round 0 at this height + Proposal bool // True if peer has proposal for this round + ProposalBlockPartsHeader types.PartSetHeader // + ProposalBlockParts *cmn.BitArray // + ProposalPOLRound int // Proposal's POL round. -1 if none. + ProposalPOL *cmn.BitArray // nil until ProposalPOLMessage received. + Prevotes *cmn.BitArray // All votes peer has for this round + Precommits *cmn.BitArray // All precommits peer has for this round + LastCommitRound int // Round of commit for last height. -1 if none. + LastCommit *cmn.BitArray // All commit precommits of commit for last height. + CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. + CatchupCommit *cmn.BitArray // All commit precommits peer has for this height & CatchupCommitRound +} + +// String returns a string representation of the PeerRoundState +func (prs PeerRoundState) String() string { + return prs.StringIndented("") +} + +// StringIndented returns a string representation of the PeerRoundState +func (prs PeerRoundState) StringIndented(indent string) string { + return fmt.Sprintf(`PeerRoundState{ +%s %v/%v/%v @%v +%s Proposal %v -> %v +%s POL %v (round %v) +%s Prevotes %v +%s Precommits %v +%s LastCommit %v (round %v) +%s Catchup %v (round %v) +%s}`, + indent, prs.Height, prs.Round, prs.Step, prs.StartTime, + indent, prs.ProposalBlockPartsHeader, prs.ProposalBlockParts, + indent, prs.ProposalPOL, prs.ProposalPOLRound, + indent, prs.Prevotes, + indent, prs.Precommits, + indent, prs.LastCommit, prs.LastCommitRound, + indent, prs.CatchupCommit, prs.CatchupCommitRound, + indent) +} diff --git a/consensus/types/state.go b/consensus/types/state.go new file mode 100644 index 000000000..0e6b15778 --- /dev/null +++ b/consensus/types/state.go @@ -0,0 +1,126 @@ +package types + +import ( + "fmt" + "time" + + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------------------------------- +// RoundStepType enum type + +// RoundStepType enumerates the state of the consensus state machine +type RoundStepType uint8 // These must be numeric, ordered. + +const ( + RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit + RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose + RoundStepPropose = RoundStepType(0x03) // Did propose, gossip proposal + RoundStepPrevote = RoundStepType(0x04) // Did prevote, gossip prevotes + RoundStepPrevoteWait = RoundStepType(0x05) // Did receive any +2/3 prevotes, start timeout + RoundStepPrecommit = RoundStepType(0x06) // Did precommit, gossip precommits + RoundStepPrecommitWait = RoundStepType(0x07) // Did receive any +2/3 precommits, start timeout + RoundStepCommit = RoundStepType(0x08) // Entered commit state machine + // NOTE: RoundStepNewHeight acts as RoundStepCommitWait. +) + +// String returns a string +func (rs RoundStepType) String() string { + switch rs { + case RoundStepNewHeight: + return "RoundStepNewHeight" + case RoundStepNewRound: + return "RoundStepNewRound" + case RoundStepPropose: + return "RoundStepPropose" + case RoundStepPrevote: + return "RoundStepPrevote" + case RoundStepPrevoteWait: + return "RoundStepPrevoteWait" + case RoundStepPrecommit: + return "RoundStepPrecommit" + case RoundStepPrecommitWait: + return "RoundStepPrecommitWait" + case RoundStepCommit: + return "RoundStepCommit" + default: + return "RoundStepUnknown" // Cannot panic. + } +} + +//----------------------------------------------------------------------------- + +// RoundState defines the internal consensus state. +// It is Immutable when returned from ConsensusState.GetRoundState() +// TODO: Actually, only the top pointer is copied, +// so access to field pointers is still racey +type RoundState struct { + Height int // Height we are working on + Round int + Step RoundStepType + StartTime time.Time + CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found + Validators *types.ValidatorSet + Proposal *types.Proposal + ProposalBlock *types.Block + ProposalBlockParts *types.PartSet + LockedRound int + LockedBlock *types.Block + LockedBlockParts *types.PartSet + Votes *HeightVoteSet + CommitRound int // + LastCommit *types.VoteSet // Last precommits at Height-1 + LastValidators *types.ValidatorSet +} + +// RoundStateEvent returns the H/R/S of the RoundState as an event. +func (rs *RoundState) RoundStateEvent() types.EventDataRoundState { + edrs := types.EventDataRoundState{ + Height: rs.Height, + Round: rs.Round, + Step: rs.Step.String(), + RoundState: rs, + } + return edrs +} + +// String returns a string +func (rs *RoundState) String() string { + return rs.StringIndented("") +} + +// StringIndented returns a string +func (rs *RoundState) StringIndented(indent string) string { + return fmt.Sprintf(`RoundState{ +%s H:%v R:%v S:%v +%s StartTime: %v +%s CommitTime: %v +%s Validators: %v +%s Proposal: %v +%s ProposalBlock: %v %v +%s LockedRound: %v +%s LockedBlock: %v %v +%s Votes: %v +%s LastCommit: %v +%s LastValidators: %v +%s}`, + indent, rs.Height, rs.Round, rs.Step, + indent, rs.StartTime, + indent, rs.CommitTime, + indent, rs.Validators.StringIndented(indent+" "), + indent, rs.Proposal, + indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), + indent, rs.LockedRound, + indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), + indent, rs.Votes.StringIndented(indent+" "), + indent, rs.LastCommit.StringShort(), + indent, rs.LastValidators.StringIndented(indent+" "), + indent) +} + +// StringShort returns a string +func (rs *RoundState) StringShort() string { + return fmt.Sprintf(`RoundState{H:%v R:%v S:%v ST:%v}`, + rs.Height, rs.Round, rs.Step, rs.StartTime) +} diff --git a/consensus/version.go b/consensus/version.go index 84f1ec81f..2c137bf7f 100644 --- a/consensus/version.go +++ b/consensus/version.go @@ -1,7 +1,7 @@ package consensus import ( - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) // kind of arbitrary @@ -10,4 +10,4 @@ var Major = "0" // var Minor = "2" // replay refactor var Revision = "2" // validation -> commit -var Version = Fmt("v%s/%s.%s.%s", Spec, Major, Minor, Revision) +var Version = cmn.Fmt("v%s/%s.%s.%s", Spec, Major, Minor, Revision) diff --git a/consensus/wal.go b/consensus/wal.go index a2ac470a0..f9a2a8015 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -6,7 +6,7 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" auto "github.com/tendermint/tmlibs/autofile" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) //-------------------------------------------------------- @@ -34,7 +34,7 @@ var _ = wire.RegisterInterface( // TODO: currently the wal is overwritten during replay catchup // give it a mode so it's either reading or appending - must read to end to start appending again type WAL struct { - BaseService + cmn.BaseService group *auto.Group light bool // ignore block parts @@ -49,7 +49,7 @@ func NewWAL(walFile string, light bool) (*WAL, error) { group: group, light: light, } - wal.BaseService = *NewBaseService(nil, "WAL", wal) + wal.BaseService = *cmn.NewBaseService(nil, "WAL", wal) return wal, nil } @@ -86,19 +86,19 @@ func (wal *WAL) Save(wmsg WALMessage) { var wmsgBytes = wire.JSONBytes(TimedWALMessage{time.Now(), wmsg}) err := wal.group.WriteLine(string(wmsgBytes)) if err != nil { - PanicQ(Fmt("Error writing msg to consensus wal. Error: %v \n\nMessage: %v", err, wmsg)) + cmn.PanicQ(cmn.Fmt("Error writing msg to consensus wal. Error: %v \n\nMessage: %v", err, wmsg)) } // TODO: only flush when necessary if err := wal.group.Flush(); err != nil { - PanicQ(Fmt("Error flushing consensus wal buf to file. Error: %v \n", err)) + cmn.PanicQ(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err)) } } func (wal *WAL) writeEndHeight(height int) { - wal.group.WriteLine(Fmt("#ENDHEIGHT: %v", height)) + wal.group.WriteLine(cmn.Fmt("#ENDHEIGHT: %v", height)) // TODO: only flush when necessary if err := wal.group.Flush(); err != nil { - PanicQ(Fmt("Error flushing consensus wal buf to file. Error: %v \n", err)) + cmn.PanicQ(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err)) } } diff --git a/docs/app-development.rst b/docs/app-development.rst index 011fb0f39..770572e13 100644 --- a/docs/app-development.rst +++ b/docs/app-development.rst @@ -142,6 +142,13 @@ It is unlikely that you will need to implement a client. For details of our client, see `here `__. +Most of the examples below are from `dummy application +`__, +which is a part of the abci repo. `persistent_dummy application +`__ +is used to show ``BeginBlock``, ``EndBlock`` and ``InitChain`` +example implementations. + Blockchain Protocol ------------------- @@ -187,6 +194,12 @@ through all transactions in the mempool, removing any that were included in the block, and re-run the rest using CheckTx against the post-Commit mempool state. +:: + + func (app *DummyApplication) CheckTx(tx []byte) types.Result { + return types.OK + } + Consensus Connection ~~~~~~~~~~~~~~~~~~~~ @@ -215,6 +228,19 @@ The block header will be updated (TODO) to include some commitment to the results of DeliverTx, be it a bitarray of non-OK transactions, or a merkle root of the data returned by the DeliverTx requests, or both. +:: + + // tx is either "key=value" or just arbitrary bytes + func (app *DummyApplication) DeliverTx(tx []byte) types.Result { + parts := strings.Split(string(tx), "=") + if len(parts) == 2 { + app.state.Set([]byte(parts[0]), []byte(parts[1])) + } else { + app.state.Set(tx, tx) + } + return types.OK + } + Commit ^^^^^^ @@ -228,7 +254,7 @@ Commit, or there will be deadlock. Note also that all remaining transactions in the mempool are replayed on the mempool connection (CheckTx) following a commit. -The Commit response includes a byte array, which is the deterministic +The app should respond to the Commit request with a byte array, which is the deterministic state root of the application. It is included in the header of the next block. It can be used to provide easily verified Merkle-proofs of the state of the application. @@ -237,6 +263,13 @@ It is expected that the app will persist state to disk on Commit. The option to have all transactions replayed from some previous block is the job of the `Handshake <#handshake>`__. +:: + + func (app *DummyApplication) Commit() types.Result { + hash := app.state.Hash() + return types.NewResultOK(hash, "") + } + BeginBlock ^^^^^^^^^^ @@ -248,6 +281,17 @@ The app should remember the latest height and header (ie. from which it has run a successful Commit) so that it can tell Tendermint where to pick up from when it restarts. See information on the Handshake, below. +:: + + // Track the block hash and header information + func (app *PersistentDummyApplication) BeginBlock(params types.RequestBeginBlock) { + // update latest block info + app.blockHeader = params.Header + + // reset valset changes + app.changes = make([]*types.Validator, 0) + } + EndBlock ^^^^^^^^ @@ -260,6 +304,13 @@ EndBlock response. To remove one, include it in the list with a validator set. Note validator set changes are only available in v0.8.0 and up. +:: + + // Update the validator set + func (app *PersistentDummyApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) { + return types.ResponseEndBlock{Diffs: app.changes} + } + Query Connection ~~~~~~~~~~~~~~~~ @@ -281,6 +332,34 @@ cause Tendermint to not connect to the corresponding peer: Note: these query formats are subject to change! +:: + + func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { + if reqQuery.Prove { + value, proof, exists := app.state.Proof(reqQuery.Data) + resQuery.Index = -1 // TODO make Proof return index + resQuery.Key = reqQuery.Data + resQuery.Value = value + resQuery.Proof = proof + if exists { + resQuery.Log = "exists" + } else { + resQuery.Log = "does not exist" + } + return + } else { + index, value, exists := app.state.Get(reqQuery.Data) + resQuery.Index = int64(index) + resQuery.Value = value + if exists { + resQuery.Log = "exists" + } else { + resQuery.Log = "does not exist" + } + return + } + } + Handshake ~~~~~~~~~ @@ -297,3 +376,28 @@ the app are synced to the latest block height. If the app returns a LastBlockHeight of 0, Tendermint will just replay all blocks. + +:: + + func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { + return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())} + } + +Genesis +~~~~~~~ + +``InitChain`` will be called once upon the genesis. ``params`` includes the +initial validator set. Later on, it may be extended to take parts of the +consensus params. + +:: + + // Save the validators in the merkle tree + func (app *PersistentDummyApplication) InitChain(params types.RequestInitChain) { + for _, v := range params.Validators { + r := app.updateValidator(v) + if r.IsErr() { + app.logger.Error("Error updating validators", "r", r) + } + } + } diff --git a/docs/ecosystem.rst b/docs/ecosystem.rst index 188f50092..c0c386304 100644 --- a/docs/ecosystem.rst +++ b/docs/ecosystem.rst @@ -36,37 +36,50 @@ Ethermint The go-ethereum state machine run as a ABCI app, written in Go, `authored by Tendermint `__. +IAVL +^^^^ -Merkle AVL Tree -^^^^^^^^^^^^^^^ +Immutable AVL+ tree with Merkle proofs, Written in Go, `authored by Tendermint `__. -The following are implementations of the Tendermint IAVL tree as an ABCI application - -Merkleeyes -~~~~~~~~~~ - -Written in Go, `authored by Tendermint `__. - -MerkleTree -~~~~~~~~~~ - -Written in Java, `authored by jTendermint `__. - - -TMChat +Lotion ^^^^^^ -P2P chat using Tendermint, written in Java, `authored by woldposd `__. - +A Javascript microframework for building blockchain applications with Tendermint, written in Javascript, `authored by Judd Keppel of Tendermint `__. See also `lotion-chat `__ and `lotion-coin `__ apps written using Lotion. + +MerkleTree +^^^^^^^^^^ + +Immutable AVL+ tree with Merkle proofs, Written in Java, `authored by jTendermint `__. + +Passchain +^^^^^^^^^ + +Passchain is a tool to securely store and share passwords, tokens and other short secrets, `authored by trusch `__. + Passwerk ^^^^^^^^ Encrypted storage web-utility backed by Tendermint, written in Go, `authored by Rigel Rozanski `__. +Py-Tendermint +^^^^^^^^^^^^^ + +A Python microframework for building blockchain applications with Tendermint, written in Python, `authored by Dave Bryson `__. + +Stratumn +^^^^^^^^ + +SDK for "Proof-of-Process" networks, written in Go, `authored by the Stratumn team `__. + +TMChat +^^^^^^ + +P2P chat using Tendermint, written in Java, `authored by wolfposd `__. + + ABCI Servers ------------ - +-------------------------------------------------------------+--------------------+--------------+ | **Name** | **Author** | **Language** | | | | | @@ -75,7 +88,9 @@ ABCI Servers +-------------------------------------------------------------+--------------------+--------------+ | `js abci `__ | Tendermint | Javascript | +-------------------------------------------------------------+--------------------+--------------+ -| `cpp-tmsp `__ | Martin Dyring | C++ | +| `cpp-tmsp `__ | Martin Dyring | C++ | ++-------------------------------------------------------------+--------------------+--------------+ +| `c-abci `__ | ChainX | C | +-------------------------------------------------------------+--------------------+--------------+ | `jabci `__ | jTendermint | Java | +-------------------------------------------------------------+--------------------+--------------+ @@ -85,6 +100,12 @@ ABCI Servers +-------------------------------------------------------------+--------------------+--------------+ | `abci_server `__ | Krzysztof Jurewicz | Erlang | +-------------------------------------------------------------+--------------------+--------------+ +| `rust-tsp `__   | Adrian Brink | Rust       | ++-------------------------------------------------------------+--------------------+--------------+ +| `hs-abci `__ | Alberto Gonzalez | Haskell | ++-------------------------------------------------------------+--------------------+--------------+ +| `haskell-abci `__ | Christoper Goes | Haskell | ++-------------------------------------------------------------+--------------------+--------------+ Deployment Tools ---------------- diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 6745b482a..4a7e4b1ba 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -73,7 +73,7 @@ Tendermint before, use: :: - tendermint init + tendermint init tendermint node If you have used Tendermint, you may want to reset the data for a new @@ -107,7 +107,24 @@ like: :: - {"jsonrpc":"2.0","id":"","result":[98,{"check_tx":{},"deliver_tx":{}}],"error":""} + { + "jsonrpc": "2.0", + "id": "", + "result": { + "check_tx": { + "code": 0, + "data": "", + "log": "" + }, + "deliver_tx": { + "code": 0, + "data": "", + "log": "" + }, + "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF", + "height": 154 + } + } The ``98`` is a type-byte, and can be ignored (it's useful for serializing and deserializing arbitrary json). Otherwise, this result is @@ -118,14 +135,27 @@ querying the app: :: - curl -s 'localhost:46657/abci_query?data="abcd"&path=""&prove=false' + curl -s 'localhost:46657/abci_query?data="abcd"' -The ``path`` and ``prove`` arguments can be ignored for now, and in a -future release can be left out. The result should look like: +The result should look like: :: - {"jsonrpc":"2.0","id":"","result":[112,{"response":{"value":"61626364","log":"exists"}}],"error":""} + { + "jsonrpc": "2.0", + "id": "", + "result": { + "response": { + "code": 0, + "index": 0, + "key": "", + "value": "61626364", + "proof": "", + "height": 0, + "log": "exists" + } + } + } Again, the ``112`` is the type-byte. Note the ``value`` in the result (``61626364``); this is the hex-encoding of the ASCII of ``abcd``. You @@ -144,7 +174,7 @@ Now if we query for ``name``, we should get ``satoshi``, or :: - curl -s 'localhost:46657/abci_query?data="name"&path=""&prove=false' + curl -s 'localhost:46657/abci_query?data="name"' Try some other transactions and queries to make sure everything is working! @@ -204,14 +234,48 @@ the number ``1``. If instead, we try to send a ``5``, we get an error: :: > curl localhost:46657/broadcast_tx_commit?tx=0x05 - {"jsonrpc":"2.0","id":"","result":[98,{"check_tx":{},"deliver_tx":{"code":3,"log":"Invalid nonce. Expected 1, got 5"}}],"error":""} + { + "jsonrpc": "2.0", + "id": "", + "result": { + "check_tx": { + "code": 0, + "data": "", + "log": "" + }, + "deliver_tx": { + "code": 3, + "data": "", + "log": "Invalid nonce. Expected 1, got 5" + }, + "hash": "33B93DFF98749B0D6996A70F64071347060DC19C", + "height": 38 + } + } But if we send a ``1``, it works again: :: > curl localhost:46657/broadcast_tx_commit?tx=0x01 - {"jsonrpc":"2.0","id":"","result":[98,{"check_tx":{},"deliver_tx":{}}],"error":""} + { + "jsonrpc": "2.0", + "id": "", + "result": { + "check_tx": { + "code": 0, + "data": "", + "log": "" + }, + "deliver_tx": { + "code": 0, + "data": "", + "log": "" + }, + "hash": "F17854A977F6FA7EEA1BD758E296710B86F72F3D", + "height": 87 + } + } For more details on the ``broadcast_tx`` API, see `the guide on using Tendermint <./using-tendermint.html>`__. diff --git a/docs/how-to-read-logs.rst b/docs/how-to-read-logs.rst new file mode 100644 index 000000000..890057a2f --- /dev/null +++ b/docs/how-to-read-logs.rst @@ -0,0 +1,165 @@ +How to read logs +================ + +Walk through example +-------------------- + +We first create three connections (mempool, consensus and query) to the +application (locally running dummy in this case). + +:: + + I[10-04|13:54:27.364] Starting multiAppConn module=proxy impl=multiAppConn + I[10-04|13:54:27.366] Starting localClient module=abci-client connection=query impl=localClient + I[10-04|13:54:27.366] Starting localClient module=abci-client connection=mempool impl=localClient + I[10-04|13:54:27.367] Starting localClient module=abci-client connection=consensus impl=localClient + +Then Tendermint Core and the application perform a handshake. + +:: + + I[10-04|13:54:27.367] ABCI Handshake module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD + I[10-04|13:54:27.368] ABCI Replay Blocks module=consensus appHeight=90 storeHeight=90 stateHeight=90 + I[10-04|13:54:27.368] Completed ABCI Handshake - Tendermint and App are synced module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD + +After that, we start a few more things like the event switch, reactors, and +perform UPNP discover in order to detect the IP address. + +:: + + I[10-04|13:54:27.374] Starting EventSwitch module=types impl=EventSwitch + I[10-04|13:54:27.375] This node is a validator module=consensus + I[10-04|13:54:27.379] Starting Node module=main impl=Node + I[10-04|13:54:27.381] Local listener module=p2p ip=:: port=46656 + I[10-04|13:54:27.382] Getting UPNP external address module=p2p + I[10-04|13:54:30.386] Could not perform UPNP discover module=p2p err="write udp4 0.0.0.0:38238->239.255.255.250:1900: i/o timeout" + I[10-04|13:54:30.386] Starting DefaultListener module=p2p impl=Listener(@10.0.2.15:46656) + I[10-04|13:54:30.387] Starting P2P Switch module=p2p impl="P2P Switch" + I[10-04|13:54:30.387] Starting MempoolReactor module=mempool impl=MempoolReactor + I[10-04|13:54:30.387] Starting BlockchainReactor module=blockchain impl=BlockchainReactor + I[10-04|13:54:30.387] Starting ConsensusReactor module=consensus impl=ConsensusReactor + I[10-04|13:54:30.387] ConsensusReactor module=consensus fastSync=false + I[10-04|13:54:30.387] Starting ConsensusState module=consensus impl=ConsensusState + I[10-04|13:54:30.387] Starting WAL module=consensus wal=/home/vagrant/.tendermint/data/cs.wal/wal impl=WAL + I[10-04|13:54:30.388] Starting TimeoutTicker module=consensus impl=TimeoutTicker + +Notice the second row where Tendermint Core reports that "This node is a +validator". It also could be just an observer (regular node). + +Next we replay all the messages from the WAL. + +:: + + I[10-04|13:54:30.390] Catchup by replaying consensus messages module=consensus height=91 + I[10-04|13:54:30.390] Replay: New Step module=consensus height=91 round=0 step=RoundStepNewHeight + I[10-04|13:54:30.390] Replay: Done module=consensus + +"Started node" message signals that everything is ready for work. + +:: + + I[10-04|13:54:30.391] Starting RPC HTTP server on tcp socket 0.0.0.0:46657 module=rpc-server + I[10-04|13:54:30.392] Started node module=main nodeInfo="NodeInfo{pk: PubKeyEd25519{DF22D7C92C91082324A1312F092AA1DA197FA598DBBFB6526E177003C4D6FD66}, moniker: anonymous, network: test-chain-3MNw2N [remote , listen 10.0.2.15:46656], version: 0.11.0-10f361fc ([wire_version=0.6.2 p2p_version=0.5.0 consensus_version=v1/0.2.2 rpc_version=0.7.0/3 tx_index=on rpc_addr=tcp://0.0.0.0:46657])}" + +Next follows a standard block creation cycle, where we enter a new round, +propose a block, receive more than 2/3 of prevotes, then precommits and finally +have a chance to commit a block. For details, please refer to `Consensus +Overview +`__ +or `Byzantine Consensus Algorithm +`__. + +:: + + I[10-04|13:54:30.393] enterNewRound(91/0). Current: 91/0/RoundStepNewHeight module=consensus + I[10-04|13:54:30.393] enterPropose(91/0). Current: 91/0/RoundStepNewRound module=consensus + I[10-04|13:54:30.393] enterPropose: Our turn to propose module=consensus proposer=125B0E3C5512F5C2B0E1109E31885C4511570C42 privValidator="PrivValidator{125B0E3C5512F5C2B0E1109E31885C4511570C42 LH:90, LR:0, LS:3}" + I[10-04|13:54:30.394] Signed proposal module=consensus height=91 round=0 proposal="Proposal{91/0 1:21B79872514F (-1,:0:000000000000) {/10EDEDD7C84E.../}}" + I[10-04|13:54:30.397] Received complete proposal block module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E + I[10-04|13:54:30.397] enterPrevote(91/0). Current: 91/0/RoundStepPropose module=consensus + I[10-04|13:54:30.397] enterPrevote: ProposalBlock is valid module=consensus height=91 round=0 + I[10-04|13:54:30.398] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" err=null + I[10-04|13:54:30.401] Added to prevote module=consensus vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" prevotes="VoteSet{H:91 R:0 T:1 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}" + I[10-04|13:54:30.401] enterPrecommit(91/0). Current: 91/0/RoundStepPrevote module=consensus + I[10-04|13:54:30.401] enterPrecommit: +2/3 prevoted proposal block. Locking module=consensus hash=F671D562C7B9242900A286E1882EE64E5556FE9E + I[10-04|13:54:30.402] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" err=null + I[10-04|13:54:30.404] Added to precommit module=consensus vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" precommits="VoteSet{H:91 R:0 T:2 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}" + I[10-04|13:54:30.404] enterCommit(91/0). Current: 91/0/RoundStepPrecommit module=consensus + I[10-04|13:54:30.405] Finalizing commit of block with 0 txs module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E root=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD + I[10-04|13:54:30.405] Block{ + Header{ + ChainID: test-chain-3MNw2N + Height: 91 + Time: 2017-10-04 13:54:30.393 +0000 UTC + NumTxs: 0 + LastBlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544 + LastCommit: 56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D + Data: + Validators: CE25FBFF2E10C0D51AA1A07C064A96931BC8B297 + App: E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD + }#F671D562C7B9242900A286E1882EE64E5556FE9E + Data{ + + }# + Commit{ + BlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544 + Precommits: Vote{0:125B0E3C5512 90/00/2(Precommit) F15AB8BEF9A6 {/FE98E2B956F0.../}} + }#56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D + }#F671D562C7B9242900A286E1882EE64E5556FE9E module=consensus + I[10-04|13:54:30.408] Executed block module=state height=91 validTxs=0 invalidTxs=0 + I[10-04|13:54:30.410] Committed state module=state height=91 txs=0 hash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD + I[10-04|13:54:30.410] Recheck txs module=mempool numtxs=0 height=91 + +List of modules +--------------- + +Here is the list of modules you may encounter in Tendermint's log and a little +overview what they do. + +- ``abci-client`` As mentioned in `Application Development Guide + `__, + Tendermint acts as an ABCI client with respect to the application and + maintains 3 connections: mempool, consensus and query. The code used by + Tendermint Core can be found `here + `__. + +- ``blockchain`` + Provides storage, pool (a group of peers), and reactor for both storing and + exchanging blocks between peers. + +- ``consensus`` + The heart of Tendermint core, which is the implementation of the consensus + algorithm. Includes two "submodules": ``wal`` (write-ahead logging) for + ensuring data integrity and ``replay`` to replay blocks and messages on + recovery from a crash. + +- ``events`` + Simple event notification system. The list of events can be found + `here + `__. + You can subscribe to them by calling ``subscribe`` RPC method. + Refer to `RPC docs + `__ + for additional information. + +- ``mempool`` + Mempool module handles all incoming transactions, whenever they are + coming from peers or the application. + +- ``p2p`` + Provides an abstraction around peer-to-peer communication. For more details, + please check out the `README + `__. + +- ``rpc`` + `Tendermint's RPC `__. + +- ``rpc-server`` + RPC server. For implementation details, please read the `README `__. + +- ``state`` + Represents the latest state and execution submodule, which executes + blocks against the application. + +- ``types`` + A collection of the publicly exposed types and methods to work with them. diff --git a/docs/index.rst b/docs/index.rst index ee1307bf2..4c226516f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,8 +15,6 @@ Welcome to Tendermint! Tendermint 101 -------------- -.. maxdepth set to 2 for sexinesss -.. but use 4 to upgrade overall documentation .. toctree:: :maxdepth: 2 @@ -25,9 +23,19 @@ Tendermint 101 getting-started.rst using-tendermint.rst +Tendermint Ecosystem +-------------------- + +.. toctree:: + :maxdepth: 2 + + ecosystem.rst + Tendermint Tools ---------------- +.. the tools/ files are pulled in from the tools repo +.. see the bottom of conf.py .. toctree:: :maxdepth: 2 @@ -38,15 +46,6 @@ Tendermint Tools tools/terraform-digitalocean.rst tools/benchmarking-and-monitoring.rst - -Tendermint Ecosystem --------------------- - -.. toctree:: - :maxdepth: 2 - - ecosystem.rst - Tendermint 102 -------------- @@ -56,6 +55,7 @@ Tendermint 102 abci-cli.rst app-architecture.rst app-development.rst + how-to-read-logs.rst Tendermint 201 -------------- diff --git a/docs/specification.rst b/docs/specification.rst index 2e8b35668..3afa9c659 100644 --- a/docs/specification.rst +++ b/docs/specification.rst @@ -2,19 +2,19 @@ Specification ############# -Here you'll find details of the Tendermint specification. See `the spec repo `__ for upcoming material. Tendermint's types are produced by `godoc `__ +Here you'll find details of the Tendermint specification. See `the spec repo `__ for upcoming material. Tendermint's types are produced by `godoc `__. .. toctree:: :maxdepth: 2 - specification/block-structure.rst - specification/byzantine-consensus-algorithm.rst - specification/configuration.rst - specification/fast-sync.rst - specification/genesis.rst - specification/light-client-protocol.rst - specification/merkle.rst - specification/rpc.rst - specification/secure-p2p.rst - specification/validators.rst - specification/wire-protocol.rst + specification/block-structure.rst + specification/byzantine-consensus-algorithm.rst + specification/configuration.rst + specification/fast-sync.rst + specification/genesis.rst + specification/light-client-protocol.rst + specification/merkle.rst + specification/rpc.rst + specification/secure-p2p.rst + specification/validators.rst + specification/wire-protocol.rst diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 81d1c41d0..94801136f 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -30,14 +30,16 @@ The main config parameters are defined - ``consensus.max_block_size_txs``: Maximum number of block txs. *Default*: ``10000`` +- ``consensus.create_empty_blocks``: Create empty blocks w/o txs. + *Default*: ``true`` +- ``consensus.create_empty_blocks_interval``: Block creation interval, even if empty. - ``consensus.timeout_*``: Various consensus timeout parameters - **TODO** - ``consensus.wal_file``: Consensus state WAL. *Default*: - ``"$TMHOME/data/cswal"`` + ``"$TMHOME/data/cs.wal/wal"`` - ``consensus.wal_light``: Whether to use light-mode for Consensus state WAL. *Default*: ``false`` -- ``mempool.*``: Various mempool parameters **TODO** +- ``mempool.*``: Various mempool parameters - ``p2p.addr_book_file``: Peer address book. *Default*: ``"$TMHOME/addrbook.json"``. **NOT USED** diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 1cb3ad86e..cc94245cd 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -116,7 +116,7 @@ new blockchain will not make any blocks. Configuration ------------- -Tendermint uses a ``config.toml`` for configutation. For details, see +Tendermint uses a ``config.toml`` for configuration. For details, see `the documentation <./specification/configuration.html>`__. Notable options include the socket address of the application @@ -126,6 +126,38 @@ Notable options include the socket address of the application Some fields from the config file can be overwritten with flags. +No Empty Blocks +--------------- + +This much requested feature was implemented in version 0.10.3. While the default behaviour of ``tendermint`` is still to create blocks approximately once per second, it is possible to disable empty blocks or set a block creation interval. In the former case, blocks will be created when there are new transactions or when the AppHash changes. + +To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, +run tendermint with this additional flag: + +:: + + tendermint node --consensus.create_empty_blocks=false + +or set the configuration via the ``config.toml`` file: + +:: + + [consensus] + create_empty_blocks = false + +Remember: because the default is to *create empty blocks*, avoiding empty blocks requires the config option to be set to ``false``. + +The block interval setting allows for a delay (in seconds) between the creation of each new empty block. It is set via the ``config.toml``: + +:: + + [consensus] + create_empty_blocks_interval = 5 + +With this setting, empty blocks will be produced every 5s if no block has been produced otherwise, +regardless of the value of `create_empty_blocks`. + + Broadcast API ------------- diff --git a/glide.lock b/glide.lock index 6e295a12a..a7a2bb5a4 100644 --- a/glide.lock +++ b/glide.lock @@ -1,16 +1,18 @@ -hash: e3649cac7b1b9a23c024a9d1bbebd5a147861d55da2bca77c95129b6021850b4 -updated: 2017-09-22T13:24:29.443800586-04:00 +hash: 816d84782ab66637e02bd0a3c7f652a9a31f9b88e3ae11438c5bf641cf585f19 +updated: 2017-10-02T23:32:49.162422718-04:00 imports: - name: github.com/btcsuite/btcd - version: 4803a8291c92a1d2d41041b942a9a9e37deab065 + version: b8df516b4b267acf2de46be593a9d948d1d2c420 subpackages: - btcec +- name: github.com/btcsuite/fastsha256 + version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/ebuchman/fail-test version: 95f809107225be108efcf10a3509e4ea6ceef3c4 - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 - name: github.com/go-kit/kit - version: 0d313fb5fb3a94d87d61e6434785264e87a5d740 + version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 subpackages: - log - log/level @@ -24,25 +26,22 @@ imports: - name: github.com/go-playground/universal-translator version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack - version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf + version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/gogo/protobuf - version: 2adc21fd136931e0388e278825291678e1d98309 + version: f7f1376d9d231a646d4e62fe1075623ced6db327 subpackages: - proto - name: github.com/golang/protobuf - version: 130e6b02ab059e7b717a096f397c5b60111cae74 + version: 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 subpackages: - proto - - ptypes - ptypes/any - - ptypes/duration - - ptypes/timestamp - name: github.com/golang/snappy version: 553a641470496b2327abcac10b36396bd98e45c9 - name: github.com/gorilla/websocket - version: 6f34763140ed8887aed6a044912009832b4733d7 + version: a91eba7f97777409bc2c443f5534d41dd20c5720 - name: github.com/hashicorp/hcl - version: 68e816d1c783414e79bc65b3994d9ab6b0a722ab + version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca subpackages: - hcl/ast - hcl/parser @@ -59,31 +58,33 @@ imports: - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/magiconair/properties - version: 8d7837e64d3c1ee4e54a880c5a920ab4316fc90a + version: 51463bfca2576e06c62a8504b5c0f06d61312647 - name: github.com/mitchellh/mapstructure - version: d0303fe809921458f417bcf828397a65db30a7e4 + version: cc8532a8e9a55ea36402aa21efdf403a60d34096 +- name: github.com/pelletier/go-buffruneio + version: c37440a7cf42ac63b919c752ca73a85067e05992 - name: github.com/pelletier/go-toml - version: 1d6b12b7cb290426e27e6b4e38b89fcda3aeef03 + version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/rcrowley/go-metrics version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c - name: github.com/spf13/afero - version: ee1bd8ee15a1306d1f9201acc41ef39cd9f99a1b + version: 9be650865eab0c12963d8753212f4f9c66cdcf12 subpackages: - mem - name: github.com/spf13/cast version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 - name: github.com/spf13/cobra - version: b78744579491c1ceeaaa3b40205e56b0591b93a3 + version: 4cdb38c072b86bf795d2c81de50784d9fdd6eb77 - name: github.com/spf13/jwalterweatherman - version: 12bd96e66386c1960ab0f74ced1362f66f552f7b + version: 8f07c835e5cc1450c082fe3a439cf87b0cbb2d99 - name: github.com/spf13/pflag - version: 7aff26db30c1be810f9de5038ec5ef96ac41fd7c + version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 - name: github.com/spf13/viper - version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 + version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2 - name: github.com/syndtr/goleveldb - version: b89cc31ef7977104127d34c1bd31ebd1a9db2199 + version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 subpackages: - leveldb - leveldb/cache @@ -122,7 +123,7 @@ imports: subpackages: - iavl - name: github.com/tendermint/tmlibs - version: 9997e3a3b46db1d2f88aa9816ed0e7915dad6ac1 + version: 096dcb90e60aa00b748b3fe49a4b95e48ebf1e13 subpackages: - autofile - cli @@ -136,7 +137,7 @@ imports: - merkle - test - name: golang.org/x/crypto - version: 7d9177d70076375b9a59c8fde23d52d9c4a7ecd5 + version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e subpackages: - curve25519 - nacl/box @@ -147,7 +148,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: 0744d001aa8470aaa53df28d32e5ceeb8af9bd70 + version: feeb485667d1fdabe727840fe00adc22431bc86e subpackages: - context - http2 @@ -157,46 +158,43 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 429f518978ab01db8bb6f44b66785088e7fba58b + version: e62c3de784db939836898e5c19ffd41bece347da subpackages: - unix - name: golang.org/x/text - version: 1cbadb444a806fd9430d14ad08967ed91da4fa0a + version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4 subpackages: - secure/bidirule - transform - unicode/bidi - unicode/norm - name: google.golang.org/genproto - version: 1e559d0a00eef8a9a43151db4665280bd8dd5886 + version: 411e09b969b1170a9f0c467558eb4c4c110d9c77 subpackages: - googleapis/rpc/status - name: google.golang.org/grpc - version: d4b75ebd4f9f8c4a2b1cdadbdbe0d7920431ccca + version: 844f573616520565fdc6fb4db242321b5456fd6d subpackages: - - balancer - codes - - connectivity - credentials - - grpclb/grpc_lb_v1/messages + - grpclb/grpc_lb_v1 - grpclog - internal - keepalive - metadata - naming - peer - - resolver - stats - status - tap - transport - name: gopkg.in/go-playground/validator.v9 - version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64 + version: 6d8c18553ea1ac493d049edd6f102f52e618f085 - name: gopkg.in/yaml.v2 - version: eb3733d160e74a9c7e442f435eb3bea458e1d19f + version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b testImports: - name: github.com/davecgh/go-spew - version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew - name: github.com/pmezard/go-difflib @@ -204,7 +202,7 @@ testImports: subpackages: - difflib - name: github.com/stretchr/testify - version: 890a5c3458b43e6104ff5da8dfa139d013d77544 + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert - require diff --git a/glide.yaml b/glide.yaml index 58f25711d..27b6cc395 100644 --- a/glide.yaml +++ b/glide.yaml @@ -30,7 +30,7 @@ import: subpackages: - iavl - package: github.com/tendermint/tmlibs - version: ~0.3.1 + version: ~0.3.2 subpackages: - autofile - cli diff --git a/mempool/mempool.go b/mempool/mempool.go index fc6e40c1a..07b267c4d 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -24,7 +24,7 @@ import ( The mempool pushes new txs onto the proxyAppConn. It gets a stream of (req, res) tuples from the proxy. -The memool stores good txs in a concurrent linked-list. +The mempool stores good txs in a concurrent linked-list. Multiple concurrent go-routines can traverse this linked-list safely by calling .NextWait() on each element. diff --git a/node/id.go b/node/id.go index c521aa4af..fa391f946 100644 --- a/node/id.go +++ b/node/id.go @@ -1,8 +1,9 @@ package node import ( - "github.com/tendermint/go-crypto" "time" + + "github.com/tendermint/go-crypto" ) type NodeID struct { diff --git a/p2p/ip_range_counter.go b/p2p/ip_range_counter.go deleted file mode 100644 index 85d9d407a..000000000 --- a/p2p/ip_range_counter.go +++ /dev/null @@ -1,29 +0,0 @@ -package p2p - -import ( - "strings" -) - -// TODO Test -func AddToIPRangeCounts(counts map[string]int, ip string) map[string]int { - changes := make(map[string]int) - ipParts := strings.Split(ip, ":") - for i := 1; i < len(ipParts); i++ { - prefix := strings.Join(ipParts[:i], ":") - counts[prefix] += 1 - changes[prefix] = counts[prefix] - } - return changes -} - -// TODO Test -func CheckIPRangeCounts(counts map[string]int, limits []int) bool { - for prefix, count := range counts { - ipParts := strings.Split(prefix, ":") - numParts := len(ipParts) - if limits[numParts] < count { - return false - } - } - return true -} diff --git a/p2p/listener.go b/p2p/listener.go index a382fbeda..971390974 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -196,7 +196,7 @@ func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) * return NewNetAddressIPPort(ext, uint16(externalPort)) } -// TODO: use syscalls: http://pastebin.com/9exZG4rh +// TODO: use syscalls: see issue #712 func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *NetAddress { addrs, err := net.InterfaceAddrs() if err != nil { diff --git a/p2p/peer.go b/p2p/peer.go index 2b986a7a7..1bdb8210a 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -7,6 +7,7 @@ import ( "time" "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" diff --git a/p2p/upnp/README.md b/p2p/upnp/README.md deleted file mode 100644 index 557d05bdc..000000000 --- a/p2p/upnp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# `tendermint/p2p/upnp` - -## Resources - -* http://www.upnp-hacks.org/upnp.html diff --git a/p2p/upnp/upnp.go b/p2p/upnp/upnp.go index 29abbe794..7d44d1e31 100644 --- a/p2p/upnp/upnp.go +++ b/p2p/upnp/upnp.go @@ -1,11 +1,9 @@ -/* -Taken from taipei-torrent - -Just enough UPnP to be able to forward ports -*/ +// Taken from taipei-torrent. +// Just enough UPnP to be able to forward ports +// For more information, see: http://www.upnp-hacks.org/upnp.html package upnp -// BUG(jae): TODO: use syscalls to get actual ourIP. http://pastebin.com/9exZG4rh +// TODO: use syscalls to get actual ourIP, see issue #712 import ( "bytes" diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index 7f29183d5..b00c97d23 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/pkg/errors" + data "github.com/tendermint/go-wire/data" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpcclient "github.com/tendermint/tendermint/rpc/lib/client" @@ -226,7 +227,9 @@ func (w *WSEvents) Start() (bool, error) { st, err := w.EventSwitch.Start() // if we did start, then OnStart here... if st && err == nil { - ws := rpcclient.NewWSClient(w.remote, w.endpoint) + ws := rpcclient.NewWSClient(w.remote, w.endpoint, rpcclient.OnReconnect(func() { + w.redoSubscriptions() + })) _, err = ws.Start() if err == nil { w.ws = ws @@ -305,6 +308,14 @@ func (w *WSEvents) RemoveListener(listenerID string) { w.EventSwitch.RemoveListener(listenerID) } +// After being reconnected, it is necessary to redo subscription +// to server otherwise no data will be automatically received +func (w *WSEvents) redoSubscriptions() { + for event, _ := range w.evtCount { + w.subscribe(event) + } +} + // eventListener is an infinite loop pulling all websocket events // and pushing them to the EventSwitch. // diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go index 935f9ff94..245db6c68 100644 --- a/rpc/client/mock/abci_test.go +++ b/rpc/client/mock/abci_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/abci/example/dummy" abci "github.com/tendermint/abci/types" data "github.com/tendermint/go-wire/data" + "github.com/tendermint/tendermint/rpc/client/mock" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" - - "github.com/tendermint/tendermint/rpc/client/mock" ) func TestABCIMock(t *testing.T) { diff --git a/rpc/client/mock/status_test.go b/rpc/client/mock/status_test.go index e4adf52ba..80ffe7b7d 100644 --- a/rpc/client/mock/status_test.go +++ b/rpc/client/mock/status_test.go @@ -5,10 +5,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - data "github.com/tendermint/go-wire/data" - ctypes "github.com/tendermint/tendermint/rpc/core/types" + data "github.com/tendermint/go-wire/data" "github.com/tendermint/tendermint/rpc/client/mock" + ctypes "github.com/tendermint/tendermint/rpc/core/types" ) func TestStatus(t *testing.T) { diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 77de9f6e8..9bcd3de45 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -6,7 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/merkleeyes/iavl" + + "github.com/tendermint/merkleeyes/iavl" //TODO use tendermint/iavl ? "github.com/tendermint/tendermint/rpc/client" rpctest "github.com/tendermint/tendermint/rpc/test" "github.com/tendermint/tendermint/types" diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 17b27303f..ad00060f4 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -5,7 +5,7 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) // Get block headers for minHeight <= height <= maxHeight. @@ -65,12 +65,12 @@ func BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, err if maxHeight == 0 { maxHeight = blockStore.Height() } else { - maxHeight = MinInt(blockStore.Height(), maxHeight) + maxHeight = cmn.MinInt(blockStore.Height(), maxHeight) } if minHeight == 0 { - minHeight = MaxInt(1, maxHeight-20) + minHeight = cmn.MaxInt(1, maxHeight-20) } else { - minHeight = MaxInt(minHeight, maxHeight-20) + minHeight = cmn.MaxInt(minHeight, maxHeight-20) } logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight) diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 0429c8d45..75ce08a9a 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -1,8 +1,8 @@ package core import ( - wire "github.com/tendermint/go-wire" cm "github.com/tendermint/tendermint/consensus" + cstypes "github.com/tendermint/tendermint/consensus/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" ) @@ -82,14 +82,11 @@ func Validators(heightPtr *int) (*ctypes.ResultValidators, error) { // } // ``` func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - roundState := consensusState.GetRoundState() - peerRoundStates := []string{} + peerRoundStates := make(map[string]*cstypes.PeerRoundState) for _, peer := range p2pSwitch.Peers().List() { - // TODO: clean this up? peerState := peer.Get(types.PeerStateKey).(*cm.PeerState) peerRoundState := peerState.GetRoundState() - peerRoundStateStr := peer.Key() + ":" + string(wire.JSONBytes(peerRoundState)) - peerRoundStates = append(peerRoundStates, peerRoundStateStr) + peerRoundStates[peer.Key()] = peerRoundState } - return &ctypes.ResultDumpConsensusState{roundState.String(), peerRoundStates}, nil + return &ctypes.ResultDumpConsensusState{consensusState.GetRoundState(), peerRoundStates}, nil } diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 86c6c65e1..20141cb91 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -2,14 +2,14 @@ package core import ( crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/log" - "github.com/tendermint/tendermint/consensus" + cstypes "github.com/tendermint/tendermint/consensus/types" p2p "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/txindex" "github.com/tendermint/tendermint/types" + "github.com/tendermint/tmlibs/log" ) //---------------------------------------------- @@ -18,7 +18,7 @@ import ( type Consensus interface { GetState() *sm.State GetValidators() (int, []*types.Validator) - GetRoundState() *consensus.RoundState + GetRoundState() *cstypes.RoundState } type P2P interface { diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 6b66f1168..a5ed6f5a8 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -4,9 +4,9 @@ import ( "strings" abci "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire/data" - + cstypes "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) @@ -76,8 +76,8 @@ type ResultValidators struct { } type ResultDumpConsensusState struct { - RoundState string `json:"round_state"` - PeerRoundStates []string `json:"peer_round_states"` + RoundState *cstypes.RoundState `json:"round_state"` + PeerRoundStates map[string]*cstypes.PeerRoundState `json:"peer_round_states"` } type ResultBroadcastTx struct { diff --git a/rpc/core/types/responses_test.go b/rpc/core/types/responses_test.go index 8eef19799..fa0da3fdf 100644 --- a/rpc/core/types/responses_test.go +++ b/rpc/core/types/responses_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/p2p" ) diff --git a/rpc/grpc/api.go b/rpc/grpc/api.go index 7cfda1587..b08a7833a 100644 --- a/rpc/grpc/api.go +++ b/rpc/grpc/api.go @@ -1,11 +1,10 @@ package core_grpc import ( - core "github.com/tendermint/tendermint/rpc/core" + context "golang.org/x/net/context" abci "github.com/tendermint/abci/types" - - context "golang.org/x/net/context" + core "github.com/tendermint/tendermint/rpc/core" ) type broadcastAPI struct { diff --git a/rpc/grpc/client_server.go b/rpc/grpc/client_server.go index e6055ede3..1c6498df7 100644 --- a/rpc/grpc/client_server.go +++ b/rpc/grpc/client_server.go @@ -8,7 +8,7 @@ import ( "google.golang.org/grpc" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) // Start the grpcServer in a go routine @@ -40,5 +40,5 @@ func StartGRPCClient(protoAddr string) BroadcastAPIClient { } func dialerFunc(addr string, timeout time.Duration) (net.Conn, error) { - return Connect(addr) + return cmn.Connect(addr) } diff --git a/rpc/grpc/types.proto b/rpc/grpc/types.proto index b32393a04..a7d18daec 100644 --- a/rpc/grpc/types.proto +++ b/rpc/grpc/types.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package core_grpc; -import "github.com/tendermint/abci/types/types.proto"; +import "github.com/tendermint/abci/blob/master/types/types.proto"; //---------------------------------------- // Message types diff --git a/rpc/lib/Dockerfile b/rpc/lib/Dockerfile deleted file mode 100644 index a194711bf..000000000 --- a/rpc/lib/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:latest - -RUN mkdir -p /go/src/github.com/tendermint/tendermint/rpc/lib -WORKDIR /go/src/github.com/tendermint/tendermint/rpc/lib - -COPY Makefile /go/src/github.com/tendermint/tendermint/rpc/lib/ -# COPY glide.yaml /go/src/github.com/tendermint/tendermint/rpc/lib/ -# COPY glide.lock /go/src/github.com/tendermint/tendermint/rpc/lib/ - -COPY . /go/src/github.com/tendermint/tendermint/rpc/lib - -RUN make get_deps diff --git a/rpc/lib/Makefile b/rpc/lib/Makefile deleted file mode 100644 index 0937558a8..000000000 --- a/rpc/lib/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -PACKAGES=$(shell go list ./... | grep -v "test") - -all: get_deps test - -test: - @echo "--> Running go test --race" - @go test --race $(PACKAGES) - @echo "--> Running integration tests" - @bash ./test/integration_test.sh - -get_deps: - @echo "--> Running go get" - @go get -v -d $(PACKAGES) - @go list -f '{{join .TestImports "\n"}}' ./... | \ - grep -v /vendor/ | sort | uniq | \ - xargs go get -v -d - -.PHONY: all test get_deps diff --git a/rpc/lib/circle.yml b/rpc/lib/circle.yml deleted file mode 100644 index 0308a4e79..000000000 --- a/rpc/lib/circle.yml +++ /dev/null @@ -1,21 +0,0 @@ -machine: - environment: - GOPATH: /home/ubuntu/.go_workspace - REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME - hosts: - circlehost: 127.0.0.1 - localhost: 127.0.0.1 - -checkout: - post: - - rm -rf $REPO - - mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME - - mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO - -dependencies: - override: - - "cd $REPO && make get_deps" - -test: - override: - - "cd $REPO && make test" diff --git a/rpc/lib/client/ws_client.go b/rpc/lib/client/ws_client.go index 1407073ae..2bdfa5c9a 100644 --- a/rpc/lib/client/ws_client.go +++ b/rpc/lib/client/ws_client.go @@ -44,6 +44,9 @@ type WSClient struct { ResultsCh chan json.RawMessage ErrorsCh chan error + // Callback, which will be called each time after successful reconnect. + onReconnect func() + // internal channels send chan types.RPCRequest // user requests backlog chan types.RPCRequest // stores a single user request received during a conn failure @@ -124,6 +127,14 @@ func PingPeriod(pingPeriod time.Duration) func(*WSClient) { } } +// OnReconnect sets the callback, which will be called every time after +// successful reconnect. +func OnReconnect(cb func()) func(*WSClient) { + return func(c *WSClient) { + c.onReconnect = cb + } +} + // String returns WS client full address. func (c *WSClient) String() string { return fmt.Sprintf("%s (%s)", c.Address, c.Endpoint) @@ -254,6 +265,9 @@ func (c *WSClient) reconnect() error { c.Logger.Error("failed to redial", "err", err) } else { c.Logger.Info("reconnected") + if c.onReconnect != nil { + go c.onReconnect() + } return nil } diff --git a/rpc/lib/README.md b/rpc/lib/doc.go similarity index 79% rename from rpc/lib/README.md rename to rpc/lib/doc.go index de481c2f6..0ea4e5c65 100644 --- a/rpc/lib/README.md +++ b/rpc/lib/doc.go @@ -1,7 +1,4 @@ -# tendermint/rpc/lib - -[![CircleCI](https://circleci.com/gh/tendermint/tendermint/rpc/lib.svg?style=svg)](https://circleci.com/gh/tendermint/tendermint/rpc/lib) - +/* HTTP RPC server supporting calls via uri params, jsonrpc, and jsonrpc over websockets # Client Requests @@ -102,20 +99,5 @@ Each route is available as a GET request, as a JSONRPCv2 POST request, and via J * [Tendermint](https://github.com/tendermint/tendermint/blob/master/rpc/core/routes.go) * [tm-monitor](https://github.com/tendermint/tools/blob/master/tm-monitor/rpc.go) - -## CHANGELOG - -### 0.7.0 - -BREAKING CHANGES: - -- removed `Client` empty interface -- `ClientJSONRPC#Call` `params` argument became a map -- rename `ClientURI` -> `URIClient`, `ClientJSONRPC` -> `JSONRPCClient` - -IMPROVEMENTS: - -- added `HTTPClient` interface, which can be used for both `ClientURI` -and `ClientJSONRPC` -- all params are now optional (Golang's default will be used if some param is missing) -- added `Call` method to `WSClient` (see method's doc for details) +*/ +package rpc diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index 7415cb36d..4e83d23ef 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-kit/kit/log/term" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/go-wire/data" client "github.com/tendermint/tendermint/rpc/lib/client" server "github.com/tendermint/tendermint/rpc/lib/server" @@ -362,7 +363,7 @@ func TestWSClientPingPong(t *testing.T) { require.Nil(t, err) defer cl.Stop() - time.Sleep(3 * time.Second) + time.Sleep(6 * time.Second) } func randBytes(t *testing.T) []byte { diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 0fafd2a84..3a3c48f02 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "reflect" + "runtime/debug" "sort" "strings" "time" @@ -116,6 +117,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han // A Notification is a Request object without an "id" member. // The Server MUST NOT reply to a Notification, including those that are within a batch request. if request.ID == "" { + logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") return } if len(r.URL.Path) > 1 { @@ -127,10 +129,13 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(request.ID)) return } - args, err := jsonParamsToArgsRPC(rpcFunc, request.Params) - if err != nil { - WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "Error converting json params to arguments"))) - return + var args []reflect.Value + if len(request.Params) > 0 { + args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) + if err != nil { + WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "Error converting json params to arguments"))) + return + } } returns := rpcFunc.f.Call(args) logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) @@ -208,13 +213,13 @@ func jsonParamsToArgs(rpcFunc *RPCFunc, raw []byte, argsOffset int) ([]reflect.V } // Convert a []interface{} OR a map[string]interface{} to properly typed values -func jsonParamsToArgsRPC(rpcFunc *RPCFunc, params *json.RawMessage) ([]reflect.Value, error) { - return jsonParamsToArgs(rpcFunc, *params, 0) +func jsonParamsToArgsRPC(rpcFunc *RPCFunc, params json.RawMessage) ([]reflect.Value, error) { + return jsonParamsToArgs(rpcFunc, params, 0) } // Same as above, but with the first param the websocket connection -func jsonParamsToArgsWS(rpcFunc *RPCFunc, params *json.RawMessage, wsCtx types.WSRPCContext) ([]reflect.Value, error) { - values, err := jsonParamsToArgs(rpcFunc, *params, 1) +func jsonParamsToArgsWS(rpcFunc *RPCFunc, params json.RawMessage, wsCtx types.WSRPCContext) ([]reflect.Value, error) { + values, err := jsonParamsToArgs(rpcFunc, params, 1) if err != nil { return nil, err } @@ -485,9 +490,23 @@ func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool { // Read from the socket and subscribe to or unsubscribe from events func (wsc *wsConnection) readRoutine() { defer func() { - wsc.baseConn.Close() + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("WSJSONRPC: %v", r) + } + wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) + wsc.WriteRPCResponse(types.RPCInternalError("unknown", err)) + go wsc.readRoutine() + } else { + wsc.baseConn.Close() + } }() + wsc.baseConn.SetPongHandler(func(m string) error { + return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)) + }) + for { select { case <-wsc.Quit: @@ -517,6 +536,7 @@ func (wsc *wsConnection) readRoutine() { // A Notification is a Request object without an "id" member. // The Server MUST NOT reply to a Notification, including those that are within a batch request. if request.ID == "" { + wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") continue } @@ -530,9 +550,13 @@ func (wsc *wsConnection) readRoutine() { var args []reflect.Value if rpcFunc.ws { wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc} - args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx) + if len(request.Params) > 0 { + args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx) + } } else { - args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) + if len(request.Params) > 0 { + args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) + } } if err != nil { wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "Error converting json params to arguments"))) diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go index 4928db514..07a8e5683 100644 --- a/rpc/lib/types/types.go +++ b/rpc/lib/types/types.go @@ -14,10 +14,10 @@ import ( // REQUEST type RPCRequest struct { - JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Method string `json:"method"` - Params *json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} + JSONRPC string `json:"jsonrpc"` + ID string `json:"id"` + Method string `json:"method"` + Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} } func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest { @@ -25,7 +25,7 @@ func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest JSONRPC: "2.0", ID: id, Method: method, - Params: ¶ms, + Params: params, } } diff --git a/scripts/publish.sh b/scripts/publish.sh index 3091575ff..ba9440878 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -12,6 +12,7 @@ if [ -z "$VERSION" ]; then echo "Please specify a version." exit 1 fi +echo "==> Copying ${DIST_DIR} to S3..." # copy to s3 aws s3 cp --recursive ${DIST_DIR} s3://tendermint/binaries/tendermint/v${VERSION} --acl public-read diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 000000000..02899ad5d --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -e + +# Get the version from the environment, or try to figure it out. +if [ -z $VERSION ]; then + VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) +fi +if [ -z "$VERSION" ]; then + echo "Please specify a version." + exit 1 +fi +echo "==> Releasing version $VERSION..." + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" + +# Change into that dir because we expect that. +cd "$DIR" + +# Building binaries +sh -c "'$DIR/scripts/dist.sh'" + +# Pushing binaries to S3 +sh -c "'$DIR/scripts/publish.sh'" + +echo "==> Crafting a Github release" +today=$(date +"%B-%d-%Y") +ghr -b "https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#${VERSION//.}-${today,}" "v$VERSION" "$DIR/build/dist" + +# Build and push Docker image + +## Get SHA256SUM of the linux archive +SHA256SUM=$(shasum -a256 "${DIR}/build/dist/tendermint_${VERSION}_linux_amd64.zip" | awk '{print $1;}') + +## Replace TM_VERSION and TM_SHA256SUM with the new values +sed -i -e "s/TM_VERSION .*/TM_VERSION $VERSION/g" "$DIR/DOCKER/Dockerfile" +sed -i -e "s/TM_SHA256SUM .*/TM_SHA256SUM $SHA256SUM/g" "$DIR/DOCKER/Dockerfile" +git commit -m "update Dockerfile" -a "$DIR/DOCKER/Dockerfile" +echo "==> TODO: update DOCKER/README.md (latest Dockerfile's hash is $(git rev-parse HEAD)) and copy it's content to https://store.docker.com/community/images/tendermint/tendermint" + +pushd "$DIR/DOCKER" + +## Build Docker image +TAG=$VERSION sh -c "'./build.sh'" + +## Push Docker image +TAG=$VERSION sh -c "'./push.sh'" + +popd + +exit 0 diff --git a/scripts/tendermint-builder/Dockerfile b/scripts/tendermint-builder/Dockerfile index daf931715..0c5130c50 100644 --- a/scripts/tendermint-builder/Dockerfile +++ b/scripts/tendermint-builder/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.8.3 +FROM golang:1.9.0 RUN apt-get update && apt-get install -y --no-install-recommends \ zip \ diff --git a/state/execution_test.go b/state/execution_test.go index 2987667c5..425f59ed2 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/abci/example/dummy" crypto "github.com/tendermint/go-crypto" "github.com/tendermint/tendermint/proxy" diff --git a/state/state_test.go b/state/state_test.go index 2c19a4164..2ab2e9349 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -7,16 +7,14 @@ import ( "github.com/stretchr/testify/assert" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/types" + abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" - - cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/types" ) // setupTestCase does setup common to all test cases diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 8a2702fc8..7e5cecef8 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.8.3 +FROM golang:1.9.0 # Add testing deps for curl RUN echo 'deb http://httpredir.debian.org/debian testing main non-free contrib' >> /etc/apt/sources.list diff --git a/types/genesis_test.go b/types/genesis_test.go index fe997b690..0ffce4b53 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + crypto "github.com/tendermint/go-crypto" ) diff --git a/types/part_set_test.go b/types/part_set_test.go index 7088ef317..76b538c1f 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) const ( @@ -15,7 +15,7 @@ const ( func TestBasicPartSet(t *testing.T) { // Construct random data of size partSize * 100 - data := RandBytes(testPartSize * 100) + data := cmn.RandBytes(testPartSize * 100) partSet := NewPartSetFromData(data, testPartSize) if len(partSet.Hash()) == 0 { @@ -65,7 +65,7 @@ func TestBasicPartSet(t *testing.T) { func TestWrongProof(t *testing.T) { // Construct random data of size partSize * 100 - data := RandBytes(testPartSize * 100) + data := cmn.RandBytes(testPartSize * 100) partSet := NewPartSetFromData(data, testPartSize) // Test adding a part with wrong data. diff --git a/types/proposal.go b/types/proposal.go index 8fe959807..8efa91b6d 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -5,7 +5,6 @@ import ( "fmt" "io" - //. "github.com/tendermint/tmlibs/common" "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" ) diff --git a/types/signable.go b/types/signable.go index 13389fef7..2134f6300 100644 --- a/types/signable.go +++ b/types/signable.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/merkle" ) @@ -19,7 +19,7 @@ func SignBytes(chainID string, o Signable) []byte { buf, n, err := new(bytes.Buffer), new(int), new(error) o.WriteSignBytes(chainID, buf, n, err) if *err != nil { - PanicCrisis(err) + cmn.PanicCrisis(err) } return buf.Bytes() } diff --git a/types/tx_test.go b/types/tx_test.go index 91cddecfe..dc99509dd 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" ctest "github.com/tendermint/tmlibs/test" diff --git a/types/validator_set.go b/types/validator_set.go index 3ec389a48..0e20417a7 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -99,7 +99,12 @@ func (valSet *ValidatorSet) GetByAddress(address []byte) (index int, val *Valida } } +// GetByIndex returns the validator by index. +// It returns nil values if index >= len(ValidatorSet.Validators) func (valSet *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) { + if index >= len(valSet.Validators) { + return nil, nil + } val = valSet.Validators[index] return val.Address, val.Copy() } diff --git a/types/vote.go b/types/vote.go index 164293c53..658415682 100644 --- a/types/vote.go +++ b/types/vote.go @@ -13,9 +13,9 @@ import ( var ( ErrVoteUnexpectedStep = errors.New("Unexpected step") - ErrVoteInvalidValidatorIndex = errors.New("Invalid round vote validator index") - ErrVoteInvalidValidatorAddress = errors.New("Invalid round vote validator address") - ErrVoteInvalidSignature = errors.New("Invalid round vote signature") + ErrVoteInvalidValidatorIndex = errors.New("Invalid validator index") + ErrVoteInvalidValidatorAddress = errors.New("Invalid validator address") + ErrVoteInvalidSignature = errors.New("Invalid signature") ErrVoteInvalidBlockHash = errors.New("Invalid block hash") ) diff --git a/types/vote_set.go b/types/vote_set.go index 938dbcb61..dcfb0088d 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" ) /* @@ -51,7 +51,7 @@ type VoteSet struct { mtx sync.Mutex valSet *ValidatorSet - votesBitArray *BitArray + votesBitArray *cmn.BitArray votes []*Vote // Primary votes to share sum int64 // Sum of voting power for seen votes, discounting conflicts maj23 *BlockID // First 2/3 majority seen @@ -62,7 +62,7 @@ type VoteSet struct { // Constructs a new VoteSet struct used to accumulate votes for given height/round. func NewVoteSet(chainID string, height int, round int, type_ byte, valSet *ValidatorSet) *VoteSet { if height == 0 { - PanicSanity("Cannot make VoteSet for height == 0, doesn't make sense.") + cmn.PanicSanity("Cannot make VoteSet for height == 0, doesn't make sense.") } return &VoteSet{ chainID: chainID, @@ -70,7 +70,7 @@ func NewVoteSet(chainID string, height int, round int, type_ byte, valSet *Valid round: round, type_: type_, valSet: valSet, - votesBitArray: NewBitArray(valSet.Size()), + votesBitArray: cmn.NewBitArray(valSet.Size()), votes: make([]*Vote, valSet.Size()), sum: 0, maj23: nil, @@ -125,7 +125,7 @@ func (voteSet *VoteSet) Size() int { // NOTE: VoteSet must not be nil func (voteSet *VoteSet) AddVote(vote *Vote) (added bool, err error) { if voteSet == nil { - PanicSanity("AddVote() on nil VoteSet") + cmn.PanicSanity("AddVote() on nil VoteSet") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() @@ -140,8 +140,10 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) { blockKey := vote.BlockID.Key() // Ensure that validator index was set - if valIndex < 0 || len(valAddr) == 0 { - panic("Validator index or address was not set in vote.") + if valIndex < 0 { + return false, ErrVoteInvalidValidatorIndex + } else if len(valAddr) == 0 { + return false, ErrVoteInvalidValidatorAddress } // Make sure the step matches. @@ -186,7 +188,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) { } } else { if !added { - PanicSanity("Expected to add non-conflicting vote") + cmn.PanicSanity("Expected to add non-conflicting vote") } return added, nil } @@ -212,7 +214,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower // Already exists in voteSet.votes? if existing := voteSet.votes[valIndex]; existing != nil { if existing.BlockID.Equals(vote.BlockID) { - PanicSanity("addVerifiedVote does not expect duplicate votes") + cmn.PanicSanity("addVerifiedVote does not expect duplicate votes") } else { conflicting = existing } @@ -283,7 +285,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower // NOTE: VoteSet must not be nil func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) { if voteSet == nil { - PanicSanity("SetPeerMaj23() on nil VoteSet") + cmn.PanicSanity("SetPeerMaj23() on nil VoteSet") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() @@ -316,7 +318,7 @@ func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) { } } -func (voteSet *VoteSet) BitArray() *BitArray { +func (voteSet *VoteSet) BitArray() *cmn.BitArray { if voteSet == nil { return nil } @@ -325,7 +327,7 @@ func (voteSet *VoteSet) BitArray() *BitArray { return voteSet.votesBitArray.Copy() } -func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *BitArray { +func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *cmn.BitArray { if voteSet == nil { return nil } @@ -356,7 +358,7 @@ func (voteSet *VoteSet) GetByAddress(address []byte) *Vote { defer voteSet.mtx.Unlock() valIndex, val := voteSet.valSet.GetByAddress(address) if val == nil { - PanicSanity("GetByAddress(address) returned nil") + cmn.PanicSanity("GetByAddress(address) returned nil") } return voteSet.votes[valIndex] } @@ -454,14 +456,14 @@ func (voteSet *VoteSet) StringShort() string { func (voteSet *VoteSet) MakeCommit() *Commit { if voteSet.type_ != VoteTypePrecommit { - PanicSanity("Cannot MakeCommit() unless VoteSet.Type is VoteTypePrecommit") + cmn.PanicSanity("Cannot MakeCommit() unless VoteSet.Type is VoteTypePrecommit") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() // Make sure we have a 2/3 majority if voteSet.maj23 == nil { - PanicSanity("Cannot MakeCommit() unless a blockhash has +2/3") + cmn.PanicSanity("Cannot MakeCommit() unless a blockhash has +2/3") } // For every validator, get the precommit @@ -482,16 +484,16 @@ func (voteSet *VoteSet) MakeCommit() *Commit { 2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true) */ type blockVotes struct { - peerMaj23 bool // peer claims to have maj23 - bitArray *BitArray // valIndex -> hasVote? - votes []*Vote // valIndex -> *Vote - sum int64 // vote sum + peerMaj23 bool // peer claims to have maj23 + bitArray *cmn.BitArray // valIndex -> hasVote? + votes []*Vote // valIndex -> *Vote + sum int64 // vote sum } func newBlockVotes(peerMaj23 bool, numValidators int) *blockVotes { return &blockVotes{ peerMaj23: peerMaj23, - bitArray: NewBitArray(numValidators), + bitArray: cmn.NewBitArray(numValidators), votes: make([]*Vote, numValidators), sum: 0, } @@ -521,7 +523,7 @@ type VoteSetReader interface { Round() int Type() byte Size() int - BitArray() *BitArray + BitArray() *cmn.BitArray GetByIndex(int) *Vote IsCommit() bool } diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 80ee6135c..5a757a00b 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -2,12 +2,11 @@ package types import ( "bytes" + "testing" "github.com/tendermint/go-crypto" - . "github.com/tendermint/tmlibs/common" - . "github.com/tendermint/tmlibs/test" - - "testing" + cmn "github.com/tendermint/tmlibs/common" + tst "github.com/tendermint/tmlibs/test" ) // NOTE: privValidators are in order @@ -137,7 +136,7 @@ func Test2_3Majority(t *testing.T) { // 7th validator voted for some blockhash { vote := withValidator(voteProto, privValidators[6].GetAddress(), 6) - signAddVote(privValidators[6], withBlockHash(vote, RandBytes(32)), voteSet) + signAddVote(privValidators[6], withBlockHash(vote, cmn.RandBytes(32)), voteSet) blockID, ok = voteSet.TwoThirdsMajority() if ok || !blockID.IsZero() { t.Errorf("There should be no 2/3 majority") @@ -217,7 +216,7 @@ func Test2_3MajorityRedux(t *testing.T) { // 70th validator voted for different BlockHash { vote := withValidator(voteProto, privValidators[69].GetAddress(), 69) - signAddVote(privValidators[69], withBlockHash(vote, RandBytes(32)), voteSet) + signAddVote(privValidators[69], withBlockHash(vote, cmn.RandBytes(32)), voteSet) blockID, ok = voteSet.TwoThirdsMajority() if ok || !blockID.IsZero() { t.Errorf("There should be no 2/3 majority: last vote added had different BlockHash") @@ -260,7 +259,7 @@ func TestBadVotes(t *testing.T) { // val0 votes again for some block. { vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) - added, err := signAddVote(privValidators[0], withBlockHash(vote, RandBytes(32)), voteSet) + added, err := signAddVote(privValidators[0], withBlockHash(vote, cmn.RandBytes(32)), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, conflicting vote.") } @@ -297,8 +296,8 @@ func TestBadVotes(t *testing.T) { func TestConflicts(t *testing.T) { height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 4, 1) - blockHash1 := RandBytes(32) - blockHash2 := RandBytes(32) + blockHash1 := cmn.RandBytes(32) + blockHash2 := cmn.RandBytes(32) voteProto := &Vote{ ValidatorAddress: nil, @@ -444,13 +443,13 @@ func TestMakeCommit(t *testing.T) { } // MakeCommit should fail. - AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeCommit() }) + tst.AssertPanics(t, "Doesn't have +2/3 majority", func() { voteSet.MakeCommit() }) // 7th voted for some other block. { vote := withValidator(voteProto, privValidators[6].GetAddress(), 6) - vote = withBlockHash(vote, RandBytes(32)) - vote = withBlockPartsHeader(vote, PartSetHeader{123, RandBytes(32)}) + vote = withBlockHash(vote, cmn.RandBytes(32)) + vote = withBlockPartsHeader(vote, PartSetHeader{123, cmn.RandBytes(32)}) signAddVote(privValidators[6], vote, voteSet) } diff --git a/version/version.go b/version/version.go index 504aee68b..b874accc8 100644 --- a/version/version.go +++ b/version/version.go @@ -2,11 +2,11 @@ package version const Maj = "0" const Min = "11" -const Fix = "0" +const Fix = "1" var ( // The full version string - Version = "0.11.0" + Version = "0.11.1" // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse HEAD)" GitCommit string