From 209f5ec6c22fe271b7266633221c4a0925cb8704 Mon Sep 17 00:00:00 2001 From: pinkiebell <40266861+pinkiebell@users.noreply.github.com> Date: Sat, 13 Apr 2019 13:58:33 +0200 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 4695414393a3e0bc3836fb1cece7f1f3768d3311 Merge: 6cc3f4d8 def5c8cf Author: Ethan Buchman Date: Fri Apr 12 10:56:03 2019 -0400 Merge pull request #3548 from tendermint/release/v0.31.4 Release/v0.31.4 commit def5c8cf124ff58cc9c4a61ea017808a0c81c722 Author: Ismail Khoffi Date: Fri Apr 12 16:48:34 2019 +0200 address review comments: (#3550) - mention ADR in release summary - remove [p2p] api changes - amend v0.31.3 log to contain note about breaking change commit b6da8880c22202a7061ae2093ef01819438a136e Author: Ismail Khoffi Date: Fri Apr 12 14:24:51 2019 +0200 prepare v0.31.4 release: - prep changelog - add missing changelog entries - fix minor glitch in existing changelog (v0.31.2) - bump versions commit a453628c4e43c67c747d5078704479aa73716182 Author: Martin Dyring-Andersen Date: Fri Apr 12 13:25:14 2019 +0200 Fix a couple of typos (#3547) Fix some typos in p2p/transport.go commit 4e4224213f3eaa89abeac6686efe4ac970ad63d0 Author: Sean Braithwaite Date: Fri Apr 12 12:32:00 2019 +0200 adr: Peer Behaviour (#3539) * [adr] ADR 037: Peer Behaviour inital draft * Update docs/architecture/adr-037-peer-behaviour.md Co-Authored-By: brapse * Update docs/architecture/adr-037-peer-behaviour.md Co-Authored-By: brapse * [docs] adr-037 Better footnote styling * [ADR] ADR-037 adjust Footnotes for github markdown * [ADR] ADR-037 fix numbered list commit b5b3b85697a9c29adaab0e1ab5d1380459e2c5eb Author: Alexander Simmerl Date: Fri Apr 12 12:31:02 2019 +0200 Bring back NodeInfo NetAddress form the dead (#3545) A prior change to address accidental DNS lookups introduced the SocketAddr on peer, which was then used to add it to the addressbook. Which in turn swallowed the self reported port of the peer, which is important on a reconnect. This change revives the NetAddress on NodeInfo which the Peer carries, but now returns an error to avoid nil dereferencing another issue observed in the past. Additionally we could potentially address #3532, yet the original problem statemenf of that issue stands. As a drive-by optimisation `MarkAsGood` now takes only a `p2p.ID` which makes it interface a bit stricter and leaner. commit 18d2c45c334d9f22621ea0af0686e2e54937fb40 Author: Anton Kaliaev Date: Fri Apr 12 10:46:07 2019 +0200 rpc: Fix response time grow over time (#3537) * rpc: store validator info periodly * increase ValidatorSetStoreInterval also - unexpose it - add a comment - refactor code - add a benchmark, which shows that 100000 results in ~ 100ms to get 100 validators * make the change non-breaking * expand comment * rename valSetStoreInterval to valSetCheckpointInterval * change the panic msg * add a test and changelog entry * update changelog entry * update changelog entry * add a link to PR * fix test * Update CHANGELOG_PENDING.md Co-Authored-By: melekes * update comment * use MaxInt64 func commit c3df21fe827aad0447bb59a9546c32db8406a5fd Author: Anton Kaliaev Date: Thu Apr 11 18:59:14 2019 +0300 add missing changelog entry (#3544) * add missing changelog entry commit bcec8be035a89b5d08ee00b8cc7115017a58c7e0 Author: Anton Kaliaev Date: Thu Apr 11 15:32:16 2019 +0200 p2p: do not log err if peer is private (#3474) * add actionable advice for ErrAddrBookNonRoutable err Should replace https://github.com/tendermint/tendermint/pull/3463 * reorder checks in addrbook#addAddress so ErrAddrBookPrivate is returned first and do not log error in DialPeersAsync if the address is private because it's not an error commit 9a415b057238d118c1edf23d673ebf9ce4f8d2ae Author: Anton Kaliaev Date: Tue Apr 9 18:21:35 2019 +0200 docs: abci#Commit: better explain the possible deadlock (#3536) commit 40da355234bda9d510dca370e94b646450673879 Author: Anton Kaliaev Date: Wed Apr 3 14:56:51 2019 +0200 docs: fix block.Header.Time description (#3529) It's not proposer local time anymore, but a weighted median Fixes #3514 commit f965a4db15796dfca4e504d93eaa83760ced4af2 Author: Anton Kaliaev Date: Wed Apr 3 11:22:52 2019 +0200 p2p: seed mode refactoring (#3011) ListOfKnownAddresses is removed panic if addrbook size is less than zero CrawlPeers does not attempt to connect to existing or peers we're currently dialing various perf. fixes improved tests (though not complete) move IsDialingOrExistingAddress check into DialPeerWithAddress (Fixes #2716) * addrbook: preallocate memory when saving addrbook to file * addrbook: remove oldestFirst struct and check for ID * oldestFirst replaced with sort.Slice * ID is now mandatory, so no need to check * addrbook: remove ListOfKnownAddresses GetSelection is used instead in seed mode. * addrbook: panic if size is less than 0 * rewrite addrbook#saveToFile to not use a counter * test AttemptDisconnects func * move IsDialingOrExistingAddress check into DialPeerWithAddress * save and cleanup crawl peer data * get rid of DefaultSeedDisconnectWaitPeriod * make linter happy * fix TestPEXReactorSeedMode * fix comment * add a changelog entry * Apply suggestions from code review Co-Authored-By: melekes * rename ErrDialingOrExistingAddress to ErrCurrentlyDialingOrExistingAddress * lowercase errors * do not persist seed data pros: - no extra files - less IO cons: - if the node crashes, seed might crawl a peer too soon * fixes after Ethan's review * add a changelog entry * we should only consult Switch about peers checking addrbook size does not make sense since only PEX reactor uses it for dialing peers! https://github.com/tendermint/tendermint/pull/3011#discussion_r270948875 commit 086d6cbe8c4432a1a03d7924ae8352c42dc9cc4f Merge: e4a03f24 6cc3f4d8 Author: Ethan Buchman Date: Tue Apr 2 16:49:44 2019 -0400 Merge pull request #3527 from tendermint/v0.31 Merge V0.31.3 back to develop commit 6cc3f4d87cae6589aef0ac2b4707f1ebc8a669b2 Merge: 79e9f205 3cfd9757 Author: Ethan Buchman Date: Tue Apr 2 16:45:04 2019 -0400 Merge pull request #3525 from tendermint/release/v0.31.3 Release/v0.31.3 commit 3cfd9757a78fc2398c6853aace2541338be8eaef Author: Ethan Buchman Date: Tue Apr 2 09:14:33 2019 -0400 changelog and version v0.31.3 commit 882622ec10ba3fdc9f360da65bfb2bbbd13193ed Author: Ethan Buchman Date: Mon Apr 1 19:59:57 2019 -0400 Fixes tendermint/tendermint#3522 * OriginalAddr -> SocketAddr OriginalAddr records the originally dialed address for outbound peers, rather than the peer's self reported address. For inbound peers, it was nil. Here, we rename it to SocketAddr and for inbound peers, set it to the RemoteAddr of the connection. * use SocketAddr Numerous places in the code call peer.NodeInfo().NetAddress(). However, this call to NetAddress() may perform a DNS lookup if the reported NodeInfo.ListenAddr includes a name. Failure of this lookup returns a nil address, which can lead to panics in the code. Instead, call peer.SocketAddr() to return the static address of the connection. * remove nodeInfo.NetAddress() Expose `transport.NetAddress()`, a static result determined when the transport is created. Removing NetAddress() from the nodeInfo prevents accidental DNS lookups. * fixes from review * linter * fixes from review commit 1ecf8148385692da971a46e14091270b23c6dff3 Author: Ethan Buchman Date: Mon Apr 1 19:45:57 2019 -0400 Fixes tendermint/tendermint#3439 * make sure we create valid private keys: - genPrivKey samples and rejects invalid fieldelems (like libsecp256k1) - GenPrivKeySecp256k1 uses `(sha(secret) mod (n − 1)) + 1` - fix typo, rename test file: s/secpk256k1/secp256k1/ * Update crypto/secp256k1/secp256k1.go commit e4a03f249dcb7d2e833fc20add382b2a5e7f167e Author: Greg Szabo <16846635+greg-szabo@users.noreply.github.com> Date: Mon Apr 1 14:18:18 2019 -0400 Release message changelog link fix (#3519) commit 56d8aa42b3ca628c6544cab1f902f8289738c97a Merge: 0ae41cc6 79e9f205 Author: Ethan Buchman Date: Mon Apr 1 14:17:58 2019 -0400 Merge pull request #3520 from tendermint/v0.31 Merge v0.31.2 release back to develop commit 79e9f205782f1adb94b4150561e414280115cf4e Merge: a0234aff ab24925c Author: Ismail Khoffi Date: Mon Apr 1 17:58:28 2019 +0200 Merge pull request #3518 from tendermint/prepare-release-v0.31.2 Release v0.31.2 commit ab24925c9432e9eff06b34445b1760217436f87d Author: Ismail Khoffi Date: Mon Apr 1 12:32:37 2019 +0200 prepare changelog and bump versions to v0.31.2 commit 0ae41cc663014454184c2d31328b53dd45dd34d1 Author: Greg Szabo <16846635+greg-szabo@users.noreply.github.com> Date: Mon Apr 1 11:47:00 2019 -0400 Fix for wrong version tag (#3517) * Fix for wrong version tag (tag on the release branch instead of master) commit 422d04c8bae80e103474d37bd5a5afd0061e8d72 Author: Ethan Buchman Date: Sun Mar 31 07:14:18 2019 -0400 Bucky/mempool txsmap (#3512) * mempool: resCb -> globalCb * reqResCb takes an externalCb * failing test for #3509 * txsMap is sync.Map * update changelog commit 2233dd45bd774905b1ba545eeadea5ebe49d1e31 Author: zjubfd Date: Sat Mar 30 01:47:53 2019 +0800 libs: remove useless code in group (#3504) * lib: remove useless code in group * update change log * Update CHANGELOG_PENDING.md Co-Authored-By: guagualvcha commit 9199f3f6132ff24500b538068450eae7d11bef64 Author: Greg Szabo <16846635+greg-szabo@users.noreply.github.com> Date: Fri Mar 29 07:57:16 2019 -0400 Release management using CircleCI (#3498) * Release management using CircleCI * Changelog updated commit 6c1a4b513747fed5be06edffd16db4a1c8749041 Author: Ismail Khoffi Date: Thu Mar 28 17:39:09 2019 +0100 blockchain: comment out logger in test code that causes a race condition (#3500) commit c7bb9984979b72e81a569b4540a2da1e73c1c470 Merge: d586945d 7b72436c Author: Ethan Buchman Date: Thu Mar 28 08:07:59 2019 -0400 Merge pull request #3502 from tendermint/bucky/merge-master Bucky/merge master commit 7b72436c751587141d8de605ccbcec3514dbea77 Merge: d586945d a0234aff Author: Ethan Buchman Date: Thu Mar 28 08:12:05 2019 -0400 Merge branch 'master' into bucky/merge-master commit a0234affb6959a0aec285eebf3a3963251d2d186 Merge: 0d985ede 9390a810 Author: Ethan Buchman Date: Thu Mar 28 07:57:42 2019 -0400 Merge pull request #3489 from tendermint/release/v0.31.1 Release/v0.31.1 commit 9390a810ebda0b1aebd90f6e8b8688562bdf7958 Author: Ethan Buchman Date: Wed Mar 27 21:03:28 2019 -0400 minor changelog updates (#3499) commit a49d80b89c9df3cb2497adb8175915c654479708 Author: Ismail Khoffi Date: Wed Mar 27 19:12:01 2019 +0100 catch up with develop and rebase on current release to include #3482 commit ccfe75ec4a701ad7dcaf1f26df772b0881ab1184 Author: Sean Braithwaite Date: Wed Mar 27 18:51:57 2019 +0100 docs: Fix broken links (#3482) (#3488) * docs: fix broken links (#3482) A bunch of links were broken in the documentation s they included the `docs` prefix. * Update CHANGELOG_PENDING * docs: switch to relative links for github compatitibility (#3482) commit d586945d6981262759e0e681e7bc92a74637185d Author: Sean Braithwaite Date: Wed Mar 27 18:51:57 2019 +0100 docs: Fix broken links (#3482) (#3488) * docs: fix broken links (#3482) A bunch of links were broken in the documentation s they included the `docs` prefix. * Update CHANGELOG_PENDING * docs: switch to relative links for github compatitibility (#3482) commit ae88965ff6ddf174607a02eee83cfc5ed11c6cbb Author: Ismail Khoffi Date: Wed Mar 27 17:46:29 2019 +0100 changelog: add summary & fix link & add external contributor (#3490) commit 233813483642c2ac370110dd42c3291556c26039 Author: Ismail Khoffi Date: Wed Mar 27 16:52:19 2019 +0100 bump versions commit 1b33a50e6d07e48f2377db64ce944a435f9f2ec4 Merge: 3c7bb6b5 5fa540bd Author: Ismail Khoffi Date: Wed Mar 27 16:50:59 2019 +0100 Merge remote-tracking branch 'remotes/origin/develop' into release/v0.31.1 commit 3c7bb6b571ac47e050a47013e22dbdfd68f02745 Author: Ismail Khoffi Date: Wed Mar 27 16:48:00 2019 +0100 Add some numbers for #2778 commit 5fa540bdc9eac01cc3df6de01119550d1122114b Author: Anton Kaliaev Date: Wed Mar 27 16:45:34 2019 +0100 mempool: add a safety check, write tests for mempoolIDs (#3487) * mempool: add a safety check, write tests for mempoolIDs and document 65536 limit in the mempool reactor spec follow-up to https://github.com/tendermint/tendermint/pull/2778 * rename the test * fixes after Ismail's review commit 52727863e187e85901e7f2e7285920a701d2a8e6 Author: Ismail Khoffi Date: Wed Mar 27 16:07:03 2019 +0100 add external contributors commit e3f840e6a6f81406c44e760e578e48fcd3fedf12 Author: Ismail Khoffi Date: Wed Mar 27 16:04:43 2019 +0100 reset CHANGELOG_PENDING.md commit ed63e1f378eaf465b714084fa1edd6ac4761782f Author: Ismail Khoffi Date: Wed Mar 27 16:03:25 2019 +0100 Add more entries to the Changelog, fix formatting, linkify commit 55b7118c981e93b920cead4616b91ec58130e316 Author: Ismail Khoffi Date: Wed Mar 27 15:35:32 2019 +0100 Prep changelog: copy from pending & update version commit 5a25b75b1d586c6d9c3fdfc940057f7b85089a90 Author: zjubfd Date: Wed Mar 27 00:13:14 2019 +0800 p2p: refactor GetSelectionWithBias for addressbook (#3475) Why submit this pr: we have suffered from infinite loop in addrbook bug which takes us a long time to find out why process become a zombie peer. It have been fixed in #3232. But the ADDRS_LOOP is still there, risk of infinite loop is still exist. The algorithm that to random pick a bucket is not stable, which means the peer may unluckily always choose the wrong bucket for a long time, the time and cpu cost is meaningless. A simple improvement: shuffle bucketsNew and bucketsOld, and pick necessary number of address from them. A stable algorithm. commit a4d9539544ba4377f16e797fea01090bc974e1b5 Author: Anton Kaliaev Date: Tue Mar 26 09:44:49 2019 +0100 rpc/client: include NetworkClient interface into Client interface (#3473) I think it's nice when the Client interface has all the methods. If someone does not need a particular method/set of methods, she can use individual interfaces (e.g. NetworkClient, MempoolClient) or write her own interface. technically breaking Fixes #3458 commit 1bb8e02a962a1ffd7ce4e7355f648b644105f649 Author: HaoyangLiu Date: Tue Mar 26 16:29:06 2019 +0800 mempool: fix broadcastTxRoutine leak (#3478) Refs #3306, irisnet@fdbb676 I ran an irishub validator. After the validator node ran several days, I dump the whole goroutine stack. I found that there were hundreds of broadcastTxRoutine. However, the connected peer quantity was less than 30. So I belive that there must be broadcastTxRoutine leakage issue. According to my analysis, I think the root cause of this issue locate in below code: select { case <-next.NextWaitChan(): // see the start of the for loop for nil check next = next.Next() case <-peer.Quit(): return case <-memR.Quit(): return } As we know, if multiple paths are avaliable in the same time, then a random path will be selected. Suppose that next.NextWaitChan() and peer.Quit() are both avaliable, and next.NextWaitChan() is chosen. // send memTx msg := &TxMessage{Tx: memTx.tx} success := peer.Send(MempoolChannel, cdc.MustMarshalBinaryBare(msg)) if !success { time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) continue } Then next will be non-empty and the peer send operation won't be success. As a result, this go routine will be track into infinite loop and won't be released. My proposal is to check peer.Quit() and memR.Quit() in every loop no matter whether next is nil. commit 6de7effb05581f9bea2e8af06e4e74a85c34bc5f Author: Dev Ojha Date: Tue Mar 26 01:27:29 2019 -0700 mempool no gossip back (#2778) Closes #1798 This is done by making every mempool tx maintain a list of peers who its received the tx from. Instead of using the 20byte peer ID, it instead uses a local map from peerID to uint16 counter, so every peer adds 2 bytes. (Word aligned to probably make it 8 bytes) This also required resetting the callback function on every CheckTx. This likely has performance ramifications for instruction caching. The actual setting operation isn't costly with the removal of defers in this PR. * Make the mempool not gossip txs back to peers its received it from * Fix adversarial memleak * Don't break interface * Update changelog * Forgot to add a mtx * forgot a mutex * Update mempool/reactor.go Co-Authored-By: ValarDragon * Update mempool/mempool.go Co-Authored-By: ValarDragon * Use unknown peer ID Co-Authored-By: ValarDragon * fix compilation * use next wait chan logic when skipping * Minor fixes * Add TxInfo * Add reverse map * Make activeID's auto-reserve 0 * 0 -> UnknownPeerID Co-Authored-By: ValarDragon * Switch to making the normal case set a callback on the reqres object The recheck case is still done via the global callback, and stats are also set via global callback * fix merge conflict * Addres comments * Add cache tests * add cache tests * minor fixes * update metrics in reqResCb and reformat code * goimport -w mempool/reactor.go * mempool: update memTx senders I had to introduce txsMap for quick mempoolTx lookups. * change senders type from []uint16 to sync.Map Fixes DATA RACE: ``` Read at 0x00c0013fcd3a by goroutine 183: github.com/tendermint/tendermint/mempool.(*MempoolReactor).broadcastTxRoutine() /go/src/github.com/tendermint/tendermint/mempool/reactor.go:195 +0x3c7 Previous write at 0x00c0013fcd3a by D[2019-02-27|10:10:49.058] Read PacketMsg switch=3 peer=35bc1e3558c182927b31987eeff3feb3d58a0fc5@127.0.0.1 :46552 conn=MConn{pipe} packet="PacketMsg{30:2B06579D0A143EB78F3D3299DE8213A51D4E11FB05ACE4D6A14F T:1}" goroutine 190: github.com/tendermint/tendermint/mempool.(*Mempool).CheckTxWithInfo() /go/src/github.com/tendermint/tendermint/mempool/mempool.go:387 +0xdc1 github.com/tendermint/tendermint/mempool.(*MempoolReactor).Receive() /go/src/github.com/tendermint/tendermint/mempool/reactor.go:134 +0xb04 github.com/tendermint/tendermint/p2p.createMConnection.func1() /go/src/github.com/tendermint/tendermint/p2p/peer.go:374 +0x25b github.com/tendermint/tendermint/p2p/conn.(*MConnection).recvRoutine() /go/src/github.com/tendermint/tendermint/p2p/conn/connection.go:599 +0xcce Goroutine 183 (running) created at: D[2019-02-27|10:10:49.058] Send switch=2 peer=1efafad5443abeea4b7a8155218e4369525d987e@127.0.0.1:46193 channel=48 conn=MConn{pipe} m sgBytes=2B06579D0A146194480ADAE00C2836ED7125FEE65C1D9DD51049 github.com/tendermint/tendermint/mempool.(*MempoolReactor).AddPeer() /go/src/github.com/tendermint/tendermint/mempool/reactor.go:105 +0x1b1 github.com/tendermint/tendermint/p2p.(*Switch).startInitPeer() /go/src/github.com/tendermint/tendermint/p2p/switch.go:683 +0x13b github.com/tendermint/tendermint/p2p.(*Switch).addPeer() /go/src/github.com/tendermint/tendermint/p2p/switch.go:650 +0x585 github.com/tendermint/tendermint/p2p.(*Switch).addPeerWithConnection() /go/src/github.com/tendermint/tendermint/p2p/test_util.go:145 +0x939 github.com/tendermint/tendermint/p2p.Connect2Switches.func2() /go/src/github.com/tendermint/tendermint/p2p/test_util.go:109 +0x50 I[2019-02-27|10:10:49.058] Added good transaction validator=0 tx=43B4D1F0F03460BD262835C4AA560DB860CFBBE85BD02386D83DAC38C67B3AD7 res="&{CheckTx:gas_w anted:1 }" height=0 total=375 Goroutine 190 (running) created at: github.com/tendermint/tendermint/p2p/conn.(*MConnection).OnStart() /go/src/github.com/tendermint/tendermint/p2p/conn/connection.go:210 +0x313 github.com/tendermint/tendermint/libs/common.(*BaseService).Start() /go/src/github.com/tendermint/tendermint/libs/common/service.go:139 +0x4df github.com/tendermint/tendermint/p2p.(*peer).OnStart() /go/src/github.com/tendermint/tendermint/p2p/peer.go:179 +0x56 github.com/tendermint/tendermint/libs/common.(*BaseService).Start() /go/src/github.com/tendermint/tendermint/libs/common/service.go:139 +0x4df github.com/tendermint/tendermint/p2p.(*peer).Start() :1 +0x43 github.com/tendermint/tendermint/p2p.(*Switch).startInitPeer() ``` * explain the choice of a map DS for senders * extract ids pool/mapper to a separate struct * fix literal copies lock value from senders: sync.Map contains sync.Mutex * use sync.Map#LoadOrStore instead of Load * fixes after Ismail's review * rename resCbNormal to resCbFirstTime commit 25a3c8b1724c9611d6edc175b1b0d079f5ee28c1 Author: zjubfd Date: Sun Mar 24 01:08:15 2019 +0800 rpc: support tls rpc (#3469) Refs #3419 commit 85be2a554e7e7752bed0b9409ab153bf04e05e7b Author: Thane Thomson Date: Fri Mar 22 09:16:38 2019 -0400 tools/tm-signer-harness: update height and round for test harness (#3466) In order to re-enable the test harness for the KMS (see tendermint/kms#227), we need some marginally more realistic proposals and votes. This is because the KMS does some additional sanity checks now to ensure the height and round are increasing over time. commit 1d4afb179b9660bf13705c67b01e20838a4506eb Author: Anton Kaliaev Date: Thu Mar 21 11:05:39 2019 +0100 replace PB2TM.ConsensusParams with a call to params#Update (#3448) Fixes #3444 commit 660bd4a53e0dd7642a473689c8686c3f83e3a0ca Author: tracebundy <745403419@qq.com> Date: Wed Mar 20 20:30:49 2019 +0800 fix comment (#3454) commit 81b9bdf40010c6a4e336133ec53fe6e4e6089911 Author: Ethan Buchman Date: Wed Mar 20 08:29:40 2019 -0400 comments on validator ordering (#3452) * comments on validator ordering * NextValidatorsHash commit 926127c774a2c9110c4284938411818918ffecac Author: Anton Kaliaev Date: Wed Mar 20 03:59:33 2019 +0300 blockchain: update the maxHeight when a peer is removed (#3350) * blockchain: update the maxHeight when a peer is removed Refs #2699 * add a changelog entry * make linter pass commit 03085c2da23b179c4a51f59a03cb40aa4e85a613 Author: zjubfd Date: Wed Mar 20 08:18:18 2019 +0800 rpc: client disable compression (#3430) commit 7af4b5086af9268f7cc8b41f5a174ade675d8ab4 Author: Anton Kaliaev Date: Wed Mar 20 03:10:54 2019 +0300 Remove RepeatTimer and refactor Switch#Broadcast (#3429) * p2p: refactor Switch#Broadcast func - call wg.Add only once - do not call peers.List twice! * bad for perfomance * peers list can change in between calls! Refs #3306 * p2p: use time.Ticker instead of RepeatTimer no need in RepeatTimer since we don't Reset them Refs #3306 * libs/common: remove RepeatTimer (also TimerMaker and Ticker interface) "ancient code that’s caused no end of trouble" Ethan I believe there's much simplier way to write a ticker than can be reset https://medium.com/@arpith/resetting-a-ticker-in-go-63858a2c17ec commit 60b2ae5f5a3e16625af1342e012462448d565394 Author: needkane <604476380@qq.com> Date: Wed Mar 20 08:00:53 2019 +0800 crypto: delete unused code (#3426) commit a6349f50633a45755041b8230a0f7eb990383cfd Author: Anca Zamfir Date: Wed Mar 20 01:56:13 2019 +0200 Formalize proposer election algorithm properties (#3140) * Update proposer-selection.md * Fixed typos * fixed typos * Attempt to address some comments * Update proposer-selection.md * Update proposer-selection.md * Update proposer-selection.md Added the normalization step. * Addressed review comments * New example for normalization section Added a new example to better show the need for normalization Added requirement for changing validator set Addressed review comments * Fixed problem with R2 * fixed the math for new validator * test * more small updates * Moved the centering above the round-robin election - the centering is now done before the actual round-robin block - updated examples - cleanup * change to reflect new implementation for new validator commit 22bcfca87a987dc43ad111a226b072ce09bbeb8d Merge: 4162ebe8 0d985ede Author: Ethan Buchman Date: Tue Mar 19 19:54:09 2019 -0400 Merge pull request #3450 from tendermint/master Merge master back to develop commit 0d985ede28bd6937fa9d3613618e42cab6fc871c Merge: 97681953 1e346978 Author: Ethan Buchman Date: Tue Mar 19 19:53:37 2019 -0400 Merge pull request #3417 from tendermint/release/v0.31.0 Release/v0.31.0 commit 1e3469789dce5a034a21b6e48288f1809a102595 Author: Ismail Khoffi Date: Wed Mar 20 00:45:51 2019 +0100 Ensure WriteTimeout > TimeoutBroadcastTxCommit (#3443) * Make sure config.TimeoutBroadcastTxCommit < rpcserver.WriteTimeout() * remove redundant comment * libs/rpc/http_server: move Read/WriteTimeout into Config * increase defaults for read/write timeouts Based on this article https://www.digitalocean.com/community/tutorials/how-to-optimize-nginx-configuration * WriteTimeout should be larger than TimeoutBroadcastTxCommit * set a deadline for subscribing to txs * extract duration into const * add two changelog entries * Update CHANGELOG_PENDING.md Co-Authored-By: melekes * Update CHANGELOG_PENDING.md Co-Authored-By: melekes * 12 -> 10 * changelog * changelog commit 5f68fbae37e11b01f4d2e5c33b182401a7283e4a Merge: 551b6322 e276f35f Author: Ethan Buchman Date: Tue Mar 19 19:25:26 2019 -0400 Merge pull request #3449 from tendermint/ismail/merge_develop_into_release/0.31.0 Merge develop into release/0.31.0 commit e276f35f86b3980e088a95aa70c96dbddcdf658b Author: Ismail Khoffi Date: Tue Mar 19 14:36:42 2019 +0100 remove 3421 from changelog commit 8e62a3d62a5b04a5a7126d00d75925c990aec9c6 Author: Ismail Khoffi Date: Tue Mar 19 12:19:02 2019 +0100 Add #3421 to changelog and reorder alphabetically commit 48aaccab8f3cc17032220a01a6d58acde695bbde Merge: 551b6322 4162ebe8 Author: Ismail Khoffi Date: Tue Mar 19 12:09:26 2019 +0100 Merge in develop and update CHANGELOG.md commit 4162ebe8b586deccc0e7476d8abafb75138bfe58 Author: Anton Kaliaev Date: Tue Mar 19 11:38:32 2019 +0400 types: refactor PB2TM.ConsensusParams to take BlockTimeIota as an arg (#3442) See https://github.com/tendermint/tendermint/pull/3403/files#r266208947 In #3403 we unexposed BlockTimeIota from the ABCI, but it's still part of the ConsensusParams struct, so we have to remember to add it back after calling PB2TM.ConsensusParams. Instead, PB2TM.ConsensusParams should take it as an argument Fixes #3432 commit 551b6322f5a74f578a8487001e805e4e1da6394d Author: Ethan Buchman Date: Sat Mar 16 19:24:12 2019 -0400 Update v0.31.0 release notes (#3434) * changelog: fix formatting * update release notes * update changelog * linkify * update UPGRADING commit 52c4e15eb25e8a3d6e77f90239710de3655d4ec6 Author: Ismail Khoffi Date: Thu Mar 14 16:07:06 2019 +0100 changelog: more review fixes/release/v0.31.0 (#3427) * Update release summary * Add pubsub config changes * Add link to issue for pubsub changes commit 5483ac6b0a2a75bc140487e8405392533a3f1687 Author: Ismail Khoffi Date: Thu Mar 14 12:17:49 2019 +0100 minor changes / fixes to release 0.31.0 (#3422) * bump ABCIVersion due to renaming BlockSizeParams -> BlockParams (https://github.com/tendermint/tendermint/pull/3417#discussion_r264974791) * Move changelog on consensus params entry to breaking * Add @melekes' suggestion for breaking change in pubsub into upgrading.md * Add changelog entry for #3351 * Add changelog entry for #3358 & #3359 * Add changelog entry for #3397 * remove changelog entry for #3397 (was already released in 0.30.2) * move 3351 to improvements * Update changelog comment commit 745713330736c5c751450245d88b8037cbee3aa6 Author: Anton Kaliaev Date: Thu Mar 14 15:00:58 2019 +0400 grpcdb: close Iterator/ReverseIterator after use (#3424) Fixes #3402 commit a59930a327065f2d091840586da6c35b6fd07472 Author: Anton Kaliaev Date: Wed Mar 13 16:09:05 2019 +0400 localnet: fix $LOG variable (#3423) Fixes #3421 Before: it was creating a file named ${LOG:-tendermint.log} in .build/nodeX After: it creates a file named tendermint.log commit 85c023db88a8517587f0e8499b1e89f0eea1112d Author: Ismail Khoffi Date: Tue Mar 12 20:07:26 2019 +0100 Prep release v0.31.0: - update changelog, reset pending - bump versions - add external contributors (partly manually) commit 4cbd36f341c8fee55a4ea386aee42a2c7fe9c67d Merge: e42f833f 97681953 Author: Anton Kaliaev Date: Tue Mar 12 21:22:03 2019 +0400 Merge pull request #3415 from tendermint/master Merge master back to develop (do not squash) commit e42f833fd4ce6cb886dbf594dcb2f3af61918325 Author: Anton Kaliaev Date: Tue Mar 12 16:20:59 2019 +0400 Merge master back to develop (#3412) * libs/db: close batch (#3397) ClevelDB requires closing when WriteBatch is no longer needed, https://godoc.org/github.com/jmhodges/levigo#WriteBatch.Close Fixes the memory leak in https://github.com/cosmos/cosmos-sdk/issues/3842 * update changelog and bump version to 0.30.2 commit ad3e990c6a424d939b181a2a7c911eb4592c0c79 Author: Anton Kaliaev Date: Mon Mar 11 23:59:00 2019 +0400 fix GO_VERSION in installation scripts (#3411) there is no such file https://storage.googleapis.com/golang/go1.12.0.linux-amd64.tar.gz Fixes #3405 commit 676212fa8fdd1ddf29e9c352129c97da3c113769 Author: srmo Date: Mon Mar 11 20:06:03 2019 +0100 cmd: make sure to have 'testnet' create the data directory for nonvals (#3409) Fixes #3408 commit 303557203402f0a1b6db4a55cb2c6562765058b1 Author: Anton Kaliaev Date: Mon Mar 11 22:52:09 2019 +0400 cs: comment out log.Error to avoid TestReactorValidatorSetChanges timing out (#3401) commit d741c7b4785c36cef4b0912900b6193db21d00e6 Author: Anton Kaliaev Date: Mon Mar 11 22:45:58 2019 +0400 limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes commit 15f621141dce76d992f6c8dcfbd4c522878b6108 Author: Anton Kaliaev Date: Mon Mar 11 22:21:17 2019 +0400 remove TimeIotaMs from ABCI consensus params (#3403) Also - init substructures to avoid panic in pb2tm.ConsensusParams Before: if csp.Block is nil and we later try to access/write to it, we'll panic. After: if csp.Block is nil and we later try to access/write to it, there'll be no panic. commit dc359bd3a51c52803a6af820a36ee41796284e87 Author: Anca Zamfir Date: Mon Mar 11 16:17:25 2019 +0200 types: remove check for priority order of existing validators (#3407) When scaling and averaging is invoked, it is possible to have validators with close priorities ending up with same priority. With the current code, this makes it impossible to verify the priority orders before and after updates. Fixes #3383 commit 976819537d199aa43c6ad55ec4e8958323322bb0 Merge: e0f89364 f996b10f Author: Ethan Buchman Date: Mon Mar 11 08:17:14 2019 -0400 Merge pull request #3399 from tendermint/release/v0.30.2 Release/v0.30.2 commit 100ff08de93ff1907bf810f584ec5bdc7a2a5260 Author: Anton Kaliaev Date: Mon Mar 11 15:31:53 2019 +0400 p2p: do not panic when filter times out (#3384) Fixes #3369 commit f996b10f479d7c9a6d81cca5a02c47b29a52b3f3 Author: Anton Kaliaev Date: Sun Mar 10 13:06:34 2019 +0400 update changelog and bump version to 0.30.2 commit 36d7180ca216f0d7ff62851fa441de2b0371d699 Author: Yumin Xia Date: Sun Mar 10 00:46:32 2019 -0800 libs/db: close batch (#3397) ClevelDB requires closing when WriteBatch is no longer needed, https://godoc.org/github.com/jmhodges/levigo#WriteBatch.Close Fixes the memory leak in https://github.com/cosmos/cosmos-sdk/issues/3842 commit b021f1e505482fb34f8a8e57cd86b171e4a57344 Author: Yumin Xia Date: Sun Mar 10 00:46:32 2019 -0800 libs/db: close batch (#3397) ClevelDB requires closing when WriteBatch is no longer needed, https://godoc.org/github.com/jmhodges/levigo#WriteBatch.Close Fixes the memory leak in https://github.com/cosmos/cosmos-sdk/issues/3842 commit 90794260bcd23989e4bd8530d596e39d9509e7ca Author: mircea-c Date: Sat Mar 9 10:13:36 2019 -0500 circleci: removed complexity from docs deployment job (#3396) commit b6a510a3e7cefca56af4bdb009413e3950c6d59e Author: Anton Kaliaev Date: Fri Mar 8 09:46:09 2019 +0400 make ineffassign linter pass (#3386) Refs #3262 This fixes two small bugs: 1) lite/dbprovider: return `ok` instead of true in parse* functions. It's weird that we're ignoring `ok` value before. 2) consensus/state: previously because of the shadowing we almost never output "Error with msg". Now we declare both `added` and `err` in the beginning of the function, so there's no shadowing. commit e415c326f93e5fa473d83a0ea6fa495ad0ea87f3 Author: Ismail Khoffi Date: Fri Mar 8 06:40:59 2019 +0100 update golang.org/x/crypto (#3392) Update Gopkg.lock via dep ensure --update golang.org/x/crypto see #3391 (comment) (nothing to review here really). commit 28e9e9e7145b357cbd7078f4c83dcf66c13d4f7d Author: Ismail Khoffi Date: Thu Mar 7 17:03:57 2019 +0100 update levigo to 1.0.0 (#3389) Although the version we were pinning to is from Nov. 2016 there were no substantial changes: jmhodges/levigo@2b8c778 added go-modules support (no code changes) jmhodges/levigo@853d788 added a badge to the readme closes #3381 commit 3ebfa99f2c2403c17348e41190406892abf21d2a Author: Anton Kaliaev Date: Thu Mar 7 19:35:04 2019 +0400 do not pin repos without releases to exact revisions (#3382) We're pinning repos without releases because it's very easy to upgrade all the dependencies by executing dep ensure --upgrade. Instead, we should just never run this command directly, only dep ensure --upgrade . And we can defend that in PRs. Refs #3374 The problem with pinning to exact revisions: people who import Tendermint as a library (e.g. abci/types) are stuck with these revisions even though the code they import may not even use them. commit 91b488f9a541f5dd69f0bb2db4249356733a2ed3 Author: YOSHIDA Masanori Date: Thu Mar 7 22:02:13 2019 +0900 docs: fix the reverse of meaning in spec (#3387) https://tools.ietf.org/html/rfc6962#section-2.1 "The largest power of two less than the number of items" is actually correct! For n > 1, let k be the largest power of two smaller than n (i.e., k < n <= 2k). commit f25d727035f4615cbb4b43452191fd83218fc1e1 Author: Anton Kaliaev Date: Thu Mar 7 09:10:34 2019 +0400 make dupl linter pass (#3385) Refs #3262 commit 411bc5e49fcc3dda866ae799ba2ceda0084f80d4 Author: Anca Zamfir Date: Wed Mar 6 09:54:49 2019 +0100 types: followup after validator set changes (#3301) * fix failure in TestProposerFrequency * Add test to check priority order after updates * Changed applyRemovals() and removed Remove() Changed applyRemovals() similar to applyUpdates() Removed function Remove() Updated comments * review comments * simplify applyRemovals and add more comments * small correction in comment * Fix check in test * Fix priority check for centering, address review comments * fix assert for priority centering * review comments * review comments * cleanup and review comments added upper limit check for validator voting power moved check for empty validator set earlier moved panic on potential negative set length in verifyRemovals added more tests * review comments commit 858875fbb8b94debc4973fdf5923ced6564d3c39 Author: Silas Davis Date: Wed Mar 6 08:22:35 2019 +0000 Copy secp256k1 code from go-ethereum to avoid GPL vendoring issues in (#3371) downstream Signed-off-by: Silas Davis commit 1eaa42cd25f2355f4b30440bddae435d056d1f44 Author: Ismail Khoffi Date: Tue Mar 5 08:08:52 2019 +0100 golang 1.12.0 (#3376) - update docker image on circleci - remove GOCACHE=off from Makefile (see: https://tip.golang.org/doc/go1.12#gocache) - update badge in readme - update in scripts/install - update Vagrantfile - update in networks/remote/integration.sh - tools/build/Makefile commit 8c9df30e28a3b95efe537afe9930de2150999441 Author: Jack Zampolin Date: Mon Mar 4 22:56:46 2019 -0800 libs/db: Add cleveldb.Stats() (#3379) Fixes: #3378 * Add stats to cleveldb implementation * update changelog * remote TODO also - sort keys - preallocate memory * fix const initializer []string literal is not a constant * add test commit 52771e1287eb44eabae99e6d2781a57c577821bb Author: Anton Kaliaev Date: Mon Mar 4 13:24:44 2019 +0400 make BlockTimeIota a consensus parameter, not a locally configurable … (#3048) * make BlockTimeIota a consensus parameter, not a locally configurable option Refs #2920 * make TimeIota int64 ms Refs #2920 * update Gopkg.toml * fixes after Ethan's review * fix TestRemoteSignerProposalSigningFailed * update changelog commit f39138aa2e543438548a150bdad45304ccc3296b Author: Anton Kaliaev Date: Mon Mar 4 12:18:32 2019 +0400 remove RoundState from EventDataRoundState (#3354) Before we're using it to get a round state in tests. Now it can be done by calling csX.GetRoundState. We will need to rewrite TestStateSlashingPrevotes and TestStateSlashingPrecommits, which are commented right now, to not rely on EventDataRoundState#RoundState field. Refs #1527 commit 8a962ffc4639eed95501114e0436ccffc52f8ef1 Author: Anton Kaliaev Date: Mon Mar 4 12:17:38 2019 +0400 deps: update gogo/protobuf from 1.1.1 to 1.2.1 and golang/protobuf from 1.1.0 to 1.3.0 (#3357) * deps: update gogo/proto from 1.1.1 to 1.2.1 - verified changes manually git diff 636bf030~ ba06b47c --stat -- ':!*.pb.go' ':!test' * deps: update golang/protobuf from 1.1.0 to 1.3.0 - verified changes manually git diff b4deda0~ c823c79 -- ':!*.pb.go' ':!test' commit 3421e4dcd7c6937e9ae8f72af17b38840840a781 Author: zjubfd Date: Sun Mar 3 05:36:52 2019 +0800 make lightd availbe (#3364) 1."abci_query": rpcserver.NewRPCFunc(c.ABCIQuery, "path,data,prove") "validators": rpcserver.NewRPCFunc(c.Validators, "height"), the parameters and function do not match, cause index out of range error. 2. the prove of query is forced to be true, while default option is false. 3. fix the wrong key of merkle commit 976b1c2ef73e557a702ef17600e1eeaf4865f292 Author: zjubfd Date: Sun Mar 3 04:21:21 2019 +0800 fix pool timer leak bug, resolve#3353 (#3358) When remove peer, block pool simple remove bpPeer, but do not stop timer, that cause stopError for recorrected peers. Stop timer when remove from pool. commit d95894152beb3b25423cdb109af006340f0ac80b Author: zjubfd Date: Sun Mar 3 04:17:37 2019 +0800 fix dirty data in peerset,resolve #3304 (#3359) * fix dirty data in peerset startInitPeer before PeerSet add the peer, once mconnection start and Receive of one Reactor faild, will try to remove it from PeerSet while PeerSet still not contain the peer. Fix this by change the order. * fix test FilterDuplicate * fix start/stop race * fix err commit 37a548414bbb5dc8dce9fe452ba5f933fad29684 Author: Sven-Hendrik Haase Date: Sat Mar 2 07:06:57 2019 +0100 docs: fix typo (#3373) commit 853dd34d31bee6de593be94df87cbb508688249e Author: Juan Leni Date: Thu Feb 28 08:48:20 2019 +0100 privval: improve Remote Signer implementation (#3351) This issue is related to #3107 This is a first renaming/refactoring step before reworking and removing heartbeats. As discussed with @Liamsi , we preferred to go for a couple of independent and separate PRs to simplify review work. The changes: Help to clarify the relation between the validator and remote signer endpoints Differentiate between timeouts and deadlines Prepare to encapsulate networking related code behind RemoteSigner in the next PR My intention is to separate and encapsulate the "network related" code from the actual signer. SignerRemote ---(uses/contains)--> SignerValidatorEndpoint <--(connects to)--> SignerServiceEndpoint ---> SignerService (future.. not here yet but would like to decouple too) All reconnection/heartbeat/whatever code goes in the endpoints. Signer[Remote/Service] do not need to know about that. I agree Endpoint may not be the perfect name. I tried to find something "Go-ish" enough. It is a common name in go-kit, kubernetes, etc. Right now: SignerValidatorEndpoint: handles the listener contains SignerRemote Implements the PrivValidator interface connects and sets a connection object in a contained SignerRemote delegates PrivValidator some calls to SignerRemote which in turn uses the conn object that was set externally SignerRemote: Implements the PrivValidator interface read/writes from a connection object directly handles heartbeats SignerServiceEndpoint: Does most things in a single place delegates to a PrivValidator IIRC. * cleanup * Refactoring step 1 * Refactoring step 2 * move messages to another file * mark for future work / next steps * mark deprecated classes in docs * Fix linter problems * additional linter fixes commit d6e2fb453d20841828cfbe1f6a7e8ca593c1b564 Author: Anton Kaliaev Date: Thu Feb 28 11:31:59 2019 +0400 update docs (#3349) * docs: explain create_empty_blocks configurations Closes #3307 * Vagrantfile: install nodejs for docs * update docs instructions npm install does not make sense since there's no packages.json file * explain broadcast_tx_* tx format Closes #536 * docs: explain how transaction ordering works Closes #2904 * bring in consensus parameters explained * example for create_empty_blocks_interval * bring in explanation from https://github.com/tendermint/tendermint/issues/2487#issuecomment-424899799 * link to formatting instead of duplicating info commit ec9bff52346d9629f690147674326a41fcefd20f Author: Anton Kaliaev Date: Mon Feb 25 09:11:07 2019 +0400 rename WAL#Flush to WAL#FlushAndSync (#3345) * rename WAL#Flush to WAL#FlushAndSync - rename auto#Flush to auto#FlushAndSync - cleanup WAL interface to not leak implementation details! * remove Group() * add WALReader interface and return it in SearchForEndHeight() - add interface assertions Refs #3337 * replace WALReader with io.ReadCloser commit 6797d8585167a9dc1e51843420ae4ab7eaf848a0 Author: Ismail Khoffi Date: Mon Feb 25 06:06:21 2019 +0100 p2p: fix comment in secret connection (#3348) Just a minor followup on the review if #3347: Fixes a comment. [#3347 (comment)](https://github.com/tendermint/tendermint/pull/3347#discussion_r259582330) commit cdf3a74f4899b3c716703b932e682307752cd6b6 Author: Anton Kaliaev Date: Sat Feb 23 19:48:28 2019 +0400 Unclean shutdown on SIGINT / SIGTERM (#3308) * libs/common: TrapSignal accepts logger as a first parameter and does not block anymore * previously it was dumping "captured ..." msg to os.Stdout * TrapSignal should not be responsible for blocking thread of execution Refs #3238 * exit with zero (0) code upon receiving SIGTERM/SIGINT Refs #3238 * fix formatting in docs/app-dev/abci-cli.md Co-Authored-By: melekes * fix formatting in docs/app-dev/abci-cli.md Co-Authored-By: melekes commit 41f91318e9e253a1b59cc1cc128ad412941b8e2a Author: Anton Kaliaev Date: Sat Feb 23 19:32:31 2019 +0400 bound mempool memory usage (#3248) * bound mempool memory usage Closes #3079 * rename SizeBytes to TxsTotalBytes and other small fixes after Zarko's review * rename MaxBytes to MaxTxsTotalBytes * make ErrMempoolIsFull more informative * expose mempool's txs_total_bytes via RPC * test full response * fixes after Ethan's review * config: rename mempool.size to mempool.max_txs https://github.com/tendermint/tendermint/pull/3248#discussion_r254034004 * test more cases https://github.com/tendermint/tendermint/pull/3248#discussion_r254036532 * simplify test * Revert "config: rename mempool.size to mempool.max_txs" This reverts commit 39bfa3696177aa46195000b90655419a975d6ff7. * rename count back to n_txs to make a change non-breaking * rename max_txs_total_bytes to max_txs_bytes * format code * fix TestWALPeriodicSync The test was sometimes failing due to processFlushTicks being called too early. The solution is to call wal#Start later in the test. * Apply suggestions from code review commit e0adc5e807c8c2ea4ac03f8eeeb006b1a154d453 Author: Ismail Khoffi Date: Sat Feb 23 16:25:57 2019 +0100 secret connection check all zeroes (#3347) * reject the shared secret if is all zeros in case the blacklist was not sufficient * Add test that verifies lower order pub-keys are rejected at the DH step * Update changelog * fix typo in test-comment commit 2137ecc130874118cc23995befed370958c34448 Author: Anton Kaliaev Date: Sat Feb 23 08:11:27 2019 +0400 pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code commit 67fd42835448172d08616589f1473e7153cc0902 Author: Anton Kaliaev Date: Fri Feb 22 11:49:08 2019 +0400 fix TestWALPeriodicSync (#3342) The test was sometimes failing due to processFlushTicks being called too early. The solution is to call wal#Start later in the test. commit f22ada442a249597dff765a59197bc57a8eae349 Author: Anton Kaliaev Date: Fri Feb 22 11:48:40 2019 +0400 refactor decideProposal in common_test (#3343) commit ed1de135486608d8b4869760960e3eb35ade2c6d Author: Anton Kaliaev Date: Thu Feb 21 18:28:02 2019 +0400 cs: update wal comments (#3334) * cs: update wal comments Follow-up to https://github.com/tendermint/tendermint/pull/3300 * Update consensus/wal.go Co-Authored-By: melekes commit 4f83eec78251b9228f0edb76f261e3e387d21c12 Merge: db5d7602 e0f89364 Author: Ethan Buchman Date: Wed Feb 20 10:11:33 2019 -0500 Merge pull request #3339 from tendermint/master Merge master back to develop commit e0f8936455029a40287a69d5b0e7baa4d5864da1 Merge: 28d75ec8 f2351dc7 Author: Ethan Buchman Date: Wed Feb 20 10:10:49 2019 -0500 Merge pull request #3326 from tendermint/release/v0.30.1 Release/v0.30.1 commit f2351dc758e5c8cf6bc6c5c32931daa8ae6e49a9 Author: Ethan Buchman Date: Wed Feb 20 10:05:57 2019 -0500 update changelog commit db5d7602fea6a3418d4ae83511c1a35cf9e1c9ca Author: Daniil Lashin Date: Wed Feb 20 14:34:52 2019 +0300 docs: fix rpc Tx() method docs (#3331) commit dff3deb2a95474ee1a10fc6aa7fd0a0a9a6aee94 Author: Thane Thomson Date: Wed Feb 20 07:45:18 2019 +0200 cs: sync WAL more frequently (#3300) As per #3043, this adds a ticker to sync the WAL every 2s while the WAL is running. * Flush WAL every 2s This adds a ticker that flushes the WAL every 2s while the WAL is running. This is related to #3043. * Fix spelling * Increase timeout to 2mins for slower build environments * Make WAL sync interval configurable * Add TODO to replace testChan with more comprehensive testBus * Remove extraneous debug statement * Remove testChan in favour of using system time As per https://github.com/tendermint/tendermint/pull/3300#discussion_r255886586, this removes the `testChan` WAL member and replaces the approach with a system time-oriented one. In this new approach, we keep track of the system time at which each flush and periodic flush successfully occurred. The naming of the various functions is also updated here to be more consistent with "flushing" as opposed to "sync'ing". * Update naming convention and ensure lock for timestamp update * Add Flush method as part of WAL interface Adds a `Flush` method as part of the WAL interface to enforce the idea that we can manually trigger a WAL flush from outside of the WAL. This is employed in the consensus state management to flush the WAL prior to signing votes/proposals, as per https://github.com/tendermint/tendermint/issues/3043#issuecomment-453853630 * Update CHANGELOG_PENDING * Remove mutex approach and replace with DI The dependency injection approach to dealing with testing concerns could allow similar effects to some kind of "testing bus"-based approach. This commit introduces an example of this, where instead of relying on (potentially fragile) timing of things between the code and the test, we inject code into the function under test that can signal the test through a channel. This allows us to avoid the `time.Sleep()`-based approach previously employed. * Update comment on WAL flushing during vote signing Co-Authored-By: thanethomson * Simplify flush interval definition Co-Authored-By: thanethomson * Expand commentary on WAL disk flushing Co-Authored-By: thanethomson * Add broken test to illustrate WAL sync test problem Removes test-related state (dependency injection code) from the WAL data structure and adds test code to illustrate the problem with using `WALGenerateNBlocks` and `wal.SearchForEndHeight` to test periodic sync'ing. * Fix test error messages * Use WAL group buffer size to check for flush A function is added to `libs/autofile/group.go#Group` in order to return the size of the buffered data (i.e. data that has not yet been flushed to disk). The test now checks that, prior to a `time.Sleep`, the group buffer has data in it. After the `time.Sleep` (during which time the periodic flush should have been called), the buffer should be empty. * Remove config root dir removal from #3291 * Add godoc for NewWAL mentioning periodic sync commit 9d4f59b8366ad393410051e09672b76578545d9e Author: Anton Kaliaev Date: Mon Feb 18 15:27:07 2019 +0400 update changelog and bump version commit d2c7f8dbcf238ccc3a2469e350523c7ed28be154 Author: Ismail Khoffi Date: Mon Feb 18 11:08:22 2019 +0100 p2p: check secret conn id matches dialed id (#3321) ref: [#3010 (comment)](https://github.com/tendermint/tendermint/issues/3010#issuecomment-464287627) > I tried searching for code where we authenticate a peer against its NetAddress.ID and couldn't find it. I don't see a reason to switch to Noise, but a need to ensure that the node's ID is authenticated e.g. after dialing from the address book. * p2p: check secret conn id matches dialed id * Fix all p2p tests & make code compile * add simple test for dialing with wrong ID * update changelog * address review comments * yet another place where to use IDAddressString and fix testSetupMultiplexTransport commit 8283ca7ddb5b56090aaa15e005b40782fa618ae7 Author: Anton Kaliaev Date: Mon Feb 18 13:23:40 2019 +0400 3291 follow-up (#3323) * changelog: use issue number instead of PR number * follow up to #3291 - rpc/test/helpers.go add StopTendermint(node) func - remove ensureDir(filepath.Dir(walFile), 0700) - mempool/mempool_test.go add type cleanupFunc func() * cmd/show_validator: wrap err to make it more clear commit 59cc6d36c944012cd4ae39fbee698e8cf353005a Author: Alessio Treglia Date: Mon Feb 18 08:45:27 2019 +0100 improve ResetTestRootWithChainID() concurrency safety (#3291) * improve ResetTestRootWithChainID() concurrency safety Rely on ioutil.TempDir() to create test root directories and ensure multiple same-chain id test cases can run in parallel. * Update config/toml.go Co-Authored-By: alessio * clean up test directories after completion Closes: #1034 * Remove redundant EnsureDir call * s/PanicSafety()/panic()/s * Put create dir functionality back in ResetTestRootWithChainID * Place test directories in OS's tempdir In modern UNIX and UNIX-like systems /tmp is very often mounted as tmpfs. This might speed test execution a bit. * Set 0700 to a const * rootsDirs -> configRootDirs * Don't double remove directories * Avoid global variables * Fix consensus tests * Reduce defer stack * Address review comments * Try to fix tests * Update CHANGELOG_PENDING.md Co-Authored-By: alessio * Update consensus/common_test.go Co-Authored-By: alessio * Update consensus/common_test.go Co-Authored-By: alessio commit af8793c01a168f0f15d3147673c572c0f8bf7863 Author: Zarko Milosevic Date: Mon Feb 18 08:29:41 2019 +0100 cs: reset triggered timeout precommit (#3310) * Reset TriggeredTimeoutPrecommit as part of updateToState * Add failing test and fix * fix DATA RACE in TestResetTimeoutPrecommitUponNewHeight ``` WARNING: DATA RACE Read at 0x00c001691d28 by goroutine 691: github.com/tendermint/tendermint/consensus.decideProposal() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:133 +0x121 github.com/tendermint/tendermint/consensus.TestResetTimeoutPrecommitUponNewHeight() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1389 +0x958 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous write at 0x00c001691d28 by goroutine 931: github.com/tendermint/tendermint/consensus.(*ConsensusState).updateToState() /go/src/github.com/tendermint/tendermint/consensus/state.go:562 +0x5b2 github.com/tendermint/tendermint/consensus.(*ConsensusState).finalizeCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1340 +0x141e github.com/tendermint/tendermint/consensus.(*ConsensusState).tryFinalizeCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1255 +0x66e github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit.func1() /go/src/github.com/tendermint/tendermint/consensus/state.go:1201 +0x135 github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1232 +0x94b github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1657 +0x132e github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1503 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:694 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:655 +0x7dd github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:655 +0x7dd Goroutine 691 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:216 +0x332 ``` * fix another DATA RACE by locking consensus ``` WARNING: DATA RACE Read at 0x00c009b835a8 by goroutine 871: github.com/tendermint/tendermint/consensus.(*ConsensusState).createProposalBlock() /go/src/github.com/tendermint/tendermint/consensus/state.go:955 +0x7c github.com/tendermint/tendermint/consensus.decideProposal() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:127 +0x53 github.com/tendermint/tendermint/consensus.TestResetTimeoutPrecommitUponNewHeight() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1389 +0x958 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous write at 0x00c009b835a8 by goroutine 931: github.com/tendermint/tendermint/consensus.(*ConsensusState).updateHeight() /go/src/github.com/tendermint/tendermint/consensus/state.go:446 +0xb7 github.com/tendermint/tendermint/consensus.(*ConsensusState).updateToState() /go/src/github.com/tendermint/tendermint/consensus/state.go:542 +0x22f github.com/tendermint/tendermint/consensus.(*ConsensusState).finalizeCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1340 +0x141e github.com/tendermint/tendermint/consensus.(*ConsensusState).tryFinalizeCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1255 +0x66e github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit.func1() /go/src/github.com/tendermint/tendermint/consensus/state.go:1201 +0x135 github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit() /go/src/github.com/tendermint/tendermint/consensus/state.go:1232 +0x94b github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1657 +0x132e github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1503 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:694 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:655 +0x7dd github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:642 +0x948 github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:655 +0x7dd ``` * Fix failing test * Delete profile.out * fix data races commit 0b0a8b3128e52bf34a34685804922a47cd3c7810 Author: Anton Kaliaev Date: Mon Feb 18 11:23:06 2019 +0400 cs/wal: refuse to encode msg that is bigger than maxMsgSizeBytes (#3303) Earlier this week somebody posted this in GoS Riot chat: ``` E[2019-02-12|10:38:37.596] Corrupted entry. Skipping... module=consensus wal=/home/gaia/.gaiad/data/cs.wal/wal err="DataCorruptionError[length 878916964 exceeded maximum possible value of 1048576 bytes]" E[2019-02-12|10:38:37.596] Corrupted entry. Skipping... module=consensus wal=/home/gaia/.gaiad/data/cs.wal/wal err="DataCorruptionError[length 825701731 exceeded maximum possible value of 1048576 bytes]" E[2019-02-12|10:38:37.596] Corrupted entry. Skipping... module=consensus wal=/home/gaia/.gaiad/data/cs.wal/wal err="DataCorruptionError[length 1631073634 exceeded maximum possible value of 1048576 bytes]" E[2019-02-12|10:38:37.596] Corrupted entry. Skipping... module=consensus wal=/home/gaia/.gaiad/data/cs.wal/wal err="DataCorruptionError[length 912418148 exceeded maximum possible value of 1048576 bytes]" E[2019-02-12|10:38:37.600] Corrupted entry. Skipping... module=consensus wal=/home/gaia/.gaiad/data/cs.wal/wal err="DataCorruptionError[failed to read data: EOF]" E[2019-02-12|10:38:37.600] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="Cannot replay height 7242. WAL does not contain #ENDHEIGHT for 7241" E[2019-02-12|10:38:37.861] Error dialing peer module=p2p err="dial tcp 35.183.126.181:26656: i/o timeout ``` Note the length error messages. What has happened is the length field got corrupted probably. I've looked at the code and noticed that we don't check the msg size during encoding. This PR fixes that. It also improves a few error messages in WALDecoder. commit 7ced9e416bc1c08a8b268875762db6a83746777f Author: Anton Kaliaev Date: Sat Feb 16 09:46:02 2019 +0400 rpc/net_info: change RemoteIP type from net.IP to String (#3309) * rpc/net_info: change RemoteIP type from net.IP to String Before: "AAAAAAAAAAAAAP//rB8ktw==" which is amino-encoded net.IP byte slice After: "192.0.2.1" Fixes #3251 * rpc/net_info: non-empty response in docs commit af3ba5145a1763dc16616a4cd9b697c10a6dd373 Author: Ismail Khoffi Date: Sat Feb 16 06:12:00 2019 +0100 fix test-vectors to actually match the sign bytes: (#3312) - sign bytes are length prefixed - change test to use SignBytes methods instead of calling amino methods directly Resolves #3311 ref: #2455 #2508 commit cf737ec85c10fb3baa2071bf5b689ab95cf797be Author: Alexander Bezobchuk Date: Sat Feb 16 00:07:18 2019 -0500 return an error on `show_validator` (#3316) * return a command error prior to init * add a pending log entry * Update CHANGELOG_PENDING.md Co-Authored-By: alexanderbez closes: #3314 commit d32f7d241608c8079f0755a38dbd1b9bd2262724 Author: Zach Date: Tue Feb 12 23:26:01 2019 -0500 update codeowners (#3305) limit the scope of @zramsay because he's annoyed by notifications commit dc6567c677dfdfed7278759927ec47e6e94e7bff Author: Ethan Buchman Date: Tue Feb 12 06:32:40 2019 +0100 consensus: flush wal on stop (#3297) Should fix #3295 Also partial fix of #3043 commit 08dabab024b88d6ed9a2416a6e8b9a2604e6d59b Author: Ethan Buchman Date: Tue Feb 12 06:04:23 2019 +0100 types: validator set update tests (#3284) * types: validator set update tests * docs: abci val updates must not include duplicates commit 8a9eecce7fd00b8510186aa03290a8c4e252d4c4 Author: Anca Zamfir Date: Tue Feb 12 06:02:44 2019 +0100 test blockExec does not panic if all vals removed (#3241) Fix for #3224 Also address #2084 commit b089587b42e7d4a6f7e78971fb4bfadcadb798d0 Author: Ismail Khoffi Date: Tue Feb 12 05:54:12 2019 +0100 make gosec linter pass (#3294) * not related to linter: remove obsolete constants: - `Insecure` and `Secure` and type `Security` are not used anywhere * not related to linter: update example - NewInsecure was deleted; change example to NewRemoteDB * address: Binds to all network interfaces (gosec): - bind to localhost instead of 0.0.0.0 - regenerate test key and cert for this purpose (was valid for ::) and otherwise we would see: transport: authentication handshake failed: x509: certificate is valid for ::, not 127.0.0.1\" (used https://github.com/google/keytransparency/blob/master/scripts/gen_server_keys.sh to regenerate certs) * use sha256 in tests instead of md5; time difference is negligible * nolint usage of math/rand in test and add comment on its import - crypto/rand is slower and we do not need sth more secure in tests * enable linter in circle-ci * another nolint math/rand in test * replace another occurrence of md5 * consistent comment about importing math/rand commit 7fd51e6ade5bd3d6820acdd53ad443c509a4fc7a Author: Anton Kaliaev Date: Mon Feb 11 16:31:34 2019 +0400 make govet linter pass (#3292) * make govet linter pass Refs #3262 * close PipeReader and check for err commit 966b5bdf6ecb89f548a9bffa6c328f918ba7e541 Author: Anca Zamfir Date: Mon Feb 11 13:21:51 2019 +0100 fix failure in TestProposerFrequency (#3293) ``` --- FAIL: TestProposerFrequency (2.50s) panic: empty validator set [recovered] panic: empty validator set goroutine 91 [running]: testing.tRunner.func1(0xc000a98c00) /usr/local/go/src/testing/testing.go:792 +0x6a7 panic(0xeae7e0, 0x11fbb30) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/types.(*ValidatorSet).RescalePriorities(0xc0000e7380, 0x0) /go/src/github.com/tendermint/tendermint/types/validator_set.go:106 +0x1ac github.com/tendermint/tendermint/state.TestProposerFrequency(0xc000a98c00) /go/src/github.com/tendermint/tendermint/state/state_test.go:335 +0xb44 testing.tRunner(0xc000a98c00, 0x111a4d8) /usr/local/go/src/testing/testing.go:827 +0x163 created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x65a FAIL github.com/tendermint/tendermint/state 3.139s ``` commit 021b5cc7f6eee5f38d4d53bf64972c392de53d57 Merge: 792b1257 28d75ec8 Author: Ethan Buchman Date: Sat Feb 9 10:07:49 2019 -0500 Merge pull request #3290 from tendermint/master Merge pull request #3288 from tendermint/release/v0.30.0 commit 28d75ec8016b7fb043735cbe4c4d92ec73355de7 Merge: a8dbc643 792b1257 Author: Ethan Buchman Date: Sat Feb 9 10:07:22 2019 -0500 Merge pull request #3288 from tendermint/release/v0.30.0 Release/v0.30.0 commit 792b12573eee1a58b574decce2bd62399b0b24c3 Author: Ethan Buchman Date: Fri Feb 8 18:50:02 2019 -0500 Prepare v0.30.0 (#3287) * changelog, upgrading, version * update for evidence fixes * linkify * fix an entry commit 4f2ef3670143e8bc46fc76e734be80416053cc16 Author: Ethan Buchman Date: Fri Feb 8 18:40:41 2019 -0500 types.NewCommit (#3275) * types.NewCommit * use types.NewCommit everywhere * fix log in unsafe_reset * memoize height and round in constructor * notes about deprecating toVote * bring back memoizeHeightRound commit 6b1b5959514fbaa1d5bb53bb957b018a9126da03 Merge: cce4d21c 87bdc42b Author: Ethan Buchman Date: Fri Feb 8 18:33:45 2019 -0500 Merge pull request #3286 from tendermint/bucky/fix-duplicate-evidence Fixes for duplicate evidence commit 87bdc42bf827c659aaa4b3c9c796bc3f8e698a87 Author: Ismail Khoffi Date: Sat Feb 9 00:30:45 2019 +0100 Reject blocks with committed evidence (#37) * evidence: NewEvidencePool takes evidenceDB * evidence: failing TestStoreCommitDuplicate tendermint/security#35 * GetEvidence -> GetEvidenceInfo * fix TestStoreCommitDuplicate * comment in VerifyEvidence * add check if evidence was already seen - modify EventPool interface (EventStore is not known in ApplyBlock): - add IsCommitted method to iface - add test * update changelog * fix TestStoreMark: - priority in evidence info gets reset to zero after evidence gets committed * review comments: simplify EvidencePool.IsCommitted - delete obsolete EvidenceStore.IsCommitted * add simple test for IsCommitted * update changelog: this is actually breaking (PR number still missing) * fix TestStoreMark: - priority in evidence info gets reset to zero after evidence gets committed * review suggestion: simplify return commit 90ba63948ad48856fbe23c725b9ffd85c956c174 Author: Ethan Buchman Date: Fri Feb 8 18:25:48 2019 -0500 Sec/bucky/35 commit duplicate evidence (#36) Don't add committed evidence to evpool commit cce4d21ccbaa94163b393dd4dc1fd7c202d4feb7 Author: Anca Zamfir Date: Fri Feb 8 19:05:09 2019 +0100 treat validator updates as set (#3222) * Initial commit for 3181..still early * unit test updates * unit test updates * fix check of dups accross updates and deletes * simplify the processChange() func * added overflow check utest * Added checks for empty valset, new utest * deepcopy changes in processUpdate() * moved to new API, fixed tests * test cleanup * address review comments * make sure votePower > 0 * gofmt fixes * handle duplicates and invalid values * more work on tests, review comments * Renamed and explained K * make TestVal private * split verifyUpdatesAndComputeNewPriorities.., added check for deletes * return error if validator set is empty after processing changes * address review comments * lint err * Fixed the total voting power and added comments * fix lint * fix lint commit c1f7399a86f61bcf26791b9e0a6cf30b28e2048c Author: Ismail Khoffi Date: Fri Feb 8 15:48:09 2019 +0100 review comment: cleaner constant for N/2, delete secp256k1N and use (#3279) `secp256k1.S256().N` directly instead commit 44a89a353703260cb971960b0ffd800b12589407 Merge: f571ee88 a8dbc643 Author: Ethan Buchman Date: Fri Feb 8 09:43:36 2019 -0500 Merge pull request #3282 from tendermint/master Merge master back to develop commit a8dbc64319ae0db3fc2621b36d4ef96ce9d62d15 Merge: 4d7b29cd af6e6cd3 Author: Ethan Buchman Date: Fri Feb 8 09:42:52 2019 -0500 Merge pull request #3276 from tendermint/release/v0.29.2 Release/v0.29.2 commit af6e6cd350541147aed50c4e936f6ba596231027 Author: Ethan Buchman Date: Thu Feb 7 20:12:57 2019 -0500 remove MixEntropy (#3278) * remove MixEntropy * changelog commit ad4bd92fec3bcba8715935c2c42eb27f68f5109a Author: Ethan Buchman Date: Thu Feb 7 19:57:30 2019 -0500 secp256k1: change build tags (#3277) commit f571ee8876d56d3b80a96019ba326fe8932ef116 Author: Ethan Buchman Date: Thu Feb 7 19:34:01 2019 -0500 prepare v0.29.2 (#3272) * update changelog * linkify * bump version * update main changelog * final fixes * entry for wal fix * changelog preamble * remove a line commit 11e36d0bfb6e62bb3b758aa4a4cdf90fa2e89fdb Author: Anton Kaliaev Date: Thu Feb 7 17:16:31 2019 +0400 addrbook_test: preallocate memory for bookSizes (#3268) Fixes https://circleci.com/gh/tendermint/tendermint/44901 commit 354a08c25afbd7c3971ffa5ac2dac3d2ef0a8c07 Author: Ethan Buchman Date: Thu Feb 7 06:58:23 2019 -0500 p2p: fix infinite loop in addrbook (#3232) * failing test * fix infinite loop in addrbook There are cases where we only have a small number of addresses marked good ("old"), but the selection mechanism keeps trying to select more of these addresses, and hence ends up in an infinite loop. Here we fix this to only try and select such "old" addresses if we have enough of them. Note this means, if we don't have enough of them, we may return more "new" addresses than otherwise expected by the newSelectionBias. This whole GetSelectionWithBias method probably needs to be rewritten, but this is a quick fix for the issue. * changelog * fix infinite loop if not enough new addrs * fix another potential infinite loop if a.nNew == 0 -> pickFromOldBucket=true, but we don't have enough items (a.nOld > len(oldBucketToAddrsMap) false) * Revert "fix another potential infinite loop" This reverts commit 146540c1125597162bd89820d611f6531f5e5e4b. * check num addresses instead of buckets, new test * fixed the int division * add slack to bias % in test, lint fixes * Added checks for selection content in test * test cleanup * Apply suggestions from code review Co-Authored-By: ebuchman * address review comments * change after Anton's review comments * use the same docker image we use for testing when building a binary for localnet * switch back to circleci classic * more review comments * more review comments * refactor addrbook_test * build linux binary inside docker in attempt to fix ``` --> Running dep + make build-linux GOOS=linux GOARCH=amd64 make build make[1]: Entering directory `/home/circleci/.go_workspace/src/github.com/tendermint/tendermint' CGO_ENABLED=0 go build -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags 'tendermint' -o build/tendermint ./cmd/tendermint/ p2p/pex/addrbook.go:373:13: undefined: math.Round ``` * change dir from /usr to /go * use concrete Go version for localnet binary * check for nil addresses just to be sure commit e70f27c8e45848687fb1b5ee73b6e1f4ae765262 Author: Thane Thomson Date: Thu Feb 7 08:32:32 2019 +0200 Add remote signer test harness (KMS) (#3149) * WIP: Starts adding remote signer test harness This commit adds a new command to Tendermint to allow for us to build a standalone binary to test remote signers such as KMS (https://github.com/tendermint/kms). Right now, all it does is test that the local public key matches the public key reported by the client, and fails at the point where it attempts to get the client to sign a proposal. * Fixes typo * Fixes proposal validation test This commit fixes the proposal validation test as per #3149. It also moves the test harness into its own internal package to isolate its exports from the `privval` package. * Adds vote signing validation * Applying recommendations from #3149 * Adds function descriptions for test harness * Adds ability to ask remote signer to shut down Prior to this commit, the remote signer needs to manually be shut down, which is not ideal for automated testing. This commit allows us to send a poison pill message to the KMS to let it shut down gracefully once testing is done (whether the tests pass or fail). * Adds tests for remote signer test harness This commit makes some minor modifications to a few files to allow for testing of the remote signer test harness. Two tests are added here: checking for a fully successful (the ideal) case, and for the case where the maximum number of retries has been reached when attempting to accept incoming connections from the remote signer. * Condenses serialization of proposals and votes using existing Tendermint functions * Removes now-unnecessary amino import and codec * Adds error message for vote signing failure * Adds key extraction command for integration test Took the code from here: https://gist.github.com/Liamsi/a80993f24bff574bbfdbbfa9efa84bc7 to create a simple utility command to extract a key from a local Tendermint validator for use in KMS integration testing. * Makes path expansion success non-compulsory * Fixes segfault on SIGTERM We need an additional variable to keep track of whether we're successfully connected, otherwise hitting Ctrl+Break during execution causes a segmentation fault. This now allows for a clean shutdown. * Consolidates shutdown checks * Adds comments indicating codes for easy lookup * Adds Docker build for remote signer harness Updates the `DOCKER/build.sh` and `DOCKER/push.sh` files to allow one to override the image name and Dockerfile using environment variables. Updates the primary `Makefile` as well as the `DOCKER/Makefile` to allow for building the `remote_val_harness` Docker image. * Adds build_remote_val_harness_docker_image to .PHONY * Removes remote signer poison pill messaging functionality * Reduces fluff code in command line parsing As per https://github.com/tendermint/tendermint/pull/3149#pullrequestreview-196171788, this reduces the amount of fluff code in the PR down to the bare minimum. * Fixes ordering of error check and info log * Moves remove_val_harness cmd into tools folder It seems to make sense to rather keep the remote signer test harness in its own tool folder (now rather named `tm-signer-harness` to keep with the tool naming convention). It is actually a separate tool, not meant to be one of the core binaries, but supplementary and supportive. * Updates documentation for tm-signer-harness * Refactors flag parsing to be more compact and less redundant * Adds version sub-command help * Removes extraneous flags parsing * Adds CHANGELOG_PENDING entry for tm-signer-harness * Improves test coverage Adds a few extra parameters to the `MockPV` type to fake broken vote and proposal signing. Also adds some more tests for the test harness so as to increase coverage for failed cases. * Fixes formatting for CHANGELOG_PENDING.md * Fix formatting for documentation config * Point users towards official Tendermint docs for tools documentation * Point users towards official Tendermint docs for tm-signer-harness * Remove extraneous constant * Rename TestHarness.sc to TestHarness.spv for naming consistency * Refactor to remove redundant goroutine * Refactor conditional to cleaner switch statement and better error handling for listener protocol * Remove extraneous goroutine * Add note about installing tmkms via Cargo * Fix typo in naming of output signing key * Add note about where to find chain ID * Replace /home/user with ~/ for brevity * Fixes "signer.key" typo * Minor edits for clarification for tm-signer-harness bulid/setup process commit fcebdf67207c21633cc29da4e3044ca339717413 Merge: 9e902645 1386707c Author: Anton Kaliaev Date: Thu Feb 7 10:28:22 2019 +0400 Merge pull request #3261 from tendermint/anton/circle Revert "quick fix for CircleCI (#2279)" commit 9e9026452cb337fa7110ccc62fd3f6707894e37f Author: Ethan Buchman Date: Wed Feb 6 10:29:51 2019 -0500 p2p/conn: don't hold stopMtx while waiting (#3254) * p2p/conn: fix deadlock in FlushStop/OnStop * makefile: set_with_deadlock * close doneSendRoutine at end of sendRoutine * conn: initialize channs in OnStart commit 45b70ae031f18bb5b95794205785bd90e36ccd4b Author: Ethan Buchman Date: Wed Feb 6 10:24:43 2019 -0500 fix non deterministic test failures and race in privval socket (#3258) * node: decrease retry conn timeout in test Should fix #3256 The retry timeout was set to the default, which is the same as the accept timeout, so it's no wonder this would fail. Here we decrease the retry timeout so we can try many times before the accept timeout. * p2p: increase handshake timeout in test This fails sometimes, presumably because the handshake timeout is so low (only 50ms). So increase it to 1s. Should fix #3187 * privval: fix race with ping. closes #3237 Pings happen in a go-routine and can happen concurrently with other messages. Since we use a request/response protocol, we expect to send a request and get back the corresponding response. But with pings happening concurrently, this assumption could be violated. We were using a mutex, but only a RWMutex, where the RLock was being held for sending messages - this was to allow the underlying connection to be replaced if it fails. Turns out we actually need to use a full lock (not just a read lock) to prevent multiple requests from happening concurrently. * node: fix test name. DelayedStop -> DelayedStart * autofile: Wait() method In the TestWALTruncate in consensus/wal_test.go we remove the WAL directory at the end of the test. However the wal.Stop() does not properly wait for the autofile group to finish shutting down. Hence it was possible that the group's go-routine is still running when the cleanup happens, which causes a panic since the directory disappeared. Here we add a Wait() method to properly wait until the go-routine exits so we can safely clean up. This fixes #2852. commit 4429826229a1b036c6396d4faf4f60ed9a0065ab Author: Ethan Buchman Date: Wed Feb 6 10:14:03 2019 -0500 cmn: GetFreePort (#3255) commit 1386707ceb922f8cd390105c86c6a598c044f748 Author: Anton Kaliaev Date: Wed Feb 6 18:36:54 2019 +0400 rename TestGCRandom to _TestGCRandom commit 1a35895ac80caa50418ed1172ec80e71a0a6b692 Author: Anton Kaliaev Date: Wed Feb 6 18:23:25 2019 +0400 remove gotype linter WARN [runner/nolint] Found unknown linters in //nolint directives: gotype commit 6941d1bb35a04a1d32a027af38d85a0d78374063 Author: Anton Kaliaev Date: Wed Feb 6 18:20:10 2019 +0400 use nolint label instead of commenting commit 23314daee4c1d3c2cb85c67f1debbdf2d5e9bd86 Author: Anton Kaliaev Date: Wed Feb 6 15:38:02 2019 +0400 comment out clist tests w/ depend on runtime.SetFinalizer commit 3c8156a55a7a13188c7c8fd6e54433b465212920 Author: Anton Kaliaev Date: Wed Feb 6 15:24:54 2019 +0400 preallocating memory when we can commit ffd3bf8448d5ca5fd36e1056a97586d7bbbf8fa4 Author: Anton Kaliaev Date: Wed Feb 6 15:16:38 2019 +0400 remove or comment out unused code commit da33dd04cc90f60f339afa8182f59294e67d151b Author: Anton Kaliaev Date: Wed Feb 6 15:00:55 2019 +0400 switch to golangci-lint from gometalinter :speedboat: commit d8f0bc3e60eaddef11fe7e83328a22a149de5a01 Author: Anton Kaliaev Date: Wed Feb 6 14:11:35 2019 +0400 Revert "quick fix for CircleCI (#2279)" This reverts commit 1cf6712a36e8ecc843a68aa373748e89e0afecba. commit 1809efa3500e215e531dfd78dd6fe180ef3ef4b1 Author: Ethan Buchman Date: Mon Feb 4 13:01:59 2019 -0500 Introduce CommitSig alias for Vote in Commit (#3245) * types: memoize height/round in commit instead of first vote * types: commit.ValidateBasic in VerifyCommit * types: new CommitSig alias for Vote In preparation for reducing the redundancy in Commits, we introduce the CommitSig as an alias for Vote. This is non-breaking on the protocol, and minor breaking on the Go API, as Commit now contains a list of CommitSig instead of Vote. * remove dependence on ToVote * update some comments * fix tests * fix tests * fixes from review commit 39eba4e1543960e7783581ec75d2e531e8d44afa Author: Ethan Buchman Date: Mon Feb 4 13:00:06 2019 -0500 WAL: better errors and new fail point (#3246) * privval: more info in errors * wal: change Debug logs to Info * wal: log and return error on corrupted wal instead of panicing * fail: Exit right away instead of sending interupt * consensus: FAIL before handling our own vote allows to replicate #3089: - run using `FAIL_TEST_INDEX=0` - delete some bytes from the end of the WAL - start normally Results in logs like: ``` I[2019-02-03|18:12:58.225] Searching for height module=consensus wal=/Users/ethanbuchman/.tendermint/data/cs.wal/wal height=1 min=0 max=0 E[2019-02-03|18:12:58.225] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="failed to read data: EOF" I[2019-02-03|18:12:58.225] Started node module=main nodeInfo="{ProtocolVersion:{P2P:6 Block:9 App:1} ID_:35e87e93f2e31f305b65a5517fd2102331b56002 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-J8JvJH Version:0.29.1 Channels:4020212223303800 Moniker:Ethans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://0.0.0.0:26657}}" E[2019-02-03|18:12:58.226] Couldn't connect to any seeds module=p2p I[2019-02-03|18:12:59.229] Timed out module=consensus dur=998.568ms height=1 round=0 step=RoundStepNewHeight I[2019-02-03|18:12:59.230] enterNewRound(1/0). Current: 1/0/RoundStepNewHeight module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose(1/0). Current: 1/0/RoundStepNewRound module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose: Our turn to propose module=consensus height=1 round=0 proposer=AD278B7767B05D7FBEB76207024C650988FA77D5 privValidator="PrivValidator{AD278B7767B05D7FBEB76207024C650988FA77D5 LH:1, LR:0, LS:2}" E[2019-02-03|18:12:59.230] enterPropose: Error signing proposal module=consensus height=1 round=0 err="Error signing proposal: Step regression at height 1 round 0. Got 1, last step 2" I[2019-02-03|18:13:02.233] Timed out module=consensus dur=3s height=1 round=0 step=RoundStepPropose I[2019-02-03|18:13:02.233] enterPrevote(1/0). Current: 1/0/RoundStepPropose module=consensus I[2019-02-03|18:13:02.233] enterPrevote: ProposalBlock is nil module=consensus height=1 round=0 E[2019-02-03|18:13:02.234] Error signing vote module=consensus height=1 round=0 vote="Vote{0:AD278B7767B0 1/00/1(Prevote) 000000000000 000000000000 @ 2019-02-04T02:13:02.233897Z}" err="Error signing vote: Conflicting data" ``` Notice the EOF, the step regression, and the conflicting data. * wal: change errors to be DataCorruptionError * exit on corrupt WAL * fix log * fix new line commit eb4e23b91eca39e3568ad1dc3a1f793773810374 Author: Ethan Buchman Date: Mon Feb 4 07:30:24 2019 -0800 fix FlushStop (#3247) * p2p/pex: failing test * p2p/conn: add stopMtx for FlushStop and OnStop * changelog commit 6485e68bebcbf4f91b1bb8fcc52e01e2c97ae6a4 Author: Ismail Khoffi Date: Mon Feb 4 09:24:54 2019 +0100 Use ethereum's secp256k1 lib (#3234) * switch from fork (tendermint/btcd) to orig package (btcsuite/btcd); also - remove obsolete check in test `size != -1` is always true - WIP as the serialization still needs to be wrapped * WIP: wrap signature & privkey, pubkey needs to be wrapped as well * wrap pubkey too * use "github.com/ethereum/go-ethereum/crypto/secp256k1" if cgo is available, else use "github.com/btcsuite/btcd/btcec" and take care of lower-S when verifying Annoyingly, had to disable pruning when importing github.com/ethereum/go-ethereum/ :-/ * update comment * update comment * emulate signature_nocgo.go for additional benchmarks: https://github.com/ethereum/go-ethereum/blob/592bf6a59cac9697f0491b24e5093cb759d7e44c/crypto/signature_nocgo.go#L60-L76 * use our format (r || s) in lower-s form when in the non-cgo case * remove comment about using the C library directly * vendor github.com/btcsuite/btcd too * Add test for the !cgo case * update changelog pending Closes #3162 #3163 Refs #1958, #2091, tendermint/btcd#1 commit d47094550315c094512a242445e0dde24b5a03f5 Author: Anton Kaliaev Date: Wed Jan 30 12:24:26 2019 +0400 update gometalinter to 3.0.0 (#3233) in the attempt to fix https://circleci.com/gh/tendermint/tendermint/43165 also code is simplified by running gofmt -s . remove unused vars enable linters we're currently passing remove deprecated linters commit 8985a1fa63fd9d64840d187243e74b7218a7167e Author: Anton Kaliaev Date: Tue Jan 29 13:16:43 2019 +0400 pubsub: fixes after Ethan's review (#3212) in https://github.com/tendermint/tendermint/pull/3209 commit 6dd817cbbcd9b0b2e2672d8871bce1c5ff385856 Author: Ismail Khoffi Date: Tue Jan 29 09:44:59 2019 +0100 secret connection: check for low order points (#3040) > Implement a check for the blacklisted low order points, ala the X25519 has_small_order() function in libsodium (#3010 (comment)) resolves first half of #3010 commit 0b3a87a3232d59ef51ee810ca8f3645504ab6d2f Author: rickyyangz <38900912+rickyyangz@users.noreply.github.com> Date: Tue Jan 29 14:12:07 2019 +0800 mempool: correct args order in the log msg (#3221) Before: Unexpected tx response from proxy during recheck\n Expected: {r.CheckTx.Data}, got: {memTx.tx} After: Unexpected tx response from proxy during recheck\n Expected: {memTx.tx}, got: {tx} Closes #3214 commit e1edd2aa6a860d505313b73cba4cb74fa4de10fc Author: Zach Date: Mon Jan 28 14:41:37 2019 -0500 hardcode rpc link (#3223) commit 9a0bfafef6eb2f10a07006fde7780ed191ebdf19 Author: Thane Thomson Date: Mon Jan 28 15:41:39 2019 +0200 docs: fix links (#3220) Because there's nothing worse than having to copy/paste a link from a web page to navigate to it :grin: commit a335caaedb5e0e700b7397864d0423c9158b7359 Author: Thane Thomson Date: Mon Jan 28 14:13:17 2019 +0200 alias amino imports (#3219) As per conversation here: https://github.com/tendermint/tendermint/pull/3218#discussion_r251364041 This is the result of running the following code on the repo: ```bash find . -name '*.go' | grep -v 'vendor/' | xargs -n 1 goimports -w ``` commit ff3c4bfc765199c31af6487661e1b23aa8be7037 Author: Anton Kaliaev Date: Mon Jan 28 14:57:47 2019 +0400 add go-deadlock tool to help detect deadlocks (#3218) * add go-deadlock tool to help detect deadlocks Run it with `make test_with_deadlock`. After it's done, use Git to cleanup `git checkout .` Link: https://github.com/sasha-s/go-deadlock/ Replaces https://github.com/tendermint/tendermint/pull/3148 * add a target to cleanup changes commit 8d2dd7e554250cd1e912d4cd9b9ff829c2110ab3 Author: Anton Kaliaev Date: Mon Jan 28 12:38:11 2019 +0400 refactor TestListenerConnectDeadlines to avoid data races (#3201) Fixes #3179 commit 71e5939441b3ecdbad99345d9733ebe7e271645d Author: cong Date: Mon Jan 28 15:36:35 2019 +0800 start eventBus & indexerService before replay and use them while replaying blocks (#3194) so if we did not index the last block (because of panic or smth else), we index it during replay Closes #3186 commit d91ea9b59d7f77b20b92ae8caf1f37d6584b994c Author: Ethan Buchman Date: Fri Jan 25 18:28:06 2019 -0500 adr-033 update commit 9b6b792ce70e52516a643abba077ee82668db48b Author: Ethan Buchman Date: Fri Jan 25 18:11:31 2019 -0500 pubsub: comments commit 57af99d901fccd7078a4da910b665842321ac4cd Author: Ethan Buchman Date: Fri Jan 25 15:01:39 2019 -0500 types: comments on user vs internal events Distinguish between user events and internal consensus events commit a58d5897e42ff58ea0919e46444f4508ed38df90 Author: Ethan Buchman Date: Fri Jan 25 15:01:22 2019 -0500 note about TmCoreSemVer commit ddbdffb4e52710a3bbbb54f2fc9eb80719d7f065 Author: Jae Kwon Date: Fri Jan 25 11:00:55 2019 -0800 add design philosophy doc (#3034) commit d6dd43cdaa6e80ff4184b5e5d8d7a0a101114e75 Author: Ismail Khoffi Date: Fri Jan 25 14:38:26 2019 +0100 adr: style fixes (#3206) - links to issues - fix a few markdown glitches - inline code - etc commit 75cbe4a1c1bfb853b393371e6f525aa2b49d4c70 Author: Joon Date: Fri Jan 25 22:10:36 2019 +0900 R4R: Config TestRoot modification for LCD test (#3177) * add ResetTestRootWithChainID * modify chainid commit 27c1563bf0e60e620f3714bf97b94ae17f5c40ef Merge: c20fbed2 4d7b29cd Author: Ethan Buchman Date: Thu Jan 24 11:35:01 2019 -0500 Merge pull request #3205 from tendermint/master Merge master back to develop commit 4d7b29cd8f8ef998cf8f7141f3a100b0a4fa718d Merge: 4514842a 90970d0d Author: Ethan Buchman Date: Thu Jan 24 11:34:20 2019 -0500 Merge pull request #3203 from tendermint/release/v0.29.1 Release/v0.29.1 commit 90970d0ddc6c0a66e521a5be55f6d60870e8ecfa Author: Ethan Buchman Date: Thu Jan 24 11:19:52 2019 -0500 fix changelog commit bb0a9b3d6d7059f40aa8201311e644b67514bab5 Author: Anton Kaliaev Date: Thu Jan 24 19:18:32 2019 +0400 bump version commit 899259619241e118be99b88aebcb7ffc20c587c3 Author: Anton Kaliaev Date: Thu Jan 24 19:17:53 2019 +0400 update changelog commit c20fbed2f75cd8f3f3e4d3f2618f27839b7cd822 Author: Zarko Milosevic Date: Thu Jan 24 15:33:47 2019 +0100 [WIP] fix halting issue (#3197) fix halting issue commit c4157549abb9be5f0239cf6e3bf6b2d49427a30a Author: Anton Kaliaev Date: Thu Jan 24 13:53:02 2019 +0400 only log "Reached max attempts to dial" once (#3144) Closes #3037 commit fbd1e79465bbb258ac85178e0073b42fec67b512 Author: Infinytum <43315617+infinytum@users.noreply.github.com> Date: Thu Jan 24 09:10:34 2019 +0100 docs: fix lite client formatting (#3198) Closes #3180 commit 1efacaa8d3e491d94095a96ccf5d62698fa776dd Author: Anton Kaliaev Date: Thu Jan 24 11:33:58 2019 +0400 docs: update pubsub ADR (#3131) * docs: update pubsub ADR * third version commit 98b42e9eb2cb102bcbf46712284bf460e06b5542 Author: Gautham Santhosh Date: Thu Jan 24 07:45:43 2019 +0100 docs: explain how someone can run his/her own ABCI app on localnet (#3195) Closes #3192. commit 2449bf7300aa1656fc246a06e1e03864aa41766f Author: Anton Kaliaev Date: Tue Jan 22 22:23:18 2019 +0400 p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ``` commit 3362da0a69a813b80686ec2c9e2da559413d1633 Merge: d9d4f3e6 4514842a Author: Ethan Buchman Date: Tue Jan 22 13:19:58 2019 -0500 Merge pull request #3191 from tendermint/master Merge master to develop commit 4514842a631059a4148026ebce0e46fdf628f875 Merge: 07263298 a97d6995 Author: Ethan Buchman Date: Tue Jan 22 13:19:28 2019 -0500 Merge pull request #3185 from tendermint/release/v0.29.0 Release/v0.29.0 commit a97d6995c990a4e22035d43dba849e80119804ee Author: Ethan Buchman Date: Tue Jan 22 12:24:26 2019 -0500 fix changelog indent (#3190) commit d9d4f3e6292c100e2c0a06c16d0a9cd4b6508255 Author: Ethan Buchman Date: Mon Jan 21 19:32:10 2019 -0500 Prepare v0.29.0 (#3184) * update changelog and upgrading * add note about max voting power in abci spec * update version * changelog commit 7a8aeff4b0f5fa7d9113315e2c2ccf64883a1f90 Author: Ethan Buchman Date: Mon Jan 21 10:02:57 2019 -0500 update spec for Merkle RFC 6962 (#3175) * spec: specify when MerkleRoot is on hashes * remove unnecessary hash methods * update changelog * fix test commit de5a6010f0d635c603c473b6a4870e0e70f129b8 Author: Ethan Buchman Date: Mon Jan 21 09:21:04 2019 -0500 fix DynamicVerifier for large validator set changes (#3171) * base verifier: bc->bv and check chainid * improve some comments * comments in dynamic verifier * fix comment in doc about BaseVerifier It requires the validator set to perfectly match. * failing test for #2862 * move errTooMuchChange to types. fixes #2862 * changelog, comments * ic -> dv * update comment, link to issue commit da95f4aa6da2b966fe9243e481e6cfb3bf3b2c5a Author: Ethan Buchman Date: Sun Jan 20 17:27:49 2019 -0500 mempool: enforce maxMsgSize limit in CheckTx (#3168) - fixes #3008 - reactor requires encoded messages are less than maxMsgSize - requires size of tx + amino-overhead to not exceed maxMsgSize commit 4f8769175ed938a031c329de7da80821edbf7880 Author: Ethan Buchman Date: Sat Jan 19 16:08:57 2019 -0500 [types] hash of ConsensusParams includes only a subset of fields (#3165) * types: dont hash entire ConsensusParams * update encoding spec * update blockchain spec * spec: consensus params hash * changelog commit 40c887baf7bdf2add66d798ae7baa14e15fe7d92 Author: Ismail Khoffi Date: Sat Jan 19 21:55:08 2019 +0100 Normalize priorities to not exceed total voting power (#3049) * more proposer priority tests - test that we don't reset to zero when updating / adding - test that same power validators alternate * add another test to track / simulate similar behaviour as in #2960 * address some of Chris' review comments * address some more of Chris' review comments * temporarily pushing branch with the following changes: The total power might change if: - a validator is added - a validator is removed - a validator is updated Decrement the accums (of all validators) directly after any of these events (by the inverse of the change) * Fix 2960 by re-normalizing / scaling priorities to be in bounds of total power, additionally: - remove heap where it doesn't make sense - avg. only at the end of IncrementProposerPriority instead of each iteration - update (and slightly improve) TestAveragingInIncrementProposerPriorityWithVotingPower to reflect above changes * Fix 2960 by re-normalizing / scaling priorities to be in bounds of total power, additionally: - remove heap where it doesn't make sense - avg. only at the end of IncrementProposerPriority instead of each iteration - update (and slightly improve) TestAveragingInIncrementProposerPriorityWithVotingPower to reflect above changes * fix tests * add comment * update changelog pending & some minor changes * comment about division will floor the result & fix typo * Update TestLargeGenesisValidator: - remove TODO and increase large genesis validator's voting power accordingly * move changelog entry to P2P Protocol * Ceil instead of flooring when dividing & update test * quickly fix failing TestProposerPriorityDoesNotGetResetToZero: - divide by Ceil((maxPriority - minPriority) / 2*totalVotingPower) * fix typo: rename getValWitMostPriority -> getValWithMostPriority * test proposer frequencies * return absolute value for diff. keep testing * use for loop for div * cleanup, more tests * spellcheck * get rid of using floats: manually ceil where necessary * Remove float, simplify, fix tests to match chris's proof (#3157) commit d3e8889411fade8c592ecd05fe2ac8839cf31732 Author: Ethan Buchman Date: Sat Jan 19 14:08:41 2019 -0500 update btcd fork for v0.1.1 (#3164) * update btcd fork for v0.1.1 * changelog commit d17969e378fab0cfc887caeca82ea646c5981aa9 Merge: f5f1416a 07263298 Author: Ethan Buchman Date: Fri Jan 18 12:39:21 2019 -0500 Merge pull request #3154 from tendermint/master Merge master back to develop commit 07263298bd76f1ea7ad82f8e06234a450150d76c Merge: aa40cfcb 5a2e69df Author: Ethan Buchman Date: Fri Jan 18 12:38:48 2019 -0500 Merge pull request #3153 from tendermint/release/v0.28.1 Release/v0.28.1 commit 5a2e69df819e094ec3b065d02912179937b51ea4 Author: Ethan Buchman Date: Fri Jan 18 12:11:02 2019 -0500 changelog and version commit f5f1416a149e525d9f48c8762c7999809d6e9395 Author: bradyjoestar Date: Fri Jan 18 16:09:12 2019 +0800 json2wal: increase reader's buffer size (#3147) ``` panic: failed to unmarshal json: unexpected end of JSON input goroutine 1 [running]: main.main() /root/gelgo/src/github.com/tendermint/tendermint/scripts/json2wal/main.go:66 +0x73f ``` Closes #3146 commit 4d36647eea5d9994f4e036d81beb8f49d25f6493 Author: Henry Harder Date: Thu Jan 17 23:46:34 2019 -0800 Add ParadigmCore to ecosystem.json (#3145) Adding the OrderStream network client (alpha), ParadigmCore, to the `ecosystem.json` file. commit 8fd8f800d0c1853a59ce9519276cce3cfced22cf Author: Ethan Buchman Date: Thu Jan 17 21:46:40 2019 -0500 Bucky/fix evidence halt (#34) * consensus: createProposalBlock function * blockExecutor.CreateProposalBlock - factored out of consensus pkg into a method on blockExec - new private interfaces for mempool ("txNotifier") and evpool with one function each - consensus tests still require more mempool methods * failing test for CreateProposalBlock * Fix bug in include evidece into block * evidence: change maxBytes to maxSize * MaxEvidencePerBlock - changed to return both the max number and the max bytes - preparation for #2590 * changelog * fix linter * Fix from review Co-Authored-By: ebuchman commit 87991059aab94ee86fbcd1b647cc0e5d58d505db Author: Anton Kaliaev Date: Thu Jan 17 17:42:57 2019 +0400 docs: fix RPC links (#3141) commit c69dbb25cec3afc2982ffda2e27a0f91fba7b9e2 Author: Thane Thomson Date: Thu Jan 17 15:10:56 2019 +0200 Consolidates deadline tests for privval Unix/TCP (#3143) * Consolidates deadline tests for privval Unix/TCP Following on from #3115 and #3132, this converts fundamental timeout errors from the client's `acceptConnection()` method so that these can be detected by the test for the TCP connection. Timeout deadlines are now tested for both TCP and Unix domain socket connections. There is also no need for the additional secret connection code: the connection will time out at the `acceptConnection()` phase for TCP connections, and will time out when attempting to obtain the `RemoteSigner`'s public key for Unix domain socket connections. * Removes extraneous logging * Adds IsConnTimeout helper function This commit adds a helper function to detect whether an error is either a fundamental networking timeout error, or an `ErrConnTimeout` error specific to the `RemoteSigner` class. * Adds a test for the IsConnTimeout() helper function * Separates tests logically for IsConnTimeout commit bc8874020f15fc557bad7682ecd3100ea0785155 Author: Gautham Santhosh Date: Thu Jan 17 11:30:51 2019 +0000 docs: fix broken link (#3142) commit 55d723870882512d1a0ba9709129ce3227446e2e Author: Dev Ojha Date: Wed Jan 16 15:03:19 2019 -0600 Add comment to simple_merkle get_split_point (#3136) * Add comment to simple_merkle get_split_point * fix grammar error commit 4a037f9fe6fdcdec7e1fedd1119e2ef61745e34e Merge: dcb8f885 aa40cfcb Author: Ethan Buchman Date: Wed Jan 16 16:02:51 2019 -0500 Merge pull request #3138 from tendermint/master Merge master back to develop commit aa40cfcbb97fac3832b47ccdcf99c6b7fb504cb2 Merge: 1e1ca15b 6d6d103f Author: Ethan Buchman Date: Wed Jan 16 16:01:58 2019 -0500 Merge pull request #3135 from tendermint/release/v0.28.0 Release/v0.28.0 commit 6d6d103f15dcea246b09aa0f5f40348262a21eb0 Author: Ethan Buchman Date: Wed Jan 16 13:41:37 2019 -0500 fixes from review (#3137) commit 239ebe20769b79f46a2ddca88de78e8044543e70 Author: Ethan Buchman Date: Wed Jan 16 10:21:15 2019 -0500 fix changelog fmt (#3134) commit 0cba0e11b5569f7e0d8b61db3c2ce5416ce2753e Author: Ethan Buchman Date: Wed Jan 16 10:16:23 2019 -0500 update changelog and upgrading (#3133) commit d4e67205411c15af756e35eed2240944aa3958b7 Author: Thane Thomson Date: Wed Jan 16 17:05:34 2019 +0200 Expanding tests to cover Unix sockets version of client (#3132) * Adds a random suffix to temporary Unix sockets during testing * Adds Unix domain socket tests for client (FAILING) This adds Unix domain socket tests for the privval client. Right now, one of the tests (TestRemoteSignerRetry) fails, probably because the Unix domain socket state is known instantaneously on both sides by the OS. Committing this to collaborate on the error. * Removes extraneous logging * Completes testing of Unix sockets client version This completes the testing of the client connecting via Unix sockets. There are two specific tests (TestSocketPVDeadline and TestRemoteSignerRetryTCPOnly) that are only relevant to TCP connections. * Renames test to show TCP-specificity * Adds testing into closures for consistency (forgot previously) * Moves test specific to RemoteSigner into own file As per discussion on #3132, `TestRemoteSignerRetryTCPOnly` doesn't really belong with the client tests. This moves it into its own file related to the `RemoteSigner` class. commit dcb8f885256e40a088e0901ee127fe095647cea0 Author: Anton Kaliaev Date: Tue Jan 15 21:16:33 2019 +0400 add "chain_id" label for all metrics (#3123) * add "chain_id" label for all metrics Refs #3082 * fix labels extraction commit a2a62c9be62eced2fb79ad92f264340db352ea36 Merge: d1afa0ed 3191ee8b Author: Thane Thomson Date: Tue Jan 15 18:17:50 2019 +0200 Merge pull request #3121 from thanethomson/release/v0.28.0 Adds tests for Unix sockets commit 3191ee8badde5a815da252dbcd77b641dd417521 Author: Thane Thomson Date: Tue Jan 15 18:06:57 2019 +0200 Dropping "construct" prefix as per #3121 commit 308b7e3bbe7d1a3e88193b691469d18b88b8ae54 Merge: ca00cd6a d1afa0ed Author: Thane Thomson Date: Tue Jan 15 17:58:33 2019 +0200 Merge branch 'release/v0.28.0' into release/v0.28.0 commit 73ea5effe5e8ad6dfaf4a3923830d89a35274663 Author: Kunal Dhariwal Date: Tue Jan 15 18:42:35 2019 +0530 docs: update link for rpc docs (#3129) commit d1afa0ed6cf3d13be9ddbaf1dc60e2d46a2acf53 Author: Ethan Buchman Date: Tue Jan 15 07:55:57 2019 -0500 privval: fixes from review (#3126) https://github.com/tendermint/tendermint/pull/2923#pullrequestreview-192065694 commit ca00cd6a78be56884873257ed4bd5f61f3210d5b Author: Thane Thomson Date: Tue Jan 15 10:14:41 2019 +0200 Make privval listener testing generic This cuts out two tests by constructing test cases and iterating through them, rather than having separate sets of tests for TCP and Unix listeners. This is as per the feedback from #3121. commit 4daca1a634c0d288d5fdab2f0ed8a7103e59c4ad Author: Anton Kaliaev Date: Tue Jan 15 02:35:31 2019 +0400 return maxPerPage (not defaultPerPage) if per_page is greater than max (#3124) it's more user-friendly. Refs #3065 commit bc00a032c17ba5c0f4a138d0da98b50e36acaa5e Author: Anton Kaliaev Date: Tue Jan 15 02:33:33 2019 +0400 makefile: fix build-docker-localnode target (#3122) cd does not work because it's executed in a subprocess so it has to be either chained by && or ; See https://stackoverflow.com/q/1789594/820520 for more details. Fixes #3058 commit 5f4d8e031e2ae6f3abb70e754aacf02c57ac84f0 Author: Anton Kaliaev Date: Mon Jan 14 23:10:13 2019 +0400 [log] fix year format (#3125) Refs #3060 commit 7b2c4bb4938f237820a117499fe58b76b1e4bfe0 Author: Ethan Buchman Date: Mon Jan 14 11:53:43 2019 -0500 update ADR-020 (#3116) commit 5f93220c614c349d0018bb6728da31aa37cb24e3 Author: Thane Thomson Date: Mon Jan 14 11:41:09 2019 +0200 Adds tests for Unix sockets As per #3115, adds simple Unix socket connect/accept deadline tests in pretty much the same way as the TCP connect/accept deadline tests work. commit ec53ce359bb8f011e4dbb715da098bea08c32ded Author: Dev Ojha Date: Sun Jan 13 17:02:38 2019 -0600 Simple merkle rfc compatibility (#2713) * Begin simple merkle compatibility PR * Fix query_test * Use trillian test vectors * Change the split point per RFC 6962 * update spec * refactor innerhash to match spec * Update changelog * Address @liamsi's comments * Write the comment requested by @liamsi commit 1f683188752dde08d7bac8d932efc0bfcf97ece8 Author: Ismail Khoffi Date: Sun Jan 13 23:56:36 2019 +0100 fix order of BlockID and Timestamp in Vote and Proposal (#3078) * Consistent order fields of Timestamp/BlockID fields in CanonicalVote and CanonicalProposal * update spec too * Introduce and use IsZero & IsComplete: - update IsZero method according to spec and introduce IsComplete - use methods in validate basic to validate: proposals come with a "complete" blockId and votes are either complete or empty - update spec: BlockID.IsNil() -> BlockID.IsZero() and fix typo * BlockID comes first * fix tests commit 1ccc0918f5f57f3e74ae471385258e504df01a27 Author: Ismail Khoffi Date: Sun Jan 13 23:40:50 2019 +0100 More ProposerPriority tests (#2966) * more proposer priority tests - test that we don't reset to zero when updating / adding - test that same power validators alternate * add another test to track / simulate similar behaviour as in #2960 * address some of Chris' review comments * address some more of Chris' review comments commit fc031d980b9801470f8eb7e7ed52a0d49e8f3724 Author: Ethan Buchman Date: Sun Jan 13 17:15:34 2019 -0500 Bucky/v0.28.0 (#3119) * changelog pending and upgrading * linkify and version bump * changelog shuffle commit 1895cde590f7abd041daa0d4751983bc9efd7ad0 Author: Zarko Milosevic Date: Sun Jan 13 20:47:00 2019 +0100 [WIP] Fill in consensus core details in ADR 030 (#2696) * Initial work towards making ConsensusCore spec complete * Initial version of executor and complete consensus commit be00cd1adde6e452cf90c5ee2c525c5e13ebdca9 Author: Gian Felipe Date: Mon Jan 14 06:34:29 2019 +1100 Hotfix/validating query result length (#3053) * Validating that there are txs in the query results before loop throught the array * Created tests to validate the error has been fixed * Added comments * Fixing misspeling * check if the variable "skipCount" is bigger than zero. If it is not, we set it to 0. If it, we do not do anything. * using function that validates the skipCount variable * undo Gopkg.lock changes commit a6011c007db764b722c5fe6c5ccb9d85edbb92fe Author: Ismail Khoffi Date: Sun Jan 13 20:31:31 2019 +0100 Close and retry a RemoteSigner on err (#2923) * Close and recreate a RemoteSigner on err * Update changelog * Address Anton's comments / suggestions: - update changelog - restart TCPVal - shut down on `ErrUnexpectedResponse` * re-init remote signer client with fresh connection if Ping fails - add/update TODOs in secret connection - rename tcp.go -> tcp_client.go, same with ipc to clarify their purpose * account for `conn returned by waitConnection can be `nil` - also add TODO about RemoteSigner conn field * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn - add rwmutex for conn field in IPC * comments and doc.go * fix ipc tests. fixes #2677 * use constants for tests * cleanup some error statements * fixes #2784, race in tests * remove print statement * minor fixes from review * update comment on sts spec * cosmetics * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * IPCVal signer refactor - use a .reset() method - don't use embedded RemoteSignerClient - guard RemoteSignerClient with mutex - drop the .conn - expose Close() on RemoteSignerClient * apply IPCVal refactor to TCPVal * remove mtx from RemoteSignerClient * consolidate IPCVal and TCPVal, fixes #3104 - done in tcp_client.go - now called SocketVal - takes a listener in the constructor - make tcpListener and unixListener contain all the differences * delete ipc files * introduce unix and tcp dialer for RemoteSigner * rename files - drop tcp_ prefix - rename priv_validator.go to file.go * bring back listener options * fix node * fix priv_val_server * fix node test * minor cleanup and comments commit ef94a322b8726974f7483deae88cccb5f1c1fe0e Author: Ethan Buchman Date: Sun Jan 13 13:46:25 2019 -0500 Make SecretConnection thread safe (#3111) * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * fix from review commit 7f607d0ce23d5c578325b2d2a6bfe2273191cf9b Author: Mauricio Serna Date: Fri Jan 11 17:41:02 2019 -0500 docs: fix p2p readme links (#3109) commit 81c51cd4fcc8e826ba4dd4e6d4da696ba5c3fe37 Author: Anton Kaliaev Date: Fri Jan 11 17:24:45 2019 +0300 rpc: include peer's remote IP in `/net_info` (#3052) Refs #3047 commit 51094f9417c83bd7a5f314cface6134250b2bcaa Author: Ethan Buchman Date: Fri Jan 11 08:28:29 2019 -0500 update README (#3097) * update README * fix from review commit 7644d273077a3e33fb8b46950bbf2367931aec06 Author: Alessio Treglia Date: Thu Jan 10 23:37:34 2019 +0000 Ensure multisig keys have 20-byte address (#3103) * Ensure multisig keys have 20-byte address Use crypto.AddressHash() to avoid returning 32-byte long address. Closes: #3102 * fix pointer * fix test commit 764cfe33aa55acb46316b3e8fdfa72c7bd49a6e4 Author: Alessio Treglia Date: Thu Jan 10 22:47:20 2019 +0000 Don't use pointer receivers for PubKeyMultisigThreshold (#3100) * Don't use pointer receivers for PubKeyMultisigThreshold * test that showcases panic when PubKeyMultisigThreshold are used in sdk: - deserialization will fail in `readInfo` which tries to read a `crypto.PubKey` into a `localInfo` (called by cosmos-sdk/client/keys.GetKeyInfo) * Update changelog * Rename routeTable to nameTable, multisig key is no longer a pointer * sed -i 's/PubKeyAminoRoute/PubKeyAminoName/g' `grep -lrw PubKeyAminoRoute .` upon Jae's request * AminoRoutes -> AminoNames * sed -e 's/PrivKeyAminoRoute/PrivKeyAminoName/g' * Update crypto/encoding/amino/amino.go Co-Authored-By: alessio commit 616c3a4baeb1f52589f6afe09bc2be256b9626d2 Author: srmo Date: Sun Jan 6 10:00:12 2019 +0100 cs: prettify logging of ignored votes (#3086) Refs #3038 commit 04e97f599aeafa939b55a9c81a7714335bd0596b Author: Piotr Husiatyński Date: Sun Jan 6 09:40:15 2019 +0100 fix build scripts (#3085) * fix build scripts Search for the right variable when introspecting Go code. `Version` was renamed to `TMCoreSemVer`. This is regression introduced in b95ac688af14d130e6ad0b580ed9a8181f6c487c * fix all `Version` introspections. Use `TMCoreSemVer` instead of `Version` commit 56a4fb4d72b986992f72e53a9f15cfbfcf1b0629 Author: Ethan Buchman Date: Wed Jan 2 17:18:45 2019 -0800 add signing spec (#3061) * add signing spec * fixes from review * more fixes * fixes from review commit 49017a57874649567e967e17aa9c31f1b7af00fd Author: srmo Date: Tue Jan 1 08:42:39 2019 +0100 3070 [docs] unindent text as it is supposed to behave the same as the parts before (#3075) commit 6a80412a01c131a83cfe0eb3dfee2b9c6c740053 Author: Ismail Khoffi Date: Sat Dec 22 06:36:45 2018 +0100 Remove privval.GetAddress(), memoize pubkey (#2948) privval: remove GetAddress(), memoize pubkey commit 2348f38927a8766592e02f63f7cfafffbff7f4b2 Author: needkane <604476380@qq.com> Date: Sat Dec 22 06:37:28 2018 +0800 Update node_info.go (#3059) commit 41e2eeee9c7bb5be3f5cbe077f649edaa070b00c Author: yutianwu Date: Sat Dec 22 05:58:27 2018 +0800 R4R: Split immutable and mutable parts of priv_validator.json (#2870) * split immutable and mutable parts of priv_validator.json * fix bugs * minor changes * retrig test * delete scripts/wire2amino.go * fix test * fixes from review * privval: remove mtx * rearrange priv_validator.go * upgrade path * write tests for the upgrade * fix for unsafe_reset_all * add test * add reset test commit a88e283a9dbe2d9c72d1b542efdf8b9d780550da Merge: daddebac 1e1ca15b Author: Ethan Buchman Date: Fri Dec 21 16:39:02 2018 -0500 Merge pull request #3063 from tendermint/master Merge master back to develop commit 1e1ca15bccedc16c2452c73745c3c2b5df91ae96 Merge: 0138530d c6604b5a Author: Ethan Buchman Date: Fri Dec 21 16:35:45 2018 -0500 Merge pull request #3062 from tendermint/release/v0.27.4 Release/v0.27.4 commit c6604b5a9b74c8de242ac9b033edb4f833c9a2d1 Author: Ethan Buchman Date: Fri Dec 21 16:31:28 2018 -0500 changelog and version commit c510f823e7858d288e64b2a7c4149e1e155fac13 Author: Anton Kaliaev Date: Mon Dec 17 20:35:05 2018 +0400 mempool: move tx to back, not front (#3036) because we pop txs from the front if the cache is full Refs #3035 commit daddebac29d9f1258f7e93282e84709bd16a0acd Author: Zach Date: Wed Dec 19 12:45:12 2018 -0500 circleci: update go version (#3051) commit 30f346fe44b416d0597374ec63b3423689cc6f6f Author: Zach Date: Mon Dec 17 14:02:26 2018 -0500 docs: add rpc link to docs navbar and re-org sidebar (#3041) * add rpc to docs navbar and close #3000 * Update config.js commit 4d8f29f79c194bd2ba5648a81b1c14074f4cc1f8 Author: Anton Kaliaev Date: Mon Dec 17 20:52:33 2018 +0400 set allow_duplicate_ip to false (#2992) * config: cors options are arrays of strings, not strings Fixes #2980 * docs: update tendermint-core/configuration.html page * set allow_duplicate_ip to false * in `tendermint testnet`, set allow_duplicate_ip to true Refs #2712 * fixes after Ismail's review commit 2182f6a7022366c52769c3fe8b073b8e4a9101b9 Author: Zach Date: Mon Dec 17 11:51:53 2018 -0500 update go version & other cleanup (#3018) * update go version & other cleanup * fix lints * go one.eleven.four * keep circle on 1.11.3 for now commit a06912b5793787df769c2991270d86129d243349 Author: Anton Kaliaev Date: Mon Dec 17 20:35:05 2018 +0400 mempool: move tx to back, not front (#3036) because we pop txs from the front if the cache is full Refs #3035 commit 0ff715125bf6bfbee99928ad6561245b177d21f4 Author: Zach Date: Sun Dec 16 23:34:13 2018 -0500 fix docs / proxy app (#2988) * fix docs / proxy app, closes #2986 * counter_serial * review comments * list all possible options * add changelog entries commit 385977d5e8c5d65887beadf66d649b9cdef8ea88 Merge: 0533c73a 0138530d Author: Ethan Buchman Date: Sun Dec 16 14:30:46 2018 -0500 Merge pull request #3033 from tendermint/master Merge pull request #3032 from tendermint/release/v0.27.3 commit 0138530df202f5b8e8be1edb9c2f705e404a79cb Merge: 4a568fce 0533c73a Author: Ethan Buchman Date: Sun Dec 16 14:30:23 2018 -0500 Merge pull request #3032 from tendermint/release/v0.27.3 Release/v0.27.3 commit 0533c73a50e1634cf7c60eb608ced25dbfd5fd4b Author: Ethan Buchman Date: Sun Dec 16 14:19:38 2018 -0500 crypto: revert to mainline Go crypto lib (#3027) * crypto: revert to mainline Go crypto lib We used to use a fork for a modified bcrypt so we could pass our own randomness but this was largely unecessary, unused, and a burden. So now we just use the mainline Go crypto lib. * changelog * fix tests * version and changelog commit 1beb45511c436d59864500169864152f2b5571f8 Merge: b3141d7d 4a568fce Author: Ethan Buchman Date: Sun Dec 16 14:13:57 2018 -0500 Merge pull request #3031 from tendermint/master Merge pull request #3030 from tendermint/release/v0.27.2 commit 4a568fcedb09493567b293a52c6c42f8d40076c7 Merge: 1f098187 b3141d7d Author: Ethan Buchman Date: Sun Dec 16 14:13:30 2018 -0500 Merge pull request #3030 from tendermint/release/v0.27.2 Release/v0.27.2 commit b3141d7d02d5c3e9175eb18e34518227e7a2840a Author: Ethan Buchman Date: Sun Dec 16 14:05:58 2018 -0500 makeNodeInfo returns error (#3029) * makeNodeInfo returns error * version and changelog commit 9a6dd96cba96155cfbb590cc522b943d6deda499 Author: Jae Kwon Date: Sun Dec 16 09:27:16 2018 -0800 Revert to using defers in addrbook. (#3025) * Revert to using defers in addrbook. ValidateBasic->Validate since it requires DNS * Update CHANGELOG_PENDING commit 9fa959619a5ad8ca67bd1194a84c726c81728d2d Merge: e4806f98 1f098187 Author: Ethan Buchman Date: Sun Dec 16 12:25:19 2018 -0500 Merge pull request #3026 from tendermint/master Merge pull request #3023 from tendermint/release/v0.27.1 commit 1f098187707c685ad17334c43c6fdaeaf5c466b2 Merge: 9c236ffd e4806f98 Author: Ethan Buchman Date: Sun Dec 16 12:24:54 2018 -0500 Merge pull request #3023 from tendermint/release/v0.27.1 Release/v0.27.1 commit e4806f980bfd25eae9d60b9cda7d26ec8b2c68a8 Author: Ethan Buchman Date: Sat Dec 15 15:58:09 2018 -0500 Bucky/v0.27.1 (#3022) * update changelog * linkify * changelog and version commit b53a2712df582d7ea4b3cf5aca0427e2e1b07751 Author: Anton Kaliaev Date: Sun Dec 16 00:38:13 2018 +0400 docs: networks/docker-compose: small fixes (#3017) commit a75dab492ce74759f32065533df63c45b1d7fd4e Author: Hleb Albau Date: Sat Dec 15 23:32:35 2018 +0300 #2980 fix cors doc (#3013) commit 7c9e767e1fc2fa3f84d50a7313e20ddbff174b46 Author: Ethan Buchman Date: Sat Dec 15 15:26:27 2018 -0500 2980 fix cors (#3021) * config: cors options are arrays of strings, not strings Fixes #2980 * docs: update tendermint-core/configuration.html page * set allow_duplicate_ip to false * in `tendermint testnet`, set allow_duplicate_ip to true Refs #2712 * fixes after Ismail's review * Revert "set allow_duplicate_ip to false" This reverts commit 24c1094ebcf2bd35f2642a44d7a1e5fb5c178fb1. commit f82a8ff73a6352355c2f10809a1cce1b23349d20 Author: JamesRay <66258875@qq.com> Date: Sun Dec 16 03:33:30 2018 +0800 During replay, when appHeight==0, only saveState if stateHeight is also 0 (#3006) * optimize addProposalBlockPart * optimize addProposalBlockPart * if ProposalBlockParts and LockedBlockParts both exist,let LockedBlockParts overwrite ProposalBlockParts. * fix tryAddBlock * broadcast lockedBlockParts in higher priority * when appHeight==0, it's better fetch genDoc than state.validators. * not save state if replay from height 1 * only save state if replay from height 1 when stateHeight is also 1 * only save state if replay from height 1 when stateHeight is also 1 * only save state if replay from height 0 when stateHeight is also 0 * handshake info's response version only update when stateHeight==0 * save the handshake responseInfo appVersion commit ae275d791e7e802b6fec9243af56c7a3b53f6a08 Author: Anton Kaliaev Date: Sat Dec 15 23:27:02 2018 +0400 mempool: notifyTxsAvailable if there're txs left, but recheck=false (#2991) (left after committing a block) Fixes #2961 -------------- ORIGINAL ISSUE Tendermint version : 0.26.4-b771798d ABCI app : kv-store Environment: OS (e.g. from /etc/os-release): macOS 10.14.1 What happened: Set mempool.recheck = false and create empty block = false in config.toml. When transactions get added right between a new empty block is being proposed and committed, the proposer won't propose new block for that transactions immediately after. That transactions are stuck in the mempool until a new transaction is added and trigger the proposer. What you expected to happen: If there is a transaction left in the mempool, new block should be proposed immediately. Have you tried the latest version: yes How to reproduce it (as minimally and precisely as possible): Fire two transaction using broadcast_tx_sync with specific delay between them. (You may need to do it multiple time before the right delay is found, on my machine the delay is 0.98s) Logs (paste a small part showing an error (< 10 lines) or link a pastebin, gist, etc. containing more of the log file): https://pastebin.com/0Wt6uhPF Config (you can paste only the changes you've made): [mempool] recheck = false create_empty_block = false Anything else we need to know: In mempool.go, we found that proposer will immediately propose new block if Last committed block has some transaction (causing AppHash to changed) or mem.notifyTxsAvailable() is called. Our scenario is as followed. A transaction is fired, it will create 1 block with 1 tx (line 1-4 in the log) and 1 empty block. After the empty block is proposed but before it is committed, second transaction is fired and added to mempool. (line 8-16) Now, since the last committed block is empty and mem.notifyTxsAvailable() will be called only if mempool.recheck = true. The proposer won't immediately propose new block, causing the second transaction to stuck in mempool until another transaction is added to mempool and trigger mem.notifyTxsAvailable(). commit f5cca9f1215814301ea4996db747b64eb3926d84 Author: Zach Date: Sat Dec 15 14:01:28 2018 -0500 docs: update DOCS_README (#3019) Co-Authored-By: zramsay commit 3fbe9f235a1cc994c0eee24f55ffae7fa952ce63 Author: Zach Date: Fri Dec 14 00:32:09 2018 -0500 docs: add edit on Github links (#3014) commit f7e463f6d31e3817416fd6d9b7b27497bcd38a0e Author: mircea-c Date: Wed Dec 12 04:48:53 2018 -0500 circleci: add a job to automatically update docs (#3005) commit bc2a9b20c0b10146a5711dd828db6d9eb0f4e96b Author: Dev Ojha Date: Wed Dec 12 01:31:35 2018 -0800 mempool: add a comment and missing changelog entry (#2996) Refs #2994 commit 9e075d8dd51d0b80304ad470fa4e6543fe5d024a Author: Zach Date: Wed Dec 12 04:20:02 2018 -0500 docs: enable full-text search (#3004) commit 8003786c9affff242861141bf7484aeb5796e42c Author: Zach Date: Tue Dec 11 13:21:54 2018 -0500 docs: fixes from 'first time' review (#2999) commit 2594cec116fe1209a374b0e3f8cd5d62abe2e1e5 Author: Daniil Lashin Date: Tue Dec 11 11:41:02 2018 +0300 add UnconfirmedTxs/NumUnconfirmedTxs methods to HTTP/Local clients (#2964) commit df32ea4be5ebb0684d17008451902b252a4673d2 Author: Dev Ojha Date: Tue Dec 11 00:17:21 2018 -0800 Make testing logger that doesn't write to stdout (#2997) commit f69e2c6d6c32b4289631be509a15fea24080573f Author: Anton Kaliaev Date: Tue Dec 11 00:24:58 2018 +0400 p2p: set MConnection#created during init (#2990) Fixes #2715 In crawlPeersRoutine, which is performed when seedMode is run, there is logic that disconnects the peer's state information at 3-hour intervals through the duration value. The duration value is calculated by referring to the created value of MConnection. When MConnection is created for the first time, the created value is not initiated, so it is not disconnected every 3 hours but every time it is disconnected. So, normal nodes are connected to seedNode and disconnected immediately, so address exchange does not work properly. https://github.com/tendermint/tendermint/blob/master/p2p/pex/pex_reactor.go#L629 This point is not work correctly. I think, https://github.com/tendermint/tendermint/blob/master/p2p/conn/connection.go#L148 created variable is missing the current time setting. commit d5d0d2bd778e2e330d156fc97a4ed6d45fe14991 Author: Dev Ojha Date: Mon Dec 10 09:56:49 2018 -0800 Make mempool fail txs with negative gas wanted (#2994) This is only one part of #2989. We also need to fix the application, and add rules to consensus to ensure this. commit 41eaf0e31d3e9462b165378d8870ee7366ed7b38 Author: Anton Kaliaev Date: Sun Dec 9 22:29:51 2018 +0400 turn off strict routability every time (#2983) previously, we're turning it off only when --populate-persistent-peers flag was used, which is obviously incorrect. Fixes https://github.com/cosmos/cosmos-sdk/issues/2983 commit 68b467886a0453e447e125ba614357f2765d6856 Author: Zach Date: Fri Dec 7 10:41:19 2018 -0500 docs: relative links in docs/spec/readme.md, js-amino lib (#2977) Co-Authored-By: zramsay commit 2f64717bb5ae3108ed938648291ba577e70fa8c6 Author: Leo Wang Date: Fri Dec 7 16:30:58 2018 +0800 return an error if validator set is empty in genesis file and after InitChain (#2971) Fixes #2951 commit c4a1cfc5c29f2f3f89bdb995184a34cdf3484c91 Author: Anton Kaliaev Date: Fri Dec 7 12:28:02 2018 +0400 don't ignore key when executing CONTAINS (#2924) Fixes #2912 commit 0f96bea41dc54c99eab71b3f4bf7e11e3347fe48 Merge: 9f8761d1 9c236ffd Author: Ethan Buchman Date: Wed Dec 5 16:51:04 2018 -0500 Merge pull request #2976 from tendermint/master Merge pull request #2975 from tendermint/release/v0.27.0 commit 9c236ffd6c56add84f3c17930ae75c26c68d61ec Merge: b771798d 9f8761d1 Author: Ethan Buchman Date: Wed Dec 5 16:50:36 2018 -0500 Merge pull request #2975 from tendermint/release/v0.27.0 Release/v0.27.0 commit 9f8761d105c8ae661c0139756dbb0b80ba7b2e98 Author: Ethan Buchman Date: Wed Dec 5 15:19:30 2018 -0500 update changelog and upgrading (#2974) commit 5413c11150809e7dd8683293c92281fb459fd68f Author: Anton Kaliaev Date: Wed Dec 5 23:21:46 2018 +0400 kv indexer: add separator to start key when matching ranges (#2925) * kv indexer: add separator to start key when matching ranges to avoid including false positives Refs #2908 * refactor code * add a test case commit a14fd8eba03147050396a5d3972d32350e5f3dd8 Author: Ethan Buchman Date: Wed Dec 5 07:32:27 2018 -0500 p2p: fix peer count mismatch #2332 (#2969) * p2p: test case for peer count mismatch #2332 * p2p: fix peer count mismatch #2332 * changelog * use httptest.Server to scrape Prometheus metrics commit 1bb7e31d63e72e1017ac68a61a498b22a137a028 Author: Ethan Buchman Date: Tue Dec 4 19:16:06 2018 -0500 p2p: panic on transport error (#2968) * p2p: panic on transport error Addresses #2823. Currently, the acceptRoutine exits if the transport returns an error trying to accept a new connection. Once this happens, the node can't accept any new connections. So here, we panic instead. While we could potentially be more intelligent by rerunning the acceptRoutine, the error may indicate something more fundamental (eg. file desriptor limit) that requires a restart anyways. We can leave it to process managers to handle that restart, and notify operators about the panic. * changelog commit 222b8978c8e0c72dfcb7c8c389cbcdec3489fde8 Author: Ethan Buchman Date: Tue Dec 4 08:30:29 2018 -0500 Minor log changes (#2959) * node: allow state and code to have diff block versions * node: pex is a log module commit d9a1aad5c559ebd4b3382ad54290ecb16079b7ea Author: Anton Kaliaev Date: Mon Dec 3 16:17:06 2018 +0400 docs: add client#Start/Stop to examples in RPC docs (#2939) follow-up on https://github.com/tendermint/tendermint/pull/2936 commit 8ef0c2681d2a20e45b056baf1efb40cf89bfa3df Author: Anton Kaliaev Date: Mon Dec 3 16:15:36 2018 +0400 check if deliverTxResCh is still open, return an err otherwise (#2947) deliverTxResCh, like any other eventBus (pubsub) channel, is closed when eventBus is stopped. We must check if the channel is still open. The alternative approach is to not close any channels, which seems a bit odd. Fixes #2408 commit c4d93fd27b5b2b9785f47a71edf5eba569411a72 Author: Ismail Khoffi Date: Fri Nov 30 20:43:16 2018 +0100 explicitly type MaxTotalVotingPower to int64 (#2953) commit dc2a338d96ed7129dd7f8ef03e7a9d1423c5c76e Author: Ethan Buchman Date: Fri Nov 30 14:05:16 2018 -0500 Bucky/v0.27.0 (#2950) * update changelog * changelog, upgrading, version commit 725ed7969accb44e7d6004169ed7ede46d6d53df Author: Ismail Khoffi Date: Thu Nov 29 23:03:41 2018 +0100 Add some ProposerPriority tests (#2946) * WIP: tests for #2785 * rebase onto develop * add Bucky's test without changing ValidatorSet.Update * make TestValidatorSetBasic fail * add ProposerPriority preserving fix to ValidatorSet.Update to fix TestValidatorSetBasic * fix randValidator_ to stay in bounds of MaxTotalVotingPower * check for expected proposer and remove some duplicate code * actually limit the voting power of random validator ... * fix test commit 44b769b1acd0b2aaa8c199b0885bc2811191fb2e Author: Ethan Buchman Date: Thu Nov 29 08:26:12 2018 -0500 types: ValidatorSet.Update preserves Accum (#2941) * types: ValidatorSet.Update preserves ProposerPriority This solves the other issue discovered as part of #2718, where Accum (now called ProposerPriority) is reset to 0 every time a validator is updated. * update changelog * add test * update comment * Update types/validator_set_test.go Co-Authored-By: ebuchman commit 380afaa678c70bd8cb261559cac17fb03a718c48 Author: Anton Kaliaev Date: Thu Nov 29 15:57:11 2018 +0400 docs: update ecosystem.json: add Rust ABCI (#2945) commit b30c34e713560d917119ceef0902fcacd4b2d015 Author: Ismail Khoffi Date: Wed Nov 28 21:35:09 2018 +0100 rename Accum -> ProposerPriority: (#2932) - rename fields, methods, comments, tests commit 403927608595bc2bec43595e51ab1714af50386c Author: Dev Ojha Date: Wed Nov 28 11:53:04 2018 -0800 remove unnecessary "crypto" import alias (#2940) commit 3f987adc921a2ed54baa6267cc969936f20c7d26 Author: Ismail Khoffi Date: Wed Nov 28 19:12:17 2018 +0100 Set accum of freshly added validator -(total voting power) (#2785) * set the accum of a new validator to (-total voting power): - disincentivize validators to unbond, then rebon to reset their negative Accum to zero additional unrelated changes: - do not capitalize error msgs - fix typo * review comments: (re)capitalize errors & delete obsolete comments * More changes suggested by @melekes * WIP: do not batch clip (#2809) * substract avgAccum on each iteration - temporarily skip test * remove unused method safeMulClip / safeMul * always substract the avg accum - temp. skip another test * remove overflow / underflow tests & add tests for avgAccum: - add test for computeAvgAccum - as we substract the avgAccum now we will not trivially over/underflow * address @cwgoes' comments * shift by avg at the end of IncrementAccum * Add comment to MaxTotalVotingPower * Guard inputs to not exceed MaxTotalVotingPower * Address review comments: - do not fetch current validator from set again - update error message * Address a few review comments: - fix typo - extract variable * address more review comments: - clarify 1.125*totalVotingPower == totalVotingPower + (totalVotingPower >> 3) * review comments: panic instead of "clipping": - total voting power is guarded to not exceed MaxTotalVotingPower -> panic if this invariant is violated * fix failing test commit b11788d36d70df6756a886959c71291cb16ca4c5 Author: Ethan Buchman Date: Wed Nov 28 13:09:29 2018 -0500 types: NewValidatorSet doesn't panic on empty valz list (#2938) * types: NewValidatorSet doesn't panic on empty valz list * changelog commit 9adcfe2804f4679086793f09b816e0e4f9287fa1 Author: Daniil Lashin Date: Wed Nov 28 19:55:18 2018 +0300 docs: add client.Start() to RPC WS examples (#2936) commit 3d15579e0c235e0c425405f409c844b16d369ec8 Author: Zach Date: Wed Nov 28 11:29:26 2018 -0500 docs: fix js-abci example (#2935) commit 4571f0fbe8b583c30310e9df5fd0d3d0be79992d Author: Dev Ojha Date: Wed Nov 28 06:09:27 2018 -0800 Enforce validators can only use the correct pubkey type (#2739) * Enforce validators can only use the correct pubkey type * adapt to variable renames * Address comments from #2636 * separate updating and validation logic * update spec * Add test case for TestStringSliceEqual, clarify slice copying code * Address @ebuchman's comments * Split up testing validator update execution, and its validation commit 8a73feae1453b7715907b1db76432cc00405f09a Merge: e291fbbe b771798d Author: Ethan Buchman Date: Wed Nov 28 08:56:39 2018 -0500 Merge pull request #2934 from tendermint/master Merge pull request #2922 from tendermint/release/v0.26.4 commit e291fbbebe2323b640ca2301a8afa70bee05526e Author: srmo Date: Wed Nov 28 14:52:35 2018 +0100 2871 remove proposalHeartbeat infrastructure (#2874) * 2871 remove proposalHeartbeat infrastructure * 2871 add preliminary changelog entry commit 416d143bf72237c026cc289e5505ad943b88944b Author: Jae Kwon Date: Wed Nov 28 22:49:24 2018 +0900 R4R: Swap start/end in ReverseIterator (#2913) * Swap start/end in ReverseIterator * update CHANGELOG_PENDING * fixes from review commit 7213869fc6f8181673d450183878ab7952fce4c6 Author: Daniil Lashin Date: Wed Nov 28 16:32:16 2018 +0300 Refactor updateState #2865 (#2929) * Refactor updateState #2865 * Apply suggestions from code review Co-Authored-By: danil-lashin * Apply suggestions from code review commit ef9902e602f113c0d54bc1f4ee44f0ff1cf6625b Author: Anton Kaliaev Date: Wed Nov 28 17:25:23 2018 +0400 docs: small improvements (#2933) * update docs - make install_c cmd (install) - explain node IDs (quick-start) - update UPGRADING section (using-tendermint) * use git clone with JS example JS devs may not have Go installed and we should not force them to. * rewrite sentence --- .circleci/config.yml | 128 +++++++- CHANGELOG.md | 144 +++++++++ CHANGELOG_PENDING.md | 2 +- DOCKER/Dockerfile | 4 +- Makefile | 11 +- abci/client/socket_client.go | 21 +- blockchain/pool.go | 52 ++- blockchain/pool_test.go | 50 ++- config/config.go | 29 ++ config/toml.go | 11 + consensus/replay.go | 2 +- consensus/state_test.go | 6 +- crypto/doc.go | 3 - crypto/example_test.go | 7 - crypto/hash.go | 8 - crypto/secp256k1/secp256k1.go | 52 ++- crypto/secp256k1/secp256k1_cgo_test.go | 39 +++ crypto/secp256k1/secp256k1_internal_test.go | 45 +++ crypto/secp256k1/secp256k1_nocgo_test.go | 1 - .../{secpk256k1_test.go => secp256k1_test.go} | 26 ++ docs/architecture/adr-037-peer-behaviour.md | 145 +++++++++ docs/networks/docker-compose.md | 2 +- docs/spec/abci/abci.md | 6 +- docs/spec/abci/apps.md | 9 +- docs/spec/blockchain/blockchain.md | 12 +- docs/spec/blockchain/encoding.md | 2 +- docs/spec/consensus/abci.md | 2 +- docs/spec/consensus/wal.md | 2 +- .../reactors/consensus/proposer-selection.md | 305 ++++++++++++++++-- docs/spec/reactors/mempool/reactor.md | 8 + docs/tendermint-core/configuration.md | 11 + libs/autofile/group.go | 251 -------------- libs/autofile/group_test.go | 199 ------------ libs/common/repeat_timer.go | 232 ------------- libs/common/repeat_timer_test.go | 136 -------- libs/common/service.go | 2 +- mempool/bench_test.go | 13 + mempool/cache_test.go | 101 ++++++ mempool/mempool.go | 201 +++++++++--- mempool/mempool_test.go | 92 ++++-- mempool/reactor.go | 104 +++++- mempool/reactor_test.go | 65 +++- node/node.go | 31 +- p2p/conn/connection.go | 12 +- p2p/dummy/peer.go | 100 ------ p2p/errors.go | 24 +- p2p/mock/peer.go | 68 ++++ p2p/node_info.go | 20 +- p2p/peer.go | 27 +- p2p/peer_set_test.go | 2 +- p2p/peer_test.go | 14 +- p2p/pex/addrbook.go | 175 ++++------ p2p/pex/addrbook_test.go | 43 ++- p2p/pex/errors.go | 8 + p2p/pex/file.go | 9 +- p2p/pex/known_address.go | 12 - p2p/pex/pex_reactor.go | 178 +++++----- p2p/pex/pex_reactor_test.go | 146 ++++----- p2p/switch.go | 86 +++-- p2p/switch_test.go | 14 +- p2p/test_util.go | 21 +- p2p/transport.go | 31 +- p2p/transport_test.go | 62 ++-- rpc/client/httpclient.go | 6 +- rpc/client/interface.go | 8 +- rpc/client/localclient.go | 6 +- rpc/client/mock/client.go | 12 + rpc/core/consensus.go | 4 +- rpc/lib/client/http_client.go | 4 +- scripts/release_management/README.md | 65 ++++ scripts/release_management/bump-semver.py | 37 +++ scripts/release_management/github-draft.py | 61 ++++ scripts/release_management/github-openpr.py | 52 +++ .../github-public-newbranch.bash | 28 ++ scripts/release_management/github-publish.py | 53 +++ scripts/release_management/github-upload.py | 68 ++++ scripts/release_management/sha-files.py | 35 ++ scripts/release_management/zip-file.py | 44 +++ state/services.go | 17 +- state/store.go | 53 ++- state/store_test.go | 67 ++++ .../internal/test_harness.go | 8 +- types/protobuf.go | 33 -- types/protobuf_test.go | 2 +- types/validator_set.go | 3 +- version/version.go | 2 +- 86 files changed, 2627 insertions(+), 1665 deletions(-) create mode 100644 crypto/secp256k1/secp256k1_cgo_test.go create mode 100644 crypto/secp256k1/secp256k1_internal_test.go rename crypto/secp256k1/{secpk256k1_test.go => secp256k1_test.go} (74%) create mode 100644 docs/architecture/adr-037-peer-behaviour.md delete mode 100644 libs/common/repeat_timer.go delete mode 100644 libs/common/repeat_timer_test.go create mode 100644 mempool/cache_test.go delete mode 100644 p2p/dummy/peer.go create mode 100644 p2p/mock/peer.go create mode 100644 scripts/release_management/README.md create mode 100755 scripts/release_management/bump-semver.py create mode 100755 scripts/release_management/github-draft.py create mode 100755 scripts/release_management/github-openpr.py create mode 100644 scripts/release_management/github-public-newbranch.bash create mode 100755 scripts/release_management/github-publish.py create mode 100755 scripts/release_management/github-upload.py create mode 100755 scripts/release_management/sha-files.py create mode 100755 scripts/release_management/zip-file.py create mode 100644 state/store_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c51bc48f06..7ad79354942 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 defaults: &defaults working_directory: /go/src/github.com/tendermint/tendermint docker: - - image: circleci/golang:1.12.0 + - image: circleci/golang environment: GOBIN: /tmp/workspace/bin @@ -14,6 +14,9 @@ docs_update_config: &docs_update_config environment: AWS_REGION: us-east-1 +release_management_docker: &release_management_docker + machine: true + jobs: setup_dependencies: <<: *defaults @@ -192,7 +195,7 @@ jobs: name: run localnet and exit on failure command: | set -x - docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang:1.11.4 make build-linux + docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux make localnet-start & ./scripts/localnet-blocks-test.sh 40 5 10 localhost @@ -256,6 +259,105 @@ jobs: echo "Website build started" fi + prepare_build: + <<: *defaults + steps: + - checkout + - run: + name: Get next release number + command: | + export LAST_TAG="`git describe --tags --abbrev=0 --match "${CIRCLE_BRANCH}.*"`" + echo "Last tag: ${LAST_TAG}" + if [ -z "${LAST_TAG}" ]; then + export LAST_TAG="${CIRCLE_BRANCH}" + echo "Last tag not found. Possibly fresh branch or feature branch. Setting ${LAST_TAG} as tag." + fi + export NEXT_TAG="`python -u scripts/release_management/bump-semver.py --version "${LAST_TAG}"`" + echo "Next tag: ${NEXT_TAG}" + echo "export CIRCLE_TAG=\"${NEXT_TAG}\"" > release-version.source + - run: + name: Build dependencies + command: | + make get_tools get_vendor_deps + - persist_to_workspace: + root: . + paths: + - "release-version.source" + - save_cache: + key: v1-release-deps-{{ .Branch }}-{{ .Revision }} + paths: + - "vendor" + + build_artifacts: + <<: *defaults + parallelism: 4 + steps: + - checkout + - restore_cache: + keys: + - v1-release-deps-{{ .Branch }}-{{ .Revision }} + - attach_workspace: + at: /tmp/workspace + - run: + name: Build artifact + command: | + # Setting CIRCLE_TAG because we do not tag the release ourselves. + source /tmp/workspace/release-version.source + if test ${CIRCLE_NODE_INDEX:-0} == 0 ;then export GOOS=linux GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi + if test ${CIRCLE_NODE_INDEX:-0} == 1 ;then export GOOS=darwin GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi + if test ${CIRCLE_NODE_INDEX:-0} == 2 ;then export GOOS=windows GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi + if test ${CIRCLE_NODE_INDEX:-0} == 3 ;then export GOOS=linux GOARCH=arm && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi + - persist_to_workspace: + root: build + paths: + - "*.zip" + - "tendermint_linux_amd64" + + release_artifacts: + <<: *defaults + steps: + - checkout + - attach_workspace: + at: /tmp/workspace + - run: + name: Deploy to GitHub + command: | + # Setting CIRCLE_TAG because we do not tag the release ourselves. + source /tmp/workspace/release-version.source + echo "---" + ls -la /tmp/workspace/*.zip + echo "---" + python -u scripts/release_management/sha-files.py + echo "---" + cat /tmp/workspace/SHA256SUMS + echo "---" + export RELEASE_ID="`python -u scripts/release_management/github-draft.py`" + echo "Release ID: ${RELEASE_ID}" + #Todo: Parallelize uploads + export GOOS=linux GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}" + export GOOS=darwin GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}" + export GOOS=windows GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}" + export GOOS=linux GOARCH=arm && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}" + python -u scripts/release_management/github-upload.py --file "/tmp/workspace/SHA256SUMS" --id "${RELEASE_ID}" + python -u scripts/release_management/github-publish.py --id "${RELEASE_ID}" + + release_docker: + <<: *release_management_docker + steps: + - checkout + - attach_workspace: + at: /tmp/workspace + - run: + name: Deploy to Docker Hub + command: | + # Setting CIRCLE_TAG because we do not tag the release ourselves. + source /tmp/workspace/release-version.source + cp /tmp/workspace/tendermint_linux_amd64 DOCKER/tendermint + docker build --label="tendermint" --tag="tendermint/tendermint:${CIRCLE_TAG}" --tag="tendermint/tendermint:latest" "DOCKER" + docker login -u "${DOCKERHUB_USER}" --password-stdin <<< "${DOCKERHUB_PASS}" + docker push "tendermint/tendermint" + docker logout + workflows: version: 2 test-suite: @@ -292,3 +394,25 @@ workflows: - upload_coverage: requires: - test_cover + release: + jobs: + - prepare_build + - build_artifacts: + requires: + - prepare_build + - release_artifacts: + requires: + - prepare_build + - build_artifacts + filters: + branches: + only: + - /v[0-9]+\.[0-9]+/ + - release_docker: + requires: + - prepare_build + - build_artifacts + filters: + branches: + only: + - /v[0-9]+\.[0-9]+/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ba675ae43..057b2e7be0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,149 @@ # Changelog +## v0.31.4 + +*April 12th, 2019* + +This release fixes a regression from v0.31.3 which used the peer's `SocketAddr` to add the peer to +the address book. This swallowed the peer's self-reported port which is important in case of reconnect. +It brings back `NetAddress()` to `NodeInfo` and uses it instead of `SocketAddr` for adding peers. +Additionally, it improves response time on the `/validators` or `/status` RPC endpoints. +As a side-effect it makes these RPC endpoint more difficult to DoS and fixes a performance degradation in `ExecCommitBlock`. +Also, it contains an [ADR](https://github.com/tendermint/tendermint/pull/3539) that proposes decoupling the +responsibility for peer behaviour from the `p2p.Switch` (by @brapse). + +Special thanks to external contributors on this release: +@brapse, @guagualvcha, @mydring + +### IMPROVEMENTS: +- [p2p] [\#3463](https://github.com/tendermint/tendermint/pull/3463) Do not log "Can't add peer's address to addrbook" error for a private peer +- [p2p] [\#3547](https://github.com/tendermint/tendermint/pull/3547) Fix a couple of annoying typos (@mdyring) + +### BUG FIXES: + +- [docs] [\#3514](https://github.com/tendermint/tendermint/issues/3514) Fix block.Header.Time description (@melekes) +- [p2p] [\#2716](https://github.com/tendermint/tendermint/issues/2716) Check if we're already connected to peer right before dialing it (@melekes) +- [p2p] [\#3545](https://github.com/tendermint/tendermint/issues/3545) Add back `NetAddress()` to `NodeInfo` and use it instead of peer's `SocketAddr()` when adding a peer to the `PEXReactor` (potential fix for [\#3532](https://github.com/tendermint/tendermint/issues/3532)) +- [state] [\#3438](https://github.com/tendermint/tendermint/pull/3438) + Persist validators every 100000 blocks even if no changes to the set + occurred (@guagualvcha). This + 1) Prevents possible DoS attack using `/validators` or `/status` RPC + endpoints. Before response time was growing linearly with height if no + changes were made to the validator set. + 2) Fixes performance degradation in `ExecCommitBlock` where we call + `LoadValidators` for each `Evidence` in the block. + +## v0.31.3 + +*April 1st, 2019* + +This release includes two security sensitive fixes: it ensures generated private +keys are valid, and it prevents certain DNS lookups that would cause the node to +panic if the lookup failed. + +### BREAKING CHANGES: +* Go API + - [crypto/secp256k1] [\#3439](https://github.com/tendermint/tendermint/issues/3439) + The `secp256k1.GenPrivKeySecp256k1` function has changed to guarantee that it returns a valid key, which means it + will return a different private key than in previous versions for the same secret. + +### BUG FIXES: + +- [crypto/secp256k1] [\#3439](https://github.com/tendermint/tendermint/issues/3439) + Ensure generated private keys are valid by randomly sampling until a valid key is found. + Previously, it was possible (though rare!) to generate keys that exceeded the curve order. + Such keys would lead to invalid signatures. +- [p2p] [\#3522](https://github.com/tendermint/tendermint/issues/3522) Memoize + socket address in peer connections to avoid DNS lookups. Previously, failed + DNS lookups could cause the node to panic. + +## v0.31.2 + +*March 30th, 2019* + +This release fixes a regression from v0.31.1 where Tendermint panics under +mempool load for external ABCI apps. + +Special thanks to external contributors on this release: +@guagualvcha + +### BREAKING CHANGES: + +* CLI/RPC/Config + +* Apps + +* Go API + - [libs/autofile] [\#3504](https://github.com/tendermint/tendermint/issues/3504) Remove unused code in autofile package. Deleted functions: `Group.Search`, `Group.FindLast`, `GroupReader.ReadLine`, `GroupReader.PushLine`, `MakeSimpleSearchFunc` (@guagualvcha) + +* Blockchain Protocol + +* P2P Protocol + +### FEATURES: + +### IMPROVEMENTS: + +- [circle] [\#3497](https://github.com/tendermint/tendermint/issues/3497) Move release management to CircleCI + +### BUG FIXES: + +- [mempool] [\#3512](https://github.com/tendermint/tendermint/issues/3512) Fix panic from concurrent access to txsMap, a regression for external ABCI apps introduced in v0.31.1 + +## v0.31.1 + +*March 27th, 2019* + +This release contains a major improvement for the mempool that reduce the amount of sent data by about 30% +(see some numbers below). +It also fixes a memory leak in the mempool and adds TLS support to the RPC server by providing a certificate and key in the config. + +Special thanks to external contributors on this release: +@brapse, @guagualvcha, @HaoyangLiu, @needkane, @TraceBundy + +### BREAKING CHANGES: + +* CLI/RPC/Config + +* Apps + +* Go API + - [crypto] [\#3426](https://github.com/tendermint/tendermint/pull/3426) Remove `Ripemd160` helper method (@needkane) + - [libs/common] [\#3429](https://github.com/tendermint/tendermint/pull/3429) Remove `RepeatTimer` (also `TimerMaker` and `Ticker` interface) + - [rpc/client] [\#3458](https://github.com/tendermint/tendermint/issues/3458) Include `NetworkClient` interface into `Client` interface + - [types] [\#3448](https://github.com/tendermint/tendermint/issues/3448) Remove method `PB2TM.ConsensusParams` + +* Blockchain Protocol + +* P2P Protocol + +### FEATURES: + + - [rpc] [\#3419](https://github.com/tendermint/tendermint/issues/3419) Start HTTPS server if `rpc.tls_cert_file` and `rpc.tls_key_file` are provided in the config (@guagualvcha) + +### IMPROVEMENTS: + +- [docs] [\#3140](https://github.com/tendermint/tendermint/issues/3140) Formalize proposer election algorithm properties +- [docs] [\#3482](https://github.com/tendermint/tendermint/issues/3482) Fix broken links (@brapse) +- [mempool] [\#2778](https://github.com/tendermint/tendermint/issues/2778) No longer send txs back to peers who sent it to you. +Also, limit to 65536 active peers. +This vastly improves the bandwidth consumption of nodes. +For instance, for a 4 node localnet, in a test sending 250byte txs for 120 sec. at 500 txs/sec (total of 15MB): + - total bytes received from 1st node: + - before: 42793967 (43MB) + - after: 30003256 (30MB) + - total bytes sent to 1st node: + - before: 30569339 (30MB) + - after: 19304964 (19MB) +- [p2p] [\#3475](https://github.com/tendermint/tendermint/issues/3475) Simplify `GetSelectionWithBias` for addressbook (@guagualvcha) +- [rpc/lib/client] [\#3430](https://github.com/tendermint/tendermint/issues/3430) Disable compression for HTTP client to prevent GZIP-bomb DoS attacks (@guagualvcha) + +### BUG FIXES: + +- [blockchain] [\#2699](https://github.com/tendermint/tendermint/issues/2699) Update the maxHeight when a peer is removed +- [mempool] [\#3478](https://github.com/tendermint/tendermint/issues/3478) Fix memory-leak related to `broadcastTxRoutine` (@HaoyangLiu) + + ## v0.31.0 *March 16th, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 37ae3a51061..bcb2af5391b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.32.0 +## v0.31.5 ** diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index 4a855f425cb..6a7f289f54c 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,5 +1,5 @@ -FROM alpine:3.7 -MAINTAINER Greg Szabo +FROM alpine:3.9 +LABEL maintainer="hello@tendermint.com" # Tendermint will be looking for the genesis file in /tendermint/config/genesis.json # (unless you change `genesis_file` in config.toml). You can put your config.toml and diff --git a/Makefile b/Makefile index 79ae6aaba75..7c2ce1d9cf2 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ GOTOOLS = \ github.com/square/certstrap GOBIN?=${GOPATH}/bin PACKAGES=$(shell go list ./...) +OUTPUT?=build/tendermint INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf BUILD_TAGS?='tendermint' @@ -19,13 +20,13 @@ check: check_tools get_vendor_deps ### Build Tendermint build: - CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o build/tendermint ./cmd/tendermint/ + CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(OUTPUT) ./cmd/tendermint/ build_c: - CGO_ENABLED=1 go build $(BUILD_FLAGS) -tags "$(BUILD_TAGS) gcc" -o build/tendermint ./cmd/tendermint/ + CGO_ENABLED=1 go build $(BUILD_FLAGS) -tags "$(BUILD_TAGS) gcc" -o $(OUTPUT) ./cmd/tendermint/ build_race: - CGO_ENABLED=0 go build -race $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o build/tendermint ./cmd/tendermint + CGO_ENABLED=0 go build -race $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(OUTPUT) ./cmd/tendermint install: CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/tendermint @@ -109,7 +110,7 @@ draw_deps: get_deps_bin_size: @# Copy of build recipe with additional flags to perform binary size analysis - $(eval $(shell go build -work -a $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o build/tendermint ./cmd/tendermint/ 2>&1)) + $(eval $(shell go build -work -a $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(OUTPUT) ./cmd/tendermint/ 2>&1)) @find $(WORK) -type f -name "*.a" | xargs -I{} du -hxs "{}" | sort -rh | sed -e s:${WORK}/::g > deps_bin_size.log @echo "Results can be found here: $(CURDIR)/deps_bin_size.log" @@ -261,7 +262,7 @@ check_dep: ### Docker image build-docker: - cp build/tendermint DOCKER/tendermint + cp $(OUTPUT) DOCKER/tendermint docker build --label=tendermint --tag="tendermint/tendermint" DOCKER rm -rf DOCKER/tendermint diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index b1ce9ff2190..f1c920921ae 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -26,16 +26,17 @@ var _ Client = (*socketClient)(nil) type socketClient struct { cmn.BaseService - reqQueue chan *ReqRes - flushTimer *cmn.ThrottleTimer + addr string mustConnect bool + conn net.Conn + + reqQueue chan *ReqRes + flushTimer *cmn.ThrottleTimer mtx sync.Mutex - addr string - conn net.Conn err error - reqSent *list.List - resCb func(*types.Request, *types.Response) // listens to all callbacks + reqSent *list.List // list of requests sent, waiting for response + resCb func(*types.Request, *types.Response) // called on all requests, if set. } @@ -86,6 +87,7 @@ func (cli *socketClient) OnStop() { cli.mtx.Lock() defer cli.mtx.Unlock() if cli.conn != nil { + // does this really need a mutex? cli.conn.Close() } @@ -207,12 +209,15 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error { reqres.Done() // Release waiters cli.reqSent.Remove(next) // Pop first item from linked list - // Notify reqRes listener if set + // Notify reqRes listener if set (request specific callback). + // NOTE: it is possible this callback isn't set on the reqres object. + // at this point, in which case it will be called after, when it is set. + // TODO: should we move this after the resCb call so the order is always consistent? if cb := reqres.GetCallback(); cb != nil { cb(res) } - // Notify client listener if set + // Notify client listener if set (global callback). if cli.resCb != nil { cli.resCb(reqres.Request, res) } diff --git a/blockchain/pool.go b/blockchain/pool.go index 2cb7dda96e2..c842c0d130e 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -69,7 +69,7 @@ type BlockPool struct { height int64 // the lowest key in requesters. // peers peers map[p2p.ID]*bpPeer - maxPeerHeight int64 + maxPeerHeight int64 // the biggest reported height // atomic numPending int32 // number of requests pending assignment or block response @@ -78,6 +78,8 @@ type BlockPool struct { errorsCh chan<- peerError } +// NewBlockPool returns a new BlockPool with the height equal to start. Block +// requests and errors will be sent to requestsCh and errorsCh accordingly. func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool { bp := &BlockPool{ peers: make(map[p2p.ID]*bpPeer), @@ -93,15 +95,15 @@ func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- p return bp } +// OnStart implements cmn.Service by spawning requesters routine and recording +// pool's start time. func (pool *BlockPool) OnStart() error { go pool.makeRequestersRoutine() pool.startTime = time.Now() return nil } -func (pool *BlockPool) OnStop() {} - -// Run spawns requesters as needed. +// spawns requesters as needed func (pool *BlockPool) makeRequestersRoutine() { for { if !pool.IsRunning() { @@ -150,6 +152,8 @@ func (pool *BlockPool) removeTimedoutPeers() { } } +// GetStatus returns pool's height, numPending requests and the number of +// requesters. func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequesters int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -157,6 +161,7 @@ func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequester return pool.height, atomic.LoadInt32(&pool.numPending), len(pool.requesters) } +// IsCaughtUp returns true if this node is caught up, false - otherwise. // TODO: relax conditions, prevent abuse. func (pool *BlockPool) IsCaughtUp() bool { pool.mtx.Lock() @@ -170,8 +175,9 @@ func (pool *BlockPool) IsCaughtUp() bool { // Some conditions to determine if we're caught up. // Ensures we've either received a block or waited some amount of time, - // and that we're synced to the highest known height. Note we use maxPeerHeight - 1 - // because to sync block H requires block H+1 to verify the LastCommit. + // and that we're synced to the highest known height. + // Note we use maxPeerHeight - 1 because to sync block H requires block H+1 + // to verify the LastCommit. receivedBlockOrTimedOut := pool.height > 0 || time.Since(pool.startTime) > 5*time.Second ourChainIsLongestAmongPeers := pool.maxPeerHeight == 0 || pool.height >= (pool.maxPeerHeight-1) isCaughtUp := receivedBlockOrTimedOut && ourChainIsLongestAmongPeers @@ -260,14 +266,14 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int } } -// MaxPeerHeight returns the highest height reported by a peer. +// MaxPeerHeight returns the highest reported height. func (pool *BlockPool) MaxPeerHeight() int64 { pool.mtx.Lock() defer pool.mtx.Unlock() return pool.maxPeerHeight } -// Sets the peer's alleged blockchain height. +// SetPeerHeight sets the peer's alleged blockchain height. func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -286,6 +292,8 @@ func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { } } +// RemovePeer removes the peer with peerID from the pool. If there's no peer +// with peerID, function is a no-op. func (pool *BlockPool) RemovePeer(peerID p2p.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -299,10 +307,32 @@ func (pool *BlockPool) removePeer(peerID p2p.ID) { requester.redo(peerID) } } - if p, exist := pool.peers[peerID]; exist && p.timeout != nil { - p.timeout.Stop() + + peer, ok := pool.peers[peerID] + if ok { + if peer.timeout != nil { + peer.timeout.Stop() + } + + delete(pool.peers, peerID) + + // Find a new peer with the biggest height and update maxPeerHeight if the + // peer's height was the biggest. + if peer.height == pool.maxPeerHeight { + pool.updateMaxPeerHeight() + } + } +} + +// If no peers are left, maxPeerHeight is set to 0. +func (pool *BlockPool) updateMaxPeerHeight() { + var max int64 + for _, peer := range pool.peers { + if peer.height > max { + max = peer.height + } } - delete(pool.peers, peerID) + pool.maxPeerHeight = max } // Pick an available peer with at least the given minHeight. diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 75a03f631c1..01d7dba2052 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -1,12 +1,15 @@ package blockchain import ( + "fmt" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) @@ -39,7 +42,9 @@ func (p testPeer) runInputRoutine() { func (p testPeer) simulateInput(input inputData) { block := &types.Block{Header: types.Header{Height: input.request.Height}} input.pool.AddBlock(input.request.PeerID, block, 123) - input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) + // TODO: uncommenting this creates a race which is detected by: https://github.com/golang/go/blob/2bd767b1022dd3254bcec469f0ee164024726486/src/testing/testing.go#L854-L856 + // see: https://github.com/tendermint/tendermint/issues/3390#issue-418379890 + // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) } type testPeers map[p2p.ID]testPeer @@ -66,7 +71,7 @@ func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { return peers } -func TestBasic(t *testing.T) { +func TestBlockPoolBasic(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) errorsCh := make(chan peerError, 1000) @@ -122,7 +127,7 @@ func TestBasic(t *testing.T) { } } -func TestTimeout(t *testing.T) { +func TestBlockPoolTimeout(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) errorsCh := make(chan peerError, 1000) @@ -180,3 +185,40 @@ func TestTimeout(t *testing.T) { } } } + +func TestBlockPoolRemovePeer(t *testing.T) { + peers := make(testPeers, 10) + for i := 0; i < 10; i++ { + peerID := p2p.ID(fmt.Sprintf("%d", i+1)) + height := int64(i + 1) + peers[peerID] = testPeer{peerID, height, make(chan inputData)} + } + requestsCh := make(chan BlockRequest) + errorsCh := make(chan peerError) + + pool := NewBlockPool(1, requestsCh, errorsCh) + pool.SetLogger(log.TestingLogger()) + err := pool.Start() + require.NoError(t, err) + defer pool.Stop() + + // add peers + for peerID, peer := range peers { + pool.SetPeerHeight(peerID, peer.height) + } + assert.EqualValues(t, 10, pool.MaxPeerHeight()) + + // remove not-existing peer + assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) }) + + // remove peer with biggest height + pool.RemovePeer(p2p.ID("10")) + assert.EqualValues(t, 9, pool.MaxPeerHeight()) + + // remove all peers + for peerID := range peers { + pool.RemovePeer(peerID) + } + + assert.EqualValues(t, 0, pool.MaxPeerHeight()) +} diff --git a/config/config.go b/config/config.go index d9628f38376..5ad5e6d59c5 100644 --- a/config/config.go +++ b/config/config.go @@ -339,6 +339,20 @@ type RPCConfig struct { // global HTTP write timeout, which applies to all connections and endpoints. // See https://github.com/tendermint/tendermint/issues/3435 TimeoutBroadcastTxCommit time.Duration `mapstructure:"timeout_broadcast_tx_commit"` + + // The name of a file containing certificate that is used to create the HTTPS server. + // + // If the certificate is signed by a certificate authority, + // the certFile should be the concatenation of the server's certificate, any intermediates, + // and the CA's certificate. + // + // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. + TLSCertFile string `mapstructure:"tls_cert_file"` + + // The name of a file containing matching private key that is used to create the HTTPS server. + // + // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. + TLSKeyFile string `mapstructure:"tls_key_file"` } // DefaultRPCConfig returns a default configuration for the RPC server @@ -357,6 +371,9 @@ func DefaultRPCConfig() *RPCConfig { MaxSubscriptionClients: 100, MaxSubscriptionsPerClient: 5, TimeoutBroadcastTxCommit: 10 * time.Second, + + TLSCertFile: "", + TLSKeyFile: "", } } @@ -395,6 +412,18 @@ func (cfg *RPCConfig) IsCorsEnabled() bool { return len(cfg.CORSAllowedOrigins) != 0 } +func (cfg RPCConfig) KeyFile() string { + return rootify(filepath.Join(defaultConfigDir, cfg.TLSKeyFile), cfg.RootDir) +} + +func (cfg RPCConfig) CertFile() string { + return rootify(filepath.Join(defaultConfigDir, cfg.TLSCertFile), cfg.RootDir) +} + +func (cfg RPCConfig) IsTLSEnabled() bool { + return cfg.TLSCertFile != "" && cfg.TLSKeyFile != "" +} + //----------------------------------------------------------------------------- // P2PConfig diff --git a/config/toml.go b/config/toml.go index 331c3d0d7a3..63bda09e529 100644 --- a/config/toml.go +++ b/config/toml.go @@ -181,6 +181,17 @@ max_subscriptions_per_client = {{ .RPC.MaxSubscriptionsPerClient }} # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "{{ .RPC.TimeoutBroadcastTxCommit }}" +# The name of a file containing certificate that is used to create the HTTPS server. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_cert_file = "{{ .RPC.TLSCertFile }}" + +# The name of a file containing matching private key that is used to create the HTTPS server. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_key_file = "{{ .RPC.TLSKeyFile }}" + ##### peer to peer configuration options ##### [p2p] diff --git a/consensus/replay.go b/consensus/replay.go index c8ab8a33185..e47d4892a57 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -324,7 +324,7 @@ func (h *Handshaker) ReplayBlocks( } if res.ConsensusParams != nil { - state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams, state.ConsensusParams.Block.TimeIotaMs) + state.ConsensusParams = state.ConsensusParams.Update(res.ConsensusParams) } sm.SaveState(h.stateDB, state) } diff --git a/consensus/state_test.go b/consensus/state_test.go index a4d01e5844a..fc1e3e949e5 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -14,7 +14,7 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" - p2pdummy "github.com/tendermint/tendermint/p2p/dummy" + p2pmock "github.com/tendermint/tendermint/p2p/mock" "github.com/tendermint/tendermint/types" ) @@ -1548,7 +1548,7 @@ func TestStateHalt1(t *testing.T) { func TestStateOutputsBlockPartsStats(t *testing.T) { // create dummy peer cs, _ := randConsensusState(1) - peer := p2pdummy.NewPeer() + peer := p2pmock.NewPeer(nil) // 1) new block part parts := types.NewPartSetFromData(cmn.RandBytes(100), 10) @@ -1591,7 +1591,7 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { func TestStateOutputVoteStats(t *testing.T) { cs, vss := randConsensusState(2) // create dummy peer - peer := p2pdummy.NewPeer() + peer := p2pmock.NewPeer(nil) vote := signVote(vss[1], types.PrecommitType, []byte("test"), types.PartSetHeader{}) diff --git a/crypto/doc.go b/crypto/doc.go index 41b3f302159..95ae0af1812 100644 --- a/crypto/doc.go +++ b/crypto/doc.go @@ -37,9 +37,6 @@ // sum := crypto.Sha256([]byte("This is Tendermint")) // fmt.Printf("%x\n", sum) -// Ripemd160 -// sum := crypto.Ripemd160([]byte("This is consensus")) -// fmt.Printf("%x\n", sum) package crypto // TODO: Add more docs in here diff --git a/crypto/example_test.go b/crypto/example_test.go index 904e1c6109c..f1d0013d483 100644 --- a/crypto/example_test.go +++ b/crypto/example_test.go @@ -26,10 +26,3 @@ func ExampleSha256() { // Output: // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e } - -func ExampleRipemd160() { - sum := crypto.Ripemd160([]byte("This is Tendermint")) - fmt.Printf("%x\n", sum) - // Output: - // 051e22663e8f0fd2f2302f1210f954adff009005 -} diff --git a/crypto/hash.go b/crypto/hash.go index c1fb41f7a5d..e1d22523f27 100644 --- a/crypto/hash.go +++ b/crypto/hash.go @@ -2,8 +2,6 @@ package crypto import ( "crypto/sha256" - - "golang.org/x/crypto/ripemd160" ) func Sha256(bytes []byte) []byte { @@ -11,9 +9,3 @@ func Sha256(bytes []byte) []byte { hasher.Write(bytes) return hasher.Sum(nil) } - -func Ripemd160(bytes []byte) []byte { - hasher := ripemd160.New() - hasher.Write(bytes) - return hasher.Sum(nil) -} diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index 78857c45c5a..fc64a0b0ff0 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -6,6 +6,7 @@ import ( "crypto/subtle" "fmt" "io" + "math/big" "golang.org/x/crypto/ripemd160" @@ -65,32 +66,61 @@ func (privKey PrivKeySecp256k1) Equals(other crypto.PrivKey) bool { } // GenPrivKey generates a new ECDSA private key on curve secp256k1 private key. -// It uses OS randomness in conjunction with the current global random seed -// in tendermint/libs/common to generate the private key. +// It uses OS randomness to generate the private key. func GenPrivKey() PrivKeySecp256k1 { return genPrivKey(crypto.CReader()) } // genPrivKey generates a new secp256k1 private key using the provided reader. func genPrivKey(rand io.Reader) PrivKeySecp256k1 { - privKeyBytes := [32]byte{} - _, err := io.ReadFull(rand, privKeyBytes[:]) - if err != nil { - panic(err) + var privKeyBytes [32]byte + d := new(big.Int) + for { + privKeyBytes = [32]byte{} + _, err := io.ReadFull(rand, privKeyBytes[:]) + if err != nil { + panic(err) + } + + d.SetBytes(privKeyBytes[:]) + // break if we found a valid point (i.e. > 0 and < N == curverOrder) + isValidFieldElement := 0 < d.Sign() && d.Cmp(secp256k1.S256().N) < 0 + if isValidFieldElement { + break + } } - // crypto.CRandBytes is guaranteed to be 32 bytes long, so it can be - // casted to PrivKeySecp256k1. + return PrivKeySecp256k1(privKeyBytes) } +var one = new(big.Int).SetInt64(1) + // GenPrivKeySecp256k1 hashes the secret with SHA2, and uses // that 32 byte output to create the private key. +// +// It makes sure the private key is a valid field element by setting: +// +// c = sha256(secret) +// k = (c mod (n − 1)) + 1, where n = curve order. +// // NOTE: secret should be the output of a KDF like bcrypt, // if it's derived from user input. func GenPrivKeySecp256k1(secret []byte) PrivKeySecp256k1 { - privKey32 := sha256.Sum256(secret) - // sha256.Sum256() is guaranteed to be 32 bytes long, so it can be - // casted to PrivKeySecp256k1. + secHash := sha256.Sum256(secret) + // to guarantee that we have a valid field element, we use the approach of: + // "Suite B Implementer’s Guide to FIPS 186-3", A.2.1 + // https://apps.nsa.gov/iaarchive/library/ia-guidance/ia-solutions-for-classified/algorithm-guidance/suite-b-implementers-guide-to-fips-186-3-ecdsa.cfm + // see also https://github.com/golang/go/blob/0380c9ad38843d523d9c9804fe300cb7edd7cd3c/src/crypto/ecdsa/ecdsa.go#L89-L101 + fe := new(big.Int).SetBytes(secHash[:]) + n := new(big.Int).Sub(secp256k1.S256().N, one) + fe.Mod(fe, n) + fe.Add(fe, one) + + feB := fe.Bytes() + var privKey32 [32]byte + // copy feB over to fixed 32 byte privKey32 and pad (if necessary) + copy(privKey32[32-len(feB):32], feB) + return PrivKeySecp256k1(privKey32) } diff --git a/crypto/secp256k1/secp256k1_cgo_test.go b/crypto/secp256k1/secp256k1_cgo_test.go new file mode 100644 index 00000000000..edb207b53d1 --- /dev/null +++ b/crypto/secp256k1/secp256k1_cgo_test.go @@ -0,0 +1,39 @@ +// +build libsecp256k1 + +package secp256k1 + +import ( + "github.com/magiconair/properties/assert" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPrivKeySecp256k1SignVerify(t *testing.T) { + msg := []byte("A.1.2 ECC Key Pair Generation by Testing Candidates") + priv := GenPrivKey() + tests := []struct { + name string + privKey PrivKeySecp256k1 + wantSignErr bool + wantVerifyPasses bool + }{ + {name: "valid sign-verify round", privKey: priv, wantSignErr: false, wantVerifyPasses: true}, + {name: "invalid private key", privKey: [32]byte{}, wantSignErr: true, wantVerifyPasses: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.privKey.Sign(msg) + if tt.wantSignErr { + require.Error(t, err) + t.Logf("Got error: %s", err) + return + } + require.NoError(t, err) + require.NotNil(t, got) + + pub := tt.privKey.PubKey() + assert.Equal(t, tt.wantVerifyPasses, pub.VerifyBytes(msg, got)) + }) + } +} diff --git a/crypto/secp256k1/secp256k1_internal_test.go b/crypto/secp256k1/secp256k1_internal_test.go new file mode 100644 index 00000000000..305f12020d5 --- /dev/null +++ b/crypto/secp256k1/secp256k1_internal_test.go @@ -0,0 +1,45 @@ +package secp256k1 + +import ( + "bytes" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + underlyingSecp256k1 "github.com/btcsuite/btcd/btcec" +) + +func Test_genPrivKey(t *testing.T) { + + empty := make([]byte, 32) + oneB := big.NewInt(1).Bytes() + onePadded := make([]byte, 32) + copy(onePadded[32-len(oneB):32], oneB) + t.Logf("one padded: %v, len=%v", onePadded, len(onePadded)) + + validOne := append(empty, onePadded...) + tests := []struct { + name string + notSoRand []byte + shouldPanic bool + }{ + {"empty bytes (panics because 1st 32 bytes are zero and 0 is not a valid field element)", empty, true}, + {"curve order: N", underlyingSecp256k1.S256().N.Bytes(), true}, + {"valid because 0 < 1 < N", validOne, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + require.Panics(t, func() { + genPrivKey(bytes.NewReader(tt.notSoRand)) + }) + return + } + got := genPrivKey(bytes.NewReader(tt.notSoRand)) + fe := new(big.Int).SetBytes(got[:]) + require.True(t, fe.Cmp(underlyingSecp256k1.S256().N) < 0) + require.True(t, fe.Sign() > 0) + }) + } +} diff --git a/crypto/secp256k1/secp256k1_nocgo_test.go b/crypto/secp256k1/secp256k1_nocgo_test.go index a06a0e3d195..17cb758151a 100644 --- a/crypto/secp256k1/secp256k1_nocgo_test.go +++ b/crypto/secp256k1/secp256k1_nocgo_test.go @@ -6,7 +6,6 @@ import ( "testing" secp256k1 "github.com/btcsuite/btcd/btcec" - "github.com/stretchr/testify/require" ) diff --git a/crypto/secp256k1/secpk256k1_test.go b/crypto/secp256k1/secp256k1_test.go similarity index 74% rename from crypto/secp256k1/secpk256k1_test.go rename to crypto/secp256k1/secp256k1_test.go index 0f0b5adce9c..2488b539997 100644 --- a/crypto/secp256k1/secpk256k1_test.go +++ b/crypto/secp256k1/secp256k1_test.go @@ -2,6 +2,7 @@ package secp256k1_test import ( "encoding/hex" + "math/big" "testing" "github.com/btcsuite/btcutil/base58" @@ -84,3 +85,28 @@ func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { require.Equal(t, privKeyBytes[:], serializedBytes) } } + +func TestGenPrivKeySecp256k1(t *testing.T) { + // curve oder N + N := underlyingSecp256k1.S256().N + tests := []struct { + name string + secret []byte + }{ + {"empty secret", []byte{}}, + {"some long secret", []byte("We live in a society exquisitely dependent on science and technology, in which hardly anyone knows anything about science and technology.")}, + {"another seed used in cosmos tests #1", []byte{0}}, + {"another seed used in cosmos tests #2", []byte("mySecret")}, + {"another seed used in cosmos tests #3", []byte("")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotPrivKey := secp256k1.GenPrivKeySecp256k1(tt.secret) + require.NotNil(t, gotPrivKey) + // interpret as a big.Int and make sure it is a valid field element: + fe := new(big.Int).SetBytes(gotPrivKey[:]) + require.True(t, fe.Cmp(N) < 0) + require.True(t, fe.Sign() > 0) + }) + } +} diff --git a/docs/architecture/adr-037-peer-behaviour.md b/docs/architecture/adr-037-peer-behaviour.md new file mode 100644 index 00000000000..36b024482d4 --- /dev/null +++ b/docs/architecture/adr-037-peer-behaviour.md @@ -0,0 +1,145 @@ +# ADR 037: Peer Behaviour Interface + +## Changelog +* 07-03-2019: Initial draft + +## Context + +The responsibility for signaling and acting upon peer behaviour lacks a single +owning component and is heavily coupled with the network stack[1](#references). Reactors +maintain a reference to the `p2p.Switch` which they use to call +`switch.StopPeerForError(...)` when a peer misbehaves and +`switch.MarkAsGood(...)` when a peer contributes in some meaningful way. +While the switch handles `StopPeerForError` internally, the `MarkAsGood` +method delegates to another component, `p2p.AddrBook`. This scheme of delegation +across Switch obscures the responsibility for handling peer behaviour +and ties up the reactors in a larger dependency graph when testing. + +## Decision + +Introduce a `PeerBehaviour` interface and concrete implementations which +provide methods for reactors to signal peer behaviour without direct +coupling `p2p.Switch`. Introduce a ErrPeer to provide +concrete reasons for stopping peers. + +### Implementation Changes + +PeerBehaviour then becomes an interface for signaling peer errors as well +as for marking peers as `good`. + +XXX: It might be better to pass p2p.ID instead of the whole peer but as +a first draft maintain the underlying implementation as much as +possible. + +```go +type PeerBehaviour interface { + Errored(peer Peer, reason ErrPeer) + MarkPeerAsGood(peer Peer) +} +``` + +Instead of signaling peers to stop with arbitrary reasons: +`reason interface{}` + +We introduce a concrete error type ErrPeer: +```go +type ErrPeer int + +const ( + ErrPeerUnknown = iota + ErrPeerBadMessage + ErrPeerMessageOutofOrder + ... +) +``` + +As a first iteration we provide a concrete implementation which wraps +the switch: +```go +type SwitchedPeerBehaviour struct { + sw *Switch +} + +func (spb *SwitchedPeerBehaviour) Errored(peer Peer, reason ErrPeer) { + spb.sw.StopPeerForError(peer, reason) +} + +func (spb *SwitchedPeerBehaviour) MarkPeerAsGood(peer Peer) { + spb.sw.MarkPeerAsGood(peer) +} + +func NewSwitchedPeerBehaviour(sw *Switch) *SwitchedPeerBehaviour { + return &SwitchedPeerBehaviour{ + sw: sw, + } +} +``` + +Reactors, which are often difficult to unit test[2](#references). could use an implementation which exposes the signals produced by the reactor in +manufactured scenarios: + +```go +type PeerErrors map[Peer][]ErrPeer +type GoodPeers map[Peer]bool + +type StorePeerBehaviour struct { + pe PeerErrors + gp GoodPeers +} + +func NewStorePeerBehaviour() *StorePeerBehaviour{ + return &StorePeerBehaviour{ + pe: make(PeerErrors), + gp: GoodPeers{}, + } +} + +func (spb StorePeerBehaviour) Errored(peer Peer, reason ErrPeer) { + if _, ok := spb.pe[peer]; !ok { + spb.pe[peer] = []ErrPeer{reason} + } else { + spb.pe[peer] = append(spb.pe[peer], reason) + } +} + +func (mpb *StorePeerBehaviour) GetPeerErrors() PeerErrors { + return mpb.pe +} + +func (spb *StorePeerBehaviour) MarkPeerAsGood(peer Peer) { + if _, ok := spb.gp[peer]; !ok { + spb.gp[peer] = true + } +} + +func (spb *StorePeerBehaviour) GetGoodPeers() GoodPeers { + return spb.gp +} +``` + +## Status + +Proposed + +## Consequences + +### Positive + + * De-couple signaling from acting upon peer behaviour. + * Reduce the coupling of reactors and the Switch and the network + stack + * The responsibility of managing peer behaviour can be migrated to + a single component instead of split between the switch and the + address book. + +### Negative + + * The first iteration will simply wrap the Switch and introduce a + level of indirection. + +### Neutral + +## References + +1. Issue [#2067](https://github.com/tendermint/tendermint/issues/2067): P2P Refactor +2. PR: [#3506](https://github.com/tendermint/tendermint/pull/3506): ADR 036: Blockchain Reactor Refactor diff --git a/docs/networks/docker-compose.md b/docs/networks/docker-compose.md index 7e4adde8d4d..8db49af5e98 100644 --- a/docs/networks/docker-compose.md +++ b/docs/networks/docker-compose.md @@ -4,7 +4,7 @@ With Docker Compose, you can spin up local testnets with a single command. ## Requirements -1. [Install tendermint](/docs/introduction/install.md) +1. [Install tendermint](../introduction/install.md) 2. [Install docker](https://docs.docker.com/engine/installation/) 3. [Install docker-compose](https://docs.docker.com/compose/install/) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index c696c938471..c65d96ec1b5 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -347,8 +347,10 @@ Commit are included in the header of the next block. - `Version (Version)`: Version of the blockchain and the application - `ChainID (string)`: ID of the blockchain - `Height (int64)`: Height of the block in the chain - - `Time (google.protobuf.Timestamp)`: Time of the block. It is the proposer's - local time when block was created. + - `Time (google.protobuf.Timestamp)`: Time of the previous block. + For heights > 1, it's the weighted median of the timestamps of the valid + votes in the block.LastCommit. + For height == 1, it's genesis time. - `NumTxs (int32)`: Number of transactions in the block - `TotalTxs (int64)`: Total number of transactions in the blockchain until now diff --git a/docs/spec/abci/apps.md b/docs/spec/abci/apps.md index ca6abe7f84e..47e62eed935 100644 --- a/docs/spec/abci/apps.md +++ b/docs/spec/abci/apps.md @@ -31,8 +31,13 @@ states to the latest committed state at once. When `Commit` completes, it unlocks the mempool. -Note that it is not possible to send transactions to Tendermint during `Commit` - if your app -tries to send a `/broadcast_tx` to Tendermint during Commit, it will deadlock. +WARNING: if the ABCI app logic processing the `Commit` message sends a +`/broadcast_tx_sync` or `/broadcast_tx_commit` and waits for the response +before proceeding, it will deadlock. Executing those `broadcast_tx` calls +involves acquiring a lock that is held during the `Commit` call, so it's not +possible. If you make the call to the `broadcast_tx` endpoints concurrently, +that's no problem, it just can't be part of the sequential logic of the +`Commit` function. ### Consensus Connection diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 00cccfc2e39..cd31c5dc1e6 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -103,7 +103,7 @@ type PartSetHeader struct { } ``` -See [MerkleRoot](/docs/spec/blockchain/encoding.md#MerkleRoot) for details. +See [MerkleRoot](./encoding.md#MerkleRoot) for details. ## Time @@ -163,7 +163,7 @@ a _precommit_ has `vote.Type == 2`. Signatures in Tendermint are raw bytes representing the underlying signature. -See the [signature spec](/docs/spec/blockchain/encoding.md#key-types) for more. +See the [signature spec](./encoding.md#key-types) for more. ## EvidenceData @@ -190,7 +190,7 @@ type DuplicateVoteEvidence struct { } ``` -See the [pubkey spec](/docs/spec/blockchain/encoding.md#key-types) for more. +See the [pubkey spec](./encoding.md#key-types) for more. ## Validation @@ -209,7 +209,7 @@ the current version of the `state` corresponds to the state after executing transactions from the `prevBlock`. Elements of an object are accessed as expected, ie. `block.Header`. -See the [definition of `State`](/docs/spec/blockchain/state.md). +See the [definition of `State`](./state.md). ### Header @@ -244,7 +244,7 @@ The height is an incrementing integer. The first block has `block.Header.Height ### Time ``` -block.Header.Timestamp >= prevBlock.Header.Timestamp + 1 ms +block.Header.Timestamp >= prevBlock.Header.Timestamp + state.consensusParams.Block.TimeIotaMs block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators) ``` @@ -332,6 +332,7 @@ block.ValidatorsHash == MerkleRoot(state.Validators) MerkleRoot of the current validator set that is committing the block. This can be used to validate the `LastCommit` included in the next block. +Note the validators are sorted by their address before computing the MerkleRoot. ### NextValidatorsHash @@ -342,6 +343,7 @@ block.NextValidatorsHash == MerkleRoot(state.NextValidators) MerkleRoot of the next validator set that will be the validator set that commits the next block. This is included so that the current validator set gets a chance to sign the next validator sets Merkle root. +Note the validators are sorted by their address before computing the MerkleRoot. ### ConsensusHash diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index e8258e4a917..bde580a14ce 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -339,6 +339,6 @@ type CanonicalVote struct { The field ordering and the fixed sized encoding for the first three fields is optimized to ease parsing of SignBytes in HSMs. It creates fixed offsets for relevant fields that need to be read in this context. -For more details, see the [signing spec](/docs/spec/consensus/signing.md). +For more details, see the [signing spec](../consensus/signing.md). Also, see the motivating discussion in [#1622](https://github.com/tendermint/tendermint/issues/1622). diff --git a/docs/spec/consensus/abci.md b/docs/spec/consensus/abci.md index 82b88161e23..226d228997b 100644 --- a/docs/spec/consensus/abci.md +++ b/docs/spec/consensus/abci.md @@ -1 +1 @@ -[Moved](/docs/spec/software/abci.md) +[Moved](../software/abci.md) diff --git a/docs/spec/consensus/wal.md b/docs/spec/consensus/wal.md index 589680f993c..6146ab9c0bb 100644 --- a/docs/spec/consensus/wal.md +++ b/docs/spec/consensus/wal.md @@ -1 +1 @@ -[Moved](/docs/spec/software/wal.md) +[Moved](../software/wal.md) diff --git a/docs/spec/reactors/consensus/proposer-selection.md b/docs/spec/reactors/consensus/proposer-selection.md index b5e0b35afbc..6cb596ec06e 100644 --- a/docs/spec/reactors/consensus/proposer-selection.md +++ b/docs/spec/reactors/consensus/proposer-selection.md @@ -2,45 +2,290 @@ This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer. As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning. -Let denote with `proposer_p(h,r)` a process returned by the Proposer Selection Procedure at the process p, at height h -and round r. Then the Proposer Selection procedure should fulfill the following properties: -`Agreement`: Given a validator set V, and two honest validators, -p and q, for each height h, and each round r, -proposer_p(h,r) = proposer_q(h,r) +At a given block height, the proposer selection algorithm runs with the same validator set at each round . +Between heights, an updated validator set may be specified by the application as part of the ABCIResponses' EndBlock. -`Liveness`: In every consecutive sequence of rounds of size K (K is system parameter), at least a -single round has an honest proposer. +## Requirements for Proposer Selection -`Fairness`: The proposer selection is proportional to the validator voting power, i.e., a validator with more -voting power is selected more frequently, proportional to its power. More precisely, given a set of processes -with the total voting power N, during a sequence of rounds of size N, every process is proposer in a number of rounds -equal to its voting power. +This sections covers the requirements with Rx being mandatory and Ox optional requirements. +The following requirements must be met by the Proposer Selection procedure: -We now look at a few particular cases to understand better how fairness should be implemented. -If we have 4 processes with the following voting power distribution (p0,4), (p1, 2), (p2, 2), (p3, 2) at some round r, -we have the following sequence of proposer selections in the following rounds: +#### R1: Determinism +Given a validator set `V`, and two honest validators `p` and `q`, for each height `h` and each round `r` the following must hold: -`p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, etc` + `proposer_p(h,r) = proposer_q(h,r)` -Let consider now the following scenario where a total voting power of faulty processes is aggregated in a single process -p0: (p0,3), (p1, 1), (p2, 1), (p3, 1), (p4, 1), (p5, 1), (p6, 1), (p7, 1). -In this case the sequence of proposer selections looks like this: +where `proposer_p(h,r)` is the proposer returned by the Proposer Selection Procedure at process `p`, at height `h` and round `r`. -`p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, etc` +#### R2: Fairness +Given a validator set with total voting power P and a sequence S of elections. In any sub-sequence of S with length C*P, a validator v must be elected as proposer P/VP(v) times, i.e. with frequency: -In this case, we see that a number of rounds coordinated by a faulty process is proportional to its voting power. -We consider also the case where we have voting power uniformly distributed among processes, i.e., we have 10 processes -each with voting power of 1. And let consider that there are 3 faulty processes with consecutive addresses, -for example the first 3 processes are faulty. Then the sequence looks like this: + f(v) ~ VP(v) / P -`p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, etc` +where C is a tolerance factor for validator set changes with following values: +- C == 1 if there are no validator set changes +- C ~ k when there are validator changes -In this case, we have 3 consecutive rounds with a faulty proposer. -One special case we consider is the case where a single honest process p0 has most of the voting power, for example: -(p0,100), (p1, 2), (p2, 3), (p3, 4). Then the sequence of proposer selection looks like this: +*[this needs more work]* -p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p1, p0, p0, p0, p0, p0, etc +### Basic Algorithm -This basically means that almost all rounds have the same proposer. But in this case, the process p0 has anyway enough -voting power to decide whatever he wants, so the fact that he coordinates almost all rounds seems correct. +At its core, the proposer selection procedure uses a weighted round-robin algorithm. + +A model that gives a good intuition on how/ why the selection algorithm works and it is fair is that of a priority queue. The validators move ahead in this queue according to their voting power (the higher the voting power the faster a validator moves towards the head of the queue). When the algorithm runs the following happens: +- all validators move "ahead" according to their powers: for each validator, increase the priority by the voting power +- first in the queue becomes the proposer: select the validator with highest priority +- move the proposer back in the queue: decrease the proposer's priority by the total voting power + +Notation: +- vset - the validator set +- n - the number of validators +- VP(i) - voting power of validator i +- A(i) - accumulated priority for validator i +- P - total voting power of set +- avg - average of all validator priorities +- prop - proposer + +Simple view at the Selection Algorithm: + +``` + def ProposerSelection (vset): + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P +``` + +### Stable Set + +Consider the validator set: + +Validator | p1| p2 +----------|---|--- +VP | 1 | 3 + +Assuming no validator changes, the following table shows the proposer priority computation over a few runs. Four runs of the selection procedure are shown, starting with the 5th the same values are computed. +Each row shows the priority queue and the process place in it. The proposer is the closest to the head, the rightmost validator. As priorities are updated, the validators move right in the queue. The proposer moves left as its priority is reduced after election. + +|Priority Run | -2| -1| 0 | 1| 2 | 3 | 4 | 5 | Alg step +|--------------- |---|---|---- |---|---- |---|---|---|-------- +| | | |p1,p2| | | | | |Initialized to 0 +|run 1 | | | | p1| | p2| | |A(i)+=VP(i) +| | | p2| | p1| | | | |A(p2)-= P +|run 2 | | | | |p1,p2| | | |A(i)+=VP(i) +| | p1| | | | p2| | | |A(p1)-= P +|run 3 | | p1| | | | | | p2|A(i)+=VP(i) +| | | p1| | p2| | | | |A(p2)-= P +|run 4 | | | p1| | | | p2| |A(i)+=VP(i) +| | | |p1,p2| | | | | |A(p2)-= P + +It can be shown that: +- At the end of each run k+1 the sum of the priorities is the same as at end of run k. If a new set's priorities are initialized to 0 then the sum of priorities will be 0 at each run while there are no changes. +- The max distance between priorites is (n-1) * P. *[formal proof not finished]* + +### Validator Set Changes +Between proposer selection runs the validator set may change. Some changes have implications on the proposer election. + +#### Voting Power Change +Consider again the earlier example and assume that the voting power of p1 is changed to 4: + +Validator | p1| p2 +----------|---| --- +VP | 4 | 3 + +Let's also assume that before this change the proposer priorites were as shown in first row (last run). As it can be seen, the selection could run again, without changes, as before. + +|Priority Run| -2 | -1 | 0 | 1 | 2 | Comment +|--------------| ---|--- |------|--- |--- |-------- +| last run | | p2 | | p1 | |__update VP(p1)__ +| next run | | | | | p2 |A(i)+=VP(i) +| | p1 | | | | p2 |A(p1)-= P + +However, when a validator changes power from a high to a low value, some other validator remain far back in the queue for a long time. This scenario is considered again in the Proposer Priority Range section. + +As before: +- At the end of each run k+1 the sum of the priorities is the same as at run k. +- The max distance between priorites is (n-1) * P. + +#### Validator Removal +Consider a new example with set: + +Validator | p1 | p2 | p3 | +--------- |--- |--- |--- | +VP | 1 | 2 | 3 | + +Let's assume that after the last run the proposer priorities were as shown in first row with their sum being 0. After p2 is removed, at the end of next proposer selection run (penultimate row) the sum of priorities is -2 (minus the priority of the removed process). + +The procedure could continue without modifications. However, after a sufficiently large number of modifications in validator set, the priority values would migrate towards maximum or minimum allowed values causing truncations due to overflow detection. +For this reason, the selection procedure adds another __new step__ that centers the current priority values such that the priority sum remains close to 0. + +|Priority Run |-3 | -2 | -1 | 0 | 1 | 2 | 4 |Comment +|--------------- |--- | ---|--- |--- |--- |--- |---|-------- +| last run |p3 | | | | p1 | p2 | |__remove p2__ +| nextrun | | | | | | | | +| __new step__ | | p3 | | | | p1 | |A(i) -= avg, avg = -1 +| | | | | | p3 | p1 | |A(i)+=VP(i) +| | | | p1 | | p3 | | |A(p1)-= P + +The modified selection algorithm is: + + def ProposerSelection (vset): + + // center priorities around zero + avg = sum(A(i) for i in vset)/len(vset) + for each validator i in vset: + A(i) -= avg + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P + +Observations: +- The sum of priorities is now close to 0. Due to integer division the sum is an integer in (-n, n), where n is the number of validators. + +#### New Validator +When a new validator is added, same problem as the one described for removal appears, the sum of priorities in the new set is not zero. This is fixed with the centering step introduced above. + +One other issue that needs to be addressed is the following. A validator V that has just been elected is moved to the end of the queue. If the validator set is large and/ or other validators have significantly higher power, V will have to wait many runs to be elected. If V removes and re-adds itself to the set, it would make a significant (albeit unfair) "jump" ahead in the queue. + +In order to prevent this, when a new validator is added, its initial priority is set to: + + A(V) = -1.125 * P + +where P is the total voting power of the set including V. + +Curent implementation uses the penalty factor of 1.125 because it provides a small punishment that is efficient to calculate. See [here](https://github.com/tendermint/tendermint/pull/2785#discussion_r235038971) for more details. + +If we consider the validator set where p3 has just been added: + +Validator | p1 | p2 | p3 +----------|--- |--- |--- +VP | 1 | 3 | 8 + +then p3 will start with proposer priority: + + A(p3) = -1.125 * (1 + 3 + 8) ~ -13 + +Note that since current computation uses integer division there is penalty loss when sum of the voting power is less than 8. + +In the next run, p3 will still be ahead in the queue, elected as proposer and moved back in the queue. + +|Priority Run |-13 | -9 | -5 | -2 | -1 | 0 | 1 | 2 | 5 | 6 | 7 |Alg step +|---------------|--- |--- |--- |----|--- |--- |---|---|---|---|---|-------- +|last run | | | | p2 | | | | p1| | | |__add p3__ +| | p3 | | | p2 | | | | p1| | | |A(p3) = -4 +|next run | | p3 | | | | | | p2| | p1| |A(i) -= avg, avg = -4 +| | | | | | p3 | | | | p2| | p1|A(i)+=VP(i) +| | | | p1 | | p3 | | | | p2| | |A(p1)-=P + +### Proposer Priority Range +With the introduction of centering, some interesting cases occur. Low power validators that bind early in a set that includes high power validator(s) benefit from subsequent additions to the set. This is because these early validators run through more right shift operations during centering, operations that increase their priority. + +As an example, consider the set where p2 is added after p1, with priority -1.125 * 80k = -90k. After the selection procedure runs once: + +Validator | p1 | p2 | Comment +----------|-----|---- |--- +VP | 80k | 10 | +A | 0 |-90k | __added p2__ +A |-45k | 45k | __run selection__ + +Then execute the following steps: + +1. Add a new validator p3: + +Validator | p1 | p2 | p3 +----------|-----|--- |---- +VP | 80k | 10 | 10 + +2. Run selection once. The notation '..p'/'p..' means very small deviations compared to column priority. + +|Priority Run | -90k..| -60k | -45k | -15k| 0 | 45k | 75k | 155k | Comment +|--------------|------ |----- |------- |---- |---|---- |----- |------- |--------- +| last run | p3 | | p2 | | | p1 | | | __added p3__ +| next run +| *right_shift*| | p3 | | p2 | | | p1 | | A(i) -= avg,avg=-30k +| | | ..p3| | ..p2| | | | p1 | A(i)+=VP(i) +| | | ..p3| | ..p2| | | p1.. | | A(p1)-=P, P=80k+20 + + +3. Remove p1 and run selection once: + +Validator | p3 | p2 | Comment +----------|----- |---- |-------- +VP | 10 | 10 | +A |-60k |-15k | +A |-22.5k|22.5k| __run selection__ + +At this point, while the total voting power is 20, the distance between priorities is 45k. It will take 4500 runs for p3 to catch up with p2. + +In order to prevent these types of scenarios, the selection algorithm performs scaling of priorities such that the difference between min and max values is smaller than two times the total voting power. + +The modified selection algorithm is: + + def ProposerSelection (vset): + + // scale the priority values + diff = max(A)-min(A) + threshold = 2 * P + if diff > threshold: + scale = diff/threshold + for each validator i in vset: + A(i) = A(i)/scale + + // center priorities around zero + avg = sum(A(i) for i in vset)/len(vset) + for each validator i in vset: + A(i) -= avg + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P + +Observations: +- With this modification, the maximum distance between priorites becomes 2 * P. + +Note also that even during steady state the priority range may increase beyond 2 * P. The scaling introduced here helps to keep the range bounded. + +### Wrinkles + +#### Validator Power Overflow Conditions +The validator voting power is a positive number stored as an int64. When a validator is added the `1.125 * P` computation must not overflow. As a consequence the code handling validator updates (add and update) checks for overflow conditions making sure the total voting power is never larger than the largest int64 `MAX`, with the property that `1.125 * MAX` is still in the bounds of int64. Fatal error is return when overflow condition is detected. + +#### Proposer Priority Overflow/ Underflow Handling +The proposer priority is stored as an int64. The selection algorithm performs additions and subtractions to these values and in the case of overflows and underflows it limits the values to: + + MaxInt64 = 1 << 63 - 1 + MinInt64 = -1 << 63 + +### Requirement Fulfillment Claims +__[R1]__ + +The proposer algorithm is deterministic giving consistent results across executions with same transactions and validator set modifications. +[WIP - needs more detail] + +__[R2]__ + +Given a set of processes with the total voting power P, during a sequence of elections of length P, the number of times any process is selected as proposer is equal to its voting power. The sequence of the P proposers then repeats. If we consider the validator set: + +Validator | p1| p2 +----------|---|--- +VP | 1 | 3 + +With no other changes to the validator set, the current implementation of proposer selection generates the sequence: +`p2, p1, p2, p2, p2, p1, p2, p2,...` or [`p2, p1, p2, p2`]* +A sequence that starts with any circular permutation of the [`p2, p1, p2, p2`] sub-sequence would also provide the same degree of fairness. In fact these circular permutations show in the sliding window (over the generated sequence) of size equal to the length of the sub-sequence. + +Assigning priorities to each validator based on the voting power and updating them at each run ensures the fairness of the proposer selection. In addition, every time a validator is elected as proposer its priority is decreased with the total voting power. + +Intuitively, a process v jumps ahead in the queue at most (max(A) - min(A))/VP(v) times until it reaches the head and is elected. The frequency is then: + + f(v) ~ VP(v)/(max(A)-min(A)) = 1/k * VP(v)/P + +For current implementation, this means v should be proposer at least VP(v) times out of k * P runs, with scaling factor k=2. diff --git a/docs/spec/reactors/mempool/reactor.md b/docs/spec/reactors/mempool/reactor.md index fa25eeb3eae..d349fc7cc23 100644 --- a/docs/spec/reactors/mempool/reactor.md +++ b/docs/spec/reactors/mempool/reactor.md @@ -12,3 +12,11 @@ for details. Sending incorrectly encoded data or data exceeding `maxMsgSize` will result in stopping the peer. + +The mempool will not send a tx back to any peer which it received it from. + +The reactor assigns an `uint16` number for each peer and maintains a map from +p2p.ID to `uint16`. Each mempool transaction carries a list of all the senders +(`[]uint16`). The list is updated every time mempool receives a transaction it +is already seen. `uint16` assumes that a node will never have over 65535 active +peers (0 is reserved for unknown source - e.g. RPC). diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index aa275c7a14e..d19c272fcad 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -127,6 +127,17 @@ max_subscriptions_per_client = 5 # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "10s" +# The name of a file containing certificate that is used to create the HTTPS server. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_cert_file = "" + +# The name of a file containing matching private key that is used to create the HTTPS server. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_key_file = "" + ##### peer to peer configuration options ##### [p2p] diff --git a/libs/autofile/group.go b/libs/autofile/group.go index d1ea0de75aa..ce73466e41d 100644 --- a/libs/autofile/group.go +++ b/libs/autofile/group.go @@ -331,172 +331,6 @@ func (g *Group) NewReader(index int) (*GroupReader, error) { return r, nil } -// Returns -1 if line comes after, 0 if found, 1 if line comes before. -type SearchFunc func(line string) (int, error) - -// Searches for the right file in Group, then returns a GroupReader to start -// streaming lines. -// Returns true if an exact match was found, otherwise returns the next greater -// line that starts with prefix. -// CONTRACT: Caller must close the returned GroupReader -func (g *Group) Search(prefix string, cmp SearchFunc) (*GroupReader, bool, error) { - g.mtx.Lock() - minIndex, maxIndex := g.minIndex, g.maxIndex - g.mtx.Unlock() - // Now minIndex/maxIndex may change meanwhile, - // but it shouldn't be a big deal - // (maybe we'll want to limit scanUntil though) - - for { - curIndex := (minIndex + maxIndex + 1) / 2 - - // Base case, when there's only 1 choice left. - if minIndex == maxIndex { - r, err := g.NewReader(maxIndex) - if err != nil { - return nil, false, err - } - match, err := scanUntil(r, prefix, cmp) - if err != nil { - r.Close() - return nil, false, err - } - return r, match, err - } - - // Read starting roughly at the middle file, - // until we find line that has prefix. - r, err := g.NewReader(curIndex) - if err != nil { - return nil, false, err - } - foundIndex, line, err := scanNext(r, prefix) - r.Close() - if err != nil { - return nil, false, err - } - - // Compare this line to our search query. - val, err := cmp(line) - if err != nil { - return nil, false, err - } - if val < 0 { - // Line will come later - minIndex = foundIndex - } else if val == 0 { - // Stroke of luck, found the line - r, err := g.NewReader(foundIndex) - if err != nil { - return nil, false, err - } - match, err := scanUntil(r, prefix, cmp) - if !match { - panic("Expected match to be true") - } - if err != nil { - r.Close() - return nil, false, err - } - return r, true, err - } else { - // We passed it - maxIndex = curIndex - 1 - } - } - -} - -// Scans and returns the first line that starts with 'prefix' -// Consumes line and returns it. -func scanNext(r *GroupReader, prefix string) (int, string, error) { - for { - line, err := r.ReadLine() - if err != nil { - return 0, "", err - } - if !strings.HasPrefix(line, prefix) { - continue - } - index := r.CurIndex() - return index, line, nil - } -} - -// Returns true iff an exact match was found. -// Pushes line, does not consume it. -func scanUntil(r *GroupReader, prefix string, cmp SearchFunc) (bool, error) { - for { - line, err := r.ReadLine() - if err != nil { - return false, err - } - if !strings.HasPrefix(line, prefix) { - continue - } - val, err := cmp(line) - if err != nil { - return false, err - } - if val < 0 { - continue - } else if val == 0 { - r.PushLine(line) - return true, nil - } else { - r.PushLine(line) - return false, nil - } - } -} - -// Searches backwards for the last line in Group with prefix. -// Scans each file forward until the end to find the last match. -func (g *Group) FindLast(prefix string) (match string, found bool, err error) { - g.mtx.Lock() - minIndex, maxIndex := g.minIndex, g.maxIndex - g.mtx.Unlock() - - r, err := g.NewReader(maxIndex) - if err != nil { - return "", false, err - } - defer r.Close() - - // Open files from the back and read -GROUP_LOOP: - for i := maxIndex; i >= minIndex; i-- { - err := r.SetIndex(i) - if err != nil { - return "", false, err - } - // Scan each line and test whether line matches - for { - line, err := r.ReadLine() - if err == io.EOF { - if found { - return match, found, nil - } - continue GROUP_LOOP - } else if err != nil { - return "", false, err - } - if strings.HasPrefix(line, prefix) { - match = line - found = true - } - if r.CurIndex() > i { - if found { - return match, found, nil - } - continue GROUP_LOOP - } - } - } - - return -} - // GroupInfo holds information about the group. type GroupInfo struct { MinIndex int // index of the first file in the group, including head @@ -654,48 +488,6 @@ func (gr *GroupReader) Read(p []byte) (n int, err error) { } } -// ReadLine reads a line (without delimiter). -// just return io.EOF if no new lines found. -func (gr *GroupReader) ReadLine() (string, error) { - gr.mtx.Lock() - defer gr.mtx.Unlock() - - // From PushLine - if gr.curLine != nil { - line := string(gr.curLine) - gr.curLine = nil - return line, nil - } - - // Open file if not open yet - if gr.curReader == nil { - err := gr.openFile(gr.curIndex) - if err != nil { - return "", err - } - } - - // Iterate over files until line is found - var linePrefix string - for { - bytesRead, err := gr.curReader.ReadBytes('\n') - if err == io.EOF { - // Open the next file - if err1 := gr.openFile(gr.curIndex + 1); err1 != nil { - return "", err1 - } - if len(bytesRead) > 0 && bytesRead[len(bytesRead)-1] == byte('\n') { - return linePrefix + string(bytesRead[:len(bytesRead)-1]), nil - } - linePrefix += string(bytesRead) - continue - } else if err != nil { - return "", err - } - return linePrefix + string(bytesRead[:len(bytesRead)-1]), nil - } -} - // IF index > gr.Group.maxIndex, returns io.EOF // CONTRACT: caller should hold gr.mtx func (gr *GroupReader) openFile(index int) error { @@ -725,20 +517,6 @@ func (gr *GroupReader) openFile(index int) error { return nil } -// PushLine makes the given line the current one, so the next time somebody -// calls ReadLine, this line will be returned. -// panics if called twice without calling ReadLine. -func (gr *GroupReader) PushLine(line string) { - gr.mtx.Lock() - defer gr.mtx.Unlock() - - if gr.curLine == nil { - gr.curLine = []byte(line) - } else { - panic("PushLine failed, already have line") - } -} - // CurIndex returns cursor's file index. func (gr *GroupReader) CurIndex() int { gr.mtx.Lock() @@ -753,32 +531,3 @@ func (gr *GroupReader) SetIndex(index int) error { defer gr.mtx.Unlock() return gr.openFile(index) } - -//-------------------------------------------------------------------------------- - -// A simple SearchFunc that assumes that the marker is of form -// . -// For example, if prefix is '#HEIGHT:', the markers of expected to be of the form: -// -// #HEIGHT:1 -// ... -// #HEIGHT:2 -// ... -func MakeSimpleSearchFunc(prefix string, target int) SearchFunc { - return func(line string) (int, error) { - if !strings.HasPrefix(line, prefix) { - return -1, fmt.Errorf("Marker line did not have prefix: %v", prefix) - } - i, err := strconv.Atoi(line[len(prefix):]) - if err != nil { - return -1, fmt.Errorf("Failed to parse marker line: %v", err.Error()) - } - if target < i { - return 1, nil - } else if target == i { - return 0, nil - } else { - return -1, nil - } - } -} diff --git a/libs/autofile/group_test.go b/libs/autofile/group_test.go index 68870df87c4..c300aba7179 100644 --- a/libs/autofile/group_test.go +++ b/libs/autofile/group_test.go @@ -1,13 +1,9 @@ package autofile import ( - "errors" - "fmt" "io" "io/ioutil" "os" - "strconv" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -106,107 +102,6 @@ func TestCheckHeadSizeLimit(t *testing.T) { destroyTestGroup(t, g) } -func TestSearch(t *testing.T) { - g := createTestGroupWithHeadSizeLimit(t, 10*1000) - - // Create some files in the group that have several INFO lines in them. - // Try to put the INFO lines in various spots. - for i := 0; i < 100; i++ { - // The random junk at the end ensures that this INFO linen - // is equally likely to show up at the end. - _, err := g.Head.Write([]byte(fmt.Sprintf("INFO %v %v\n", i, cmn.RandStr(123)))) - require.NoError(t, err, "Failed to write to head") - g.checkHeadSizeLimit() - for j := 0; j < 10; j++ { - _, err1 := g.Head.Write([]byte(cmn.RandStr(123) + "\n")) - require.NoError(t, err1, "Failed to write to head") - g.checkHeadSizeLimit() - } - } - - // Create a search func that searches for line - makeSearchFunc := func(target int) SearchFunc { - return func(line string) (int, error) { - parts := strings.Split(line, " ") - if len(parts) != 3 { - return -1, errors.New("Line did not have 3 parts") - } - i, err := strconv.Atoi(parts[1]) - if err != nil { - return -1, errors.New("Failed to parse INFO: " + err.Error()) - } - if target < i { - return 1, nil - } else if target == i { - return 0, nil - } else { - return -1, nil - } - } - } - - // Now search for each number - for i := 0; i < 100; i++ { - gr, match, err := g.Search("INFO", makeSearchFunc(i)) - require.NoError(t, err, "Failed to search for line, tc #%d", i) - assert.True(t, match, "Expected Search to return exact match, tc #%d", i) - line, err := gr.ReadLine() - require.NoError(t, err, "Failed to read line after search, tc #%d", i) - if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", i)) { - t.Fatalf("Failed to get correct line, tc #%d", i) - } - // Make sure we can continue to read from there. - cur := i + 1 - for { - line, err := gr.ReadLine() - if err == io.EOF { - if cur == 99+1 { - // OK! - break - } else { - t.Fatalf("Got EOF after the wrong INFO #, tc #%d", i) - } - } else if err != nil { - t.Fatalf("Error reading line, tc #%d, err:\n%s", i, err) - } - if !strings.HasPrefix(line, "INFO ") { - continue - } - if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", cur)) { - t.Fatalf("Unexpected INFO #. Expected %v got:\n%v, tc #%d", cur, line, i) - } - cur++ - } - gr.Close() - } - - // Now search for something that is too small. - // We should get the first available line. - { - gr, match, err := g.Search("INFO", makeSearchFunc(-999)) - require.NoError(t, err, "Failed to search for line") - assert.False(t, match, "Expected Search to not return exact match") - line, err := gr.ReadLine() - require.NoError(t, err, "Failed to read line after search") - if !strings.HasPrefix(line, "INFO 0 ") { - t.Error("Failed to fetch correct line, which is the earliest INFO") - } - err = gr.Close() - require.NoError(t, err, "Failed to close GroupReader") - } - - // Now search for something that is too large. - // We should get an EOF error. - { - gr, _, err := g.Search("INFO", makeSearchFunc(999)) - assert.Equal(t, io.EOF, err) - assert.Nil(t, gr) - } - - // Cleanup - destroyTestGroup(t, g) -} - func TestRotateFile(t *testing.T) { g := createTestGroupWithHeadSizeLimit(t, 0) g.WriteLine("Line 1") @@ -237,100 +132,6 @@ func TestRotateFile(t *testing.T) { destroyTestGroup(t, g) } -func TestFindLast1(t *testing.T) { - g := createTestGroupWithHeadSizeLimit(t, 0) - - g.WriteLine("Line 1") - g.WriteLine("Line 2") - g.WriteLine("# a") - g.WriteLine("Line 3") - g.FlushAndSync() - g.RotateFile() - g.WriteLine("Line 4") - g.WriteLine("Line 5") - g.WriteLine("Line 6") - g.WriteLine("# b") - g.FlushAndSync() - - match, found, err := g.FindLast("#") - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, "# b", match) - - // Cleanup - destroyTestGroup(t, g) -} - -func TestFindLast2(t *testing.T) { - g := createTestGroupWithHeadSizeLimit(t, 0) - - g.WriteLine("Line 1") - g.WriteLine("Line 2") - g.WriteLine("Line 3") - g.FlushAndSync() - g.RotateFile() - g.WriteLine("# a") - g.WriteLine("Line 4") - g.WriteLine("Line 5") - g.WriteLine("# b") - g.WriteLine("Line 6") - g.FlushAndSync() - - match, found, err := g.FindLast("#") - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, "# b", match) - - // Cleanup - destroyTestGroup(t, g) -} - -func TestFindLast3(t *testing.T) { - g := createTestGroupWithHeadSizeLimit(t, 0) - - g.WriteLine("Line 1") - g.WriteLine("# a") - g.WriteLine("Line 2") - g.WriteLine("# b") - g.WriteLine("Line 3") - g.FlushAndSync() - g.RotateFile() - g.WriteLine("Line 4") - g.WriteLine("Line 5") - g.WriteLine("Line 6") - g.FlushAndSync() - - match, found, err := g.FindLast("#") - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, "# b", match) - - // Cleanup - destroyTestGroup(t, g) -} - -func TestFindLast4(t *testing.T) { - g := createTestGroupWithHeadSizeLimit(t, 0) - - g.WriteLine("Line 1") - g.WriteLine("Line 2") - g.WriteLine("Line 3") - g.FlushAndSync() - g.RotateFile() - g.WriteLine("Line 4") - g.WriteLine("Line 5") - g.WriteLine("Line 6") - g.FlushAndSync() - - match, found, err := g.FindLast("#") - assert.NoError(t, err) - assert.False(t, found) - assert.Empty(t, match) - - // Cleanup - destroyTestGroup(t, g) -} - func TestWrite(t *testing.T) { g := createTestGroupWithHeadSizeLimit(t, 0) diff --git a/libs/common/repeat_timer.go b/libs/common/repeat_timer.go deleted file mode 100644 index 5d049738dd3..00000000000 --- a/libs/common/repeat_timer.go +++ /dev/null @@ -1,232 +0,0 @@ -package common - -import ( - "sync" - "time" -) - -// Used by RepeatTimer the first time, -// and every time it's Reset() after Stop(). -type TickerMaker func(dur time.Duration) Ticker - -// Ticker is a basic ticker interface. -type Ticker interface { - - // Never changes, never closes. - Chan() <-chan time.Time - - // Stopping a stopped Ticker will panic. - Stop() -} - -//---------------------------------------- -// defaultTicker - -var _ Ticker = (*defaultTicker)(nil) - -type defaultTicker time.Ticker - -func defaultTickerMaker(dur time.Duration) Ticker { - ticker := time.NewTicker(dur) - return (*defaultTicker)(ticker) -} - -// Implements Ticker -func (t *defaultTicker) Chan() <-chan time.Time { - return t.C -} - -// Implements Ticker -func (t *defaultTicker) Stop() { - ((*time.Ticker)(t)).Stop() -} - -//---------------------------------------- -// LogicalTickerMaker - -// Construct a TickerMaker that always uses `source`. -// It's useful for simulating a deterministic clock. -func NewLogicalTickerMaker(source chan time.Time) TickerMaker { - return func(dur time.Duration) Ticker { - return newLogicalTicker(source, dur) - } -} - -type logicalTicker struct { - source <-chan time.Time - ch chan time.Time - quit chan struct{} -} - -func newLogicalTicker(source <-chan time.Time, interval time.Duration) Ticker { - lt := &logicalTicker{ - source: source, - ch: make(chan time.Time), - quit: make(chan struct{}), - } - go lt.fireRoutine(interval) - return lt -} - -// We need a goroutine to read times from t.source -// and fire on t.Chan() when `interval` has passed. -func (t *logicalTicker) fireRoutine(interval time.Duration) { - source := t.source - - // Init `lasttime` - lasttime := time.Time{} - select { - case lasttime = <-source: - case <-t.quit: - return - } - // Init `lasttime` end - - for { - select { - case newtime := <-source: - elapsed := newtime.Sub(lasttime) - if interval <= elapsed { - // Block for determinism until the ticker is stopped. - select { - case t.ch <- newtime: - case <-t.quit: - return - } - // Reset timeleft. - // Don't try to "catch up" by sending more. - // "Ticker adjusts the intervals or drops ticks to make up for - // slow receivers" - https://golang.org/pkg/time/#Ticker - lasttime = newtime - } - case <-t.quit: - return // done - } - } -} - -// Implements Ticker -func (t *logicalTicker) Chan() <-chan time.Time { - return t.ch // immutable -} - -// Implements Ticker -func (t *logicalTicker) Stop() { - close(t.quit) // it *should* panic when stopped twice. -} - -//--------------------------------------------------------------------- - -/* - RepeatTimer repeatedly sends a struct{}{} to `.Chan()` after each `dur` - period. (It's good for keeping connections alive.) - A RepeatTimer must be stopped, or it will keep a goroutine alive. -*/ -type RepeatTimer struct { - name string - ch chan time.Time - tm TickerMaker - - mtx sync.Mutex - dur time.Duration - ticker Ticker - quit chan struct{} -} - -// NewRepeatTimer returns a RepeatTimer with a defaultTicker. -func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer { - return NewRepeatTimerWithTickerMaker(name, dur, defaultTickerMaker) -} - -// NewRepeatTimerWithTicker returns a RepeatTimer with the given ticker -// maker. -func NewRepeatTimerWithTickerMaker(name string, dur time.Duration, tm TickerMaker) *RepeatTimer { - var t = &RepeatTimer{ - name: name, - ch: make(chan time.Time), - tm: tm, - dur: dur, - ticker: nil, - quit: nil, - } - t.reset() - return t -} - -// receive ticks on ch, send out on t.ch -func (t *RepeatTimer) fireRoutine(ch <-chan time.Time, quit <-chan struct{}) { - for { - select { - case tick := <-ch: - select { - case t.ch <- tick: - case <-quit: - return - } - case <-quit: // NOTE: `t.quit` races. - return - } - } -} - -func (t *RepeatTimer) Chan() <-chan time.Time { - return t.ch -} - -func (t *RepeatTimer) Stop() { - t.mtx.Lock() - defer t.mtx.Unlock() - - t.stop() -} - -// Wait the duration again before firing. -func (t *RepeatTimer) Reset() { - t.mtx.Lock() - defer t.mtx.Unlock() - - t.reset() -} - -//---------------------------------------- -// Misc. - -// CONTRACT: (non-constructor) caller should hold t.mtx. -func (t *RepeatTimer) reset() { - if t.ticker != nil { - t.stop() - } - t.ticker = t.tm(t.dur) - t.quit = make(chan struct{}) - go t.fireRoutine(t.ticker.Chan(), t.quit) -} - -// CONTRACT: caller should hold t.mtx. -func (t *RepeatTimer) stop() { - if t.ticker == nil { - /* - Similar to the case of closing channels twice: - https://groups.google.com/forum/#!topic/golang-nuts/rhxMiNmRAPk - Stopping a RepeatTimer twice implies that you do - not know whether you are done or not. - If you're calling stop on a stopped RepeatTimer, - you probably have race conditions. - */ - panic("Tried to stop a stopped RepeatTimer") - } - t.ticker.Stop() - t.ticker = nil - /* - From https://golang.org/pkg/time/#Ticker: - "Stop the ticker to release associated resources" - "After Stop, no more ticks will be sent" - So we shouldn't have to do the below. - - select { - case <-t.ch: - // read off channel if there's anything there - default: - } - */ - close(t.quit) -} diff --git a/libs/common/repeat_timer_test.go b/libs/common/repeat_timer_test.go deleted file mode 100644 index f2a7b16c3bb..00000000000 --- a/libs/common/repeat_timer_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package common - -import ( - "sync" - "testing" - "time" - - "github.com/fortytw2/leaktest" - "github.com/stretchr/testify/assert" -) - -func TestDefaultTicker(t *testing.T) { - ticker := defaultTickerMaker(time.Millisecond * 10) - <-ticker.Chan() - ticker.Stop() -} - -func TestRepeatTimer(t *testing.T) { - - ch := make(chan time.Time, 100) - mtx := new(sync.Mutex) - - // tick() fires from start to end - // (exclusive) in milliseconds with incr. - // It locks on mtx, so subsequent calls - // run in series. - tick := func(startMs, endMs, incrMs time.Duration) { - mtx.Lock() - go func() { - for tMs := startMs; tMs < endMs; tMs += incrMs { - lt := time.Time{} - lt = lt.Add(tMs * time.Millisecond) - ch <- lt - } - mtx.Unlock() - }() - } - - // tock consumes Ticker.Chan() events and checks them against the ms in "timesMs". - tock := func(t *testing.T, rt *RepeatTimer, timesMs []int64) { - - // Check against timesMs. - for _, timeMs := range timesMs { - tyme := <-rt.Chan() - sinceMs := tyme.Sub(time.Time{}) / time.Millisecond - assert.Equal(t, timeMs, int64(sinceMs)) - } - - // TODO detect number of running - // goroutines to ensure that - // no other times will fire. - // See https://github.com/tendermint/tendermint/libs/issues/120. - time.Sleep(time.Millisecond * 100) - done := true - select { - case <-rt.Chan(): - done = false - default: - } - assert.True(t, done) - } - - tm := NewLogicalTickerMaker(ch) - rt := NewRepeatTimerWithTickerMaker("bar", time.Second, tm) - - /* NOTE: Useful for debugging deadlocks... - go func() { - time.Sleep(time.Second * 3) - trace := make([]byte, 102400) - count := runtime.Stack(trace, true) - fmt.Printf("Stack of %d bytes: %s\n", count, trace) - }() - */ - - tick(0, 1000, 10) - tock(t, rt, []int64{}) - tick(1000, 2000, 10) - tock(t, rt, []int64{1000}) - tick(2005, 5000, 10) - tock(t, rt, []int64{2005, 3005, 4005}) - tick(5001, 5999, 1) - // Read 5005 instead of 5001 because - // it's 1 second greater than 4005. - tock(t, rt, []int64{5005}) - tick(6000, 7005, 1) - tock(t, rt, []int64{6005}) - tick(7033, 8032, 1) - tock(t, rt, []int64{7033}) - - // After a reset, nothing happens - // until two ticks are received. - rt.Reset() - tock(t, rt, []int64{}) - tick(8040, 8041, 1) - tock(t, rt, []int64{}) - tick(9555, 9556, 1) - tock(t, rt, []int64{9555}) - - // After a stop, nothing more is sent. - rt.Stop() - tock(t, rt, []int64{}) - - // Another stop panics. - assert.Panics(t, func() { rt.Stop() }) -} - -func TestRepeatTimerReset(t *testing.T) { - // check that we are not leaking any go-routines - defer leaktest.Check(t)() - - timer := NewRepeatTimer("test", 20*time.Millisecond) - defer timer.Stop() - - // test we don't receive tick before duration ms. - select { - case <-timer.Chan(): - t.Fatal("did not expect to receive tick") - default: - } - - timer.Reset() - - // test we receive tick after Reset is called - select { - case <-timer.Chan(): - // all good - case <-time.After(40 * time.Millisecond): - t.Fatal("expected to receive tick after reset") - } - - // just random calls - for i := 0; i < 100; i++ { - time.Sleep(time.Duration(RandIntn(40)) * time.Millisecond) - timer.Reset() - } -} diff --git a/libs/common/service.go b/libs/common/service.go index 96a5e632af0..21fb0df3ecb 100644 --- a/libs/common/service.go +++ b/libs/common/service.go @@ -209,7 +209,7 @@ func (bs *BaseService) Wait() { <-bs.quit } -// String implements Servce by returning a string representation of the service. +// String implements Service by returning a string representation of the service. func (bs *BaseService) String() string { return bs.name } diff --git a/mempool/bench_test.go b/mempool/bench_test.go index 8936f8dfbec..0cd394cd60f 100644 --- a/mempool/bench_test.go +++ b/mempool/bench_test.go @@ -26,6 +26,19 @@ func BenchmarkReap(b *testing.B) { } } +func BenchmarkCheckTx(b *testing.B) { + app := kvstore.NewKVStoreApplication() + cc := proxy.NewLocalClientCreator(app) + mempool, cleanup := newMempoolWithApp(cc) + defer cleanup() + + for i := 0; i < b.N; i++ { + tx := make([]byte, 8) + binary.BigEndian.PutUint64(tx, uint64(i)) + mempool.CheckTx(tx, nil) + } +} + func BenchmarkCacheInsertTime(b *testing.B) { cache := newMapTxCache(b.N) txs := make([][]byte, b.N) diff --git a/mempool/cache_test.go b/mempool/cache_test.go new file mode 100644 index 00000000000..ea9f63fd67b --- /dev/null +++ b/mempool/cache_test.go @@ -0,0 +1,101 @@ +package mempool + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/abci/example/kvstore" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" +) + +func TestCacheRemove(t *testing.T) { + cache := newMapTxCache(100) + numTxs := 10 + txs := make([][]byte, numTxs) + for i := 0; i < numTxs; i++ { + // probability of collision is 2**-256 + txBytes := make([]byte, 32) + rand.Read(txBytes) // nolint: gosec + txs[i] = txBytes + cache.Push(txBytes) + // make sure its added to both the linked list and the map + require.Equal(t, i+1, len(cache.map_)) + require.Equal(t, i+1, cache.list.Len()) + } + for i := 0; i < numTxs; i++ { + cache.Remove(txs[i]) + // make sure its removed from both the map and the linked list + require.Equal(t, numTxs-(i+1), len(cache.map_)) + require.Equal(t, numTxs-(i+1), cache.list.Len()) + } +} + +func TestCacheAfterUpdate(t *testing.T) { + app := kvstore.NewKVStoreApplication() + cc := proxy.NewLocalClientCreator(app) + mempool, cleanup := newMempoolWithApp(cc) + defer cleanup() + + // reAddIndices & txsInCache can have elements > numTxsToCreate + // also assumes max index is 255 for convenience + // txs in cache also checks order of elements + tests := []struct { + numTxsToCreate int + updateIndices []int + reAddIndices []int + txsInCache []int + }{ + {1, []int{}, []int{1}, []int{1, 0}}, // adding new txs works + {2, []int{1}, []int{}, []int{1, 0}}, // update doesn't remove tx from cache + {2, []int{2}, []int{}, []int{2, 1, 0}}, // update adds new tx to cache + {2, []int{1}, []int{1}, []int{1, 0}}, // re-adding after update doesn't make dupe + } + for tcIndex, tc := range tests { + for i := 0; i < tc.numTxsToCreate; i++ { + tx := types.Tx{byte(i)} + err := mempool.CheckTx(tx, nil) + require.NoError(t, err) + } + + updateTxs := []types.Tx{} + for _, v := range tc.updateIndices { + tx := types.Tx{byte(v)} + updateTxs = append(updateTxs, tx) + } + mempool.Update(int64(tcIndex), updateTxs, nil, nil) + + for _, v := range tc.reAddIndices { + tx := types.Tx{byte(v)} + _ = mempool.CheckTx(tx, nil) + } + + cache := mempool.cache.(*mapTxCache) + node := cache.list.Front() + counter := 0 + for node != nil { + require.NotEqual(t, len(tc.txsInCache), counter, + "cache larger than expected on testcase %d", tcIndex) + + nodeVal := node.Value.([sha256.Size]byte) + expectedBz := sha256.Sum256([]byte{byte(tc.txsInCache[len(tc.txsInCache)-counter-1])}) + // Reference for reading the errors: + // >>> sha256('\x00').hexdigest() + // '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d' + // >>> sha256('\x01').hexdigest() + // '4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a' + // >>> sha256('\x02').hexdigest() + // 'dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986' + + require.Equal(t, expectedBz, nodeVal, "Equality failed on index %d, tc %d", counter, tcIndex) + counter++ + node = node.Next() + } + require.Equal(t, len(tc.txsInCache), counter, + "cache smaller than expected on testcase %d", tcIndex) + mempool.Flush() + } +} diff --git a/mempool/mempool.go b/mempool/mempool.go index 41ee59cb4c5..a5b14466ac9 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -31,6 +31,14 @@ type PreCheckFunc func(types.Tx) error // transaction doesn't require more gas than available for the block. type PostCheckFunc func(types.Tx, *abci.ResponseCheckTx) error +// TxInfo are parameters that get passed when attempting to add a tx to the +// mempool. +type TxInfo struct { + // We don't use p2p.ID here because it's too big. The gain is to store max 2 + // bytes with each tx to identify the sender rather than 20 bytes. + PeerID uint16 +} + /* The mempool pushes new txs onto the proxyAppConn. @@ -141,6 +149,11 @@ func TxID(tx []byte) string { return fmt.Sprintf("%X", types.Tx(tx).Hash()) } +// txKey is the fixed length array sha256 hash used as the key in maps. +func txKey(tx types.Tx) [sha256.Size]byte { + return sha256.Sum256(tx) +} + // Mempool is an ordered in-memory pool for transactions before they are proposed in a consensus // round. Transaction validity is checked using the CheckTx abci message before the transaction is // added to the pool. The Mempool uses a concurrent list structure for storing transactions that @@ -148,20 +161,30 @@ func TxID(tx []byte) string { type Mempool struct { config *cfg.MempoolConfig - proxyMtx sync.Mutex - proxyAppConn proxy.AppConnMempool - txs *clist.CList // concurrent linked-list of good txs - height int64 // the last block Update()'d to - rechecking int32 // for re-checking filtered txs on Update() - recheckCursor *clist.CElement // next expected response - recheckEnd *clist.CElement // re-checking stops here + proxyMtx sync.Mutex + proxyAppConn proxy.AppConnMempool + txs *clist.CList // concurrent linked-list of good txs + preCheck PreCheckFunc + postCheck PostCheckFunc + + // Track whether we're rechecking txs. + // These are not protected by a mutex and are expected to be mutated + // in serial (ie. by abci responses which are called in serial). + recheckCursor *clist.CElement // next expected response + recheckEnd *clist.CElement // re-checking stops here + + // notify listeners (ie. consensus) when txs are available notifiedTxsAvailable bool txsAvailable chan struct{} // fires once for each height, when the mempool is not empty - preCheck PreCheckFunc - postCheck PostCheckFunc + + // Map for quick access to txs to record sender in CheckTx. + // txsMap: txKey -> CElement + txsMap sync.Map // Atomic integers - txsBytes int64 // see TxsBytes + height int64 // the last block Update()'d to + rechecking int32 // for re-checking filtered txs on Update() + txsBytes int64 // total size of mempool, in bytes // Keep a cache of already-seen txs. // This reduces the pressure on the proxyApp. @@ -201,7 +224,7 @@ func NewMempool( } else { mempool.cache = nopTxCache{} } - proxyAppConn.SetResponseCallback(mempool.resCb) + proxyAppConn.SetResponseCallback(mempool.globalCb) for _, option := range options { option(mempool) } @@ -286,8 +309,8 @@ func (mem *Mempool) TxsBytes() int64 { return atomic.LoadInt64(&mem.txsBytes) } -// FlushAppConn flushes the mempool connection to ensure async resCb calls are -// done e.g. from CheckTx. +// FlushAppConn flushes the mempool connection to ensure async reqResCb calls are +// done. E.g. from CheckTx. func (mem *Mempool) FlushAppConn() error { return mem.proxyAppConn.FlushSync() } @@ -304,6 +327,7 @@ func (mem *Mempool) Flush() { e.DetachPrev() } + mem.txsMap = sync.Map{} _ = atomic.SwapInt64(&mem.txsBytes, 0) } @@ -327,6 +351,13 @@ func (mem *Mempool) TxsWaitChan() <-chan struct{} { // It gets called from another goroutine. // CONTRACT: Either cb will get called, or err returned. func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { + return mem.CheckTxWithInfo(tx, cb, TxInfo{PeerID: UnknownPeerID}) +} + +// CheckTxWithInfo performs the same operation as CheckTx, but with extra meta data about the tx. +// Currently this metadata is the peer who sent it, +// used to prevent the tx from being gossiped back to them. +func (mem *Mempool) CheckTxWithInfo(tx types.Tx, cb func(*abci.Response), txInfo TxInfo) (err error) { mem.proxyMtx.Lock() // use defer to unlock mutex because application (*local client*) might panic defer mem.proxyMtx.Unlock() @@ -357,6 +388,19 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { // CACHE if !mem.cache.Push(tx) { + // Record a new sender for a tx we've already seen. + // Note it's possible a tx is still in the cache but no longer in the mempool + // (eg. after committing a block, txs are removed from mempool but not cache), + // so we only record the sender for txs still in the mempool. + if e, ok := mem.txsMap.Load(txKey(tx)); ok { + memTx := e.(*clist.CElement).Value.(*mempoolTx) + if _, loaded := memTx.senders.LoadOrStore(txInfo.PeerID, true); loaded { + // TODO: consider punishing peer for dups, + // its non-trivial since invalid txs can become valid, + // but they can spam the same tx with little cost to them atm. + } + } + return ErrTxInCache } // END CACHE @@ -379,29 +423,90 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { if err = mem.proxyAppConn.Error(); err != nil { return err } + reqRes := mem.proxyAppConn.CheckTxAsync(tx) - if cb != nil { - reqRes.SetCallback(cb) - } + reqRes.SetCallback(mem.reqResCb(tx, txInfo.PeerID, cb)) return nil } -// ABCI callback function -func (mem *Mempool) resCb(req *abci.Request, res *abci.Response) { +// Global callback that will be called after every ABCI response. +// Having a single global callback avoids needing to set a callback for each request. +// However, processing the checkTx response requires the peerID (so we can track which txs we heard from who), +// and peerID is not included in the ABCI request, so we have to set request-specific callbacks that +// include this information. If we're not in the midst of a recheck, this function will just return, +// so the request specific callback can do the work. +// When rechecking, we don't need the peerID, so the recheck callback happens here. +func (mem *Mempool) globalCb(req *abci.Request, res *abci.Response) { if mem.recheckCursor == nil { - mem.resCbNormal(req, res) - } else { - mem.metrics.RecheckTimes.Add(1) - mem.resCbRecheck(req, res) + return } + + mem.metrics.RecheckTimes.Add(1) + mem.resCbRecheck(req, res) + + // update metrics mem.metrics.Size.Set(float64(mem.Size())) } -func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { +// Request specific callback that should be set on individual reqRes objects +// to incorporate local information when processing the response. +// This allows us to track the peer that sent us this tx, so we can avoid sending it back to them. +// NOTE: alternatively, we could include this information in the ABCI request itself. +// +// External callers of CheckTx, like the RPC, can also pass an externalCb through here that is called +// when all other response processing is complete. +// +// Used in CheckTxWithInfo to record PeerID who sent us the tx. +func (mem *Mempool) reqResCb(tx []byte, peerID uint16, externalCb func(*abci.Response)) func(res *abci.Response) { + return func(res *abci.Response) { + if mem.recheckCursor != nil { + // this should never happen + panic("recheck cursor is not nil in reqResCb") + } + + mem.resCbFirstTime(tx, peerID, res) + + // update metrics + mem.metrics.Size.Set(float64(mem.Size())) + + // passed in by the caller of CheckTx, eg. the RPC + if externalCb != nil { + externalCb(res) + } + } +} + +// Called from: +// - resCbFirstTime (lock not held) if tx is valid +func (mem *Mempool) addTx(memTx *mempoolTx) { + e := mem.txs.PushBack(memTx) + mem.txsMap.Store(txKey(memTx.tx), e) + atomic.AddInt64(&mem.txsBytes, int64(len(memTx.tx))) + mem.metrics.TxSizeBytes.Observe(float64(len(memTx.tx))) +} + +// Called from: +// - Update (lock held) if tx was committed +// - resCbRecheck (lock not held) if tx was invalidated +func (mem *Mempool) removeTx(tx types.Tx, elem *clist.CElement, removeFromCache bool) { + mem.txs.Remove(elem) + elem.DetachPrev() + mem.txsMap.Delete(txKey(tx)) + atomic.AddInt64(&mem.txsBytes, int64(-len(tx))) + + if removeFromCache { + mem.cache.Remove(tx) + } +} + +// callback, which is called after the app checked the tx for the first time. +// +// The case where the app checks the tx for the second and subsequent times is +// handled by the resCbRecheck callback. +func (mem *Mempool) resCbFirstTime(tx []byte, peerID uint16, res *abci.Response) { switch r := res.Value.(type) { case *abci.Response_CheckTx: - tx := req.GetCheckTx().Tx var postCheckErr error if mem.postCheck != nil { postCheckErr = mem.postCheck(tx, r.CheckTx) @@ -412,15 +517,14 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { gasWanted: r.CheckTx.GasWanted, tx: tx, } - mem.txs.PushBack(memTx) - atomic.AddInt64(&mem.txsBytes, int64(len(tx))) + memTx.senders.Store(peerID, true) + mem.addTx(memTx) mem.logger.Info("Added good transaction", "tx", TxID(tx), "res", r, "height", memTx.height, "total", mem.Size(), ) - mem.metrics.TxSizeBytes.Observe(float64(len(tx))) mem.notifyTxsAvailable() } else { // ignore bad transaction @@ -434,6 +538,10 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { } } +// callback, which is called after the app rechecked the tx. +// +// The case where the app checks the tx for the first time is handled by the +// resCbFirstTime callback. func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) { switch r := res.Value.(type) { case *abci.Response_CheckTx: @@ -454,12 +562,8 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) { } else { // Tx became invalidated due to newly committed block. mem.logger.Info("Tx is no longer valid", "tx", TxID(tx), "res", r, "err", postCheckErr) - mem.txs.Remove(mem.recheckCursor) - atomic.AddInt64(&mem.txsBytes, int64(-len(tx))) - mem.recheckCursor.DetachPrev() - - // remove from cache (it might be good later) - mem.cache.Remove(tx) + // NOTE: we remove tx from the cache because it might be good later + mem.removeTx(tx, mem.recheckCursor, true) } if mem.recheckCursor == mem.recheckEnd { mem.recheckCursor = nil @@ -627,12 +731,9 @@ func (mem *Mempool) removeTxs(txs types.Txs) []types.Tx { memTx := e.Value.(*mempoolTx) // Remove the tx if it's already in a block. if _, ok := txsMap[string(memTx.tx)]; ok { - // remove from clist - mem.txs.Remove(e) - atomic.AddInt64(&mem.txsBytes, int64(-len(memTx.tx))) - e.DetachPrev() - // NOTE: we don't remove committed txs from the cache. + mem.removeTx(memTx.tx, e, false) + continue } txsLeft = append(txsLeft, memTx.tx) @@ -650,7 +751,7 @@ func (mem *Mempool) recheckTxs(txs []types.Tx) { mem.recheckEnd = mem.txs.Back() // Push txs to proxyAppConn - // NOTE: resCb() may be called concurrently. + // NOTE: globalCb may be called concurrently. for _, tx := range txs { mem.proxyAppConn.CheckTxAsync(tx) } @@ -664,6 +765,10 @@ type mempoolTx struct { height int64 // height that this tx had been validated in gasWanted int64 // amount of gas this tx states it will require tx types.Tx // + + // ids of peers who've sent us this tx (as a map for quick lookups). + // senders: PeerID -> bool + senders sync.Map } // Height returns the height for this transaction @@ -679,13 +784,13 @@ type txCache interface { Remove(tx types.Tx) } -// mapTxCache maintains a cache of transactions. This only stores -// the hash of the tx, due to memory concerns. +// mapTxCache maintains a LRU cache of transactions. This only stores the hash +// of the tx, due to memory concerns. type mapTxCache struct { mtx sync.Mutex size int map_ map[[sha256.Size]byte]*list.Element - list *list.List // to remove oldest tx when cache gets too big + list *list.List } var _ txCache = (*mapTxCache)(nil) @@ -707,14 +812,14 @@ func (cache *mapTxCache) Reset() { cache.mtx.Unlock() } -// Push adds the given tx to the cache and returns true. It returns false if tx -// is already in the cache. +// Push adds the given tx to the cache and returns true. It returns +// false if tx is already in the cache. func (cache *mapTxCache) Push(tx types.Tx) bool { cache.mtx.Lock() defer cache.mtx.Unlock() // Use the tx hash in the cache - txHash := sha256.Sum256(tx) + txHash := txKey(tx) if moved, exists := cache.map_[txHash]; exists { cache.list.MoveToBack(moved) return false @@ -728,15 +833,15 @@ func (cache *mapTxCache) Push(tx types.Tx) bool { cache.list.Remove(popped) } } - cache.list.PushBack(txHash) - cache.map_[txHash] = cache.list.Back() + e := cache.list.PushBack(txHash) + cache.map_[txHash] = e return true } // Remove removes the given tx from the cache. func (cache *mapTxCache) Remove(tx types.Tx) { cache.mtx.Lock() - txHash := sha256.Sum256(tx) + txHash := txKey(tx) popped := cache.map_[txHash] delete(cache.map_, txHash) if popped != nil { diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 5928fbc563c..d5f25396d33 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -6,17 +6,20 @@ import ( "encoding/binary" "fmt" "io/ioutil" + mrand "math/rand" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/abci/example/counter" "github.com/tendermint/tendermint/abci/example/kvstore" + abciserver "github.com/tendermint/tendermint/abci/server" abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" @@ -63,8 +66,9 @@ func ensureFire(t *testing.T, ch <-chan struct{}, timeoutMS int) { } } -func checkTxs(t *testing.T, mempool *Mempool, count int) types.Txs { +func checkTxs(t *testing.T, mempool *Mempool, count int, peerID uint16) types.Txs { txs := make(types.Txs, count) + txInfo := TxInfo{PeerID: peerID} for i := 0; i < count; i++ { txBytes := make([]byte, 20) txs[i] = txBytes @@ -72,7 +76,7 @@ func checkTxs(t *testing.T, mempool *Mempool, count int) types.Txs { if err != nil { t.Error(err) } - if err := mempool.CheckTx(txBytes, nil); err != nil { + if err := mempool.CheckTxWithInfo(txBytes, nil, txInfo); err != nil { // Skip invalid txs. // TestMempoolFilters will fail otherwise. It asserts a number of txs // returned. @@ -92,7 +96,7 @@ func TestReapMaxBytesMaxGas(t *testing.T) { defer cleanup() // Ensure gas calculation behaves as expected - checkTxs(t, mempool, 1) + checkTxs(t, mempool, 1, UnknownPeerID) tx0 := mempool.TxsFront().Value.(*mempoolTx) // assert that kv store has gas wanted = 1. require.Equal(t, app.CheckTx(tx0.tx).GasWanted, int64(1), "KVStore had a gas value neq to 1") @@ -126,7 +130,7 @@ func TestReapMaxBytesMaxGas(t *testing.T) { {20, 20000, 30, 20}, } for tcIndex, tt := range tests { - checkTxs(t, mempool, tt.numTxsToCreate) + checkTxs(t, mempool, tt.numTxsToCreate, UnknownPeerID) got := mempool.ReapMaxBytesMaxGas(tt.maxBytes, tt.maxGas) assert.Equal(t, tt.expectedNumTxs, len(got), "Got %d txs, expected %d, tc #%d", len(got), tt.expectedNumTxs, tcIndex) @@ -167,7 +171,7 @@ func TestMempoolFilters(t *testing.T) { } for tcIndex, tt := range tests { mempool.Update(1, emptyTxArr, tt.preFilter, tt.postFilter) - checkTxs(t, mempool, tt.numTxsToCreate) + checkTxs(t, mempool, tt.numTxsToCreate, UnknownPeerID) require.Equal(t, tt.expectedNumTxs, mempool.Size(), "mempool had the incorrect size, on test case %d", tcIndex) mempool.Flush() } @@ -198,7 +202,7 @@ func TestTxsAvailable(t *testing.T) { ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) // send a bunch of txs, it should only fire once - txs := checkTxs(t, mempool, 100) + txs := checkTxs(t, mempool, 100, UnknownPeerID) ensureFire(t, mempool.TxsAvailable(), timeoutMS) ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) @@ -213,7 +217,7 @@ func TestTxsAvailable(t *testing.T) { ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) // send a bunch more txs. we already fired for this height so it shouldnt fire again - moreTxs := checkTxs(t, mempool, 50) + moreTxs := checkTxs(t, mempool, 50, UnknownPeerID) ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) // now call update with all the txs. it should not fire as there are no txs left @@ -224,7 +228,7 @@ func TestTxsAvailable(t *testing.T) { ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) // send a bunch more txs, it should only fire once - checkTxs(t, mempool, 100) + checkTxs(t, mempool, 100, UnknownPeerID) ensureFire(t, mempool.TxsAvailable(), timeoutMS) ensureNoFire(t, mempool.TxsAvailable(), timeoutMS) } @@ -340,28 +344,6 @@ func TestSerialReap(t *testing.T) { reapCheck(600) } -func TestCacheRemove(t *testing.T) { - cache := newMapTxCache(100) - numTxs := 10 - txs := make([][]byte, numTxs) - for i := 0; i < numTxs; i++ { - // probability of collision is 2**-256 - txBytes := make([]byte, 32) - rand.Read(txBytes) - txs[i] = txBytes - cache.Push(txBytes) - // make sure its added to both the linked list and the map - require.Equal(t, i+1, len(cache.map_)) - require.Equal(t, i+1, cache.list.Len()) - } - for i := 0; i < numTxs; i++ { - cache.Remove(txs[i]) - // make sure its removed from both the map and the linked list - require.Equal(t, numTxs-(i+1), len(cache.map_)) - require.Equal(t, numTxs-(i+1), cache.list.Len()) - } -} - func TestMempoolCloseWAL(t *testing.T) { // 1. Create the temporary directory for mempool and WAL testing. rootDir, err := ioutil.TempDir("", "mempool-test") @@ -530,6 +512,54 @@ func TestMempoolTxsBytes(t *testing.T) { assert.EqualValues(t, 0, mempool.TxsBytes()) } +// This will non-deterministically catch some concurrency failures like +// https://github.com/tendermint/tendermint/issues/3509 +// TODO: all of the tests should probably also run using the remote proxy app +// since otherwise we're not actually testing the concurrency of the mempool here! +func TestMempoolRemoteAppConcurrency(t *testing.T) { + sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", cmn.RandStr(6)) + app := kvstore.NewKVStoreApplication() + cc, server := newRemoteApp(t, sockPath, app) + defer server.Stop() + config := cfg.ResetTestRoot("mempool_test") + mempool, cleanup := newMempoolWithAppAndConfig(cc, config) + defer cleanup() + + // generate small number of txs + nTxs := 10 + txLen := 200 + txs := make([]types.Tx, nTxs) + for i := 0; i < nTxs; i++ { + txs[i] = cmn.RandBytes(txLen) + } + + // simulate a group of peers sending them over and over + N := config.Mempool.Size + maxPeers := 5 + for i := 0; i < N; i++ { + peerID := mrand.Intn(maxPeers) + txNum := mrand.Intn(nTxs) + tx := txs[int(txNum)] + + // this will err with ErrTxInCache many times ... + mempool.CheckTxWithInfo(tx, nil, TxInfo{PeerID: uint16(peerID)}) + } + err := mempool.FlushAppConn() + require.NoError(t, err) +} + +// caller must close server +func newRemoteApp(t *testing.T, addr string, app abci.Application) (clientCreator proxy.ClientCreator, server cmn.Service) { + clientCreator = proxy.NewRemoteClientCreator(addr, "socket", true) + + // Start server + server = abciserver.NewSocketServer(addr, app) + server.SetLogger(log.TestingLogger().With("module", "abci-server")) + if err := server.Start(); err != nil { + t.Fatalf("Error starting socket server: %v", err.Error()) + } + return clientCreator, server +} func checksumIt(data []byte) string { h := sha256.New() h.Write(data) diff --git a/mempool/reactor.go b/mempool/reactor.go index ff87f05067f..e1376b2879e 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -2,14 +2,16 @@ package mempool import ( "fmt" + "math" "reflect" + "sync" "time" amino "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/libs/clist" - "github.com/tendermint/tendermint/libs/log" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/clist" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) @@ -21,13 +23,85 @@ const ( maxTxSize = maxMsgSize - 8 // account for amino overhead of TxMessage peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount + + // UnknownPeerID is the peer ID to use when running CheckTx when there is + // no peer (e.g. RPC) + UnknownPeerID uint16 = 0 + + maxActiveIDs = math.MaxUint16 ) // MempoolReactor handles mempool tx broadcasting amongst peers. +// It maintains a map from peer ID to counter, to prevent gossiping txs to the +// peers you received it from. type MempoolReactor struct { p2p.BaseReactor config *cfg.MempoolConfig Mempool *Mempool + ids *mempoolIDs +} + +type mempoolIDs struct { + mtx sync.RWMutex + peerMap map[p2p.ID]uint16 + nextID uint16 // assumes that a node will never have over 65536 active peers + activeIDs map[uint16]struct{} // used to check if a given peerID key is used, the value doesn't matter +} + +// Reserve searches for the next unused ID and assignes it to the +// peer. +func (ids *mempoolIDs) ReserveForPeer(peer p2p.Peer) { + ids.mtx.Lock() + defer ids.mtx.Unlock() + + curID := ids.nextPeerID() + ids.peerMap[peer.ID()] = curID + ids.activeIDs[curID] = struct{}{} +} + +// nextPeerID returns the next unused peer ID to use. +// This assumes that ids's mutex is already locked. +func (ids *mempoolIDs) nextPeerID() uint16 { + if len(ids.activeIDs) == maxActiveIDs { + panic(fmt.Sprintf("node has maximum %d active IDs and wanted to get one more", maxActiveIDs)) + } + + _, idExists := ids.activeIDs[ids.nextID] + for idExists { + ids.nextID++ + _, idExists = ids.activeIDs[ids.nextID] + } + curID := ids.nextID + ids.nextID++ + return curID +} + +// Reclaim returns the ID reserved for the peer back to unused pool. +func (ids *mempoolIDs) Reclaim(peer p2p.Peer) { + ids.mtx.Lock() + defer ids.mtx.Unlock() + + removedID, ok := ids.peerMap[peer.ID()] + if ok { + delete(ids.activeIDs, removedID) + delete(ids.peerMap, peer.ID()) + } +} + +// GetForPeer returns an ID reserved for the peer. +func (ids *mempoolIDs) GetForPeer(peer p2p.Peer) uint16 { + ids.mtx.RLock() + defer ids.mtx.RUnlock() + + return ids.peerMap[peer.ID()] +} + +func newMempoolIDs() *mempoolIDs { + return &mempoolIDs{ + peerMap: make(map[p2p.ID]uint16), + activeIDs: map[uint16]struct{}{0: {}}, + nextID: 1, // reserve unknownPeerID(0) for mempoolReactor.BroadcastTx + } } // NewMempoolReactor returns a new MempoolReactor with the given config and mempool. @@ -35,6 +109,7 @@ func NewMempoolReactor(config *cfg.MempoolConfig, mempool *Mempool) *MempoolReac memR := &MempoolReactor{ config: config, Mempool: mempool, + ids: newMempoolIDs(), } memR.BaseReactor = *p2p.NewBaseReactor("MempoolReactor", memR) return memR @@ -68,11 +143,13 @@ func (memR *MempoolReactor) GetChannels() []*p2p.ChannelDescriptor { // AddPeer implements Reactor. // It starts a broadcast routine ensuring all txs are forwarded to the given peer. func (memR *MempoolReactor) AddPeer(peer p2p.Peer) { + memR.ids.ReserveForPeer(peer) go memR.broadcastTxRoutine(peer) } // RemovePeer implements Reactor. func (memR *MempoolReactor) RemovePeer(peer p2p.Peer, reason interface{}) { + memR.ids.Reclaim(peer) // broadcast routine checks if peer is gone and returns } @@ -89,7 +166,8 @@ func (memR *MempoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { switch msg := msg.(type) { case *TxMessage: - err := memR.Mempool.CheckTx(msg.Tx, nil) + peerID := memR.ids.GetForPeer(src) + err := memR.Mempool.CheckTxWithInfo(msg.Tx, nil, TxInfo{PeerID: peerID}) if err != nil { memR.Logger.Info("Could not check tx", "tx", TxID(msg.Tx), "err", err) } @@ -110,8 +188,13 @@ func (memR *MempoolReactor) broadcastTxRoutine(peer p2p.Peer) { return } + peerID := memR.ids.GetForPeer(peer) var next *clist.CElement for { + // In case of both next.NextWaitChan() and peer.Quit() are variable at the same time + if !memR.IsRunning() || !peer.IsRunning() { + return + } // This happens because the CElement we were looking at got garbage // collected (removed). That is, .NextWait() returned nil. Go ahead and // start from the beginning. @@ -146,12 +229,15 @@ func (memR *MempoolReactor) broadcastTxRoutine(peer p2p.Peer) { continue } - // send memTx - msg := &TxMessage{Tx: memTx.tx} - success := peer.Send(MempoolChannel, cdc.MustMarshalBinaryBare(msg)) - if !success { - time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) - continue + // ensure peer hasn't already sent us this tx + if _, ok := memTx.senders.Load(peerID); !ok { + // send memTx + msg := &TxMessage{Tx: memTx.tx} + success := peer.Send(MempoolChannel, cdc.MustMarshalBinaryBare(msg)) + if !success { + time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) + continue + } } select { diff --git a/mempool/reactor_test.go b/mempool/reactor_test.go index 51d130187f0..c9cf498098d 100644 --- a/mempool/reactor_test.go +++ b/mempool/reactor_test.go @@ -2,21 +2,21 @@ package mempool import ( "fmt" + "net" "sync" "testing" "time" "github.com/fortytw2/leaktest" + "github.com/go-kit/kit/log/term" "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/go-kit/kit/log/term" - "github.com/tendermint/tendermint/abci/example/kvstore" - "github.com/tendermint/tendermint/libs/log" - cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/mock" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" ) @@ -102,6 +102,12 @@ func _waitForTxs(t *testing.T, wg *sync.WaitGroup, txs types.Txs, reactorIdx int wg.Done() } +// ensure no txs on reactor after some timeout +func ensureNoTxs(t *testing.T, reactor *MempoolReactor, timeout time.Duration) { + time.Sleep(timeout) // wait for the txs in all mempools + assert.Zero(t, reactor.Mempool.Size()) +} + const ( NUM_TXS = 1000 TIMEOUT = 120 * time.Second // ridiculously high because CircleCI is slow @@ -124,10 +130,26 @@ func TestReactorBroadcastTxMessage(t *testing.T) { // send a bunch of txs to the first reactor's mempool // and wait for them all to be received in the others - txs := checkTxs(t, reactors[0].Mempool, NUM_TXS) + txs := checkTxs(t, reactors[0].Mempool, NUM_TXS, UnknownPeerID) waitForTxs(t, txs, reactors) } +func TestReactorNoBroadcastToSender(t *testing.T) { + config := cfg.TestConfig() + const N = 2 + reactors := makeAndConnectMempoolReactors(config, N) + defer func() { + for _, r := range reactors { + r.Stop() + } + }() + + // send a bunch of txs to the first reactor's mempool, claiming it came from peer + // ensure peer gets no txs + checkTxs(t, reactors[0].Mempool, NUM_TXS, 1) + ensureNoTxs(t, reactors[1], 100*time.Millisecond) +} + func TestBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") @@ -169,3 +191,36 @@ func TestBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { // i.e. broadcastTxRoutine finishes when reactor is stopped leaktest.CheckTimeout(t, 10*time.Second)() } + +func TestMempoolIDsBasic(t *testing.T) { + ids := newMempoolIDs() + + peer := mock.NewPeer(net.IP{127, 0, 0, 1}) + + ids.ReserveForPeer(peer) + assert.EqualValues(t, 1, ids.GetForPeer(peer)) + ids.Reclaim(peer) + + ids.ReserveForPeer(peer) + assert.EqualValues(t, 2, ids.GetForPeer(peer)) + ids.Reclaim(peer) +} + +func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { + if testing.Short() { + return + } + + // 0 is already reserved for UnknownPeerID + ids := newMempoolIDs() + + for i := 0; i < maxActiveIDs-1; i++ { + peer := mock.NewPeer(net.IP{127, 0, 0, 1}) + ids.ReserveForPeer(peer) + } + + assert.Panics(t, func() { + peer := mock.NewPeer(net.IP{127, 0, 0, 1}) + ids.ReserveForPeer(peer) + }) +} diff --git a/node/node.go b/node/node.go index 22735792166..b2843ac8a29 100644 --- a/node/node.go +++ b/node/node.go @@ -490,7 +490,7 @@ func NewNode(config *cfg.Config, addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) // Add ourselves to addrbook to prevent dialing ourselves - addrBook.AddOurAddress(nodeInfo.NetAddress()) + addrBook.AddOurAddress(sw.NetAddress()) addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile())) if config.P2P.PexReactor { @@ -499,6 +499,12 @@ func NewNode(config *cfg.Config, &pex.PEXReactorConfig{ Seeds: splitAndTrimEmpty(config.P2P.Seeds, ",", " "), SeedMode: config.P2P.SeedMode, + // See consensus/reactor.go: blocksToContributeToBecomeGoodPeer 10000 + // blocks assuming 10s blocks ~ 28 hours. + // TODO (melekes): make it dynamic based on the actual block latencies + // from the live network. + // https://github.com/tendermint/tendermint/issues/3523 + SeedDisconnectWaitPeriod: 28 * time.Hour, }) pexReactor.SetLogger(logger.With("module", "pex")) sw.AddReactor("PEX", pexReactor) @@ -716,13 +722,24 @@ func (n *Node) startRPC() ([]net.Listener, error) { }) rootHandler = corsMiddleware.Handler(mux) } + if n.config.RPC.IsTLSEnabled() { + go rpcserver.StartHTTPAndTLSServer( + listener, + rootHandler, + n.config.RPC.CertFile(), + n.config.RPC.KeyFile(), + rpcLogger, + config, + ) + } else { + go rpcserver.StartHTTPServer( + listener, + rootHandler, + rpcLogger, + config, + ) + } - go rpcserver.StartHTTPServer( - listener, - rootHandler, - rpcLogger, - config, - ) listeners[i] = listener } diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index c1e90ab76ad..e0ce062ab75 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -95,13 +95,13 @@ type MConnection struct { stopMtx sync.Mutex flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. - pingTimer *cmn.RepeatTimer // send pings periodically + pingTimer *time.Ticker // send pings periodically // close conn if pong is not received in pongTimeout pongTimer *time.Timer pongTimeoutCh chan bool // true - timeout, false - peer sent pong - chStatsTimer *cmn.RepeatTimer // update channel stats periodically + chStatsTimer *time.Ticker // update channel stats periodically created time.Time // time of creation @@ -201,9 +201,9 @@ func (c *MConnection) OnStart() error { return err } c.flushTimer = cmn.NewThrottleTimer("flush", c.config.FlushThrottle) - c.pingTimer = cmn.NewRepeatTimer("ping", c.config.PingInterval) + c.pingTimer = time.NewTicker(c.config.PingInterval) c.pongTimeoutCh = make(chan bool, 1) - c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateStats) + c.chStatsTimer = time.NewTicker(updateStats) c.quitSendRoutine = make(chan struct{}) c.doneSendRoutine = make(chan struct{}) go c.sendRoutine() @@ -401,11 +401,11 @@ FOR_LOOP: // NOTE: flushTimer.Set() must be called every time // something is written to .bufConnWriter. c.flush() - case <-c.chStatsTimer.Chan(): + case <-c.chStatsTimer.C: for _, channel := range c.channels { channel.updateStats() } - case <-c.pingTimer.Chan(): + case <-c.pingTimer.C: c.Logger.Debug("Send Ping") _n, err = cdc.MarshalBinaryLengthPrefixedWriter(c.bufConnWriter, PacketPing{}) if err != nil { diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go deleted file mode 100644 index 57edafc6769..00000000000 --- a/p2p/dummy/peer.go +++ /dev/null @@ -1,100 +0,0 @@ -package dummy - -import ( - "net" - - cmn "github.com/tendermint/tendermint/libs/common" - p2p "github.com/tendermint/tendermint/p2p" - tmconn "github.com/tendermint/tendermint/p2p/conn" -) - -type peer struct { - cmn.BaseService - kv map[string]interface{} -} - -var _ p2p.Peer = (*peer)(nil) - -// NewPeer creates new dummy peer. -func NewPeer() *peer { - p := &peer{ - kv: make(map[string]interface{}), - } - p.BaseService = *cmn.NewBaseService(nil, "peer", p) - - return p -} - -// FlushStop just calls Stop. -func (p *peer) FlushStop() { - p.Stop() -} - -// ID always returns dummy. -func (p *peer) ID() p2p.ID { - return p2p.ID("dummy") -} - -// IsOutbound always returns false. -func (p *peer) IsOutbound() bool { - return false -} - -// IsPersistent always returns false. -func (p *peer) IsPersistent() bool { - return false -} - -// NodeInfo always returns empty node info. -func (p *peer) NodeInfo() p2p.NodeInfo { - return p2p.DefaultNodeInfo{} -} - -// RemoteIP always returns localhost. -func (p *peer) RemoteIP() net.IP { - return net.ParseIP("127.0.0.1") -} - -// Addr always returns tcp://localhost:8800. -func (p *peer) RemoteAddr() net.Addr { - return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8800} -} - -// CloseConn always returns nil. -func (p *peer) CloseConn() error { - return nil -} - -// Status always returns empry connection status. -func (p *peer) Status() tmconn.ConnectionStatus { - return tmconn.ConnectionStatus{} -} - -// Send does not do anything and just returns true. -func (p *peer) Send(byte, []byte) bool { - return true -} - -// TrySend does not do anything and just returns true. -func (p *peer) TrySend(byte, []byte) bool { - return true -} - -// Set records value under key specified in the map. -func (p *peer) Set(key string, value interface{}) { - p.kv[key] = value -} - -// Get returns a value associated with the key. Nil is returned if no value -// found. -func (p *peer) Get(key string) interface{} { - if value, ok := p.kv[key]; ok { - return value - } - return nil -} - -// OriginalAddr always returns nil. -func (p *peer) OriginalAddr() *p2p.NetAddress { - return nil -} diff --git a/p2p/errors.go b/p2p/errors.go index 706150945b8..3650a7a0a8e 100644 --- a/p2p/errors.go +++ b/p2p/errors.go @@ -103,7 +103,7 @@ type ErrSwitchDuplicatePeerID struct { } func (e ErrSwitchDuplicatePeerID) Error() string { - return fmt.Sprintf("Duplicate peer ID %v", e.ID) + return fmt.Sprintf("duplicate peer ID %v", e.ID) } // ErrSwitchDuplicatePeerIP to be raised whena a peer is connecting with a known @@ -113,7 +113,7 @@ type ErrSwitchDuplicatePeerIP struct { } func (e ErrSwitchDuplicatePeerIP) Error() string { - return fmt.Sprintf("Duplicate peer IP %v", e.IP.String()) + return fmt.Sprintf("duplicate peer IP %v", e.IP.String()) } // ErrSwitchConnectToSelf to be raised when trying to connect to itself. @@ -122,7 +122,7 @@ type ErrSwitchConnectToSelf struct { } func (e ErrSwitchConnectToSelf) Error() string { - return fmt.Sprintf("Connect to self: %v", e.Addr) + return fmt.Sprintf("connect to self: %v", e.Addr) } type ErrSwitchAuthenticationFailure struct { @@ -132,7 +132,7 @@ type ErrSwitchAuthenticationFailure struct { func (e ErrSwitchAuthenticationFailure) Error() string { return fmt.Sprintf( - "Failed to authenticate peer. Dialed %v, but got peer with ID %s", + "failed to authenticate peer. Dialed %v, but got peer with ID %s", e.Dialed, e.Got, ) @@ -152,7 +152,7 @@ type ErrNetAddressNoID struct { } func (e ErrNetAddressNoID) Error() string { - return fmt.Sprintf("Address (%s) does not contain ID", e.Addr) + return fmt.Sprintf("address (%s) does not contain ID", e.Addr) } type ErrNetAddressInvalid struct { @@ -161,7 +161,7 @@ type ErrNetAddressInvalid struct { } func (e ErrNetAddressInvalid) Error() string { - return fmt.Sprintf("Invalid address (%s): %v", e.Addr, e.Err) + return fmt.Sprintf("invalid address (%s): %v", e.Addr, e.Err) } type ErrNetAddressLookup struct { @@ -170,5 +170,15 @@ type ErrNetAddressLookup struct { } func (e ErrNetAddressLookup) Error() string { - return fmt.Sprintf("Error looking up host (%s): %v", e.Addr, e.Err) + return fmt.Sprintf("error looking up host (%s): %v", e.Addr, e.Err) +} + +// ErrCurrentlyDialingOrExistingAddress indicates that we're currently +// dialing this address or it belongs to an existing peer. +type ErrCurrentlyDialingOrExistingAddress struct { + Addr string +} + +func (e ErrCurrentlyDialingOrExistingAddress) Error() string { + return fmt.Sprintf("connection with %s has been established or dialed", e.Addr) } diff --git a/p2p/mock/peer.go b/p2p/mock/peer.go new file mode 100644 index 00000000000..bdcf012de28 --- /dev/null +++ b/p2p/mock/peer.go @@ -0,0 +1,68 @@ +package mock + +import ( + "net" + + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/conn" +) + +type Peer struct { + *cmn.BaseService + ip net.IP + id p2p.ID + addr *p2p.NetAddress + kv map[string]interface{} + Outbound, Persistent bool +} + +// NewPeer creates and starts a new mock peer. If the ip +// is nil, random routable address is used. +func NewPeer(ip net.IP) *Peer { + var netAddr *p2p.NetAddress + if ip == nil { + _, netAddr = p2p.CreateRoutableAddr() + } else { + netAddr = p2p.NewNetAddressIPPort(ip, 26656) + } + nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} + netAddr.ID = nodeKey.ID() + mp := &Peer{ + ip: ip, + id: nodeKey.ID(), + addr: netAddr, + kv: make(map[string]interface{}), + } + mp.BaseService = cmn.NewBaseService(nil, "MockPeer", mp) + mp.Start() + return mp +} + +func (mp *Peer) FlushStop() { mp.Stop() } +func (mp *Peer) TrySend(chID byte, msgBytes []byte) bool { return true } +func (mp *Peer) Send(chID byte, msgBytes []byte) bool { return true } +func (mp *Peer) NodeInfo() p2p.NodeInfo { + return p2p.DefaultNodeInfo{ + ID_: mp.addr.ID, + ListenAddr: mp.addr.DialString(), + } +} +func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } +func (mp *Peer) ID() p2p.ID { return mp.id } +func (mp *Peer) IsOutbound() bool { return mp.Outbound } +func (mp *Peer) IsPersistent() bool { return mp.Persistent } +func (mp *Peer) Get(key string) interface{} { + if value, ok := mp.kv[key]; ok { + return value + } + return nil +} +func (mp *Peer) Set(key string, value interface{}) { + mp.kv[key] = value +} +func (mp *Peer) RemoteIP() net.IP { return mp.ip } +func (mp *Peer) SocketAddr() *p2p.NetAddress { return mp.addr } +func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } +func (mp *Peer) CloseConn() error { return nil } diff --git a/p2p/node_info.go b/p2p/node_info.go index 699fd7f1e39..8195471ed71 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -23,14 +23,13 @@ func MaxNodeInfoSize() int { // NodeInfo exposes basic info of a node // and determines if we're compatible. type NodeInfo interface { + ID() ID nodeInfoAddress nodeInfoTransport } -// nodeInfoAddress exposes just the core info of a node. type nodeInfoAddress interface { - ID() ID - NetAddress() *NetAddress + NetAddress() (*NetAddress, error) } // nodeInfoTransport validates a nodeInfo and checks @@ -215,20 +214,9 @@ OUTER_LOOP: // it includes the authenticated peer ID and the self-reported // ListenAddr. Note that the ListenAddr is not authenticated and // may not match that address actually dialed if its an outbound peer. -func (info DefaultNodeInfo) NetAddress() *NetAddress { +func (info DefaultNodeInfo) NetAddress() (*NetAddress, error) { idAddr := IDAddressString(info.ID(), info.ListenAddr) - netAddr, err := NewNetAddressString(idAddr) - if err != nil { - switch err.(type) { - case ErrNetAddressLookup: - // XXX If the peer provided a host name and the lookup fails here - // we're out of luck. - // TODO: use a NetAddress in DefaultNodeInfo - default: - panic(err) // everything should be well formed by now - } - } - return netAddr + return NewNetAddressString(idAddr) } //----------------------------------------------------------- diff --git a/p2p/peer.go b/p2p/peer.go index 73332a2aa8f..fab3b42d46c 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -29,7 +29,7 @@ type Peer interface { NodeInfo() NodeInfo // peer's info Status() tmconn.ConnectionStatus - OriginalAddr() *NetAddress // original address for outbound peers + SocketAddr() *NetAddress // actual address of the socket Send(byte, []byte) bool TrySend(byte, []byte) bool @@ -46,7 +46,7 @@ type peerConn struct { persistent bool conn net.Conn // source connection - originalAddr *NetAddress // nil for inbound connections + socketAddr *NetAddress // cached RemoteIP() ip net.IP @@ -55,14 +55,14 @@ type peerConn struct { func newPeerConn( outbound, persistent bool, conn net.Conn, - originalAddr *NetAddress, + socketAddr *NetAddress, ) peerConn { return peerConn{ - outbound: outbound, - persistent: persistent, - conn: conn, - originalAddr: originalAddr, + outbound: outbound, + persistent: persistent, + conn: conn, + socketAddr: socketAddr, } } @@ -223,13 +223,12 @@ func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo } -// OriginalAddr returns the original address, which was used to connect with -// the peer. Returns nil for inbound peers. -func (p *peer) OriginalAddr() *NetAddress { - if p.peerConn.outbound { - return p.peerConn.originalAddr - } - return nil +// SocketAddr returns the address of the socket. +// For outbound peers, it's the address dialed (after DNS resolution). +// For inbound peers, it's the address returned by the underlying connection +// (not what's reported in the peer's NodeInfo). +func (p *peer) SocketAddr() *NetAddress { + return p.peerConn.socketAddr } // Status returns the peer's ConnectionStatus. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 1d2372fb087..4bacb07d0e1 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -29,7 +29,7 @@ func (mp *mockPeer) IsPersistent() bool { return true } func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } -func (mp *mockPeer) OriginalAddr() *NetAddress { return nil } +func (mp *mockPeer) SocketAddr() *NetAddress { return nil } func (mp *mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } func (mp *mockPeer) CloseConn() error { return nil } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 90be311317c..bf61beb4fa6 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -109,25 +109,27 @@ func testOutboundPeerConn( persistent bool, ourNodePrivKey crypto.PrivKey, ) (peerConn, error) { + + var pc peerConn conn, err := testDial(addr, config) if err != nil { - return peerConn{}, cmn.ErrorWrap(err, "Error creating peer") + return pc, cmn.ErrorWrap(err, "Error creating peer") } - pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) + pc, err = testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) if err != nil { if cerr := conn.Close(); cerr != nil { - return peerConn{}, cmn.ErrorWrap(err, cerr.Error()) + return pc, cmn.ErrorWrap(err, cerr.Error()) } - return peerConn{}, err + return pc, err } // ensure dialed ID matches connection ID if addr.ID != pc.ID() { if cerr := conn.Close(); cerr != nil { - return peerConn{}, cmn.ErrorWrap(err, cerr.Error()) + return pc, cmn.ErrorWrap(err, cerr.Error()) } - return peerConn{}, ErrSwitchAuthenticationFailure{addr, pc.ID()} + return pc, ErrSwitchAuthenticationFailure{addr, pc.ID()} } return pc, nil diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 3cda9ac7473..85dd05248ff 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "fmt" "math" + "math/rand" "net" "sync" "time" @@ -54,7 +55,7 @@ type AddrBook interface { PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress // Mark address - MarkGood(*p2p.NetAddress) + MarkGood(p2p.ID) MarkAttempt(*p2p.NetAddress) MarkBad(*p2p.NetAddress) @@ -65,8 +66,7 @@ type AddrBook interface { // Send a selection of addresses with bias GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddress - // TODO: remove - ListOfKnownAddresses() []*knownAddress + Size() int // Persist to disk Save() @@ -253,7 +253,7 @@ func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress { bookSize := a.size() if bookSize <= 0 { if bookSize < 0 { - a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld) + panic(fmt.Sprintf("Addrbook size %d (new: %d + old: %d) is less than 0", a.nNew+a.nOld, a.nNew, a.nOld)) } return nil } @@ -296,11 +296,11 @@ func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress { // MarkGood implements AddrBook - it marks the peer as good and // moves it into an "old" bucket. -func (a *addrBook) MarkGood(addr *p2p.NetAddress) { +func (a *addrBook) MarkGood(id p2p.ID) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.ID] + ka := a.addrLookup[id] if ka == nil { return } @@ -338,7 +338,7 @@ func (a *addrBook) GetSelection() []*p2p.NetAddress { bookSize := a.size() if bookSize <= 0 { if bookSize < 0 { - a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld) + panic(fmt.Sprintf("Addrbook size %d (new: %d + old: %d) is less than 0", a.nNew+a.nOld, a.nNew, a.nOld)) } return nil } @@ -388,7 +388,7 @@ func (a *addrBook) GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddre bookSize := a.size() if bookSize <= 0 { if bookSize < 0 { - a.Logger.Error("Addrbook size less than 0", "nNew", a.nNew, "nOld", a.nOld) + panic(fmt.Sprintf("Addrbook size %d (new: %d + old: %d) is less than 0", a.nNew+a.nOld, a.nNew, a.nOld)) } return nil } @@ -405,104 +405,14 @@ func (a *addrBook) GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddre bookSize*getSelectionPercent/100) numAddresses = cmn.MinInt(maxGetSelection, numAddresses) - selection := make([]*p2p.NetAddress, numAddresses) - - oldBucketToAddrsMap := make(map[int]map[string]struct{}) - var oldIndex int - newBucketToAddrsMap := make(map[int]map[string]struct{}) - var newIndex int - - // initialize counters used to count old and new added addresses. - // len(oldBucketToAddrsMap) cannot be used as multiple addresses can endup in the same bucket. - var oldAddressesAdded int - var newAddressesAdded int - // number of new addresses that, if possible, should be in the beginning of the selection - numRequiredNewAdd := percentageOfNum(biasTowardsNewAddrs, numAddresses) - - selectionIndex := 0 -ADDRS_LOOP: - for selectionIndex < numAddresses { - // biasedTowardsOldAddrs indicates if the selection can switch to old addresses - biasedTowardsOldAddrs := selectionIndex >= numRequiredNewAdd - // An old addresses is selected if: - // - the bias is for old and old addressees are still available or, - // - there are no new addresses or all new addresses have been selected. - // numAddresses <= a.nOld + a.nNew therefore it is guaranteed that there are enough - // addresses to fill the selection - pickFromOldBucket := - (biasedTowardsOldAddrs && oldAddressesAdded < a.nOld) || - a.nNew == 0 || newAddressesAdded >= a.nNew - - bucket := make(map[string]*knownAddress) - - // loop until we pick a random non-empty bucket - for len(bucket) == 0 { - if pickFromOldBucket { - oldIndex = a.rand.Intn(len(a.bucketsOld)) - bucket = a.bucketsOld[oldIndex] - } else { - newIndex = a.rand.Intn(len(a.bucketsNew)) - bucket = a.bucketsNew[newIndex] - } - } - - // pick a random index - randIndex := a.rand.Intn(len(bucket)) - - // loop over the map to return that index - var selectedAddr *p2p.NetAddress - for _, ka := range bucket { - if randIndex == 0 { - selectedAddr = ka.Addr - break - } - randIndex-- - } - - // if we have selected the address before, restart the loop - // otherwise, record it and continue - if pickFromOldBucket { - if addrsMap, ok := oldBucketToAddrsMap[oldIndex]; ok { - if _, ok = addrsMap[selectedAddr.String()]; ok { - continue ADDRS_LOOP - } - } else { - oldBucketToAddrsMap[oldIndex] = make(map[string]struct{}) - } - oldBucketToAddrsMap[oldIndex][selectedAddr.String()] = struct{}{} - oldAddressesAdded++ - } else { - if addrsMap, ok := newBucketToAddrsMap[newIndex]; ok { - if _, ok = addrsMap[selectedAddr.String()]; ok { - continue ADDRS_LOOP - } - } else { - newBucketToAddrsMap[newIndex] = make(map[string]struct{}) - } - newBucketToAddrsMap[newIndex][selectedAddr.String()] = struct{}{} - newAddressesAdded++ - } - - selection[selectionIndex] = selectedAddr - selectionIndex++ - } - + // if there are no enough old addrs, will choose new addr instead. + numRequiredNewAdd := cmn.MaxInt(percentageOfNum(biasTowardsNewAddrs, numAddresses), numAddresses-a.nOld) + selection := a.randomPickAddresses(bucketTypeNew, numRequiredNewAdd) + selection = append(selection, a.randomPickAddresses(bucketTypeOld, numAddresses-len(selection))...) return selection } -// ListOfKnownAddresses returns the new and old addresses. -func (a *addrBook) ListOfKnownAddresses() []*knownAddress { - a.mtx.Lock() - defer a.mtx.Unlock() - - addrs := []*knownAddress{} - for _, addr := range a.addrLookup { - addrs = append(addrs, addr.copy()) - } - return addrs -} - //------------------------------------------------ // Size returns the number of addresses in the book. @@ -550,8 +460,7 @@ func (a *addrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAd case bucketTypeOld: return a.bucketsOld[bucketIdx] default: - cmn.PanicSanity("Should not happen") - return nil + panic("Invalid bucket type") } } @@ -677,16 +586,16 @@ func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { return ErrAddrBookNilAddr{addr, src} } - if a.routabilityStrict && !addr.Routable() { - return ErrAddrBookNonRoutable{addr} + if !addr.HasID() { + return ErrAddrBookInvalidAddrNoID{addr} } - if !addr.Valid() { - return ErrAddrBookInvalidAddr{addr} + if _, ok := a.privateIDs[addr.ID]; ok { + return ErrAddrBookPrivate{addr} } - if !addr.HasID() { - return ErrAddrBookInvalidAddrNoID{addr} + if _, ok := a.privateIDs[src.ID]; ok { + return ErrAddrBookPrivateSrc{src} } // TODO: we should track ourAddrs by ID and by IP:PORT and refuse both. @@ -694,12 +603,12 @@ func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { return ErrAddrBookSelf{addr} } - if _, ok := a.privateIDs[addr.ID]; ok { - return ErrAddrBookPrivate{addr} + if a.routabilityStrict && !addr.Routable() { + return ErrAddrBookNonRoutable{addr} } - if _, ok := a.privateIDs[src.ID]; ok { - return ErrAddrBookPrivateSrc{src} + if !addr.Valid() { + return ErrAddrBookInvalidAddr{addr} } ka := a.addrLookup[addr.ID] @@ -726,6 +635,44 @@ func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { return nil } +func (a *addrBook) randomPickAddresses(bucketType byte, num int) []*p2p.NetAddress { + var buckets []map[string]*knownAddress + switch bucketType { + case bucketTypeNew: + buckets = a.bucketsNew + case bucketTypeOld: + buckets = a.bucketsOld + default: + panic("unexpected bucketType") + } + total := 0 + for _, bucket := range buckets { + total = total + len(bucket) + } + addresses := make([]*knownAddress, 0, total) + for _, bucket := range buckets { + for _, ka := range bucket { + addresses = append(addresses, ka) + } + } + selection := make([]*p2p.NetAddress, 0, num) + chosenSet := make(map[string]bool, num) + rand.Shuffle(total, func(i, j int) { + addresses[i], addresses[j] = addresses[j], addresses[i] + }) + for _, addr := range addresses { + if chosenSet[addr.Addr.String()] { + continue + } + chosenSet[addr.Addr.String()] = true + selection = append(selection, addr.Addr) + if len(selection) >= num { + return selection + } + } + return selection +} + // Make space in the new buckets by expiring the really bad entries. // If no bad entries are available we remove the oldest. func (a *addrBook) expireNew(bucketIdx int) { diff --git a/p2p/pex/addrbook_test.go b/p2p/pex/addrbook_test.go index 9effa5d0e90..13bac28c866 100644 --- a/p2p/pex/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -41,7 +41,7 @@ func TestAddrBookPickAddress(t *testing.T) { assert.NotNil(t, addr, "expected an address") // pick an address when we only have old address - book.MarkGood(addrSrc.addr) + book.MarkGood(addrSrc.addr.ID) addr = book.PickAddress(0) assert.NotNil(t, addr, "expected an address") addr = book.PickAddress(50) @@ -126,7 +126,7 @@ func TestAddrBookPromoteToOld(t *testing.T) { // Promote half of them for i, addrSrc := range randAddrs { if i%2 == 0 { - book.MarkGood(addrSrc.addr) + book.MarkGood(addrSrc.addr.ID) } } @@ -330,7 +330,7 @@ func TestAddrBookGetSelectionWithBias(t *testing.T) { randAddrsLen := len(randAddrs) for i, addrSrc := range randAddrs { if int((float64(i)/float64(randAddrsLen))*100) >= 20 { - book.MarkGood(addrSrc.addr) + book.MarkGood(addrSrc.addr.ID) } } @@ -435,12 +435,12 @@ func TestPrivatePeers(t *testing.T) { func testAddrBookAddressSelection(t *testing.T, bookSize int) { // generate all combinations of old (m) and new addresses - for nOld := 0; nOld <= bookSize; nOld++ { - nNew := bookSize - nOld - dbgStr := fmt.Sprintf("book of size %d (new %d, old %d)", bookSize, nNew, nOld) + for nBookOld := 0; nBookOld <= bookSize; nBookOld++ { + nBookNew := bookSize - nBookOld + dbgStr := fmt.Sprintf("book of size %d (new %d, old %d)", bookSize, nBookNew, nBookOld) // create book and get selection - book, fname := createAddrBookWithMOldAndNNewAddrs(t, nOld, nNew) + book, fname := createAddrBookWithMOldAndNNewAddrs(t, nBookOld, nBookNew) defer deleteTempFile(fname) addrs := book.GetSelectionWithBias(biasToSelectNewPeers) assert.NotNil(t, addrs, "%s - expected a non-nil selection", dbgStr) @@ -460,27 +460,25 @@ func testAddrBookAddressSelection(t *testing.T, bookSize int) { // Given: // n - num new addrs, m - num old addrs // k - num new addrs expected in the beginning (based on bias %) - // i=min(n, k), aka expFirstNew + // i=min(n, max(k,r-m)), aka expNew // j=min(m, r-i), aka expOld // // We expect this layout: - // indices: 0...i-1 i...i+j-1 i+j...r - // addresses: N0..Ni-1 O0..Oj-1 Ni... + // indices: 0...i-1 i...i+j-1 + // addresses: N0..Ni-1 O0..Oj-1 // // There is at least one partition and at most three. var ( - k = percentageOfNum(biasToSelectNewPeers, nAddrs) - expFirstNew = cmn.MinInt(nNew, k) - expOld = cmn.MinInt(nOld, nAddrs-expFirstNew) - expNew = nAddrs - expOld - expLastNew = expNew - expFirstNew + k = percentageOfNum(biasToSelectNewPeers, nAddrs) + expNew = cmn.MinInt(nNew, cmn.MaxInt(k, nAddrs-nBookOld)) + expOld = cmn.MinInt(nOld, nAddrs-expNew) ) // Verify that the number of old and new addresses are as expected - if nNew < expNew || nNew > expNew { + if nNew != expNew { t.Fatalf("%s - expected new addrs %d, got %d", dbgStr, expNew, nNew) } - if nOld < expOld || nOld > expOld { + if nOld != expOld { t.Fatalf("%s - expected old addrs %d, got %d", dbgStr, expOld, nOld) } @@ -499,15 +497,12 @@ func testAddrBookAddressSelection(t *testing.T, bookSize int) { case expOld == 0: // all new addresses expSeqLens = []int{nAddrs} expSeqTypes = []int{1} - case expFirstNew == 0: // all old addresses + case expNew == 0: // all old addresses expSeqLens = []int{nAddrs} expSeqTypes = []int{2} - case nAddrs-expFirstNew-expOld == 0: // new addresses, old addresses - expSeqLens = []int{expFirstNew, expOld} + case nAddrs-expNew-expOld == 0: // new addresses, old addresses + expSeqLens = []int{expNew, expOld} expSeqTypes = []int{1, 2} - default: // new addresses, old addresses, new addresses - expSeqLens = []int{expFirstNew, expOld, expLastNew} - expSeqTypes = []int{1, 2, 1} } assert.Equal(t, expSeqLens, seqLens, @@ -574,7 +569,7 @@ func createAddrBookWithMOldAndNNewAddrs(t *testing.T, nOld, nNew int) (book *add randAddrs := randNetAddressPairs(t, nOld) for _, addr := range randAddrs { book.AddAddress(addr.addr, addr.src) - book.MarkGood(addr.addr) + book.MarkGood(addr.addr.ID) } randAddrs = randNetAddressPairs(t, nNew) diff --git a/p2p/pex/errors.go b/p2p/pex/errors.go index 1f44ceee7e0..543056af53f 100644 --- a/p2p/pex/errors.go +++ b/p2p/pex/errors.go @@ -30,6 +30,10 @@ func (err ErrAddrBookPrivate) Error() string { return fmt.Sprintf("Cannot add private peer with address %v", err.Addr) } +func (err ErrAddrBookPrivate) PrivateAddr() bool { + return true +} + type ErrAddrBookPrivateSrc struct { Src *p2p.NetAddress } @@ -38,6 +42,10 @@ func (err ErrAddrBookPrivateSrc) Error() string { return fmt.Sprintf("Cannot add peer coming from private peer with address %v", err.Src) } +func (err ErrAddrBookPrivateSrc) PrivateAddr() bool { + return true +} + type ErrAddrBookNilAddr struct { Addr *p2p.NetAddress Src *p2p.NetAddress diff --git a/p2p/pex/file.go b/p2p/pex/file.go index 33fec033695..d4a516850e0 100644 --- a/p2p/pex/file.go +++ b/p2p/pex/file.go @@ -16,16 +16,15 @@ type addrBookJSON struct { } func (a *addrBook) saveToFile(filePath string) { - a.Logger.Info("Saving AddrBook to file", "size", a.Size()) - a.mtx.Lock() defer a.mtx.Unlock() - // Compile Addrs - addrs := []*knownAddress{} + + a.Logger.Info("Saving AddrBook to file", "size", a.size()) + + addrs := make([]*knownAddress, 0, len(a.addrLookup)) for _, ka := range a.addrLookup { addrs = append(addrs, ka) } - aJSON := &addrBookJSON{ Key: a.key, Addrs: addrs, diff --git a/p2p/pex/known_address.go b/p2p/pex/known_address.go index 5673dec1197..acde385bcfb 100644 --- a/p2p/pex/known_address.go +++ b/p2p/pex/known_address.go @@ -33,18 +33,6 @@ func (ka *knownAddress) ID() p2p.ID { return ka.Addr.ID } -func (ka *knownAddress) copy() *knownAddress { - return &knownAddress{ - Addr: ka.Addr, - Src: ka.Src, - Attempts: ka.Attempts, - LastAttempt: ka.LastAttempt, - LastSuccess: ka.LastSuccess, - BucketType: ka.BucketType, - Buckets: ka.Buckets, - } -} - func (ka *knownAddress) isOld() bool { return ka.BucketType == bucketTypeOld } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 01d1d8db588..c24ee983cfc 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -3,7 +3,6 @@ package pex import ( "fmt" "reflect" - "sort" "sync" "time" @@ -35,16 +34,11 @@ const ( // Seed/Crawler constants - // We want seeds to only advertise good peers. Therefore they should wait at - // least as long as we expect it to take for a peer to become good before - // disconnecting. - // see consensus/reactor.go: blocksToContributeToBecomeGoodPeer - // 10000 blocks assuming 1s blocks ~ 2.7 hours. - defaultSeedDisconnectWaitPeriod = 3 * time.Hour + // minTimeBetweenCrawls is a minimum time between attempts to crawl a peer. + minTimeBetweenCrawls = 2 * time.Minute - defaultCrawlPeerInterval = 2 * time.Minute // don't redial for this. TODO: back-off. what for? - - defaultCrawlPeersPeriod = 30 * time.Second // check some peers every this + // check some peers every this + crawlPeerPeriod = 30 * time.Second maxAttemptsToDial = 16 // ~ 35h in total (last attempt - 18h) @@ -77,6 +71,9 @@ type PEXReactor struct { seedAddrs []*p2p.NetAddress attemptsToDial sync.Map // address (string) -> {number of attempts (int), last time dialed (time.Time)} + + // seed/crawled mode fields + crawlPeerInfos map[p2p.ID]crawlPeerInfo } func (r *PEXReactor) minReceiveRequestInterval() time.Duration { @@ -90,6 +87,11 @@ type PEXReactorConfig struct { // Seed/Crawler mode SeedMode bool + // We want seeds to only advertise good peers. Therefore they should wait at + // least as long as we expect it to take for a peer to become good before + // disconnecting. + SeedDisconnectWaitPeriod time.Duration + // Seeds is a list of addresses reactor may use // if it can't connect to peers in the addrbook. Seeds []string @@ -108,6 +110,7 @@ func NewPEXReactor(b AddrBook, config *PEXReactorConfig) *PEXReactor { ensurePeersPeriod: defaultEnsurePeersPeriod, requestsSent: cmn.NewCMap(), lastReceivedRequests: cmn.NewCMap(), + crawlPeerInfos: make(map[p2p.ID]crawlPeerInfo), } r.BaseReactor = *p2p.NewBaseReactor("PEXReactor", r) return r @@ -167,12 +170,18 @@ func (r *PEXReactor) AddPeer(p Peer) { } } else { // inbound peer is its own source - addr := p.NodeInfo().NetAddress() + addr, err := p.NodeInfo().NetAddress() + if err != nil { + r.Logger.Error("Failed to get peer NetAddress", "err", err, "peer", p) + return + } + + // Make it explicit that addr and src are the same for an inbound peer. src := addr // add to book. dont RequestAddrs right away because // we don't trust inbound as much - let ensurePeersRoutine handle it. - err := r.book.AddAddress(addr, src) + err = r.book.AddAddress(addr, src) r.logErrAddrBook(err) } } @@ -309,7 +318,10 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { } r.requestsSent.Delete(id) - srcAddr := src.NodeInfo().NetAddress() + srcAddr, err := src.NodeInfo().NetAddress() + if err != nil { + return err + } for _, netAddr := range addrs { // Validate netAddr. Disconnect from a peer if it sends us invalid data. if netAddr == nil { @@ -363,9 +375,9 @@ func (r *PEXReactor) ensurePeersRoutine() { ) // Randomize first round of communication to avoid thundering herd. - // If no potential peers are present directly start connecting so we guarantee - // swift setup with the help of configured seeds. - if r.hasPotentialPeers() { + // If no peers are present directly start connecting so we guarantee swift + // setup with the help of configured seeds. + if r.nodeHasSomePeersOrDialingAny() { time.Sleep(time.Duration(jitter)) } @@ -493,23 +505,26 @@ func (r *PEXReactor) dialPeer(addr *p2p.NetAddress) { err := r.Switch.DialPeerWithAddress(addr, false) if err != nil { + if _, ok := err.(p2p.ErrCurrentlyDialingOrExistingAddress); ok { + return + } + r.Logger.Error("Dialing failed", "addr", addr, "err", err, "attempts", attempts) - // TODO: detect more "bad peer" scenarios + markAddrInBookBasedOnErr(addr, r.book, err) if _, ok := err.(p2p.ErrSwitchAuthenticationFailure); ok { - r.book.MarkBad(addr) r.attemptsToDial.Delete(addr.DialString()) } else { - r.book.MarkAttempt(addr) // FIXME: if the addr is going to be removed from the addrbook (hard to // tell at this point), we need to Delete it from attemptsToDial, not // record another attempt. // record attempt r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()}) } - } else { - // cleanup any history - r.attemptsToDial.Delete(addr.DialString()) + return } + + // cleanup any history + r.attemptsToDial.Delete(addr.DialString()) } // checkSeeds checks that addresses are well formed. @@ -568,101 +583,92 @@ func (r *PEXReactor) AttemptsToDial(addr *p2p.NetAddress) int { // from peers, except other seed nodes. func (r *PEXReactor) crawlPeersRoutine() { // Do an initial crawl - r.crawlPeers() + r.crawlPeers(r.book.GetSelection()) // Fire periodically - ticker := time.NewTicker(defaultCrawlPeersPeriod) + ticker := time.NewTicker(crawlPeerPeriod) for { select { case <-ticker.C: r.attemptDisconnects() - r.crawlPeers() + r.crawlPeers(r.book.GetSelection()) + r.cleanupCrawlPeerInfos() case <-r.Quit(): return } } } -// hasPotentialPeers indicates if there is a potential peer to connect to, by -// consulting the Switch as well as the AddrBook. -func (r *PEXReactor) hasPotentialPeers() bool { +// nodeHasSomePeersOrDialingAny returns true if the node is connected to some +// peers or dialing them currently. +func (r *PEXReactor) nodeHasSomePeersOrDialingAny() bool { out, in, dial := r.Switch.NumPeers() - - return out+in+dial > 0 && len(r.book.ListOfKnownAddresses()) > 0 + return out+in+dial > 0 } -// crawlPeerInfo handles temporary data needed for the -// network crawling performed during seed/crawler mode. +// crawlPeerInfo handles temporary data needed for the network crawling +// performed during seed/crawler mode. type crawlPeerInfo struct { - // The listening address of a potential peer we learned about - Addr *p2p.NetAddress - - // The last time we attempt to reach this address - LastAttempt time.Time - - // The last time we successfully reached this address - LastSuccess time.Time + Addr *p2p.NetAddress `json:"addr"` + // The last time we crawled the peer or attempted to do so. + LastCrawled time.Time `json:"last_crawled"` } -// oldestFirst implements sort.Interface for []crawlPeerInfo -// based on the LastAttempt field. -type oldestFirst []crawlPeerInfo - -func (of oldestFirst) Len() int { return len(of) } -func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } -func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } +// crawlPeers will crawl the network looking for new peer addresses. +func (r *PEXReactor) crawlPeers(addrs []*p2p.NetAddress) { + now := time.Now() -// getPeersToCrawl returns addresses of potential peers that we wish to validate. -// NOTE: The status information is ordered as described above. -func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { - // TODO: be more selective - addrs := r.book.ListOfKnownAddresses() - of := make(oldestFirst, 0, len(addrs)) for _, addr := range addrs { - if len(addr.ID()) == 0 { - continue // dont use peers without id - } - - of = append(of, crawlPeerInfo{ - Addr: addr.Addr, - LastAttempt: addr.LastAttempt, - LastSuccess: addr.LastSuccess, - }) - } - sort.Sort(of) - return of -} - -// crawlPeers will crawl the network looking for new peer addresses. (once) -func (r *PEXReactor) crawlPeers() { - peerInfos := r.getPeersToCrawl() + peerInfo, ok := r.crawlPeerInfos[addr.ID] - now := time.Now() - // Use addresses we know of to reach additional peers - for _, pi := range peerInfos { - // Do not attempt to connect with peers we recently dialed - if now.Sub(pi.LastAttempt) < defaultCrawlPeerInterval { + // Do not attempt to connect with peers we recently crawled. + if ok && now.Sub(peerInfo.LastCrawled) < minTimeBetweenCrawls { continue } - // Otherwise, attempt to connect with the known address - err := r.Switch.DialPeerWithAddress(pi.Addr, false) + + // Record crawling attempt. + r.crawlPeerInfos[addr.ID] = crawlPeerInfo{ + Addr: addr, + LastCrawled: now, + } + + err := r.Switch.DialPeerWithAddress(addr, false) if err != nil { - r.book.MarkAttempt(pi.Addr) + if _, ok := err.(p2p.ErrCurrentlyDialingOrExistingAddress); ok { + continue + } + + r.Logger.Error("Dialing failed", "addr", addr, "err", err) + markAddrInBookBasedOnErr(addr, r.book, err) continue } - // Ask for more addresses - peer := r.Switch.Peers().Get(pi.Addr.ID) + + peer := r.Switch.Peers().Get(addr.ID) if peer != nil { r.RequestAddrs(peer) } } } +func (r *PEXReactor) cleanupCrawlPeerInfos() { + for id, info := range r.crawlPeerInfos { + // If we did not crawl a peer for 24 hours, it means the peer was removed + // from the addrbook => remove + // + // 10000 addresses / maxGetSelection = 40 cycles to get all addresses in + // the ideal case, + // 40 * crawlPeerPeriod ~ 20 minutes + if time.Since(info.LastCrawled) > 24*time.Hour { + delete(r.crawlPeerInfos, id) + } + } +} + // attemptDisconnects checks if we've been with each peer long enough to disconnect func (r *PEXReactor) attemptDisconnects() { for _, peer := range r.Switch.Peers().List() { - if peer.Status().Duration < defaultSeedDisconnectWaitPeriod { + if peer.Status().Duration < r.config.SeedDisconnectWaitPeriod { continue } if peer.IsPersistent() { @@ -672,6 +678,16 @@ func (r *PEXReactor) attemptDisconnects() { } } +func markAddrInBookBasedOnErr(addr *p2p.NetAddress, book AddrBook, err error) { + // TODO: detect more "bad peer" scenarios + switch err.(type) { + case p2p.ErrSwitchAuthenticationFailure: + book.MarkBad(addr) + default: + book.MarkAttempt(addr) + } +} + //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index 4f4ccb03948..077f07a60f5 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -3,7 +3,6 @@ package pex import ( "fmt" "io/ioutil" - "net" "os" "path/filepath" "testing" @@ -12,14 +11,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/p2p/conn" + "github.com/tendermint/tendermint/p2p/mock" ) var ( @@ -101,7 +96,7 @@ func TestPEXReactorRunning(t *testing.T) { } addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) { - addr := switches[otherSwitchIndex].NodeInfo().NetAddress() + addr := switches[otherSwitchIndex].NetAddress() books[switchIndex].AddAddress(addr, addr) } @@ -132,7 +127,7 @@ func TestPEXReactorReceive(t *testing.T) { r.RequestAddrs(peer) size := book.Size() - addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*p2p.NetAddress{peer.SocketAddr()} msg := cdc.MustMarshalBinaryBare(&pexAddrsMessage{Addrs: addrs}) r.Receive(PexChannel, peer, msg) assert.Equal(t, size+1, book.Size()) @@ -148,7 +143,7 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { sw := createSwitchAndAddReactors(r) sw.SetAddrBook(book) - peer := newMockPeer() + peer := mock.NewPeer(nil) p2p.AddPeerToSwitch(sw, peer) assert.True(t, sw.Peers().Has(peer.ID())) @@ -178,7 +173,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { sw := createSwitchAndAddReactors(r) sw.SetAddrBook(book) - peer := newMockPeer() + peer := mock.NewPeer(nil) p2p.AddPeerToSwitch(sw, peer) assert.True(t, sw.Peers().Has(peer.ID())) @@ -189,7 +184,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { assert.True(t, r.requestsSent.Has(id)) assert.True(t, sw.Peers().Has(peer.ID())) - addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*p2p.NetAddress{peer.SocketAddr()} msg := cdc.MustMarshalBinaryBare(&pexAddrsMessage{Addrs: addrs}) // receive some addrs. should clear the request @@ -209,36 +204,36 @@ func TestCheckSeeds(t *testing.T) { defer os.RemoveAll(dir) // nolint: errcheck // 1. test creating peer with no seeds works - peer := testCreateDefaultPeer(dir, 0) - require.Nil(t, peer.Start()) - peer.Stop() + peerSwitch := testCreateDefaultPeer(dir, 0) + require.Nil(t, peerSwitch.Start()) + peerSwitch.Stop() // 2. create seed seed := testCreateSeed(dir, 1, []*p2p.NetAddress{}, []*p2p.NetAddress{}) // 3. test create peer with online seed works - peer = testCreatePeerWithSeed(dir, 2, seed) - require.Nil(t, peer.Start()) - peer.Stop() + peerSwitch = testCreatePeerWithSeed(dir, 2, seed) + require.Nil(t, peerSwitch.Start()) + peerSwitch.Stop() // 4. test create peer with all seeds having unresolvable DNS fails badPeerConfig := &PEXReactorConfig{ Seeds: []string{"ed3dfd27bfc4af18f67a49862f04cc100696e84d@bad.network.addr:26657", "d824b13cb5d40fa1d8a614e089357c7eff31b670@anotherbad.network.addr:26657"}, } - peer = testCreatePeerWithConfig(dir, 2, badPeerConfig) - require.Error(t, peer.Start()) - peer.Stop() + peerSwitch = testCreatePeerWithConfig(dir, 2, badPeerConfig) + require.Error(t, peerSwitch.Start()) + peerSwitch.Stop() // 5. test create peer with one good seed address succeeds badPeerConfig = &PEXReactorConfig{ Seeds: []string{"ed3dfd27bfc4af18f67a49862f04cc100696e84d@bad.network.addr:26657", "d824b13cb5d40fa1d8a614e089357c7eff31b670@anotherbad.network.addr:26657", - seed.NodeInfo().NetAddress().String()}, + seed.NetAddress().String()}, } - peer = testCreatePeerWithConfig(dir, 2, badPeerConfig) - require.Nil(t, peer.Start()) - peer.Stop() + peerSwitch = testCreatePeerWithConfig(dir, 2, badPeerConfig) + require.Nil(t, peerSwitch.Start()) + peerSwitch.Stop() } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { @@ -268,12 +263,13 @@ func TestConnectionSpeedForPeerReceivedFromSeed(t *testing.T) { defer os.RemoveAll(dir) // nolint: errcheck // 1. create peer - peer := testCreateDefaultPeer(dir, 1) - require.Nil(t, peer.Start()) - defer peer.Stop() + peerSwitch := testCreateDefaultPeer(dir, 1) + require.Nil(t, peerSwitch.Start()) + defer peerSwitch.Stop() // 2. Create seed which knows about the peer - seed := testCreateSeed(dir, 2, []*p2p.NetAddress{peer.NodeInfo().NetAddress()}, []*p2p.NetAddress{peer.NodeInfo().NetAddress()}) + peerAddr := peerSwitch.NetAddress() + seed := testCreateSeed(dir, 2, []*p2p.NetAddress{peerAddr}, []*p2p.NetAddress{peerAddr}) require.Nil(t, seed.Start()) defer seed.Stop() @@ -289,31 +285,41 @@ func TestConnectionSpeedForPeerReceivedFromSeed(t *testing.T) { assertPeersWithTimeout(t, []*p2p.Switch{secondPeer}, 10*time.Millisecond, 1*time.Second, 2) } -func TestPEXReactorCrawlStatus(t *testing.T) { - pexR, book := createReactor(&PEXReactorConfig{SeedMode: true}) +func TestPEXReactorSeedMode(t *testing.T) { + // directory to store address books + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) // nolint: errcheck + + pexR, book := createReactor(&PEXReactorConfig{SeedMode: true, SeedDisconnectWaitPeriod: 10 * time.Millisecond}) defer teardownReactor(book) - // Seed/Crawler mode uses data from the Switch sw := createSwitchAndAddReactors(pexR) sw.SetAddrBook(book) + err = sw.Start() + require.NoError(t, err) + defer sw.Stop() - // Create a peer, add it to the peer set and the addrbook. - peer := p2p.CreateRandomPeer(false) - p2p.AddPeerToSwitch(pexR.Switch, peer) - addr1 := peer.NodeInfo().NetAddress() - pexR.book.AddAddress(addr1, addr1) + assert.Zero(t, sw.Peers().Size()) + + peerSwitch := testCreateDefaultPeer(dir, 1) + require.NoError(t, peerSwitch.Start()) + defer peerSwitch.Stop() - // Add a non-connected address to the book. - _, addr2 := p2p.CreateRoutableAddr() - pexR.book.AddAddress(addr2, addr1) + // 1. Test crawlPeers dials the peer + pexR.crawlPeers([]*p2p.NetAddress{peerSwitch.NetAddress()}) + assert.Equal(t, 1, sw.Peers().Size()) + assert.True(t, sw.Peers().Has(peerSwitch.NodeInfo().ID())) - // Get some peerInfos to crawl - peerInfos := pexR.getPeersToCrawl() + // 2. attemptDisconnects should not disconnect because of wait period + pexR.attemptDisconnects() + assert.Equal(t, 1, sw.Peers().Size()) - // Make sure it has the proper number of elements - assert.Equal(t, 2, len(peerInfos)) + time.Sleep(100 * time.Millisecond) - // TODO: test + // 3. attemptDisconnects should disconnect after wait period + pexR.attemptDisconnects() + assert.Equal(t, 0, sw.Peers().Size()) } // connect a peer to a seed, wait a bit, then stop it. @@ -364,7 +370,7 @@ func TestPEXReactorSeedModeFlushStop(t *testing.T) { reactor := switches[0].Reactors()["pex"].(*PEXReactor) peerID := switches[1].NodeInfo().ID() - err = switches[1].DialPeerWithAddress(switches[0].NodeInfo().NetAddress(), false) + err = switches[1].DialPeerWithAddress(switches[0].NetAddress(), false) assert.NoError(t, err) // sleep up to a second while waiting for the peer to send us a message. @@ -402,7 +408,7 @@ func TestPEXReactorDoesNotAddPrivatePeersToAddrBook(t *testing.T) { pexR.RequestAddrs(peer) size := book.Size() - addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*p2p.NetAddress{peer.SocketAddr()} msg := cdc.MustMarshalBinaryBare(&pexAddrsMessage{Addrs: addrs}) pexR.Receive(PexChannel, peer, msg) assert.Equal(t, size, book.Size()) @@ -418,8 +424,8 @@ func TestPEXReactorDialPeer(t *testing.T) { sw := createSwitchAndAddReactors(pexR) sw.SetAddrBook(book) - peer := newMockPeer() - addr := peer.NodeInfo().NetAddress() + peer := mock.NewPeer(nil) + addr := peer.SocketAddr() assert.Equal(t, 0, pexR.AttemptsToDial(addr)) @@ -444,44 +450,6 @@ func TestPEXReactorDialPeer(t *testing.T) { } } -type mockPeer struct { - *cmn.BaseService - pubKey crypto.PubKey - addr *p2p.NetAddress - outbound, persistent bool -} - -func newMockPeer() mockPeer { - _, netAddr := p2p.CreateRoutableAddr() - mp := mockPeer{ - addr: netAddr, - pubKey: ed25519.GenPrivKey().PubKey(), - } - mp.BaseService = cmn.NewBaseService(nil, "MockPeer", mp) - mp.Start() - return mp -} - -func (mp mockPeer) FlushStop() { mp.Stop() } -func (mp mockPeer) ID() p2p.ID { return mp.addr.ID } -func (mp mockPeer) IsOutbound() bool { return mp.outbound } -func (mp mockPeer) IsPersistent() bool { return mp.persistent } -func (mp mockPeer) NodeInfo() p2p.NodeInfo { - return p2p.DefaultNodeInfo{ - ID_: mp.addr.ID, - ListenAddr: mp.addr.DialString(), - } -} -func (mockPeer) RemoteIP() net.IP { return net.ParseIP("127.0.0.1") } -func (mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } -func (mockPeer) Send(byte, []byte) bool { return false } -func (mockPeer) TrySend(byte, []byte) bool { return false } -func (mockPeer) Set(string, interface{}) {} -func (mockPeer) Get(string) interface{} { return nil } -func (mockPeer) OriginalAddr() *p2p.NetAddress { return nil } -func (mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8800} } -func (mockPeer) CloseConn() error { return nil } - func assertPeersWithTimeout( t *testing.T, switches []*p2p.Switch, @@ -571,7 +539,7 @@ func testCreateSeed(dir string, id int, knownAddrs, srcAddrs []*p2p.NetAddress) book.SetLogger(log.TestingLogger()) for j := 0; j < len(knownAddrs); j++ { book.AddAddress(knownAddrs[j], srcAddrs[j]) - book.MarkGood(knownAddrs[j]) + book.MarkGood(knownAddrs[j].ID) } sw.SetAddrBook(book) @@ -590,7 +558,7 @@ func testCreateSeed(dir string, id int, knownAddrs, srcAddrs []*p2p.NetAddress) // Starting and stopping the peer is left to the caller func testCreatePeerWithSeed(dir string, id int, seed *p2p.Switch) *p2p.Switch { conf := &PEXReactorConfig{ - Seeds: []string{seed.NodeInfo().NetAddress().String()}, + Seeds: []string{seed.NetAddress().String()}, } return testCreatePeerWithConfig(dir, id, conf) } diff --git a/p2p/switch.go b/p2p/switch.go index a07f70ce976..afd7d9656d6 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -6,6 +6,8 @@ import ( "sync" "time" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p/conn" @@ -46,7 +48,7 @@ type AddrBook interface { AddAddress(addr *NetAddress, src *NetAddress) error AddOurAddress(*NetAddress) OurAddress(*NetAddress) bool - MarkGood(*NetAddress) + MarkGood(ID) RemoveAddress(*NetAddress) HasAddress(*NetAddress) bool Save() @@ -86,6 +88,12 @@ type Switch struct { metrics *Metrics } +// NetAddress returns the address the switch is listening on. +func (sw *Switch) NetAddress() *NetAddress { + addr := sw.transport.NetAddress() + return &addr +} + // SwitchOption sets an optional parameter on the Switch. type SwitchOption func(*Switch) @@ -234,21 +242,26 @@ func (sw *Switch) OnStop() { // // NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved. func (sw *Switch) Broadcast(chID byte, msgBytes []byte) chan bool { - successChan := make(chan bool, len(sw.peers.List())) sw.Logger.Debug("Broadcast", "channel", chID, "msgBytes", fmt.Sprintf("%X", msgBytes)) + + peers := sw.peers.List() var wg sync.WaitGroup - for _, peer := range sw.peers.List() { - wg.Add(1) - go func(peer Peer) { + wg.Add(len(peers)) + successChan := make(chan bool, len(peers)) + + for _, peer := range peers { + go func(p Peer) { defer wg.Done() - success := peer.Send(chID, msgBytes) + success := p.Send(chID, msgBytes) successChan <- success }(peer) } + go func() { wg.Wait() close(successChan) }() + return successChan } @@ -284,13 +297,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { sw.stopAndRemovePeer(peer, reason) if peer.IsPersistent() { - addr := peer.OriginalAddr() - if addr == nil { - // FIXME: persistent peers can't be inbound right now. - // self-reported address for inbound persistent peers - addr = peer.NodeInfo().NetAddress() - } - go sw.reconnectToPeer(addr) + go sw.reconnectToPeer(peer.SocketAddr()) } } @@ -334,14 +341,11 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { return } - if sw.IsDialingOrExistingAddress(addr) { - sw.Logger.Debug("Peer connection has been established or dialed while we waiting next try", "addr", addr) - return - } - err := sw.DialPeerWithAddress(addr, true) if err == nil { return // success + } else if _, ok := err.(ErrCurrentlyDialingOrExistingAddress); ok { + return } sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) @@ -360,9 +364,12 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { // sleep an exponentially increasing amount sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) + err := sw.DialPeerWithAddress(addr, true) if err == nil { return // success + } else if _, ok := err.(ErrCurrentlyDialingOrExistingAddress); ok { + return } sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) } @@ -378,13 +385,22 @@ func (sw *Switch) SetAddrBook(addrBook AddrBook) { // like contributed to consensus. func (sw *Switch) MarkPeerAsGood(peer Peer) { if sw.addrBook != nil { - sw.addrBook.MarkGood(peer.NodeInfo().NetAddress()) + sw.addrBook.MarkGood(peer.ID()) } } //--------------------------------------------------------------------- // Dialing +type privateAddr interface { + PrivateAddr() bool +} + +func isPrivateAddr(err error) bool { + te, ok := errors.Cause(err).(privateAddr) + return ok && te.PrivateAddr() +} + // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). // Used to dial peers from config on startup or from unsafe-RPC (trusted sources). // TODO: remove addrBook arg since it's now set on the switch @@ -395,7 +411,7 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b sw.Logger.Error("Error in peer's address", "err", err) } - ourAddr := sw.nodeInfo.NetAddress() + ourAddr := sw.NetAddress() // TODO: this code feels like it's in the wrong place. // The integration tests depend on the addrBook being saved @@ -407,7 +423,11 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b // do not add our address or ID if !netAddr.Same(ourAddr) { if err := addrBook.AddAddress(netAddr, ourAddr); err != nil { - sw.Logger.Error("Can't add peer's address to addrbook", "err", err) + if isPrivateAddr(err) { + sw.Logger.Debug("Won't add peer's address to addrbook", "err", err) + } else { + sw.Logger.Error("Can't add peer's address to addrbook", "err", err) + } } } } @@ -430,15 +450,10 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b sw.randomSleep(0) - if sw.IsDialingOrExistingAddress(addr) { - sw.Logger.Debug("Ignore attempt to connect to an existing peer", "addr", addr) - return - } - err := sw.DialPeerWithAddress(addr, persistent) if err != nil { switch err.(type) { - case ErrSwitchConnectToSelf, ErrSwitchDuplicatePeerID: + case ErrSwitchConnectToSelf, ErrSwitchDuplicatePeerID, ErrCurrentlyDialingOrExistingAddress: sw.Logger.Debug("Error dialing peer", "err", err) default: sw.Logger.Error("Error dialing peer", "err", err) @@ -449,11 +464,20 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b return nil } -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully. -// If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. +// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects +// and authenticates successfully. +// If `persistent == true`, the switch will always try to reconnect to this +// peer if the connection ever fails. +// If we're currently dialing this address or it belongs to an existing peer, +// ErrCurrentlyDialingOrExistingAddress is returned. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) error { + if sw.IsDialingOrExistingAddress(addr) { + return ErrCurrentlyDialingOrExistingAddress{addr.String()} + } + sw.dialing.Set(string(addr.ID), addr) defer sw.dialing.Delete(string(addr.ID)) + return sw.addOutboundPeerWithConfig(addr, sw.config, persistent) } @@ -531,7 +555,7 @@ func (sw *Switch) acceptRoutine() { if in >= sw.config.MaxNumInboundPeers { sw.Logger.Info( "Ignoring inbound connection: already have enough inbound peers", - "address", p.NodeInfo().NetAddress().String(), + "address", p.SocketAddr(), "have", in, "max", sw.config.MaxNumInboundPeers, ) @@ -648,7 +672,7 @@ func (sw *Switch) addPeer(p Peer) error { return err } - p.SetLogger(sw.Logger.With("peer", p.NodeInfo().NetAddress())) + p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) // Handle the shut down case where the switch has stopped but we're // concurrently trying to add a peer. diff --git a/p2p/switch_test.go b/p2p/switch_test.go index d5dd178b671..bf105e0fa23 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -161,10 +161,6 @@ func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, r func TestSwitchFiltersOutItself(t *testing.T) { s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) - // addr := s1.NodeInfo().NetAddress() - - // // add ourselves like we do in node.go#427 - // s1.addrBook.AddOurAddress(addr) // simulate s1 having a public IP by creating a remote peer with the same ID rp := &remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} @@ -498,7 +494,7 @@ func TestSwitchAcceptRoutine(t *testing.T) { rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} remotePeers = append(remotePeers, rp) rp.Start() - c, err := rp.Dial(sw.NodeInfo().NetAddress()) + c, err := rp.Dial(sw.NetAddress()) require.NoError(t, err) // spawn a reading routine to prevent connection from closing go func(c net.Conn) { @@ -517,7 +513,7 @@ func TestSwitchAcceptRoutine(t *testing.T) { // 2. check we close new connections if we already have MaxNumInboundPeers peers rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} rp.Start() - conn, err := rp.Dial(sw.NodeInfo().NetAddress()) + conn, err := rp.Dial(sw.NetAddress()) require.NoError(t, err) // check conn is closed one := make([]byte, 1) @@ -537,6 +533,10 @@ type errorTransport struct { acceptErr error } +func (et errorTransport) NetAddress() NetAddress { + panic("not implemented") +} + func (et errorTransport) Accept(c peerConfig) (Peer, error) { return nil, et.acceptErr } @@ -626,7 +626,7 @@ func (book *addrBookMock) OurAddress(addr *NetAddress) bool { _, ok := book.ourAddrs[addr.String()] return ok } -func (book *addrBookMock) MarkGood(*NetAddress) {} +func (book *addrBookMock) MarkGood(ID) {} func (book *addrBookMock) HasAddress(addr *NetAddress) bool { _, ok := book.addrs[addr.String()] return ok diff --git a/p2p/test_util.go b/p2p/test_util.go index 2d320df8594..f8020924cf1 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -23,7 +23,7 @@ type mockNodeInfo struct { } func (ni mockNodeInfo) ID() ID { return ni.addr.ID } -func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr } +func (ni mockNodeInfo) NetAddress() (*NetAddress, error) { return ni.addr, nil } func (ni mockNodeInfo) Validate() error { return nil } func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil } @@ -35,7 +35,8 @@ func CreateRandomPeer(outbound bool) *peer { addr, netAddr := CreateRoutableAddr() p := &peer{ peerConn: peerConn{ - outbound: outbound, + outbound: outbound, + socketAddr: netAddr, }, nodeInfo: mockNodeInfo{netAddr}, mconn: &conn.MConnection{}, @@ -174,10 +175,15 @@ func MakeSwitch( PrivKey: ed25519.GenPrivKey(), } nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) + addr, err := NewNetAddressString( + IDAddressString(nodeKey.ID(), nodeInfo.(DefaultNodeInfo).ListenAddr), + ) + if err != nil { + panic(err) + } t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) - addr := nodeInfo.NetAddress() if err := t.Listen(*addr); err != nil { panic(err) } @@ -214,7 +220,7 @@ func testPeerConn( cfg *config.P2PConfig, outbound, persistent bool, ourNodePrivKey crypto.PrivKey, - originalAddr *NetAddress, + socketAddr *NetAddress, ) (pc peerConn, err error) { conn := rawConn @@ -231,12 +237,7 @@ func testPeerConn( } // Only the information we already have - return peerConn{ - outbound: outbound, - persistent: persistent, - conn: conn, - originalAddr: originalAddr, - }, nil + return newPeerConn(outbound, persistent, conn, socketAddr), nil } //---------------------------------------------------------------- diff --git a/p2p/transport.go b/p2p/transport.go index d36065ab1c5..ebf77c9f4b8 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -24,6 +24,7 @@ type IPResolver interface { // accept is the container to carry the upgraded connection and NodeInfo from an // asynchronously running routine to the Accept method. type accept struct { + netAddr *NetAddress conn net.Conn nodeInfo NodeInfo err error @@ -47,6 +48,9 @@ type peerConfig struct { // the transport. Each transport is also responsible to filter establishing // peers specific to its domain. type Transport interface { + // Listening address. + NetAddress() NetAddress + // Accept returns a newly connected Peer. Accept(peerConfig) (Peer, error) @@ -115,6 +119,7 @@ func MultiplexTransportResolver(resolver IPResolver) MultiplexTransportOption { // MultiplexTransport accepts and dials tcp connections and upgrades them to // multiplexed peers. type MultiplexTransport struct { + netAddr NetAddress listener net.Listener acceptc chan accept @@ -161,6 +166,11 @@ func NewMultiplexTransport( } } +// NetAddress implements Transport. +func (mt *MultiplexTransport) NetAddress() NetAddress { + return mt.netAddr +} + // Accept implements Transport. func (mt *MultiplexTransport) Accept(cfg peerConfig) (Peer, error) { select { @@ -173,7 +183,7 @@ func (mt *MultiplexTransport) Accept(cfg peerConfig) (Peer, error) { cfg.outbound = false - return mt.wrapPeer(a.conn, a.nodeInfo, cfg, nil), nil + return mt.wrapPeer(a.conn, a.nodeInfo, cfg, a.netAddr), nil case <-mt.closec: return nil, ErrTransportClosed{} } @@ -224,6 +234,7 @@ func (mt *MultiplexTransport) Listen(addr NetAddress) error { return err } + mt.netAddr = addr mt.listener = ln go mt.acceptPeers() @@ -258,15 +269,21 @@ func (mt *MultiplexTransport) acceptPeers() { var ( nodeInfo NodeInfo secretConn *conn.SecretConnection + netAddr *NetAddress ) err := mt.filterConn(c) if err == nil { secretConn, nodeInfo, err = mt.upgrade(c, nil) + if err == nil { + addr := c.RemoteAddr() + id := PubKeyToID(secretConn.RemotePubKey()) + netAddr = NewNetAddress(id, addr) + } } select { - case mt.acceptc <- accept{secretConn, nodeInfo, err}: + case mt.acceptc <- accept{netAddr, secretConn, nodeInfo, err}: // Make the upgraded peer available. case <-mt.closec: // Give up if the transport was closed. @@ -347,7 +364,7 @@ func (mt *MultiplexTransport) upgrade( if err != nil { return nil, nil, ErrRejected{ conn: c, - err: fmt.Errorf("secrect conn failed: %v", err), + err: fmt.Errorf("secret conn failed: %v", err), isAuthFailure: true, } } @@ -360,7 +377,7 @@ func (mt *MultiplexTransport) upgrade( conn: c, id: connID, err: fmt.Errorf( - "conn.ID (%v) dialed ID (%v) missmatch", + "conn.ID (%v) dialed ID (%v) mismatch", connID, dialedID, ), @@ -392,7 +409,7 @@ func (mt *MultiplexTransport) upgrade( conn: c, id: connID, err: fmt.Errorf( - "conn.ID (%v) NodeInfo.ID (%v) missmatch", + "conn.ID (%v) NodeInfo.ID (%v) mismatch", connID, nodeInfo.ID(), ), @@ -426,14 +443,14 @@ func (mt *MultiplexTransport) wrapPeer( c net.Conn, ni NodeInfo, cfg peerConfig, - dialedAddr *NetAddress, + socketAddr *NetAddress, ) Peer { peerConn := newPeerConn( cfg.outbound, cfg.persistent, c, - dialedAddr, + socketAddr, ) p := newPeer( diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 81f9d1b8e9a..35fd9c66b97 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/p2p/conn" ) @@ -142,43 +144,23 @@ func TestTransportMultiplexConnFilterTimeout(t *testing.T) { func TestTransportMultiplexAcceptMultiple(t *testing.T) { mt := testSetupMultiplexTransport(t) + id, addr := mt.nodeKey.ID(), mt.listener.Addr().String() + laddr, err := NewNetAddressStringWithOptionalID(IDAddressString(id, addr)) + require.NoError(t, err) var ( - seed = rand.New(rand.NewSource(time.Now().UnixNano())) - errc = make(chan error, seed.Intn(64)+64) + seed = rand.New(rand.NewSource(time.Now().UnixNano())) + nDialers = seed.Intn(64) + 64 + errc = make(chan error, nDialers) ) // Setup dialers. - for i := 0; i < cap(errc); i++ { - go func() { - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(PubKeyToID(pv.PubKey()), defaultNodeName), - NodeKey{ - PrivKey: pv, - }, - ) - ) - addr, err := NewNetAddressStringWithOptionalID(IDAddressString(mt.nodeKey.ID(), mt.listener.Addr().String())) - if err != nil { - errc <- err - return - } - - _, err = dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } - - // Signal that the connection was established. - errc <- nil - }() + for i := 0; i < nDialers; i++ { + go testDialer(*laddr, errc) } // Catch connection errors. - for i := 0; i < cap(errc); i++ { + for i := 0; i < nDialers; i++ { if err := <-errc; err != nil { t.Fatal(err) } @@ -216,6 +198,27 @@ func TestTransportMultiplexAcceptMultiple(t *testing.T) { } } +func testDialer(dialAddr NetAddress, errc chan error) { + var ( + pv = ed25519.GenPrivKey() + dialer = newMultiplexTransport( + testNodeInfo(PubKeyToID(pv.PubKey()), defaultNodeName), + NodeKey{ + PrivKey: pv, + }, + ) + ) + + _, err := dialer.Dial(dialAddr, peerConfig{}) + if err != nil { + errc <- err + return + } + + // Signal that the connection was established. + errc <- nil +} + func TestTransportMultiplexAcceptNonBlocking(t *testing.T) { mt := testSetupMultiplexTransport(t) @@ -591,6 +594,7 @@ func TestTransportHandshake(t *testing.T) { } } +// create listener func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { var ( pv = ed25519.GenPrivKey() diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index e982292e7d4..55c7b4f17a2 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -52,11 +52,7 @@ func NewHTTP(remote, wsEndpoint string) *HTTP { } } -var ( - _ Client = (*HTTP)(nil) - _ NetworkClient = (*HTTP)(nil) - _ EventsClient = (*HTTP)(nil) -) +var _ Client = (*HTTP)(nil) func (c *HTTP) Status() (*ctypes.ResultStatus, error) { result := new(ctypes.ResultStatus) diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 605d84ba25c..8f9ed937224 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -72,17 +72,15 @@ type StatusClient interface { type Client interface { cmn.Service ABCIClient - SignClient + EventsClient HistoryClient + NetworkClient + SignClient StatusClient - EventsClient } // NetworkClient is general info about the network state. May not // be needed usually. -// -// Not included in the Client interface, but generally implemented -// by concrete implementations. type NetworkClient interface { NetInfo() (*ctypes.ResultNetInfo, error) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 976c9892a8a..d57ced311cf 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -58,11 +58,7 @@ func NewLocal(node *nm.Node) *Local { } } -var ( - _ Client = (*Local)(nil) - _ NetworkClient = (*Local)(nil) - _ EventsClient = (*Local)(nil) -) +var _ Client = (*Local)(nil) // SetLogger allows to set a logger on the client. func (c *Local) SetLogger(l log.Logger) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 9c0eb75b828..c2e19b6d4fa 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -108,6 +108,18 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo(&rpctypes.Context{}) } +func (c Client) ConsensusState() (*ctypes.ResultConsensusState, error) { + return core.ConsensusState(&rpctypes.Context{}) +} + +func (c Client) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + return core.DumpConsensusState(&rpctypes.Context{}) +} + +func (c Client) Health() (*ctypes.ResultHealth, error) { + return core.Health(&rpctypes.Context{}) +} + func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return core.UnsafeDialSeeds(&rpctypes.Context{}, seeds) } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index b8a91f107b5..ad23a461c24 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -10,6 +10,8 @@ import ( // Get the validator set at the given block height. // If no height is provided, it will fetch the current validator set. +// Note the validators are sorted by their address - this is the canonical +// order for the validators in the set as used in computing their Merkle root. // // ```shell // curl 'localhost:26657/validators' @@ -216,7 +218,7 @@ func DumpConsensusState(ctx *rpctypes.Context) (*ctypes.ResultDumpConsensusState } peerStates[i] = ctypes.PeerStateInfo{ // Peer basic info. - NodeAddress: peer.NodeInfo().NetAddress().String(), + NodeAddress: peer.SocketAddr().String(), // Peer consensus state. PeerState: peerStateJSON, } diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go index 97b8dfe7b7e..cfa26e89c67 100644 --- a/rpc/lib/client/http_client.go +++ b/rpc/lib/client/http_client.go @@ -74,7 +74,9 @@ func makeHTTPClient(remoteAddr string) (string, *http.Client) { protocol, address, dialer := makeHTTPDialer(remoteAddr) return protocol + "://" + address, &http.Client{ Transport: &http.Transport{ - Dial: dialer, + // Set to true to prevent GZIP-bomb DoS attacks + DisableCompression: true, + Dial: dialer, }, } } diff --git a/scripts/release_management/README.md b/scripts/release_management/README.md new file mode 100644 index 00000000000..e92f1ccf657 --- /dev/null +++ b/scripts/release_management/README.md @@ -0,0 +1,65 @@ +# Release management scripts + +## Overview +The scripts in this folder are used for release management in CircleCI. Although the scripts are fully configurable using input parameters, +the default settings were modified to accommodate CircleCI execution. + +# Build scripts +These scripts help during the build process. They prepare the release files. + +## bump-semver.py +Bumps the semantic version of the input `--version`. Versions are expected in vMAJOR.MINOR.PATCH format or vMAJOR.MINOR format. + +In vMAJOR.MINOR format, the result will be patch version 0 of that version, for example `v1.2 -> v1.2.0`. + +In vMAJOR.MINOR.PATCH format, the result will be a bumped PATCH version, for example `v1.2.3 -> v1.2.4`. + +If the PATCH number contains letters, it is considered a development version, in which case, the result is the non-development version of that number. +The patch number will not be bumped, only the "-dev" or similar additional text will be removed. For example: `v1.2.6-rc1 -> v1.2.6`. + +## zip-file.py +Specialized ZIP command for release management. Special features: +1. Uses Python ZIP libaries, so the `zip` command does not need to be installed. +1. Can only zip one file. +1. Optionally gets file version, Go OS and architecture. +1. By default all inputs and output is formatted exactly how CircleCI needs it. + +By default, the command will try to ZIP the file at `build/tendermint_${GOOS}_${GOARCH}`. +This can be changed with the `--file` input parameter. + +By default, the command will output the ZIP file to `build/tendermint_${CIRCLE_TAG}_${GOOS}_${GOARCH}.zip`. +This can be changed with the `--destination` (folder), `--version`, `--goos` and `--goarch` input parameters respectively. + +## sha-files.py +Specialized `shasum` command for release management. Special features: +1. Reads all ZIP files in the given folder. +1. By default all inputs and output is formatted exactly how CircleCI needs it. + +By default, the command will look up all ZIP files in the `build/` folder. + +By default, the command will output results into the `build/SHA256SUMS` file. + +# GitHub management +Uploading build results to GitHub requires at least these steps: +1. Create a new release on GitHub with content +2. Upload all binaries to the release +3. Publish the release +The below scripts help with these steps. + +## github-draft.py +Creates a GitHub release and fills the content with the CHANGELOG.md link. The version number can be changed by the `--version` parameter. + +By default, the command will use the tendermint/tendermint organization/repo, which can be changed using the `--org` and `--repo` parameters. + +By default, the command will get the version number from the `${CIRCLE_TAG}` variable. + +Returns the GitHub release ID. + +## github-upload.py +Upload a file to a GitHub release. The release is defined by the mandatory `--id` (release ID) input parameter. + +By default, the command will upload the file `/tmp/workspace/tendermint_${CIRCLE_TAG}_${GOOS}_${GOARCH}.zip`. This can be changed by the `--file` input parameter. + +## github-publish.py +Publish a GitHub release. The release is defined by the mandatory `--id` (release ID) input parameter. + diff --git a/scripts/release_management/bump-semver.py b/scripts/release_management/bump-semver.py new file mode 100755 index 00000000000..b13a1034291 --- /dev/null +++ b/scripts/release_management/bump-semver.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# Bump the release number of a semantic version number and print it. --version is required. +# Version is +# - vA.B.C, in which case vA.B.C+1 will be returned +# - vA.B.C-devorwhatnot in which case vA.B.C will be returned +# - vA.B in which case vA.B.0 will be returned + +import re +import argparse + + +def semver(ver): + if re.match('v[0-9]+\.[0-9]+',ver) is None: + ver="v0.0" + #raise argparse.ArgumentTypeError('--version must be a semantic version number with major, minor and patch numbers') + return ver + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--version", help="Version number to bump, e.g.: v1.0.0", required=True, type=semver) + args = parser.parse_args() + + found = re.match('(v[0-9]+\.[0-9]+)(\.(.+))?', args.version) + majorminorprefix = found.group(1) + patch = found.group(3) + if patch is None: + patch = "0-new" + + if re.match('[0-9]+$',patch) is None: + patchfound = re.match('([0-9]+)',patch) + patch = int(patchfound.group(1)) + else: + patch = int(patch) + 1 + + print("{0}.{1}".format(majorminorprefix, patch)) diff --git a/scripts/release_management/github-draft.py b/scripts/release_management/github-draft.py new file mode 100755 index 00000000000..8a189d53e30 --- /dev/null +++ b/scripts/release_management/github-draft.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +# Create a draft release on GitHub. By default in the tendermint/tendermint repo. +# Optimized for CircleCI + +import argparse +import httplib +import json +import os +from base64 import b64encode + +def request(org, repo, data): + user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") + headers = { + 'User-Agent': 'tenderbot', + 'Accept': 'application/vnd.github.v3+json', + 'Authorization': 'Basic %s' % user_and_pass + } + + conn = httplib.HTTPSConnection('api.github.com', timeout=5) + conn.request('POST', '/repos/{0}/{1}/releases'.format(org,repo), data, headers) + response = conn.getresponse() + if response.status < 200 or response.status > 299: + print("{0}: {1}".format(response.status, response.reason)) + conn.close() + raise IOError(response.reason) + responsedata = response.read() + conn.close() + return json.loads(responsedata) + + +def create_draft(org,repo,branch,version): + draft = { + 'tag_name': version, + 'target_commitish': '{0}'.format(branch), + 'name': '{0} (WARNING: ALPHA SOFTWARE)'.format(version), + 'body': 'https://github.com/{0}/{1}/blob/{2}/CHANGELOG.md#{3}'.format(org,repo,branch,version.replace('.','')), + 'draft': True, + 'prerelease': False + } + data=json.dumps(draft) + return request(org, repo, data) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--org", default="tendermint", help="GitHub organization") + parser.add_argument("--repo", default="tendermint", help="GitHub repository") + parser.add_argument("--branch", default=os.environ.get('CIRCLE_BRANCH'), help="Branch to build from, e.g.: v1.0") + parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for binary, e.g.: v1.0.0") + args = parser.parse_args() + + if not os.environ.has_key('GITHUB_USERNAME'): + raise parser.error('environment variable GITHUB_USERNAME is required') + + if not os.environ.has_key('GITHUB_TOKEN'): + raise parser.error('environment variable GITHUB_TOKEN is required') + + release = create_draft(args.org,args.repo,args.branch,args.version) + + print(release["id"]) + diff --git a/scripts/release_management/github-openpr.py b/scripts/release_management/github-openpr.py new file mode 100755 index 00000000000..af0434f02dd --- /dev/null +++ b/scripts/release_management/github-openpr.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +# Open a PR against the develop branch. --branch required. +# Optimized for CircleCI + +import json +import os +import argparse +import httplib +from base64 import b64encode + + +def request(org, repo, data): + user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") + headers = { + 'User-Agent': 'tenderbot', + 'Accept': 'application/vnd.github.v3+json', + 'Authorization': 'Basic %s' % user_and_pass + } + + conn = httplib.HTTPSConnection('api.github.com', timeout=5) + conn.request('POST', '/repos/{0}/{1}/pulls'.format(org,repo), data, headers) + response = conn.getresponse() + if response.status < 200 or response.status > 299: + print(response) + conn.close() + raise IOError(response.reason) + responsedata = response.read() + conn.close() + return json.loads(responsedata) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--org", default="tendermint", help="GitHub organization. Defaults to tendermint.") + parser.add_argument("--repo", default="tendermint", help="GitHub repository. Defaults to tendermint.") + parser.add_argument("--head", help="The name of the branch where your changes are implemented.", required=True) + parser.add_argument("--base", help="The name of the branch you want the changes pulled into.", required=True) + parser.add_argument("--title", default="Security release {0}".format(os.environ.get('CIRCLE_TAG')), help="The title of the pull request.") + args = parser.parse_args() + + if not os.environ.has_key('GITHUB_USERNAME'): + raise parser.error('GITHUB_USERNAME not set.') + + if not os.environ.has_key('GITHUB_TOKEN'): + raise parser.error('GITHUB_TOKEN not set.') + + if os.environ.get('CIRCLE_TAG') is None: + raise parser.error('CIRCLE_TAG not set.') + + result = request(args.org, args.repo, data=json.dumps({'title':"{0}".format(args.title),'head':"{0}".format(args.head),'base':"{0}".format(args.base),'body':""})) + print(result['html_url']) diff --git a/scripts/release_management/github-public-newbranch.bash b/scripts/release_management/github-public-newbranch.bash new file mode 100644 index 00000000000..ca2fa131412 --- /dev/null +++ b/scripts/release_management/github-public-newbranch.bash @@ -0,0 +1,28 @@ +#!/bin/sh + +# github-public-newbranch.bash - create public branch from the security repository + +set -euo pipefail + +# Create new branch +BRANCH="${CIRCLE_TAG:-v0.0.0}-security-`date -u +%Y%m%d%H%M%S`" +# Check if the patch release exist already as a branch +if [ -n "`git branch | grep '${BRANCH}'`" ]; then + echo "WARNING: Branch ${BRANCH} already exists." +else + echo "Creating branch ${BRANCH}." + git branch "${BRANCH}" +fi + +# ... and check it out +git checkout "${BRANCH}" + +# Add entry to public repository +git remote add tendermint-origin git@github.com:tendermint/tendermint.git + +# Push branch and tag to public repository +git push tendermint-origin +git push tendermint-origin --tags + +# Create a PR from the public branch to the assumed release branch in public (release branch has to exist) +python -u scripts/release_management/github-openpr.py --head "${BRANCH}" --base "${BRANCH:%.*}" diff --git a/scripts/release_management/github-publish.py b/scripts/release_management/github-publish.py new file mode 100755 index 00000000000..31071aecdb8 --- /dev/null +++ b/scripts/release_management/github-publish.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Publish an existing GitHub draft release. --id required. +# Optimized for CircleCI + +import json +import os +import argparse +import httplib +from base64 import b64encode + + +def request(org, repo, id, data): + user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") + headers = { + 'User-Agent': 'tenderbot', + 'Accept': 'application/vnd.github.v3+json', + 'Authorization': 'Basic %s' % user_and_pass + } + + conn = httplib.HTTPSConnection('api.github.com', timeout=5) + conn.request('POST', '/repos/{0}/{1}/releases/{2}'.format(org,repo,id), data, headers) + response = conn.getresponse() + if response.status < 200 or response.status > 299: + print(response) + conn.close() + raise IOError(response.reason) + responsedata = response.read() + conn.close() + return json.loads(responsedata) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--org", default="tendermint", help="GitHub organization") + parser.add_argument("--repo", default="tendermint", help="GitHub repository") + parser.add_argument("--id", help="GitHub release ID", required=True, type=int) + parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for the release, e.g.: v1.0.0") + args = parser.parse_args() + + if not os.environ.has_key('GITHUB_USERNAME'): + raise parser.error('GITHUB_USERNAME not set.') + + if not os.environ.has_key('GITHUB_TOKEN'): + raise parser.error('GITHUB_TOKEN not set.') + + try: + result = request(args.org, args.repo, args.id, data=json.dumps({'draft':False,'tag_name':"{0}".format(args.version)})) + except IOError as e: + print(e) + result = request(args.org, args.repo, args.id, data=json.dumps({'draft':False,'tag_name':"{0}-autorelease".format(args.version)})) + + print(result['name']) diff --git a/scripts/release_management/github-upload.py b/scripts/release_management/github-upload.py new file mode 100755 index 00000000000..77c76a75544 --- /dev/null +++ b/scripts/release_management/github-upload.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# Upload a file to a GitHub draft release. --id and --file are required. +# Optimized for CircleCI + +import json +import os +import re +import argparse +import mimetypes +import httplib +from base64 import b64encode + + +def request(baseurl, path, mimetype, mimeencoding, data): + user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") + + headers = { + 'User-Agent': 'tenderbot', + 'Accept': 'application/vnd.github.v3.raw+json', + 'Authorization': 'Basic %s' % user_and_pass, + 'Content-Type': mimetype, + 'Content-Encoding': mimeencoding + } + + conn = httplib.HTTPSConnection(baseurl, timeout=5) + conn.request('POST', path, data, headers) + response = conn.getresponse() + if response.status < 200 or response.status > 299: + print(response) + conn.close() + raise IOError(response.reason) + responsedata = response.read() + conn.close() + return json.loads(responsedata) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--id", help="GitHub release ID", required=True, type=int) + parser.add_argument("--file", default="/tmp/workspace/tendermint_{0}_{1}_{2}.zip".format(os.environ.get('CIRCLE_TAG'),os.environ.get('GOOS'),os.environ.get('GOARCH')), help="File to upload") + parser.add_argument("--return-id-only", help="Return only the release ID after upload to GitHub.", action='store_true') + args = parser.parse_args() + + if not os.environ.has_key('GITHUB_USERNAME'): + raise parser.error('GITHUB_USERNAME not set.') + + if not os.environ.has_key('GITHUB_TOKEN'): + raise parser.error('GITHUB_TOKEN not set.') + + mimetypes.init() + filename = os.path.basename(args.file) + mimetype,mimeencoding = mimetypes.guess_type(filename, strict=False) + if mimetype is None: + mimetype = 'application/zip' + if mimeencoding is None: + mimeencoding = 'utf8' + + with open(args.file,'rb') as f: + asset = f.read() + + result = request('uploads.github.com', '/repos/tendermint/tendermint/releases/{0}/assets?name={1}'.format(args.id, filename), mimetype, mimeencoding, asset) + + if args.return_id_only: + print(result['id']) + else: + print(result['browser_download_url']) + diff --git a/scripts/release_management/sha-files.py b/scripts/release_management/sha-files.py new file mode 100755 index 00000000000..2a9ee0d594d --- /dev/null +++ b/scripts/release_management/sha-files.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Create SHA256 summaries from all ZIP files in a folder +# Optimized for CircleCI + +import re +import os +import argparse +import zipfile +import hashlib + + +BLOCKSIZE = 65536 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--folder", default="/tmp/workspace", help="Folder to look for, for ZIP files") + parser.add_argument("--shafile", default="/tmp/workspace/SHA256SUMS", help="SHA256 summaries File") + args = parser.parse_args() + + for filename in os.listdir(args.folder): + if re.search('\.zip$',filename) is None: + continue + if not os.path.isfile(os.path.join(args.folder, filename)): + continue + with open(args.shafile,'a+') as shafile: + hasher = hashlib.sha256() + with open(os.path.join(args.folder, filename),'r') as f: + buf = f.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = f.read(BLOCKSIZE) + shafile.write("{0} {1}\n".format(hasher.hexdigest(),filename)) + diff --git a/scripts/release_management/zip-file.py b/scripts/release_management/zip-file.py new file mode 100755 index 00000000000..5d2f5b2c816 --- /dev/null +++ b/scripts/release_management/zip-file.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# ZIP one file as "tendermint" into a ZIP like tendermint_VERSION_OS_ARCH.zip +# Use environment variables CIRCLE_TAG, GOOS and GOARCH for easy input parameters. +# Optimized for CircleCI + +import os +import argparse +import zipfile +import hashlib + + +BLOCKSIZE = 65536 + + +def zip_asset(file,destination,arcname,version,goos,goarch): + filename = os.path.basename(file) + output = "{0}/{1}_{2}_{3}_{4}.zip".format(destination,arcname,version,goos,goarch) + + with zipfile.ZipFile(output,'w') as f: + f.write(filename=file,arcname=arcname) + f.comment=filename + return output + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--file", default="build/tendermint_{0}_{1}".format(os.environ.get('GOOS'),os.environ.get('GOARCH')), help="File to zip") + parser.add_argument("--destination", default="build", help="Destination folder for files") + parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for binary, e.g.: v1.0.0") + parser.add_argument("--goos", default=os.environ.get('GOOS'), help="GOOS parameter") + parser.add_argument("--goarch", default=os.environ.get('GOARCH'), help="GOARCH parameter") + args = parser.parse_args() + + if args.version is None: + raise parser.error("argument --version is required") + if args.goos is None: + raise parser.error("argument --goos is required") + if args.goarch is None: + raise parser.error("argument --goarch is required") + + file = zip_asset(args.file,args.destination,"tendermint",args.version,args.goos,args.goarch) + print(file) + diff --git a/state/services.go b/state/services.go index 02c3aa7d179..07d12c5a10c 100644 --- a/state/services.go +++ b/state/services.go @@ -23,6 +23,7 @@ type Mempool interface { Size() int CheckTx(types.Tx, func(*abci.Response)) error + CheckTxWithInfo(types.Tx, func(*abci.Response), mempool.TxInfo) error ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs Update(int64, types.Txs, mempool.PreCheckFunc, mempool.PostCheckFunc) error Flush() @@ -37,11 +38,17 @@ type MockMempool struct{} var _ Mempool = MockMempool{} -func (MockMempool) Lock() {} -func (MockMempool) Unlock() {} -func (MockMempool) Size() int { return 0 } -func (MockMempool) CheckTx(_ types.Tx, _ func(*abci.Response)) error { return nil } -func (MockMempool) ReapMaxBytesMaxGas(_, _ int64) types.Txs { return types.Txs{} } +func (MockMempool) Lock() {} +func (MockMempool) Unlock() {} +func (MockMempool) Size() int { return 0 } +func (MockMempool) CheckTx(_ types.Tx, _ func(*abci.Response)) error { + return nil +} +func (MockMempool) CheckTxWithInfo(_ types.Tx, _ func(*abci.Response), + _ mempool.TxInfo) error { + return nil +} +func (MockMempool) ReapMaxBytesMaxGas(_, _ int64) types.Txs { return types.Txs{} } func (MockMempool) Update( _ int64, _ types.Txs, diff --git a/state/store.go b/state/store.go index 6b01a829575..73116b43fc6 100644 --- a/state/store.go +++ b/state/store.go @@ -9,6 +9,14 @@ import ( "github.com/tendermint/tendermint/types" ) +const ( + // persist validators every valSetCheckpointInterval blocks to avoid + // LoadValidators taking too much time. + // https://github.com/tendermint/tendermint/pull/3438 + // 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators) + valSetCheckpointInterval = 100000 +) + //------------------------------------------------------------------------ func calcValidatorsKey(height int64) []byte { @@ -182,25 +190,38 @@ func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) { if valInfo == nil { return nil, ErrNoValSetForHeight{height} } - if valInfo.ValidatorSet == nil { - valInfo2 := loadValidatorsInfo(db, valInfo.LastHeightChanged) + lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) + valInfo2 := loadValidatorsInfo(db, lastStoredHeight) if valInfo2 == nil { - panic( - fmt.Sprintf( - "Couldn't find validators at height %d as last changed from height %d", - valInfo.LastHeightChanged, - height, - ), - ) + // TODO (melekes): remove the below if condition in the 0.33 major + // release and just panic. Old chains might panic otherwise if they + // haven't saved validators at intermediate (%valSetCheckpointInterval) + // height yet. + // https://github.com/tendermint/tendermint/issues/3543 + valInfo2 = loadValidatorsInfo(db, valInfo.LastHeightChanged) + lastStoredHeight = valInfo.LastHeightChanged + if valInfo2 == nil { + panic( + fmt.Sprintf("Couldn't find validators at height %d (height %d was originally requested)", + lastStoredHeight, + height, + ), + ) + } } - valInfo2.ValidatorSet.IncrementProposerPriority(int(height - valInfo.LastHeightChanged)) // mutate + valInfo2.ValidatorSet.IncrementProposerPriority(int(height - lastStoredHeight)) // mutate valInfo = valInfo2 } return valInfo.ValidatorSet, nil } +func lastStoredHeightFor(height, lastHeightChanged int64) int64 { + checkpointHeight := height - height%valSetCheckpointInterval + return cmn.MaxInt64(checkpointHeight, lastHeightChanged) +} + // CONTRACT: Returned ValidatorsInfo can be mutated. func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo { buf := db.Get(calcValidatorsKey(height)) @@ -221,10 +242,10 @@ func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo { } // saveValidatorsInfo persists the validator set. -// `height` is the effective height for which the validator is responsible for signing. -// It should be called from s.Save(), right before the state itself is persisted. -// If the validator set did not change after processing the latest block, -// only the last height for which the validators changed is persisted. +// +// `height` is the effective height for which the validator is responsible for +// signing. It should be called from s.Save(), right before the state itself is +// persisted. func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) { if lastHeightChanged > height { panic("LastHeightChanged cannot be greater than ValidatorsInfo height") @@ -232,7 +253,9 @@ func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *type valInfo := &ValidatorsInfo{ LastHeightChanged: lastHeightChanged, } - if lastHeightChanged == height { + // Only persist validator set if it was updated or checkpoint height (see + // valSetCheckpointInterval) is reached. + if height == lastHeightChanged || height%valSetCheckpointInterval == 0 { valInfo.ValidatorSet = valSet } db.Set(calcValidatorsKey(height), valInfo.Bytes()) diff --git a/state/store_test.go b/state/store_test.go new file mode 100644 index 00000000000..dd48cae718b --- /dev/null +++ b/state/store_test.go @@ -0,0 +1,67 @@ +package state + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + cfg "github.com/tendermint/tendermint/config" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/types" +) + +func TestSaveValidatorsInfo(t *testing.T) { + // test we persist validators every valSetCheckpointInterval blocks + stateDB := dbm.NewMemDB() + val, _ := types.RandValidator(true, 10) + vals := types.NewValidatorSet([]*types.Validator{val}) + + // TODO(melekes): remove in 0.33 release + // https://github.com/tendermint/tendermint/issues/3543 + saveValidatorsInfo(stateDB, 1, 1, vals) + saveValidatorsInfo(stateDB, 2, 1, vals) + assert.NotPanics(t, func() { + _, err := LoadValidators(stateDB, 2) + if err != nil { + panic(err) + } + }) + //ENDREMOVE + + saveValidatorsInfo(stateDB, valSetCheckpointInterval, 1, vals) + + loadedVals, err := LoadValidators(stateDB, valSetCheckpointInterval) + assert.NoError(t, err) + assert.NotZero(t, loadedVals.Size()) +} + +func BenchmarkLoadValidators(b *testing.B) { + const valSetSize = 100 + + config := cfg.ResetTestRoot("state_") + defer os.RemoveAll(config.RootDir) + dbType := dbm.DBBackendType(config.DBBackend) + stateDB := dbm.NewDB("state", dbType, config.DBDir()) + state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + if err != nil { + b.Fatal(err) + } + state.Validators = genValSet(valSetSize) + state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) + SaveState(stateDB, state) + + for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... + saveValidatorsInfo(stateDB, int64(i), state.LastHeightValidatorsChanged, state.NextValidators) + + b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) { + for n := 0; n < b.N; n++ { + _, err := LoadValidators(stateDB, int64(i)) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/tools/tm-signer-harness/internal/test_harness.go b/tools/tm-signer-harness/internal/test_harness.go index 00548913378..7fefdfb4201 100644 --- a/tools/tm-signer-harness/internal/test_harness.go +++ b/tools/tm-signer-harness/internal/test_harness.go @@ -198,8 +198,8 @@ func (th *TestHarness) TestSignProposal() error { hash := tmhash.Sum([]byte("hash")) prop := &types.Proposal{ Type: types.ProposalType, - Height: 12345, - Round: 23456, + Height: 100, + Round: 0, POLRound: -1, BlockID: types.BlockID{ Hash: hash, @@ -240,8 +240,8 @@ func (th *TestHarness) TestSignVote() error { hash := tmhash.Sum([]byte("hash")) vote := &types.Vote{ Type: voteType, - Height: 12345, - Round: 23456, + Height: 101, + Round: 0, BlockID: types.BlockID{ Hash: hash, PartsHeader: types.PartSetHeader{ diff --git a/types/protobuf.go b/types/protobuf.go index e10b9186954..c87e82c0a81 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -220,36 +220,3 @@ func (pb2tm) ValidatorUpdates(vals []abci.ValidatorUpdate) ([]*Validator, error) } return tmVals, nil } - -// BlockParams.TimeIotaMs is not exposed to the application. Therefore a caller -// must provide it. -func (pb2tm) ConsensusParams(csp *abci.ConsensusParams, blockTimeIotaMs int64) ConsensusParams { - params := ConsensusParams{ - Block: BlockParams{}, - Evidence: EvidenceParams{}, - Validator: ValidatorParams{}, - } - - // we must defensively consider any structs may be nil - if csp.Block != nil { - params.Block = BlockParams{ - MaxBytes: csp.Block.MaxBytes, - MaxGas: csp.Block.MaxGas, - TimeIotaMs: blockTimeIotaMs, - } - } - - if csp.Evidence != nil { - params.Evidence = EvidenceParams{ - MaxAge: csp.Evidence.MaxAge, - } - } - - if csp.Validator != nil { - params.Validator = ValidatorParams{ - PubKeyTypes: csp.Validator.PubKeyTypes, - } - } - - return params -} diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 152c92d12ff..64caa3f4c11 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -64,7 +64,7 @@ func TestABCIValidators(t *testing.T) { func TestABCIConsensusParams(t *testing.T) { cp := DefaultConsensusParams() abciCP := TM2PB.ConsensusParams(cp) - cp2 := PB2TM.ConsensusParams(abciCP, cp.Block.TimeIotaMs) + cp2 := cp.Update(abciCP) assert.Equal(t, *cp, cp2) } diff --git a/types/validator_set.go b/types/validator_set.go index 3d31cf7d076..36ce67f061e 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -31,7 +31,8 @@ const ( // ValidatorSet represent a set of *Validator at a given height. // The validators can be fetched by address or index. // The index is in order of .Address, so the indices are fixed -// for all rounds of a given blockchain height. +// for all rounds of a given blockchain height - ie. the validators +// are sorted by their address. // On the other hand, the .ProposerPriority of each validator and // the designated .GetProposer() of a set changes every round, // upon calling .IncrementProposerPriority(). diff --git a/version/version.go b/version/version.go index b2202206cf5..9ba38de6cfe 100644 --- a/version/version.go +++ b/version/version.go @@ -20,7 +20,7 @@ const ( // Must be a string because scripts like dist.sh read this file. // XXX: Don't change the name of this variable or you will break // automation :) - TMCoreSemVer = "0.31.0" + TMCoreSemVer = "0.31.4" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.16.0"