From e0f686ccaca27cfef86ead0e24a0375a5d3de0b7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 29 Sep 2020 12:15:54 +0400 Subject: [PATCH 001/108] mempool: fix nil pointer dereference (#5412) previously, the second next could return nil, which would be the reason for panic on line 275: memTx := next.Value.(*mempoolTx) Closes #5408 --- mempool/reactor.go | 5 ++-- mempool/reactor_test.go | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/mempool/reactor.go b/mempool/reactor.go index 13e4b16f20..b4f76b9e8d 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -288,10 +288,11 @@ func (memR *Reactor) txs(next *clist.CElement, peerID uint16, peerHeight int64) batch = append(batch, memTx.tx) } - if next.Next() == nil { + n := next.Next() + if n == nil { return batch } - next = next.Next() + next = n } } diff --git a/mempool/reactor_test.go b/mempool/reactor_test.go index 3bc5597d9e..d9e67d166c 100644 --- a/mempool/reactor_test.go +++ b/mempool/reactor_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" + abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -64,6 +65,66 @@ func TestReactorBroadcastTxsMessage(t *testing.T) { waitForTxsOnReactors(t, txs, reactors) } +// regression test for https://github.com/tendermint/tendermint/issues/5408 +func TestReactorConcurrency(t *testing.T) { + config := cfg.TestConfig() + const N = 2 + reactors := makeAndConnectReactors(config, N) + defer func() { + for _, r := range reactors { + if err := r.Stop(); err != nil { + assert.NoError(t, err) + } + } + }() + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + peer.Set(types.PeerStateKey, peerState{1}) + } + } + var wg sync.WaitGroup + + const numTxs = 5 + + for i := 0; i < 1000; i++ { + wg.Add(2) + + // 1. submit a bunch of txs + // 2. update the whole mempool + txs := checkTxs(t, reactors[0].mempool, numTxs, UnknownPeerID) + go func() { + defer wg.Done() + + reactors[0].mempool.Lock() + defer reactors[0].mempool.Unlock() + + deliverTxResponses := make([]*abci.ResponseDeliverTx, len(txs)) + for i := range txs { + deliverTxResponses[i] = &abci.ResponseDeliverTx{Code: 0} + } + err := reactors[0].mempool.Update(1, txs, deliverTxResponses, nil, nil) + assert.NoError(t, err) + }() + + // 1. submit a bunch of txs + // 2. update none + _ = checkTxs(t, reactors[1].mempool, numTxs, UnknownPeerID) + go func() { + defer wg.Done() + + reactors[1].mempool.Lock() + defer reactors[1].mempool.Unlock() + err := reactors[1].mempool.Update(1, []types.Tx{}, make([]*abci.ResponseDeliverTx, 0), nil, nil) + assert.NoError(t, err) + }() + + // 1. flush the mempool + reactors[1].mempool.Flush() + } + + wg.Wait() +} + // Send a bunch of txs to the first reactor's mempool, claiming it came from peer // ensure peer gets no txs. func TestReactorNoBroadcastToSender(t *testing.T) { From 1a2cc933a0b5e5204ee21f9ab82b7243a475ebe7 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 1 Oct 2020 15:55:57 +0200 Subject: [PATCH 002/108] config: set statesync.rpc_servers when generating config file (#5433) (#5438) Required for #5291, to generate configuration files with state sync RPC servers. --- CHANGELOG_PENDING.md | 2 ++ config/toml.go | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 96f794c773..402e0db20a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,5 +22,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS +- [config] \#5433 `statesync.rpc_servers` is now properly set when writing the configuration file (@erikgrinaker) + ### BUG FIXES diff --git a/config/toml.go b/config/toml.go index 275057d660..260eda7cd5 100644 --- a/config/toml.go +++ b/config/toml.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "path/filepath" + "strings" "text/template" tmos "github.com/tendermint/tendermint/libs/os" @@ -17,7 +18,10 @@ var configTemplate *template.Template func init() { var err error - if configTemplate, err = template.New("configFileTemplate").Parse(defaultConfigTemplate); err != nil { + tmpl := template.New("configFileTemplate").Funcs(template.FuncMap{ + "StringsJoin": strings.Join, + }) + if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil { panic(err) } } @@ -350,7 +354,7 @@ enable = {{ .StateSync.Enable }} # # For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 # weeks) during which they can be financially punished (slashed) for misbehavior. -rpc_servers = "" +rpc_servers = "{{ StringsJoin .StateSync.RPCServers "," }}" trust_height = {{ .StateSync.TrustHeight }} trust_hash = "{{ .StateSync.TrustHash }}" trust_period = "{{ .StateSync.TrustPeriod }}" From 6149f21cd6938ec711ace347ec54f63dcf69d4e4 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 1 Oct 2020 16:06:34 +0200 Subject: [PATCH 003/108] privval: allow passing options to NewSignerDialerEndpoint (#5434) (#5437) Required for #5291 to set timeouts for remote signers. --- CHANGELOG_PENDING.md | 2 ++ privval/signer_dialer_endpoint.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 402e0db20a..78d13cf9a6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,5 +24,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [config] \#5433 `statesync.rpc_servers` is now properly set when writing the configuration file (@erikgrinaker) +- [privval] \#5437 `NewSignerDialerEndpoint` can now be given `SignerServiceEndpointOption` (@erikgrinaker) + ### BUG FIXES diff --git a/privval/signer_dialer_endpoint.go b/privval/signer_dialer_endpoint.go index 7336f64be7..bd98314b60 100644 --- a/privval/signer_dialer_endpoint.go +++ b/privval/signer_dialer_endpoint.go @@ -48,6 +48,7 @@ type SignerDialerEndpoint struct { func NewSignerDialerEndpoint( logger log.Logger, dialer SocketDialer, + options ...SignerServiceEndpointOption, ) *SignerDialerEndpoint { sd := &SignerDialerEndpoint{ @@ -56,6 +57,10 @@ func NewSignerDialerEndpoint( maxConnRetries: defaultMaxDialRetries, } + for _, optionFunc := range options { + optionFunc(sd) + } + sd.BaseService = *service.NewBaseService(logger, "SignerDialerEndpoint", sd) sd.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second From 52994aa2a9cc0640d542344433f06e776da74b08 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 1 Oct 2020 16:11:54 +0200 Subject: [PATCH 004/108] consensus: check block parts don't exceed maximum block bytes (#5436) --- consensus/common_test.go | 2 +- consensus/state.go | 7 +++++ consensus/state_test.go | 59 ++++++++++++++++++++++++++++++++++++++++ types/part_set.go | 13 +++++++++ types/part_set_test.go | 13 +++++---- 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/consensus/common_test.go b/consensus/common_test.go index 8049c11099..5a87ce402c 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -603,7 +603,7 @@ func ensureProposal(proposalCh <-chan tmpubsub.Message, height int64, round int3 panic(fmt.Sprintf("expected round %v, got %v", round, proposalEvent.Round)) } if !proposalEvent.BlockID.Equals(propID) { - panic("Proposed block does not match expected block") + panic(fmt.Sprintf("Proposed block does not match expected block (%v != %v)", proposalEvent.BlockID, propID)) } } } diff --git a/consensus/state.go b/consensus/state.go index fada3b2229..d1140e66aa 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1779,16 +1779,23 @@ func (cs *State) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (add if err != nil { return added, err } + if cs.ProposalBlockParts.ByteSize() > cs.state.ConsensusParams.Block.MaxBytes { + return added, fmt.Errorf("total size of proposal block parts exceeds maximum block bytes (%d > %d)", + cs.ProposalBlockParts.ByteSize(), cs.state.ConsensusParams.Block.MaxBytes, + ) + } if added && cs.ProposalBlockParts.IsComplete() { bz, err := ioutil.ReadAll(cs.ProposalBlockParts.GetReader()) if err != nil { return added, err } + var pbb = new(tmproto.Block) err = proto.Unmarshal(bz, pbb) if err != nil { return added, err } + block, err := types.BlockFromProto(pbb) if err != nil { return added, err diff --git a/consensus/state_test.go b/consensus/state_test.go index 5b0c581c29..85358ae503 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -29,6 +29,7 @@ x * TestProposerSelection2 - round robin ordering, round 2++ x * TestEnterProposeNoValidator - timeout into prevote round x * TestEnterPropose - finish propose without timing out (we have the proposal) x * TestBadProposal - 2 vals, bad proposal (bad block state hash), should prevote and precommit nil +x * TestOversizedBlock - block with too many txs should be rejected FullRoundSuite x * TestFullRound1 - 1 val, full successful round x * TestFullRoundNil - 1 val, full round of nil @@ -238,6 +239,64 @@ func TestStateBadProposal(t *testing.T) { signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } +func TestStateOversizedBlock(t *testing.T) { + cs1, vss := randState(2) + cs1.state.ConsensusParams.Block.MaxBytes = 2000 + height, round := cs1.Height, cs1.Round + vs2 := vss[1] + + partSize := types.BlockPartSizeBytes + + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + voteCh := subscribe(cs1.eventBus, types.EventQueryVote) + + propBlock, _ := cs1.createProposalBlock() + propBlock.Data.Txs = []types.Tx{tmrand.Bytes(2001)} + propBlock.Header.DataHash = propBlock.Data.Hash() + + // make the second validator the proposer by incrementing round + round++ + incrementRound(vss[1:]...) + + propBlockParts := propBlock.MakePartSet(partSize) + blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} + proposal := types.NewProposal(height, round, -1, blockID) + p := proposal.ToProto() + if err := vs2.SignProposal(config.ChainID(), p); err != nil { + t.Fatal("failed to sign bad proposal", err) + } + proposal.Signature = p.Signature + + totalBytes := 0 + for i := 0; i < int(propBlockParts.Total()); i++ { + part := propBlockParts.GetPart(i) + totalBytes += len(part.Bytes) + } + + if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { + t.Fatal(err) + } + + // start the machine + startTestRound(cs1, height, round) + + t.Log("Block Sizes", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) + + // c1 should log an error with the block part message as it exceeds the consensus params. The + // block is not added to cs.ProposalBlock so the node timeouts. + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + + // and then should send nil prevote and precommit regardless of whether other validators prevote and + // precommit on it + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], nil) + signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + ensurePrevote(voteCh, height, round) + ensurePrecommit(voteCh, height, round) + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) +} + //---------------------------------------------------------------------------------------------------- // FullRoundSuite diff --git a/types/part_set.go b/types/part_set.go index ca93b2b1a8..b16fc583c9 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -155,6 +155,9 @@ type PartSet struct { parts []*Part partsBitArray *bits.BitArray count uint32 + // a count of the total size (in bytes). Used to ensure that the + // part set doesn't exceed the maximum block bytes + byteSize int64 } // Returns an immutable, full PartSet from the data bytes. @@ -186,6 +189,7 @@ func NewPartSetFromData(data []byte, partSize uint32) *PartSet { parts: parts, partsBitArray: partsBitArray, count: total, + byteSize: int64(len(data)), } } @@ -197,6 +201,7 @@ func NewPartSetFromHeader(header PartSetHeader) *PartSet { parts: make([]*Part, header.Total), partsBitArray: bits.NewBitArray(int(header.Total)), count: 0, + byteSize: 0, } } @@ -244,6 +249,13 @@ func (ps *PartSet) Count() uint32 { return ps.count } +func (ps *PartSet) ByteSize() int64 { + if ps == nil { + return 0 + } + return ps.byteSize +} + func (ps *PartSet) Total() uint32 { if ps == nil { return 0 @@ -277,6 +289,7 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { ps.parts[part.Index] = part ps.partsBitArray.SetIndex(int(part.Index), true) ps.count++ + ps.byteSize += int64(len(part.Bytes)) return true, nil } diff --git a/types/part_set_test.go b/types/part_set_test.go index b7253da10e..e7347e2f1d 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -17,15 +17,17 @@ const ( func TestBasicPartSet(t *testing.T) { // Construct random data of size partSize * 100 - data := tmrand.Bytes(testPartSize * 100) + nParts := 100 + data := tmrand.Bytes(testPartSize * nParts) partSet := NewPartSetFromData(data, testPartSize) assert.NotEmpty(t, partSet.Hash()) - assert.EqualValues(t, 100, partSet.Total()) - assert.Equal(t, 100, partSet.BitArray().Size()) + assert.EqualValues(t, nParts, partSet.Total()) + assert.Equal(t, nParts, partSet.BitArray().Size()) assert.True(t, partSet.HashesTo(partSet.Hash())) assert.True(t, partSet.IsComplete()) - assert.EqualValues(t, 100, partSet.Count()) + assert.EqualValues(t, nParts, partSet.Count()) + assert.EqualValues(t, testPartSize*nParts, partSet.ByteSize()) // Test adding parts to a new partSet. partSet2 := NewPartSetFromHeader(partSet.Header()) @@ -49,7 +51,8 @@ func TestBasicPartSet(t *testing.T) { assert.Nil(t, err) assert.Equal(t, partSet.Hash(), partSet2.Hash()) - assert.EqualValues(t, 100, partSet2.Total()) + assert.EqualValues(t, nParts, partSet2.Total()) + assert.EqualValues(t, nParts*testPartSize, partSet.ByteSize()) assert.True(t, partSet2.IsComplete()) // Reconstruct data, assert that they are equal. From e74176ad1aa68daa911bfe697d5538ce12e2bcbc Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 1 Oct 2020 17:02:11 +0200 Subject: [PATCH 005/108] privval: fix ping message encoding (#5442) Fixes #5371. --- CHANGELOG_PENDING.md | 2 ++ privval/msgs.go | 6 +++--- privval/msgs_test.go | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 78d13cf9a6..9028185f04 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -28,3 +28,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES +- [privval] \#5441 Fix faulty ping message encoding causing nil message errors in logs (@erikgrinaker) + diff --git a/privval/msgs.go b/privval/msgs.go index b7a95dbfe0..bcfed629bd 100644 --- a/privval/msgs.go +++ b/privval/msgs.go @@ -29,11 +29,11 @@ func mustWrapMsg(pb proto.Message) privvalproto.Message { case *privvalproto.SignProposalRequest: msg.Sum = &privvalproto.Message_SignProposalRequest{SignProposalRequest: pb} case *privvalproto.PingRequest: - msg.Sum = &privvalproto.Message_PingRequest{} + msg.Sum = &privvalproto.Message_PingRequest{PingRequest: pb} case *privvalproto.PingResponse: - msg.Sum = &privvalproto.Message_PingResponse{} + msg.Sum = &privvalproto.Message_PingResponse{PingResponse: pb} default: - panic(fmt.Errorf("unknown message type %T", msg)) + panic(fmt.Errorf("unknown message type %T", pb)) } return msg diff --git a/privval/msgs_test.go b/privval/msgs_test.go index 61b809b2f4..f6f1681dba 100644 --- a/privval/msgs_test.go +++ b/privval/msgs_test.go @@ -78,8 +78,8 @@ func TestPrivvalVectors(t *testing.T) { msg proto.Message expBytes string }{ - {"ping request", &privproto.PingRequest{}, ""}, - {"ping response", &privproto.PingResponse{}, ""}, + {"ping request", &privproto.PingRequest{}, "3a00"}, + {"ping response", &privproto.PingResponse{}, "4200"}, {"pubKey request", &privproto.PubKeyRequest{}, "0a00"}, {"pubKey response", &privproto.PubKeyResponse{PubKey: &ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"}, {"pubKey response with error", &privproto.PubKeyResponse{PubKey: nil, Error: remoteError}, "121212100801120c697427732061206572726f72"}, From 383bc5337f166c7eb280221671ca7064b9cee151 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Mon, 5 Oct 2020 12:27:58 +0200 Subject: [PATCH 006/108] changelog: add missing date to v0.33.5 release, fix indentation (#5454) (#5455) I forgot to add the date when we cut 0.33.5. This fixes that. It also fixes a header indentation issue for 0.33.8. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9ac9ac92..d57c9d5201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -240,7 +240,7 @@ This release was removed, as a premature GitHub tag was recorded on sum.golang.o *August 11, 2020* -## Go security update +### Go security update Go reported a security vulnerability that affected the `encoding/binary` package. The most recent binary for tendermint is using 1.14.6, for this reason the Tendermint engineering team has opted to conduct a release to aid users in using the correct version of Go. Read more about the security issue [here](https://github.com/golang/go/issues/40618). @@ -322,6 +322,8 @@ need to update your code.** ## v0.33.5 +*May 28, 2020* + Special thanks to external contributors on this release: @tau3, Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). From dac18d73a75eac9946a77b4936b48be2b8b684d5 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 7 Oct 2020 12:01:40 +0200 Subject: [PATCH 007/108] fix RPC blockresults return (#5459) (#5463) --- CHANGELOG_PENDING.md | 2 +- crypto/encoding/codec.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 9028185f04..512863c417 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -29,4 +29,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES - [privval] \#5441 Fix faulty ping message encoding causing nil message errors in logs (@erikgrinaker) - +- [rpc] \#5459 Register the interface of public keys for json encoding (@marbar3778) diff --git a/crypto/encoding/codec.go b/crypto/encoding/codec.go index 56f33f4c7e..7438cb5fb0 100644 --- a/crypto/encoding/codec.go +++ b/crypto/encoding/codec.go @@ -5,9 +5,15 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/json" pc "github.com/tendermint/tendermint/proto/tendermint/crypto" ) +func init() { + json.RegisterType((*pc.PublicKey)(nil), "tendermint.crypto.PublicKey") + json.RegisterType((*pc.PublicKey_Ed25519)(nil), "tendermint.crypto.PublicKey_Ed25519") +} + // PubKeyToProto takes crypto.PubKey and transforms it to a protobuf Pubkey func PubKeyToProto(k crypto.PubKey) (pc.PublicKey, error) { var kp pc.PublicKey From 7d5d417dc95d437086273f119dad340ae1c4d44b Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 8 Oct 2020 14:38:11 +0200 Subject: [PATCH 008/108] evidence: use bytes instead of quantity to limit size (#5449)(#5476) --- CHANGELOG_PENDING.md | 1 + config/toml.go | 2 +- consensus/reactor_test.go | 6 +- evidence/doc.go | 2 +- evidence/mocks/state_store.go | 52 --------------- evidence/pool.go | 48 +++++++------ evidence/pool_test.go | 41 ++++++++---- evidence/reactor_test.go | 9 ++- evidence/verify_test.go | 6 +- node/node_test.go | 8 ++- proto/tendermint/types/params.pb.go | 100 ++++++++++++++-------------- proto/tendermint/types/params.proto | 9 ++- state/execution.go | 4 +- state/mocks/evidence_pool.go | 13 +++- state/services.go | 6 +- state/tx_filter.go | 3 +- state/tx_filter_test.go | 6 +- state/validation.go | 4 +- state/validation_test.go | 31 +++++---- types/block.go | 34 +++++++--- types/block_test.go | 37 +++++----- types/evidence.go | 15 ++--- types/evidence_test.go | 31 --------- types/params.go | 23 +++---- types/params_test.go | 8 +-- types/part_set.go | 2 +- 26 files changed, 227 insertions(+), 274 deletions(-) delete mode 100644 evidence/mocks/state_store.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 512863c417..2966b2ec43 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -15,6 +15,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API + - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) `MaxNum` evidence consensus parameter has been changed to `MaxBytes` (@cmwaters) - Blockchain Protocol diff --git a/config/toml.go b/config/toml.go index 260eda7cd5..0dce3e6dce 100644 --- a/config/toml.go +++ b/config/toml.go @@ -515,7 +515,7 @@ var testGenesisFmt = `{ "evidence": { "max_age_num_blocks": "100000", "max_age_duration": "172800000000000", - "max_num": 50 + "max_bytes": "1048576" }, "validator": { "pub_key_types": [ diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index eb8591848d..ce08daab43 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -167,11 +167,11 @@ func TestReactorWithEvidence(t *testing.T) { // mock the evidence pool // everyone includes evidence of another double signing vIdx := (i + 1) % nValidators + ev := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) evpool := &statemocks.EvidencePool{} evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) - evpool.On("PendingEvidence", mock.AnythingOfType("uint32")).Return([]types.Evidence{ - types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()), - }) + evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return([]types.Evidence{ + ev}, int64(len(ev.Bytes()))) evpool.On("Update", mock.AnythingOfType("state.State")).Return() evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return( []abci.Evidence{}) diff --git a/evidence/doc.go b/evidence/doc.go index 5d823952df..d521debd30 100644 --- a/evidence/doc.go +++ b/evidence/doc.go @@ -32,7 +32,7 @@ All evidence is proto encoded to disk. Proposing When a new block is being proposed (in state/execution.go#CreateProposalBlock), -`PendingEvidence(maxNum)` is called to send up to the maxNum number of uncommitted evidence, from the evidence store, +`PendingEvidence(maxBytes)` is called to send up to the maxBytes of uncommitted evidence, from the evidence store, prioritized in order of age. All evidence is checked for expiration. When a node receives evidence in a block it will use the evidence module as a cache first to see if it has diff --git a/evidence/mocks/state_store.go b/evidence/mocks/state_store.go deleted file mode 100644 index cdda2ce9a3..0000000000 --- a/evidence/mocks/state_store.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by mockery v2.1.0. DO NOT EDIT. - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" - state "github.com/tendermint/tendermint/state" - - types "github.com/tendermint/tendermint/types" -) - -// StateStore is an autogenerated mock type for the StateStore type -type StateStore struct { - mock.Mock -} - -// LoadState provides a mock function with given fields: -func (_m *StateStore) LoadState() state.State { - ret := _m.Called() - - var r0 state.State - if rf, ok := ret.Get(0).(func() state.State); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(state.State) - } - - return r0 -} - -// LoadValidators provides a mock function with given fields: height -func (_m *StateStore) LoadValidators(height int64) (*types.ValidatorSet, error) { - ret := _m.Called(height) - - var r0 *types.ValidatorSet - if rf, ok := ret.Get(0).(func(int64) *types.ValidatorSet); ok { - r0 = rf(height) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.ValidatorSet) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int64) error); ok { - r1 = rf(height) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/evidence/pool.go b/evidence/pool.go index 9db7048c75..87ae46e3dd 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -64,19 +64,16 @@ func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, logger: log.NewNopLogger(), evidenceStore: evidenceDB, evidenceList: clist.New(), - evidenceSize: 0, - pruningHeight: state.LastBlockHeight, - pruningTime: state.LastBlockTime, } // if pending evidence already in db, in event of prior failure, then check for expiration, // update the size and load it back to the evidenceList - pool.removeExpiredPendingEvidence() - evList, err := pool.listEvidence(baseKeyPending, -1) + pool.pruningHeight, pool.pruningTime = pool.removeExpiredPendingEvidence() + evList, _, err := pool.listEvidence(baseKeyPending, -1) if err != nil { return nil, err } - atomic.AddUint32(&pool.evidenceSize, uint32(len(evList))) + atomic.StoreUint32(&pool.evidenceSize, uint32(len(evList))) for _, ev := range evList { pool.evidenceList.PushBack(ev) } @@ -85,12 +82,15 @@ func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, } // PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence. -func (evpool *Pool) PendingEvidence(maxNum uint32) []types.Evidence { - evidence, err := evpool.listEvidence(baseKeyPending, int64(maxNum)) +func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) { + if atomic.LoadUint32(&evpool.evidenceSize) == 0 { + return []types.Evidence{}, 0 + } + evidence, size, err := evpool.listEvidence(baseKeyPending, maxBytes) if err != nil { evpool.logger.Error("Unable to retrieve pending evidence", "err", err) } - return evidence + return evidence, size } // Update pulls the latest state to be used for expiration and evidence params and then prunes all expired evidence @@ -330,6 +330,7 @@ type info struct { Time time.Time Validators []*types.Validator TotalVotingPower int64 + ByteSize int64 } // ToProto encodes into protobuf @@ -381,6 +382,7 @@ func infoFromProto(proto *evproto.Info) (info, error) { Time: proto.Time, Validators: vals, TotalVotingPower: proto.TotalVotingPower, + ByteSize: int64(proto.Evidence.Size()), }, nil } @@ -489,31 +491,32 @@ func (evpool *Pool) removePendingEvidence(evidence types.Evidence) { } } -// listEvidence lists up to maxNum pieces of evidence for the given prefix key. -// If maxNum is -1, there's no cap on the size of returned evidence. -func (evpool *Pool) listEvidence(prefixKey byte, maxNum int64) ([]types.Evidence, error) { - var count int64 +// listEvidence retrieves lists evidence from oldest to newest within maxBytes. +// If maxBytes is -1, there's no cap on the size of returned evidence. +func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Evidence, int64, error) { + var totalSize int64 var evidence []types.Evidence iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey}) if err != nil { - return nil, fmt.Errorf("database error: %v", err) + return nil, totalSize, fmt.Errorf("database error: %v", err) } defer iter.Close() for ; iter.Valid(); iter.Next() { - if count == maxNum { - return evidence, nil - } - count++ - evInfo, err := bytesToInfo(iter.Value()) if err != nil { - return nil, err + return nil, totalSize, err + } + + totalSize += evInfo.ByteSize + + if maxBytes != -1 && totalSize > maxBytes { + return evidence, totalSize - evInfo.ByteSize, nil } evidence = append(evidence, evInfo.Evidence) } - return evidence, nil + return evidence, totalSize, nil } func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { @@ -534,7 +537,8 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { if len(blockEvidenceMap) != 0 { evpool.removeEvidenceFromList(blockEvidenceMap) } - // return the time with which this evidence will have expired so we know when to prune next + + // return the height and time with which this evidence will have expired so we know when to prune next return evInfo.Evidence.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1, evInfo.Time.Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second) } diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 53e9e9ba24..40949ef891 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -32,7 +32,10 @@ func TestMain(m *testing.M) { const evidenceChainID = "test_chain" -var defaultEvidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) +var ( + defaultEvidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) + defaultEvidenceMaxBytes int64 = 1000 +) func TestEvidencePoolBasic(t *testing.T) { var ( @@ -52,10 +55,12 @@ func TestEvidencePoolBasic(t *testing.T) { pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore) require.NoError(t, err) + pool.SetLogger(log.TestingLogger()) // evidence not seen yet: - evs := pool.PendingEvidence(10) + evs, size := pool.PendingEvidence(defaultEvidenceMaxBytes) assert.Equal(t, 0, len(evs)) + assert.Zero(t, size) ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID) @@ -78,12 +83,14 @@ func TestEvidencePoolBasic(t *testing.T) { next := pool.EvidenceFront() assert.Equal(t, ev, next.Value.(types.Evidence)) - evs = pool.PendingEvidence(10) + evs, size = pool.PendingEvidence(defaultEvidenceMaxBytes) assert.Equal(t, 1, len(evs)) + assert.Equal(t, int64(357), size) // check that the size of the single evidence in bytes is correct // shouldn't be able to add evidence twice assert.Error(t, pool.AddEvidence(ev)) - assert.Equal(t, 1, len(pool.PendingEvidence(10))) + evs, _ = pool.PendingEvidence(defaultEvidenceMaxBytes) + assert.Equal(t, 1, len(evs)) } @@ -183,12 +190,15 @@ func TestEvidencePoolUpdate(t *testing.T) { }, } assert.Equal(t, expectedByzVals, byzVals) - assert.Equal(t, 1, len(pool.PendingEvidence(10))) + evList, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) + assert.Equal(t, 1, len(evList)) pool.Update(state) // a) Update marks evidence as committed so pending evidence should be empty - assert.Empty(t, pool.PendingEvidence(10)) + evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes) + assert.Empty(t, evList) + assert.Zero(t, evSize) // b) If we try to check this evidence again it should fail because it has already been committed err = pool.CheckEvidence(types.EvidenceList{ev}) @@ -293,7 +303,6 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) { func TestRecoverPendingEvidence(t *testing.T) { height := int64(10) - expiredEvidenceTime := time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC) val := types.NewMockPV() valAddress := val.PrivKey.PubKey().Address() evidenceDB := dbm.NewMemDB() @@ -301,21 +310,24 @@ func TestRecoverPendingEvidence(t *testing.T) { state, err := stateStore.Load() require.NoError(t, err) blockStore := initializeBlockStore(dbm.NewMemDB(), state, valAddress) + // create previous pool and populate it pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore) require.NoError(t, err) pool.SetLogger(log.TestingLogger()) goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), - expiredEvidenceTime, val, evidenceChainID) + defaultEvidenceTime, val, evidenceChainID) err = pool.AddEvidence(goodEvidence) require.NoError(t, err) err = pool.AddEvidence(expiredEvidence) require.NoError(t, err) + + // now recover from the previous pool at a different time newStateStore := &smmocks.Store{} newStateStore.On("Load").Return(sm.State{ - LastBlockTime: defaultEvidenceTime.Add(49 * time.Hour), - LastBlockHeight: height + 12, + LastBlockTime: defaultEvidenceTime.Add(25 * time.Minute), + LastBlockHeight: height + 15, ConsensusParams: tmproto.ConsensusParams{ Block: tmproto.BlockParams{ MaxBytes: 22020096, @@ -323,14 +335,15 @@ func TestRecoverPendingEvidence(t *testing.T) { }, Evidence: tmproto.EvidenceParams{ MaxAgeNumBlocks: 20, - MaxAgeDuration: 1 * time.Hour, - MaxNum: 50, + MaxAgeDuration: 20 * time.Minute, + MaxBytes: 1000, }, }, }, nil) newPool, err := evidence.NewPool(evidenceDB, newStateStore, blockStore) assert.NoError(t, err) - assert.Equal(t, 1, len(newPool.PendingEvidence(10))) + evList, _ := newPool.PendingEvidence(defaultEvidenceMaxBytes) + assert.Equal(t, 1, len(evList)) next := newPool.EvidenceFront() assert.Equal(t, goodEvidence, next.Value.(types.Evidence)) @@ -356,7 +369,7 @@ func initializeStateFromValidatorSet(valSet *types.ValidatorSet, height int64) s Evidence: tmproto.EvidenceParams{ MaxAgeNumBlocks: 20, MaxAgeDuration: 20 * time.Minute, - MaxNum: 50, + MaxBytes: 1000, }, }, } diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index d21ab52b50..8b05ab0fd0 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -106,14 +106,17 @@ func _waitForEvidence( pools []*evidence.Pool, ) { evpool := pools[poolIdx] - for len(evpool.PendingEvidence(uint32(len(evs)))) != len(evs) { + var evList []types.Evidence + currentPoolSize := 0 + for currentPoolSize != len(evs) { + evList, _ = evpool.PendingEvidence(int64(len(evs) * 500)) // each evidence should not be more than 500 bytes + currentPoolSize = len(evList) time.Sleep(time.Millisecond * 100) } - reapedEv := evpool.PendingEvidence(uint32(len(evs))) // put the reaped evidence in a map so we can quickly check we got everything evMap := make(map[string]types.Evidence) - for _, e := range reapedEv { + for _, e := range evList { evMap[string(e.Hash())] = e } for i, expectedEv := range evs { diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 016a867dce..e344cd4967 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -102,7 +102,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { err = pool.CheckEvidence(evList) assert.NoError(t, err) - pendingEvs := pool.PendingEvidence(2) + pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) pubKey, err := newPrivVal.GetPubKey() @@ -206,7 +206,7 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { err = pool.CheckEvidence(evList) assert.NoError(t, err) - pendingEvs := pool.PendingEvidence(2) + pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) pubKey, err := conflictingPrivVals[0].GetPubKey() @@ -303,7 +303,7 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { err = pool.CheckEvidence(evList) assert.NoError(t, err) - pendingEvs := pool.PendingEvidence(2) + pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) pubKey, err := conflictingPrivVals[0].GetPubKey() diff --git a/node/node_test.go b/node/node_test.go index 101699ccef..65a972fc79 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -234,9 +234,9 @@ func TestCreateProposalBlock(t *testing.T) { state, stateDB, privVals := state(1, height) stateStore := sm.NewStore(stateDB) maxBytes := 16384 - maxEvidence := 10 + maxEvidenceBytes := int64(maxBytes / 2) state.ConsensusParams.Block.MaxBytes = int64(maxBytes) - state.ConsensusParams.Evidence.MaxNum = uint32(maxEvidence) + state.ConsensusParams.Evidence.MaxBytes = maxEvidenceBytes proposerAddr, _ := state.Validators.GetByIndex(0) // Make Mempool @@ -260,8 +260,10 @@ func TestCreateProposalBlock(t *testing.T) { // fill the evidence pool with more evidence // than can fit in a block - for i := 0; i <= maxEvidence; i++ { + var currentBytes int64 = 0 + for currentBytes <= maxEvidenceBytes { ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") + currentBytes += int64(len(ev.Bytes())) err := evidencePool.AddEvidenceFromConsensus(ev, time.Now(), state.Validators) require.NoError(t, err) } diff --git a/proto/tendermint/types/params.pb.go b/proto/tendermint/types/params.pb.go index 0b0773250b..5c9eff877f 100644 --- a/proto/tendermint/types/params.pb.go +++ b/proto/tendermint/types/params.pb.go @@ -179,11 +179,10 @@ type EvidenceParams struct { // mechanism for handling [Nothing-At-Stake // attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed). MaxAgeDuration time.Duration `protobuf:"bytes,2,opt,name=max_age_duration,json=maxAgeDuration,proto3,stdduration" json:"max_age_duration"` - // This sets the maximum number of evidence that can be committed in a single block. - // and should fall comfortably under the max block bytes when we consider the size of - // each evidence (See MaxEvidenceBytes). The maximum number is MaxEvidencePerBlock. - // Default is 50 - MaxNum uint32 `protobuf:"varint,3,opt,name=max_num,json=maxNum,proto3" json:"max_num,omitempty"` + // This sets the maximum size of total evidence in bytes that can be committed in a single block. + // and should fall comfortably under the max block bytes. + // Default is 1048576 or 1MB + MaxBytes int64 `protobuf:"varint,3,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` } func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } @@ -233,9 +232,9 @@ func (m *EvidenceParams) GetMaxAgeDuration() time.Duration { return 0 } -func (m *EvidenceParams) GetMaxNum() uint32 { +func (m *EvidenceParams) GetMaxBytes() int64 { if m != nil { - return m.MaxNum + return m.MaxBytes } return 0 } @@ -398,42 +397,41 @@ func init() { func init() { proto.RegisterFile("tendermint/types/params.proto", fileDescriptor_e12598271a686f57) } var fileDescriptor_e12598271a686f57 = []byte{ - // 545 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x4f, 0x6f, 0xd3, 0x30, - 0x1c, 0xad, 0xd7, 0xb2, 0xb5, 0xbf, 0xae, 0xeb, 0x64, 0x21, 0x51, 0x86, 0x96, 0x94, 0x1c, 0xd0, - 0x24, 0xa4, 0x44, 0x82, 0x03, 0x62, 0x97, 0x89, 0xc0, 0x34, 0x10, 0xea, 0x84, 0x22, 0xe0, 0xb0, - 0x4b, 0xe4, 0xb4, 0x26, 0x8b, 0x56, 0xc7, 0x51, 0x6c, 0x57, 0xed, 0xb7, 0xe0, 0xb8, 0xe3, 0x2e, - 0x48, 0x7c, 0x04, 0x3e, 0xc2, 0x8e, 0x3b, 0x72, 0x02, 0xd4, 0x5e, 0xf8, 0x18, 0x28, 0x4e, 0x4d, - 0xff, 0x6c, 0xb7, 0xe4, 0xf7, 0x7b, 0xef, 0xd9, 0xef, 0x3d, 0x19, 0xf6, 0x25, 0x4d, 0x07, 0x34, - 0x67, 0x49, 0x2a, 0x3d, 0x39, 0xc9, 0xa8, 0xf0, 0x32, 0x92, 0x13, 0x26, 0xdc, 0x2c, 0xe7, 0x92, - 0xe3, 0xdd, 0xc5, 0xda, 0xd5, 0xeb, 0xbd, 0xfb, 0x31, 0x8f, 0xb9, 0x5e, 0x7a, 0xc5, 0x57, 0x89, - 0xdb, 0xb3, 0x62, 0xce, 0xe3, 0x21, 0xf5, 0xf4, 0x5f, 0xa4, 0xbe, 0x78, 0x03, 0x95, 0x13, 0x99, - 0xf0, 0xb4, 0xdc, 0x3b, 0x97, 0x1b, 0xd0, 0x7e, 0xcd, 0x53, 0x41, 0x53, 0xa1, 0xc4, 0x07, 0x7d, - 0x02, 0x7e, 0x09, 0xf7, 0xa2, 0x21, 0xef, 0x5f, 0x74, 0x50, 0x17, 0x1d, 0x34, 0x9f, 0xed, 0xbb, - 0xeb, 0x67, 0xb9, 0x7e, 0xb1, 0x2e, 0xd1, 0x7e, 0xed, 0xfa, 0x97, 0x5d, 0x09, 0x4a, 0x06, 0xf6, - 0xa1, 0x4e, 0x47, 0xc9, 0x80, 0xa6, 0x7d, 0xda, 0xd9, 0xd0, 0xec, 0xee, 0x6d, 0xf6, 0xf1, 0x1c, - 0xb1, 0x22, 0xf0, 0x9f, 0x87, 0x8f, 0xa1, 0x31, 0x22, 0xc3, 0x64, 0x40, 0x24, 0xcf, 0x3b, 0x55, - 0x2d, 0xf2, 0xf8, 0xb6, 0xc8, 0x67, 0x03, 0x59, 0x51, 0x59, 0x30, 0xf1, 0x11, 0x6c, 0x8d, 0x68, - 0x2e, 0x12, 0x9e, 0x76, 0x6a, 0x5a, 0xc4, 0xbe, 0x43, 0xa4, 0x04, 0xac, 0x48, 0x18, 0x96, 0x43, - 0xa1, 0xb9, 0xe4, 0x13, 0x3f, 0x82, 0x06, 0x23, 0xe3, 0x30, 0x9a, 0x48, 0x2a, 0x74, 0x32, 0xd5, - 0xa0, 0xce, 0xc8, 0xd8, 0x2f, 0xfe, 0xf1, 0x03, 0xd8, 0x2a, 0x96, 0x31, 0x11, 0xda, 0x76, 0x35, - 0xd8, 0x64, 0x64, 0x7c, 0x42, 0x04, 0xee, 0xc2, 0xb6, 0x4c, 0x18, 0x0d, 0x13, 0x2e, 0x49, 0xc8, - 0x84, 0xf6, 0x53, 0x0d, 0xa0, 0x98, 0xbd, 0xe3, 0x92, 0xf4, 0x84, 0xf3, 0x0d, 0xc1, 0xce, 0x6a, - 0x22, 0xf8, 0x29, 0xe0, 0x42, 0x8d, 0xc4, 0x34, 0x4c, 0x15, 0x0b, 0x75, 0xb4, 0xe6, 0xcc, 0x36, - 0x23, 0xe3, 0x57, 0x31, 0x3d, 0x55, 0x4c, 0x5f, 0x4e, 0xe0, 0x1e, 0xec, 0x1a, 0xb0, 0xe9, 0x76, - 0x1e, 0xfd, 0x43, 0xb7, 0x2c, 0xdf, 0x35, 0xe5, 0xbb, 0x6f, 0xe6, 0x00, 0xbf, 0x5e, 0x58, 0xbd, - 0xfc, 0x6d, 0xa3, 0x60, 0xa7, 0xd4, 0x33, 0x1b, 0xe3, 0x24, 0x55, 0x4c, 0xdf, 0xb5, 0xa5, 0x9d, - 0x9c, 0x2a, 0xe6, 0x1c, 0x41, 0x7b, 0x2d, 0x73, 0xec, 0x40, 0x2b, 0x53, 0x51, 0x78, 0x41, 0x27, - 0xa1, 0xce, 0xb3, 0x83, 0xba, 0xd5, 0x83, 0x46, 0xd0, 0xcc, 0x54, 0xf4, 0x9e, 0x4e, 0x3e, 0x16, - 0xa3, 0xc3, 0xfa, 0x8f, 0x2b, 0x1b, 0xfd, 0xbd, 0xb2, 0x91, 0x73, 0x08, 0xad, 0x95, 0xbc, 0xb1, - 0x0d, 0x4d, 0x92, 0x65, 0xa1, 0x69, 0xa9, 0xf0, 0x57, 0x0b, 0x80, 0x64, 0xd9, 0x1c, 0xb6, 0xc4, - 0x3d, 0x83, 0xed, 0xb7, 0x44, 0x9c, 0xd3, 0xc1, 0x9c, 0xfa, 0x04, 0xda, 0x3a, 0x95, 0x70, 0xbd, - 0x92, 0x96, 0x1e, 0xf7, 0x4c, 0x2f, 0x0e, 0xb4, 0x16, 0xb8, 0x45, 0x3b, 0x4d, 0x83, 0x3a, 0x21, - 0xc2, 0xff, 0xf4, 0x7d, 0x6a, 0xa1, 0xeb, 0xa9, 0x85, 0x6e, 0xa6, 0x16, 0xfa, 0x33, 0xb5, 0xd0, - 0xd7, 0x99, 0x55, 0xb9, 0x99, 0x59, 0x95, 0x9f, 0x33, 0xab, 0x72, 0xf6, 0x22, 0x4e, 0xe4, 0xb9, - 0x8a, 0xdc, 0x3e, 0x67, 0xde, 0xf2, 0x93, 0x5c, 0x7c, 0x96, 0x6f, 0x6e, 0xfd, 0xb9, 0x46, 0x9b, - 0x7a, 0xfe, 0xfc, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x53, 0x0a, 0x6c, 0x0d, 0xc9, 0x03, 0x00, - 0x00, + // 537 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x31, 0x6f, 0xd3, 0x40, + 0x18, 0xcd, 0xd5, 0xa5, 0x4d, 0xbe, 0x34, 0x4d, 0x75, 0x42, 0x22, 0x14, 0xd5, 0x0e, 0x1e, 0x50, + 0x25, 0x24, 0x5b, 0x82, 0x01, 0xd1, 0xa5, 0xc2, 0x50, 0x15, 0x84, 0x82, 0x90, 0x05, 0x0c, 0x5d, + 0xac, 0x73, 0x72, 0xb8, 0x56, 0x73, 0x3e, 0xcb, 0x77, 0x8e, 0x92, 0x7f, 0xc1, 0xd8, 0xb1, 0x23, + 0xfc, 0x03, 0x7e, 0x42, 0xc7, 0x8e, 0x4c, 0x80, 0x92, 0x85, 0x9f, 0x81, 0x7c, 0xce, 0xe1, 0x38, + 0x65, 0xf3, 0x7d, 0xdf, 0x7b, 0xef, 0xfc, 0xde, 0xd3, 0xc1, 0x81, 0xa4, 0xc9, 0x88, 0x66, 0x2c, + 0x4e, 0xa4, 0x2b, 0x67, 0x29, 0x15, 0x6e, 0x4a, 0x32, 0xc2, 0x84, 0x93, 0x66, 0x5c, 0x72, 0xbc, + 0x57, 0xad, 0x1d, 0xb5, 0xde, 0xbf, 0x1b, 0xf1, 0x88, 0xab, 0xa5, 0x5b, 0x7c, 0x95, 0xb8, 0x7d, + 0x33, 0xe2, 0x3c, 0x1a, 0x53, 0x57, 0x9d, 0xc2, 0xfc, 0xb3, 0x3b, 0xca, 0x33, 0x22, 0x63, 0x9e, + 0x94, 0x7b, 0xfb, 0x72, 0x03, 0xba, 0x2f, 0x79, 0x22, 0x68, 0x22, 0x72, 0xf1, 0x5e, 0xdd, 0x80, + 0x9f, 0xc3, 0x9d, 0x70, 0xcc, 0x87, 0x17, 0x3d, 0xd4, 0x47, 0x87, 0xed, 0x27, 0x07, 0xce, 0xfa, + 0x5d, 0x8e, 0x57, 0xac, 0x4b, 0xb4, 0xb7, 0x79, 0xfd, 0xd3, 0x6a, 0xf8, 0x25, 0x03, 0x7b, 0xd0, + 0xa4, 0x93, 0x78, 0x44, 0x93, 0x21, 0xed, 0x6d, 0x28, 0x76, 0xff, 0x36, 0xfb, 0x64, 0x89, 0xa8, + 0x09, 0xfc, 0xe3, 0xe1, 0x13, 0x68, 0x4d, 0xc8, 0x38, 0x1e, 0x11, 0xc9, 0xb3, 0x9e, 0xa1, 0x44, + 0x1e, 0xde, 0x16, 0xf9, 0xa4, 0x21, 0x35, 0x95, 0x8a, 0x89, 0x8f, 0x61, 0x7b, 0x42, 0x33, 0x11, + 0xf3, 0xa4, 0xb7, 0xa9, 0x44, 0xac, 0xff, 0x88, 0x94, 0x80, 0x9a, 0x84, 0x66, 0xd9, 0x14, 0xda, + 0x2b, 0x3e, 0xf1, 0x03, 0x68, 0x31, 0x32, 0x0d, 0xc2, 0x99, 0xa4, 0x42, 0x25, 0x63, 0xf8, 0x4d, + 0x46, 0xa6, 0x5e, 0x71, 0xc6, 0xf7, 0x60, 0xbb, 0x58, 0x46, 0x44, 0x28, 0xdb, 0x86, 0xbf, 0xc5, + 0xc8, 0xf4, 0x94, 0x08, 0xdc, 0x87, 0x1d, 0x19, 0x33, 0x1a, 0xc4, 0x5c, 0x92, 0x80, 0x09, 0xe5, + 0xc7, 0xf0, 0xa1, 0x98, 0xbd, 0xe1, 0x92, 0x0c, 0x84, 0xfd, 0x0d, 0xc1, 0x6e, 0x3d, 0x11, 0xfc, + 0x18, 0x70, 0xa1, 0x46, 0x22, 0x1a, 0x24, 0x39, 0x0b, 0x54, 0xb4, 0xfa, 0xce, 0x2e, 0x23, 0xd3, + 0x17, 0x11, 0x7d, 0x97, 0x33, 0xf5, 0x73, 0x02, 0x0f, 0x60, 0x4f, 0x83, 0x75, 0xb7, 0xcb, 0xe8, + 0xef, 0x3b, 0x65, 0xf9, 0x8e, 0x2e, 0xdf, 0x79, 0xb5, 0x04, 0x78, 0xcd, 0xc2, 0xea, 0xe5, 0x2f, + 0x0b, 0xf9, 0xbb, 0xa5, 0x9e, 0xde, 0xd4, 0x6d, 0x1a, 0x75, 0x9b, 0xf6, 0x31, 0x74, 0xd7, 0x72, + 0xc7, 0x36, 0x74, 0xd2, 0x3c, 0x0c, 0x2e, 0xe8, 0x2c, 0x50, 0x99, 0xf6, 0x50, 0xdf, 0x38, 0x6c, + 0xf9, 0xed, 0x34, 0x0f, 0xdf, 0xd2, 0xd9, 0x87, 0x62, 0x74, 0xd4, 0xfc, 0x7e, 0x65, 0xa1, 0x3f, + 0x57, 0x16, 0xb2, 0x8f, 0xa0, 0x53, 0xcb, 0x1c, 0x5b, 0xd0, 0x26, 0x69, 0x1a, 0xe8, 0xa6, 0x0a, + 0x8f, 0x9b, 0x3e, 0x90, 0x34, 0x5d, 0xc2, 0x56, 0xb8, 0x67, 0xb0, 0xf3, 0x9a, 0x88, 0x73, 0x3a, + 0x5a, 0x52, 0x1f, 0x41, 0x57, 0x25, 0x13, 0xac, 0xd7, 0xd2, 0x51, 0xe3, 0x81, 0xee, 0xc6, 0x86, + 0x4e, 0x85, 0xab, 0x1a, 0x6a, 0x6b, 0xd4, 0x29, 0x11, 0xde, 0xc7, 0xaf, 0x73, 0x13, 0x5d, 0xcf, + 0x4d, 0x74, 0x33, 0x37, 0xd1, 0xef, 0xb9, 0x89, 0xbe, 0x2c, 0xcc, 0xc6, 0xcd, 0xc2, 0x6c, 0xfc, + 0x58, 0x98, 0x8d, 0xb3, 0x67, 0x51, 0x2c, 0xcf, 0xf3, 0xd0, 0x19, 0x72, 0xe6, 0xae, 0x3e, 0xcb, + 0xea, 0xb3, 0x7c, 0x77, 0xeb, 0x4f, 0x36, 0xdc, 0x52, 0xf3, 0xa7, 0x7f, 0x03, 0x00, 0x00, 0xff, + 0xff, 0xfe, 0xe0, 0x3d, 0x9c, 0xcd, 0x03, 0x00, 0x00, } func (this *ConsensusParams) Equal(that interface{}) bool { @@ -524,7 +522,7 @@ func (this *EvidenceParams) Equal(that interface{}) bool { if this.MaxAgeDuration != that1.MaxAgeDuration { return false } - if this.MaxNum != that1.MaxNum { + if this.MaxBytes != that1.MaxBytes { return false } return true @@ -730,8 +728,8 @@ func (m *EvidenceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.MaxNum != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.MaxNum)) + if m.MaxBytes != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxBytes)) i-- dAtA[i] = 0x18 } @@ -993,8 +991,8 @@ func (m *EvidenceParams) Size() (n int) { } l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxAgeDuration) n += 1 + l + sovParams(uint64(l)) - if m.MaxNum != 0 { - n += 1 + sovParams(uint64(m.MaxNum)) + if m.MaxBytes != 0 { + n += 1 + sovParams(uint64(m.MaxBytes)) } return n } @@ -1425,9 +1423,9 @@ func (m *EvidenceParams) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 3: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxNum", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MaxBytes", wireType) } - m.MaxNum = 0 + m.MaxBytes = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -1437,7 +1435,7 @@ func (m *EvidenceParams) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MaxNum |= uint32(b&0x7F) << shift + m.MaxBytes |= int64(b&0x7F) << shift if b < 0x80 { break } diff --git a/proto/tendermint/types/params.proto b/proto/tendermint/types/params.proto index 897c07c17f..0de7d846fb 100644 --- a/proto/tendermint/types/params.proto +++ b/proto/tendermint/types/params.proto @@ -48,11 +48,10 @@ message EvidenceParams { google.protobuf.Duration max_age_duration = 2 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; - // This sets the maximum number of evidence that can be committed in a single block. - // and should fall comfortably under the max block bytes when we consider the size of - // each evidence (See MaxEvidenceBytes). The maximum number is MaxEvidencePerBlock. - // Default is 50 - uint32 max_num = 3; + // This sets the maximum size of total evidence in bytes that can be committed in a single block. + // and should fall comfortably under the max block bytes. + // Default is 1048576 or 1MB + int64 max_bytes = 3; } // ValidatorParams restrict the public key types validators can use. diff --git a/state/execution.go b/state/execution.go index 41ea8a9b05..2cb5857a19 100644 --- a/state/execution.go +++ b/state/execution.go @@ -100,10 +100,10 @@ func (blockExec *BlockExecutor) CreateProposalBlock( maxBytes := state.ConsensusParams.Block.MaxBytes maxGas := state.ConsensusParams.Block.MaxGas - evidence := blockExec.evpool.PendingEvidence(state.ConsensusParams.Evidence.MaxNum) + evidence, evSize := blockExec.evpool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) // Fetch a limited amount of valid txs - maxDataBytes := types.MaxDataBytes(maxBytes, state.Validators.Size(), len(evidence)) + maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size()) txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) return state.MakeBlock(height, txs, commit, evidence, proposerAddr) diff --git a/state/mocks/evidence_pool.go b/state/mocks/evidence_pool.go index 0915c9966e..bfd82e5964 100644 --- a/state/mocks/evidence_pool.go +++ b/state/mocks/evidence_pool.go @@ -61,11 +61,11 @@ func (_m *EvidencePool) CheckEvidence(_a0 types.EvidenceList) error { } // PendingEvidence provides a mock function with given fields: _a0 -func (_m *EvidencePool) PendingEvidence(_a0 uint32) []types.Evidence { +func (_m *EvidencePool) PendingEvidence(_a0 int64) ([]types.Evidence, int64) { ret := _m.Called(_a0) var r0 []types.Evidence - if rf, ok := ret.Get(0).(func(uint32) []types.Evidence); ok { + if rf, ok := ret.Get(0).(func(int64) []types.Evidence); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -73,7 +73,14 @@ func (_m *EvidencePool) PendingEvidence(_a0 uint32) []types.Evidence { } } - return r0 + var r1 int64 + if rf, ok := ret.Get(1).(func(int64) int64); ok { + r1 = rf(_a0) + } else { + r1 = ret.Get(1).(int64) + } + + return r0, r1 } // Update provides a mock function with given fields: _a0 diff --git a/state/services.go b/state/services.go index bf00b3fe07..e2f12b2378 100644 --- a/state/services.go +++ b/state/services.go @@ -43,7 +43,7 @@ type BlockStore interface { // EvidencePool defines the EvidencePool interface used by State. type EvidencePool interface { - PendingEvidence(uint32) []types.Evidence + PendingEvidence(maxBytes int64) (ev []types.Evidence, size int64) AddEvidence(types.Evidence) error Update(State) CheckEvidence(types.EvidenceList) error @@ -54,7 +54,9 @@ type EvidencePool interface { // to the consensus evidence pool interface type EmptyEvidencePool struct{} -func (EmptyEvidencePool) PendingEvidence(uint32) []types.Evidence { return nil } +func (EmptyEvidencePool) PendingEvidence(maxBytes int64) (ev []types.Evidence, size int64) { + return nil, 0 +} func (EmptyEvidencePool) AddEvidence(types.Evidence) error { return nil } func (EmptyEvidencePool) Update(State) {} func (EmptyEvidencePool) CheckEvidence(evList types.EvidenceList) error { return nil } diff --git a/state/tx_filter.go b/state/tx_filter.go index acc4aad8ff..52d0559660 100644 --- a/state/tx_filter.go +++ b/state/tx_filter.go @@ -8,10 +8,9 @@ import ( // TxPreCheck returns a function to filter transactions before processing. // The function limits the size of a transaction to the block's maximum data size. func TxPreCheck(state State) mempl.PreCheckFunc { - maxDataBytes := types.MaxDataBytesUnknownEvidence( + maxDataBytes := types.MaxDataBytesNoEvidence( state.ConsensusParams.Block.MaxBytes, state.Validators.Size(), - state.ConsensusParams.Evidence.MaxNum, ) return mempl.PreCheckMaxBytes(maxDataBytes) } diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index fa53923372..f5923b84be 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -17,7 +17,7 @@ import ( func TestTxFilter(t *testing.T) { genDoc := randomGenesisDoc() genDoc.ConsensusParams.Block.MaxBytes = 3000 - genDoc.ConsensusParams.Evidence.MaxNum = 1 + genDoc.ConsensusParams.Evidence.MaxBytes = 1500 // Max size of Txs is much smaller than size of block, // since we need to account for commits and evidence. @@ -25,8 +25,8 @@ func TestTxFilter(t *testing.T) { tx types.Tx isErr bool }{ - {types.Tx(tmrand.Bytes(1680)), false}, - {types.Tx(tmrand.Bytes(1853)), true}, + {types.Tx(tmrand.Bytes(2154)), false}, + {types.Tx(tmrand.Bytes(2155)), true}, {types.Tx(tmrand.Bytes(3000)), true}, } diff --git a/state/validation.go b/state/validation.go index 4bcf117219..779edf2734 100644 --- a/state/validation.go +++ b/state/validation.go @@ -142,8 +142,8 @@ func validateBlock(evidencePool EvidencePool, state State, block *types.Block) e block.Height, state.InitialHeight) } - // Check evidence doesn't exceed the limit. MaxNumEvidence is capped at uint16, so conversion is always safe. - if max, got := int(state.ConsensusParams.Evidence.MaxNum), len(block.Evidence.Evidence); got > max { + // Check evidence doesn't exceed the limit amount of bytes. + if max, got := state.ConsensusParams.Evidence.MaxBytes, block.Evidence.ByteSize(); got > max { return types.NewErrEvidenceOverflow(max, got) } diff --git a/state/validation_test.go b/state/validation_test.go index a4b0667c8d..8fa5f89b3d 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -222,7 +222,7 @@ func TestValidateBlockEvidence(t *testing.T) { evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return( []abci.Evidence{}) - state.ConsensusParams.Evidence.MaxNum = 3 + state.ConsensusParams.Evidence.MaxBytes = 1000 blockExec := sm.NewBlockExecutor( stateStore, log.TestingLogger(), @@ -234,17 +234,19 @@ func TestValidateBlockEvidence(t *testing.T) { for height := int64(1); height < validationTestsStopHeight; height++ { proposerAddr := state.Validators.GetProposer().Address - maxNumEvidence := state.ConsensusParams.Evidence.MaxNum + maxBytesEvidence := state.ConsensusParams.Evidence.MaxBytes if height > 1 { /* A block with too much evidence fails */ - require.True(t, maxNumEvidence > 2) evidence := make([]types.Evidence, 0) - // one more than the maximum allowed evidence - for i := uint32(0); i <= maxNumEvidence; i++ { - evidence = append(evidence, types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), - privVals[proposerAddr.String()], chainID)) + var currentBytes int64 = 0 + // more bytes than the maximum allowed for evidence + for currentBytes <= maxBytesEvidence { + newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), + privVals[proposerAddr.String()], chainID) + evidence = append(evidence, newEv) + currentBytes += int64(len(newEv.Bytes())) } block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) err := blockExec.ValidateBlock(state, block) @@ -257,14 +259,17 @@ func TestValidateBlockEvidence(t *testing.T) { /* A good block with several pieces of good evidence passes */ - require.True(t, maxNumEvidence > 2) evidence := make([]types.Evidence, 0) + var currentBytes int64 = 0 // precisely the amount of allowed evidence - for i := int32(0); uint32(i) < maxNumEvidence; i++ { - // make different evidence for each validator - _, val := state.Validators.GetByIndex(i) - evidence = append(evidence, types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, - privVals[val.Address.String()], chainID)) + for { + newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, + privVals[proposerAddr.String()], chainID) + currentBytes += int64(len(newEv.Bytes())) + if currentBytes >= maxBytesEvidence { + break + } + evidence = append(evidence, newEv) } var err error diff --git a/types/block.go b/types/block.go index 749770768b..f4942a2431 100644 --- a/types/block.go +++ b/types/block.go @@ -273,12 +273,12 @@ func BlockFromProto(bp *tmproto.Block) (*Block, error) { // MaxDataBytes returns the maximum size of block's data. // // XXX: Panics on negative result. -func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { +func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - MaxHeaderBytes - int64(valsCount)*MaxVoteBytes - - int64(evidenceCount)*MaxEvidenceBytes + evidenceBytes if maxDataBytes < 0 { panic(fmt.Sprintf( @@ -292,18 +292,16 @@ func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { } -// MaxDataBytesUnknownEvidence returns the maximum size of block's data when +// MaxDataBytesNoEvidence returns the maximum size of block's data when // evidence count is unknown. MaxEvidencePerBlock will be used for the size // of evidence. // // XXX: Panics on negative result. -func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int, maxNumEvidence uint32) int64 { - maxEvidenceBytes := int64(maxNumEvidence) * MaxEvidenceBytes +func MaxDataBytesNoEvidence(maxBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes - - maxEvidenceBytes + int64(valsCount)*MaxVoteBytes if maxDataBytes < 0 { panic(fmt.Sprintf( @@ -1073,8 +1071,9 @@ func DataFromProto(dp *tmproto.Data) (Data, error) { type EvidenceData struct { Evidence EvidenceList `json:"evidence"` - // Volatile - hash tmbytes.HexBytes + // Volatile. Used as cache + hash tmbytes.HexBytes + byteSize int64 } // Hash returns the hash of the data. @@ -1085,6 +1084,20 @@ func (data *EvidenceData) Hash() tmbytes.HexBytes { return data.hash } +// ByteSize returns the total byte size of all the evidence +func (data *EvidenceData) ByteSize() int64 { + if data.byteSize == 0 && len(data.Evidence) != 0 { + for _, ev := range data.Evidence { + pb, err := EvidenceToProto(ev) + if err != nil { + panic(err) + } + data.byteSize += int64(pb.Size()) + } + } + return data.byteSize +} + // StringIndented returns a string representation of the evidence. func (data *EvidenceData) StringIndented(indent string) string { if data == nil { @@ -1142,11 +1155,10 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceData) error { return err } eviBzs[i] = evi + data.byteSize += int64(eviData.Evidence[i].Size()) } data.Evidence = eviBzs - data.hash = eviData.GetHash() - return nil } diff --git a/types/block_test.go b/types/block_test.go index eb7d29237a..8e2d65f965 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -401,7 +401,7 @@ func TestBlockMaxDataBytes(t *testing.T) { testCases := []struct { maxBytes int64 valsCount int - evidenceCount int + evidenceBytes int64 panics bool result int64 }{ @@ -416,43 +416,41 @@ func TestBlockMaxDataBytes(t *testing.T) { tc := tc if tc.panics { assert.Panics(t, func() { - MaxDataBytes(tc.maxBytes, tc.valsCount, tc.evidenceCount) + MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount) }, "#%v", i) } else { assert.Equal(t, tc.result, - MaxDataBytes(tc.maxBytes, tc.valsCount, tc.evidenceCount), + MaxDataBytes(tc.maxBytes, tc.evidenceBytes, tc.valsCount), "#%v", i) } } } -func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { +func TestBlockMaxDataBytesNoEvidence(t *testing.T) { testCases := []struct { - maxBytes int64 - maxEvidence uint32 - valsCount int - panics bool - result int64 + maxBytes int64 + valsCount int + panics bool + result int64 }{ - 0: {-10, 0, 1, true, 0}, - 1: {10, 0, 1, true, 0}, - 2: {845, 0, 1, true, 0}, - 3: {846, 0, 1, false, 0}, - 4: {1290, 1, 1, false, 0}, - 5: {1291, 1, 1, false, 1}, + 0: {-10, 1, true, 0}, + 1: {10, 1, true, 0}, + 2: {845, 1, true, 0}, + 3: {846, 1, false, 0}, + 4: {847, 1, false, 1}, } for i, tc := range testCases { tc := tc if tc.panics { assert.Panics(t, func() { - MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount, tc.maxEvidence) + MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount) }, "#%v", i) } else { assert.Equal(t, tc.result, - MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount, tc.maxEvidence), + MaxDataBytesNoEvidence(tc.maxBytes, tc.valsCount), "#%v", i) } } @@ -620,7 +618,7 @@ func TestBlockProtoBuf(t *testing.T) { require.NoError(t, err, tc.msg) require.EqualValues(t, tc.b1.Header, block.Header, tc.msg) require.EqualValues(t, tc.b1.Data, block.Data, tc.msg) - require.EqualValues(t, tc.b1.Evidence, block.Evidence, tc.msg) + require.EqualValues(t, tc.b1.Evidence.Evidence, block.Evidence.Evidence, tc.msg) require.EqualValues(t, *tc.b1.LastCommit, *block.LastCommit, tc.msg) } else { require.Error(t, err, tc.msg) @@ -653,6 +651,7 @@ func TestDataProtoBuf(t *testing.T) { } } +// TestEvidenceDataProtoBuf ensures parity in converting to and from proto. func TestEvidenceDataProtoBuf(t *testing.T) { val := NewMockPV() blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) @@ -662,7 +661,7 @@ func TestEvidenceDataProtoBuf(t *testing.T) { v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, time.Now()) ev := NewDuplicateVoteEvidence(v2, v) data := &EvidenceData{Evidence: EvidenceList{ev}} - _ = data.Hash() + _ = data.ByteSize() testCases := []struct { msg string data1 *EvidenceData diff --git a/types/evidence.go b/types/evidence.go index f6a8c560f6..5b450f9e1c 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -25,11 +25,6 @@ type Evidence interface { String() string // string format of the evidence } -const ( - // MaxEvidenceBytes is a maximum size of any evidence (including amino overhead). - MaxEvidenceBytes int64 = 444 -) - //-------------------------------------------------------------------------------------- // DuplicateVoteEvidence contains evidence a validator signed two conflicting @@ -365,20 +360,20 @@ func (err *ErrInvalidEvidence) Error() string { return fmt.Sprintf("Invalid evidence: %v. Evidence: %v", err.Reason, err.Evidence) } -// ErrEvidenceOverflow is for when there is too much evidence in a block. +// ErrEvidenceOverflow is for when there the amount of evidence exceeds the max bytes. type ErrEvidenceOverflow struct { - MaxNum int - GotNum int + Max int64 + Got int64 } // NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max. -func NewErrEvidenceOverflow(max, got int) *ErrEvidenceOverflow { +func NewErrEvidenceOverflow(max, got int64) *ErrEvidenceOverflow { return &ErrEvidenceOverflow{max, got} } // Error returns a string representation of the error. func (err *ErrEvidenceOverflow) Error() string { - return fmt.Sprintf("Too much evidence: Max %d, got %d", err.MaxNum, err.GotNum) + return fmt.Sprintf("Too much evidence: Max %d, got %d", err.Max, err.Got) } //-------------------------------------------- MOCKING -------------------------------------- diff --git a/types/evidence_test.go b/types/evidence_test.go index 1afbf6a99f..4982a7d90d 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -27,37 +27,6 @@ func TestEvidenceList(t *testing.T) { assert.False(t, evl.Has(&DuplicateVoteEvidence{})) } -func TestMaxEvidenceBytes(t *testing.T) { - val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - maxTime := time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC) - const chainID = "mychain" - ev := &DuplicateVoteEvidence{ - VoteA: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, math.MaxInt64, blockID, maxTime), - VoteB: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, math.MaxInt64, blockID2, maxTime), - } - - //TODO: Add other types of evidence to test and set MaxEvidenceBytes accordingly - - testCases := []struct { - testName string - evidence Evidence - }{ - {"DuplicateVote", ev}, - } - - for _, tt := range testCases { - pb, err := EvidenceToProto(tt.evidence) - require.NoError(t, err, tt.testName) - bz, err := pb.Marshal() - require.NoError(t, err, tt.testName) - - assert.LessOrEqual(t, int64(len(bz)), MaxEvidenceBytes, tt.testName) - } - -} - func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { val := NewMockPV() blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) diff --git a/types/params.go b/types/params.go index 1466faa60e..16c85aa557 100644 --- a/types/params.go +++ b/types/params.go @@ -19,9 +19,6 @@ const ( // MaxBlockPartsCount is the maximum number of block parts. MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1 - - // Restrict the upper bound of the amount of evidence (uses uint16 for safe conversion) - MaxEvidencePerBlock = 65535 ) // DefaultConsensusParams returns a default ConsensusParams. @@ -48,7 +45,7 @@ func DefaultEvidenceParams() tmproto.EvidenceParams { return tmproto.EvidenceParams{ MaxAgeNumBlocks: 100000, // 27.8 hrs at 1block/s MaxAgeDuration: 48 * time.Hour, - MaxNum: 50, + MaxBytes: 1048576, // 1MB } } @@ -98,23 +95,23 @@ func ValidateConsensusParams(params tmproto.ConsensusParams) error { } if params.Evidence.MaxAgeNumBlocks <= 0 { - return fmt.Errorf("evidenceParams.MaxAgeNumBlocks must be greater than 0. Got %d", + return fmt.Errorf("evidence.MaxAgeNumBlocks must be greater than 0. Got %d", params.Evidence.MaxAgeNumBlocks) } if params.Evidence.MaxAgeDuration <= 0 { - return fmt.Errorf("evidenceParams.MaxAgeDuration must be grater than 0 if provided, Got %v", + return fmt.Errorf("evidence.MaxAgeDuration must be grater than 0 if provided, Got %v", params.Evidence.MaxAgeDuration) } - if params.Evidence.MaxNum > MaxEvidencePerBlock { - return fmt.Errorf("evidenceParams.MaxNumEvidence is greater than upper bound, %d > %d", - params.Evidence.MaxNum, MaxEvidencePerBlock) + if params.Evidence.MaxBytes > params.Block.MaxBytes { + return fmt.Errorf("evidence.MaxBytesEvidence is greater than upper bound, %d > %d", + params.Evidence.MaxBytes, params.Block.MaxBytes) } - if int64(params.Evidence.MaxNum)*MaxEvidenceBytes > params.Block.MaxBytes { - return fmt.Errorf("total possible evidence size is bigger than block.MaxBytes, %d > %d", - int64(params.Evidence.MaxNum)*MaxEvidenceBytes, params.Block.MaxBytes) + if params.Evidence.MaxBytes < 0 { + return fmt.Errorf("evidence.MaxBytes must be non negative. Got: %d", + params.Evidence.MaxBytes) } if len(params.Validator.PubKeyTypes) == 0 { @@ -174,7 +171,7 @@ func UpdateConsensusParams(params tmproto.ConsensusParams, params2 *abci.Consens if params2.Evidence != nil { res.Evidence.MaxAgeNumBlocks = params2.Evidence.MaxAgeNumBlocks res.Evidence.MaxAgeDuration = params2.Evidence.MaxAgeDuration - res.Evidence.MaxNum = params2.Evidence.MaxNum + res.Evidence.MaxBytes = params2.Evidence.MaxBytes } if params2.Validator != nil { // Copy params2.Validator.PubkeyTypes, and set result's value to the copy. diff --git a/types/params_test.go b/types/params_test.go index e266c2389d..e6fbc4cade 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -33,7 +33,7 @@ func TestConsensusParamsValidation(t *testing.T) { 8: {makeParams(1, 0, -10, 2, 0, valEd25519), false}, // test evidence params 9: {makeParams(1, 0, 10, 0, 0, valEd25519), false}, - 10: {makeParams(1, 0, 10, 2, 1, valEd25519), false}, + 10: {makeParams(1, 0, 10, 2, 2, valEd25519), false}, 11: {makeParams(1000, 0, 10, 2, 1, valEd25519), true}, 12: {makeParams(1, 0, 10, -1, 0, valEd25519), false}, // test no pubkey type provided @@ -54,7 +54,7 @@ func makeParams( blockBytes, blockGas int64, blockTimeIotaMs int64, evidenceAge int64, - maxEvidence uint32, + maxEvidenceBytes int64, pubkeyTypes []string, ) tmproto.ConsensusParams { return tmproto.ConsensusParams{ @@ -66,7 +66,7 @@ func makeParams( Evidence: tmproto.EvidenceParams{ MaxAgeNumBlocks: evidenceAge, MaxAgeDuration: time.Duration(evidenceAge), - MaxNum: maxEvidence, + MaxBytes: maxEvidenceBytes, }, Validator: tmproto.ValidatorParams{ PubKeyTypes: pubkeyTypes, @@ -124,7 +124,7 @@ func TestConsensusParamsUpdate(t *testing.T) { Evidence: &tmproto.EvidenceParams{ MaxAgeNumBlocks: 300, MaxAgeDuration: time.Duration(300), - MaxNum: 50, + MaxBytes: 50, }, Validator: &tmproto.ValidatorParams{ PubKeyTypes: valEd25519, diff --git a/types/part_set.go b/types/part_set.go index b16fc583c9..5e76b57faa 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -121,7 +121,7 @@ func (psh PartSetHeader) ValidateBasic() error { return nil } -// ToProto converts BloPartSetHeaderckID to protobuf +// ToProto converts PartSetHeader to protobuf func (psh *PartSetHeader) ToProto() tmproto.PartSetHeader { if psh == nil { return tmproto.PartSetHeader{} From 09982ae40764bbec7464f2623793476f48f93aa8 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 13 Oct 2020 18:07:54 +0200 Subject: [PATCH 009/108] backport block size fixes (#5492) * mempool: length prefix txs when getting them from mempool (#5483) * correctly calculate evidence data size (#5482) * block: use commit sig size instead of vote size (#5490) * tx: reduce function to one parameter (#5493) --- blockchain/msgs_test.go | 2 +- mempool/clist_mempool.go | 12 +- mempool/clist_mempool_test.go | 16 +- mempool/mempool.go | 3 +- node/node_test.go | 80 +++++- proto/tendermint/types/evidence.pb.go | 105 ++------ proto/tendermint/types/evidence.proto | 1 - proto/tendermint/types/types.pb.go | 367 +++++++------------------- proto/tendermint/types/types.proto | 5 - rpc/client/rpc_test.go | 2 +- state/execution.go | 1 + state/tx_filter_test.go | 6 +- types/block.go | 59 ++--- types/block_test.go | 70 ++++- types/tx.go | 8 + types/vote.go | 4 +- types/vote_test.go | 33 --- 17 files changed, 320 insertions(+), 454 deletions(-) diff --git a/blockchain/msgs_test.go b/blockchain/msgs_test.go index 097e9a1951..df8efca149 100644 --- a/blockchain/msgs_test.go +++ b/blockchain/msgs_test.go @@ -97,7 +97,7 @@ func TestBlockchainMessageVectors(t *testing.T) { BlockRequest: &bcproto.BlockRequest{Height: math.MaxInt64}}}, "0a0a08ffffffffffffffff7f"}, {"BlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_BlockResponse{ - BlockResponse: &bcproto.BlockResponse{Block: bpb}}}, "1ab5010ab2010a5b0a02080b1803220b088092b8c398feffffff012a0212003a20c4da88e876062aa1543400d50d0eaa0dac88096057949cfb7bca7f3a48c04bf96a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855122f0a0b48656c6c6f20576f726c641220c4da88e876062aa1543400d50d0eaa0dac88096057949cfb7bca7f3a48c04bf91a221220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + BlockResponse: &bcproto.BlockResponse{Block: bpb}}}, "1a700a6e0a5b0a02080b1803220b088092b8c398feffffff012a0212003a20c4da88e876062aa1543400d50d0eaa0dac88096057949cfb7bca7f3a48c04bf96a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855120d0a0b48656c6c6f20576f726c641a00"}, {"NoBlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_NoBlockResponse{ NoBlockResponse: &bcproto.NoBlockResponse{Height: 1}}}, "12020801"}, {"NoBlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_NoBlockResponse{ diff --git a/mempool/clist_mempool.go b/mempool/clist_mempool.go index 48a25e7955..7b0c975222 100644 --- a/mempool/clist_mempool.go +++ b/mempool/clist_mempool.go @@ -517,21 +517,21 @@ func (mem *CListMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { mem.updateMtx.RLock() defer mem.updateMtx.RUnlock() - var ( - totalBytes int64 - totalGas int64 - ) + var totalGas int64 + // TODO: we will get a performance boost if we have a good estimate of avg // size per tx, and set the initial capacity based off of that. // txs := make([]types.Tx, 0, tmmath.MinInt(mem.txs.Len(), max/mem.avgTxSize)) txs := make([]types.Tx, 0, mem.txs.Len()) for e := mem.txs.Front(); e != nil; e = e.Next() { memTx := e.Value.(*mempoolTx) + + dataSize := types.ComputeProtoSizeForTxs(append(txs, memTx.tx)) + // Check total size requirement - if maxBytes > -1 && totalBytes+int64(len(memTx.tx)) > maxBytes { + if maxBytes > -1 && dataSize > maxBytes { return txs } - totalBytes += int64(len(memTx.tx)) // Check total gas requirement. // If maxGas is negative, skip this check. // Since newTotalGas < masGas, which diff --git a/mempool/clist_mempool_test.go b/mempool/clist_mempool_test.go index 5c27db5a76..46c161b657 100644 --- a/mempool/clist_mempool_test.go +++ b/mempool/clist_mempool_test.go @@ -121,11 +121,11 @@ func TestReapMaxBytesMaxGas(t *testing.T) { {20, 0, -1, 0}, {20, 0, 10, 0}, {20, 10, 10, 0}, - {20, 20, 10, 1}, - {20, 100, 5, 5}, - {20, 200, -1, 10}, - {20, 200, 10, 10}, - {20, 200, 15, 10}, + {20, 24, 10, 1}, + {20, 240, 5, 5}, + {20, 240, -1, 10}, + {20, 240, 10, 10}, + {20, 240, 15, 10}, {20, 20000, -1, 20}, {20, 20000, 5, 5}, {20, 20000, 30, 20}, @@ -159,15 +159,15 @@ func TestMempoolFilters(t *testing.T) { }{ {10, nopPreFilter, nopPostFilter, 10}, {10, PreCheckMaxBytes(10), nopPostFilter, 0}, - {10, PreCheckMaxBytes(20), nopPostFilter, 10}, + {10, PreCheckMaxBytes(22), nopPostFilter, 10}, {10, nopPreFilter, PostCheckMaxGas(-1), 10}, {10, nopPreFilter, PostCheckMaxGas(0), 0}, {10, nopPreFilter, PostCheckMaxGas(1), 10}, {10, nopPreFilter, PostCheckMaxGas(3000), 10}, {10, PreCheckMaxBytes(10), PostCheckMaxGas(20), 0}, {10, PreCheckMaxBytes(30), PostCheckMaxGas(20), 10}, - {10, PreCheckMaxBytes(20), PostCheckMaxGas(1), 10}, - {10, PreCheckMaxBytes(20), PostCheckMaxGas(0), 0}, + {10, PreCheckMaxBytes(22), PostCheckMaxGas(1), 10}, + {10, PreCheckMaxBytes(22), PostCheckMaxGas(0), 0}, } for tcIndex, tt := range tests { err := mempool.Update(1, emptyTxArr, abciResponses(len(emptyTxArr), abci.CodeTypeOK), tt.preFilter, tt.postFilter) diff --git a/mempool/mempool.go b/mempool/mempool.go index 3df88a6768..d01958b539 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -105,7 +105,8 @@ type TxInfo struct { // PreCheckMaxBytes checks that the size of the transaction is smaller or equal to the expected maxBytes. func PreCheckMaxBytes(maxBytes int64) PreCheckFunc { return func(tx types.Tx) error { - txSize := int64(len(tx)) + txSize := types.ComputeProtoSizeForTxs([]types.Tx{tx}) + if txSize > maxBytes { return fmt.Errorf("tx size is too big: %d, max: %d", txSize, maxBytes) diff --git a/node/node_test.go b/node/node_test.go index 65a972fc79..f25e6243d9 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -234,13 +234,14 @@ func TestCreateProposalBlock(t *testing.T) { state, stateDB, privVals := state(1, height) stateStore := sm.NewStore(stateDB) maxBytes := 16384 + var partSize uint32 = 256 maxEvidenceBytes := int64(maxBytes / 2) state.ConsensusParams.Block.MaxBytes = int64(maxBytes) state.ConsensusParams.Evidence.MaxBytes = maxEvidenceBytes proposerAddr, _ := state.Validators.GetByIndex(0) // Make Mempool - memplMetrics := mempl.PrometheusMetrics("node_test") + memplMetrics := mempl.PrometheusMetrics("node_test_1") mempool := mempl.NewCListMempool( config.Mempool, proxyApp.Mempool(), @@ -270,8 +271,8 @@ func TestCreateProposalBlock(t *testing.T) { // fill the mempool with more txs // than can fit in a block - txLength := 1000 - for i := 0; i < maxBytes/txLength; i++ { + txLength := 100 + for i := 0; i <= maxBytes/txLength; i++ { tx := tmrand.Bytes(txLength) err := mempool.CheckTx(tx, nil, mempl.TxInfo{}) assert.NoError(t, err) @@ -292,10 +293,83 @@ func TestCreateProposalBlock(t *testing.T) { proposerAddr, ) + // check that the part set does not exceed the maximum block size + partSet := block.MakePartSet(partSize) + assert.Less(t, partSet.ByteSize(), int64(maxBytes)) + + partSetFromHeader := types.NewPartSetFromHeader(partSet.Header()) + for partSetFromHeader.Count() < partSetFromHeader.Total() { + added, err := partSetFromHeader.AddPart(partSet.GetPart(int(partSetFromHeader.Count()))) + require.NoError(t, err) + require.True(t, added) + } + assert.EqualValues(t, partSetFromHeader.ByteSize(), partSet.ByteSize()) + err = blockExec.ValidateBlock(state, block) assert.NoError(t, err) } +func TestMaxProposalBlockSize(t *testing.T) { + config := cfg.ResetTestRoot("node_create_proposal") + defer os.RemoveAll(config.RootDir) + cc := proxy.NewLocalClientCreator(kvstore.NewApplication()) + proxyApp := proxy.NewAppConns(cc) + err := proxyApp.Start() + require.Nil(t, err) + defer proxyApp.Stop() //nolint:errcheck // ignore for tests + + logger := log.TestingLogger() + + var height int64 = 1 + state, stateDB, _ := state(1, height) + stateStore := sm.NewStore(stateDB) + var maxBytes int64 = 16384 + var partSize uint32 = 256 + state.ConsensusParams.Block.MaxBytes = maxBytes + proposerAddr, _ := state.Validators.GetByIndex(0) + + // Make Mempool + memplMetrics := mempl.PrometheusMetrics("node_test_2") + mempool := mempl.NewCListMempool( + config.Mempool, + proxyApp.Mempool(), + state.LastBlockHeight, + mempl.WithMetrics(memplMetrics), + mempl.WithPreCheck(sm.TxPreCheck(state)), + mempl.WithPostCheck(sm.TxPostCheck(state)), + ) + mempool.SetLogger(logger) + + // fill the mempool with one txs just below the maximum size + txLength := int(types.MaxDataBytesNoEvidence(maxBytes, 1)) + tx := tmrand.Bytes(txLength - 4) // to account for the varint + err = mempool.CheckTx(tx, nil, mempl.TxInfo{}) + assert.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp.Consensus(), + mempool, + sm.EmptyEvidencePool{}, + ) + + commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) + block, _ := blockExec.CreateProposalBlock( + height, + state, commit, + proposerAddr, + ) + + pb, err := block.ToProto() + require.NoError(t, err) + assert.Less(t, int64(pb.Size()), maxBytes) + + // check that the part set does not exceed the maximum block size + partSet := block.MakePartSet(partSize) + assert.EqualValues(t, partSet.ByteSize(), int64(pb.Size())) +} + func TestNodeNewNodeCustomReactors(t *testing.T) { config := cfg.ResetTestRoot("node_new_node_custom_reactors_test") defer os.RemoveAll(config.RootDir) diff --git a/proto/tendermint/types/evidence.pb.go b/proto/tendermint/types/evidence.pb.go index c44fc074f7..c1ed2f09f0 100644 --- a/proto/tendermint/types/evidence.pb.go +++ b/proto/tendermint/types/evidence.pb.go @@ -217,7 +217,6 @@ func (*Evidence) XXX_OneofWrappers() []interface{} { // EvidenceData contains any evidence of malicious wrong-doing by validators type EvidenceData struct { Evidence []Evidence `protobuf:"bytes,1,rep,name=evidence,proto3" json:"evidence"` - Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` } func (m *EvidenceData) Reset() { *m = EvidenceData{} } @@ -260,13 +259,6 @@ func (m *EvidenceData) GetEvidence() []Evidence { return nil } -func (m *EvidenceData) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - func init() { proto.RegisterType((*DuplicateVoteEvidence)(nil), "tendermint.types.DuplicateVoteEvidence") proto.RegisterType((*LightClientAttackEvidence)(nil), "tendermint.types.LightClientAttackEvidence") @@ -277,32 +269,32 @@ func init() { func init() { proto.RegisterFile("tendermint/types/evidence.proto", fileDescriptor_6825fabc78e0a168) } var fileDescriptor_6825fabc78e0a168 = []byte{ - // 398 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xcd, 0x6a, 0xea, 0x40, - 0x14, 0x4e, 0xae, 0x3f, 0xc8, 0xe8, 0x05, 0x6f, 0xb8, 0xb6, 0x2a, 0x12, 0x4b, 0xba, 0xa8, 0x50, - 0x9a, 0x80, 0x5d, 0x74, 0xd3, 0x8d, 0xa9, 0x05, 0x0b, 0xdd, 0x34, 0x8b, 0x2e, 0xba, 0x49, 0x27, - 0x93, 0x69, 0x32, 0x98, 0xcc, 0x88, 0x4e, 0x84, 0x3e, 0x43, 0x37, 0x7d, 0x2c, 0x97, 0x2e, 0xbb, - 0x2a, 0x45, 0xfb, 0x20, 0x25, 0x13, 0x8d, 0x62, 0x94, 0x6e, 0x86, 0xc3, 0x39, 0xdf, 0x9c, 0xef, - 0x87, 0x03, 0xda, 0x1c, 0x53, 0x17, 0x8f, 0x43, 0x42, 0xb9, 0xc1, 0x5f, 0x47, 0x78, 0x62, 0xe0, - 0x29, 0x71, 0x31, 0x45, 0x58, 0x1f, 0x8d, 0x19, 0x67, 0x4a, 0x75, 0x03, 0xd0, 0x05, 0xa0, 0xf9, - 0xdf, 0x63, 0x1e, 0x13, 0x43, 0x23, 0xae, 0x12, 0x5c, 0xb3, 0x95, 0x59, 0x24, 0xde, 0x64, 0xaa, - 0x45, 0xa0, 0xd6, 0x8f, 0x46, 0x01, 0x41, 0x90, 0xe3, 0x47, 0xc6, 0xf1, 0xed, 0x8a, 0x44, 0xb9, - 0x00, 0xc5, 0x29, 0xe3, 0xd8, 0x86, 0x75, 0xf9, 0x44, 0xee, 0x94, 0xbb, 0x47, 0xfa, 0x2e, 0x9f, - 0x1e, 0xe3, 0xad, 0x42, 0x8c, 0xea, 0xa5, 0x70, 0xa7, 0xfe, 0xe7, 0x77, 0xb8, 0xa9, 0xbd, 0xc9, - 0xa0, 0x71, 0x4f, 0x3c, 0x9f, 0xdf, 0x04, 0x04, 0x53, 0xde, 0xe3, 0x1c, 0xa2, 0x61, 0xca, 0x7d, - 0x07, 0xfe, 0x21, 0x46, 0x5f, 0x02, 0x82, 0x38, 0xa1, 0x9e, 0xed, 0x04, 0x0c, 0x0d, 0x57, 0x32, - 0x5a, 0xd9, 0xbd, 0x62, 0x8f, 0x19, 0x63, 0xac, 0xea, 0xd6, 0x37, 0xd1, 0x51, 0x4e, 0xc1, 0x5f, - 0xc4, 0xc2, 0x90, 0x51, 0xdb, 0xc7, 0x31, 0x4e, 0xc8, 0xcb, 0x59, 0x95, 0xa4, 0x39, 0x10, 0x3d, - 0xed, 0x5b, 0x06, 0xa5, 0x94, 0x1c, 0x82, 0x63, 0x77, 0x9d, 0x88, 0x2d, 0x3c, 0xad, 0x83, 0x5f, - 0x49, 0x38, 0xcb, 0x4a, 0xd8, 0x1b, 0xe1, 0x40, 0xb2, 0x6a, 0xee, 0xde, 0x6c, 0x29, 0x68, 0x05, - 0x31, 0xb1, 0x8d, 0x84, 0x7b, 0x1b, 0x0a, 0xfb, 0x1b, 0x9e, 0x24, 0xc2, 0xf3, 0x03, 0x56, 0xf7, - 0x45, 0x36, 0x90, 0xac, 0x46, 0x70, 0x68, 0x68, 0x16, 0x40, 0x6e, 0x12, 0x85, 0xda, 0x33, 0xa8, - 0xac, 0x5b, 0x7d, 0xc8, 0xa1, 0x72, 0x0d, 0x4a, 0x5b, 0xd6, 0x72, 0x9d, 0x72, 0xb7, 0x99, 0xa5, - 0x4c, 0x97, 0xe4, 0x67, 0x9f, 0x6d, 0xc9, 0x4a, 0x7f, 0x28, 0x0a, 0xc8, 0xfb, 0x70, 0xe2, 0x0b, - 0xb1, 0x15, 0x4b, 0xd4, 0xe6, 0xc3, 0x6c, 0xa1, 0xca, 0xf3, 0x85, 0x2a, 0x7f, 0x2d, 0x54, 0xf9, - 0x7d, 0xa9, 0x4a, 0xf3, 0xa5, 0x2a, 0x7d, 0x2c, 0x55, 0xe9, 0xe9, 0xca, 0x23, 0xdc, 0x8f, 0x1c, - 0x1d, 0xb1, 0xd0, 0xd8, 0x3e, 0xc8, 0x4d, 0x99, 0x1c, 0xee, 0xee, 0xb1, 0x3a, 0x45, 0xd1, 0xbf, - 0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x75, 0x06, 0x32, 0x10, 0x03, 0x00, 0x00, + // 388 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xc1, 0x6a, 0xea, 0x40, + 0x14, 0x4d, 0x9e, 0x4f, 0x91, 0xd1, 0x07, 0xbe, 0xf0, 0x7c, 0x55, 0x91, 0x58, 0xd2, 0x45, 0x85, + 0xd2, 0x04, 0xec, 0xa2, 0x9b, 0x6e, 0x4c, 0x2d, 0x58, 0x70, 0xd3, 0x2c, 0xba, 0xe8, 0x26, 0x24, + 0x93, 0x69, 0x1c, 0x4c, 0x66, 0x44, 0x6f, 0x84, 0x7e, 0x43, 0x37, 0xfd, 0x2c, 0x97, 0x2e, 0xbb, + 0x2a, 0x45, 0xfb, 0x21, 0x25, 0x13, 0x8d, 0x62, 0x94, 0x6e, 0x86, 0xe1, 0xdc, 0x73, 0xef, 0xb9, + 0xe7, 0x70, 0x51, 0x0b, 0x08, 0xf3, 0xc8, 0x24, 0xa4, 0x0c, 0x0c, 0x78, 0x19, 0x93, 0xa9, 0x41, + 0x66, 0xd4, 0x23, 0x0c, 0x13, 0x7d, 0x3c, 0xe1, 0xc0, 0x95, 0xca, 0x96, 0xa0, 0x0b, 0x42, 0xe3, + 0x9f, 0xcf, 0x7d, 0x2e, 0x8a, 0x46, 0xfc, 0x4b, 0x78, 0x8d, 0x66, 0x66, 0x90, 0x78, 0x93, 0xaa, + 0x16, 0xa1, 0x6a, 0x2f, 0x1a, 0x07, 0x14, 0x3b, 0x40, 0x1e, 0x39, 0x90, 0xbb, 0xb5, 0x88, 0x72, + 0x89, 0x0a, 0x33, 0x0e, 0xc4, 0x76, 0x6a, 0xf2, 0xa9, 0xdc, 0x2e, 0x75, 0xfe, 0xeb, 0xfb, 0x7a, + 0x7a, 0xcc, 0xb7, 0xf2, 0x31, 0xab, 0x9b, 0xd2, 0xdd, 0xda, 0xaf, 0x9f, 0xe9, 0xa6, 0xf6, 0x2a, + 0xa3, 0xfa, 0x80, 0xfa, 0x43, 0xb8, 0x0d, 0x28, 0x61, 0xd0, 0x05, 0x70, 0xf0, 0x28, 0xd5, 0xbe, + 0x47, 0x7f, 0x31, 0x67, 0xcf, 0x01, 0xc5, 0x40, 0x99, 0x6f, 0xbb, 0x01, 0xc7, 0xa3, 0xf5, 0x1a, + 0xcd, 0xec, 0x5c, 0x31, 0xc7, 0x8c, 0x39, 0x56, 0x65, 0xa7, 0x4d, 0x20, 0xca, 0x19, 0xfa, 0x83, + 0x79, 0x18, 0x72, 0x66, 0x0f, 0x49, 0xcc, 0x13, 0xeb, 0xe5, 0xac, 0x72, 0x02, 0xf6, 0x05, 0xa6, + 0x7d, 0xc9, 0xa8, 0x98, 0x8a, 0x3b, 0xe8, 0xc4, 0xdb, 0x24, 0x62, 0x0b, 0x4f, 0x9b, 0xe0, 0xd7, + 0x2b, 0x9c, 0x67, 0x57, 0x38, 0x18, 0x61, 0x5f, 0xb2, 0xaa, 0xde, 0xc1, 0x6c, 0x19, 0x6a, 0x06, + 0xb1, 0xb0, 0x8d, 0x85, 0x7b, 0xdb, 0x11, 0xf6, 0xb7, 0x3a, 0x49, 0x84, 0x17, 0x47, 0xac, 0x1e, + 0x8a, 0xac, 0x2f, 0x59, 0xf5, 0xe0, 0x58, 0xd1, 0xcc, 0xa3, 0xdc, 0x34, 0x0a, 0xb5, 0x01, 0x2a, + 0x6f, 0xa0, 0x9e, 0x03, 0x8e, 0x72, 0x83, 0x8a, 0x3b, 0xd6, 0x72, 0xed, 0x52, 0xa7, 0x91, 0x95, + 0x4c, 0x87, 0xfc, 0x9e, 0x7f, 0xb4, 0x24, 0x2b, 0xed, 0x30, 0x1f, 0xe6, 0x4b, 0x55, 0x5e, 0x2c, + 0x55, 0xf9, 0x73, 0xa9, 0xca, 0x6f, 0x2b, 0x55, 0x5a, 0xac, 0x54, 0xe9, 0x7d, 0xa5, 0x4a, 0x4f, + 0xd7, 0x3e, 0x85, 0x61, 0xe4, 0xea, 0x98, 0x87, 0xc6, 0xee, 0xf1, 0x6d, 0xbf, 0xc9, 0x91, 0xee, + 0x1f, 0xa6, 0x5b, 0x10, 0xf8, 0xd5, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x7f, 0xe9, 0x7a, + 0xfc, 0x02, 0x00, 0x00, } func (m *DuplicateVoteEvidence) Marshal() (dAtA []byte, err error) { @@ -486,13 +478,6 @@ func (m *EvidenceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Hash) > 0 { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintEvidence(dAtA, i, uint64(len(m.Hash))) - i-- - dAtA[i] = 0x12 - } if len(m.Evidence) > 0 { for iNdEx := len(m.Evidence) - 1; iNdEx >= 0; iNdEx-- { { @@ -602,10 +587,6 @@ func (m *EvidenceData) Size() (n int) { n += 1 + l + sovEvidence(uint64(l)) } } - l = len(m.Hash) - if l > 0 { - n += 1 + l + sovEvidence(uint64(l)) - } return n } @@ -1034,40 +1015,6 @@ func (m *EvidenceData) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvidence - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthEvidence - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthEvidence - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvidence(dAtA[iNdEx:]) diff --git a/proto/tendermint/types/evidence.proto b/proto/tendermint/types/evidence.proto index 609e7f5518..5f7d860bf4 100644 --- a/proto/tendermint/types/evidence.proto +++ b/proto/tendermint/types/evidence.proto @@ -28,5 +28,4 @@ message Evidence { // EvidenceData contains any evidence of malicious wrong-doing by validators message EvidenceData { repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; - bytes hash = 2; } diff --git a/proto/tendermint/types/types.pb.go b/proto/tendermint/types/types.pb.go index c38e60c295..27a936097f 100644 --- a/proto/tendermint/types/types.pb.go +++ b/proto/tendermint/types/types.pb.go @@ -10,7 +10,6 @@ import ( _ "github.com/gogo/protobuf/types" github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" crypto "github.com/tendermint/tendermint/proto/tendermint/crypto" - bits "github.com/tendermint/tendermint/proto/tendermint/libs/bits" version "github.com/tendermint/tendermint/proto/tendermint/version" io "io" math "math" @@ -422,8 +421,6 @@ type Data struct { // NOTE: not all txs here are valid. We're just agreeing on the order first. // This means that block.AppHash does not include these txs. Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"` - // Volatile - Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` } func (m *Data) Reset() { *m = Data{} } @@ -466,13 +463,6 @@ func (m *Data) GetTxs() [][]byte { return nil } -func (m *Data) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { @@ -577,12 +567,10 @@ func (m *Vote) GetSignature() []byte { // Commit contains the evidence that a block was committed by a set of validators. type Commit struct { - Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` - Round int32 `protobuf:"varint,2,opt,name=round,proto3" json:"round,omitempty"` - BlockID BlockID `protobuf:"bytes,3,opt,name=block_id,json=blockId,proto3" json:"block_id"` - Signatures []CommitSig `protobuf:"bytes,4,rep,name=signatures,proto3" json:"signatures"` - Hash []byte `protobuf:"bytes,5,opt,name=hash,proto3" json:"hash,omitempty"` - BitArray *bits.BitArray `protobuf:"bytes,6,opt,name=bit_array,json=bitArray,proto3" json:"bit_array,omitempty"` + Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + Round int32 `protobuf:"varint,2,opt,name=round,proto3" json:"round,omitempty"` + BlockID BlockID `protobuf:"bytes,3,opt,name=block_id,json=blockId,proto3" json:"block_id"` + Signatures []CommitSig `protobuf:"bytes,4,rep,name=signatures,proto3" json:"signatures"` } func (m *Commit) Reset() { *m = Commit{} } @@ -646,20 +634,6 @@ func (m *Commit) GetSignatures() []CommitSig { return nil } -func (m *Commit) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - -func (m *Commit) GetBitArray() *bits.BitArray { - if m != nil { - return m.BitArray - } - return nil -} - // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { BlockIdFlag BlockIDFlag `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3,enum=tendermint.types.BlockIDFlag" json:"block_id_flag,omitempty"` @@ -1075,93 +1049,90 @@ func init() { func init() { proto.RegisterFile("tendermint/types/types.proto", fileDescriptor_d3a6e55e2345de56) } var fileDescriptor_d3a6e55e2345de56 = []byte{ - // 1364 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcd, 0x6e, 0xdb, 0x46, - 0x10, 0x36, 0x25, 0xca, 0x92, 0x46, 0x92, 0x2d, 0x13, 0x4e, 0xa2, 0x28, 0xb1, 0x4c, 0xa8, 0x68, - 0xeb, 0xa4, 0x01, 0x95, 0x3a, 0x45, 0x7f, 0x50, 0xf4, 0x20, 0xc9, 0x4e, 0x22, 0xc4, 0x96, 0x55, - 0x4a, 0x49, 0xd1, 0x5e, 0x08, 0x4a, 0xdc, 0x48, 0x6c, 0x28, 0x92, 0xe0, 0xae, 0x5c, 0x3b, 0x4f, - 0x50, 0xf8, 0x94, 0x5e, 0x7a, 0xf3, 0xa9, 0x3d, 0xf4, 0xde, 0x37, 0xe8, 0x29, 0xc7, 0xdc, 0xda, - 0x4b, 0xd3, 0xc2, 0x01, 0x8a, 0x3e, 0x46, 0xb1, 0x3f, 0xa2, 0x48, 0xcb, 0x6e, 0x03, 0x23, 0xe8, - 0x45, 0xe0, 0xce, 0x7c, 0x33, 0x3b, 0xf3, 0xed, 0xc7, 0x1d, 0x0a, 0xae, 0x13, 0xe4, 0x5a, 0x28, - 0x18, 0xdb, 0x2e, 0xa9, 0x91, 0x43, 0x1f, 0x61, 0xfe, 0xab, 0xf9, 0x81, 0x47, 0x3c, 0xa5, 0x38, - 0xf3, 0x6a, 0xcc, 0x5e, 0x5e, 0x1d, 0x7a, 0x43, 0x8f, 0x39, 0x6b, 0xf4, 0x89, 0xe3, 0xca, 0xeb, - 0x43, 0xcf, 0x1b, 0x3a, 0xa8, 0xc6, 0x56, 0xfd, 0xc9, 0xe3, 0x1a, 0xb1, 0xc7, 0x08, 0x13, 0x73, - 0xec, 0x0b, 0x80, 0x1a, 0xd9, 0xc6, 0xb1, 0xfb, 0xb8, 0xd6, 0xb7, 0x49, 0x6c, 0xab, 0xf2, 0x5a, - 0x04, 0x31, 0x08, 0x0e, 0x7d, 0xe2, 0xd1, 0x6c, 0xde, 0x63, 0xe1, 0xae, 0x44, 0xdc, 0xfb, 0x28, - 0xc0, 0xb6, 0xe7, 0xc6, 0xc2, 0xd5, 0xb9, 0x3e, 0xf6, 0x4d, 0xc7, 0xb6, 0x4c, 0xe2, 0x05, 0x1c, - 0x51, 0xfd, 0x04, 0x0a, 0x1d, 0x33, 0x20, 0x5d, 0x44, 0xee, 0x23, 0xd3, 0x42, 0x81, 0xb2, 0x0a, - 0x29, 0xe2, 0x11, 0xd3, 0x29, 0x49, 0xaa, 0xb4, 0x51, 0xd0, 0xf9, 0x42, 0x51, 0x40, 0x1e, 0x99, - 0x78, 0x54, 0x4a, 0xa8, 0xd2, 0x46, 0x5e, 0x67, 0xcf, 0xd5, 0x11, 0xc8, 0x34, 0x94, 0x46, 0xd8, - 0xae, 0x85, 0x0e, 0xa6, 0x11, 0x6c, 0x41, 0xad, 0xfd, 0x43, 0x82, 0xb0, 0x08, 0xe1, 0x0b, 0xe5, - 0x03, 0x48, 0xb1, 0xfa, 0x4b, 0x49, 0x55, 0xda, 0xc8, 0x6d, 0x96, 0xb4, 0x08, 0x95, 0xbc, 0x3f, - 0xad, 0x43, 0xfd, 0x0d, 0xf9, 0xf9, 0xcb, 0xf5, 0x05, 0x9d, 0x83, 0xab, 0x0e, 0xa4, 0x1b, 0x8e, - 0x37, 0x78, 0xd2, 0xda, 0x0a, 0x0b, 0x91, 0x66, 0x85, 0x28, 0xbb, 0xb0, 0xec, 0x9b, 0x01, 0x31, - 0x30, 0x22, 0xc6, 0x88, 0x75, 0xc1, 0x36, 0xcd, 0x6d, 0xae, 0x6b, 0xa7, 0x4f, 0x4a, 0x8b, 0x35, - 0x2b, 0x76, 0x29, 0xf8, 0x51, 0x63, 0xf5, 0x2f, 0x19, 0x16, 0x05, 0x19, 0x9f, 0x41, 0x5a, 0xd0, - 0xca, 0x36, 0xcc, 0x6d, 0xae, 0x45, 0x33, 0x0a, 0x97, 0xd6, 0xf4, 0x5c, 0x8c, 0x5c, 0x3c, 0xc1, - 0x22, 0xdf, 0x34, 0x46, 0x79, 0x07, 0x32, 0x83, 0x91, 0x69, 0xbb, 0x86, 0x6d, 0xb1, 0x8a, 0xb2, - 0x8d, 0xdc, 0xc9, 0xcb, 0xf5, 0x74, 0x93, 0xda, 0x5a, 0x5b, 0x7a, 0x9a, 0x39, 0x5b, 0x96, 0x72, - 0x19, 0x16, 0x47, 0xc8, 0x1e, 0x8e, 0x08, 0xa3, 0x25, 0xa9, 0x8b, 0x95, 0xf2, 0x31, 0xc8, 0x54, - 0x32, 0x25, 0x99, 0xed, 0x5d, 0xd6, 0xb8, 0x9e, 0xb4, 0xa9, 0x9e, 0xb4, 0xde, 0x54, 0x4f, 0x8d, - 0x0c, 0xdd, 0xf8, 0xd9, 0x1f, 0xeb, 0x92, 0xce, 0x22, 0x94, 0x26, 0x14, 0x1c, 0x13, 0x13, 0xa3, - 0x4f, 0x69, 0xa3, 0xdb, 0xa7, 0x58, 0x8a, 0xab, 0xf3, 0x84, 0x08, 0x62, 0x45, 0xe9, 0x39, 0x1a, - 0xc5, 0x4d, 0x96, 0xb2, 0x01, 0x45, 0x96, 0x64, 0xe0, 0x8d, 0xc7, 0x36, 0x31, 0x18, 0xef, 0x8b, - 0x8c, 0xf7, 0x25, 0x6a, 0x6f, 0x32, 0xf3, 0x7d, 0x7a, 0x02, 0xd7, 0x20, 0x6b, 0x99, 0xc4, 0xe4, - 0x90, 0x34, 0x83, 0x64, 0xa8, 0x81, 0x39, 0xdf, 0x85, 0xe5, 0x50, 0x75, 0x98, 0x43, 0x32, 0x3c, - 0xcb, 0xcc, 0xcc, 0x80, 0xb7, 0x61, 0xd5, 0x45, 0x07, 0xc4, 0x38, 0x8d, 0xce, 0x32, 0xb4, 0x42, - 0x7d, 0x8f, 0xe2, 0x11, 0x6f, 0xc3, 0xd2, 0x60, 0x4a, 0x3e, 0xc7, 0x02, 0xc3, 0x16, 0x42, 0x2b, - 0x83, 0x5d, 0x85, 0x8c, 0xe9, 0xfb, 0x1c, 0x90, 0x63, 0x80, 0xb4, 0xe9, 0xfb, 0xcc, 0x75, 0x13, - 0x56, 0x58, 0x8f, 0x01, 0xc2, 0x13, 0x87, 0x88, 0x24, 0x79, 0x86, 0x59, 0xa6, 0x0e, 0x9d, 0xdb, - 0x19, 0xf6, 0x2d, 0x28, 0xa0, 0x7d, 0xdb, 0x42, 0xee, 0x00, 0x71, 0x5c, 0x81, 0xe1, 0xf2, 0x53, - 0x23, 0x03, 0xdd, 0x80, 0xa2, 0x1f, 0x78, 0xbe, 0x87, 0x51, 0x60, 0x98, 0x96, 0x15, 0x20, 0x8c, - 0x4b, 0x4b, 0x3c, 0xdf, 0xd4, 0x5e, 0xe7, 0xe6, 0xea, 0x2d, 0x90, 0xb7, 0x4c, 0x62, 0x2a, 0x45, - 0x48, 0x92, 0x03, 0x5c, 0x92, 0xd4, 0xe4, 0x46, 0x5e, 0xa7, 0x8f, 0x67, 0xbe, 0x6e, 0x7f, 0x27, - 0x40, 0x7e, 0xe4, 0x11, 0xa4, 0xdc, 0x01, 0x99, 0x1e, 0x1d, 0x53, 0xe4, 0xd2, 0x59, 0x1a, 0xef, - 0xda, 0x43, 0x17, 0x59, 0xbb, 0x78, 0xd8, 0x3b, 0xf4, 0x91, 0xce, 0xc0, 0x11, 0x89, 0x25, 0x62, - 0x12, 0x5b, 0x85, 0x54, 0xe0, 0x4d, 0x5c, 0x8b, 0x29, 0x2f, 0xa5, 0xf3, 0x85, 0xb2, 0x0d, 0x99, - 0x50, 0x39, 0xf2, 0x7f, 0x29, 0x67, 0x99, 0x2a, 0x87, 0xea, 0x5a, 0x18, 0xf4, 0x74, 0x5f, 0x08, - 0xa8, 0x01, 0xd9, 0xf0, 0xca, 0x13, 0x0a, 0x7c, 0x3d, 0x11, 0xcf, 0xc2, 0x94, 0xf7, 0x60, 0x25, - 0xd4, 0x43, 0x48, 0x28, 0x57, 0x61, 0x31, 0x74, 0x08, 0x46, 0x63, 0x52, 0x33, 0xf8, 0xa5, 0x94, - 0x66, 0x7d, 0xcd, 0xa4, 0xd6, 0x62, 0xb7, 0xd3, 0x75, 0xc8, 0x62, 0x7b, 0xe8, 0x9a, 0x64, 0x12, - 0x20, 0xa1, 0xc6, 0x99, 0xa1, 0xfa, 0x5d, 0x02, 0x16, 0xb9, 0xba, 0x23, 0xbc, 0x49, 0x67, 0xf3, - 0x96, 0x38, 0x8f, 0xb7, 0xe4, 0xc5, 0x79, 0xab, 0x03, 0x84, 0xc5, 0xe0, 0x92, 0xac, 0x26, 0x37, - 0x72, 0x9b, 0xd7, 0xe6, 0x13, 0xf1, 0x12, 0xbb, 0xf6, 0x50, 0xbc, 0xbc, 0x91, 0xa0, 0x50, 0x41, - 0xa9, 0xc8, 0x3d, 0xf9, 0x29, 0x64, 0xfb, 0x36, 0x31, 0xcc, 0x20, 0x30, 0x0f, 0x19, 0x85, 0xb9, - 0xcd, 0x4a, 0x34, 0x2b, 0x1d, 0x41, 0x1a, 0x1d, 0x41, 0x5a, 0xc3, 0x26, 0x75, 0x8a, 0xd2, 0x33, - 0x7d, 0xf1, 0x54, 0xfd, 0x5d, 0x82, 0x6c, 0xb8, 0xa1, 0x52, 0x87, 0xc2, 0xb4, 0x51, 0xe3, 0xb1, - 0x63, 0x0e, 0x85, 0x18, 0xd7, 0xce, 0xed, 0xf6, 0xae, 0x63, 0x0e, 0xf5, 0x9c, 0x68, 0x90, 0x2e, - 0xce, 0x3e, 0xd8, 0xc4, 0x39, 0x07, 0x1b, 0x53, 0x52, 0xf2, 0x62, 0x4a, 0x8a, 0x9d, 0xb9, 0x7c, - 0xfa, 0xcc, 0x7f, 0x4e, 0x40, 0xa6, 0xc3, 0x5e, 0x50, 0xd3, 0xf9, 0x3f, 0x5e, 0xb1, 0x6b, 0x90, - 0xf5, 0x3d, 0xc7, 0xe0, 0x1e, 0x99, 0x79, 0x32, 0xbe, 0xe7, 0xe8, 0x73, 0x3a, 0x4a, 0xbd, 0xa1, - 0xf7, 0x6f, 0xf1, 0x0d, 0xb0, 0x96, 0x3e, 0xcd, 0x5a, 0x00, 0x79, 0x4e, 0x85, 0x18, 0x98, 0xb7, - 0x29, 0x07, 0x6c, 0x02, 0x4b, 0xf3, 0x03, 0x9e, 0x97, 0xcd, 0x91, 0xba, 0xc0, 0xd1, 0x08, 0x3e, - 0x5f, 0xc4, 0xcc, 0x2e, 0x9d, 0xa7, 0x73, 0x5d, 0xe0, 0xaa, 0xdf, 0x4b, 0x00, 0x3b, 0x94, 0x59, - 0xd6, 0x2f, 0x1d, 0x75, 0x98, 0x95, 0x60, 0xc4, 0x76, 0xae, 0x9c, 0x77, 0x68, 0x62, 0xff, 0x3c, - 0x8e, 0xd6, 0xdd, 0x84, 0xc2, 0x4c, 0x8c, 0x18, 0x4d, 0x8b, 0x39, 0x23, 0x49, 0x38, 0x81, 0xba, - 0x88, 0xe8, 0xf9, 0xfd, 0xc8, 0xaa, 0xfa, 0x8b, 0x04, 0x59, 0x56, 0xd3, 0x2e, 0x22, 0x66, 0xec, - 0x0c, 0xa5, 0x8b, 0x9f, 0xe1, 0x1a, 0x00, 0x4f, 0x83, 0xed, 0xa7, 0x48, 0x28, 0x2b, 0xcb, 0x2c, - 0x5d, 0xfb, 0x29, 0x52, 0x3e, 0x0c, 0x09, 0x4f, 0xfe, 0x3b, 0xe1, 0xe2, 0x8e, 0x98, 0xd2, 0x7e, - 0x05, 0xd2, 0xee, 0x64, 0x6c, 0xd0, 0xb9, 0x23, 0x73, 0xb5, 0xba, 0x93, 0x71, 0xef, 0x00, 0x57, - 0xbf, 0x86, 0x74, 0xef, 0x80, 0x7d, 0x83, 0x51, 0x89, 0x06, 0x9e, 0x27, 0x06, 0x3f, 0xff, 0xe0, - 0xca, 0x50, 0x03, 0x9b, 0x73, 0x0a, 0xc8, 0x74, 0xc2, 0x4f, 0x47, 0x14, 0x7d, 0x56, 0xb4, 0xd7, - 0xfc, 0xba, 0x13, 0xdf, 0x75, 0x37, 0x7f, 0x95, 0x20, 0x17, 0xb9, 0x1f, 0x94, 0xf7, 0xe1, 0x52, - 0x63, 0x67, 0xaf, 0xf9, 0xc0, 0x68, 0x6d, 0x19, 0x77, 0x77, 0xea, 0xf7, 0x8c, 0x87, 0xed, 0x07, - 0xed, 0xbd, 0x2f, 0xda, 0xc5, 0x85, 0xf2, 0xe5, 0xa3, 0x63, 0x55, 0x89, 0x60, 0x1f, 0xba, 0x4f, - 0x5c, 0xef, 0x1b, 0x57, 0xa9, 0xc1, 0x6a, 0x3c, 0xa4, 0xde, 0xe8, 0x6e, 0xb7, 0x7b, 0x45, 0xa9, - 0x7c, 0xe9, 0xe8, 0x58, 0x5d, 0x89, 0x44, 0xd4, 0xfb, 0x18, 0xb9, 0x64, 0x3e, 0xa0, 0xb9, 0xb7, - 0xbb, 0xdb, 0xea, 0x15, 0x13, 0x73, 0x01, 0x62, 0x02, 0xdc, 0x80, 0x95, 0x78, 0x40, 0xbb, 0xb5, - 0x53, 0x4c, 0x96, 0x95, 0xa3, 0x63, 0x75, 0x29, 0x82, 0x6e, 0xdb, 0x4e, 0x39, 0xf3, 0xed, 0x0f, - 0x95, 0x85, 0x9f, 0x7e, 0xac, 0x48, 0xb4, 0xb3, 0x42, 0xec, 0x8e, 0x50, 0x6e, 0xc1, 0x95, 0x6e, - 0xeb, 0x5e, 0x7b, 0x7b, 0xcb, 0xd8, 0xed, 0xde, 0x33, 0x7a, 0x5f, 0x76, 0xb6, 0x23, 0xdd, 0x2d, - 0x1f, 0x1d, 0xab, 0x39, 0xd1, 0xd2, 0x79, 0xe8, 0x8e, 0xbe, 0xfd, 0x68, 0xaf, 0xb7, 0x5d, 0x94, - 0x38, 0xba, 0x13, 0xa0, 0x7d, 0x8f, 0x20, 0x86, 0xbe, 0x0d, 0x57, 0xcf, 0x40, 0x87, 0x8d, 0xad, - 0x1c, 0x1d, 0xab, 0x85, 0x4e, 0x80, 0xf8, 0xfb, 0xc3, 0x22, 0x34, 0x28, 0xcd, 0x47, 0xec, 0x75, - 0xf6, 0xba, 0xf5, 0x9d, 0xa2, 0x5a, 0x2e, 0x1e, 0x1d, 0xab, 0xf9, 0xe9, 0x65, 0x48, 0xf1, 0xb3, - 0xce, 0x1a, 0x9f, 0x3f, 0x3f, 0xa9, 0x48, 0x2f, 0x4e, 0x2a, 0xd2, 0x9f, 0x27, 0x15, 0xe9, 0xd9, - 0xab, 0xca, 0xc2, 0x8b, 0x57, 0x95, 0x85, 0xdf, 0x5e, 0x55, 0x16, 0xbe, 0xfa, 0x68, 0x68, 0x93, - 0xd1, 0xa4, 0xaf, 0x0d, 0xbc, 0x71, 0x2d, 0xfa, 0xbf, 0x63, 0xf6, 0xc8, 0xff, 0x21, 0x9d, 0xfe, - 0x4f, 0xd2, 0x5f, 0x64, 0xf6, 0x3b, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xb5, 0xf9, 0x01, 0xed, - 0x76, 0x0d, 0x00, 0x00, + // 1314 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0x9b, 0xd8, 0x7e, 0xb6, 0x13, 0x67, 0x95, 0xb6, 0xae, 0xdb, 0x38, 0x2b, 0x23, + 0x20, 0x2d, 0x68, 0x53, 0x52, 0xc4, 0x9f, 0x03, 0x07, 0xdb, 0x49, 0x5b, 0xab, 0x89, 0x63, 0xd6, + 0x6e, 0x11, 0x5c, 0x56, 0x6b, 0xef, 0xd4, 0x5e, 0xba, 0xde, 0x59, 0xed, 0x8c, 0x43, 0xd2, 0x4f, + 0x80, 0x72, 0xea, 0x89, 0x5b, 0x4e, 0x70, 0xe0, 0xce, 0x17, 0x40, 0x9c, 0x7a, 0xec, 0x0d, 0x2e, + 0x14, 0x94, 0x4a, 0x88, 0x8f, 0x81, 0xe6, 0x8f, 0xd7, 0xeb, 0x38, 0x86, 0xaa, 0xaa, 0xb8, 0x58, + 0x3b, 0xef, 0xfd, 0xde, 0xcc, 0x7b, 0xbf, 0xf7, 0x9b, 0x3f, 0x86, 0xeb, 0x14, 0xf9, 0x0e, 0x0a, + 0x87, 0xae, 0x4f, 0xb7, 0xe8, 0x71, 0x80, 0x88, 0xf8, 0x35, 0x82, 0x10, 0x53, 0xac, 0x15, 0x26, + 0x5e, 0x83, 0xdb, 0x4b, 0x6b, 0x7d, 0xdc, 0xc7, 0xdc, 0xb9, 0xc5, 0xbe, 0x04, 0xae, 0xb4, 0xd1, + 0xc7, 0xb8, 0xef, 0xa1, 0x2d, 0x3e, 0xea, 0x8e, 0x1e, 0x6d, 0x51, 0x77, 0x88, 0x08, 0xb5, 0x87, + 0x81, 0x04, 0xac, 0xc7, 0x96, 0xe9, 0x85, 0xc7, 0x01, 0xc5, 0x0c, 0x8b, 0x1f, 0x49, 0x77, 0x39, + 0xe6, 0x3e, 0x44, 0x21, 0x71, 0xb1, 0x1f, 0xcf, 0xa3, 0xa4, 0xcf, 0x64, 0x79, 0x68, 0x7b, 0xae, + 0x63, 0x53, 0x1c, 0x0a, 0x44, 0xe5, 0x53, 0xc8, 0xb7, 0xec, 0x90, 0xb6, 0x11, 0xbd, 0x87, 0x6c, + 0x07, 0x85, 0xda, 0x1a, 0x2c, 0x52, 0x4c, 0x6d, 0xaf, 0xa8, 0xe8, 0xca, 0x66, 0xde, 0x14, 0x03, + 0x4d, 0x03, 0x75, 0x60, 0x93, 0x41, 0x31, 0xa1, 0x2b, 0x9b, 0x39, 0x93, 0x7f, 0x57, 0x06, 0xa0, + 0xb2, 0x50, 0x16, 0xe1, 0xfa, 0x0e, 0x3a, 0x1a, 0x47, 0xf0, 0x01, 0xb3, 0x76, 0x8f, 0x29, 0x22, + 0x32, 0x44, 0x0c, 0xb4, 0x0f, 0x61, 0x91, 0xe7, 0x5f, 0x4c, 0xea, 0xca, 0x66, 0x76, 0xbb, 0x68, + 0xc4, 0x88, 0x12, 0xf5, 0x19, 0x2d, 0xe6, 0xaf, 0xa9, 0xcf, 0x5e, 0x6c, 0x2c, 0x98, 0x02, 0x5c, + 0xf1, 0x20, 0x55, 0xf3, 0x70, 0xef, 0x71, 0x63, 0x27, 0x4a, 0x44, 0x99, 0x24, 0xa2, 0xed, 0xc3, + 0x4a, 0x60, 0x87, 0xd4, 0x22, 0x88, 0x5a, 0x03, 0x5e, 0x05, 0x5f, 0x34, 0xbb, 0xbd, 0x61, 0x9c, + 0xef, 0x83, 0x31, 0x55, 0xac, 0x5c, 0x25, 0x1f, 0xc4, 0x8d, 0x95, 0xbf, 0x54, 0x58, 0x92, 0x64, + 0x7c, 0x06, 0x29, 0x49, 0x2b, 0x5f, 0x30, 0xbb, 0xbd, 0x1e, 0x9f, 0x51, 0xba, 0x8c, 0x3a, 0xf6, + 0x09, 0xf2, 0xc9, 0x88, 0xc8, 0xf9, 0xc6, 0x31, 0xda, 0x3b, 0x90, 0xee, 0x0d, 0x6c, 0xd7, 0xb7, + 0x5c, 0x87, 0x67, 0x94, 0xa9, 0x65, 0xcf, 0x5e, 0x6c, 0xa4, 0xea, 0xcc, 0xd6, 0xd8, 0x31, 0x53, + 0xdc, 0xd9, 0x70, 0xb4, 0xcb, 0xb0, 0x34, 0x40, 0x6e, 0x7f, 0x40, 0x39, 0x2d, 0x49, 0x53, 0x8e, + 0xb4, 0x4f, 0x40, 0x65, 0x82, 0x28, 0xaa, 0x7c, 0xed, 0x92, 0x21, 0xd4, 0x62, 0x8c, 0xd5, 0x62, + 0x74, 0xc6, 0x6a, 0xa9, 0xa5, 0xd9, 0xc2, 0x4f, 0xff, 0xd8, 0x50, 0x4c, 0x1e, 0xa1, 0xd5, 0x21, + 0xef, 0xd9, 0x84, 0x5a, 0x5d, 0x46, 0x1b, 0x5b, 0x7e, 0x91, 0x4f, 0x71, 0x75, 0x96, 0x10, 0x49, + 0xac, 0x4c, 0x3d, 0xcb, 0xa2, 0x84, 0xc9, 0xd1, 0x36, 0xa1, 0xc0, 0x27, 0xe9, 0xe1, 0xe1, 0xd0, + 0xa5, 0x16, 0xe7, 0x7d, 0x89, 0xf3, 0xbe, 0xcc, 0xec, 0x75, 0x6e, 0xbe, 0xc7, 0x3a, 0x70, 0x0d, + 0x32, 0x8e, 0x4d, 0x6d, 0x01, 0x49, 0x71, 0x48, 0x9a, 0x19, 0xb8, 0xf3, 0x5d, 0x58, 0x89, 0x54, + 0x47, 0x04, 0x24, 0x2d, 0x66, 0x99, 0x98, 0x39, 0xf0, 0x16, 0xac, 0xf9, 0xe8, 0x88, 0x5a, 0xe7, + 0xd1, 0x19, 0x8e, 0xd6, 0x98, 0xef, 0xe1, 0x74, 0xc4, 0xdb, 0xb0, 0xdc, 0x1b, 0x93, 0x2f, 0xb0, + 0xc0, 0xb1, 0xf9, 0xc8, 0xca, 0x61, 0x57, 0x21, 0x6d, 0x07, 0x81, 0x00, 0x64, 0x39, 0x20, 0x65, + 0x07, 0x01, 0x77, 0xdd, 0x84, 0x55, 0x5e, 0x63, 0x88, 0xc8, 0xc8, 0xa3, 0x72, 0x92, 0x1c, 0xc7, + 0xac, 0x30, 0x87, 0x29, 0xec, 0x1c, 0xfb, 0x16, 0xe4, 0xd1, 0xa1, 0xeb, 0x20, 0xbf, 0x87, 0x04, + 0x2e, 0xcf, 0x71, 0xb9, 0xb1, 0x91, 0x83, 0x6e, 0x40, 0x21, 0x08, 0x71, 0x80, 0x09, 0x0a, 0x2d, + 0xdb, 0x71, 0x42, 0x44, 0x48, 0x71, 0x59, 0xcc, 0x37, 0xb6, 0x57, 0x85, 0xb9, 0x52, 0x04, 0x75, + 0xc7, 0xa6, 0xb6, 0x56, 0x80, 0x24, 0x3d, 0x22, 0x45, 0x45, 0x4f, 0x6e, 0xe6, 0x4c, 0xf6, 0x59, + 0xf9, 0x3b, 0x01, 0xea, 0x43, 0x4c, 0x91, 0x76, 0x1b, 0x54, 0xd6, 0x26, 0xae, 0xbe, 0xe5, 0x8b, + 0xf4, 0xdc, 0x76, 0xfb, 0x3e, 0x72, 0xf6, 0x49, 0xbf, 0x73, 0x1c, 0x20, 0x93, 0x83, 0x63, 0x72, + 0x4a, 0x4c, 0xc9, 0x69, 0x0d, 0x16, 0x43, 0x3c, 0xf2, 0x1d, 0xae, 0xb2, 0x45, 0x53, 0x0c, 0xb4, + 0x5d, 0x48, 0x47, 0x2a, 0x51, 0xff, 0x4b, 0x25, 0x2b, 0x4c, 0x25, 0x4c, 0xc3, 0xd2, 0x60, 0xa6, + 0xba, 0x52, 0x2c, 0x35, 0xc8, 0x44, 0x87, 0x97, 0x54, 0xdb, 0xab, 0x09, 0x76, 0x12, 0xa6, 0xbd, + 0x07, 0xab, 0x51, 0xef, 0x23, 0xf2, 0x84, 0xe2, 0x0a, 0x91, 0x43, 0xb2, 0x37, 0x25, 0x2b, 0x4b, + 0x1c, 0x40, 0x29, 0x5e, 0xd7, 0x44, 0x56, 0x0d, 0x7e, 0x12, 0x5d, 0x87, 0x0c, 0x71, 0xfb, 0xbe, + 0x4d, 0x47, 0x21, 0x92, 0xca, 0x9b, 0x18, 0x2a, 0x3f, 0x2b, 0xb0, 0x24, 0x94, 0x1c, 0xe3, 0x4d, + 0xb9, 0x98, 0xb7, 0xc4, 0x3c, 0xde, 0x92, 0xaf, 0xcf, 0x5b, 0x15, 0x20, 0x4a, 0x86, 0x14, 0x55, + 0x3d, 0xb9, 0x99, 0xdd, 0xbe, 0x36, 0x3b, 0x91, 0x48, 0xb1, 0xed, 0xf6, 0xe5, 0x46, 0x8d, 0x05, + 0x55, 0x7e, 0x57, 0x20, 0x13, 0xf9, 0xb5, 0x2a, 0xe4, 0xc7, 0x79, 0x59, 0x8f, 0x3c, 0xbb, 0x2f, + 0xb5, 0xb3, 0x3e, 0x37, 0xb9, 0x3b, 0x9e, 0xdd, 0x37, 0xb3, 0x32, 0x1f, 0x36, 0xb8, 0xb8, 0x0f, + 0x89, 0x39, 0x7d, 0x98, 0x6a, 0x7c, 0xf2, 0xf5, 0x1a, 0x3f, 0xd5, 0x22, 0xf5, 0x7c, 0x8b, 0x7e, + 0x4a, 0x40, 0xba, 0xc5, 0xf7, 0x8e, 0xed, 0xfd, 0x1f, 0x3b, 0xe2, 0x1a, 0x64, 0x02, 0xec, 0x59, + 0xc2, 0xa3, 0x72, 0x4f, 0x3a, 0xc0, 0x9e, 0x39, 0xd3, 0xf6, 0xc5, 0x37, 0xb4, 0x5d, 0x96, 0xde, + 0x00, 0x6b, 0xa9, 0xf3, 0xac, 0x85, 0x90, 0x13, 0x54, 0xc8, 0xbb, 0xec, 0x16, 0xe3, 0x80, 0x5f, + 0x8e, 0xca, 0xec, 0xdd, 0x2b, 0xd2, 0x16, 0x48, 0x53, 0xe2, 0x58, 0x84, 0x38, 0xfa, 0xe5, 0x75, + 0x5a, 0x9c, 0x27, 0x4b, 0x53, 0xe2, 0x2a, 0xdf, 0x29, 0x00, 0x7b, 0x8c, 0x59, 0x5e, 0x2f, 0xbb, + 0x85, 0x08, 0x4f, 0xc1, 0x9a, 0x5a, 0xb9, 0x3c, 0xaf, 0x69, 0x72, 0xfd, 0x1c, 0x89, 0xe7, 0x5d, + 0x87, 0xfc, 0x44, 0x8c, 0x04, 0x8d, 0x93, 0xb9, 0x60, 0x92, 0xe8, 0x72, 0x68, 0x23, 0x6a, 0xe6, + 0x0e, 0x63, 0xa3, 0xca, 0x2f, 0x0a, 0x64, 0x78, 0x4e, 0xfb, 0x88, 0xda, 0x53, 0x3d, 0x54, 0x5e, + 0xbf, 0x87, 0xeb, 0x00, 0x62, 0x1a, 0xe2, 0x3e, 0x41, 0x52, 0x59, 0x19, 0x6e, 0x69, 0xbb, 0x4f, + 0x90, 0xf6, 0x51, 0x44, 0x78, 0xf2, 0xdf, 0x09, 0x97, 0x5b, 0x7a, 0x4c, 0xfb, 0x15, 0x48, 0xf9, + 0xa3, 0xa1, 0xc5, 0xae, 0x04, 0x55, 0xa8, 0xd5, 0x1f, 0x0d, 0x3b, 0x47, 0xa4, 0xf2, 0x35, 0xa4, + 0x3a, 0x47, 0xfc, 0x79, 0xc4, 0x24, 0x1a, 0x62, 0x2c, 0xef, 0x64, 0xf1, 0x16, 0x4a, 0x33, 0x03, + 0xbf, 0x82, 0x34, 0x50, 0xd9, 0xe5, 0x3b, 0x7e, 0xac, 0xb1, 0x6f, 0xcd, 0x78, 0xc5, 0x87, 0x97, + 0x7c, 0x72, 0xdd, 0xfc, 0x55, 0x81, 0x6c, 0xec, 0x7c, 0xd0, 0x3e, 0x80, 0x4b, 0xb5, 0xbd, 0x83, + 0xfa, 0x7d, 0xab, 0xb1, 0x63, 0xdd, 0xd9, 0xab, 0xde, 0xb5, 0x1e, 0x34, 0xef, 0x37, 0x0f, 0xbe, + 0x68, 0x16, 0x16, 0x4a, 0x97, 0x4f, 0x4e, 0x75, 0x2d, 0x86, 0x7d, 0xe0, 0x3f, 0xf6, 0xf1, 0x37, + 0xbe, 0xb6, 0x05, 0x6b, 0xd3, 0x21, 0xd5, 0x5a, 0x7b, 0xb7, 0xd9, 0x29, 0x28, 0xa5, 0x4b, 0x27, + 0xa7, 0xfa, 0x6a, 0x2c, 0xa2, 0xda, 0x25, 0xc8, 0xa7, 0xb3, 0x01, 0xf5, 0x83, 0xfd, 0xfd, 0x46, + 0xa7, 0x90, 0x98, 0x09, 0x90, 0x07, 0xf6, 0x0d, 0x58, 0x9d, 0x0e, 0x68, 0x36, 0xf6, 0x0a, 0xc9, + 0x92, 0x76, 0x72, 0xaa, 0x2f, 0xc7, 0xd0, 0x4d, 0xd7, 0x2b, 0xa5, 0xbf, 0xfd, 0xbe, 0xbc, 0xf0, + 0xe3, 0x0f, 0x65, 0x85, 0x55, 0x96, 0x9f, 0x3a, 0x23, 0xb4, 0xf7, 0xe1, 0x4a, 0xbb, 0x71, 0xb7, + 0xb9, 0xbb, 0x63, 0xed, 0xb7, 0xef, 0x5a, 0x9d, 0x2f, 0x5b, 0xbb, 0xb1, 0xea, 0x56, 0x4e, 0x4e, + 0xf5, 0xac, 0x2c, 0x69, 0x1e, 0xba, 0x65, 0xee, 0x3e, 0x3c, 0xe8, 0xec, 0x16, 0x14, 0x81, 0x6e, + 0x85, 0xe8, 0x10, 0x53, 0xc4, 0xd1, 0xb7, 0xe0, 0xea, 0x05, 0xe8, 0xa8, 0xb0, 0xd5, 0x93, 0x53, + 0x3d, 0xdf, 0x0a, 0x91, 0xd8, 0x3f, 0x3c, 0xc2, 0x80, 0xe2, 0x6c, 0xc4, 0x41, 0xeb, 0xa0, 0x5d, + 0xdd, 0x2b, 0xe8, 0xa5, 0xc2, 0xc9, 0xa9, 0x9e, 0x1b, 0x1f, 0x86, 0x0c, 0x3f, 0xa9, 0xac, 0xf6, + 0xf9, 0xb3, 0xb3, 0xb2, 0xf2, 0xfc, 0xac, 0xac, 0xfc, 0x79, 0x56, 0x56, 0x9e, 0xbe, 0x2c, 0x2f, + 0x3c, 0x7f, 0x59, 0x5e, 0xf8, 0xed, 0x65, 0x79, 0xe1, 0xab, 0x8f, 0xfb, 0x2e, 0x1d, 0x8c, 0xba, + 0x46, 0x0f, 0x0f, 0xb7, 0xe2, 0x7f, 0x09, 0x26, 0x9f, 0xe2, 0xaf, 0xc9, 0xf9, 0xbf, 0x0b, 0xdd, + 0x25, 0x6e, 0xbf, 0xfd, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x78, 0x43, 0xdf, 0xef, 0x0c, + 0x00, 0x00, } func (m *PartSetHeader) Marshal() (dAtA []byte, err error) { @@ -1430,13 +1401,6 @@ func (m *Data) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Hash) > 0 { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) - i-- - dAtA[i] = 0x12 - } if len(m.Txs) > 0 { for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Txs[iNdEx]) @@ -1544,25 +1508,6 @@ func (m *Commit) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.BitArray != nil { - { - size, err := m.BitArray.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 - } - if len(m.Hash) > 0 { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) - i-- - dAtA[i] = 0x2a - } if len(m.Signatures) > 0 { for iNdEx := len(m.Signatures) - 1; iNdEx >= 0; iNdEx-- { { @@ -1627,12 +1572,12 @@ func (m *CommitSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err10 != nil { - return 0, err10 + n9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err9 != nil { + return 0, err9 } - i -= n10 - i = encodeVarintTypes(dAtA, i, uint64(n10)) + i -= n9 + i = encodeVarintTypes(dAtA, i, uint64(n9)) i-- dAtA[i] = 0x1a if len(m.ValidatorAddress) > 0 { @@ -1677,12 +1622,12 @@ func (m *Proposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x3a } - n11, err11 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err11 != nil { - return 0, err11 + n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err10 != nil { + return 0, err10 } - i -= n11 - i = encodeVarintTypes(dAtA, i, uint64(n11)) + i -= n10 + i = encodeVarintTypes(dAtA, i, uint64(n10)) i-- dAtA[i] = 0x32 { @@ -2044,10 +1989,6 @@ func (m *Data) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } - l = len(m.Hash) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } return n } @@ -2104,14 +2045,6 @@ func (m *Commit) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } - l = len(m.Hash) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - if m.BitArray != nil { - l = m.BitArray.Size() - n += 1 + l + sovTypes(uint64(l)) - } return n } @@ -3181,40 +3114,6 @@ func (m *Data) Unmarshal(dAtA []byte) error { m.Txs = append(m.Txs, make([]byte, postIndex-iNdEx)) copy(m.Txs[len(m.Txs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -3636,76 +3535,6 @@ func (m *Commit) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BitArray", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.BitArray == nil { - m.BitArray = &bits.BitArray{} - } - if err := m.BitArray.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/proto/tendermint/types/types.proto b/proto/tendermint/types/types.proto index d2f1d41263..7f7ea74cac 100644 --- a/proto/tendermint/types/types.proto +++ b/proto/tendermint/types/types.proto @@ -5,7 +5,6 @@ option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; -import "tendermint/libs/bits/types.proto"; import "tendermint/crypto/proof.proto"; import "tendermint/version/types.proto"; import "tendermint/types/validator.proto"; @@ -88,8 +87,6 @@ message Data { // NOTE: not all txs here are valid. We're just agreeing on the order first. // This means that block.AppHash does not include these txs. repeated bytes txs = 1; - // Volatile - bytes hash = 2; } // Vote represents a prevote, precommit, or commit vote from validators for @@ -113,8 +110,6 @@ message Commit { int32 round = 2; BlockID block_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; - bytes hash = 5; - tendermint.libs.bits.BitArray bit_array = 6; } // CommitSig is a part of the Vote included in a Commit. diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 0ecc03db0b..85b9c501db 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -290,7 +290,7 @@ func TestAppCalls(t *testing.T) { h = apph - 1 commit2, err := c.Commit(context.Background(), &h) require.NoError(err) - assert.Equal(block.Block.LastCommit, commit2.Commit) + assert.Equal(block.Block.LastCommitHash, commit2.Commit.Hash()) // and we got a proof that works! _pres, err := c.ABCIQueryWithOptions(context.Background(), "/key", k, client.ABCIQueryOptions{Prove: true}) diff --git a/state/execution.go b/state/execution.go index 2cb5857a19..009fdea227 100644 --- a/state/execution.go +++ b/state/execution.go @@ -104,6 +104,7 @@ func (blockExec *BlockExecutor) CreateProposalBlock( // Fetch a limited amount of valid txs maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size()) + txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) return state.MakeBlock(height, txs, commit, evidence, proposerAddr) diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index f5923b84be..c1bbfd3463 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -25,8 +25,8 @@ func TestTxFilter(t *testing.T) { tx types.Tx isErr bool }{ - {types.Tx(tmrand.Bytes(2154)), false}, - {types.Tx(tmrand.Bytes(2155)), true}, + {types.Tx(tmrand.Bytes(2187)), false}, + {types.Tx(tmrand.Bytes(2188)), true}, {types.Tx(tmrand.Bytes(3000)), true}, } @@ -37,7 +37,7 @@ func TestTxFilter(t *testing.T) { state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) require.NoError(t, err) - f := sm.TxPreCheck(state) // current max size of a tx 1850 + f := sm.TxPreCheck(state) if tc.isErr { assert.NotNil(t, f(tc.tx), "#%v", i) } else { diff --git a/types/block.go b/types/block.go index f4942a2431..bf48abad87 100644 --- a/types/block.go +++ b/types/block.go @@ -277,7 +277,7 @@ func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes - + MaxCommitBytes(valsCount) - evidenceBytes if maxDataBytes < 0 { @@ -289,7 +289,6 @@ func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { } return maxDataBytes - } // MaxDataBytesNoEvidence returns the maximum size of block's data when @@ -301,7 +300,7 @@ func MaxDataBytesNoEvidence(maxBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes + MaxCommitBytes(valsCount) if maxDataBytes < 0 { panic(fmt.Sprintf( @@ -581,6 +580,14 @@ const ( BlockIDFlagNil ) +const ( + // Max size of commit without any commitSigs -> 82 for BlockID, 8 for Height, 4 for Round. + MaxCommitOverheadBytes int64 = 94 + // Commit sig size is made up of 32 bytes for the signature, 20 bytes for the address, + // 1 byte for the flag and 14 bytes for the timestamp + MaxCommitSigBytes int64 = 77 +) + // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { BlockIDFlag BlockIDFlag `json:"block_id_flag"` @@ -599,9 +606,10 @@ func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) Commi } } -// ForBlock returns true if CommitSig is for the block. -func (cs CommitSig) ForBlock() bool { - return cs.BlockIDFlag == BlockIDFlagCommit +func MaxCommitBytes(valCount int) int64 { + // From the repeated commit sig field + var protoEncodingOverhead int64 = 2 + return MaxCommitOverheadBytes + ((MaxCommitSigBytes + protoEncodingOverhead) * int64(valCount)) } // NewCommitSigAbsent returns new CommitSig with BlockIDFlagAbsent. Other @@ -612,6 +620,11 @@ func NewCommitSigAbsent() CommitSig { } } +// ForBlock returns true if CommitSig is for the block. +func (cs CommitSig) ForBlock() bool { + return cs.BlockIDFlag == BlockIDFlagCommit +} + // Absent returns true if CommitSig is absent. func (cs CommitSig) Absent() bool { return cs.BlockIDFlag == BlockIDFlagAbsent @@ -935,10 +948,7 @@ func (commit *Commit) ToProto() *tmproto.Commit { c.Height = commit.Height c.Round = commit.Round c.BlockID = commit.BlockID.ToProto() - if commit.hash != nil { - c.Hash = commit.hash - } - c.BitArray = commit.bitArray.ToProto() + return c } @@ -950,8 +960,7 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { } var ( - commit = new(Commit) - bitArray *bits.BitArray + commit = new(Commit) ) bi, err := BlockIDFromProto(&cp.BlockID) @@ -959,8 +968,6 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { return nil, err } - bitArray.FromProto(cp.BitArray) - sigs := make([]CommitSig, len(cp.Signatures)) for i := range cp.Signatures { if err := sigs[i].FromProto(cp.Signatures[i]); err != nil { @@ -972,8 +979,6 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) { commit.Height = cp.Height commit.Round = cp.Round commit.BlockID = *bi - commit.hash = cp.Hash - commit.bitArray = bitArray return commit, commit.ValidateBasic() } @@ -1035,10 +1040,6 @@ func (data *Data) ToProto() tmproto.Data { tp.Txs = txBzs } - if data.hash != nil { - tp.Hash = data.hash - } - return *tp } @@ -1060,8 +1061,6 @@ func DataFromProto(dp *tmproto.Data) (Data, error) { data.Txs = Txs{} } - data.hash = dp.Hash - return *data, nil } @@ -1087,13 +1086,11 @@ func (data *EvidenceData) Hash() tmbytes.HexBytes { // ByteSize returns the total byte size of all the evidence func (data *EvidenceData) ByteSize() int64 { if data.byteSize == 0 && len(data.Evidence) != 0 { - for _, ev := range data.Evidence { - pb, err := EvidenceToProto(ev) - if err != nil { - panic(err) - } - data.byteSize += int64(pb.Size()) + pb, err := data.ToProto() + if err != nil { + panic(err) } + data.byteSize = int64(pb.Size()) } return data.byteSize } @@ -1135,10 +1132,6 @@ func (data *EvidenceData) ToProto() (*tmproto.EvidenceData, error) { } evi.Evidence = eviBzs - if data.hash != nil { - evi.Hash = data.hash - } - return evi, nil } @@ -1155,9 +1148,9 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceData) error { return err } eviBzs[i] = evi - data.byteSize += int64(eviData.Evidence[i].Size()) } data.Evidence = eviBzs + data.byteSize = int64(eviData.Size()) return nil } diff --git a/types/block_test.go b/types/block_test.go index 8e2d65f965..1565f5be0a 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -257,6 +257,62 @@ func TestCommitValidateBasic(t *testing.T) { } } +func TestMaxCommitSigBytes(t *testing.T) { + // time is varint encoded so need to pick the max. + // year int, month Month, day, hour, min, sec, nsec int, loc *Location + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + + cs := &CommitSig{ + BlockIDFlag: BlockIDFlagNil, + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), + Timestamp: timestamp, + Signature: tmhash.Sum([]byte("signature")), + } + + pb := cs.ToProto() + + assert.EqualValues(t, MaxCommitSigBytes, pb.Size()) +} + +func TestMaxCommitBytes(t *testing.T) { + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + + cs := CommitSig{ + BlockIDFlag: BlockIDFlagNil, + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), + Timestamp: timestamp, + Signature: tmhash.Sum([]byte("signature")), + } + + // check size with a single commit + commit := &Commit{ + Height: math.MaxInt64, + Round: math.MaxInt32, + BlockID: BlockID{ + Hash: tmhash.Sum([]byte("blockID_hash")), + PartSetHeader: PartSetHeader{ + Total: math.MaxInt32, + Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + }, + }, + Signatures: []CommitSig{cs}, + } + + pb := commit.ToProto() + + assert.EqualValues(t, MaxCommitBytes(1), int64(pb.Size())) + + // check the upper bound of the commit size + for i := 1; i < MaxVotesCount; i++ { + commit.Signatures = append(commit.Signatures, cs) + } + + pb = commit.ToProto() + + assert.EqualValues(t, MaxCommitBytes(MaxVotesCount), int64(pb.Size())) + +} + func TestHeaderHash(t *testing.T) { testCases := []struct { desc string @@ -407,9 +463,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {844, 1, 0, true, 0}, - 3: {846, 1, 0, false, 0}, - 4: {847, 1, 0, false, 1}, + 2: {809, 1, 0, true, 0}, + 3: {810, 1, 0, false, 0}, + 4: {811, 1, 0, false, 1}, } for i, tc := range testCases { @@ -436,9 +492,9 @@ func TestBlockMaxDataBytesNoEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {845, 1, true, 0}, - 3: {846, 1, false, 0}, - 4: {847, 1, false, 1}, + 2: {809, 1, true, 0}, + 3: {810, 1, false, 0}, + 4: {811, 1, false, 1}, } for i, tc := range testCases { @@ -628,9 +684,7 @@ func TestBlockProtoBuf(t *testing.T) { func TestDataProtoBuf(t *testing.T) { data := &Data{Txs: Txs{Tx([]byte{1}), Tx([]byte{2}), Tx([]byte{3})}} - _ = data.Hash() data2 := &Data{Txs: Txs{}} - _ = data2.Hash() testCases := []struct { msg string data1 *Data diff --git a/types/tx.go b/types/tx.go index 55defb5b28..92df92f13c 100644 --- a/types/tx.go +++ b/types/tx.go @@ -137,3 +137,11 @@ func TxProofFromProto(pb tmproto.TxProof) (TxProof, error) { return pbtp, nil } + +// ComputeProtoSizeForTxs wraps the transactions in tmproto.Data{} and calculates the size. +// https://developers.google.com/protocol-buffers/docs/encoding +func ComputeProtoSizeForTxs(txs []Tx) int64 { + data := Data{Txs: txs} + pdData := data.ToProto() + return int64(pdData.Size()) +} diff --git a/types/vote.go b/types/vote.go index 177eb23a75..29cfdd0519 100644 --- a/types/vote.go +++ b/types/vote.go @@ -13,9 +13,7 @@ import ( ) const ( - // MaxVoteBytes is a maximum vote size (including amino overhead). - MaxVoteBytes int64 = 209 - nilVoteStr string = "nil-Vote" + nilVoteStr string = "nil-Vote" ) var ( diff --git a/types/vote_test.go b/types/vote_test.go index 64e6f20fa1..51e20cd7ae 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -1,7 +1,6 @@ package types import ( - "math" "testing" "time" @@ -218,38 +217,6 @@ func TestVoteVerify(t *testing.T) { } } -func TestMaxVoteBytes(t *testing.T) { - // time is varint encoded so need to pick the max. - // year int, month Month, day, hour, min, sec, nsec int, loc *Location - timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - - vote := &Vote{ - ValidatorAddress: crypto.AddressHash([]byte("validator_address")), - ValidatorIndex: math.MaxInt32, - Height: math.MaxInt64, - Round: math.MaxInt32, - Timestamp: timestamp, - Type: tmproto.PrevoteType, - BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), - PartSetHeader: PartSetHeader{ - Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), - }, - }, - } - - v := vote.ToProto() - privVal := NewMockPV() - err := privVal.SignVote("test_chain_id", v) - require.NoError(t, err) - - bz, err := proto.Marshal(v) - require.NoError(t, err) - - assert.EqualValues(t, MaxVoteBytes, len(bz)) -} - func TestVoteString(t *testing.T) { str := examplePrecommit().String() expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests From bd1f43d79300012768167a909a6b2388acdbe411 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 13 Oct 2020 20:00:59 +0200 Subject: [PATCH 010/108] changelog: prepare changelog for RC5 (#5494) * changelog: prepare changelog for RC5 * Update CHANGELOG.md Co-authored-by: Marko * Update CHANGELOG.md Co-authored-by: Marko Co-authored-by: Marko --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d57c9d5201..5b9bb315be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## v0.34.0-rc5 + +*October 13, 2020* + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### BREAKING CHANGES + +- Go API + - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) `MaxNum` evidence consensus parameter has been changed to `MaxBytes` (@cmwaters) + - [mempool] [\#5493](https://github.com/tendermint/tendermint/issues/5493) reduce `ComputeProtoSizeForTxs` to take one parameter (@marbar3778) + +### IMPROVEMENTS + +- [config] [\#5433](https://github.com/tendermint/tendermint/issues/5433) `statesync.rpc_servers` is now properly set when writing the configuration file (@erikgrinaker) + +- [privval] [\#5437](https://github.com/tendermint/tendermint/issues/5437) `NewSignerDialerEndpoint` can now be given `SignerServiceEndpointOption` (@erikgrinaker) +- [types] [\#5490](https://github.com/tendermint/tendermint/pull/5490) Use `Commit` and `CommitSig` max sizes instead of vote max size to calculate the maxiumum block size. (@cmwaters) + +### BUG FIXES + +- [privval] [\#5441](https://github.com/tendermint/tendermint/issues/5441) Fix faulty ping message encoding causing nil message errors in logs (@erikgrinaker) +- [rpc] [\#5459](https://github.com/tendermint/tendermint/issues/5459) Register the interface of public keys for json encoding (@marbar3778) +- [consensus] [\#5431](https://github.com/tendermint/tendermint/pull/5431) Check BlockParts do not exceed Maxium block size. (@cmwaters) +- [mempool] [\#5483](https://github.com/tendermint/tendermint/pull/5483) Check tx size does not exceed available free space in the block. (@marbar3778) + ## v0.34.0-rc4 *September 24, 2020* From b3238cdcd9b98ebdbb95633f020ffb6a62fc682b Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 16 Oct 2020 14:39:56 +0200 Subject: [PATCH 011/108] statesync: check all necessary heights when adding snapshot to pool (#5516) (#5518) Fixes #5511. --- CHANGELOG_PENDING.md | 10 ++-------- statesync/stateprovider.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2966b2ec43..0156b7f4b0 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Unreleased Changes -## v0.34.0-rc5 +## v0.34.0-rc6 Special thanks to external contributors on this release: @@ -15,7 +15,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API - - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) `MaxNum` evidence consensus parameter has been changed to `MaxBytes` (@cmwaters) - Blockchain Protocol @@ -23,11 +22,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS -- [config] \#5433 `statesync.rpc_servers` is now properly set when writing the configuration file (@erikgrinaker) - -- [privval] \#5437 `NewSignerDialerEndpoint` can now be given `SignerServiceEndpointOption` (@erikgrinaker) +- [statesync] \#5516 Check that all heights necessary to rebuild state for a snapshot exist before adding the snapshot to the pool. (@erikgrinaker) ### BUG FIXES - -- [privval] \#5441 Fix faulty ping message encoding causing nil message errors in logs (@erikgrinaker) -- [rpc] \#5459 Register the interface of public keys for json encoding (@marbar3778) diff --git a/statesync/stateprovider.go b/statesync/stateprovider.go index 1e21b0aafb..4b1c75e328 100644 --- a/statesync/stateprovider.go +++ b/statesync/stateprovider.go @@ -94,6 +94,22 @@ func (s *lightClientStateProvider) AppHash(ctx context.Context, height uint64) ( if err != nil { return nil, err } + // We also try to fetch the blocks at height H and H+2, since we need these + // when building the state while restoring the snapshot. This avoids the race + // condition where we try to restore a snapshot before H+2 exists. + // + // FIXME This is a hack, since we can't add new methods to the interface without + // breaking it. We should instead have a Has(ctx, height) method which checks + // that the state provider has access to the necessary data for the height. + // We piggyback on AppHash() since it's called when adding snapshots to the pool. + _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now()) + if err != nil { + return nil, err + } + _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now()) + if err != nil { + return nil, err + } return header.AppHash, nil } From 6f908eb81470df769709f05ea76e3f92d6027485 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 15 Oct 2020 10:10:06 +0200 Subject: [PATCH 012/108] crypto: add in secp256k1 support (#5500) Secp256k1 was removed in the protobuf migration, this pr adds it back in order to provide this functionality for users (band) Closes: #5495 --- .markdownlintignore | 1 + CHANGELOG.md | 1 - UPGRADING.md | 105 ++++++------ abci/example/kvstore/persistent_kvstore.go | 2 +- crypto/ed25519/ed25519.go | 6 +- crypto/encoding/codec.go | 16 ++ crypto/secp256k1/secp256k1.go | 173 ++++++++++++++++++++ crypto/secp256k1/secp256k1_internal_test.go | 74 +++++++++ crypto/secp256k1/secp256k1_nocgo.go | 75 +++++++++ crypto/secp256k1/secp256k1_test.go | 116 +++++++++++++ go.mod | 7 +- go.sum | 65 +++++--- proto/tendermint/crypto/keys.pb.go | 153 +++++++++++++++-- proto/tendermint/crypto/keys.proto | 3 +- types/params_test.go | 7 +- types/protobuf.go | 7 +- 16 files changed, 711 insertions(+), 100 deletions(-) create mode 100644 crypto/secp256k1/secp256k1.go create mode 100644 crypto/secp256k1/secp256k1_internal_test.go create mode 100644 crypto/secp256k1/secp256k1_nocgo.go create mode 100644 crypto/secp256k1/secp256k1_test.go diff --git a/.markdownlintignore b/.markdownlintignore index c4f0964fc8..12b20d6bb1 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -1,5 +1,6 @@ docs/node_modules CHANGELOG.md docs/architecture/* +crypto/secp256k1/** scripts/* .github diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b9bb315be..19a668bc35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [evidence] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove Amnesia & potentialAmnesia evidence types and removed POLC. (@marbar3778) - [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and change evidence interface (@cmwaters) - [params] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove `ProofofTrialPeriod` from evidence params (@marbar3778) - - [crypto/secp256k1] [\#5280](https://github.com/tendermint/tendermint/issues/5280) `secp256k1` has been removed from the Tendermint repo. (@marbar3778) - [light] [\#5347](https://github.com/tendermint/tendermint/issues/5347) `NewClient`, `NewHTTPClient`, `VerifyHeader` and `VerifyLightBlockAtHeight` now accept `context.Context` as 1st param (@melekes) - [state] [\#5348](https://github.com/tendermint/tendermint/issues/5348) Define an Interface for the state store. (@marbar3778) diff --git a/UPGRADING.md b/UPGRADING.md index cfd713a433..ff8309f9a0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -10,42 +10,42 @@ the encoding format (see "Protocol Buffers," below) and the block header (see "B ### ABCI Changes -* New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`) - were added to support the new State Sync feature. - Previously, syncing a new node to a preexisting network could take days; but with State Sync, - new nodes are able to join a network in a matter of seconds. - Read [the spec](https://docs.tendermint.com/master/spec/abci/apps.html#state-sync) - if you want to learn more about State Sync, or if you'd like your application to use it. - (If you don't want to support State Sync in your application, you can just implement these new - ABCI methods as no-ops, leaving them empty.) - -* `KV.Pair` has been replaced with `abci.EventAttribute`. The `EventAttribute.Index` field +* New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`) + were added to support the new State Sync feature. + Previously, syncing a new node to a preexisting network could take days; but with State Sync, + new nodes are able to join a network in a matter of seconds. + Read [the spec](https://docs.tendermint.com/master/spec/abci/apps.html#state-sync) + if you want to learn more about State Sync, or if you'd like your application to use it. + (If you don't want to support State Sync in your application, you can just implement these new + ABCI methods as no-ops, leaving them empty.) + +* `KV.Pair` has been replaced with `abci.EventAttribute`. The `EventAttribute.Index` field allows ABCI applications to dictate which events should be indexed. -* The blockchain can now start from an arbitrary initial height, +* The blockchain can now start from an arbitrary initial height, provided to the application via `RequestInitChain.InitialHeight`. -* ABCI evidence type is now an enum with two recognized types of evidence: - `DUPLICATE_VOTE` and `LIGHT_CLIENT_ATTACK`. - Applications should be able to handle these evidence types +* ABCI evidence type is now an enum with two recognized types of evidence: + `DUPLICATE_VOTE` and `LIGHT_CLIENT_ATTACK`. + Applications should be able to handle these evidence types (i.e., through slashing or other accountability measures). -* The [`PublicKey` type](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto#L13-L15) - (used in ABCI as part of `ValidatorUpdate`) now uses a `oneof` protobuf type. - Note that since Tendermint only supports ed25519 validator keys, there's only one +* The [`PublicKey` type](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto#L13-L15) + (used in ABCI as part of `ValidatorUpdate`) now uses a `oneof` protobuf type. + Note that since Tendermint only supports ed25519 validator keys, there's only one option in the `oneof`. For more, see "Protocol Buffers," below. -* The field `Proof`, on the ABCI type `ResponseQuery`, is now named `ProofOps`. - For more, see "Crypto," below. +* The field `Proof`, on the ABCI type `ResponseQuery`, is now named `ProofOps`. + For more, see "Crypto," below. ### P2P Protocol The default codec is now proto3, not amino. The schema files can be found in the `/proto` -directory. For more, see "Protobuf," below. +directory. For more, see "Protobuf," below. ### Blockchain Protocol -* `Header#LastResultsHash` previously was the root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data)` responses. +* `Header#LastResultsHash` previously was the root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data)` responses. As of 0.34,`Header#LastResultsHash` is now the root hash of a Merkle tree built from: * `BeginBlock#Events` * Root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data, @@ -53,20 +53,20 @@ directory. For more, see "Protobuf," below. * `BeginBlock#Events` * Merkle hashes of empty trees previously returned nothing, but now return the hash of an empty input, - to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962). + to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962). This mainly affects `Header#DataHash`, `Header#LastResultsHash`, and `Header#EvidenceHash`, which are often empty. Non-empty hashes can also be affected, e.g. if their inputs depend on other (empty) Merkle hashes, giving different results. ### Transaction Indexing -Tendermint now relies on the application to tell it which transactions to index. This means that -in the `config.toml`, generated by Tendermint, there is no longer a way to specify which +Tendermint now relies on the application to tell it which transactions to index. This means that +in the `config.toml`, generated by Tendermint, there is no longer a way to specify which transactions to index. `tx.height` & `tx.hash` will always be indexed when using the `kv` indexer. -Applications must now choose to either a) enable indexing for all transactions, or +Applications must now choose to either a) enable indexing for all transactions, or b) allow node operators to decide which transactions to index. -Applications can notify Tendermint to index a specific transaction by setting +Applications can notify Tendermint to index a specific transaction by setting `Index: bool` to `true` in the Event Attribute: ```go @@ -82,19 +82,19 @@ Applications can notify Tendermint to index a specific transaction by setting ### Protocol Buffers -Tendermint 0.34 replaces Amino with Protocol Buffers for encoding. -This migration is extensive and results in a number of changes, however, +Tendermint 0.34 replaces Amino with Protocol Buffers for encoding. +This migration is extensive and results in a number of changes, however, Tendermint only uses the types generated from Protocol Buffers for disk and -wire serialization. +wire serialization. **This means that these changes should not affect you as a Tendermint user.** However, Tendermint users and contributors may note the following changes: -* Directory layout changes: All proto files have been moved under one directory, `/proto`. - This is in line with the recommended file layout by [Buf](https://buf.build). +* Directory layout changes: All proto files have been moved under one directory, `/proto`. + This is in line with the recommended file layout by [Buf](https://buf.build). For more, see the [Buf documentation](https://buf.build/docs/lint-checkers#file_layout). -* ABCI Changes: As noted in the "ABCI Changes" section above, the `PublicKey` type now uses - a `oneof` type. +* ABCI Changes: As noted in the "ABCI Changes" section above, the `PublicKey` type now uses + a `oneof` type. For more on the Protobuf changes, please see our [blog post on this migration](https://medium.com/tendermint/tendermint-0-34-protocol-buffers-and-you-8c40558939ae). @@ -114,30 +114,27 @@ Tendermint 0.34 includes new and updated consensus parameters. #### Keys -* Keys no longer include a type prefix. For example, ed25519 pubkeys have been renamed from - `PubKeyEd25519` to `PubKey`. This reduces stutter (e.g., `ed25519.PubKey`). +* Keys no longer include a type prefix. For example, ed25519 pubkeys have been renamed from + `PubKeyEd25519` to `PubKey`. This reduces stutter (e.g., `ed25519.PubKey`). * Keys are now byte slices (`[]byte`) instead of byte arrays (`[]byte`). -* The multisig functionality that was previously in Tendermint now has - a new home within the Cosmos SDK: +* The multisig functionality that was previously in Tendermint now has + a new home within the Cosmos SDK: [`cosmos/cosmos-sdk/types/multisig`](https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go). -* Similarly, secp256k1 has been removed from the Tendermint repo. - There is still [a secp256k1 implementation in the Cosmos SDK](https://github.com/cosmos/cosmos-sdk/tree/443e0c1f89bd3730a731aea30453bd732f7efa35/crypto/keys/secp256k1), - and we recommend you use that package for all your secp256k1 needs. #### `merkle` Package * `SimpleHashFromMap()` and `SimpleProofsFromMap()` were removed. -* The prefix `Simple` has been removed. (For example, `SimpleProof` is now called `Proof`.) -* All protobuf messages have been moved to the `/proto` directory. -* The protobuf message `Proof` that contained multiple ProofOp's has been renamed to `ProofOps`. - As noted above, this affects the ABCI type `ResponseQuery`: +* The prefix `Simple` has been removed. (For example, `SimpleProof` is now called `Proof`.) +* All protobuf messages have been moved to the `/proto` directory. +* The protobuf message `Proof` that contained multiple ProofOp's has been renamed to `ProofOps`. + As noted above, this affects the ABCI type `ResponseQuery`: The field that was named Proof is now named `ProofOps`. * `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962). ### `libs` Package -The `bech32` package has moved to the Cosmos SDK: +The `bech32` package has moved to the Cosmos SDK: [`cosmos/cosmos-sdk/types/bech32`](https://github.com/cosmos/cosmos-sdk/tree/4173ea5ebad906dd9b45325bed69b9c655504867/types/bech32). ### CLI @@ -147,37 +144,37 @@ See [the docs](https://docs.tendermint.com/master/tendermint-core/light-client-p ### Light Client -We have a new, rewritten light client! You can +We have a new, rewritten light client! You can [read more](https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98) -about the justifications and details behind this change. +about the justifications and details behind this change. Other user-relevant changes include: * The old `lite` package was removed; the new light client uses the `light` package. -* The `Verifier` was broken up into two pieces: - * Core verification logic (pure `VerifyX` functions) +* The `Verifier` was broken up into two pieces: + * Core verification logic (pure `VerifyX` functions) * `Client` object, which represents the complete light client -* The RPC client can be found in the `/rpc` directory. +* The RPC client can be found in the `/rpc` directory. * The HTTP(S) proxy is located in the `/proxy` directory. ### `state` Package * A new field `State.InitialHeight` has been added to record the initial chain height, which must be `1` (not `0`) if starting from height `1`. This can be configured via the genesis field `initial_height`. -* The `state` package now has a `Store` interface. All functions in - [state/store.go](https://github.com/tendermint/tendermint/blob/56911ee35298191c95ef1c7d3d5ec508237aaff4/state/store.go#L42-L42) +* The `state` package now has a `Store` interface. All functions in + [state/store.go](https://github.com/tendermint/tendermint/blob/56911ee35298191c95ef1c7d3d5ec508237aaff4/state/store.go#L42-L42) are now part of the interface. The interface returns errors on all methods and can be used by calling `state.NewStore(dbm.DB)`. ### `privval` Package All requests are now accompanied by the chain ID from the network. -This is a optional field and can be ignored by key management systems. +This is a optional field and can be ignored by key management systems. It is recommended to check the chain ID if using the same key management system for multiple chains. ### RPC `/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and -`/unsafe_write_heap_profile` were removed. +`/unsafe_write_heap_profile` were removed. For profiling, please use the pprof server, which can be enabled through `--rpc.pprof_laddr=X` flag or `pprof_laddr=X` config setting in the rpc section. diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index 27c22a719f..93676bdded 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -243,11 +243,11 @@ func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.Respon // add, update, or remove a validator func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx { - key := []byte("val:" + string(v.PubKey.GetEd25519())) pubkey, err := cryptoenc.PubKeyFromProto(v.PubKey) if err != nil { panic(fmt.Errorf("can't decode public key: %w", err)) } + key := []byte("val:" + string(pubkey.Bytes())) if v.Power == 0 { // remove validator diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index b7318043ad..36095eece4 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -31,7 +31,7 @@ const ( // private key representations used by RFC 8032. SeedSize = 32 - keyType = "ed25519" + KeyType = "ed25519" ) func init() { @@ -93,7 +93,7 @@ func (privKey PrivKey) Equals(other crypto.PrivKey) bool { } func (privKey PrivKey) Type() string { - return keyType + return KeyType } // GenPrivKey generates a new ed25519 private key. @@ -159,7 +159,7 @@ func (pubKey PubKey) String() string { } func (pubKey PubKey) Type() string { - return keyType + return KeyType } func (pubKey PubKey) Equals(other crypto.PubKey) bool { diff --git a/crypto/encoding/codec.go b/crypto/encoding/codec.go index 7438cb5fb0..f9c721131b 100644 --- a/crypto/encoding/codec.go +++ b/crypto/encoding/codec.go @@ -5,6 +5,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/libs/json" pc "github.com/tendermint/tendermint/proto/tendermint/crypto" ) @@ -12,6 +13,7 @@ import ( func init() { json.RegisterType((*pc.PublicKey)(nil), "tendermint.crypto.PublicKey") json.RegisterType((*pc.PublicKey_Ed25519)(nil), "tendermint.crypto.PublicKey_Ed25519") + json.RegisterType((*pc.PublicKey_Secp256K1)(nil), "tendermint.crypto.PublicKey_Secp256K1") } // PubKeyToProto takes crypto.PubKey and transforms it to a protobuf Pubkey @@ -24,6 +26,12 @@ func PubKeyToProto(k crypto.PubKey) (pc.PublicKey, error) { Ed25519: k, }, } + case secp256k1.PubKey: + kp = pc.PublicKey{ + Sum: &pc.PublicKey_Secp256K1{ + Secp256K1: k, + }, + } default: return kp, fmt.Errorf("toproto: key type %v is not supported", k) } @@ -41,6 +49,14 @@ func PubKeyFromProto(k pc.PublicKey) (crypto.PubKey, error) { pk := make(ed25519.PubKey, ed25519.PubKeySize) copy(pk, k.Ed25519) return pk, nil + case *pc.PublicKey_Secp256K1: + if len(k.Secp256K1) != secp256k1.PubKeySize { + return nil, fmt.Errorf("invalid size for PubKeyEd25519. Got %d, expected %d", + len(k.Secp256K1), secp256k1.PubKeySize) + } + pk := make(secp256k1.PubKey, secp256k1.PubKeySize) + copy(pk, k.Secp256K1) + return pk, nil default: return nil, fmt.Errorf("fromproto: key type %v is not supported", k) } diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go new file mode 100644 index 0000000000..fe5296900a --- /dev/null +++ b/crypto/secp256k1/secp256k1.go @@ -0,0 +1,173 @@ +package secp256k1 + +import ( + "bytes" + "crypto/sha256" + "crypto/subtle" + "fmt" + "io" + "math/big" + + secp256k1 "github.com/btcsuite/btcd/btcec" + "golang.org/x/crypto/ripemd160" // nolint: staticcheck // necessary for Bitcoin address format + + "github.com/tendermint/tendermint/crypto" + tmjson "github.com/tendermint/tendermint/libs/json" +) + +//------------------------------------- +const ( + PrivKeyName = "tendermint/PrivKeySecp256k1" + PubKeyName = "tendermint/PubKeySecp256k1" + + KeyType = "secp256k1" + PrivKeySize = 32 +) + +func init() { + tmjson.RegisterType(PubKey{}, PubKeyName) + tmjson.RegisterType(PrivKey{}, PrivKeyName) +} + +var _ crypto.PrivKey = PrivKey{} + +// PrivKey implements PrivKey. +type PrivKey []byte + +// Bytes marshalls the private key using amino encoding. +func (privKey PrivKey) Bytes() []byte { + return []byte(privKey) +} + +// PubKey performs the point-scalar multiplication from the privKey on the +// generator point to get the pubkey. +func (privKey PrivKey) PubKey() crypto.PubKey { + _, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey) + + pk := pubkeyObject.SerializeCompressed() + + return PubKey(pk) +} + +// Equals - you probably don't need to use this. +// Runs in constant time based on length of the keys. +func (privKey PrivKey) Equals(other crypto.PrivKey) bool { + if otherSecp, ok := other.(PrivKey); ok { + return subtle.ConstantTimeCompare(privKey[:], otherSecp[:]) == 1 + } + return false +} + +func (privKey PrivKey) Type() string { + return KeyType +} + +// GenPrivKey generates a new ECDSA private key on curve secp256k1 private key. +// It uses OS randomness to generate the private key. +func GenPrivKey() PrivKey { + return genPrivKey(crypto.CReader()) +} + +// genPrivKey generates a new secp256k1 private key using the provided reader. +func genPrivKey(rand io.Reader) PrivKey { + var privKeyBytes [PrivKeySize]byte + d := new(big.Int) + + for { + privKeyBytes = [PrivKeySize]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 + } + } + + return PrivKey(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) PrivKey { + 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() + privKey32 := make([]byte, PrivKeySize) + // copy feB over to fixed 32 byte privKey32 and pad (if necessary) + copy(privKey32[32-len(feB):32], feB) + + return PrivKey(privKey32) +} + +//------------------------------------- + +var _ crypto.PubKey = PubKey{} + +// PubKeySize is comprised of 32 bytes for one field element +// (the x-coordinate), plus one byte for the parity of the y-coordinate. +const PubKeySize = 33 + +// PubKey implements crypto.PubKey. +// It is the compressed form of the pubkey. The first byte depends is a 0x02 byte +// if the y-coordinate is the lexicographically largest of the two associated with +// the x-coordinate. Otherwise the first byte is a 0x03. +// This prefix is followed with the x-coordinate. +type PubKey []byte + +// Address returns a Bitcoin style addresses: RIPEMD160(SHA256(pubkey)) +func (pubKey PubKey) Address() crypto.Address { + if len(pubKey) != PubKeySize { + panic("length of pubkey is incorrect") + } + hasherSHA256 := sha256.New() + _, _ = hasherSHA256.Write(pubKey) // does not error + sha := hasherSHA256.Sum(nil) + + hasherRIPEMD160 := ripemd160.New() + _, _ = hasherRIPEMD160.Write(sha) // does not error + + return crypto.Address(hasherRIPEMD160.Sum(nil)) +} + +// Bytes returns the pubkey marshalled with amino encoding. +func (pubKey PubKey) Bytes() []byte { + return []byte(pubKey) +} + +func (pubKey PubKey) String() string { + return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:]) +} + +func (pubKey PubKey) Equals(other crypto.PubKey) bool { + if otherSecp, ok := other.(PubKey); ok { + return bytes.Equal(pubKey[:], otherSecp[:]) + } + return false +} + +func (pubKey PubKey) Type() string { + return KeyType +} diff --git a/crypto/secp256k1/secp256k1_internal_test.go b/crypto/secp256k1/secp256k1_internal_test.go new file mode 100644 index 0000000000..bceddc24ff --- /dev/null +++ b/crypto/secp256k1/secp256k1_internal_test.go @@ -0,0 +1,74 @@ +package secp256k1 + +import ( + "bytes" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + secp256k1 "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", secp256k1.S256().N.Bytes(), true}, + {"valid because 0 < 1 < N", validOne, false}, + } + for _, tt := range tests { + tt := tt + 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(secp256k1.S256().N) < 0) + require.True(t, fe.Sign() > 0) + }) + } +} + +// Ensure that signature verification works, and that +// non-canonical signatures fail. +// Note: run with CGO_ENABLED=0 or go test -tags !cgo. +func TestSignatureVerificationAndRejectUpperS(t *testing.T) { + msg := []byte("We have lingered long enough on the shores of the cosmic ocean.") + for i := 0; i < 500; i++ { + priv := GenPrivKey() + sigStr, err := priv.Sign(msg) + require.NoError(t, err) + sig := signatureFromBytes(sigStr) + require.False(t, sig.S.Cmp(secp256k1halfN) > 0) + + pub := priv.PubKey() + require.True(t, pub.VerifySignature(msg, sigStr)) + + // malleate: + sig.S.Sub(secp256k1.S256().CurveParams.N, sig.S) + require.True(t, sig.S.Cmp(secp256k1halfN) > 0) + malSigStr := serializeSig(sig) + + require.False(t, pub.VerifySignature(msg, malSigStr), + "VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v", + sig, + priv, + ) + } +} diff --git a/crypto/secp256k1/secp256k1_nocgo.go b/crypto/secp256k1/secp256k1_nocgo.go new file mode 100644 index 0000000000..cba9bbe4c3 --- /dev/null +++ b/crypto/secp256k1/secp256k1_nocgo.go @@ -0,0 +1,75 @@ +// +build !libsecp256k1 + +package secp256k1 + +import ( + "math/big" + + secp256k1 "github.com/btcsuite/btcd/btcec" + + "github.com/tendermint/tendermint/crypto" +) + +// used to reject malleable signatures +// see: +// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 +// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39 +var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1) + +// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. +// The returned signature will be of the form R || S (in lower-S form). +func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { + priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey) + + sig, err := priv.Sign(crypto.Sha256(msg)) + if err != nil { + return nil, err + } + + sigBytes := serializeSig(sig) + return sigBytes, nil +} + +// VerifySignature verifies a signature of the form R || S. +// It rejects signatures which are not in lower-S form. +func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool { + if len(sigStr) != 64 { + return false + } + + pub, err := secp256k1.ParsePubKey(pubKey, secp256k1.S256()) + if err != nil { + return false + } + + // parse the signature: + signature := signatureFromBytes(sigStr) + // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. + // see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 + if signature.S.Cmp(secp256k1halfN) > 0 { + return false + } + + return signature.Verify(crypto.Sha256(msg), pub) +} + +// Read Signature struct from R || S. Caller needs to ensure +// that len(sigStr) == 64. +func signatureFromBytes(sigStr []byte) *secp256k1.Signature { + return &secp256k1.Signature{ + R: new(big.Int).SetBytes(sigStr[:32]), + S: new(big.Int).SetBytes(sigStr[32:64]), + } +} + +// Serialize signature to R || S. +// R, S are padded to 32 bytes respectively. +func serializeSig(sig *secp256k1.Signature) []byte { + rBytes := sig.R.Bytes() + sBytes := sig.S.Bytes() + sigBytes := make([]byte, 64) + // 0 pad the byte arrays from the left if they aren't big enough. + copy(sigBytes[32-len(rBytes):32], rBytes) + copy(sigBytes[64-len(sBytes):64], sBytes) + return sigBytes +} diff --git a/crypto/secp256k1/secp256k1_test.go b/crypto/secp256k1/secp256k1_test.go new file mode 100644 index 0000000000..83249ef6a0 --- /dev/null +++ b/crypto/secp256k1/secp256k1_test.go @@ -0,0 +1,116 @@ +package secp256k1_test + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" + + underlyingSecp256k1 "github.com/btcsuite/btcd/btcec" +) + +type keyData struct { + priv string + pub string + addr string +} + +var secpDataTable = []keyData{ + { + priv: "a96e62ed3955e65be32703f12d87b6b5cf26039ecfa948dc5107a495418e5330", + pub: "02950e1cdfcb133d6024109fd489f734eeb4502418e538c28481f22bce276f248c", + addr: "1CKZ9Nx4zgds8tU7nJHotKSDr4a9bYJCa3", + }, +} + +func TestPubKeySecp256k1Address(t *testing.T) { + for _, d := range secpDataTable { + privB, _ := hex.DecodeString(d.priv) + pubB, _ := hex.DecodeString(d.pub) + addrBbz, _, _ := base58.CheckDecode(d.addr) + addrB := crypto.Address(addrBbz) + + var priv secp256k1.PrivKey = secp256k1.PrivKey(privB) + + pubKey := priv.PubKey() + pubT, _ := pubKey.(secp256k1.PubKey) + pub := pubT + addr := pubKey.Address() + + assert.Equal(t, pub, secp256k1.PubKey(pubB), "Expected pub keys to match") + assert.Equal(t, addr, addrB, "Expected addresses to match") + } +} + +func TestSignAndValidateSecp256k1(t *testing.T) { + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + + msg := crypto.CRandBytes(128) + sig, err := privKey.Sign(msg) + require.Nil(t, err) + + assert.True(t, pubKey.VerifySignature(msg, sig)) + + // Mutate the signature, just one bit. + sig[3] ^= byte(0x01) + + assert.False(t, pubKey.VerifySignature(msg, sig)) +} + +// This test is intended to justify the removal of calls to the underlying library +// in creating the privkey. +func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { + numberOfTests := 256 + for i := 0; i < numberOfTests; i++ { + // Seed the test case with some random bytes + privKeyBytes := [32]byte{} + copy(privKeyBytes[:], crypto.CRandBytes(32)) + + // This function creates a private and public key in the underlying libraries format. + // The private key is basically calling new(big.Int).SetBytes(pk), which removes leading zero bytes + priv, _ := underlyingSecp256k1.PrivKeyFromBytes(underlyingSecp256k1.S256(), privKeyBytes[:]) + // this takes the bytes returned by `(big int).Bytes()`, and if the length is less than 32 bytes, + // pads the bytes from the left with zero bytes. Therefore these two functions composed + // result in the identity function on privKeyBytes, hence the following equality check + // always returning true. + serializedBytes := priv.Serialize() + 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 { + tt := tt + 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/go.mod b/go.mod index 44d36687a1..1a812dd1c4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.14 require ( github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d github.com/Workiva/go-datastructures v1.0.52 + github.com/btcsuite/btcd v0.21.0-beta + github.com/btcsuite/btcutil v1.0.2 github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.10.0 github.com/go-logfmt/logfmt v0.5.0 @@ -14,6 +16,7 @@ require ( github.com/gtank/merlin v0.1.1 github.com/libp2p/go-buffer-pool v0.0.2 github.com/minio/highwayhash v1.0.1 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 @@ -25,7 +28,9 @@ require ( github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 github.com/tendermint/tm-db v0.6.2 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc + google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect google.golang.org/grpc v1.32.0 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 3146c274ed..6846cd2491 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -51,6 +52,20 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -80,9 +95,11 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger/v2 v2.2007.1 h1:t36VcBCpo4SsmAD5M8wVv1ieVzcALyGfaJ92z4ccULM= github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -161,6 +178,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -236,11 +254,14 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -252,6 +273,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -301,6 +323,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -313,6 +337,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -483,6 +508,7 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -494,8 +520,12 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -515,9 +545,9 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -534,7 +564,6 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= @@ -542,7 +571,6 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -559,7 +587,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -574,21 +601,16 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -621,7 +643,6 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -643,11 +664,12 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -658,9 +680,8 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -668,20 +689,20 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= @@ -691,13 +712,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/proto/tendermint/crypto/keys.pb.go b/proto/tendermint/crypto/keys.pb.go index cb54ce0f1a..2e7282b0aa 100644 --- a/proto/tendermint/crypto/keys.pb.go +++ b/proto/tendermint/crypto/keys.pb.go @@ -28,6 +28,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type PublicKey struct { // Types that are valid to be assigned to Sum: // *PublicKey_Ed25519 + // *PublicKey_Secp256K1 Sum isPublicKey_Sum `protobuf_oneof:"sum"` } @@ -75,8 +76,12 @@ type isPublicKey_Sum interface { type PublicKey_Ed25519 struct { Ed25519 []byte `protobuf:"bytes,1,opt,name=ed25519,proto3,oneof" json:"ed25519,omitempty"` } +type PublicKey_Secp256K1 struct { + Secp256K1 []byte `protobuf:"bytes,2,opt,name=secp256k1,proto3,oneof" json:"secp256k1,omitempty"` +} -func (*PublicKey_Ed25519) isPublicKey_Sum() {} +func (*PublicKey_Ed25519) isPublicKey_Sum() {} +func (*PublicKey_Secp256K1) isPublicKey_Sum() {} func (m *PublicKey) GetSum() isPublicKey_Sum { if m != nil { @@ -92,10 +97,18 @@ func (m *PublicKey) GetEd25519() []byte { return nil } +func (m *PublicKey) GetSecp256K1() []byte { + if x, ok := m.GetSum().(*PublicKey_Secp256K1); ok { + return x.Secp256K1 + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*PublicKey) XXX_OneofWrappers() []interface{} { return []interface{}{ (*PublicKey_Ed25519)(nil), + (*PublicKey_Secp256K1)(nil), } } @@ -106,19 +119,20 @@ func init() { func init() { proto.RegisterFile("tendermint/crypto/keys.proto", fileDescriptor_cb048658b234868c) } var fileDescriptor_cb048658b234868c = []byte{ - // 180 bytes of a gzipped FileDescriptorProto + // 199 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x29, 0x49, 0xcd, 0x4b, 0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0xcf, 0x4e, 0xad, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x44, 0xc8, 0xea, 0x41, 0x64, 0xa5, - 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x92, 0x05, 0x17, 0x67, + 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x52, 0x04, 0x17, 0x67, 0x40, 0x69, 0x52, 0x4e, 0x66, 0xb2, 0x77, 0x6a, 0xa5, 0x90, 0x14, 0x17, 0x7b, 0x6a, 0x8a, 0x91, - 0xa9, 0xa9, 0xa1, 0xa5, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x07, 0x43, 0x10, 0x4c, 0xc0, 0x8a, - 0xe3, 0xc5, 0x02, 0x79, 0xc6, 0x17, 0x0b, 0xe5, 0x19, 0x9d, 0x58, 0xb9, 0x98, 0x8b, 0x4b, 0x73, - 0x9d, 0x82, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, - 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x22, 0x3d, 0xb3, - 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xc9, 0x95, 0x48, 0x4c, 0x88, 0x33, 0x30, - 0x7c, 0x90, 0xc4, 0x06, 0x96, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x51, 0x7d, 0xc1, 0x7b, - 0xdd, 0x00, 0x00, 0x00, + 0xa9, 0xa9, 0xa1, 0xa5, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x07, 0x43, 0x10, 0x4c, 0x40, 0x48, + 0x8e, 0x8b, 0xb3, 0x38, 0x35, 0xb9, 0xc0, 0xc8, 0xd4, 0x2c, 0xdb, 0x50, 0x82, 0x09, 0x2a, 0x8b, + 0x10, 0xb2, 0xe2, 0x78, 0xb1, 0x40, 0x9e, 0xf1, 0xc5, 0x42, 0x79, 0x46, 0x27, 0x56, 0x2e, 0xe6, + 0xe2, 0xd2, 0x5c, 0xa7, 0xa0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, + 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xb2, + 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x47, 0xf2, 0x05, 0x12, 0x13, + 0xe2, 0x4c, 0x0c, 0x1f, 0x26, 0xb1, 0x81, 0x25, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe8, + 0x1d, 0x1e, 0xe2, 0xfd, 0x00, 0x00, 0x00, } func (this *PublicKey) Compare(that interface{}) int { @@ -157,6 +171,8 @@ func (this *PublicKey) Compare(that interface{}) int { switch this.Sum.(type) { case *PublicKey_Ed25519: thisType = 0 + case *PublicKey_Secp256K1: + thisType = 1 default: panic(fmt.Sprintf("compare: unexpected type %T in oneof", this.Sum)) } @@ -164,6 +180,8 @@ func (this *PublicKey) Compare(that interface{}) int { switch that1.Sum.(type) { case *PublicKey_Ed25519: that1Type = 0 + case *PublicKey_Secp256K1: + that1Type = 1 default: panic(fmt.Sprintf("compare: unexpected type %T in oneof", that1.Sum)) } @@ -209,6 +227,36 @@ func (this *PublicKey_Ed25519) Compare(that interface{}) int { } return 0 } +func (this *PublicKey_Secp256K1) Compare(that interface{}) int { + if that == nil { + if this == nil { + return 0 + } + return 1 + } + + that1, ok := that.(*PublicKey_Secp256K1) + if !ok { + that2, ok := that.(PublicKey_Secp256K1) + if ok { + that1 = &that2 + } else { + return 1 + } + } + if that1 == nil { + if this == nil { + return 0 + } + return 1 + } else if this == nil { + return -1 + } + if c := bytes.Compare(this.Secp256K1, that1.Secp256K1); c != 0 { + return c + } + return 0 +} func (this *PublicKey) Equal(that interface{}) bool { if that == nil { return this == nil @@ -263,6 +311,30 @@ func (this *PublicKey_Ed25519) Equal(that interface{}) bool { } return true } +func (this *PublicKey_Secp256K1) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PublicKey_Secp256K1) + if !ok { + that2, ok := that.(PublicKey_Secp256K1) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !bytes.Equal(this.Secp256K1, that1.Secp256K1) { + return false + } + return true +} func (m *PublicKey) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -311,6 +383,22 @@ func (m *PublicKey_Ed25519) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } +func (m *PublicKey_Secp256K1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PublicKey_Secp256K1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Secp256K1 != nil { + i -= len(m.Secp256K1) + copy(dAtA[i:], m.Secp256K1) + i = encodeVarintKeys(dAtA, i, uint64(len(m.Secp256K1))) + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} func encodeVarintKeys(dAtA []byte, offset int, v uint64) int { offset -= sovKeys(v) base := offset @@ -346,6 +434,18 @@ func (m *PublicKey_Ed25519) Size() (n int) { } return n } +func (m *PublicKey_Secp256K1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Secp256K1 != nil { + l = len(m.Secp256K1) + n += 1 + l + sovKeys(uint64(l)) + } + return n +} func sovKeys(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 @@ -415,6 +515,39 @@ func (m *PublicKey) Unmarshal(dAtA []byte) error { copy(v, dAtA[iNdEx:postIndex]) m.Sum = &PublicKey_Ed25519{v} iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Secp256K1", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthKeys + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthKeys + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Sum = &PublicKey_Secp256K1{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipKeys(dAtA[iNdEx:]) diff --git a/proto/tendermint/crypto/keys.proto b/proto/tendermint/crypto/keys.proto index af9db49fc8..16fd7adf3e 100644 --- a/proto/tendermint/crypto/keys.proto +++ b/proto/tendermint/crypto/keys.proto @@ -11,6 +11,7 @@ message PublicKey { option (gogoproto.equal) = true; oneof sum { - bytes ed25519 = 1; + bytes ed25519 = 1; + bytes secp256k1 = 2; } } diff --git a/types/params_test.go b/types/params_test.go index e6fbc4cade..f3a71ca50a 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -13,7 +13,8 @@ import ( ) var ( - valEd25519 = []string{ABCIPubKeyTypeEd25519} + valEd25519 = []string{ABCIPubKeyTypeEd25519} + valSecp256k1 = []string{ABCIPubKeyTypeSecp256k1} ) func TestConsensusParamsValidation(t *testing.T) { @@ -127,10 +128,10 @@ func TestConsensusParamsUpdate(t *testing.T) { MaxBytes: 50, }, Validator: &tmproto.ValidatorParams{ - PubKeyTypes: valEd25519, + PubKeyTypes: valSecp256k1, }, }, - makeParams(100, 200, 10, 300, 50, valEd25519), + makeParams(100, 200, 10, 300, 50, valSecp256k1), }, } for _, tc := range testCases { diff --git a/types/protobuf.go b/types/protobuf.go index 75d7b1cebf..1e633338b7 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -8,6 +8,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/crypto/secp256k1" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -15,13 +16,15 @@ import ( // Use strings to distinguish types in ABCI messages const ( - ABCIPubKeyTypeEd25519 = "ed25519" + ABCIPubKeyTypeEd25519 = ed25519.KeyType + ABCIPubKeyTypeSecp256k1 = secp256k1.KeyType ) // TODO: Make non-global by allowing for registration of more pubkey types var ABCIPubKeyTypesToNames = map[string]string{ - ABCIPubKeyTypeEd25519: ed25519.PubKeyName, + ABCIPubKeyTypeEd25519: ed25519.PubKeyName, + ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyName, } //------------------------------------------------------- From 29ca7de63c377abce9ca9ccb254375d938f89672 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 20 Oct 2020 10:56:48 +0200 Subject: [PATCH 013/108] abci/grpc: return async responses in order (#5520) (#5531) Fixes #5439. This is really a workaround for #5519 (unless we require async implementations to return ordered responses, but that kind of defeats the purpose of having an async API). --- CHANGELOG_PENDING.md | 2 ++ abci/client/grpc_client.go | 65 +++++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0156b7f4b0..22416fc3f0 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,3 +25,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [statesync] \#5516 Check that all heights necessary to rebuild state for a snapshot exist before adding the snapshot to the pool. (@erikgrinaker) ### BUG FIXES + +- [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index da84bb77d3..265b55532c 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -22,8 +22,9 @@ type grpcClient struct { service.BaseService mustConnect bool - client types.ABCIApplicationClient - conn *grpc.ClientConn + client types.ABCIApplicationClient + conn *grpc.ClientConn + chReqRes chan *ReqRes // dispatches "async" responses to callbacks *in order*, needed by mempool mtx tmsync.Mutex addr string @@ -35,6 +36,13 @@ func NewGRPCClient(addr string, mustConnect bool) Client { cli := &grpcClient{ addr: addr, mustConnect: mustConnect, + // Buffering the channel is needed to make calls appear asynchronous, + // which is required when the caller makes multiple async calls before + // processing callbacks (e.g. due to holding locks). 64 means that a + // caller can make up to 64 async calls before a callback must be + // processed (otherwise it deadlocks). It also means that we can make 64 + // gRPC calls while processing a slow callback at the channel head. + chReqRes: make(chan *ReqRes, 64), } cli.BaseService = *service.NewBaseService(nil, "grpcClient", cli) return cli @@ -48,6 +56,34 @@ func (cli *grpcClient) OnStart() error { if err := cli.BaseService.OnStart(); err != nil { return err } + + // This processes asynchronous request/response messages and dispatches + // them to callbacks. + go func() { + // Use a separate function to use defer for mutex unlocks (this handles panics) + callCb := func(reqres *ReqRes) { + cli.mtx.Lock() + defer cli.mtx.Unlock() + + // Notify client listener if set + if cli.resCb != nil { + cli.resCb(reqres.Request, reqres.Response) + } + + // Notify reqRes listener if set + if cb := reqres.GetCallback(); cb != nil { + cb(reqres.Response) + } + } + for reqres := range cli.chReqRes { + if reqres != nil { + callCb(reqres) + } else { + cli.Logger.Error("Received nil reqres") + } + } + }() + RETRY_LOOP: for { conn, err := grpc.Dial(cli.addr, grpc.WithInsecure(), grpc.WithContextDialer(dialerFunc)) @@ -85,6 +121,7 @@ func (cli *grpcClient) OnStop() { if cli.conn != nil { cli.conn.Close() } + close(cli.chReqRes) } func (cli *grpcClient) StopForError(err error) { @@ -263,26 +300,10 @@ func (cli *grpcClient) ApplySnapshotChunkAsync(params types.RequestApplySnapshot func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes { reqres := NewReqRes(req) - reqres.Response = res // Set response - reqres.Done() // Release waiters - reqres.SetDone() // so reqRes.SetCallback will run the callback - - // goroutine for callbacks - go func() { - cli.mtx.Lock() - defer cli.mtx.Unlock() - - // Notify client listener if set - if cli.resCb != nil { - cli.resCb(reqres.Request, res) - } - - // Notify reqRes listener if set - if cb := reqres.GetCallback(); cb != nil { - cb(res) - } - }() - + reqres.Response = res // Set response + reqres.Done() // Release waiters + reqres.SetDone() // so reqRes.SetCallback will run the callback + cli.chReqRes <- reqres // use channel for async responses, since they must be ordered return reqres } From 79d535dd67416ab2926b30c3f48ecb784e2f6c38 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 20 Oct 2020 14:19:00 +0400 Subject: [PATCH 014/108] blockchain/v2: fix "panic: duplicate block enqueued by processor" (#5499) When a peer is stopped due to some network issue, the Reactor calls scheduler#handleRemovePeer, which removes the peer from the scheduler. BUT the peer stays in the processor, which sometimes could lead to "duplicate block enqueued by processor" panic WHEN the same block is requested by the scheduler again from a different peer. The solution is to return scPeerError, which will be propagated to the processor. The processor will clean up the blocks associated with the peer in purgePeer. Closes #5513, #5517 --- CHANGELOG_PENDING.md | 1 + blockchain/v2/processor.go | 31 ++++++++++++++++++------ blockchain/v2/reactor.go | 35 +++++++++++++++++++++++++-- blockchain/v2/routine.go | 36 ++++++++++++++++++++++++--- blockchain/v2/scheduler.go | 43 ++++++++++++++++++++++++++------- blockchain/v2/scheduler_test.go | 3 +-- 6 files changed, 124 insertions(+), 25 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 22416fc3f0..33fc24eaa6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,4 +26,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES +- [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) - [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) diff --git a/blockchain/v2/processor.go b/blockchain/v2/processor.go index d4b2c637bc..f9036f3b9d 100644 --- a/blockchain/v2/processor.go +++ b/blockchain/v2/processor.go @@ -17,6 +17,11 @@ type pcBlockVerificationFailure struct { secondPeerID p2p.ID } +func (e pcBlockVerificationFailure) String() string { + return fmt.Sprintf("pcBlockVerificationFailure{%d 1st peer: %v, 2nd peer: %v}", + e.height, e.firstPeerID, e.secondPeerID) +} + // successful block execution type pcBlockProcessed struct { priorityNormal @@ -24,6 +29,10 @@ type pcBlockProcessed struct { peerID p2p.ID } +func (e pcBlockProcessed) String() string { + return fmt.Sprintf("pcBlockProcessed{%d peer: %v}", e.height, e.peerID) +} + // processor has finished type pcFinished struct { priorityNormal @@ -87,9 +96,12 @@ func (state *pcState) synced() bool { } func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) { - if _, ok := state.queue[height]; ok { - panic("duplicate block enqueued by processor") + if item, ok := state.queue[height]; ok { + panic(fmt.Sprintf( + "duplicate block %d (%X) enqueued by processor (sent by %v; existing block %X from %v)", + height, block.Hash(), peerID, item.block.Hash(), item.peerID)) } + state.queue[height] = queueItem{block: block, peerID: peerID} } @@ -145,16 +157,20 @@ func (state *pcState) handle(event Event) (Event, error) { } return noOp, nil } - first, second := firstItem.block, secondItem.block - firstParts := first.MakePartSet(types.BlockPartSizeBytes) - firstPartSetHeader := firstParts.Header() - firstID := types.BlockID{Hash: first.Hash(), PartSetHeader: firstPartSetHeader} + var ( + first, second = firstItem.block, secondItem.block + firstParts = first.MakePartSet(types.BlockPartSizeBytes) + firstID = types.BlockID{Hash: first.Hash(), PartSetHeader: firstParts.Header()} + ) + // verify if +second+ last commit "confirms" +first+ block err = state.context.verifyCommit(tmState.ChainID, firstID, first.Height, second.LastCommit) if err != nil { state.purgePeer(firstItem.peerID) - state.purgePeer(secondItem.peerID) + if firstItem.peerID != secondItem.peerID { + state.purgePeer(secondItem.peerID) + } return pcBlockVerificationFailure{ height: first.Height, firstPeerID: firstItem.peerID, secondPeerID: secondItem.peerID}, nil @@ -170,7 +186,6 @@ func (state *pcState) handle(event Event) (Event, error) { state.blocksSynced++ return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil - } return noOp, nil diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index 6aee5078ea..8cb33f5420 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -187,7 +187,7 @@ type rTryPrunePeer struct { } func (e rTryPrunePeer) String() string { - return fmt.Sprintf(": %v", e.time) + return fmt.Sprintf("rTryPrunePeer{%v}", e.time) } // ticker event for scheduling block requests @@ -197,7 +197,7 @@ type rTrySchedule struct { } func (e rTrySchedule) String() string { - return fmt.Sprintf(": %v", e.time) + return fmt.Sprintf("rTrySchedule{%v}", e.time) } // ticker for block processing @@ -205,6 +205,10 @@ type rProcessBlock struct { priorityNormal } +func (e rProcessBlock) String() string { + return "rProcessBlock" +} + // reactor generated events based on blockchain related messages from peers: // blockResponse message received from a peer type bcBlockResponse struct { @@ -215,6 +219,11 @@ type bcBlockResponse struct { block *types.Block } +func (resp bcBlockResponse) String() string { + return fmt.Sprintf("bcBlockResponse{%d#%X (size: %d bytes) from %v at %v}", + resp.block.Height, resp.block.Hash(), resp.size, resp.peerID, resp.time) +} + // blockNoResponse message received from a peer type bcNoBlockResponse struct { priorityNormal @@ -223,6 +232,11 @@ type bcNoBlockResponse struct { height int64 } +func (resp bcNoBlockResponse) String() string { + return fmt.Sprintf("bcNoBlockResponse{%v has no block at height %d at %v}", + resp.peerID, resp.height, resp.time) +} + // statusResponse message received from a peer type bcStatusResponse struct { priorityNormal @@ -232,12 +246,21 @@ type bcStatusResponse struct { height int64 } +func (resp bcStatusResponse) String() string { + return fmt.Sprintf("bcStatusResponse{%v is at height %d (base: %d) at %v}", + resp.peerID, resp.height, resp.base, resp.time) +} + // new peer is connected type bcAddNewPeer struct { priorityNormal peerID p2p.ID } +func (resp bcAddNewPeer) String() string { + return fmt.Sprintf("bcAddNewPeer{%v}", resp.peerID) +} + // existing peer is removed type bcRemovePeer struct { priorityHigh @@ -245,12 +268,20 @@ type bcRemovePeer struct { reason interface{} } +func (resp bcRemovePeer) String() string { + return fmt.Sprintf("bcRemovePeer{%v due to %v}", resp.peerID, resp.reason) +} + // resets the scheduler and processor state, e.g. following a switch from state syncing type bcResetState struct { priorityHigh state state.State } +func (e bcResetState) String() string { + return fmt.Sprintf("bcResetState{%v}", e.state) +} + // Takes the channel as a parameter to avoid race conditions on r.events. func (r *BlockchainReactor) demux(events <-chan Event) { var lastRate = 0.0 diff --git a/blockchain/v2/routine.go b/blockchain/v2/routine.go index 40e1971fed..e4ca52add7 100644 --- a/blockchain/v2/routine.go +++ b/blockchain/v2/routine.go @@ -2,6 +2,7 @@ package v2 import ( "fmt" + "strings" "sync/atomic" "github.com/Workiva/go-datastructures/queue" @@ -11,6 +12,8 @@ import ( type handleFunc = func(event Event) (Event, error) +const historySize = 25 + // Routine is a structure that models a finite state machine as serialized // stream of events processed by a handle function. This Routine structure // handles the concurrency and messaging guarantees. Events are sent via @@ -21,6 +24,7 @@ type Routine struct { name string handle handleFunc queue *queue.PriorityQueue + history []Event out chan Event fin chan error rdy chan struct{} @@ -34,6 +38,7 @@ func newRoutine(name string, handleFunc handleFunc, bufferSize int) *Routine { name: name, handle: handleFunc, queue: queue.NewPriorityQueue(bufferSize, true), + history: make([]Event, 0, historySize), out: make(chan Event, bufferSize), rdy: make(chan struct{}, 1), fin: make(chan error, 1), @@ -53,13 +58,24 @@ func (rt *Routine) setMetrics(metrics *Metrics) { } func (rt *Routine) start() { - rt.logger.Info(fmt.Sprintf("%s: run\n", rt.name)) + rt.logger.Info(fmt.Sprintf("%s: run", rt.name)) running := atomic.CompareAndSwapUint32(rt.running, uint32(0), uint32(1)) if !running { panic(fmt.Sprintf("%s is already running", rt.name)) } close(rt.rdy) defer func() { + if r := recover(); r != nil { + var ( + b strings.Builder + j int + ) + for i := len(rt.history) - 1; i >= 0; i-- { + fmt.Fprintf(&b, "%d: %+v\n", j, rt.history[i]) + j++ + } + panic(fmt.Sprintf("%v\nlast events:\n%v", r, b.String())) + } stopped := atomic.CompareAndSwapUint32(rt.running, uint32(1), uint32(0)) if !stopped { panic(fmt.Sprintf("%s is failed to stop", rt.name)) @@ -82,7 +98,19 @@ func (rt *Routine) start() { return } rt.metrics.EventsOut.With("routine", rt.name).Add(1) - rt.logger.Debug(fmt.Sprintf("%s: produced %T %+v\n", rt.name, oEvent, oEvent)) + rt.logger.Debug(fmt.Sprintf("%s: produced %T %+v", rt.name, oEvent, oEvent)) + + // Skip rTrySchedule and rProcessBlock events as they clutter the history + // due to their frequency. + switch events[0].(type) { + case rTrySchedule: + case rProcessBlock: + default: + rt.history = append(rt.history, events[0].(Event)) + if len(rt.history) > historySize { + rt.history = rt.history[1:] + } + } rt.out <- oEvent } @@ -97,7 +125,7 @@ func (rt *Routine) send(event Event) bool { err := rt.queue.Put(event) if err != nil { rt.metrics.EventsShed.With("routine", rt.name).Add(1) - rt.logger.Info(fmt.Sprintf("%s: send failed, queue was full/stopped \n", rt.name)) + rt.logger.Error(fmt.Sprintf("%s: send failed, queue was full/stopped", rt.name)) return false } @@ -122,7 +150,7 @@ func (rt *Routine) stop() { return } - rt.logger.Info(fmt.Sprintf("%s: stop\n", rt.name)) + rt.logger.Info(fmt.Sprintf("%s: stop", rt.name)) rt.queue.Dispose() // this should block until all queue items are free? } diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go index 84dee7a53b..a5ca33cd0c 100644 --- a/blockchain/v2/scheduler.go +++ b/blockchain/v2/scheduler.go @@ -2,6 +2,7 @@ package v2 import ( "bytes" + "errors" "fmt" "math" "sort" @@ -18,6 +19,10 @@ type scFinishedEv struct { reason string } +func (e scFinishedEv) String() string { + return fmt.Sprintf("scFinishedEv{%v}", e.reason) +} + // send a blockRequest message type scBlockRequest struct { priorityNormal @@ -25,6 +30,10 @@ type scBlockRequest struct { height int64 } +func (e scBlockRequest) String() string { + return fmt.Sprintf("scBlockRequest{%d from %v}", e.height, e.peerID) +} + // a block has been received and validated by the scheduler type scBlockReceived struct { priorityNormal @@ -32,6 +41,10 @@ type scBlockReceived struct { block *types.Block } +func (e scBlockReceived) String() string { + return fmt.Sprintf("scBlockReceived{%d#%X from %v}", e.block.Height, e.block.Hash(), e.peerID) +} + // scheduler detected a peer error type scPeerError struct { priorityHigh @@ -40,7 +53,7 @@ type scPeerError struct { } func (e scPeerError) String() string { - return fmt.Sprintf("scPeerError - peerID %s, err %s", e.peerID, e.reason) + return fmt.Sprintf("scPeerError{%v errored with %v}", e.peerID, e.reason) } // scheduler removed a set of peers (timed out or slow peer) @@ -49,6 +62,10 @@ type scPeersPruned struct { peers []p2p.ID } +func (e scPeersPruned) String() string { + return fmt.Sprintf("scPeersPruned{%v}", e.peers) +} + // XXX: make this fatal? // scheduler encountered a fatal error type scSchedulerFail struct { @@ -56,6 +73,10 @@ type scSchedulerFail struct { reason error } +func (e scSchedulerFail) String() string { + return fmt.Sprintf("scSchedulerFail{%v}", e.reason) +} + type blockState int const ( @@ -295,6 +316,9 @@ func (sc *scheduler) setPeerRange(peerID p2p.ID, base int64, height int64) error } if base > height { + if err := sc.removePeer(peerID); err != nil { + return err + } return fmt.Errorf("cannot set peer base higher than its height") } @@ -418,7 +442,7 @@ func (sc *scheduler) markProcessed(height int64) error { return fmt.Errorf("cannot mark height %d received from block state %s", height, state) } - sc.height++ + sc.height = height + 1 delete(sc.receivedBlocks, height) delete(sc.blockStates, height) sc.addNewBlocks() @@ -532,14 +556,12 @@ func (sc *scheduler) handleBlockResponse(event bcBlockResponse) (Event, error) { } func (sc *scheduler) handleNoBlockResponse(event bcNoBlockResponse) (Event, error) { - if len(sc.peers) == 0 { - return noOp, nil - } - + // No such peer or peer was removed. peer, ok := sc.peers[event.peerID] if !ok || peer.state == peerStateRemoved { return noOp, nil } + // The peer may have been just removed due to errors, low speed or timeouts. _ = sc.removePeer(event.peerID) @@ -550,8 +572,9 @@ func (sc *scheduler) handleNoBlockResponse(event bcNoBlockResponse) (Event, erro func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error) { if event.height != sc.height { - panic(fmt.Sprintf("processed height %d but expected height %d", event.height, sc.height)) + panic(fmt.Sprintf("processed height %d, but expected height %d", event.height, sc.height)) } + err := sc.markProcessed(event.height) if err != nil { // It is possible that a peer error or timeout is handled after the processor @@ -601,11 +624,13 @@ func (sc *scheduler) handleRemovePeer(event bcRemovePeer) (Event, error) { if sc.allBlocksProcessed() { return scFinishedEv{reason: "removed peer"}, nil } - return noOp, nil + + // Return scPeerError so the peer (and all associated blocks) is removed from + // the processor. + return scPeerError{peerID: event.peerID, reason: errors.New("peer was stopped")}, nil } func (sc *scheduler) handleTryPrunePeer(event rTryPrunePeer) (Event, error) { - // Check behavior of peer responsible to deliver block at sc.height. timeHeightAsked, ok := sc.pendingTime[sc.height] if ok && time.Since(timeHeightAsked) > sc.peerTimeout { diff --git a/blockchain/v2/scheduler_test.go b/blockchain/v2/scheduler_test.go index a4636d9547..fce0c05637 100644 --- a/blockchain/v2/scheduler_test.go +++ b/blockchain/v2/scheduler_test.go @@ -586,8 +586,7 @@ func TestScSetPeerRange(t *testing.T) { allB: []int64{1, 2, 3, 4}}, args: args{peerID: "P1", base: 6, height: 5}, wantFields: scTestParams{ - peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, - allB: []int64{1, 2, 3, 4}}, + peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, wantErr: true, }, { From 020edbc11d29bb68dcc090a82a210980121fb90d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 20 Oct 2020 14:29:36 +0400 Subject: [PATCH 015/108] blockchain/v2: fix panic: processed height X+1 but expected height X (#5530) Before: scheduler receives psBlockProcessed event, but does not mark block as processed because peer timed out (or was removed for other reasons) and all associated blocks were rescheduled. After: scheduler receives psBlockProcessed event and marks block as processed in any case (even if peer who provided this block errors). Closes #5387 --- CHANGELOG_PENDING.md | 2 ++ blockchain/v2/scheduler.go | 15 ++++++--------- blockchain/v2/scheduler_test.go | 27 ++++++++++++++------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 33fc24eaa6..f71bdc11df 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -28,3 +28,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) - [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) + +- [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go index a5ca33cd0c..b97ee2f643 100644 --- a/blockchain/v2/scheduler.go +++ b/blockchain/v2/scheduler.go @@ -436,17 +436,17 @@ func (sc *scheduler) markPending(peerID p2p.ID, height int64, time time.Time) er } func (sc *scheduler) markProcessed(height int64) error { + // It is possible that a peer error or timeout is handled after the processor + // has processed the block but before the scheduler received this event, so + // when pcBlockProcessed event is received, the block had been requested + // again => don't check the block state. sc.lastAdvance = time.Now() - state := sc.getStateAtHeight(height) - if state != blockStateReceived { - return fmt.Errorf("cannot mark height %d received from block state %s", height, state) - } - sc.height = height + 1 + delete(sc.pendingBlocks, height) + delete(sc.pendingTime, height) delete(sc.receivedBlocks, height) delete(sc.blockStates, height) sc.addNewBlocks() - return nil } @@ -577,9 +577,6 @@ func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error) err := sc.markProcessed(event.height) if err != nil { - // It is possible that a peer error or timeout is handled after the processor - // has processed the block but before the scheduler received this event, - // so when pcBlockProcessed event is received the block had been requested again. return scSchedulerFail{reason: err}, nil } diff --git a/blockchain/v2/scheduler_test.go b/blockchain/v2/scheduler_test.go index fce0c05637..60984c42de 100644 --- a/blockchain/v2/scheduler_test.go +++ b/blockchain/v2/scheduler_test.go @@ -992,19 +992,20 @@ func TestScMarkProcessed(t *testing.T) { { name: "processed an unreceived block", fields: scTestParams{ - peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, - allB: []int64{1, 2}, - pending: map[int64]p2p.ID{2: "P1"}, - pendingTime: map[int64]time.Time{2: now}, - received: map[int64]p2p.ID{1: "P1"}}, + height: 2, + peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, + allB: []int64{2}, + pending: map[int64]p2p.ID{2: "P1"}, + pendingTime: map[int64]time.Time{2: now}, + targetPending: 1, + }, args: args{height: 2}, wantFields: scTestParams{ - peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, - allB: []int64{1, 2}, - pending: map[int64]p2p.ID{2: "P1"}, - pendingTime: map[int64]time.Time{2: now}, - received: map[int64]p2p.ID{1: "P1"}}, - wantErr: true, + height: 3, + peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, + allB: []int64{3}, + targetPending: 1, + }, }, { name: "mark processed success", @@ -1571,7 +1572,7 @@ func TestScHandleBlockProcessed(t *testing.T) { name: "empty scheduler", fields: scTestParams{height: 6}, args: args{event: processed6FromP1}, - wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { name: "processed block we don't have", @@ -1583,7 +1584,7 @@ func TestScHandleBlockProcessed(t *testing.T) { pendingTime: map[int64]time.Time{6: now}, }, args: args{event: processed6FromP1}, - wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { name: "processed block ok, we processed all blocks", From 7c17fa115a63280cbf4a2791ec88338651ab807f Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 21 Oct 2020 18:24:38 +0200 Subject: [PATCH 016/108] consensus: open target WAL as read/write during autorepair (#5536) (#5547) Fixes #5422. That turned out to be a whole lot easier than expected. Backport of #5536. --- CHANGELOG_PENDING.md | 2 +- consensus/state.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f71bdc11df..122bcac87d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -28,5 +28,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) - [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) - - [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) +- [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) diff --git a/consensus/state.go b/consensus/state.go index d1140e66aa..d59a4f81df 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -328,7 +328,7 @@ func (cs *State) OnStart() error { return err } - cs.Logger.Info("WAL file is corrupted. Attempting repair", "err", err) + cs.Logger.Error("WAL file is corrupted, attempting repair", "err", err) // 1) prep work if err := cs.wal.Stop(); err != nil { @@ -345,7 +345,7 @@ func (cs *State) OnStart() error { // 3) try to repair (WAL file will be overwritten!) if err := repairWalFile(corruptedFile, cs.config.WalFile()); err != nil { - cs.Logger.Error("Repair failed", "err", err) + cs.Logger.Error("WAL repair failed", "err", err) return err } cs.Logger.Info("Successful repair") @@ -2212,7 +2212,7 @@ func repairWalFile(src, dst string) error { } defer in.Close() - out, err := os.Open(dst) + out, err := os.Create(dst) if err != nil { return err } From 3822ab924e99c1f63ca1537e8fe3994f60b5172e Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 25 Sep 2020 11:19:04 +0200 Subject: [PATCH 017/108] simplify commit and validators rpc calls (#5393) --- light/rpc/client.go | 130 ++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 58 deletions(-) diff --git a/light/rpc/client.go b/light/rpc/client.go index df1615e6c6..20aad7549d 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -13,6 +13,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmmath "github.com/tendermint/tendermint/libs/math" service "github.com/tendermint/tendermint/libs/service" light "github.com/tendermint/tendermint/light" rpcclient "github.com/tendermint/tendermint/rpc/client" @@ -375,32 +376,16 @@ func (c *Client) BlockResults(ctx context.Context, height *int64) (*ctypes.Resul } func (c *Client) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { - res, err := c.next.Commit(ctx, height) + // Update the light client if we're behind and retrieve the light block at the requested height + l, err := c.updateLightClientIfNeededTo(ctx, *height) if err != nil { return nil, err } - // Validate res. - if err := res.SignedHeader.ValidateBasic(c.lc.ChainID()); err != nil { - return nil, err - } - if res.Height <= 0 { - return nil, errNegOrZeroHeight - } - - // Update the light client if we're behind. - l, err := c.updateLightClientIfNeededTo(ctx, res.Height) - if err != nil { - return nil, err - } - - // Verify commit. - if rH, tH := res.Hash(), l.Hash(); !bytes.Equal(rH, tH) { - return nil, fmt.Errorf("header %X does not match with trusted header %X", - rH, tH) - } - - return res, nil + return &ctypes.ResultCommit{ + SignedHeader: *l.SignedHeader, + CanonicalCommit: true, + }, nil } // Tx calls rpcclient#Tx method and then verifies the proof if such was @@ -432,52 +417,30 @@ func (c *Client) TxSearch(ctx context.Context, query string, prove bool, page, p } // Validators fetches and verifies validators. -// -// WARNING: only full validator sets are verified (when length of validators is -// less than +perPage+. +perPage+ default is 30, max is 100). -func (c *Client) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) { - res, err := c.next.Validators(ctx, height, page, perPage) +func (c *Client) Validators(ctx context.Context, height *int64, pagePtr, perPagePtr *int) (*ctypes.ResultValidators, + error) { + // Update the light client if we're behind and retrieve the light block at the requested height. + l, err := c.updateLightClientIfNeededTo(ctx, *height) if err != nil { return nil, err } - // Validate res. - if res.BlockHeight <= 0 { - return nil, errNegOrZeroHeight - } - - updateHeight := res.BlockHeight - 1 - - // updateHeight can't be zero which happens when we are looking for the validators of the first block - if updateHeight == 0 { - updateHeight = 1 - } - - // Update the light client if we're behind. - l, err := c.updateLightClientIfNeededTo(ctx, updateHeight) + totalCount := len(l.ValidatorSet.Validators) + perPage := validatePerPage(perPagePtr) + page, err := validatePage(pagePtr, perPage, totalCount) if err != nil { return nil, err } - var tH tmbytes.HexBytes - switch res.BlockHeight { - case 1: - // if it's the first block we need to validate with the current validator hash as opposed to the - // next validator hash - tH = l.ValidatorsHash - default: - tH = l.NextValidatorsHash - } + skipCount := validateSkipCount(page, perPage) - // Verify validators. - if res.Count <= res.Total { - if rH := types.NewValidatorSet(res.Validators).Hash(); !bytes.Equal(rH, tH) { - return nil, fmt.Errorf("validators %X does not match with trusted validators %X", - rH, tH) - } - } + v := l.ValidatorSet.Validators[skipCount : skipCount+tmmath.MinInt(perPage, totalCount-skipCount)] - return res, nil + return &ctypes.ResultValidators{ + BlockHeight: *height, + Validators: v, + Count: len(v), + Total: totalCount}, nil } func (c *Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) { @@ -575,3 +538,54 @@ func parseQueryStorePath(path string) (storeName string, err error) { return paths[1], nil } + +// XXX: Copied from rpc/core/env.go +const ( + // see README + defaultPerPage = 30 + maxPerPage = 100 +) + +func validatePage(pagePtr *int, perPage, totalCount int) (int, error) { + if perPage < 1 { + panic(fmt.Sprintf("zero or negative perPage: %d", perPage)) + } + + if pagePtr == nil { // no page parameter + return 1, nil + } + + pages := ((totalCount - 1) / perPage) + 1 + if pages == 0 { + pages = 1 // one page (even if it's empty) + } + page := *pagePtr + if page <= 0 || page > pages { + return 1, fmt.Errorf("page should be within [1, %d] range, given %d", pages, page) + } + + return page, nil +} + +func validatePerPage(perPagePtr *int) int { + if perPagePtr == nil { // no per_page parameter + return defaultPerPage + } + + perPage := *perPagePtr + if perPage < 1 { + return defaultPerPage + } else if perPage > maxPerPage { + return maxPerPage + } + return perPage +} + +func validateSkipCount(page, perPage int) int { + skipCount := (page - 1) * perPage + if skipCount < 0 { + return 0 + } + + return skipCount +} From c374fc010a27e303743168c6f6d1978f546078a6 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 25 Sep 2020 08:45:56 +0200 Subject: [PATCH 018/108] cli: light home dir should default to where the full node default is (#5392) --- cmd/tendermint/commands/debug/debug.go | 2 +- cmd/tendermint/commands/debug/dump.go | 4 +- cmd/tendermint/commands/lite.go | 18 +++++---- .../commands/reset_priv_validator.go | 2 +- cmd/tendermint/commands/root.go | 2 +- cmd/tendermint/commands/run_node.go | 40 +++++++++---------- cmd/tendermint/commands/testnet.go | 24 +++++------ light/detector.go | 4 +- 8 files changed, 49 insertions(+), 47 deletions(-) diff --git a/cmd/tendermint/commands/debug/debug.go b/cmd/tendermint/commands/debug/debug.go index b5fc3c9e65..414b2b8744 100644 --- a/cmd/tendermint/commands/debug/debug.go +++ b/cmd/tendermint/commands/debug/debug.go @@ -33,7 +33,7 @@ func init() { &nodeRPCAddr, flagNodeRPCAddr, "tcp://localhost:26657", - "The Tendermint node's RPC address (:)", + "the Tendermint node's RPC address (:)", ) DebugCmd.AddCommand(killCmd) diff --git a/cmd/tendermint/commands/debug/dump.go b/cmd/tendermint/commands/debug/dump.go index b6f29336ae..678f707918 100644 --- a/cmd/tendermint/commands/debug/dump.go +++ b/cmd/tendermint/commands/debug/dump.go @@ -32,14 +32,14 @@ func init() { &frequency, flagFrequency, 30, - "The frequency (seconds) in which to poll, aggregate and dump Tendermint debug data", + "the frequency (seconds) in which to poll, aggregate and dump Tendermint debug data", ) dumpCmd.Flags().StringVar( &profAddr, flagProfAddr, "", - "The profiling server address (:)", + "the profiling server address (:)", ) } diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/lite.go index 7884c3a54a..59d922f1c0 100644 --- a/cmd/tendermint/commands/lite.go +++ b/cmd/tendermint/commands/lite.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "strings" "time" @@ -68,27 +69,28 @@ var ( func init() { LightCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", - "Serve the proxy on the given address") + "serve the proxy on the given address") LightCmd.Flags().StringVarP(&primaryAddr, "primary", "p", "", - "Connect to a Tendermint node at this address") + "connect to a Tendermint node at this address") LightCmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "", - "Tendermint nodes to cross-check the primary node, comma-separated") - LightCmd.Flags().StringVar(&home, "home-dir", ".tendermint-light", "Specify the home directory") + "tendermint nodes to cross-check the primary node, comma-separated") + LightCmd.Flags().StringVar(&home, "home-dir", os.ExpandEnv(filepath.Join("$HOME", ".tendermint-light")), + "specify the home directory") LightCmd.Flags().IntVar( &maxOpenConnections, "max-open-connections", 900, - "Maximum number of simultaneous connections (including WebSocket).") + "maximum number of simultaneous connections (including WebSocket).") LightCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, - "Trusting period that headers can be verified within. Should be significantly less than the unbonding period") + "trusting period that headers can be verified within. Should be significantly less than the unbonding period") LightCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height") LightCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash") LightCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") LightCmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3", - "Trust level. Must be between 1/3 and 3/3", + "trust level. Must be between 1/3 and 3/3", ) LightCmd.Flags().BoolVar(&sequential, "sequential", false, - "Sequential Verification. Verify all headers sequentially as opposed to using skipping verification", + "sequential verification. Verify all headers sequentially as opposed to using skipping verification", ) } diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index f47342d759..beefee5cbf 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -21,7 +21,7 @@ var ResetAllCmd = &cobra.Command{ var keepAddrBook bool func init() { - ResetAllCmd.Flags().BoolVar(&keepAddrBook, "keep-addr-book", false, "Keep the address book intact") + ResetAllCmd.Flags().BoolVar(&keepAddrBook, "keep-addr-book", false, "keep the address book intact") } // ResetPrivValidatorCmd resets the private validator files. diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 4fa0cf3f70..664f8ff149 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -23,7 +23,7 @@ func init() { } func registerFlagsRootCmd(cmd *cobra.Command) { - cmd.PersistentFlags().String("log_level", config.LogLevel, "Log level") + cmd.PersistentFlags().String("log_level", config.LogLevel, "log level") } // ParseConfig retrieves the default environment configuration, diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 8a67515da9..af77553fa5 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -22,34 +22,34 @@ var ( // These are exposed for convenience of commands embedding a tendermint node func AddNodeFlags(cmd *cobra.Command) { // bind flags - cmd.Flags().String("moniker", config.Moniker, "Node Name") + cmd.Flags().String("moniker", config.Moniker, "node name") // priv val flags cmd.Flags().String( "priv_validator_laddr", config.PrivValidatorListenAddr, - "Socket address to listen on for connections from external priv_validator process") + "socket address to listen on for connections from external priv_validator process") // node flags - cmd.Flags().Bool("fast_sync", config.FastSyncMode, "Fast blockchain syncing") + cmd.Flags().Bool("fast_sync", config.FastSyncMode, "fast blockchain syncing") cmd.Flags().BytesHexVar( &genesisHash, "genesis_hash", []byte{}, - "Optional SHA-256 hash of the genesis file") + "optional SHA-256 hash of the genesis file") cmd.Flags().Int64("consensus.double_sign_check_height", config.Consensus.DoubleSignCheckHeight, - "How many blocks to look back to check existence of the node's "+ + "how many blocks to look back to check existence of the node's "+ "consensus votes before joining consensus") // abci flags cmd.Flags().String( "proxy_app", config.ProxyApp, - "Proxy app address, or one of: 'kvstore',"+ + "proxy app address, or one of: 'kvstore',"+ " 'persistent_kvstore',"+ " 'counter',"+ " 'counter_serial' or 'noop' for local testing.") - cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)") + cmd.Flags().String("abci", config.ABCI, "specify abci transport (socket | grpc)") // rpc flags cmd.Flags().String("rpc.laddr", config.RPC.ListenAddress, "RPC listen address. Port required") @@ -57,42 +57,42 @@ func AddNodeFlags(cmd *cobra.Command) { "rpc.grpc_laddr", config.RPC.GRPCListenAddress, "GRPC listen address (BroadcastTx only). Port required") - cmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "Enabled unsafe rpc methods") + cmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "enabled unsafe rpc methods") cmd.Flags().String("rpc.pprof_laddr", config.RPC.PprofListenAddress, "pprof listen address (https://golang.org/pkg/net/http/pprof)") // p2p flags cmd.Flags().String( "p2p.laddr", config.P2P.ListenAddress, - "Node listen address. (0.0.0.0:0 means any interface, any port)") - cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma-delimited ID@host:port seed nodes") - cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma-delimited ID@host:port persistent peers") + "node listen address. (0.0.0.0:0 means any interface, any port)") + cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "comma-delimited ID@host:port seed nodes") + cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "comma-delimited ID@host:port persistent peers") cmd.Flags().String("p2p.unconditional_peer_ids", - config.P2P.UnconditionalPeerIDs, "Comma-delimited IDs of unconditional peers") - cmd.Flags().Bool("p2p.upnp", config.P2P.UPNP, "Enable/disable UPNP port forwarding") - cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") - cmd.Flags().Bool("p2p.seed_mode", config.P2P.SeedMode, "Enable/disable seed mode") - cmd.Flags().String("p2p.private_peer_ids", config.P2P.PrivatePeerIDs, "Comma-delimited private peer IDs") + config.P2P.UnconditionalPeerIDs, "comma-delimited IDs of unconditional peers") + cmd.Flags().Bool("p2p.upnp", config.P2P.UPNP, "enable/disable UPNP port forwarding") + cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "enable/disable Peer-Exchange") + cmd.Flags().Bool("p2p.seed_mode", config.P2P.SeedMode, "enable/disable seed mode") + cmd.Flags().String("p2p.private_peer_ids", config.P2P.PrivatePeerIDs, "comma-delimited private peer IDs") // consensus flags cmd.Flags().Bool( "consensus.create_empty_blocks", config.Consensus.CreateEmptyBlocks, - "Set this to false to only produce blocks when there are txs or when the AppHash changes") + "set this to false to only produce blocks when there are txs or when the AppHash changes") cmd.Flags().String( "consensus.create_empty_blocks_interval", config.Consensus.CreateEmptyBlocksInterval.String(), - "The possible interval between empty blocks") + "the possible interval between empty blocks") // db flags cmd.Flags().String( "db_backend", config.DBBackend, - "Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb") + "database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb") cmd.Flags().String( "db_dir", config.DBPath, - "Database directory") + "database directory") } // NewRunNodeCmd returns the command that allows the CLI to start a node. diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 8995fe399e..d5e71b9409 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -42,38 +42,38 @@ const ( func init() { TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4, - "Number of validators to initialize the testnet with") + "number of validators to initialize the testnet with") TestnetFilesCmd.Flags().StringVar(&configFile, "config", "", - "Config file to use (note some options may be overwritten)") + "config file to use (note some options may be overwritten)") TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0, - "Number of non-validators to initialize the testnet with") + "number of non-validators to initialize the testnet with") TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") + "directory to store initialization data for the testnet") TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") + "prefix the directory name for each node with (node results in node0, node1, ...)") TestnetFilesCmd.Flags().Int64Var(&initialHeight, "initial-height", 0, - "Initial height of the first block") + "initial height of the first block") TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true, - "Update config of each node with the list of persistent peers build using either"+ + "update config of each node with the list of persistent peers build using either"+ " hostname-prefix or"+ " starting-ip-address") TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node", - "Hostname prefix (\"node\" results in persistent peers list ID0@node0:26656, ID1@node1:26656, ...)") + "hostname prefix (\"node\" results in persistent peers list ID0@node0:26656, ID1@node1:26656, ...)") TestnetFilesCmd.Flags().StringVar(&hostnameSuffix, "hostname-suffix", "", - "Hostname suffix ("+ + "hostname suffix ("+ "\".xyz.com\""+ " results in persistent peers list ID0@node0.xyz.com:26656, ID1@node1.xyz.com:26656, ...)") TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "", - "Starting IP address ("+ + "starting IP address ("+ "\"192.168.0.1\""+ " results in persistent peers list ID0@192.168.0.1:26656, ID1@192.168.0.2:26656, ...)") TestnetFilesCmd.Flags().StringArrayVar(&hostnames, "hostname", []string{}, - "Manually override all hostnames of validators and non-validators (use --hostname multiple times for multiple hosts)") + "manually override all hostnames of validators and non-validators (use --hostname multiple times for multiple hosts)") TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 26656, "P2P Port") TestnetFilesCmd.Flags().BoolVar(&randomMonikers, "random-monikers", false, - "Randomize the moniker for each generated node") + "randomize the moniker for each generated node") } // TestnetFilesCmd allows initialisation of files for a Tendermint testnet. diff --git a/light/detector.go b/light/detector.go index 2517fa766f..9e58c1bc35 100644 --- a/light/detector.go +++ b/light/detector.go @@ -35,7 +35,7 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig lastVerifiedHeader = primaryTrace[len(primaryTrace)-1].SignedHeader witnessesToRemove = make([]int, 0) ) - c.logger.Info("Running detector against trace", "endBlockHeight", lastVerifiedHeader.Height, + c.logger.Debug("Running detector against trace", "endBlockHeight", lastVerifiedHeader.Height, "endBlockHash", lastVerifiedHeader.Hash, "length", len(primaryTrace)) c.providerMutex.Lock() @@ -178,7 +178,7 @@ func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan erro errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} } - c.logger.Info("Matching header received by witness", "height", h.Height, "witness", witnessIndex) + c.logger.Debug("Matching header received by witness", "height", h.Height, "witness", witnessIndex) errc <- nil } From 406dd74220a6f356dcb8d827b61715c4b1fbb35d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 9 Oct 2020 14:29:22 +0400 Subject: [PATCH 019/108] light: cross-check the very first header (#5429) Closes #5428 --- cmd/tendermint/commands/lite.go | 21 ++++++++++- light/client.go | 53 ++++++++++++++++++++++++++-- light/client_test.go | 2 +- light/detector_test.go | 62 ++++++++++++++++++++++++++------- 4 files changed, 122 insertions(+), 16 deletions(-) diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/lite.go index 59d922f1c0..50ce7c9fba 100644 --- a/cmd/tendermint/commands/lite.go +++ b/cmd/tendermint/commands/lite.go @@ -1,6 +1,7 @@ package commands import ( + "bufio" "context" "errors" "fmt" @@ -140,7 +141,25 @@ func runProxy(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't parse trust level: %w", err) } - options := []light.Option{light.Logger(logger)} + options := []light.Option{ + light.Logger(logger), + light.ConfirmationFunction(func(action string) bool { + fmt.Println(action) + scanner := bufio.NewScanner(os.Stdin) + for { + scanner.Scan() + response := scanner.Text() + switch response { + case "y", "Y": + return true + case "n", "N": + return false + default: + fmt.Println("please input 'Y' or 'n' and press ENTER") + } + } + }), + } if sequential { options = append(options, light.SequentialVerification()) diff --git a/light/client.go b/light/client.go index 49858c2bba..78b01f94ac 100644 --- a/light/client.go +++ b/light/client.go @@ -356,13 +356,20 @@ func (c *Client) initializeWithTrustOptions(ctx context.Context, options TrustOp return fmt.Errorf("expected header's hash %X, but got %X", options.Hash, l.Hash()) } - // Ensure that +2/3 of validators signed correctly. + // 2) Ensure that +2/3 of validators signed correctly. err = l.ValidatorSet.VerifyCommitLight(c.chainID, l.Commit.BlockID, l.Height, l.Commit) if err != nil { return fmt.Errorf("invalid commit: %w", err) } - // 3) Persist both of them and continue. + // 3) Cross-verify with witnesses to ensure everybody has the same state. + if len(c.witnesses) > 0 { + if err := c.compareFirstHeaderWithWitnesses(ctx, l.SignedHeader); err != nil { + return err + } + } + + // 4) Persist both of them and continue. return c.updateTrustedLightBlock(l) } @@ -982,6 +989,48 @@ func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*type return l, err } +// compareFirstHeaderWithWitnesses compares h with all witnesses. If any +// witness reports a different header than h, the function returns an error. +func (c *Client) compareFirstHeaderWithWitnesses(ctx context.Context, h *types.SignedHeader) error { + compareCtx, cancel := context.WithCancel(ctx) + defer cancel() + + errc := make(chan error, len(c.witnesses)) + for i, witness := range c.witnesses { + go c.compareNewHeaderWithWitness(compareCtx, errc, h, witness, i) + } + + witnessesToRemove := make([]int, 0, len(c.witnesses)) + + // handle errors from the header comparisons as they come in + for i := 0; i < cap(errc); i++ { + err := <-errc + + switch e := err.(type) { + case nil: + continue + case errConflictingHeaders: + c.logger.Error(fmt.Sprintf(`Witness #%d has a different header. Please check primary is correct +and remove witness. Otherwise, use the different primary`, e.WitnessIndex), "witness", c.witnesses[e.WitnessIndex]) + return err + case errBadWitness: + // If witness sent us an invalid header, then remove it. If it didn't + // respond or couldn't find the block, then we ignore it and move on to + // the next witness. + if _, ok := e.Reason.(provider.ErrBadLightBlock); ok { + c.logger.Info("Witness sent us invalid header / vals -> removing it", "witness", c.witnesses[e.WitnessIndex]) + witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) + } + } + } + + for _, idx := range witnessesToRemove { + c.removeWitness(idx) + } + + return nil +} + func hash2str(hash []byte) string { return fmt.Sprintf("%X", hash) } diff --git a/light/client_test.go b/light/client_test.go index a41315dc31..13d550de73 100644 --- a/light/client_test.go +++ b/light/client_test.go @@ -488,7 +488,7 @@ func TestClientRestoresTrustedHeaderAfterStartup1(t *testing.T) { err := trustedStore.SaveLightBlock(l1) require.NoError(t, err) - // header1 != header + // header1 != h1 header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) diff --git a/light/detector_test.go b/light/detector_test.go index 4777d0c2d5..dc08234767 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -162,13 +162,16 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { assert.True(t, primary.HasEvidence(evAgainstWitness)) } -func TestClientDivergentTraces(t *testing.T) { +// 1. Different nodes therefore a divergent header is produced. +// => light client returns an error upon creation because primary and witness +// have a different view. +func TestClientDivergentTraces1(t *testing.T) { primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) firstBlock, err := primary.LightBlock(ctx, 1) require.NoError(t, err) witness := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) - c, err := light.NewClient( + _, err = light.NewClient( ctx, chainID, light.TrustOptions{ @@ -182,18 +185,52 @@ func TestClientDivergentTraces(t *testing.T) { light.Logger(log.TestingLogger()), light.MaxRetryAttempts(1), ) + require.Error(t, err) + assert.Contains(t, err.Error(), "does not match primary") +} + +// 2. Two out of three nodes don't respond but the third has a header that matches +// => verification should be successful and all the witnesses should remain +func TestClientDivergentTraces2(t *testing.T) { + primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) + firstBlock, err := primary.LightBlock(ctx, 1) + require.NoError(t, err) + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Height: 1, + Hash: firstBlock.Hash(), + Period: 4 * time.Hour, + }, + primary, + []provider.Provider{deadNode, deadNode, primary}, + dbs.New(dbm.NewMemDB(), chainID), + light.Logger(log.TestingLogger()), + light.MaxRetryAttempts(1), + ) require.NoError(t, err) - // 1. Different nodes therefore a divergent header is produced but the - // light client can't verify it because it has a different trusted header. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) - assert.Error(t, err) - assert.Equal(t, 0, len(c.Witnesses())) + assert.NoError(t, err) + assert.Equal(t, 3, len(c.Witnesses())) +} + +// 3. witness has the same first header, but different second header +// => creation should succeed, but the verification should fail +func TestClientDivergentTraces3(t *testing.T) { + _, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime) + primary := mockp.New(chainID, primaryHeaders, primaryVals) + + firstBlock, err := primary.LightBlock(ctx, 1) + require.NoError(t, err) - // 2. Two out of three nodes don't respond but the third has a header that matches - // verification should be successful and all the witnesses should remain + _, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime) + mockHeaders[1] = primaryHeaders[1] + mockVals[1] = primaryVals[1] + witness := mockp.New(chainID, mockHeaders, mockVals) - c, err = light.NewClient( + c, err := light.NewClient( ctx, chainID, light.TrustOptions{ @@ -202,13 +239,14 @@ func TestClientDivergentTraces(t *testing.T) { Period: 4 * time.Hour, }, primary, - []provider.Provider{deadNode, deadNode, primary}, + []provider.Provider{witness}, dbs.New(dbm.NewMemDB(), chainID), light.Logger(log.TestingLogger()), light.MaxRetryAttempts(1), ) require.NoError(t, err) + _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) - assert.NoError(t, err) - assert.Equal(t, 3, len(c.Witnesses())) + assert.Error(t, err) + assert.Equal(t, 0, len(c.Witnesses())) } From 55ff694aa6cf5bee94cf61f854027a48a664829f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 12 Oct 2020 16:36:37 +0400 Subject: [PATCH 020/108] light/rpc: fix ABCIQuery (#5375) Closes #5106 --- cmd/tendermint/commands/{lite.go => light.go} | 28 +- crypto/merkle/proof_op.go | 2 +- go.mod | 6 +- go.sum | 27 + light/proxy/routes.go | 13 +- light/rpc/client.go | 102 ++- light/rpc/client_test.go | 152 ++++ light/rpc/mocks/light_client.go | 78 ++ light/rpc/proof.go | 14 - light/rpc/query_test.go | 160 ---- rpc/client/mocks/client.go | 780 ++++++++++++++++++ 11 files changed, 1135 insertions(+), 227 deletions(-) rename cmd/tendermint/commands/{lite.go => light.go} (89%) create mode 100644 light/rpc/client_test.go create mode 100644 light/rpc/mocks/light_client.go delete mode 100644 light/rpc/proof.go delete mode 100644 light/rpc/query_test.go create mode 100644 rpc/client/mocks/client.go diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/light.go similarity index 89% rename from cmd/tendermint/commands/lite.go rename to cmd/tendermint/commands/light.go index 50ce7c9fba..85c94118f4 100644 --- a/cmd/tendermint/commands/lite.go +++ b/cmd/tendermint/commands/light.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "regexp" "strings" "time" @@ -15,6 +16,7 @@ import ( dbm "github.com/tendermint/tm-db" + "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/libs/log" tmmath "github.com/tendermint/tendermint/libs/math" tmos "github.com/tendermint/tendermint/libs/os" @@ -41,6 +43,12 @@ need a primary RPC address, a trusted hash and height and witness RPC addresses (if not using sequential verification). To restart the node, thereafter only the chainID is required. +When /abci_query is called, the Merkle key path format is: + + /{store name}/{key} + +Please verify with your application that this Merkle key format is used (true +for applications built w/ Cosmos SDK). `, RunE: runProxy, Args: cobra.ExactArgs(1), @@ -215,7 +223,7 @@ func runProxy(cmd *cobra.Command, args []string) error { p := lproxy.Proxy{ Addr: listenAddr, Config: cfg, - Client: lrpc.NewClient(rpcClient, c), + Client: lrpc.NewClient(rpcClient, c, lrpc.KeyPathFn(defaultMerkleKeyPathFn())), Logger: logger, } // Stop upon receiving SIGTERM or CTRL-C. @@ -256,3 +264,21 @@ func saveProviders(db dbm.DB, primaryAddr, witnessesAddrs string) error { } return nil } + +func defaultMerkleKeyPathFn() lrpc.KeyPathFunc { + // regexp for extracting store name from /abci_query path + storeNameRegexp := regexp.MustCompile(`\/store\/(.+)\/key`) + + return func(path string, key []byte) (merkle.KeyPath, error) { + matches := storeNameRegexp.FindStringSubmatch(path) + if len(matches) != 2 { + return nil, fmt.Errorf("can't find store name in %s using %s", path, storeNameRegexp) + } + storeName := matches[1] + + kp := merkle.KeyPath{} + kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) + kp = kp.AppendKey(key, merkle.KeyEncodingURL) + return kp, nil + } +} diff --git a/crypto/merkle/proof_op.go b/crypto/merkle/proof_op.go index 8c6937a707..038037cf53 100644 --- a/crypto/merkle/proof_op.go +++ b/crypto/merkle/proof_op.go @@ -60,7 +60,7 @@ func (poz ProofOperators) Verify(root []byte, keypath string, args [][]byte) (er } } if !bytes.Equal(root, args[0]) { - return fmt.Errorf("calculated root hash is invalid: expected %+v but got %+v", root, args[0]) + return fmt.Errorf("calculated root hash is invalid: expected %X but got %X", root, args[0]) } if len(keys) != 0 { return errors.New("keypath not consumed all") diff --git a/go.mod b/go.mod index 1a812dd1c4..b9c0eadb69 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/Workiva/go-datastructures v1.0.52 github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 + github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb + github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.10.0 github.com/go-logfmt/logfmt v0.5.0 @@ -16,8 +18,6 @@ require ( github.com/gtank/merlin v0.1.1 github.com/libp2p/go-buffer-pool v0.0.2 github.com/minio/highwayhash v1.0.1 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 @@ -30,7 +30,5 @@ require ( github.com/tendermint/tm-db v0.6.2 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc - google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect google.golang.org/grpc v1.32.0 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 6846cd2491..3788a68aae 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -78,6 +79,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb h1:+7FsS1gZ1Km5LRjGV2hztpier/5i6ngNjvNpxbWP5I0= +github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= @@ -91,6 +94,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd h1:K3bmPkMDnd2KVQ7xoGmgp+pxoXcBW58vMWaMl9ZWx3c= +github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd/go.mod h1:3xOIaNNX19p0QrX0VqWa6voPRoJRGGYtny+DH8NEPvE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -137,6 +142,7 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= @@ -152,6 +158,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -215,9 +222,13 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.1/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.7 h1:Nk5kuHrnWUTf/0GL1a/vchH/om9Ap2/HnVna+jYZgTY= +github.com/grpc-ecosystem/grpc-gateway v1.14.7/go.mod h1:oYZKL012gGh6LMyg/xA7Q2yq6j8bu0wa+9w14EEthWU= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= @@ -417,6 +428,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -478,6 +490,7 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D6 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4= github.com/tendermint/tm-db v0.6.2 h1:DOn8jwCdjJblrCFJbtonEIPD1IuJWpbRUUdR8GWE4RM= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -531,6 +544,7 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -545,6 +559,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -565,12 +580,15 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -599,12 +617,14 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= @@ -641,6 +661,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -667,11 +688,14 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -682,6 +706,8 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -714,6 +740,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/light/proxy/routes.go b/light/proxy/routes.go index cb60118cf7..0ed7f94334 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -3,6 +3,7 @@ package proxy import ( "github.com/tendermint/tendermint/libs/bytes" lrpc "github.com/tendermint/tendermint/light/rpc" + rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" @@ -213,11 +214,17 @@ func makeBroadcastTxAsyncFunc(c *lrpc.Client) rpcBroadcastTxAsyncFunc { } } -type rpcABCIQueryFunc func(ctx *rpctypes.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) +type rpcABCIQueryFunc func(ctx *rpctypes.Context, path string, + data bytes.HexBytes, height int64, prove bool) (*ctypes.ResultABCIQuery, error) func makeABCIQueryFunc(c *lrpc.Client) rpcABCIQueryFunc { - return func(ctx *rpctypes.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) { - return c.ABCIQuery(ctx.Context(), path, data) + return func(ctx *rpctypes.Context, path string, data bytes.HexBytes, + height int64, prove bool) (*ctypes.ResultABCIQuery, error) { + + return c.ABCIQueryWithOptions(ctx.Context(), path, data, rpcclient.ABCIQueryOptions{ + Height: height, + Prove: prove, + }) } } diff --git a/light/rpc/client.go b/light/rpc/client.go index 20aad7549d..cdf16e524b 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "strings" "time" "github.com/gogo/protobuf/proto" @@ -15,7 +14,6 @@ import ( tmbytes "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" service "github.com/tendermint/tendermint/libs/service" - light "github.com/tendermint/tendermint/light" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" @@ -24,26 +22,54 @@ import ( var errNegOrZeroHeight = errors.New("negative or zero height") -// Client is an RPC client, which uses light#Client to verify data (if it can be -// proved!). +// KeyPathFunc builds a merkle path out of the given path and key. +type KeyPathFunc func(path string, key []byte) (merkle.KeyPath, error) + +// LightClient is an interface that contains functionality needed by Client from the light client. +type LightClient interface { + ChainID() string + VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error) + TrustedLightBlock(height int64) (*types.LightBlock, error) +} + +// Client is an RPC client, which uses light#Client to verify data (if it can +// be proved!). merkle.DefaultProofRuntime is used to verify values returned by +// ABCIQuery. type Client struct { service.BaseService next rpcclient.Client - lc *light.Client - prt *merkle.ProofRuntime + lc LightClient + // Proof runtime used to verify values returned by ABCIQuery + prt *merkle.ProofRuntime + keyPathFn KeyPathFunc } var _ rpcclient.Client = (*Client)(nil) +// Option allow you to tweak Client. +type Option func(*Client) + +// KeyPathFn option can be used to set a function, which parses a given path +// and builds the merkle path for the prover. It must be provided if you want +// to call ABCIQuery or ABCIQueryWithOptions. +func KeyPathFn(fn KeyPathFunc) Option { + return func(c *Client) { + c.keyPathFn = fn + } +} + // NewClient returns a new client. -func NewClient(next rpcclient.Client, lc *light.Client) *Client { +func NewClient(next rpcclient.Client, lc LightClient, opts ...Option) *Client { c := &Client{ next: next, lc: lc, - prt: defaultProofRuntime(), + prt: merkle.DefaultProofRuntime(), } c.BaseService = *service.NewBaseService(nil, "Client", c) + for _, o := range opts { + o(c) + } return c } @@ -70,15 +96,18 @@ func (c *Client) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) { return c.next.ABCIInfo(ctx) } +// ABCIQuery requests proof by default. func (c *Client) ABCIQuery(ctx context.Context, path string, data tmbytes.HexBytes) (*ctypes.ResultABCIQuery, error) { return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions. -// XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store. +// ABCIQueryWithOptions returns an error if opts.Prove is false. func (c *Client) ABCIQueryWithOptions(ctx context.Context, path string, data tmbytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + // always request the proof + opts.Prove = true + res, err := c.next.ABCIQueryWithOptions(ctx, path, data, opts) if err != nil { return nil, err @@ -89,8 +118,11 @@ func (c *Client) ABCIQueryWithOptions(ctx context.Context, path string, data tmb if resp.IsErr() { return nil, fmt.Errorf("err response code: %v", resp.Code) } - if len(resp.Key) == 0 || resp.ProofOps == nil { - return nil, errors.New("empty tree") + if len(resp.Key) == 0 { + return nil, errors.New("empty key") + } + if resp.ProofOps == nil || len(resp.ProofOps.Ops) == 0 { + return nil, errors.New("no proof ops") } if resp.Height <= 0 { return nil, errNegOrZeroHeight @@ -105,28 +137,28 @@ func (c *Client) ABCIQueryWithOptions(ctx context.Context, path string, data tmb // Validate the value proof against the trusted header. if resp.Value != nil { - // Value exists - // XXX How do we encode the key into a string... - storeName, err := parseQueryStorePath(path) + // 1) build a Merkle key path from path and resp.Key + if c.keyPathFn == nil { + return nil, errors.New("please configure Client with KeyPathFn option") + } + + kp, err := c.keyPathFn(path, resp.Key) if err != nil { - return nil, err + return nil, fmt.Errorf("can't build merkle key path: %w", err) } - kp := merkle.KeyPath{} - kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) - kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) + + // 2) verify value err = c.prt.VerifyValue(resp.ProofOps, l.AppHash, kp.String(), resp.Value) if err != nil { return nil, fmt.Errorf("verify value proof: %w", err) } - return &ctypes.ResultABCIQuery{Response: resp}, nil + } else { // OR validate the absence proof against the trusted header. + err = c.prt.VerifyAbsence(resp.ProofOps, l.AppHash, string(resp.Key)) + if err != nil { + return nil, fmt.Errorf("verify absence proof: %w", err) + } } - // OR validate the ansence proof against the trusted header. - // XXX How do we encode the key into a string... - err = c.prt.VerifyAbsence(resp.ProofOps, l.AppHash, string(resp.Key)) - if err != nil { - return nil, fmt.Errorf("verify absence proof: %w", err) - } return &ctypes.ResultABCIQuery{Response: resp}, nil } @@ -521,24 +553,6 @@ func (c *Client) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscr return &ctypes.ResultUnsubscribe{}, nil } -func parseQueryStorePath(path string) (storeName string, err error) { - if !strings.HasPrefix(path, "/") { - return "", errors.New("expected path to start with /") - } - - paths := strings.SplitN(path[1:], "/", 3) - switch { - case len(paths) != 3: - return "", errors.New("expected format like /store//key") - case paths[0] != "store": - return "", errors.New("expected format like /store//key") - case paths[2] != "key": - return "", errors.New("expected format like /store//key") - } - - return paths[1], nil -} - // XXX: Copied from rpc/core/env.go const ( // see README diff --git a/light/rpc/client_test.go b/light/rpc/client_test.go new file mode 100644 index 0000000000..f614f44d33 --- /dev/null +++ b/light/rpc/client_test.go @@ -0,0 +1,152 @@ +package rpc + +import ( + "context" + "encoding/hex" + "fmt" + "testing" + + ics23 "github.com/confio/ics23/go" + "github.com/cosmos/iavl" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tm-db" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/libs/bytes" + lcmock "github.com/tendermint/tendermint/light/rpc/mocks" + tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + rpcmock "github.com/tendermint/tendermint/rpc/client/mocks" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" +) + +// TestABCIQuery tests ABCIQuery requests and verifies proofs. HAPPY PATH 😀 +func TestABCIQuery(t *testing.T) { + tree, err := iavl.NewMutableTree(dbm.NewMemDB(), 100) + require.NoError(t, err) + + var ( + key = []byte("foo") + value = []byte("bar") + ) + tree.Set(key, value) + + commitmentProof, err := tree.GetMembershipProof(key) + require.NoError(t, err) + + op := &testOp{ + Spec: ics23.IavlSpec, + Key: key, + Proof: commitmentProof, + } + + next := &rpcmock.Client{} + next.On( + "ABCIQueryWithOptions", + context.Background(), + mock.AnythingOfType("string"), + bytes.HexBytes(key), + mock.AnythingOfType("client.ABCIQueryOptions"), + ).Return(&ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Code: 0, + Key: key, + Value: value, + Height: 1, + ProofOps: &tmcrypto.ProofOps{ + Ops: []tmcrypto.ProofOp{op.ProofOp()}, + }, + }, + }, nil) + + lc := &lcmock.LightClient{} + appHash, _ := hex.DecodeString("5EFD44055350B5CC34DBD26085347A9DBBE44EA192B9286A9FC107F40EA1FAC5") + lc.On("VerifyLightBlockAtHeight", context.Background(), int64(2), mock.AnythingOfType("time.Time")).Return( + &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: &types.Header{AppHash: appHash}, + }, + }, + nil, + ) + + c := NewClient(next, lc, + KeyPathFn(func(_ string, key []byte) (merkle.KeyPath, error) { + kp := merkle.KeyPath{} + kp = kp.AppendKey(key, merkle.KeyEncodingURL) + return kp, nil + })) + c.RegisterOpDecoder("ics23:iavl", testOpDecoder) + res, err := c.ABCIQuery(context.Background(), "/store/accounts/key", key) + require.NoError(t, err) + + assert.NotNil(t, res) +} + +type testOp struct { + Spec *ics23.ProofSpec + Key []byte + Proof *ics23.CommitmentProof +} + +var _ merkle.ProofOperator = testOp{} + +func (op testOp) GetKey() []byte { + return op.Key +} + +func (op testOp) ProofOp() tmcrypto.ProofOp { + bz, err := op.Proof.Marshal() + if err != nil { + panic(err.Error()) + } + return tmcrypto.ProofOp{ + Type: "ics23:iavl", + Key: op.Key, + Data: bz, + } +} + +func (op testOp) Run(args [][]byte) ([][]byte, error) { + // calculate root from proof + root, err := op.Proof.Calculate() + if err != nil { + return nil, fmt.Errorf("could not calculate root for proof: %v", err) + } + // Only support an existence proof or nonexistence proof (batch proofs currently unsupported) + switch len(args) { + case 0: + // Args are nil, so we verify the absence of the key. + absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key) + if !absent { + return nil, fmt.Errorf("proof did not verify absence of key: %s", string(op.Key)) + } + case 1: + // Args is length 1, verify existence of key with value args[0] + if !ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) { + return nil, fmt.Errorf("proof did not verify existence of key %s with given value %x", op.Key, args[0]) + } + default: + return nil, fmt.Errorf("args must be length 0 or 1, got: %d", len(args)) + } + + return [][]byte{root}, nil +} + +func testOpDecoder(pop tmcrypto.ProofOp) (merkle.ProofOperator, error) { + proof := &ics23.CommitmentProof{} + err := proof.Unmarshal(pop.Data) + if err != nil { + return nil, err + } + + op := testOp{ + Key: pop.Key, + Spec: ics23.IavlSpec, + Proof: proof, + } + return op, nil +} diff --git a/light/rpc/mocks/light_client.go b/light/rpc/mocks/light_client.go new file mode 100644 index 0000000000..2f512d881b --- /dev/null +++ b/light/rpc/mocks/light_client.go @@ -0,0 +1,78 @@ +// Code generated by mockery v2.3.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + time "time" + + types "github.com/tendermint/tendermint/types" +) + +// LightClient is an autogenerated mock type for the LightClient type +type LightClient struct { + mock.Mock +} + +// ChainID provides a mock function with given fields: +func (_m *LightClient) ChainID() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// TrustedLightBlock provides a mock function with given fields: height +func (_m *LightClient) TrustedLightBlock(height int64) (*types.LightBlock, error) { + ret := _m.Called(height) + + var r0 *types.LightBlock + if rf, ok := ret.Get(0).(func(int64) *types.LightBlock); ok { + r0 = rf(height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.LightBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int64) error); ok { + r1 = rf(height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// VerifyLightBlockAtHeight provides a mock function with given fields: ctx, height, now +func (_m *LightClient) VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error) { + ret := _m.Called(ctx, height, now) + + var r0 *types.LightBlock + if rf, ok := ret.Get(0).(func(context.Context, int64, time.Time) *types.LightBlock); ok { + r0 = rf(ctx, height, now) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.LightBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int64, time.Time) error); ok { + r1 = rf(ctx, height, now) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/light/rpc/proof.go b/light/rpc/proof.go deleted file mode 100644 index fc349c1722..0000000000 --- a/light/rpc/proof.go +++ /dev/null @@ -1,14 +0,0 @@ -package rpc - -import ( - "github.com/tendermint/tendermint/crypto/merkle" -) - -func defaultProofRuntime() *merkle.ProofRuntime { - prt := merkle.NewProofRuntime() - prt.RegisterOpDecoder( - merkle.ProofOpValue, - merkle.ValueOpDecoder, - ) - return prt -} diff --git a/light/rpc/query_test.go b/light/rpc/query_test.go deleted file mode 100644 index ae4daced6a..0000000000 --- a/light/rpc/query_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package rpc - -//import ( -// "fmt" -// "os" -// "testing" -// "time" - -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" - -// "github.com/tendermint/tendermint/abci/example/kvstore" -// "github.com/tendermint/tendermint/crypto/merkle" -// nm "github.com/tendermint/tendermint/node" -// "github.com/tendermint/tendermint/rpc/client" -// rpctest "github.com/tendermint/tendermint/rpc/test" -// "github.com/tendermint/tendermint/types" -//) - -//var node *nm.Node -//var chainID = "tendermint_test" // TODO use from config. -////nolint:unused -//var waitForEventTimeout = 5 * time.Second - -//// TODO fix tests!! - -//func TestMain(m *testing.M) { -// app := kvstore.NewKVStoreApplication() -// node = rpctest.StartTendermint(app) - -// code := m.Run() - -// rpctest.StopTendermint(node) -// os.Exit(code) -//} - -//func kvstoreTx(k, v []byte) []byte { -// return []byte(fmt.Sprintf("%s=%s", k, v)) -//} - -//// TODO: enable it after general proof format has been adapted -//// in abci/examples/kvstore.go -////nolint:unused,deadcode -//func _TestAppProofs(t *testing.T) { -// assert, require := assert.New(t), require.New(t) - -// prt := defaultProofRuntime() -// cl := client.NewLocal(node) -// client.WaitForHeight(cl, 1, nil) - -// // This sets up our trust on the node based on some past point. -// source := certclient.NewProvider(chainID, cl) -// seed, err := source.LatestFullCommit(chainID, 1, 1) -// require.NoError(err, "%#v", err) -// cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) - -// // Wait for tx confirmation. -// done := make(chan int64) -// go func() { -// evtTyp := types.EventTx -// _, err = client.WaitForOneEvent(cl, evtTyp, waitForEventTimeout) -// require.Nil(err, "%#v", err) -// close(done) -// }() - -// // Submit a transaction. -// k := []byte("my-key") -// v := []byte("my-value") -// tx := kvstoreTx(k, v) -// br, err := cl.BroadcastTxCommit(tx) -// require.NoError(err, "%#v", err) -// require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) -// require.EqualValues(0, br.DeliverTx.Code) -// brh := br.Height - -// // Fetch latest after tx commit. -// <-done -// latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) -// require.NoError(err, "%#v", err) -// rootHash := latest.SignedHeader.AppHash -// if rootHash == nil { -// // Fetch one block later, AppHash hasn't been committed yet. -// // TODO find a way to avoid doing this. -// client.WaitForHeight(cl, latest.SignedHeader.Height+1, nil) -// latest, err = source.LatestFullCommit(chainID, latest.SignedHeader.Height+1, 1<<63-1) -// require.NoError(err, "%#v", err) -// rootHash = latest.SignedHeader.AppHash -// } -// require.NotNil(rootHash) - -// // verify a query before the tx block has no data (and valid non-exist proof) -// bs, height, proof, err := GetWithProof(prt, k, brh-1, cl, cert) -// require.NoError(err, "%#v", err) -// require.NotNil(proof) -// require.Equal(height, brh-1) -// // require.NotNil(proof) -// // TODO: Ensure that *some* keys will be there, ensuring that proof is nil, -// // (currently there's a race condition) -// // and ensure that proof proves absence of k. -// require.Nil(bs) - -// // but given that block it is good -// bs, height, proof, err = GetWithProof(prt, k, brh, cl, cert) -// require.NoError(err, "%#v", err) -// require.NotNil(proof) -// require.Equal(height, brh) - -// assert.EqualValues(v, bs) -// err = prt.VerifyValue(proof, rootHash, string(k), bs) // XXX key encoding -// assert.NoError(err, "%#v", err) - -// // Test non-existing key. -// missing := []byte("my-missing-key") -// bs, _, proof, err = GetWithProof(prt, missing, 0, cl, cert) -// require.NoError(err) -// require.Nil(bs) -// require.NotNil(proof) -// err = prt.VerifyAbsence(proof, rootHash, string(missing)) // XXX VerifyAbsence(), keyencoding -// assert.NoError(err, "%#v", err) -// err = prt.VerifyAbsence(proof, rootHash, string(k)) // XXX VerifyAbsence(), keyencoding -// assert.Error(err, "%#v", err) -//} - -//func TestTxProofs(t *testing.T) { -// assert, require := assert.New(t), require.New(t) - -// cl := client.NewLocal(node) -// client.WaitForHeight(cl, 1, nil) - -// tx := kvstoreTx([]byte("key-a"), []byte("value-a")) -// br, err := cl.BroadcastTxCommit(tx) -// require.NoError(err, "%#v", err) -// require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) -// require.EqualValues(0, br.DeliverTx.Code) -// brh := br.Height - -// source := certclient.NewProvider(chainID, cl) -// seed, err := source.LatestFullCommit(chainID, brh-2, brh-2) -// require.NoError(err, "%#v", err) -// cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) - -// // First let's make sure a bogus transaction hash returns a valid non-existence proof. -// key := types.Tx([]byte("bogus")).Hash() -// _, err = cl.Tx(key, true) -// require.NotNil(err) -// require.Contains(err.Error(), "not found") - -// // Now let's check with the real tx root hash. -// key = types.Tx(tx).Hash() -// res, err := cl.Tx(key, true) -// require.NoError(err, "%#v", err) -// require.NotNil(res) -// keyHash := merkle.SimpleHashFromByteSlices([][]byte{key}) -// err = res.Proof.Validate(keyHash) -// assert.NoError(err, "%#v", err) - -// commit, err := GetCertifiedCommit(br.Height, cl, cert) -// require.Nil(err, "%#v", err) -// require.Equal(res.Proof.RootHash, commit.Header.DataHash) -//} diff --git a/rpc/client/mocks/client.go b/rpc/client/mocks/client.go new file mode 100644 index 0000000000..6a90087171 --- /dev/null +++ b/rpc/client/mocks/client.go @@ -0,0 +1,780 @@ +// Code generated by mockery v2.3.0. DO NOT EDIT. + +package mocks + +import ( + bytes "github.com/tendermint/tendermint/libs/bytes" + client "github.com/tendermint/tendermint/rpc/client" + + context "context" + + coretypes "github.com/tendermint/tendermint/rpc/core/types" + + log "github.com/tendermint/tendermint/libs/log" + + mock "github.com/stretchr/testify/mock" + + types "github.com/tendermint/tendermint/types" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// ABCIInfo provides a mock function with given fields: _a0 +func (_m *Client) ABCIInfo(_a0 context.Context) (*coretypes.ResultABCIInfo, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultABCIInfo + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultABCIInfo); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ABCIQuery provides a mock function with given fields: ctx, path, data +func (_m *Client) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { + ret := _m.Called(ctx, path, data) + + var r0 *coretypes.ResultABCIQuery + if rf, ok := ret.Get(0).(func(context.Context, string, bytes.HexBytes) *coretypes.ResultABCIQuery); ok { + r0 = rf(ctx, path, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bytes.HexBytes) error); ok { + r1 = rf(ctx, path, data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ABCIQueryWithOptions provides a mock function with given fields: ctx, path, data, opts +func (_m *Client) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + ret := _m.Called(ctx, path, data, opts) + + var r0 *coretypes.ResultABCIQuery + if rf, ok := ret.Get(0).(func(context.Context, string, bytes.HexBytes, client.ABCIQueryOptions) *coretypes.ResultABCIQuery); ok { + r0 = rf(ctx, path, data, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bytes.HexBytes, client.ABCIQueryOptions) error); ok { + r1 = rf(ctx, path, data, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Block provides a mock function with given fields: ctx, height +func (_m *Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultBlock + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultBlock); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *Client) BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error) { + ret := _m.Called(ctx, hash) + + var r0 *coretypes.ResultBlock + if rf, ok := ret.Get(0).(func(context.Context, []byte) *coretypes.ResultBlock); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockResults provides a mock function with given fields: ctx, height +func (_m *Client) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultBlockResults + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultBlockResults); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockResults) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockchainInfo provides a mock function with given fields: ctx, minHeight, maxHeight +func (_m *Client) BlockchainInfo(ctx context.Context, minHeight int64, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { + ret := _m.Called(ctx, minHeight, maxHeight) + + var r0 *coretypes.ResultBlockchainInfo + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *coretypes.ResultBlockchainInfo); ok { + r0 = rf(ctx, minHeight, maxHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockchainInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { + r1 = rf(ctx, minHeight, maxHeight) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastEvidence provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastEvidence(_a0 context.Context, _a1 types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastEvidence + if rf, ok := ret.Get(0).(func(context.Context, types.Evidence) *coretypes.ResultBroadcastEvidence); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastEvidence) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Evidence) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxAsync provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxAsync(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxCommit provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxCommit(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTxCommit + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTxCommit); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTxCommit) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxSync provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxSync(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckTx provides a mock function with given fields: _a0, _a1 +func (_m *Client) CheckTx(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultCheckTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultCheckTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultCheckTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultCheckTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Commit provides a mock function with given fields: ctx, height +func (_m *Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultCommit + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultCommit); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultCommit) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ConsensusParams provides a mock function with given fields: ctx, height +func (_m *Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultConsensusParams + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultConsensusParams); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultConsensusParams) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ConsensusState provides a mock function with given fields: _a0 +func (_m *Client) ConsensusState(_a0 context.Context) (*coretypes.ResultConsensusState, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultConsensusState + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultConsensusState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultConsensusState) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DumpConsensusState provides a mock function with given fields: _a0 +func (_m *Client) DumpConsensusState(_a0 context.Context) (*coretypes.ResultDumpConsensusState, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultDumpConsensusState + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultDumpConsensusState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultDumpConsensusState) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Genesis provides a mock function with given fields: _a0 +func (_m *Client) Genesis(_a0 context.Context) (*coretypes.ResultGenesis, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultGenesis + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultGenesis); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultGenesis) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Health provides a mock function with given fields: _a0 +func (_m *Client) Health(_a0 context.Context) (*coretypes.ResultHealth, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultHealth + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultHealth); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultHealth) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsRunning provides a mock function with given fields: +func (_m *Client) IsRunning() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// NetInfo provides a mock function with given fields: _a0 +func (_m *Client) NetInfo(_a0 context.Context) (*coretypes.ResultNetInfo, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultNetInfo + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultNetInfo); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultNetInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NumUnconfirmedTxs provides a mock function with given fields: _a0 +func (_m *Client) NumUnconfirmedTxs(_a0 context.Context) (*coretypes.ResultUnconfirmedTxs, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultUnconfirmedTxs + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultUnconfirmedTxs); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTxs) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OnReset provides a mock function with given fields: +func (_m *Client) OnReset() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OnStart provides a mock function with given fields: +func (_m *Client) OnStart() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OnStop provides a mock function with given fields: +func (_m *Client) OnStop() { + _m.Called() +} + +// Quit provides a mock function with given fields: +func (_m *Client) Quit() <-chan struct{} { + ret := _m.Called() + + var r0 <-chan struct{} + if rf, ok := ret.Get(0).(func() <-chan struct{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan struct{}) + } + } + + return r0 +} + +// Reset provides a mock function with given fields: +func (_m *Client) Reset() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetLogger provides a mock function with given fields: _a0 +func (_m *Client) SetLogger(_a0 log.Logger) { + _m.Called(_a0) +} + +// Start provides a mock function with given fields: +func (_m *Client) Start() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Status provides a mock function with given fields: _a0 +func (_m *Client) Status(_a0 context.Context) (*coretypes.ResultStatus, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultStatus + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultStatus); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultStatus) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Stop provides a mock function with given fields: +func (_m *Client) Stop() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// String provides a mock function with given fields: +func (_m *Client) String() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Subscribe provides a mock function with given fields: ctx, subscriber, query, outCapacity +func (_m *Client) Subscribe(ctx context.Context, subscriber string, query string, outCapacity ...int) (<-chan coretypes.ResultEvent, error) { + _va := make([]interface{}, len(outCapacity)) + for _i := range outCapacity { + _va[_i] = outCapacity[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, subscriber, query) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 <-chan coretypes.ResultEvent + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...int) <-chan coretypes.ResultEvent); ok { + r0 = rf(ctx, subscriber, query, outCapacity...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan coretypes.ResultEvent) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, ...int) error); ok { + r1 = rf(ctx, subscriber, query, outCapacity...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx provides a mock function with given fields: ctx, hash, prove +func (_m *Client) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) { + ret := _m.Called(ctx, hash, prove) + + var r0 *coretypes.ResultTx + if rf, ok := ret.Get(0).(func(context.Context, []byte, bool) *coretypes.ResultTx); ok { + r0 = rf(ctx, hash, prove) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []byte, bool) error); ok { + r1 = rf(ctx, hash, prove) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxSearch provides a mock function with given fields: ctx, query, prove, page, perPage, orderBy +func (_m *Client) TxSearch(ctx context.Context, query string, prove bool, page *int, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { + ret := _m.Called(ctx, query, prove, page, perPage, orderBy) + + var r0 *coretypes.ResultTxSearch + if rf, ok := ret.Get(0).(func(context.Context, string, bool, *int, *int, string) *coretypes.ResultTxSearch); ok { + r0 = rf(ctx, query, prove, page, perPage, orderBy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultTxSearch) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bool, *int, *int, string) error); ok { + r1 = rf(ctx, query, prove, page, perPage, orderBy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnconfirmedTxs provides a mock function with given fields: ctx, limit +func (_m *Client) UnconfirmedTxs(ctx context.Context, limit *int) (*coretypes.ResultUnconfirmedTxs, error) { + ret := _m.Called(ctx, limit) + + var r0 *coretypes.ResultUnconfirmedTxs + if rf, ok := ret.Get(0).(func(context.Context, *int) *coretypes.ResultUnconfirmedTxs); ok { + r0 = rf(ctx, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTxs) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int) error); ok { + r1 = rf(ctx, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unsubscribe provides a mock function with given fields: ctx, subscriber, query +func (_m *Client) Unsubscribe(ctx context.Context, subscriber string, query string) error { + ret := _m.Called(ctx, subscriber, query) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, subscriber, query) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnsubscribeAll provides a mock function with given fields: ctx, subscriber +func (_m *Client) UnsubscribeAll(ctx context.Context, subscriber string) error { + ret := _m.Called(ctx, subscriber) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, subscriber) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Validators provides a mock function with given fields: ctx, height, page, perPage +func (_m *Client) Validators(ctx context.Context, height *int64, page *int, perPage *int) (*coretypes.ResultValidators, error) { + ret := _m.Called(ctx, height, page, perPage) + + var r0 *coretypes.ResultValidators + if rf, ok := ret.Get(0).(func(context.Context, *int64, *int, *int) *coretypes.ResultValidators); ok { + r0 = rf(ctx, height, page, perPage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultValidators) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64, *int, *int) error); ok { + r1 = rf(ctx, height, page, perPage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} From 0f3b49a915a35d599251c68fed38b1da39b96a7f Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 22 Oct 2020 12:39:27 +0200 Subject: [PATCH 021/108] ci: docker remove circleci and add github action (#5551) --- .circleci/config.yml | 26 ----------------- .github/workflows/docker.yml | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/docker.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 170e8529fc..340e232d99 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -205,23 +205,6 @@ jobs: export GOOS=linux GOARCH=arm64 && 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: - machine: - image: ubuntu-1604:201903-01 - 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 reproducible_builds: executor: golang steps: @@ -328,12 +311,3 @@ workflows: branches: only: - /v[0-9]+\.[0-9]+/ - - release_docker: - requires: - - prepare_build - - build_artifacts - filters: - branches: - only: - - /v[0-9]+\.[0-9]+/ - - master diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..35d132b796 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,55 @@ +name: Build & Push +# Build & Push rebuilds the tendermint docker image on every push to master and creation of tags +# and pushes the image to https://hub.docker.com/r/interchainio/simapp/tags +on: + push: + branches: + - master + tags: + - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 + - "v[0-9]+.[0-9]+.[0-9]+-rc*" # Push events to matching v*, i.e. v1.0-rc1, v20.15.10-rc5 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Prepare + id: prep + run: | + DOCKER_IMAGE=tendermint/tendermint + VERSION=noop + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + elif [[ $GITHUB_REF == refs/heads/* ]]; then + VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g') + if [ "${{ github.event.repository.default_branch }}" = "$VERSION" ]; then + VERSION=latest + fi + fi + TAGS="${DOCKER_IMAGE}:${VERSION}" + if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + TAGS="$TAGS,${DOCKER_IMAGE}:${VERSION}" + fi + echo ::set-output name=tags::${TAGS} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build Tendermint + run: | + make build-linux && cp build/tendermint DOCKER/tendermint + + - name: Publish to Docker Hub + uses: docker/build-push-action@v2 + with: + context: ./DOCKER + file: ./DOCKER/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.prep.outputs.tags }} From 41ab19937843fbf037081f62207d0e136c1a1c11 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 28 Sep 2020 09:20:54 +0200 Subject: [PATCH 022/108] blockchain/v1: add noBlockResponse handling (#5401) ## Description Add simple `NoBlockResponse` handling to blockchain reactor v1. I tested before and after with erik's e2e testing and was not able to reproduce the inability to sync after the changes were applied Closes: #5394 --- blockchain/v1/reactor.go | 10 ++++++++++ blockchain/v1/reactor_fsm.go | 9 +++++++++ blockchain/v1/reactor_fsm_test.go | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/blockchain/v1/reactor.go b/blockchain/v1/reactor.go index 7eb4ae67d5..30bd4dd8a7 100644 --- a/blockchain/v1/reactor.go +++ b/blockchain/v1/reactor.go @@ -298,6 +298,16 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) } bcR.Logger.Info("Received", "src", src, "height", bi.Height) bcR.messagesForFSMCh <- msgForFSM + case *bcproto.NoBlockResponse: + msgForFSM := bcReactorMessage{ + event: noBlockResponseEv, + data: bReactorEventData{ + peerID: src.ID(), + height: msg.Height, + }, + } + bcR.Logger.Debug("Peer does not have requested block", "peer", src, "height", msg.Height) + bcR.messagesForFSMCh <- msgForFSM case *bcproto.StatusResponse: // Got a peer status. Unverified. diff --git a/blockchain/v1/reactor_fsm.go b/blockchain/v1/reactor_fsm.go index 84944d0a6a..2571dff6a8 100644 --- a/blockchain/v1/reactor_fsm.go +++ b/blockchain/v1/reactor_fsm.go @@ -74,6 +74,7 @@ const ( startFSMEv = iota + 1 statusResponseEv blockResponseEv + noBlockResponseEv processedBlockEv makeRequestsEv stopFSMEv @@ -94,6 +95,9 @@ func (msg *bcReactorMessage) String() string { case blockResponseEv: dataStr = fmt.Sprintf("peer=%v block.height=%v length=%v", msg.data.peerID, msg.data.block.Height, msg.data.length) + case noBlockResponseEv: + dataStr = fmt.Sprintf("peer=%v requested height=%v", + msg.data.peerID, msg.data.height) case processedBlockEv: dataStr = fmt.Sprintf("error=%v", msg.data.err) case makeRequestsEv: @@ -119,6 +123,8 @@ func (ev bReactorEvent) String() string { return "statusResponseEv" case blockResponseEv: return "blockResponseEv" + case noBlockResponseEv: + return "noBlockResponseEv" case processedBlockEv: return "processedBlockEv" case makeRequestsEv: @@ -269,7 +275,10 @@ func init() { return waitForPeer, err } return waitForBlock, err + case noBlockResponseEv: + fsm.logger.Error("peer does not have requested block", "peer", data.peerID) + return waitForBlock, nil case processedBlockEv: if data.err != nil { first, second, _ := fsm.pool.FirstTwoBlocksAndPeers() diff --git a/blockchain/v1/reactor_fsm_test.go b/blockchain/v1/reactor_fsm_test.go index 5980ceb086..99b8d378d7 100644 --- a/blockchain/v1/reactor_fsm_test.go +++ b/blockchain/v1/reactor_fsm_test.go @@ -822,7 +822,7 @@ const ( maxRequestsPerPeerTest = 20 maxTotalPendingRequestsTest = 600 maxNumPeersTest = 1000 - maxNumBlocksInChainTest = 10000 //should be smaller than 9999999 + maxNumBlocksInChainTest = 10000 // should be smaller than 9999999 ) func makeCorrectTransitionSequenceWithRandomParameters() testFields { From 1b733ea28dd30cc0938767fa10b3ebd1a4ac4615 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 25 Sep 2020 11:38:28 +0700 Subject: [PATCH 023/108] fix a few typos (#5402) --- abci/example/kvstore/kvstore_test.go | 2 +- consensus/replay_test.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/abci/example/kvstore/kvstore_test.go b/abci/example/kvstore/kvstore_test.go index bb2cf909dd..2269ec2284 100644 --- a/abci/example/kvstore/kvstore_test.go +++ b/abci/example/kvstore/kvstore_test.go @@ -129,7 +129,7 @@ func TestValUpdates(t *testing.T) { total := 10 nInit := 5 vals := RandVals(total) - // iniitalize with the first nInit + // initialize with the first nInit kvstore.InitChain(types.RequestInitChain{ Validators: vals[:nInit], }) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 0ac5a9f519..5f279e330c 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -665,17 +665,17 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin var commits []*types.Commit var store *mockBlockStore var stateDB dbm.DB - var genisisState sm.State + var genesisState sm.State if testValidatorsChange { testConfig := ResetConfig(fmt.Sprintf("%s_%v_m", t.Name(), mode)) defer os.RemoveAll(testConfig.RootDir) stateDB = dbm.NewMemDB() - genisisState = sim.GenesisState + genesisState = sim.GenesisState config = sim.Config chain = append([]*types.Block{}, sim.Chain...) // copy chain commits = sim.Commits - store = newMockBlockStore(config, genisisState.ConsensusParams) + store = newMockBlockStore(config, genesisState.ConsensusParams) } else { //test single node testConfig := ResetConfig(fmt.Sprintf("%s_%v_s", t.Name(), mode)) defer os.RemoveAll(testConfig.RootDir) @@ -700,14 +700,14 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin require.NoError(t, err) pubKey, err := privVal.GetPubKey() require.NoError(t, err) - stateDB, genisisState, store = stateAndStore(config, pubKey, kvstore.ProtocolVersion) + stateDB, genesisState, store = stateAndStore(config, pubKey, kvstore.ProtocolVersion) } stateStore := sm.NewStore(stateDB) store.chain = chain store.commits = commits - state := genisisState.Copy() + state := genesisState.Copy() // run the chain through state.ApplyBlock to build up the tendermint state state = buildTMStateFromChain(config, stateStore, state, chain, nBlocks, mode) latestAppHash := state.AppHash @@ -723,9 +723,9 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin proxyApp := proxy.NewAppConns(clientCreator2) stateDB1 := dbm.NewMemDB() stateStore := sm.NewStore(stateDB1) - err := stateStore.Save(genisisState) + err := stateStore.Save(genesisState) require.NoError(t, err) - buildAppStateFromChain(proxyApp, stateStore, genisisState, chain, nBlocks, mode) + buildAppStateFromChain(proxyApp, stateStore, genesisState, chain, nBlocks, mode) } // Prune block store if requested From a58454e788d757f9a2693516fda2b2ee4ebe2200 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 5 Oct 2020 11:35:01 +0200 Subject: [PATCH 024/108] test: add end-to-end testing framework (#5435) Partial fix for #5291. For details, see [README.md](https://github.com/tendermint/tendermint/blob/erik/e2e-tests/test/e2e/README.md) and [RFC-001](https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-001-end-to-end-testing.md). This only includes a single test case under `test/e2e/tests/`, as a proof of concept - additional test cases will be submitted separately. A randomized testnet generator will also be submitted separately, there a currently just a handful of static testnets under `test/e2e/networks/`. This will eventually replace the current P2P tests and run in CI. --- .dockerignore | 5 + .gitignore | 2 + go.mod | 1 + privval/file.go | 13 +- test/e2e/Makefile | 16 + test/e2e/README.md | 78 +++++ test/e2e/app/app.go | 217 +++++++++++++ test/e2e/app/config.go | 50 +++ test/e2e/app/main.go | 173 +++++++++++ test/e2e/app/snapshots.go | 155 ++++++++++ test/e2e/app/state.go | 155 ++++++++++ test/e2e/docker/Dockerfile | 32 ++ test/e2e/docker/entrypoint | 10 + test/e2e/docker/entrypoint-builtin | 6 + test/e2e/networks/ci.toml | 97 ++++++ test/e2e/networks/simple.toml | 4 + test/e2e/networks/single.toml | 1 + test/e2e/pkg/manifest.go | 127 ++++++++ test/e2e/pkg/testnet.go | 470 +++++++++++++++++++++++++++++ test/e2e/runner/cleanup.go | 35 +++ test/e2e/runner/exec.go | 50 +++ test/e2e/runner/load.go | 106 +++++++ test/e2e/runner/main.go | 184 +++++++++++ test/e2e/runner/perturb.go | 75 +++++ test/e2e/runner/rpc.go | 107 +++++++ test/e2e/runner/setup.go | 360 ++++++++++++++++++++++ test/e2e/runner/start.go | 68 +++++ test/e2e/runner/test.go | 19 ++ test/e2e/runner/wait.go | 24 ++ test/e2e/tests/app_test.go | 30 ++ test/e2e/tests/e2e_test.go | 57 ++++ 31 files changed, 2722 insertions(+), 5 deletions(-) create mode 100644 .dockerignore create mode 100644 test/e2e/Makefile create mode 100644 test/e2e/README.md create mode 100644 test/e2e/app/app.go create mode 100644 test/e2e/app/config.go create mode 100644 test/e2e/app/main.go create mode 100644 test/e2e/app/snapshots.go create mode 100644 test/e2e/app/state.go create mode 100644 test/e2e/docker/Dockerfile create mode 100755 test/e2e/docker/entrypoint create mode 100755 test/e2e/docker/entrypoint-builtin create mode 100644 test/e2e/networks/ci.toml create mode 100644 test/e2e/networks/simple.toml create mode 100644 test/e2e/networks/single.toml create mode 100644 test/e2e/pkg/manifest.go create mode 100644 test/e2e/pkg/testnet.go create mode 100644 test/e2e/runner/cleanup.go create mode 100644 test/e2e/runner/exec.go create mode 100644 test/e2e/runner/load.go create mode 100644 test/e2e/runner/main.go create mode 100644 test/e2e/runner/perturb.go create mode 100644 test/e2e/runner/rpc.go create mode 100644 test/e2e/runner/setup.go create mode 100644 test/e2e/runner/start.go create mode 100644 test/e2e/runner/test.go create mode 100644 test/e2e/runner/wait.go create mode 100644 test/e2e/tests/app_test.go create mode 100644 test/e2e/tests/e2e_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..a7ae6a5b0c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +build +test/e2e/build +test/e2e/networks +test/logs +test/p2p/data diff --git a/.gitignore b/.gitignore index 5d2d31aa23..8cd4bf484e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ remote_dump .revision vendor .vagrant +test/e2e/build +test/e2e/networks/*/ test/p2p/data/ test/logs coverage.txt diff --git a/go.mod b/go.mod index b9c0eadb69..ee8ae5d190 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tendermint/tendermint go 1.14 require ( + github.com/BurntSushi/toml v0.3.1 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d github.com/Workiva/go-datastructures v1.0.52 github.com/btcsuite/btcd v0.21.0-beta diff --git a/privval/file.go b/privval/file.go index 87bffe92e8..c4542112ba 100644 --- a/privval/file.go +++ b/privval/file.go @@ -152,11 +152,8 @@ type FilePV struct { LastSignState FilePVLastSignState } -// GenFilePV generates a new validator with randomly generated private key -// and sets the filePaths, but does not call Save(). -func GenFilePV(keyFilePath, stateFilePath string) *FilePV { - privKey := ed25519.GenPrivKey() - +// NewFilePV generates a new validator from the given key and paths. +func NewFilePV(privKey crypto.PrivKey, keyFilePath, stateFilePath string) *FilePV { return &FilePV{ Key: FilePVKey{ Address: privKey.PubKey().Address(), @@ -171,6 +168,12 @@ func GenFilePV(keyFilePath, stateFilePath string) *FilePV { } } +// GenFilePV generates a new validator with randomly generated private key +// and sets the filePaths, but does not call Save(). +func GenFilePV(keyFilePath, stateFilePath string) *FilePV { + return NewFilePV(ed25519.GenPrivKey(), keyFilePath, stateFilePath) +} + // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double // signing prevention by persisting data to the stateFilePath. If either file path // does not exist, the program will exit. diff --git a/test/e2e/Makefile b/test/e2e/Makefile new file mode 100644 index 0000000000..858335e3f2 --- /dev/null +++ b/test/e2e/Makefile @@ -0,0 +1,16 @@ +docker: + docker build --tag tendermint/e2e-node -f docker/Dockerfile ../.. + +ci: runner + ./build/runner -f networks/ci.toml + +# We need to build support for database backends into the app in +# order to build a binary with a Tendermint node in it (for built-in +# ABCI testing). +app: + go build -o build/app -tags badgerdb,boltdb,cleveldb,rocksdb ./app + +runner: + go build -o build/runner ./runner + +.PHONY: app ci docker runner diff --git a/test/e2e/README.md b/test/e2e/README.md new file mode 100644 index 0000000000..7c4891ce22 --- /dev/null +++ b/test/e2e/README.md @@ -0,0 +1,78 @@ +# End-to-End Tests + +Spins up and tests Tendermint networks in Docker Compose based on a testnet manifest. To run the CI testnet: + +```sh +make docker +make runner +./build/runner -f networks/ci.toml +``` + +This creates and runs a testnet named `ci` under `networks/ci/` (determined by the manifest filename). + +## Testnet Manifests + +Testnets are specified as TOML manifests. For an example see [`networks/ci.toml`](networks/ci.toml), and for documentation see [`pkg/manifest.go`](pkg/manifest.go). + +## Test Stages + +The test runner has the following stages, which can also be executed explicitly by running `./build/runner -f `: + +* `setup`: generates configuration files. + +* `start`: starts Docker containers. + +* `load`: generates a transaction load against the testnet nodes. + +* `perturb`: runs any requested perturbations (e.g. node restarts or network disconnects). + +* `wait`: waits for a few blocks to be produced, and for all nodes to catch up to it. + +* `test`: runs test cases in `tests/` against all nodes in a running testnet. + +* `stop`: stops Docker containers. + +* `cleanup`: removes configuration files and Docker containers/networks. + +* `logs`: outputs all node logs. + +## Tests + +Test cases are written as normal Go tests in `tests/`. They use a `testNode()` helper which executes each test as a parallel subtest for each node in the network. + +### Running Manual Tests + +To run tests manually, set the `E2E_MANIFEST` environment variable to the path of the testnet manifest (e.g. `networks/ci.toml`) and run them as normal, e.g.: + +```sh +./build/runner -f networks/ci.toml start +E2E_MANIFEST=networks/ci.toml go test -v ./tests/... +``` + +Optionally, `E2E_NODE` specifies the name of a single testnet node to test. + +These environment variables can also be specified in `tests/e2e_test.go` to run tests from an editor or IDE: + +```go +func init() { + // This can be used to manually specify a testnet manifest and/or node to + // run tests against. The testnet must have been started by the runner first. + os.Setenv("E2E_MANIFEST", "networks/ci.toml") + os.Setenv("E2E_NODE", "validator01") +} +``` + +### Debugging Failures + +If a command or test fails, the runner simply exits with an error message and non-zero status code. The testnet is left running with data in the testnet directory, and can be inspected with e.g. `docker ps`, `docker logs`, or `./build/runner -f logs` or `tail`. To shut down and remove the testnet, run `./build/runner -f cleanup`. + +## Enabling IPv6 + +Docker does not enable IPv6 by default. To do so, enter the following in `daemon.json` (or in the Docker for Mac UI under Preferences → Docker Engine): + +```json +{ + "ipv6": true, + "fixed-cidr-v6": "2001:db8:1::/64" +} +``` diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go new file mode 100644 index 0000000000..415fc7ad70 --- /dev/null +++ b/test/e2e/app/app.go @@ -0,0 +1,217 @@ +package main + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/tendermint/tendermint/abci/example/code" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/version" +) + +// Application is an ABCI application for use by end-to-end tests. It is a +// simple key/value store for strings, storing data in memory and persisting +// to disk as JSON, taking state sync snapshots if requested. +type Application struct { + abci.BaseApplication + logger log.Logger + state *State + snapshots *SnapshotStore + cfg *Config + restoreSnapshot *abci.Snapshot + restoreChunks [][]byte +} + +// NewApplication creates the application. +func NewApplication(cfg *Config) (*Application, error) { + state, err := NewState(filepath.Join(cfg.Dir, "state.json"), cfg.PersistInterval) + if err != nil { + return nil, err + } + snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots")) + if err != nil { + return nil, err + } + return &Application{ + logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)), + state: state, + snapshots: snapshots, + cfg: cfg, + }, nil +} + +// Info implements ABCI. +func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo { + return abci.ResponseInfo{ + Version: version.ABCIVersion, + AppVersion: 1, + LastBlockHeight: int64(app.state.Height), + LastBlockAppHash: app.state.Hash, + } +} + +// Info implements ABCI. +func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { + var err error + app.state.initialHeight = uint64(req.InitialHeight) + if len(req.AppStateBytes) > 0 { + err = app.state.Import(0, req.AppStateBytes) + if err != nil { + panic(err) + } + } + resp := abci.ResponseInitChain{ + AppHash: app.state.Hash, + } + if resp.Validators, err = app.validatorUpdates(0); err != nil { + panic(err) + } + return resp +} + +// CheckTx implements ABCI. +func (app *Application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + _, _, err := parseTx(req.Tx) + if err != nil { + return abci.ResponseCheckTx{ + Code: code.CodeTypeEncodingError, + Log: err.Error(), + } + } + return abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} +} + +// DeliverTx implements ABCI. +func (app *Application) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + key, value, err := parseTx(req.Tx) + if err != nil { + panic(err) // shouldn't happen since we verified it in CheckTx + } + app.state.Set(key, value) + return abci.ResponseDeliverTx{Code: code.CodeTypeOK} +} + +// EndBlock implements ABCI. +func (app *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { + var err error + resp := abci.ResponseEndBlock{} + if resp.ValidatorUpdates, err = app.validatorUpdates(uint64(req.Height)); err != nil { + panic(err) + } + return resp +} + +// Commit implements ABCI. +func (app *Application) Commit() abci.ResponseCommit { + height, hash, err := app.state.Commit() + if err != nil { + panic(err) + } + if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 { + snapshot, err := app.snapshots.Create(app.state) + if err != nil { + panic(err) + } + logger.Info("Created state sync snapshot", "height", snapshot.Height) + } + retainHeight := int64(0) + if app.cfg.RetainBlocks > 0 { + retainHeight = int64(height - app.cfg.RetainBlocks + 1) + } + return abci.ResponseCommit{ + Data: hash, + RetainHeight: retainHeight, + } +} + +// Query implements ABCI. +func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery { + return abci.ResponseQuery{ + Height: int64(app.state.Height), + Key: req.Data, + Value: []byte(app.state.Get(string(req.Data))), + } +} + +// ListSnapshots implements ABCI. +func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { + snapshots, err := app.snapshots.List() + if err != nil { + panic(err) + } + return abci.ResponseListSnapshots{Snapshots: snapshots} +} + +// LoadSnapshotChunk implements ABCI. +func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { + chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk) + if err != nil { + panic(err) + } + return abci.ResponseLoadSnapshotChunk{Chunk: chunk} +} + +// OfferSnapshot implements ABCI. +func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { + if app.restoreSnapshot != nil { + panic("A snapshot is already being restored") + } + app.restoreSnapshot = req.Snapshot + app.restoreChunks = [][]byte{} + return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT} +} + +// ApplySnapshotChunk implements ABCI. +func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { + if app.restoreSnapshot == nil { + panic("No restore in progress") + } + app.restoreChunks = append(app.restoreChunks, req.Chunk) + if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) { + bz := []byte{} + for _, chunk := range app.restoreChunks { + bz = append(bz, chunk...) + } + err := app.state.Import(app.restoreSnapshot.Height, bz) + if err != nil { + panic(err) + } + app.restoreSnapshot = nil + app.restoreChunks = nil + } + return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT} +} + +// validatorUpdates generates a validator set update. +func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, error) { + updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)] + if len(updates) == 0 { + return nil, nil + } + valUpdates := abci.ValidatorUpdates{} + for keyString, power := range updates { + keyBytes, err := base64.StdEncoding.DecodeString(keyString) + if err != nil { + return nil, fmt.Errorf("invalid base64 pubkey value %q: %w", keyString, err) + } + valUpdates = append(valUpdates, abci.Ed25519ValidatorUpdate(keyBytes, int64(power))) + } + return valUpdates, nil +} + +// parseTx parses a tx in 'key=value' format into a key and value. +func parseTx(tx []byte) (string, string, error) { + parts := bytes.Split(tx, []byte("=")) + if len(parts) != 2 { + return "", "", fmt.Errorf("invalid tx format: %q", string(tx)) + } + if len(parts[0]) == 0 { + return "", "", errors.New("key cannot be empty") + } + return string(parts[0]), string(parts[1]), nil +} diff --git a/test/e2e/app/config.go b/test/e2e/app/config.go new file mode 100644 index 0000000000..bee8f59e58 --- /dev/null +++ b/test/e2e/app/config.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/BurntSushi/toml" +) + +// Config is the application configuration. +type Config struct { + ChainID string `toml:"chain_id"` + Listen string + Protocol string + Dir string + PersistInterval uint64 `toml:"persist_interval"` + SnapshotInterval uint64 `toml:"snapshot_interval"` + RetainBlocks uint64 `toml:"retain_blocks"` + ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"` + PrivValServer string `toml:"privval_server"` + PrivValKey string `toml:"privval_key"` + PrivValState string `toml:"privval_state"` +} + +// LoadConfig loads the configuration from disk. +func LoadConfig(file string) (*Config, error) { + cfg := &Config{ + Listen: "unix:///var/run/app.sock", + Protocol: "socket", + PersistInterval: 1, + } + _, err := toml.DecodeFile(file, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to load config from %q: %w", file, err) + } + return cfg, cfg.Validate() +} + +// Validate validates the configuration. We don't do exhaustive config +// validation here, instead relying on Testnet.Validate() to handle it. +func (cfg Config) Validate() error { + switch { + case cfg.ChainID == "": + return errors.New("chain_id parameter is required") + case cfg.Listen == "" && cfg.Protocol != "builtin": + return errors.New("listen parameter is required") + default: + return nil + } +} diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go new file mode 100644 index 0000000000..d87eb40a7d --- /dev/null +++ b/test/e2e/app/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/spf13/viper" + "github.com/tendermint/tendermint/abci/server" + "github.com/tendermint/tendermint/config" + tmflags "github.com/tendermint/tendermint/libs/cli/flags" + "github.com/tendermint/tendermint/libs/log" + tmnet "github.com/tendermint/tendermint/libs/net" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" +) + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + +// main is the binary entrypoint. +func main() { + if len(os.Args) != 2 { + fmt.Printf("Usage: %v ", os.Args[0]) + return + } + configFile := "" + if len(os.Args) == 2 { + configFile = os.Args[1] + } + + if err := run(configFile); err != nil { + logger.Error(err.Error()) + os.Exit(1) + } +} + +// run runs the application - basically like main() with error handling. +func run(configFile string) error { + cfg, err := LoadConfig(configFile) + if err != nil { + return err + } + + switch cfg.Protocol { + case "socket", "grpc": + err = startApp(cfg) + case "builtin": + err = startNode(cfg) + default: + err = fmt.Errorf("invalid protocol %q", cfg.Protocol) + } + if err != nil { + return err + } + + // Start remote signer + if cfg.PrivValServer != "" { + if err = startSigner(cfg); err != nil { + return err + } + } + + // Apparently there's no way to wait for the server, so we just sleep + for { + time.Sleep(1 * time.Hour) + } +} + +// startApp starts the application server, listening for connections from Tendermint. +func startApp(cfg *Config) error { + app, err := NewApplication(cfg) + if err != nil { + return err + } + server, err := server.NewServer(cfg.Listen, cfg.Protocol, app) + if err != nil { + return err + } + err = server.Start() + if err != nil { + return err + } + logger.Info(fmt.Sprintf("Server listening on %v (%v protocol)", cfg.Listen, cfg.Protocol)) + return nil +} + +// startNode starts a Tendermint node running the application directly. It assumes the Tendermint +// configuration is in $TMHOME/config/tendermint.toml. +// +// FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper. +func startNode(cfg *Config) error { + app, err := NewApplication(cfg) + if err != nil { + return err + } + + home := os.Getenv("TMHOME") + if home == "" { + return errors.New("TMHOME not set") + } + viper.AddConfigPath(filepath.Join(home, "config")) + viper.SetConfigName("config") + err = viper.ReadInConfig() + if err != nil { + return err + } + tmcfg := config.DefaultConfig() + err = viper.Unmarshal(tmcfg) + if err != nil { + return err + } + tmcfg.SetRoot(home) + if err = tmcfg.ValidateBasic(); err != nil { + return fmt.Errorf("error in config file: %v", err) + } + if tmcfg.LogFormat == config.LogFormatJSON { + logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) + } + logger, err = tmflags.ParseLogLevel(tmcfg.LogLevel, logger, config.DefaultLogLevel()) + if err != nil { + return err + } + logger = logger.With("module", "main") + + nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) + if err != nil { + return fmt.Errorf("failed to load or gen node key %s: %w", tmcfg.NodeKeyFile(), err) + } + + n, err := node.NewNode(tmcfg, + privval.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + node.DefaultGenesisDocProviderFunc(tmcfg), + node.DefaultDBProvider, + node.DefaultMetricsProvider(tmcfg.Instrumentation), + logger, + ) + if err != nil { + return err + } + return n.Start() +} + +// startSigner starts a signer server connecting to the given endpoint. +func startSigner(cfg *Config) error { + filePV := privval.LoadFilePV(cfg.PrivValKey, cfg.PrivValState) + + protocol, address := tmnet.ProtocolAndAddress(cfg.PrivValServer) + var dialFn privval.SocketDialer + switch protocol { + case "tcp": + dialFn = privval.DialTCPFn(address, 3*time.Second, filePV.Key.PrivKey) + case "unix": + dialFn = privval.DialUnixFn(address) + default: + return fmt.Errorf("invalid privval protocol %q", protocol) + } + + endpoint := privval.NewSignerDialerEndpoint(logger, dialFn, + privval.SignerDialerEndpointRetryWaitInterval(1*time.Second), + privval.SignerDialerEndpointConnRetries(100)) + err := privval.NewSignerServer(endpoint, cfg.ChainID, filePV).Start() + if err != nil { + return err + } + logger.Info(fmt.Sprintf("Remote signer connecting to %v", cfg.PrivValServer)) + return nil +} diff --git a/test/e2e/app/snapshots.go b/test/e2e/app/snapshots.go new file mode 100644 index 0000000000..590b13cee0 --- /dev/null +++ b/test/e2e/app/snapshots.go @@ -0,0 +1,155 @@ +// nolint: gosec +package main + +import ( + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math" + "os" + "path/filepath" + "sync" + + abci "github.com/tendermint/tendermint/abci/types" +) + +const ( + snapshotChunkSize = 1e6 +) + +// SnapshotStore stores state sync snapshots. Snapshots are stored simply as +// JSON files, and chunks are generated on-the-fly by splitting the JSON data +// into fixed-size chunks. +type SnapshotStore struct { + sync.RWMutex + dir string + metadata []abci.Snapshot +} + +// NewSnapshotStore creates a new snapshot store. +func NewSnapshotStore(dir string) (*SnapshotStore, error) { + store := &SnapshotStore{dir: dir} + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, err + } + if err := store.loadMetadata(); err != nil { + return nil, err + } + return store, nil +} + +// loadMetadata loads snapshot metadata. Does not take out locks, since it's +// called internally on construction. +func (s *SnapshotStore) loadMetadata() error { + file := filepath.Join(s.dir, "metadata.json") + metadata := []abci.Snapshot{} + + bz, err := ioutil.ReadFile(file) + switch { + case errors.Is(err, os.ErrNotExist): + case err != nil: + return fmt.Errorf("failed to load snapshot metadata from %q: %w", file, err) + } + if len(bz) != 0 { + err = json.Unmarshal(bz, &metadata) + if err != nil { + return fmt.Errorf("invalid snapshot data in %q: %w", file, err) + } + } + s.metadata = metadata + return nil +} + +// saveMetadata saves snapshot metadata. Does not take out locks, since it's +// called internally from e.g. Create(). +func (s *SnapshotStore) saveMetadata() error { + bz, err := json.Marshal(s.metadata) + if err != nil { + return err + } + + // save the file to a new file and move it to make saving atomic. + newFile := filepath.Join(s.dir, "metadata.json.new") + file := filepath.Join(s.dir, "metadata.json") + err = ioutil.WriteFile(newFile, bz, 0644) // nolint: gosec + if err != nil { + return err + } + return os.Rename(newFile, file) +} + +// Create creates a snapshot of the given application state's key/value pairs. +func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) { + s.Lock() + defer s.Unlock() + bz, err := state.Export() + if err != nil { + return abci.Snapshot{}, err + } + hash := sha256.Sum256(bz) + snapshot := abci.Snapshot{ + Height: state.Height, + Format: 1, + Hash: hash[:], + Chunks: byteChunks(bz), + } + err = ioutil.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644) + if err != nil { + return abci.Snapshot{}, err + } + s.metadata = append(s.metadata, snapshot) + err = s.saveMetadata() + if err != nil { + return abci.Snapshot{}, err + } + return snapshot, nil +} + +// List lists available snapshots. +func (s *SnapshotStore) List() ([]*abci.Snapshot, error) { + s.RLock() + defer s.RUnlock() + snapshots := []*abci.Snapshot{} + for _, snapshot := range s.metadata { + s := snapshot // copy to avoid pointer to range variable + snapshots = append(snapshots, &s) + } + return snapshots, nil +} + +// LoadChunk loads a snapshot chunk. +func (s *SnapshotStore) LoadChunk(height uint64, format uint32, chunk uint32) ([]byte, error) { + s.RLock() + defer s.RUnlock() + for _, snapshot := range s.metadata { + if snapshot.Height == height && snapshot.Format == format { + bz, err := ioutil.ReadFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", height))) + if err != nil { + return nil, err + } + return byteChunk(bz, chunk), nil + } + } + return nil, nil +} + +// byteChunk returns the chunk at a given index from the full byte slice. +func byteChunk(bz []byte, index uint32) []byte { + start := int(index * snapshotChunkSize) + end := int((index + 1) * snapshotChunkSize) + switch { + case start >= len(bz): + return nil + case end >= len(bz): + return bz[start:] + default: + return bz[start:end] + } +} + +// byteChunks calculates the number of chunks in the byte slice. +func byteChunks(bz []byte) uint32 { + return uint32(math.Ceil(float64(len(bz)) / snapshotChunkSize)) +} diff --git a/test/e2e/app/state.go b/test/e2e/app/state.go new file mode 100644 index 0000000000..ad99601056 --- /dev/null +++ b/test/e2e/app/state.go @@ -0,0 +1,155 @@ +//nolint: gosec +package main + +import ( + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "sort" + "sync" +) + +// State is the application state. +type State struct { + sync.RWMutex + Height uint64 + Values map[string]string + Hash []byte + + // private fields aren't marshalled to disk. + file string + persistInterval uint64 + initialHeight uint64 +} + +// NewState creates a new state. +func NewState(file string, persistInterval uint64) (*State, error) { + state := &State{ + Values: make(map[string]string), + file: file, + persistInterval: persistInterval, + } + state.Hash = hashItems(state.Values) + err := state.load() + switch { + case errors.Is(err, os.ErrNotExist): + case err != nil: + return nil, err + } + return state, nil +} + +// load loads state from disk. It does not take out a lock, since it is called +// during construction. +func (s *State) load() error { + bz, err := ioutil.ReadFile(s.file) + if err != nil { + return fmt.Errorf("failed to read state from %q: %w", s.file, err) + } + err = json.Unmarshal(bz, s) + if err != nil { + return fmt.Errorf("invalid state data in %q: %w", s.file, err) + } + return nil +} + +// save saves the state to disk. It does not take out a lock since it is called +// internally by Commit which does lock. +func (s *State) save() error { + bz, err := json.Marshal(s) + if err != nil { + return fmt.Errorf("failed to marshal state: %w", err) + } + // We write the state to a separate file and move it to the destination, to + // make it atomic. + newFile := fmt.Sprintf("%v.new", s.file) + err = ioutil.WriteFile(newFile, bz, 0644) + if err != nil { + return fmt.Errorf("failed to write state to %q: %w", s.file, err) + } + return os.Rename(newFile, s.file) +} + +// Export exports key/value pairs as JSON, used for state sync snapshots. +func (s *State) Export() ([]byte, error) { + s.RLock() + defer s.RUnlock() + return json.Marshal(s.Values) +} + +// Import imports key/value pairs from JSON bytes, used for InitChain.AppStateBytes and +// state sync snapshots. It also saves the state once imported. +func (s *State) Import(height uint64, jsonBytes []byte) error { + s.Lock() + defer s.Unlock() + values := map[string]string{} + err := json.Unmarshal(jsonBytes, &values) + if err != nil { + return fmt.Errorf("failed to decode imported JSON data: %w", err) + } + s.Height = height + s.Values = values + s.Hash = hashItems(values) + return s.save() +} + +// Get fetches a value. A missing value is returned as an empty string. +func (s *State) Get(key string) string { + s.RLock() + defer s.RUnlock() + return s.Values[key] +} + +// Set sets a value. Setting an empty value is equivalent to deleting it. +func (s *State) Set(key, value string) { + s.Lock() + defer s.Unlock() + if value == "" { + delete(s.Values, key) + } else { + s.Values[key] = value + } +} + +// Commit commits the current state. +func (s *State) Commit() (uint64, []byte, error) { + s.Lock() + defer s.Unlock() + s.Hash = hashItems(s.Values) + switch { + case s.Height > 0: + s.Height++ + case s.initialHeight > 0: + s.Height = s.initialHeight + default: + s.Height = 1 + } + if s.persistInterval > 0 && s.Height%s.persistInterval == 0 { + err := s.save() + if err != nil { + return 0, nil, err + } + } + return s.Height, s.Hash, nil +} + +// hashItems hashes a set of key/value items. +func hashItems(items map[string]string) []byte { + keys := make([]string, 0, len(items)) + for key := range items { + keys = append(keys, key) + } + sort.Strings(keys) + + hasher := sha256.New() + for _, key := range keys { + _, _ = hasher.Write([]byte(key)) + _, _ = hasher.Write([]byte{0}) + _, _ = hasher.Write([]byte(items[key])) + _, _ = hasher.Write([]byte{0}) + } + return hasher.Sum(nil) +} diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile new file mode 100644 index 0000000000..ba0e51be41 --- /dev/null +++ b/test/e2e/docker/Dockerfile @@ -0,0 +1,32 @@ +# We need to build in a Linux environment to support C libraries, e.g. RocksDB. +# We use Debian instead of Alpine, so that we can use binary database packages +# instead of spending time compiling them. +FROM golang:1.15 + +RUN apt-get update -y && apt-get upgrade -y +RUN apt-get install -y libleveldb-dev librocksdb-dev + +# Set up build directory /src/tendermint +ENV TENDERMINT_BUILD_OPTIONS badgerdb,boltdb,cleveldb,rocksdb +WORKDIR /src/tendermint + +# Fetch dependencies separately (for layer caching) +COPY go.mod go.sum ./ +RUN go mod download + +# Build Tendermint and install into /usr/bin/tendermint +COPY . . +RUN make build && cp build/tendermint /usr/bin/tendermint +COPY test/e2e/docker/entrypoint* /usr/bin/ +RUN cd test/e2e && make app && cp build/app /usr/bin/app + +# Set up runtime directory. We don't use a separate runtime image since we need +# e.g. leveldb and rocksdb which are already installed in the build image. +WORKDIR /tendermint +VOLUME /tendermint +ENV TMHOME=/tendermint + +EXPOSE 26656 26657 26660 +ENTRYPOINT ["/usr/bin/entrypoint"] +CMD ["node"] +STOPSIGNAL SIGTERM diff --git a/test/e2e/docker/entrypoint b/test/e2e/docker/entrypoint new file mode 100755 index 0000000000..50d57a3134 --- /dev/null +++ b/test/e2e/docker/entrypoint @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Forcibly remove any stray UNIX sockets left behind from previous runs +rm -rf /var/run/privval.sock /var/run/app.sock + +/usr/bin/app /tendermint/config/app.toml & + +sleep 1 + +/usr/bin/tendermint "$@" diff --git a/test/e2e/docker/entrypoint-builtin b/test/e2e/docker/entrypoint-builtin new file mode 100755 index 0000000000..3bec086711 --- /dev/null +++ b/test/e2e/docker/entrypoint-builtin @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Forcibly remove any stray UNIX sockets left behind from previous runs +rm -rf /var/run/privval.sock /var/run/app.sock + +/usr/bin/app /tendermint/config/app.toml diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml new file mode 100644 index 0000000000..94ddecb02b --- /dev/null +++ b/test/e2e/networks/ci.toml @@ -0,0 +1,97 @@ +# This testnet is (will be) run by CI, and attempts to cover a broad range of +# functionality with a single network. + +initial_height = 1000 +initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } + +[validators] +validator01 = 100 + +[validator_update.0] +validator01 = 10 +validator02 = 20 +validator03 = 30 +validator04 = 40 + +[validator_update.1010] +validator05 = 50 + +# validator03 gets killed and validator05 has lots of perturbations, so weight them low. +[validator_update.1020] +validator01 = 100 +validator02 = 100 +validator03 = 50 +validator04 = 100 +validator05 = 50 + +[node.seed01] +mode = "seed" + +[node.validator01] +seeds = ["seed01"] +snapshot_interval = 5 +perturb = ["disconnect"] + +[node.validator02] +seeds = ["seed01"] +database = "boltdb" +abci_protocol = "tcp" +privval_protocol = "tcp" +persist_interval = 0 +# FIXME The WAL gets corrupted when restarted +# https://github.com/tendermint/tendermint/issues/5422 +#perturb = ["restart"] + +[node.validator03] +seeds = ["seed01"] +database = "badgerdb" +# FIXME Should use grpc, but it has race conditions +# https://github.com/tendermint/tendermint/issues/5439 +abci_protocol = "unix" +privval_protocol = "unix" +persist_interval = 3 +retain_blocks = 3 +# FIXME The WAL gets corrupted when killed +# https://github.com/tendermint/tendermint/issues/5422 +#perturb = ["kill"] + +[node.validator04] +persistent_peers = ["validator01"] +database = "rocksdb" +abci_protocol = "builtin" +retain_blocks = 1 +perturb = ["pause"] + +[node.validator05] +start_at = 1005 # Becomes part of the validator set at 1010 +seeds = ["seed01"] +database = "cleveldb" +fast_sync = "v0" +# FIXME Should use grpc, but it has race conditions +# https://github.com/tendermint/tendermint/issues/5439 +abci_protocol = "tcp" +privval_protocol = "tcp" +# FIXME The WAL gets corrupted when killed +# https://github.com/tendermint/tendermint/issues/5422 +#perturb = ["kill", "pause", "disconnect", "restart"] + +[node.full01] +start_at = 1010 +mode = "full" +# FIXME Should use v1, but it won't catch up since some nodes don't have all blocks +# https://github.com/tendermint/tendermint/issues/5444 +fast_sync = "v2" +persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] +# FIXME The WAL gets corrupted when restarted +# https://github.com/tendermint/tendermint/issues/5422 +#perturb = ["restart"] + +[node.full02] +start_at = 1015 +mode = "full" +fast_sync = "v2" +state_sync = true +seeds = ["seed01"] +# FIXME The WAL gets corrupted when restarted +# https://github.com/tendermint/tendermint/issues/5422 +#perturb = ["restart"] diff --git a/test/e2e/networks/simple.toml b/test/e2e/networks/simple.toml new file mode 100644 index 0000000000..96b81f79fe --- /dev/null +++ b/test/e2e/networks/simple.toml @@ -0,0 +1,4 @@ +[node.validator01] +[node.validator02] +[node.validator03] +[node.validator04] diff --git a/test/e2e/networks/single.toml b/test/e2e/networks/single.toml new file mode 100644 index 0000000000..54c40b19e4 --- /dev/null +++ b/test/e2e/networks/single.toml @@ -0,0 +1 @@ +[node.validator] diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go new file mode 100644 index 0000000000..2e06347119 --- /dev/null +++ b/test/e2e/pkg/manifest.go @@ -0,0 +1,127 @@ +package e2e + +import ( + "fmt" + + "github.com/BurntSushi/toml" +) + +// Manifest represents a TOML testnet manifest. +type Manifest struct { + // IPv6 uses IPv6 networking instead of IPv4. Defaults to IPv4. + IPv6 bool `toml:"ipv6"` + + // InitialHeight specifies the initial block height, set in genesis. Defaults to 1. + InitialHeight int64 `toml:"initial_height"` + + // InitialState is an initial set of key/value pairs for the application, + // set in genesis. Defaults to nothing. + InitialState map[string]string `toml:"initial_state"` + + // Validators is the initial validator set in genesis, given as node names + // and power: + // + // validators = { validator01 = 10; validator02 = 20; validator03 = 30 } + // + // Defaults to all nodes that have mode=validator at power 100. Explicitly + // specifying an empty set will start with no validators in genesis, and + // the application must return the validator set in InitChain via the + // setting validator_update.0 (see below). + Validators *map[string]int64 + + // ValidatorUpdates is a map of heights to validator names and their power, + // and will be returned by the ABCI application. For example, the following + // changes the power of validator01 and validator02 at height 1000: + // + // [validator_update.1000] + // validator01 = 20 + // validator02 = 10 + // + // Specifying height 0 returns the validator update during InitChain. The + // application returns the validator updates as-is, i.e. removing a + // validator must be done by returning it with power 0, and any validators + // not specified are not changed. + ValidatorUpdates map[string]map[string]int64 `toml:"validator_update"` + + // Nodes specifies the network nodes. At least one node must be given. + Nodes map[string]ManifestNode `toml:"node"` +} + +// ManifestNode represents a node in a testnet manifest. +type ManifestNode struct { + // Mode specifies the type of node: "validator", "full", or "seed". Defaults to + // "validator". Full nodes do not get a signing key (a dummy key is generated), + // and seed nodes run in seed mode with the PEX reactor enabled. + Mode string + + // Seeds is the list of node names to use as P2P seed nodes. Defaults to none. + Seeds []string + + // PersistentPeers is a list of node names to maintain persistent P2P + // connections to. If neither seeds nor persistent peers are specified, + // this defaults to all other nodes in the network. + PersistentPeers []string `toml:"persistent_peers"` + + // Database specifies the database backend: "goleveldb", "cleveldb", + // "rocksdb", "boltdb", or "badgerdb". Defaults to goleveldb. + Database string + + // ABCIProtocol specifies the protocol used to communicate with the ABCI + // application: "unix", "tcp", "grpc", or "builtin". Defaults to unix. + // builtin will build a complete Tendermint node into the application and + // launch it instead of launching a separate Tendermint process. + ABCIProtocol string `toml:"abci_protocol"` + + // PrivvalProtocol specifies the protocol used to sign consensus messages: + // "file", "unix", or "tcp". Defaults to "file". For unix and tcp, the ABCI + // application will launch a remote signer client in a separate goroutine. + // Only nodes with mode=validator will actually make use of this. + PrivvalProtocol string `toml:"privval_protocol"` + + // StartAt specifies the block height at which the node will be started. The + // runner will wait for the network to reach at least this block height. + StartAt int64 `toml:"start_at"` + + // FastSync specifies the fast sync mode: "" (disable), "v0", "v1", or "v2". + // Defaults to disabled. + FastSync string `toml:"fast_sync"` + + // StateSync enables state sync. The runner automatically configures trusted + // block hashes and RPC servers. At least one node in the network must have + // SnapshotInterval set to non-zero, and the state syncing node must have + // StartAt set to an appropriate height where a snapshot is available. + StateSync bool `toml:"state_sync"` + + // PersistInterval specifies the height interval at which the application + // will persist state to disk. Defaults to 1 (every height), setting this to + // 0 disables state persistence. + PersistInterval *uint64 `toml:"persist_interval"` + + // SnapshotInterval specifies the height interval at which the application + // will take state sync snapshots. Defaults to 0 (disabled). + SnapshotInterval uint64 `toml:"snapshot_interval"` + + // RetainBlocks specifies the number of recent blocks to retain. Defaults to + // 0, which retains all blocks. Must be greater that PersistInterval and + // SnapshotInterval. + RetainBlocks uint64 `toml:"retain_blocks"` + + // Perturb lists perturbations to apply to the node after it has been + // started and synced with the network: + // + // disconnect: temporarily disconnects the node from the network + // kill: kills the node with SIGKILL then restarts it + // pause: temporarily pauses (freezes) the node + // restart: restarts the node, shutting it down with SIGTERM + Perturb []string +} + +// LoadManifest loads a testnet manifest from a file. +func LoadManifest(file string) (Manifest, error) { + manifest := Manifest{} + _, err := toml.DecodeFile(file, &manifest) + if err != nil { + return manifest, fmt.Errorf("failed to load testnet manifest %q: %w", file, err) + } + return manifest, nil +} diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go new file mode 100644 index 0000000000..5dda076bc2 --- /dev/null +++ b/test/e2e/pkg/testnet.go @@ -0,0 +1,470 @@ +//nolint: gosec +package e2e + +import ( + "errors" + "fmt" + "io" + "math/rand" + "net" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" +) + +const ( + randomSeed int64 = 2308084734268 + proxyPortFirst uint32 = 5701 + networkIPv4 = "10.186.73.0/24" + networkIPv6 = "fd80:b10c::/48" +) + +type Mode string +type Protocol string +type Perturbation string + +const ( + ModeValidator Mode = "validator" + ModeFull Mode = "full" + ModeSeed Mode = "seed" + + ProtocolBuiltin Protocol = "builtin" + ProtocolFile Protocol = "file" + ProtocolGRPC Protocol = "grpc" + ProtocolTCP Protocol = "tcp" + ProtocolUNIX Protocol = "unix" + + PerturbationDisconnect Perturbation = "disconnect" + PerturbationKill Perturbation = "kill" + PerturbationPause Perturbation = "pause" + PerturbationRestart Perturbation = "restart" +) + +// Testnet represents a single testnet. +type Testnet struct { + Name string + File string + Dir string + IP *net.IPNet + InitialHeight int64 + InitialState map[string]string + Validators map[*Node]int64 + ValidatorUpdates map[int64]map[*Node]int64 + Nodes []*Node +} + +// Node represents a Tendermint node in a testnet. +type Node struct { + Name string + Testnet *Testnet + Mode Mode + Key crypto.PrivKey + IP net.IP + ProxyPort uint32 + StartAt int64 + FastSync string + StateSync bool + Database string + ABCIProtocol Protocol + PrivvalProtocol Protocol + PersistInterval uint64 + SnapshotInterval uint64 + RetainBlocks uint64 + Seeds []*Node + PersistentPeers []*Node + Perturbations []Perturbation +} + +// LoadTestnet loads a testnet from a manifest file, using the filename to +// determine the testnet name and directory (from the basename of the file). +// The testnet generation must be deterministic, since it is generated +// separately by the runner and the test cases. For this reason, testnets use a +// random seed to generate e.g. keys. +func LoadTestnet(file string) (*Testnet, error) { + manifest, err := LoadManifest(file) + if err != nil { + return nil, err + } + dir := strings.TrimSuffix(file, filepath.Ext(file)) + + // Set up resource generators. These must be deterministic. + netAddress := networkIPv4 + if manifest.IPv6 { + netAddress = networkIPv6 + } + _, ipNet, err := net.ParseCIDR(netAddress) + if err != nil { + return nil, fmt.Errorf("invalid IP network address %q: %w", netAddress, err) + } + + ipGen := newIPGenerator(ipNet) + keyGen := newKeyGenerator(randomSeed) + proxyPortGen := newPortGenerator(proxyPortFirst) + + testnet := &Testnet{ + Name: filepath.Base(dir), + File: file, + Dir: dir, + IP: ipGen.Network(), + InitialHeight: 1, + InitialState: manifest.InitialState, + Validators: map[*Node]int64{}, + ValidatorUpdates: map[int64]map[*Node]int64{}, + Nodes: []*Node{}, + } + if manifest.InitialHeight > 0 { + testnet.InitialHeight = manifest.InitialHeight + } + + // Set up nodes, in alphabetical order (IPs and ports get same order). + nodeNames := []string{} + for name := range manifest.Nodes { + nodeNames = append(nodeNames, name) + } + sort.Strings(nodeNames) + + for _, name := range nodeNames { + nodeManifest := manifest.Nodes[name] + node := &Node{ + Name: name, + Testnet: testnet, + Key: keyGen.Generate(), + IP: ipGen.Next(), + ProxyPort: proxyPortGen.Next(), + Mode: ModeValidator, + Database: "goleveldb", + ABCIProtocol: ProtocolUNIX, + PrivvalProtocol: ProtocolFile, + StartAt: nodeManifest.StartAt, + FastSync: nodeManifest.FastSync, + StateSync: nodeManifest.StateSync, + PersistInterval: 1, + SnapshotInterval: nodeManifest.SnapshotInterval, + RetainBlocks: nodeManifest.RetainBlocks, + Perturbations: []Perturbation{}, + } + if nodeManifest.Mode != "" { + node.Mode = Mode(nodeManifest.Mode) + } + if nodeManifest.Database != "" { + node.Database = nodeManifest.Database + } + if nodeManifest.ABCIProtocol != "" { + node.ABCIProtocol = Protocol(nodeManifest.ABCIProtocol) + } + if nodeManifest.PrivvalProtocol != "" { + node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol) + } + if nodeManifest.PersistInterval != nil { + node.PersistInterval = *nodeManifest.PersistInterval + } + for _, p := range nodeManifest.Perturb { + node.Perturbations = append(node.Perturbations, Perturbation(p)) + } + testnet.Nodes = append(testnet.Nodes, node) + } + + // We do a second pass to set up seeds and persistent peers, which allows graph cycles. + for _, node := range testnet.Nodes { + nodeManifest, ok := manifest.Nodes[node.Name] + if !ok { + return nil, fmt.Errorf("failed to look up manifest for node %q", node.Name) + } + for _, seedName := range nodeManifest.Seeds { + seed := testnet.LookupNode(seedName) + if seed == nil { + return nil, fmt.Errorf("unknown seed %q for node %q", seedName, node.Name) + } + node.Seeds = append(node.Seeds, seed) + } + for _, peerName := range nodeManifest.PersistentPeers { + peer := testnet.LookupNode(peerName) + if peer == nil { + return nil, fmt.Errorf("unknown persistent peer %q for node %q", peerName, node.Name) + } + node.PersistentPeers = append(node.PersistentPeers, peer) + } + + // If there are no seeds or persistent peers specified, default to persistent + // connections to all other nodes. + if len(node.PersistentPeers) == 0 && len(node.Seeds) == 0 { + for _, peer := range testnet.Nodes { + if peer.Name == node.Name { + continue + } + node.PersistentPeers = append(node.PersistentPeers, peer) + } + } + } + + // Set up genesis validators. If not specified explicitly, use all validator nodes. + if manifest.Validators != nil { + for validatorName, power := range *manifest.Validators { + validator := testnet.LookupNode(validatorName) + if validator == nil { + return nil, fmt.Errorf("unknown validator %q", validatorName) + } + testnet.Validators[validator] = power + } + } else { + for _, node := range testnet.Nodes { + if node.Mode == ModeValidator { + testnet.Validators[node] = 100 + } + } + } + + // Set up validator updates. + for heightStr, validators := range manifest.ValidatorUpdates { + height, err := strconv.Atoi(heightStr) + if err != nil { + return nil, fmt.Errorf("invalid validator update height %q: %w", height, err) + } + valUpdate := map[*Node]int64{} + for name, power := range validators { + node := testnet.LookupNode(name) + if node == nil { + return nil, fmt.Errorf("unknown validator %q for update at height %v", name, height) + } + valUpdate[node] = power + } + testnet.ValidatorUpdates[int64(height)] = valUpdate + } + + return testnet, testnet.Validate() +} + +// Validate validates a testnet. +func (t Testnet) Validate() error { + if t.Name == "" { + return errors.New("network has no name") + } + if t.IP == nil { + return errors.New("network has no IP") + } + if len(t.Nodes) == 0 { + return errors.New("network has no nodes") + } + for _, node := range t.Nodes { + if err := node.Validate(t); err != nil { + return fmt.Errorf("invalid node %q: %w", node.Name, err) + } + } + return nil +} + +// Validate validates a node. +func (n Node) Validate(testnet Testnet) error { + if n.Name == "" { + return errors.New("node has no name") + } + if n.IP == nil { + return errors.New("node has no IP address") + } + if !testnet.IP.Contains(n.IP) { + return fmt.Errorf("node IP %v is not in testnet network %v", n.IP, testnet.IP) + } + if n.ProxyPort > 0 { + if n.ProxyPort <= 1024 { + return fmt.Errorf("local port %v must be >1024", n.ProxyPort) + } + for _, peer := range testnet.Nodes { + if peer.Name != n.Name && peer.ProxyPort == n.ProxyPort { + return fmt.Errorf("peer %q also has local port %v", peer.Name, n.ProxyPort) + } + } + } + switch n.FastSync { + case "", "v0", "v1", "v2": + default: + return fmt.Errorf("invalid fast sync setting %q", n.FastSync) + } + switch n.Database { + case "goleveldb", "cleveldb", "boltdb", "rocksdb", "badgerdb": + default: + return fmt.Errorf("invalid database setting %q", n.Database) + } + switch n.ABCIProtocol { + case ProtocolBuiltin, ProtocolUNIX, ProtocolTCP, ProtocolGRPC: + default: + return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol) + } + switch n.PrivvalProtocol { + case ProtocolFile, ProtocolUNIX, ProtocolTCP: + default: + return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol) + } + + if n.StateSync && n.StartAt == 0 { + return errors.New("state synced nodes cannot start at the initial height") + } + if n.PersistInterval == 0 && n.RetainBlocks > 0 { + return errors.New("persist_interval=0 requires retain_blocks=0") + } + if n.PersistInterval > 1 && n.RetainBlocks > 0 && n.RetainBlocks < n.PersistInterval { + return errors.New("persist_interval must be less than or equal to retain_blocks") + } + if n.SnapshotInterval > 0 && n.RetainBlocks > 0 && n.RetainBlocks < n.SnapshotInterval { + return errors.New("snapshot_interval must be less than er equal to retain_blocks") + } + + for _, perturbation := range n.Perturbations { + switch perturbation { + case PerturbationDisconnect, PerturbationKill, PerturbationPause, PerturbationRestart: + default: + return fmt.Errorf("invalid perturbation %q", perturbation) + } + } + return nil +} + +// LookupNode looks up a node by name. For now, simply do a linear search. +func (t Testnet) LookupNode(name string) *Node { + for _, node := range t.Nodes { + if node.Name == name { + return node + } + } + return nil +} + +// ArchiveNodes returns a list of archive nodes that start at the initial height +// and contain the entire blockchain history. They are used e.g. as light client +// RPC servers. +func (t Testnet) ArchiveNodes() []*Node { + nodes := []*Node{} + for _, node := range t.Nodes { + if node.Mode != ModeSeed && node.StartAt == 0 && node.RetainBlocks == 0 { + nodes = append(nodes, node) + } + } + return nodes +} + +// RandomNode returns a random non-seed node. +func (t Testnet) RandomNode() *Node { + for { + node := t.Nodes[rand.Intn(len(t.Nodes))] + if node.Mode != ModeSeed { + return node + } + } +} + +// IPv6 returns true if the testnet is an IPv6 network. +func (t Testnet) IPv6() bool { + return t.IP.IP.To4() == nil +} + +// Address returns a P2P endpoint address for the node. +func (n Node) AddressP2P(withID bool) string { + ip := n.IP.String() + if n.IP.To4() == nil { + // IPv6 addresses must be wrapped in [] to avoid conflict with : port separator + ip = fmt.Sprintf("[%v]", ip) + } + addr := fmt.Sprintf("%v:26656", ip) + if withID { + addr = fmt.Sprintf("%x@%v", n.Key.PubKey().Address().Bytes(), addr) + } + return addr +} + +// Address returns an RPC endpoint address for the node. +func (n Node) AddressRPC() string { + ip := n.IP.String() + if n.IP.To4() == nil { + // IPv6 addresses must be wrapped in [] to avoid conflict with : port separator + ip = fmt.Sprintf("[%v]", ip) + } + return fmt.Sprintf("%v:26657", ip) +} + +// Client returns an RPC client for a node. +func (n Node) Client() (*rpchttp.HTTP, error) { + return rpchttp.New(fmt.Sprintf("http://127.0.0.1:%v", n.ProxyPort), "/websocket") +} + +// keyGenerator generates pseudorandom Ed25519 keys based on a seed. +type keyGenerator struct { + random *rand.Rand +} + +func newKeyGenerator(seed int64) *keyGenerator { + return &keyGenerator{ + random: rand.New(rand.NewSource(seed)), + } +} + +func (g *keyGenerator) Generate() crypto.PrivKey { + seed := make([]byte, ed25519.SeedSize) + + _, err := io.ReadFull(g.random, seed) + if err != nil { + panic(err) // this shouldn't happen + } + + return ed25519.GenPrivKeyFromSecret(seed) +} + +// portGenerator generates local Docker proxy ports for each node. +type portGenerator struct { + nextPort uint32 +} + +func newPortGenerator(firstPort uint32) *portGenerator { + return &portGenerator{nextPort: firstPort} +} + +func (g *portGenerator) Next() uint32 { + port := g.nextPort + g.nextPort++ + if g.nextPort == 0 { + panic("port overflow") + } + return port +} + +// ipGenerator generates sequential IP addresses for each node, using a random +// network address. +type ipGenerator struct { + network *net.IPNet + nextIP net.IP +} + +func newIPGenerator(network *net.IPNet) *ipGenerator { + nextIP := make([]byte, len(network.IP)) + copy(nextIP, network.IP) + gen := &ipGenerator{network: network, nextIP: nextIP} + // Skip network and gateway addresses + gen.Next() + gen.Next() + return gen +} + +func (g *ipGenerator) Network() *net.IPNet { + n := &net.IPNet{ + IP: make([]byte, len(g.network.IP)), + Mask: make([]byte, len(g.network.Mask)), + } + copy(n.IP, g.network.IP) + copy(n.Mask, g.network.Mask) + return n +} + +func (g *ipGenerator) Next() net.IP { + ip := make([]byte, len(g.nextIP)) + copy(ip, g.nextIP) + for i := len(g.nextIP) - 1; i >= 0; i-- { + g.nextIP[i]++ + if g.nextIP[i] != 0 { + break + } + } + return ip +} diff --git a/test/e2e/runner/cleanup.go b/test/e2e/runner/cleanup.go new file mode 100644 index 0000000000..3f7eff3e68 --- /dev/null +++ b/test/e2e/runner/cleanup.go @@ -0,0 +1,35 @@ +package main + +import ( + "errors" + "fmt" + "os" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Cleanup removes the Docker Compose containers and testnet directory. +func Cleanup(testnet *e2e.Testnet) error { + if testnet.Dir == "" { + return errors.New("no directory set") + } + _, err := os.Stat(testnet.Dir) + if os.IsNotExist(err) { + return nil + } else if err != nil { + return err + } + + logger.Info("Removing Docker containers and networks") + err = execCompose(testnet.Dir, "down") + if err != nil { + return err + } + + logger.Info(fmt.Sprintf("Removing testnet directory %q", testnet.Dir)) + err = os.RemoveAll(testnet.Dir) + if err != nil { + return err + } + return nil +} diff --git a/test/e2e/runner/exec.go b/test/e2e/runner/exec.go new file mode 100644 index 0000000000..f790f7fc15 --- /dev/null +++ b/test/e2e/runner/exec.go @@ -0,0 +1,50 @@ +//nolint: gosec +package main + +import ( + "fmt" + "os" + osexec "os/exec" + "path/filepath" +) + +// execute executes a shell command. +func exec(args ...string) error { + cmd := osexec.Command(args[0], args[1:]...) + out, err := cmd.CombinedOutput() + switch err := err.(type) { + case nil: + return nil + case *osexec.ExitError: + return fmt.Errorf("failed to run %q:\n%v", args, string(out)) + default: + return err + } +} + +// execVerbose executes a shell command while displaying its output. +func execVerbose(args ...string) error { + cmd := osexec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// execCompose runs a Docker Compose command for a testnet. +func execCompose(dir string, args ...string) error { + return exec(append( + []string{"docker-compose", "-f", filepath.Join(dir, "docker-compose.yml")}, + args...)...) +} + +// execComposeVerbose runs a Docker Compose command for a testnet and displays its output. +func execComposeVerbose(dir string, args ...string) error { + return execVerbose(append( + []string{"docker-compose", "-f", filepath.Join(dir, "docker-compose.yml")}, + args...)...) +} + +// execDocker runs a Docker command. +func execDocker(args ...string) error { + return exec(append([]string{"docker"}, args...)...) +} diff --git a/test/e2e/runner/load.go b/test/e2e/runner/load.go new file mode 100644 index 0000000000..a59b3f5032 --- /dev/null +++ b/test/e2e/runner/load.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "crypto/rand" + "errors" + "fmt" + "math" + "time" + + rpchttp "github.com/tendermint/tendermint/rpc/client/http" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" +) + +// Load generates transactions against the network until the given +// context is cancelled. +func Load(ctx context.Context, testnet *e2e.Testnet) error { + concurrency := 50 + initialTimeout := 1 * time.Minute + stallTimeout := 15 * time.Second + + chTx := make(chan types.Tx) + chSuccess := make(chan types.Tx) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Spawn job generator and processors. + logger.Info("Starting transaction load...") + started := time.Now() + + go loadGenerate(ctx, chTx) + + for w := 0; w < concurrency; w++ { + go loadProcess(ctx, testnet, chTx, chSuccess) + } + + // Monitor successful transactions, and abort on stalls. + success := 0 + timeout := initialTimeout + for { + select { + case <-chSuccess: + success++ + timeout = stallTimeout + case <-time.After(timeout): + return fmt.Errorf("unable to submit transactions for %v", timeout) + case <-ctx.Done(): + if success == 0 { + return errors.New("failed to submit any transactions") + } + logger.Info(fmt.Sprintf("Ending transaction load after %v txs (%.1f tx/s)...", + success, float64(success)/time.Since(started).Seconds())) + return nil + } + } +} + +// loadGenerate generates jobs until the context is cancelled +func loadGenerate(ctx context.Context, chTx chan<- types.Tx) { + for i := 0; i < math.MaxInt64; i++ { + // We keep generating the same 1000 keys over and over, with different values. + // This gives a reasonable load without putting too much data in the app. + id := i % 1000 + + bz := make([]byte, 2048) // 4kb hex-encoded + _, err := rand.Read(bz) + if err != nil { + panic(fmt.Sprintf("Failed to read random bytes: %v", err)) + } + tx := types.Tx(fmt.Sprintf("load-%X=%x", id, bz)) + + select { + case chTx <- tx: + time.Sleep(10 * time.Millisecond) + case <-ctx.Done(): + close(chTx) + return + } + } +} + +// loadProcess processes transactions +func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx, chSuccess chan<- types.Tx) { + // Each worker gets its own client to each node, which allows for some + // concurrency while still bounding it. + clients := map[string]*rpchttp.HTTP{} + + var err error + for tx := range chTx { + node := testnet.RandomNode() + client, ok := clients[node.Name] + if !ok { + client, err = node.Client() + if err != nil { + continue + } + clients[node.Name] = client + } + _, err = client.BroadcastTxCommit(ctx, tx) + if err != nil { + continue + } + chSuccess <- tx + } +} diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go new file mode 100644 index 0000000000..b01b208f83 --- /dev/null +++ b/test/e2e/runner/main.go @@ -0,0 +1,184 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/log" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + +func main() { + NewCLI().Run() +} + +// CLI is the Cobra-based command-line interface. +type CLI struct { + root *cobra.Command + testnet *e2e.Testnet +} + +// NewCLI sets up the CLI. +func NewCLI() *CLI { + cli := &CLI{} + cli.root = &cobra.Command{ + Use: "runner", + Short: "End-to-end test runner", + SilenceUsage: true, + SilenceErrors: true, // we'll output them ourselves in Run() + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + file, err := cmd.Flags().GetString("file") + if err != nil { + return err + } + testnet, err := e2e.LoadTestnet(file) + if err != nil { + return err + } + + cli.testnet = testnet + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := Cleanup(cli.testnet); err != nil { + return err + } + if err := Setup(cli.testnet); err != nil { + return err + } + + chLoadResult := make(chan error) + ctx, loadCancel := context.WithCancel(context.Background()) + defer loadCancel() + go func() { + err := Load(ctx, cli.testnet) + if err != nil { + logger.Error(fmt.Sprintf("Transaction load failed: %v", err.Error())) + } + chLoadResult <- err + }() + + if err := Start(cli.testnet); err != nil { + return err + } + if err := Perturb(cli.testnet); err != nil { + return err + } + if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle + return err + } + + loadCancel() + if err := <-chLoadResult; err != nil { + return err + } + if err := Wait(cli.testnet, 3); err != nil { // wait for last txs to commit + return err + } + if err := Test(cli.testnet); err != nil { + return err + } + if err := Cleanup(cli.testnet); err != nil { + return err + } + return nil + }, + } + + cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest") + _ = cli.root.MarkPersistentFlagRequired("file") + + cli.root.AddCommand(&cobra.Command{ + Use: "setup", + Short: "Generates the testnet directory and configuration", + RunE: func(cmd *cobra.Command, args []string) error { + return Setup(cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "start", + Short: "Starts the Docker testnet, waiting for nodes to become available", + RunE: func(cmd *cobra.Command, args []string) error { + _, err := os.Stat(cli.testnet.Dir) + if os.IsNotExist(err) { + err = Setup(cli.testnet) + } + if err != nil { + return err + } + return Start(cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "perturb", + Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes", + RunE: func(cmd *cobra.Command, args []string) error { + return Perturb(cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "wait", + Short: "Waits for a few blocks to be produced and all nodes to catch up", + RunE: func(cmd *cobra.Command, args []string) error { + return Wait(cli.testnet, 5) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "stop", + Short: "Stops the Docker testnet", + RunE: func(cmd *cobra.Command, args []string) error { + logger.Info("Stopping testnet") + return execCompose(cli.testnet.Dir, "down") + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "load", + Short: "Generates transaction load until the command is cancelled", + RunE: func(cmd *cobra.Command, args []string) error { + return Load(context.Background(), cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "test", + Short: "Runs test cases against a running testnet", + RunE: func(cmd *cobra.Command, args []string) error { + return Test(cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "cleanup", + Short: "Removes the testnet directory", + RunE: func(cmd *cobra.Command, args []string) error { + return Cleanup(cli.testnet) + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "logs", + Short: "Shows the testnet logs", + RunE: func(cmd *cobra.Command, args []string) error { + return execComposeVerbose(cli.testnet.Dir, "logs", "--follow") + }, + }) + + return cli +} + +// Run runs the CLI. +func (cli *CLI) Run() { + if err := cli.root.Execute(); err != nil { + logger.Error(err.Error()) + os.Exit(1) + } +} diff --git a/test/e2e/runner/perturb.go b/test/e2e/runner/perturb.go new file mode 100644 index 0000000000..5194b70a6e --- /dev/null +++ b/test/e2e/runner/perturb.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "time" + + rpctypes "github.com/tendermint/tendermint/rpc/core/types" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Perturbs a running testnet. +func Perturb(testnet *e2e.Testnet) error { + for _, node := range testnet.Nodes { + for _, perturbation := range node.Perturbations { + _, err := PerturbNode(node, perturbation) + if err != nil { + return err + } + time.Sleep(3 * time.Second) // give network some time to recover between each + } + } + return nil +} + +// PerturbNode perturbs a node with a given perturbation, returning its status +// after recovering. +func PerturbNode(node *e2e.Node, perturbation e2e.Perturbation) (*rpctypes.ResultStatus, error) { + testnet := node.Testnet + switch perturbation { + case e2e.PerturbationDisconnect: + logger.Info(fmt.Sprintf("Disconnecting node %v...", node.Name)) + if err := execDocker("network", "disconnect", testnet.Name+"_"+testnet.Name, node.Name); err != nil { + return nil, err + } + time.Sleep(10 * time.Second) + if err := execDocker("network", "connect", testnet.Name+"_"+testnet.Name, node.Name); err != nil { + return nil, err + } + + case e2e.PerturbationKill: + logger.Info(fmt.Sprintf("Killing node %v...", node.Name)) + if err := execCompose(testnet.Dir, "kill", "-s", "SIGKILL", node.Name); err != nil { + return nil, err + } + if err := execCompose(testnet.Dir, "start", node.Name); err != nil { + return nil, err + } + + case e2e.PerturbationPause: + logger.Info(fmt.Sprintf("Pausing node %v...", node.Name)) + if err := execCompose(testnet.Dir, "pause", node.Name); err != nil { + return nil, err + } + time.Sleep(10 * time.Second) + if err := execCompose(testnet.Dir, "unpause", node.Name); err != nil { + return nil, err + } + + case e2e.PerturbationRestart: + logger.Info(fmt.Sprintf("Restarting node %v...", node.Name)) + if err := execCompose(testnet.Dir, "restart", node.Name); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unexpected perturbation %q", perturbation) + } + + status, err := waitForNode(node, 0, 10*time.Second) + if err != nil { + return nil, err + } + logger.Info(fmt.Sprintf("Node %v recovered at height %v", node.Name, status.SyncInfo.LatestBlockHeight)) + return status, nil +} diff --git a/test/e2e/runner/rpc.go b/test/e2e/runner/rpc.go new file mode 100644 index 0000000000..82b1a8ec53 --- /dev/null +++ b/test/e2e/runner/rpc.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "errors" + "fmt" + "time" + + rpchttp "github.com/tendermint/tendermint/rpc/client/http" + rpctypes "github.com/tendermint/tendermint/rpc/core/types" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" +) + +// waitForHeight waits for the network to reach a certain height (or above), +// returning the highest height seen. Errors if the network is not making +// progress at all. +func waitForHeight(testnet *e2e.Testnet, height int64) (*types.Block, *types.BlockID, error) { + var ( + err error + maxResult *rpctypes.ResultBlock + clients = map[string]*rpchttp.HTTP{} + lastIncrease = time.Now() + ) + + for { + for _, node := range testnet.Nodes { + if node.Mode == e2e.ModeSeed { + continue + } + client, ok := clients[node.Name] + if !ok { + client, err = node.Client() + if err != nil { + continue + } + clients[node.Name] = client + } + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + result, err := client.Block(ctx, nil) + if err != nil { + continue + } + if result.Block != nil && (maxResult == nil || result.Block.Height >= maxResult.Block.Height) { + maxResult = result + lastIncrease = time.Now() + } + if maxResult != nil && maxResult.Block.Height >= height { + return maxResult.Block, &maxResult.BlockID, nil + } + } + + if len(clients) == 0 { + return nil, nil, errors.New("unable to connect to any network nodes") + } + if time.Since(lastIncrease) >= 10*time.Second { + if maxResult == nil { + return nil, nil, errors.New("chain stalled at unknown height") + } + return nil, nil, fmt.Errorf("chain stalled at height %v", maxResult.Block.Height) + } + time.Sleep(1 * time.Second) + } +} + +// waitForNode waits for a node to become available and catch up to the given block height. +func waitForNode(node *e2e.Node, height int64, timeout time.Duration) (*rpctypes.ResultStatus, error) { + client, err := node.Client() + if err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + for { + status, err := client.Status(ctx) + switch { + case errors.Is(err, context.DeadlineExceeded): + return nil, fmt.Errorf("timed out waiting for %v to reach height %v", node.Name, height) + case errors.Is(err, context.Canceled): + return nil, err + case err == nil && status.SyncInfo.LatestBlockHeight >= height: + return status, nil + } + + time.Sleep(200 * time.Millisecond) + } +} + +// waitForAllNodes waits for all nodes to become available and catch up to the given block height. +func waitForAllNodes(testnet *e2e.Testnet, height int64, timeout time.Duration) (int64, error) { + lastHeight := int64(0) + for _, node := range testnet.Nodes { + if node.Mode == e2e.ModeSeed { + continue + } + status, err := waitForNode(node, height, 20*time.Second) + if err != nil { + return 0, err + } + if status.SyncInfo.LatestBlockHeight > lastHeight { + lastHeight = status.SyncInfo.LatestBlockHeight + } + } + return lastHeight, nil +} diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go new file mode 100644 index 0000000000..00ee6594d7 --- /dev/null +++ b/test/e2e/runner/setup.go @@ -0,0 +1,360 @@ +// nolint: gosec +package main + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "text/template" + "time" + + "github.com/BurntSushi/toml" + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" +) + +const ( + AppAddressTCP = "tcp://127.0.0.1:30000" + AppAddressUNIX = "unix:///var/run/app.sock" + + PrivvalAddressTCP = "tcp://0.0.0.0:27559" + PrivvalAddressUNIX = "unix:///var/run/privval.sock" + PrivvalKeyFile = "config/priv_validator_key.json" + PrivvalStateFile = "data/priv_validator_state.json" + PrivvalDummyKeyFile = "config/dummy_validator_key.json" + PrivvalDummyStateFile = "data/dummy_validator_state.json" +) + +// Setup sets up the testnet configuration. +func Setup(testnet *e2e.Testnet) error { + logger.Info(fmt.Sprintf("Generating testnet files in %q", testnet.Dir)) + + err := os.MkdirAll(testnet.Dir, os.ModePerm) + if err != nil { + return err + } + + compose, err := MakeDockerCompose(testnet) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0644) + if err != nil { + return err + } + + genesis, err := MakeGenesis(testnet) + if err != nil { + return err + } + + for _, node := range testnet.Nodes { + nodeDir := filepath.Join(testnet.Dir, node.Name) + dirs := []string{ + filepath.Join(nodeDir, "config"), + filepath.Join(nodeDir, "data"), + filepath.Join(nodeDir, "data", "app"), + } + for _, dir := range dirs { + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + } + + err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json")) + if err != nil { + return err + } + + cfg, err := MakeConfig(node) + if err != nil { + return err + } + config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics + + appCfg, err := MakeAppConfig(node) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644) + if err != nil { + return err + } + + err = (&p2p.NodeKey{PrivKey: node.Key}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) + if err != nil { + return err + } + + (privval.NewFilePV(node.Key, + filepath.Join(nodeDir, PrivvalKeyFile), + filepath.Join(nodeDir, PrivvalStateFile), + )).Save() + + // Set up a dummy validator. Tendermint requires a file PV even when not used, so we + // give it a dummy such that it will fail if it actually tries to use it. + (privval.NewFilePV(ed25519.GenPrivKey(), + filepath.Join(nodeDir, PrivvalDummyKeyFile), + filepath.Join(nodeDir, PrivvalDummyStateFile), + )).Save() + } + + return nil +} + +// MakeDockerCompose generates a Docker Compose config for a testnet. +func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) { + // Must use version 2 Docker Compose format, to support IPv6. + tmpl, err := template.New("docker-compose").Parse(`version: '2.4' + +networks: + {{ .Name }}: + driver: bridge +{{- if .IPv6 }} + enable_ipv6: true +{{- end }} + ipam: + driver: default + config: + - subnet: {{ .IP }} + +services: +{{- range .Nodes }} + {{ .Name }}: + container_name: {{ .Name }} + image: tendermint/e2e-node +{{- if eq .ABCIProtocol "builtin" }} + entrypoint: /usr/bin/entrypoint-builtin +{{- end }} + init: true + ports: + - 26656 + - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657 + volumes: + - ./{{ .Name }}:/tendermint + networks: + {{ $.Name }}: + ipv{{ if $.IPv6 }}6{{ else }}4{{ end}}_address: {{ .IP }} + +{{end}}`) + if err != nil { + return nil, err + } + var buf bytes.Buffer + err = tmpl.Execute(&buf, testnet) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// MakeGenesis generates a genesis document. +func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) { + genesis := types.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: testnet.Name, + ConsensusParams: types.DefaultConsensusParams(), + InitialHeight: testnet.InitialHeight, + } + for validator, power := range testnet.Validators { + genesis.Validators = append(genesis.Validators, types.GenesisValidator{ + Name: validator.Name, + Address: validator.Key.PubKey().Address(), + PubKey: validator.Key.PubKey(), + Power: power, + }) + } + // The validator set will be sorted internally by Tendermint ranked by power, + // but we sort it here as well so that all genesis files are identical. + sort.Slice(genesis.Validators, func(i, j int) bool { + return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1 + }) + if len(testnet.InitialState) > 0 { + appState, err := json.Marshal(testnet.InitialState) + if err != nil { + return genesis, err + } + genesis.AppState = appState + } + return genesis, genesis.ValidateAndComplete() +} + +// MakeConfig generates a Tendermint config for a node. +func MakeConfig(node *e2e.Node) (*config.Config, error) { + cfg := config.DefaultConfig() + cfg.Moniker = node.Name + cfg.ProxyApp = AppAddressTCP + cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657" + cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false)) + cfg.P2P.AddrBookStrict = false + cfg.DBBackend = node.Database + cfg.StateSync.DiscoveryTime = 5 * time.Second + + switch node.ABCIProtocol { + case e2e.ProtocolUNIX: + cfg.ProxyApp = AppAddressUNIX + case e2e.ProtocolTCP: + cfg.ProxyApp = AppAddressTCP + case e2e.ProtocolGRPC: + cfg.ProxyApp = AppAddressTCP + cfg.ABCI = "grpc" + case e2e.ProtocolBuiltin: + cfg.ProxyApp = "" + cfg.ABCI = "" + default: + return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol) + } + + // Tendermint errors if it does not have a privval key set up, regardless of whether + // it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy + // key here by default, and use the real key for actual validators that should use + // the file privval. + cfg.PrivValidatorListenAddr = "" + cfg.PrivValidatorKey = PrivvalDummyKeyFile + cfg.PrivValidatorState = PrivvalDummyStateFile + + switch node.Mode { + case e2e.ModeValidator: + switch node.PrivvalProtocol { + case e2e.ProtocolFile: + cfg.PrivValidatorKey = PrivvalKeyFile + cfg.PrivValidatorState = PrivvalStateFile + case e2e.ProtocolUNIX: + cfg.PrivValidatorListenAddr = PrivvalAddressUNIX + case e2e.ProtocolTCP: + cfg.PrivValidatorListenAddr = PrivvalAddressTCP + default: + return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol) + } + case e2e.ModeSeed: + cfg.P2P.SeedMode = true + cfg.P2P.PexReactor = true + case e2e.ModeFull: + // Don't need to do anything, since we're using a dummy privval key by default. + default: + return nil, fmt.Errorf("unexpected mode %q", node.Mode) + } + + if node.FastSync == "" { + cfg.FastSyncMode = false + } else { + cfg.FastSync.Version = node.FastSync + } + + if node.StateSync { + cfg.StateSync.Enable = true + cfg.StateSync.RPCServers = []string{} + for _, peer := range node.Testnet.ArchiveNodes() { + if peer.Name == node.Name { + continue + } + cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC()) + } + if len(cfg.StateSync.RPCServers) < 2 { + return nil, errors.New("unable to find 2 suitable state sync RPC servers") + } + } + + cfg.P2P.Seeds = "" + for _, seed := range node.Seeds { + if len(cfg.P2P.Seeds) > 0 { + cfg.P2P.Seeds += "," + } + cfg.P2P.Seeds += seed.AddressP2P(true) + } + cfg.P2P.PersistentPeers = "" + for _, peer := range node.PersistentPeers { + if len(cfg.P2P.PersistentPeers) > 0 { + cfg.P2P.PersistentPeers += "," + } + cfg.P2P.PersistentPeers += peer.AddressP2P(true) + } + return cfg, nil +} + +// MakeAppConfig generates an ABCI application config for a node. +func MakeAppConfig(node *e2e.Node) ([]byte, error) { + cfg := map[string]interface{}{ + "chain_id": node.Testnet.Name, + "dir": "data/app", + "listen": AppAddressUNIX, + "protocol": "socket", + "persist_interval": node.PersistInterval, + "snapshot_interval": node.SnapshotInterval, + "retain_blocks": node.RetainBlocks, + } + switch node.ABCIProtocol { + case e2e.ProtocolUNIX: + cfg["listen"] = AppAddressUNIX + case e2e.ProtocolTCP: + cfg["listen"] = AppAddressTCP + case e2e.ProtocolGRPC: + cfg["listen"] = AppAddressTCP + cfg["protocol"] = "grpc" + case e2e.ProtocolBuiltin: + delete(cfg, "listen") + cfg["protocol"] = "builtin" + default: + return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol) + } + switch node.PrivvalProtocol { + case e2e.ProtocolFile: + case e2e.ProtocolTCP: + cfg["privval_server"] = PrivvalAddressTCP + cfg["privval_key"] = PrivvalKeyFile + cfg["privval_state"] = PrivvalStateFile + case e2e.ProtocolUNIX: + cfg["privval_server"] = PrivvalAddressUNIX + cfg["privval_key"] = PrivvalKeyFile + cfg["privval_state"] = PrivvalStateFile + default: + return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol) + } + + if len(node.Testnet.ValidatorUpdates) > 0 { + validatorUpdates := map[string]map[string]int64{} + for height, validators := range node.Testnet.ValidatorUpdates { + updateVals := map[string]int64{} + for node, power := range validators { + updateVals[base64.StdEncoding.EncodeToString(node.Key.PubKey().Bytes())] = power + } + validatorUpdates[fmt.Sprintf("%v", height)] = updateVals + } + cfg["validator_update"] = validatorUpdates + } + + var buf bytes.Buffer + err := toml.NewEncoder(&buf).Encode(cfg) + if err != nil { + return nil, fmt.Errorf("failed to generate app config: %w", err) + } + return buf.Bytes(), nil +} + +// UpdateConfigStateSync updates the state sync config for a node. +func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error { + cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml") + + // FIXME Apparently there's no function to simply load a config file without + // involving the entire Viper apparatus, so we'll just resort to regexps. + bz, err := ioutil.ReadFile(cfgPath) + if err != nil { + return err + } + bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height))) + bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash))) + return ioutil.WriteFile(cfgPath, bz, 0644) +} diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go new file mode 100644 index 0000000000..bf52e190ef --- /dev/null +++ b/test/e2e/runner/start.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "sort" + "time" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +func Start(testnet *e2e.Testnet) error { + + // Sort nodes by starting order + nodeQueue := testnet.Nodes + sort.SliceStable(nodeQueue, func(i, j int) bool { + return nodeQueue[i].StartAt < nodeQueue[j].StartAt + }) + + // Start initial nodes (StartAt: 0) + logger.Info("Starting initial network nodes...") + for len(nodeQueue) > 0 && nodeQueue[0].StartAt == 0 { + node := nodeQueue[0] + nodeQueue = nodeQueue[1:] + if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { + return err + } + if _, err := waitForNode(node, 0, 10*time.Second); err != nil { + return err + } + logger.Info(fmt.Sprintf("Node %v up on http://127.0.0.1:%v", node.Name, node.ProxyPort)) + } + + // Wait for initial height + logger.Info(fmt.Sprintf("Waiting for initial height %v...", testnet.InitialHeight)) + block, blockID, err := waitForHeight(testnet, testnet.InitialHeight) + if err != nil { + return err + } + + // Update any state sync nodes with a trusted height and hash + for _, node := range nodeQueue { + if node.StateSync { + err = UpdateConfigStateSync(node, block.Height, blockID.Hash.Bytes()) + if err != nil { + return err + } + } + } + + // Start up remaining nodes + for _, node := range nodeQueue { + logger.Info(fmt.Sprintf("Starting node %v at height %v...", node.Name, node.StartAt)) + if _, _, err := waitForHeight(testnet, node.StartAt); err != nil { + return err + } + if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { + return err + } + status, err := waitForNode(node, node.StartAt, 30*time.Second) + if err != nil { + return err + } + logger.Info(fmt.Sprintf("Node %v up on http://127.0.0.1:%v at height %v", + node.Name, node.ProxyPort, status.SyncInfo.LatestBlockHeight)) + } + + return nil +} diff --git a/test/e2e/runner/test.go b/test/e2e/runner/test.go new file mode 100644 index 0000000000..834ce6f2d0 --- /dev/null +++ b/test/e2e/runner/test.go @@ -0,0 +1,19 @@ +package main + +import ( + "os" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Test runs test cases under tests/ +func Test(testnet *e2e.Testnet) error { + logger.Info("Running tests in ./tests/...") + + err := os.Setenv("E2E_MANIFEST", testnet.File) + if err != nil { + return err + } + + return execVerbose("go", "test", "-count", "1", "./tests/...") +} diff --git a/test/e2e/runner/wait.go b/test/e2e/runner/wait.go new file mode 100644 index 0000000000..fd3474c5c5 --- /dev/null +++ b/test/e2e/runner/wait.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "time" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Wait waits for a number of blocks to be produced, and for all nodes to catch +// up with it. +func Wait(testnet *e2e.Testnet, blocks int64) error { + block, _, err := waitForHeight(testnet, 0) + if err != nil { + return err + } + waitFor := block.Height + blocks + logger.Info(fmt.Sprintf("Waiting for all nodes to reach height %v...", waitFor)) + _, err = waitForAllNodes(testnet, waitFor, 20*time.Second) + if err != nil { + return err + } + return nil +} diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go new file mode 100644 index 0000000000..c60bea6899 --- /dev/null +++ b/test/e2e/tests/app_test.go @@ -0,0 +1,30 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Tests that any initial state given in genesis has made it into the app. +func TestApp_InitialState(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + switch { + case node.Mode == e2e.ModeSeed: + return + case len(node.Testnet.InitialState) == 0: + return + } + + client, err := node.Client() + require.NoError(t, err) + for k, v := range node.Testnet.InitialState { + resp, err := client.ABCIQuery(ctx, "", []byte(k)) + require.NoError(t, err) + assert.Equal(t, k, string(resp.Response.Key)) + assert.Equal(t, v, string(resp.Response.Value)) + } + }) +} diff --git a/test/e2e/tests/e2e_test.go b/test/e2e/tests/e2e_test.go new file mode 100644 index 0000000000..9d89316663 --- /dev/null +++ b/test/e2e/tests/e2e_test.go @@ -0,0 +1,57 @@ +package e2e_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +func init() { + // This can be used to manually specify a testnet manifest and/or node to + // run tests against. The testnet must have been started by the runner first. + //os.Setenv("E2E_MANIFEST", "networks/simple.toml") + //os.Setenv("E2E_NODE", "validator01") +} + +var ( + ctx = context.Background() +) + +// testNode runs tests for testnet nodes. The callback function is given a +// single node to test, running as a subtest in parallel with other subtests. +// +// The testnet manifest must be given as the envvar E2E_MANIFEST. If not set, +// these tests are skipped so that they're not picked up during normal unit +// test runs. If E2E_NODE is also set, only the specified node is tested, +// otherwise all nodes are tested. +func testNode(t *testing.T, testFunc func(*testing.T, e2e.Node)) { + manifest := os.Getenv("E2E_MANIFEST") + if manifest == "" { + t.Skip("E2E_MANIFEST not set, not an end-to-end test run") + } + if !filepath.IsAbs(manifest) { + manifest = filepath.Join("..", manifest) + } + + testnet, err := e2e.LoadTestnet(manifest) + require.NoError(t, err) + nodes := testnet.Nodes + + if name := os.Getenv("E2E_NODE"); name != "" { + node := testnet.LookupNode(name) + require.NotNil(t, node, "node %q not found in testnet %q", name, testnet.Name) + nodes = []*e2e.Node{node} + } + + for _, node := range nodes { + node := *node + t.Run(node.Name, func(t *testing.T) { + t.Parallel() + testFunc(t, node) + }) + } +} From 64b0f5b36363a5a6068ebce5e3fe71e74b07a222 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 5 Oct 2020 12:23:12 +0200 Subject: [PATCH 025/108] test: add basic end-to-end test cases (#5450) Partial fix for #5291. This adds a basic set of test cases for core network invariants. Although small, it is sufficient to replace and extend the current set of P2P tests. Further test cases can be added later. --- test/e2e/tests/app_test.go | 58 ++++++++++- test/e2e/tests/block_test.go | 82 ++++++++++++++++ test/e2e/tests/e2e_test.go | 101 ++++++++++++++++--- test/e2e/tests/validator_test.go | 161 +++++++++++++++++++++++++++++++ 4 files changed, 386 insertions(+), 16 deletions(-) create mode 100644 test/e2e/tests/block_test.go create mode 100644 test/e2e/tests/validator_test.go diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index c60bea6899..60018cace5 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -1,20 +1,21 @@ package e2e_test import ( + "fmt" + "math/rand" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" ) // Tests that any initial state given in genesis has made it into the app. func TestApp_InitialState(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { - switch { - case node.Mode == e2e.ModeSeed: - return - case len(node.Testnet.InitialState) == 0: + if len(node.Testnet.InitialState) == 0 { return } @@ -28,3 +29,52 @@ func TestApp_InitialState(t *testing.T) { } }) } + +// Tests that the app hash (as reported by the app) matches the last +// block and the node sync status. +func TestApp_Hash(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + info, err := client.ABCIInfo(ctx) + require.NoError(t, err) + require.NotEmpty(t, info.Response.LastBlockAppHash, "expected app to return app hash") + + block, err := client.Block(ctx, nil) + require.NoError(t, err) + require.EqualValues(t, info.Response.LastBlockAppHash, block.Block.AppHash, + "app hash does not match last block's app hash") + + status, err := client.Status(ctx) + require.NoError(t, err) + require.EqualValues(t, info.Response.LastBlockAppHash, status.SyncInfo.LatestAppHash, + "app hash does not match node status") + }) +} + +// Tests that we can set a value and retrieve it. +func TestApp_Tx(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + + // Generate a random value, to prevent duplicate tx errors when + // manually running the test multiple times for a testnet. + r := rand.New(rand.NewSource(time.Now().UnixNano())) + bz := make([]byte, 32) + _, err = r.Read(bz) + require.NoError(t, err) + + key := fmt.Sprintf("testapp-tx-%v", node.Name) + value := fmt.Sprintf("%x", bz) + tx := types.Tx(fmt.Sprintf("%v=%v", key, value)) + + _, err = client.BroadcastTxCommit(ctx, tx) + require.NoError(t, err) + + resp, err := client.ABCIQuery(ctx, "", []byte(key)) + require.NoError(t, err) + assert.Equal(t, key, string(resp.Response.Key)) + assert.Equal(t, value, string(resp.Response.Value)) + }) +} diff --git a/test/e2e/tests/block_test.go b/test/e2e/tests/block_test.go new file mode 100644 index 0000000000..688d7bd6c6 --- /dev/null +++ b/test/e2e/tests/block_test.go @@ -0,0 +1,82 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Tests that block headers are identical across nodes where present. +func TestBlock_Header(t *testing.T) { + blocks := fetchBlockChain(t) + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + status, err := client.Status(ctx) + require.NoError(t, err) + + first := status.SyncInfo.EarliestBlockHeight + last := status.SyncInfo.LatestBlockHeight + if node.RetainBlocks > 0 { + first++ // avoid race conditions with block pruning + } + + for _, block := range blocks { + if block.Header.Height < first { + continue + } + if block.Header.Height > last { + break + } + resp, err := client.Block(ctx, &block.Header.Height) + require.NoError(t, err) + require.Equal(t, block, resp.Block, + "block mismatch for height %v", block.Header.Height) + } + }) +} + +// Tests that the node contains the expected block range. +func TestBlock_Range(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + status, err := client.Status(ctx) + require.NoError(t, err) + + first := status.SyncInfo.EarliestBlockHeight + last := status.SyncInfo.LatestBlockHeight + + switch { + case node.StateSync: + assert.Greater(t, first, node.Testnet.InitialHeight, + "state synced nodes should not contain network's initial height") + + case node.RetainBlocks > 0 && int64(node.RetainBlocks) < (last-node.Testnet.InitialHeight+1): + // Delta handles race conditions in reading first/last heights. + assert.InDelta(t, node.RetainBlocks, last-first+1, 1, + "node not pruning expected blocks") + + default: + assert.Equal(t, node.Testnet.InitialHeight, first, + "node's first block should be network's initial height") + } + + for h := first; h <= last; h++ { + resp, err := client.Block(ctx, &(h)) + if err != nil && node.RetainBlocks > 0 && h == first { + // Ignore errors in first block if node is pruning blocks due to race conditions. + continue + } + require.NoError(t, err) + assert.Equal(t, h, resp.Block.Height) + } + + for h := node.Testnet.InitialHeight; h < first; h++ { + _, err := client.Block(ctx, &(h)) + require.Error(t, err) + } + }) +} diff --git a/test/e2e/tests/e2e_test.go b/test/e2e/tests/e2e_test.go index 9d89316663..80b43229ca 100644 --- a/test/e2e/tests/e2e_test.go +++ b/test/e2e/tests/e2e_test.go @@ -4,21 +4,29 @@ import ( "context" "os" "path/filepath" + "sync" "testing" "github.com/stretchr/testify/require" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" + rpctypes "github.com/tendermint/tendermint/rpc/core/types" e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" ) func init() { // This can be used to manually specify a testnet manifest and/or node to // run tests against. The testnet must have been started by the runner first. - //os.Setenv("E2E_MANIFEST", "networks/simple.toml") - //os.Setenv("E2E_NODE", "validator01") + // os.Setenv("E2E_MANIFEST", "networks/ci.toml") + // os.Setenv("E2E_NODE", "validator01") } var ( - ctx = context.Background() + ctx = context.Background() + testnetCache = map[string]e2e.Testnet{} + testnetCacheMtx = sync.Mutex{} + blocksCache = map[string][]*types.Block{} + blocksCacheMtx = sync.Mutex{} ) // testNode runs tests for testnet nodes. The callback function is given a @@ -29,16 +37,9 @@ var ( // test runs. If E2E_NODE is also set, only the specified node is tested, // otherwise all nodes are tested. func testNode(t *testing.T, testFunc func(*testing.T, e2e.Node)) { - manifest := os.Getenv("E2E_MANIFEST") - if manifest == "" { - t.Skip("E2E_MANIFEST not set, not an end-to-end test run") - } - if !filepath.IsAbs(manifest) { - manifest = filepath.Join("..", manifest) - } + t.Helper() - testnet, err := e2e.LoadTestnet(manifest) - require.NoError(t, err) + testnet := loadTestnet(t) nodes := testnet.Nodes if name := os.Getenv("E2E_NODE"); name != "" { @@ -55,3 +56,79 @@ func testNode(t *testing.T, testFunc func(*testing.T, e2e.Node)) { }) } } + +// loadTestnet loads the testnet based on the E2E_MANIFEST envvar. +func loadTestnet(t *testing.T) e2e.Testnet { + t.Helper() + + manifest := os.Getenv("E2E_MANIFEST") + if manifest == "" { + t.Skip("E2E_MANIFEST not set, not an end-to-end test run") + } + if !filepath.IsAbs(manifest) { + manifest = filepath.Join("..", manifest) + } + + testnetCacheMtx.Lock() + defer testnetCacheMtx.Unlock() + if testnet, ok := testnetCache[manifest]; ok { + return testnet + } + + testnet, err := e2e.LoadTestnet(manifest) + require.NoError(t, err) + testnetCache[manifest] = *testnet + return *testnet +} + +// fetchBlockChain fetches a complete, up-to-date block history from +// the freshest testnet archive node. +func fetchBlockChain(t *testing.T) []*types.Block { + t.Helper() + + testnet := loadTestnet(t) + + // Find the freshest archive node + var ( + client *rpchttp.HTTP + status *rpctypes.ResultStatus + ) + for _, node := range testnet.ArchiveNodes() { + c, err := node.Client() + require.NoError(t, err) + s, err := c.Status(ctx) + require.NoError(t, err) + if status == nil || s.SyncInfo.LatestBlockHeight > status.SyncInfo.LatestBlockHeight { + client = c + status = s + } + } + require.NotNil(t, client, "couldn't find an archive node") + + // Fetch blocks. Look for existing block history in the block cache, and + // extend it with any new blocks that have been produced. + blocksCacheMtx.Lock() + defer blocksCacheMtx.Unlock() + + from := status.SyncInfo.EarliestBlockHeight + to := status.SyncInfo.LatestBlockHeight + blocks, ok := blocksCache[testnet.Name] + if !ok { + blocks = make([]*types.Block, 0, to-from+1) + } + if len(blocks) > 0 { + from = blocks[len(blocks)-1].Height + 1 + } + + for h := from; h <= to; h++ { + resp, err := client.Block(ctx, &(h)) + require.NoError(t, err) + require.NotNil(t, resp.Block) + require.Equal(t, h, resp.Block.Height, "unexpected block height %v", resp.Block.Height) + blocks = append(blocks, resp.Block) + } + require.NotEmpty(t, blocks, "blockchain does not contain any blocks") + blocksCache[testnet.Name] = blocks + + return blocks +} diff --git a/test/e2e/tests/validator_test.go b/test/e2e/tests/validator_test.go new file mode 100644 index 0000000000..2398d0e629 --- /dev/null +++ b/test/e2e/tests/validator_test.go @@ -0,0 +1,161 @@ +package e2e_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" +) + +// Tests that validator sets are available and correct according to +// scheduled validator updates. +func TestValidator_Sets(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + status, err := client.Status(ctx) + require.NoError(t, err) + + first := status.SyncInfo.EarliestBlockHeight + last := status.SyncInfo.LatestBlockHeight + + // skip first block if node is pruning blocks, to avoid race conditions + if node.RetainBlocks > 0 { + first++ + } + + valSchedule := newValidatorSchedule(*node.Testnet) + valSchedule.Increment(first - node.Testnet.InitialHeight) + + for h := first; h <= last; h++ { + validators := []*types.Validator{} + perPage := 100 + for page := 1; ; page++ { + resp, err := client.Validators(ctx, &(h), &(page), &perPage) + require.NoError(t, err) + validators = append(validators, resp.Validators...) + if len(validators) == resp.Total { + break + } + } + require.Equal(t, valSchedule.Set.Validators, validators, + "incorrect validator set at height %v", h) + valSchedule.Increment(1) + } + }) +} + +// Tests that a validator proposes blocks when it's supposed to. It tolerates some +// missed blocks, e.g. due to testnet perturbations. +func TestValidator_Propose(t *testing.T) { + blocks := fetchBlockChain(t) + testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode != e2e.ModeValidator { + return + } + address := node.Key.PubKey().Address() + valSchedule := newValidatorSchedule(*node.Testnet) + + expectCount := 0 + proposeCount := 0 + for _, block := range blocks { + if bytes.Equal(valSchedule.Set.Proposer.Address, address) { + expectCount++ + if bytes.Equal(block.ProposerAddress, address) { + proposeCount++ + } + } + valSchedule.Increment(1) + } + + require.False(t, proposeCount == 0 && expectCount > 0, + "node did not propose any blocks (expected %v)", expectCount) + require.Less(t, expectCount-proposeCount, 5, + "validator missed proposing too many blocks (proposed %v out of %v)", proposeCount, expectCount) + }) +} + +// Tests that a validator signs blocks when it's supposed to. It tolerates some +// missed blocks, e.g. due to testnet perturbations. +func TestValidator_Sign(t *testing.T) { + blocks := fetchBlockChain(t) + testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode != e2e.ModeValidator { + return + } + address := node.Key.PubKey().Address() + valSchedule := newValidatorSchedule(*node.Testnet) + + expectCount := 0 + signCount := 0 + for _, block := range blocks[1:] { // Skip first block, since it has no signatures + signed := false + for _, sig := range block.LastCommit.Signatures { + if bytes.Equal(sig.ValidatorAddress, address) { + signed = true + break + } + } + if valSchedule.Set.HasAddress(address) { + expectCount++ + if signed { + signCount++ + } + } else { + require.False(t, signed, "unexpected signature for block %v", block.LastCommit.Height) + } + valSchedule.Increment(1) + } + + require.False(t, signCount == 0 && expectCount > 0, + "node did not sign any blocks (expected %v)", expectCount) + require.Less(t, float64(expectCount-signCount)/float64(expectCount), 0.5, + "validator missed signing too many blocks (signed %v out of %v)", signCount, expectCount) + }) +} + +// validatorSchedule is a validator set iterator, which takes into account +// validator set updates. +type validatorSchedule struct { + Set *types.ValidatorSet + height int64 + updates map[int64]map[*e2e.Node]int64 +} + +func newValidatorSchedule(testnet e2e.Testnet) *validatorSchedule { + valMap := testnet.Validators // genesis validators + if v, ok := testnet.ValidatorUpdates[0]; ok { // InitChain validators + valMap = v + } + return &validatorSchedule{ + height: testnet.InitialHeight, + Set: types.NewValidatorSet(makeVals(valMap)), + updates: testnet.ValidatorUpdates, + } +} + +func (s *validatorSchedule) Increment(heights int64) { + for i := int64(0); i < heights; i++ { + s.height++ + if s.height > 2 { + // validator set updates are offset by 2, since they only take effect + // two blocks after they're returned. + if update, ok := s.updates[s.height-2]; ok { + if err := s.Set.UpdateWithChangeSet(makeVals(update)); err != nil { + panic(err) + } + } + } + s.Set.IncrementProposerPriority(1) + } +} + +func makeVals(valMap map[*e2e.Node]int64) []*types.Validator { + vals := make([]*types.Validator, 0, len(valMap)) + for node, power := range valMap { + vals = append(vals, types.NewValidator(node.Key.PubKey(), power)) + } + return vals +} From 4b3565fcaa0fe279032574c92ee15f69320548be Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 5 Oct 2020 12:44:35 +0200 Subject: [PATCH 026/108] test: add GitHub action for end-to-end tests (#5452) Partial fix for #5291. --- .github/workflows/e2e.yml | 38 ++++++++++++++++++++++++++++++++++++++ test/e2e/README.md | 2 ++ test/e2e/docker/Dockerfile | 4 ++-- test/e2e/runner/main.go | 8 ++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000000..c081070343 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,38 @@ +name: e2e-tests +# Runs the CI end-to-end test network on all pushes to master or release branches +# and every pull request, but only if any Go files have been changed. +on: + pull_request: + push: + branches: + - master + - release/** + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v2 + - uses: technote-space/get-diff-action@v3 + with: + SUFFIX_FILTER: | + .go + .mod + .sum + SET_ENV_NAME_INSERTIONS: 1 + SET_ENV_NAME_LINES: 1 + + - name: Build + working-directory: test/e2e + # Run two make jobs in parallel, since we can't run steps in parallel. + run: make -j2 docker runner + + - name: Run CI testnet + working-directory: test/e2e + run: sudo ./build/runner -f networks/ci.toml + + - name: Emit logs on failure + if: ${{ failure() }} + working-directory: test/e2e + run: ./build/runner -f networks/ci.toml logs diff --git a/test/e2e/README.md b/test/e2e/README.md index 7c4891ce22..a352959c6d 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -36,6 +36,8 @@ The test runner has the following stages, which can also be executed explicitly * `logs`: outputs all node logs. +* `tail`: tails (follows) node logs until cancelled. + ## Tests Test cases are written as normal Go tests in `tests/`. They use a `testNode()` helper which executes each test as a parallel subtest for each node in the network. diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile index ba0e51be41..273bd07c6c 100644 --- a/test/e2e/docker/Dockerfile +++ b/test/e2e/docker/Dockerfile @@ -3,8 +3,8 @@ # instead of spending time compiling them. FROM golang:1.15 -RUN apt-get update -y && apt-get upgrade -y -RUN apt-get install -y libleveldb-dev librocksdb-dev +RUN apt-get -qq update -y && apt-get -qq upgrade -y >/dev/null +RUN apt-get -qq install -y libleveldb-dev librocksdb-dev >/dev/null # Set up build directory /src/tendermint ENV TENDERMINT_BUILD_OPTIONS badgerdb,boltdb,cleveldb,rocksdb diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index b01b208f83..d58305c9b9 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -167,6 +167,14 @@ func NewCLI() *CLI { cli.root.AddCommand(&cobra.Command{ Use: "logs", Short: "Shows the testnet logs", + RunE: func(cmd *cobra.Command, args []string) error { + return execComposeVerbose(cli.testnet.Dir, "logs") + }, + }) + + cli.root.AddCommand(&cobra.Command{ + Use: "tail", + Short: "Tails the testnet logs", RunE: func(cmd *cobra.Command, args []string) error { return execComposeVerbose(cli.testnet.Dir, "logs", "--follow") }, From 0003aabe6562c089d0efe3b31b01c91414a2dbfc Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 5 Oct 2020 17:16:05 +0200 Subject: [PATCH 027/108] circleci: remove Gitian reproducible_builds job (#5462) --- .circleci/config.yml | 34 --- scripts/gitian-build.sh | 201 ------------------ scripts/gitian-descriptors/gitian-darwin.yml | 107 ---------- scripts/gitian-descriptors/gitian-linux.yml | 106 --------- scripts/gitian-descriptors/gitian-windows.yml | 107 ---------- scripts/gitian-keys/README.md | 29 --- scripts/gitian-keys/keys.txt | 1 - 7 files changed, 585 deletions(-) delete mode 100755 scripts/gitian-build.sh delete mode 100644 scripts/gitian-descriptors/gitian-darwin.yml delete mode 100644 scripts/gitian-descriptors/gitian-linux.yml delete mode 100644 scripts/gitian-descriptors/gitian-windows.yml delete mode 100644 scripts/gitian-keys/README.md delete mode 100644 scripts/gitian-keys/keys.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index 340e232d99..779d0a2881 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -205,34 +205,6 @@ jobs: export GOOS=linux GOARCH=arm64 && 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}" - reproducible_builds: - executor: golang - steps: - - attach_workspace: - at: /tmp/workspace - - checkout - - setup_remote_docker: - docker_layer_caching: true - - run: - name: Build tendermint - no_output_timeout: 20m - command: | - sudo apt-get update - sudo apt-get install -y ruby - bash -x ./scripts/gitian-build.sh all - for os in darwin linux windows; do - cp gitian-build-${os}/result/tendermint-${os}-res.yml . - cp gitian-build-${os}/build/out/tendermint-*.tar.gz . - rm -rf gitian-build-${os}/ - done - - store_artifacts: - path: /go/src/github.com/tendermint/tendermint/tendermint-darwin-res.yml - - store_artifacts: - path: /go/src/github.com/tendermint/tendermint/tendermint-linux-res.yml - - store_artifacts: - path: /go/src/github.com/tendermint/tendermint/tendermint-windows-res.yml - - store_artifacts: - path: /go/src/github.com/tendermint/tendermint/tendermint-*.tar.gz # # Test RPC implementation against the swagger documented specs # contract_tests: @@ -287,12 +259,6 @@ workflows: - test_p2p: name: test_p2p_ipv6 ipv: 6 - - reproducible_builds: - filters: - branches: - only: - - master - - /v[0-9]+\.[0-9]+/ # - contract_tests: # requires: # - setup_dependencies diff --git a/scripts/gitian-build.sh b/scripts/gitian-build.sh deleted file mode 100755 index 7471b472fa..0000000000 --- a/scripts/gitian-build.sh +++ /dev/null @@ -1,201 +0,0 @@ -#!/bin/bash - -# symbol prefixes: -# g_ -> global -# l_ - local variable -# f_ -> function - -set -euo pipefail - -GITIAN_CACHE_DIRNAME='.gitian-builder-cache' -GO_RELEASE='1.13.3' -GO_TARBALL="go${GO_RELEASE}.linux-amd64.tar.gz" -GO_TARBALL_URL="https://dl.google.com/go/${GO_TARBALL}" - -# Defaults - -DEFAULT_SIGN_COMMAND='gpg --detach-sign' -DEFAULT_TENDERMINT_SIGS=${TENDERMINT_SIGS:-'tendermint.sigs'} -DEFAULT_GITIAN_REPO='https://github.com/tendermint/gitian-builder' -DEFAULT_GBUILD_FLAGS='' -DEFAULT_SIGS_REPO='https://github.com/tendermint/tendermint.sigs' - -# Overrides - -SIGN_COMMAND=${SIGN_COMMAND:-${DEFAULT_SIGN_COMMAND}} -GITIAN_REPO=${GITIAN_REPO:-${DEFAULT_GITIAN_REPO}} -GBUILD_FLAGS=${GBUILD_FLAGS:-${DEFAULT_GBUILD_FLAGS}} - -# Globals - -g_workdir='' -g_gitian_cache='' -g_cached_gitian='' -g_cached_go_tarball='' -g_sign_identity='' -g_sigs_dir='' -g_flag_commit='' - - -f_help() { - cat >&2 <&2 - mkdir "${l_builddir}/inputs/" - cp -v "${g_cached_go_tarball}" "${l_builddir}/inputs/" - done -} - -f_build() { - local l_descriptor - - l_descriptor=$1 - - bin/gbuild --commit tendermint="$g_commit" ${GBUILD_FLAGS} "$l_descriptor" - libexec/stop-target || f_echo_stderr "warning: couldn't stop target" -} - -f_sign_verify() { - local l_descriptor - - l_descriptor=$1 - - bin/gsign -p "${SIGN_COMMAND}" -s "${g_sign_identity}" --destination="${g_sigs_dir}" --release=${g_release} ${l_descriptor} - bin/gverify --destination="${g_sigs_dir}" --release="${g_release}" ${l_descriptor} -} - -f_commit_sig() { - local l_release_name - - l_release_name=$1 - - pushd "${g_sigs_dir}" - git add . || echo "git add failed" >&2 - git commit -m "Add ${l_release_name} reproducible build" || echo "git commit failed" >&2 - popd -} - -f_prep_docker_image() { - pushd $1 - bin/make-base-vm --docker --suite bionic --arch amd64 - popd -} - -f_ensure_cache() { - g_gitian_cache="${g_workdir}/${GITIAN_CACHE_DIRNAME}" - [ -d "${g_gitian_cache}" ] || mkdir "${g_gitian_cache}" - - g_cached_go_tarball="${g_gitian_cache}/${GO_TARBALL}" - if [ ! -f "${g_cached_go_tarball}" ]; then - f_echo_stderr "${g_cached_go_tarball}: cache miss, caching..." - curl -L "${GO_TARBALL_URL}" --output "${g_cached_go_tarball}" - fi - - g_cached_gitian="${g_gitian_cache}/gitian-builder" - if [ ! -d "${g_cached_gitian}" ]; then - f_echo_stderr "${g_cached_gitian}: cache miss, caching..." - git clone ${GITIAN_REPO} "${g_cached_gitian}" - fi -} - -f_demangle_platforms() { - case "${1}" in - all) - printf '%s' 'darwin linux windows' ;; - linux|darwin|windows) - printf '%s' "${1}" ;; - *) - echo "invalid platform -- ${1}" - exit 1 - esac -} - -f_echo_stderr() { - echo $@ >&2 -} - - -while getopts ":cs:h" opt; do - case "${opt}" in - h) f_help ; exit 0 ;; - c) g_flag_commit=y ;; - s) g_sign_identity="${OPTARG}" ;; - esac -done - -shift "$((OPTIND-1))" - -g_platforms=$(f_demangle_platforms "${1}") -g_workdir="$(pwd)" -g_commit="$(git rev-parse HEAD)" -g_sigs_dir=${TENDERMINT_SIGS:-"${g_workdir}/${DEFAULT_TENDERMINT_SIGS}"} - -f_ensure_cache - -f_prep_docker_image "${g_cached_gitian}" - -f_prep_build "${g_platforms}" - -export USE_DOCKER=1 -for g_os in ${g_platforms}; do - g_release="$(git describe --tags --abbrev=9 | sed 's/^v//')-${g_os}" - g_descriptor="${g_workdir}/scripts/gitian-descriptors/gitian-${g_os}.yml" - [ -f ${g_descriptor} ] - g_builddir="$(f_builddir ${g_os})" - - pushd "${g_builddir}" - f_build "${g_descriptor}" - if [ -n "${g_sign_identity}" ]; then - f_sign_verify "${g_descriptor}" - fi - popd - - if [ -n "${g_sign_identity}" -a -n "${g_flag_commit}" ]; then - [ -d "${g_sigs_dir}/.git/" ] && f_commit_sig ${g_release} || f_echo_stderr "couldn't commit, ${g_sigs_dir} is not a git clone" - fi -done - -exit 0 diff --git a/scripts/gitian-descriptors/gitian-darwin.yml b/scripts/gitian-descriptors/gitian-darwin.yml deleted file mode 100644 index 90a9fb9d45..0000000000 --- a/scripts/gitian-descriptors/gitian-darwin.yml +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: "tendermint-darwin" -enable_cache: true -distro: "ubuntu" -suites: -- "bionic" -architectures: -- "amd64" -packages: -- "bsdmainutils" -- "build-essential" -- "ca-certificates" -- "curl" -- "debhelper" -- "dpkg-dev" -- "devscripts" -- "fakeroot" -- "git" -- "golang-any" -- "xxd" -- "quilt" -remotes: -- "url": "https://github.com/tendermint/tendermint.git" - "dir": "tendermint" -files: -- "go1.13.3.linux-amd64.tar.gz" -script: | - set -e -o pipefail - - GO_SRC_RELEASE=go1.13.3.linux-amd64 - GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" - # Compile go and configure the environment - export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" - export BUILD_DIR=`pwd` - tar xf "${GO_SRC_TARBALL}" - rm -f "${GO_SRC_TARBALL}" - [ -d go/ ] - - export GOOS=darwin - export GOROOT=${BUILD_DIR}/go - export GOPATH=${BUILD_DIR}/gopath - mkdir -p ${GOPATH}/bin - - export PATH_orig=${PATH} - export PATH=$GOPATH/bin:$GOROOT/bin:$PATH - - export ARCHS='386 amd64' - export GO111MODULE=on - - # Make release tarball - pushd tendermint - VERSION=$(git describe --tags | sed 's/^v//') - COMMIT=$(git rev-parse --short=8 HEAD) - DISTNAME=tendermint-${VERSION} - git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD - SOURCEDIST=`pwd`/`echo tendermint-*.tar.gz` - popd - - # Correct tar file order - mkdir -p temp - pushd temp - tar xf $SOURCEDIST - rm $SOURCEDIST - find tendermint-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST - popd - - # Prepare GOPATH and install deps - distsrc=${GOPATH}/src/github.com/tendermint/tendermint - mkdir -p ${distsrc} - pushd ${distsrc} - tar --strip-components=1 -xf $SOURCEDIST - go mod download - popd - - # Configure LDFLAGS for reproducible builds - LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ - -X github.com/tendermint/tendermint/version.GitCommit=${COMMIT}" - - # Extract release tarball and build - for arch in ${ARCHS}; do - INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} - mkdir -p ${INSTALLPATH} - - # Build tendermint binary - pushd ${distsrc} - GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ - -trimpath \ - -gcflags=all=-trimpath=${GOPATH} \ - -asmflags=all=-trimpath=${GOPATH} \ - -mod=readonly -tags "tendermint" \ - -ldflags="${LDFLAGS}" \ - -o ${INSTALLPATH}/tendermint ./cmd/tendermint/ - - popd # ${distsrc} - - pushd ${INSTALLPATH} - find -type f | sort | tar \ - --no-recursion --mode='u+rw,go+r-w,a+X' \ - --numeric-owner --sort=name \ - --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-darwin-${arch}.tar.gz - popd # installed - done - - rm -rf ${distsrc} - - mkdir -p $OUTDIR/src - mv $SOURCEDIST $OUTDIR/src diff --git a/scripts/gitian-descriptors/gitian-linux.yml b/scripts/gitian-descriptors/gitian-linux.yml deleted file mode 100644 index 8aab869eec..0000000000 --- a/scripts/gitian-descriptors/gitian-linux.yml +++ /dev/null @@ -1,106 +0,0 @@ ---- -name: "tendermint-linux" -enable_cache: true -distro: "ubuntu" -suites: -- "bionic" -architectures: -- "amd64" -packages: -- "bsdmainutils" -- "build-essential" -- "ca-certificates" -- "curl" -- "debhelper" -- "dpkg-dev" -- "devscripts" -- "fakeroot" -- "git" -- "golang-any" -- "xxd" -- "quilt" -remotes: -- "url": "https://github.com/tendermint/tendermint.git" - "dir": "tendermint" -files: -- "go1.13.3.linux-amd64.tar.gz" -script: | - set -e -o pipefail - - GO_SRC_RELEASE=go1.13.3.linux-amd64 - GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" - # Compile go and configure the environment - export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" - export BUILD_DIR=`pwd` - tar xf "${GO_SRC_TARBALL}" - rm -f "${GO_SRC_TARBALL}" - [ -d go/ ] - - export GOROOT=${BUILD_DIR}/go - export GOPATH=${BUILD_DIR}/gopath - mkdir -p ${GOPATH}/bin - - export PATH_orig=${PATH} - export PATH=$GOPATH/bin:$GOROOT/bin:$PATH - - export ARCHS='386 amd64 arm arm64' - export GO111MODULE=on - - # Make release tarball - pushd tendermint - VERSION=$(git describe --tags | sed 's/^v//') - COMMIT=$(git rev-parse --short=8 HEAD) - DISTNAME=tendermint-${VERSION} - git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD - SOURCEDIST=`pwd`/`echo tendermint-*.tar.gz` - popd - - # Correct tar file order - mkdir -p temp - pushd temp - tar xf $SOURCEDIST - rm $SOURCEDIST - find tendermint-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST - popd - - # Prepare GOPATH and install deps - distsrc=${GOPATH}/src/github.com/tendermint/tendermint - mkdir -p ${distsrc} - pushd ${distsrc} - tar --strip-components=1 -xf $SOURCEDIST - go mod download - popd - - # Configure LDFLAGS for reproducible builds - LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ - -X github.com/tendermint/tendermint/version.GitCommit=${COMMIT}" - - # Extract release tarball and build - for arch in ${ARCHS}; do - INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} - mkdir -p ${INSTALLPATH} - - # Build tendermint binary - pushd ${distsrc} - GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ - -trimpath \ - -gcflags=all=-trimpath=${GOPATH} \ - -asmflags=all=-trimpath=${GOPATH} \ - -mod=readonly -tags "tendermint" \ - -ldflags="${LDFLAGS}" \ - -o ${INSTALLPATH}/tendermint ./cmd/tendermint/ - - popd # ${distsrc} - - pushd ${INSTALLPATH} - find -type f | sort | tar \ - --no-recursion --mode='u+rw,go+r-w,a+X' \ - --numeric-owner --sort=name \ - --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-linux-${arch}.tar.gz - popd # installed - done - - rm -rf ${distsrc} - - mkdir -p $OUTDIR/src - mv $SOURCEDIST $OUTDIR/src diff --git a/scripts/gitian-descriptors/gitian-windows.yml b/scripts/gitian-descriptors/gitian-windows.yml deleted file mode 100644 index 23dbdab2f7..0000000000 --- a/scripts/gitian-descriptors/gitian-windows.yml +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: "tendermint-windows" -enable_cache: true -distro: "ubuntu" -suites: -- "bionic" -architectures: -- "amd64" -packages: -- "bsdmainutils" -- "build-essential" -- "ca-certificates" -- "curl" -- "debhelper" -- "dpkg-dev" -- "devscripts" -- "fakeroot" -- "git" -- "golang-any" -- "xxd" -- "quilt" -remotes: -- "url": "https://github.com/tendermint/tendermint.git" - "dir": "tendermint" -files: -- "go1.13.3.linux-amd64.tar.gz" -script: | - set -e -o pipefail - - GO_SRC_RELEASE=go1.13.3.linux-amd64 - GO_SRC_TARBALL="${GO_SRC_RELEASE}.tar.gz" - # Compile go and configure the environment - export TAR_OPTIONS="--mtime="$REFERENCE_DATE\\\ $REFERENCE_TIME"" - export BUILD_DIR=`pwd` - tar xf "${GO_SRC_TARBALL}" - rm -f "${GO_SRC_TARBALL}" - [ -d go/ ] - - export GOOS=windows - export GOROOT=${BUILD_DIR}/go - export GOPATH=${BUILD_DIR}/gopath - mkdir -p ${GOPATH}/bin - - export PATH_orig=${PATH} - export PATH=$GOPATH/bin:$GOROOT/bin:$PATH - - export ARCHS='386 amd64' - export GO111MODULE=on - - # Make release tarball - pushd tendermint - VERSION=$(git describe --tags | sed 's/^v//') - COMMIT=$(git rev-parse --short=8 HEAD) - DISTNAME=tendermint-${VERSION} - git archive --format tar.gz --prefix ${DISTNAME}/ -o ${DISTNAME}.tar.gz HEAD - SOURCEDIST=`pwd`/`echo tendermint-*.tar.gz` - popd - - # Correct tar file order - mkdir -p temp - pushd temp - tar xf $SOURCEDIST - rm $SOURCEDIST - find tendermint-* | sort | tar --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > $SOURCEDIST - popd - - # Prepare GOPATH and install deps - distsrc=${GOPATH}/src/github.com/tendermint/tendermint - mkdir -p ${distsrc} - pushd ${distsrc} - tar --strip-components=1 -xf $SOURCEDIST - go mod download - popd - - # Configure LDFLAGS for reproducible builds - LDFLAGS="-extldflags=-static -buildid=${VERSION} -s -w \ - -X github.com/tendermint/tendermint/version.GitCommit=${COMMIT}" - - # Extract release tarball and build - for arch in ${ARCHS}; do - INSTALLPATH=`pwd`/installed/${DISTNAME}-${arch} - mkdir -p ${INSTALLPATH} - - # Build tendermint binary - pushd ${distsrc} - GOARCH=${arch} GOROOT_FINAL=${GOROOT} go build -a \ - -trimpath \ - -gcflags=all=-trimpath=${GOPATH} \ - -asmflags=all=-trimpath=${GOPATH} \ - -mod=readonly -tags "tendermint" \ - -ldflags="${LDFLAGS}" \ - -o ${INSTALLPATH}/tendermint.exe ./cmd/tendermint/ - - popd # ${distsrc} - - pushd ${INSTALLPATH} - find -type f | sort | tar \ - --no-recursion --mode='u+rw,go+r-w,a+X' \ - --numeric-owner --sort=name \ - --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-windows-${arch}.tar.gz - popd # installed - done - - rm -rf ${distsrc} - - mkdir -p $OUTDIR/src - mv $SOURCEDIST $OUTDIR/src diff --git a/scripts/gitian-keys/README.md b/scripts/gitian-keys/README.md deleted file mode 100644 index 2ed7e02dd6..0000000000 --- a/scripts/gitian-keys/README.md +++ /dev/null @@ -1,29 +0,0 @@ -## PGP keys of Gitian builders and Tendermint Developers - -The file `keys.txt` contains fingerprints of the public keys of Gitian builders -and active developers. - -The associated keys are mainly used to sign git commits or the build results -of Gitian builds. - -The most recent version of each pgp key can be found on most PGP key servers. - -Fetch the latest version from the key server to see if any key was revoked in -the meantime. -To fetch the latest version of all pgp keys in your gpg homedir, - -```bash -gpg --refresh-keys -``` - -To fetch keys of Gitian builders and active core developers, feed the list of -fingerprints of the primary keys into gpg: - -```bash -while read fingerprint keyholder_name; \ -do gpg --keyserver hkp://subset.pool.sks-keyservers.net \ ---recv-keys ${fingerprint}; done < ./keys.txt -``` - -Add your key to the list if you are a Tendermint core developer or you have -provided Gitian signatures for two major or minor releases of Tendermint. diff --git a/scripts/gitian-keys/keys.txt b/scripts/gitian-keys/keys.txt deleted file mode 100644 index 91330ae0b3..0000000000 --- a/scripts/gitian-keys/keys.txt +++ /dev/null @@ -1 +0,0 @@ -04160004A8276E40BB9890FBE8A48AE5311D765A Alessio Treglia From 4462e2697ca057a3ad3471dcf7ca8148e72ea560 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 5 Oct 2020 17:52:01 +0200 Subject: [PATCH 028/108] test: remove P2P tests (#5453) --- .circleci/config.yml | 44 +++------- .github/workflows/net.yml | 28 ------ scripts/localnet-blocks-test.sh | 41 --------- test/README.md | 6 -- test/p2p/README.md | 66 -------------- test/p2p/address.sh | 28 ------ test/p2p/atomic_broadcast/test.sh | 76 ---------------- test/p2p/basic/test.sh | 76 ---------------- test/p2p/circleci.sh | 55 ------------ test/p2p/client.sh | 26 ------ test/p2p/dsrr/check_peer.sh | 64 -------------- test/p2p/dsrr/test.sh | 13 --- test/p2p/dsrr/test_peer.sh | 65 -------------- test/p2p/fast_sync/check_peer.sh | 44 ---------- test/p2p/fast_sync/test.sh | 15 ---- test/p2p/fast_sync/test_peer.sh | 39 --------- test/p2p/kill_all/check_peers.sh | 50 ----------- test/p2p/kill_all/test.sh | 31 ------- test/p2p/local_testnet_start.sh | 27 ------ test/p2p/local_testnet_stop.sh | 12 --- test/p2p/peer.sh | 53 ------------ test/p2p/persistent_peers.sh | 12 --- test/p2p/pex/check_peer.sh | 18 ---- test/p2p/pex/dial_peers.sh | 22 ----- test/p2p/pex/test.sh | 14 --- test/p2p/pex/test_addrbook.sh | 67 --------------- test/p2p/pex/test_dial_peers.sh | 38 -------- test/p2p/test.sh | 45 ---------- test/persist/test_failure_indices.sh | 124 --------------------------- test/persist/test_simple.sh | 70 --------------- test/persist/txs.sh | 23 ----- tests.mk | 43 ---------- 32 files changed, 10 insertions(+), 1325 deletions(-) delete mode 100644 .github/workflows/net.yml delete mode 100755 scripts/localnet-blocks-test.sh delete mode 100644 test/p2p/README.md delete mode 100755 test/p2p/address.sh delete mode 100644 test/p2p/atomic_broadcast/test.sh delete mode 100755 test/p2p/basic/test.sh delete mode 100644 test/p2p/circleci.sh delete mode 100644 test/p2p/client.sh delete mode 100644 test/p2p/dsrr/check_peer.sh delete mode 100644 test/p2p/dsrr/test.sh delete mode 100644 test/p2p/dsrr/test_peer.sh delete mode 100644 test/p2p/fast_sync/check_peer.sh delete mode 100644 test/p2p/fast_sync/test.sh delete mode 100644 test/p2p/fast_sync/test_peer.sh delete mode 100644 test/p2p/kill_all/check_peers.sh delete mode 100644 test/p2p/kill_all/test.sh delete mode 100644 test/p2p/local_testnet_start.sh delete mode 100644 test/p2p/local_testnet_stop.sh delete mode 100644 test/p2p/peer.sh delete mode 100644 test/p2p/persistent_peers.sh delete mode 100644 test/p2p/pex/check_peer.sh delete mode 100644 test/p2p/pex/dial_peers.sh delete mode 100644 test/p2p/pex/test.sh delete mode 100644 test/p2p/pex/test_addrbook.sh delete mode 100644 test/p2p/pex/test_dial_peers.sh delete mode 100644 test/p2p/test.sh delete mode 100644 test/persist/test_failure_indices.sh delete mode 100644 test/persist/test_simple.sh delete mode 100644 test/persist/txs.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 779d0a2881..cfcd257564 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,30 +72,6 @@ jobs: paths: - "." - test_persistence: - executor: golang - steps: - - run_test: - script_path: test/persist/test_failure_indices.sh - - test_p2p: - environment: - GOBIN: /home/circleci/.go_workspace/bin - GOPATH: /home/circleci/.go_workspace - machine: - image: circleci/classic:latest - parameters: - ipv: - type: integer - default: 4 - steps: - - checkout - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - - run: bash test/p2p/circleci.sh << parameters.ipv >> - - store_artifacts: - path: /home/circleci/project/test/p2p/logs - deploy_docs: executor: docs steps: @@ -234,7 +210,7 @@ jobs: workflows: version: 2 - test-suite: + docs: jobs: - deploy_docs: context: tendermint-docs @@ -251,24 +227,24 @@ workflows: branches: only: - docs-staging - - setup_dependencies - - test_persistence: - requires: - - setup_dependencies - - test_p2p - - test_p2p: - name: test_p2p_ipv6 - ipv: 6 # - contract_tests: # requires: # - setup_dependencies release: jobs: - - prepare_build + - prepare_build: + filters: + branches: + only: + - /v[0-9]+\.[0-9]+/ - build_artifacts: requires: - prepare_build + filters: + branches: + only: + - /v[0-9]+\.[0-9]+/ - release_artifacts: requires: - prepare_build diff --git a/.github/workflows/net.yml b/.github/workflows/net.yml deleted file mode 100644 index 55ea5542d0..0000000000 --- a/.github/workflows/net.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Net -# Net creates a 4 node test network with docker-compose -# This workflow is run on every pull request, if a *{.go, .mod, .sum} file has been modified, and push to master and release/** branches -on: - pull_request: - paths: - - "**.go" - - "**.mod" - - "**.sum" - push: - branches: - - master - - release/** - -jobs: - net-short: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v2 - - name: 10 Blocks - run: | - set -x - 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 - -# Decide if we want to run longer lived testnets diff --git a/scripts/localnet-blocks-test.sh b/scripts/localnet-blocks-test.sh deleted file mode 100755 index a33ab00f3a..0000000000 --- a/scripts/localnet-blocks-test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -ITERATIONS=$1 -SLEEP=$2 -NUMBLOCKS=$3 -NODEADDR=$4 - -if [ -z "$1" ]; then - echo "Need to input number of iterations to run..." - exit 1 -fi - -if [ -z "$2" ]; then - echo "Need to input number of seconds to sleep between iterations" - exit 1 -fi - -if [ -z "$3" ]; then - echo "Need to input block height to declare completion..." - exit 1 -fi - -if [ -z "$4" ]; then - echo "Need to input node address to poll..." - exit 1 -fi - -I=0 -while [ ${I} -lt "$ITERATIONS" ]; do - var=$(curl -s "$NODEADDR:26657/status" | jq -r ".result.sync_info.latest_block_height") - echo "Number of Blocks: ${var}" - if [ ! -z "${var}" ] && [ "${var}" -gt "${NUMBLOCKS}" ]; then - echo "Number of blocks reached, exiting success..." - exit 0 - fi - I=$((I+1)) - sleep "$SLEEP" -done - -echo "Timeout reached, exiting failure..." -exit 1 diff --git a/test/README.md b/test/README.md index 4f5b1379a9..0e0d666e54 100644 --- a/test/README.md +++ b/test/README.md @@ -14,9 +14,3 @@ and run the following tests in docker containers: - counter app over grpc - persistence tests - crash tendermint at each of many predefined points, restart, and ensure it syncs properly with the app -- p2p tests - - start a local kvstore app testnet on a docker network (requires docker version 1.10+) - - send a tx on each node and ensure the state root is updated on all of them - - crash and restart nodes one at a time and ensure they can sync back up (via fastsync) - - crash and restart all nodes at once and ensure they can sync back up - - restart each nodes with double_sign_check_height and ensure panic if the same consensus key was used to sign in double_sign_check_height blocks diff --git a/test/p2p/README.md b/test/p2p/README.md deleted file mode 100644 index 0b1f7326dd..0000000000 --- a/test/p2p/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Tendermint P2P Tests - -These scripts facilitate setting up and testing a local testnet using docker containers. - -Setup your own local testnet as follows. - -For consistency, we assume all commands are run from the Tendermint repository root. - -First, build the docker image: - -```sh -docker build -t tendermint_tester -f ./test/docker/Dockerfile . -``` - -Now create the docker network: - -```sh -docker network create --driver bridge --subnet 172.57.0.0/16 my_testnet -``` - -This gives us a new network with IP addresses in the rage `172.57.0.0 - 172.57.255.255`. -Peers on the network can have any IP address in this range. -For our four node network, let's pick `172.57.0.101 - 172.57.0.104`. -Since we use Tendermint's default listening port of 26656, our list of seed nodes will look like: - -```sh -172.57.0.101:26656,172.57.0.102:26656,172.57.0.103:26656,172.57.0.104:26656 -``` - -Now we can start up the peers. We already have config files setup in `test/p2p/data/`. -Let's use a for-loop to start our peers: - -```sh -for i in $(seq 1 4); do - docker run -d \ - --net=my_testnet\ - --ip="172.57.0.$((100 + $i))" \ - --name local_testnet_$i \ - --entrypoint tendermint \ - -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((i-1)) \ - tendermint_tester node --p2p.persistent_peers 172.57.0.101:26656,172.57.0.102:26656,172.57.0.103:26656,172.57.0.104:26656 --proxy_app=kvstore -done -``` - -If you now run `docker ps`, you'll see your containers! - -We can confirm they are making blocks by checking the `/status` message using `curl` and `jq` to pretty print the output json: - -```sh -curl 172.57.0.101:26657/status | jq . -``` - -## IPv6 tests - -IPv6 tests require a Docker daemon with IPv6 enabled, by setting the following in `daemon.json`: - -```json -{ - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" -} -``` - -In Docker for Mac, this is done via Preferences → Docker Engine. - -Once set, run IPv6 tests via `make test_p2p_ipv6`. diff --git a/test/p2p/address.sh b/test/p2p/address.sh deleted file mode 100755 index 0b0248db21..0000000000 --- a/test/p2p/address.sh +++ /dev/null @@ -1,28 +0,0 @@ -#! /bin/bash -set -eu - -IPV=$1 -ID=$2 -PORT=${3:-} -DOCKER_IMAGE=${4:-} - -if [[ "$IPV" == 6 ]]; then - ADDRESS="fd80:b10c::" -else - ADDRESS="172.57.0." -fi -ADDRESS="$ADDRESS$((100+$ID))" - -if [[ -n "$PORT" ]]; then - if [[ "$IPV" == 6 ]]; then - ADDRESS="[$ADDRESS]" - fi - ADDRESS="$ADDRESS:$PORT" -fi - -if [[ -n "$DOCKER_IMAGE" ]]; then - NODEID="$(docker run --rm -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1)) $DOCKER_IMAGE tendermint show_node_id)" - ADDRESS="$NODEID@$ADDRESS" -fi - -echo $ADDRESS \ No newline at end of file diff --git a/test/p2p/atomic_broadcast/test.sh b/test/p2p/atomic_broadcast/test.sh deleted file mode 100644 index a93067c3d9..0000000000 --- a/test/p2p/atomic_broadcast/test.sh +++ /dev/null @@ -1,76 +0,0 @@ -#! /bin/bash -set -u - -IPV=$1 -N=$2 - -################################################################### -# assumes peers are already synced up -# test sending txs -# for each peer: -# send a tx, wait for commit -# assert app hash on every peer reflects the post tx state -################################################################### - -echo "" -# run the test on each of them -for i in $(seq 1 "$N"); do - addr=$(test/p2p/address.sh $IPV $i 26657) - - # current state - HASH1=$(curl -s "$addr/status" | jq .result.sync_info.latest_app_hash) - - # - send a tx - TX=aadeadbeefbeefbeef0$i - echo "Broadcast Tx $TX" - curl -s "$addr/broadcast_tx_commit?tx=0x$TX" - echo "" - - # we need to wait another block to get the new app_hash - h1=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | jq fromjson) - h2=$h1 - while [ "$h2" == "$h1" ]; do - sleep 1 - h2=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | jq fromjson) - done - - # wait for all other peers to get to this height - minHeight=$h2 - for j in $(seq 1 "$N"); do - if [[ "$i" != "$j" ]]; then - addrJ=$(test/p2p/address.sh $IPV $j 26657) - - h=$(curl -s "$addrJ/status" | jq .result.sync_info.latest_block_height | jq fromjson) - while [ "$h" -lt "$minHeight" ]; do - sleep 1 - h=$(curl -s "$addrJ/status" | jq .result.sync_info.latest_block_height | jq fromjson) - done - fi - done - - # check that hash was updated - HASH2=$(curl -s "$addr/status" | jq .result.sync_info.latest_app_hash) - if [[ "$HASH1" == "$HASH2" ]]; then - echo "Expected state hash to update from $HASH1. Got $HASH2" - exit 1 - fi - - # check we get the same new hash on all other nodes - for j in $(seq 1 "$N"); do - if [[ "$i" != "$j" ]]; then - addrJ=$(test/p2p/address.sh $IPV $j 26657) - HASH3=$(curl -s "$addrJ/status" | jq .result.sync_info.latest_app_hash) - - if [[ "$HASH2" != "$HASH3" ]]; then - echo "App hash for node $j doesn't match. Got $HASH3, expected $HASH2" - exit 1 - fi - fi - done - - echo "All nodes are up to date" -done - -echo "" -echo "PASS" -echo "" diff --git a/test/p2p/basic/test.sh b/test/p2p/basic/test.sh deleted file mode 100755 index 676b5cbe6c..0000000000 --- a/test/p2p/basic/test.sh +++ /dev/null @@ -1,76 +0,0 @@ -#! /bin/bash -set -u - -IPV=$1 -N=$2 - -################################################################### -# wait for all peers to come online -# for each peer: -# wait to have N-1 peers -# wait to be at height > 1 -################################################################### - -# wait 60s per step per peer -MAX_SLEEP=60 - -# wait for everyone to come online -echo "Waiting for nodes to come online" -for i in `seq 1 $N`; do - addr=$(test/p2p/address.sh $IPV $i 26657) - curl -s $addr/status > /dev/null - ERR=$? - COUNT=0 - while [ "$ERR" != 0 ]; do - sleep 1 - curl -s $addr/status > /dev/null - ERR=$? - COUNT=$((COUNT+1)) - if [ "$COUNT" -gt "$MAX_SLEEP" ]; then - echo "Waited too long for node $i to come online" - exit 1 - fi - done - echo "... node $i is up" -done - -echo "" -# wait for each of them to sync up -for i in `seq 1 $N`; do - addr=$(test/p2p/address.sh $IPV $i 26657) - N_1=$(($N - 1)) - - # - assert everyone has N-1 other peers - N_PEERS=`curl -s $addr/net_info | jq '.result.peers | length'` - COUNT=0 - while [ "$N_PEERS" != $N_1 ]; do - echo "Waiting for node $i to connect to all peers ..." - sleep 1 - N_PEERS=`curl -s $addr/net_info | jq '.result.peers | length'` - COUNT=$((COUNT+1)) - if [ "$COUNT" -gt "$MAX_SLEEP" ]; then - echo "Waited too long for node $i to connect to all peers" - exit 1 - fi - done - - # - assert block height is greater than 1 - BLOCK_HEIGHT=`curl -s $addr/status | jq .result.sync_info.latest_block_height | jq fromjson` - COUNT=0 - echo "$$BLOCK_HEIGHT IS $BLOCK_HEIGHT" - while [ "$BLOCK_HEIGHT" -le 1 ]; do - echo "Waiting for node $i to commit a block ..." - sleep 1 - BLOCK_HEIGHT=`curl -s $addr/status | jq .result.sync_info.latest_block_height | jq fromjson` - COUNT=$((COUNT+1)) - if [ "$COUNT" -gt "$MAX_SLEEP" ]; then - echo "Waited too long for node $i to commit a block" - exit 1 - fi - done - echo "Node $i is connected to all peers and at block $BLOCK_HEIGHT" -done - -echo "" -echo "PASS" -echo "" diff --git a/test/p2p/circleci.sh b/test/p2p/circleci.sh deleted file mode 100644 index fe8a972bdd..0000000000 --- a/test/p2p/circleci.sh +++ /dev/null @@ -1,55 +0,0 @@ -#! /bin/bash -set -eux - -# Take IP version as parameter -IPV="${1:-4}" - -# Get the directory of where this script is. -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - -# Enable IPv6 support in Docker daemon -if [[ "$IPV" == "6" ]]; then - echo - echo "* [$(date +"%T")] enabling IPv6 stack in Docker daemon" - cat <<'EOF' | sudo tee /etc/docker/daemon.json -{ - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" -} -EOF - sudo service docker restart -fi - -LOGS_DIR="$DIR/logs" -echo -echo "* [$(date +"%T")] cleaning up $LOGS_DIR" -rm -rf "$LOGS_DIR" -mkdir -p "$LOGS_DIR" - -set +e -echo -echo "* [$(date +"%T")] removing run_test container" -docker rm -vf run_test -set -e - -echo -echo "* [$(date +"%T")] starting rsyslog container" -docker rm -f rsyslog || true -docker run -d -v "$LOGS_DIR:/var/log/" -p 127.0.0.1:5514:514/udp --name rsyslog voxxit/rsyslog - -set +u -if [[ "$SKIP_BUILD" == "" ]]; then - echo - echo "* [$(date +"%T")] building docker image" - bash "$DIR/../docker/build.sh" -fi - -echo -echo "* [$(date +"%T")] running IPv$IPV p2p tests on a local docker network" -bash "$DIR/../p2p/test.sh" tester $IPV - -echo -echo "* [$(date +"%T")] copying log files out of docker container into $LOGS_DIR" -docker cp rsyslog:/var/log $LOGS_DIR diff --git a/test/p2p/client.sh b/test/p2p/client.sh deleted file mode 100644 index b3c907fbac..0000000000 --- a/test/p2p/client.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -ID=$4 -CMD=$5 - -NAME=test_container_$ID - -if [[ "$IPV" == 6 ]]; then - IP_SWITCH="--ip6" -else - IP_SWITCH="--ip" -fi - -echo "starting test client container with CMD=$CMD" -# run the test container on the local network -docker run -t --rm \ - -v "$PWD/test/p2p/:/go/src/github.com/tendermint/tendermint/test/p2p" \ - --net="$NETWORK_NAME" \ - $IP_SWITCH=$(test/p2p/address.sh $IPV -1) \ - --name "$NAME" \ - --entrypoint bash \ - "$DOCKER_IMAGE" $CMD diff --git a/test/p2p/dsrr/check_peer.sh b/test/p2p/dsrr/check_peer.sh deleted file mode 100644 index fdd6bd3a31..0000000000 --- a/test/p2p/dsrr/check_peer.sh +++ /dev/null @@ -1,64 +0,0 @@ -#! /bin/bash -set -eu -set -o pipefail - -IPV=$1 -ID=$2 -ASSERT_CASE=$3 -ASSERT_NODE_UP=1 -ASSERT_NODE_DOWN=0 -MAX_TRY=10 - - -########################################### -# -# Wait for peer to catchup to other peers -# -########################################### - -addr=$(test/p2p/address.sh $IPV $ID 26657) -peerID=$(( $(($ID % 4)) + 1 )) # 1->2 ... 3->4 ... 4->1 -peer_addr=$(test/p2p/address.sh $IPV $peerID 26657) - -# get another peer's height -h1=`curl -s $peer_addr/status | jq .result.sync_info.latest_block_height | jq fromjson` - -# get another peer's state -root1=`curl -s $peer_addr/status | jq .result.sync_info.latest_app_hash` - -echo "Other peer is on height $h1 with state $root1" -echo "Waiting for peer $ID to catch up" - -# wait for it to sync to past its previous height -set +e -set +o pipefail -h2="0" -COUNT=0 -while [[ "$h2" -lt "$(($h1+1))" ]]; do - sleep 1 - h2=`curl -s $addr/status --connect-timeout 1 | jq .result.sync_info.latest_block_height | jq fromjson` - COUNT=$((COUNT+1)) - echo "... $h2, try $COUNT" - if [ "$COUNT" -ge "$MAX_TRY" ]; then - if [ $ASSERT_CASE -eq $ASSERT_NODE_DOWN ]; then - echo "double sign risk reduction operates normally as expected" - fi - if [ $ASSERT_CASE -eq $ASSERT_NODE_UP ]; then - echo "double sign risk reduction fail" - exit 1 - fi - break - fi -done - - -if [ $ASSERT_CASE -eq $ASSERT_NODE_UP ]; then - # check the app hash - root2=`curl -s $addr/status | jq .result.sync_info.latest_app_hash` - - if [[ "$root1" != "$root2" ]]; then - echo "App hash after restart does not match. Got $root2; expected $root1" - exit 1 - fi - echo "... double sign risk reduction test passed" -fi diff --git a/test/p2p/dsrr/test.sh b/test/p2p/dsrr/test.sh deleted file mode 100644 index 6f28f6c06a..0000000000 --- a/test/p2p/dsrr/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -PROXY_APP=$5 - -# run it on each of them -for i in `seq 1 $N`; do - bash test/p2p/dsrr/test_peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $i $N $PROXY_APP -done diff --git a/test/p2p/dsrr/test_peer.sh b/test/p2p/dsrr/test_peer.sh deleted file mode 100644 index 4194c3c9a7..0000000000 --- a/test/p2p/dsrr/test_peer.sh +++ /dev/null @@ -1,65 +0,0 @@ -#! /bin/bash -set -eu -set -o pipefail - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -ID=$4 -N=$5 -PROXY_APP=$6 -ASSERT_NODE_UP=1 -ASSERT_NODE_DOWN=0 - -###########################s#################################### -# this runs on each peer: -# kill peer -# bring it back online with double_sign_check_height 10 -# wait node is not run by double sign risk reduction -# -# kill peer -# bring it back online with double_sign_check_height 1 -# pass double sign risk reduction, wait for it to sync and check the app hash -# -# kill peer -# bring it back online with double_sign_check_height 0 -# wait for it to sync and check the app hash -############################################################### - -echo "Testing double sign risk reduction on node $ID" - -# kill peer -set +e - docker rm -vf local_testnet_$ID - set -e - PERSISTENT_PEERS="$(test/p2p/address.sh $IPV 1 26656 $DOCKER_IMAGE)" - for j in `seq 2 $N`; do - PERSISTENT_PEERS="$PERSISTENT_PEERS,$(test/p2p/address.sh $IPV $j 26656 $DOCKER_IMAGE)" - done - - # bring it back online with double_sign_check_height 10 - # wait node is not run by double sign risk reduction - DSCH=10 - bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $ID $PROXY_APP "--p2p.persistent_peers $PERSISTENT_PEERS --p2p.pex --rpc.unsafe --consensus.double_sign_check_height $DSCH" - bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV fs_$ID "test/p2p/dsrr/check_peer.sh $IPV $ID $ASSERT_NODE_DOWN" - - - docker stop local_testnet_$ID - docker rm local_testnet_$ID - # bring it back online with double_sign_check_height 1 - # pass double sign risk reduction, wait for it to sync and check the app hash - DSCH=1 - bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $ID $PROXY_APP "--p2p.persistent_peers $PERSISTENT_PEERS --p2p.pex --rpc.unsafe --consensus.double_sign_check_height $DSCH" - bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV fs_$ID "test/p2p/dsrr/check_peer.sh $IPV $ID $ASSERT_NODE_UP" - - docker stop local_testnet_$ID - docker rm local_testnet_$ID - DSCH=0 - # bring it back online with double_sign_check_height 0 - # double sign risk reduction is not activated, wait for it to sync and check the app hash - bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $ID $PROXY_APP "--p2p.persistent_peers $PERSISTENT_PEERS --p2p.pex --rpc.unsafe --consensus.double_sign_check_height $DSCH" - bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV fs_$ID "test/p2p/dsrr/check_peer.sh $IPV $ID $ASSERT_NODE_UP" - - echo "" - echo "PASS" - echo "" diff --git a/test/p2p/fast_sync/check_peer.sh b/test/p2p/fast_sync/check_peer.sh deleted file mode 100644 index 798b508fa8..0000000000 --- a/test/p2p/fast_sync/check_peer.sh +++ /dev/null @@ -1,44 +0,0 @@ -#! /bin/bash -set -eu -set -o pipefail - -IPV=$1 -ID=$2 - -########################################### -# -# Wait for peer to catchup to other peers -# -########################################### - -addr=$(test/p2p/address.sh $IPV $ID 26657) -peerID=$(( $(($ID % 4)) + 1 )) # 1->2 ... 3->4 ... 4->1 -peer_addr=$(test/p2p/address.sh $IPV $peerID 26657) - -# get another peer's height -h1=`curl -s $peer_addr/status | jq .result.sync_info.latest_block_height | jq fromjson` - -# get another peer's state -root1=`curl -s $peer_addr/status | jq .result.sync_info.latest_app_hash` - -echo "Other peer is on height $h1 with state $root1" -echo "Waiting for peer $ID to catch up" - -# wait for it to sync to past its previous height -set +e -set +o pipefail -h2="0" -while [[ "$h2" -lt "$(($h1+3))" ]]; do - sleep 1 - h2=`curl -s $addr/status | jq .result.sync_info.latest_block_height | jq fromjson` - echo "... $h2" -done - -# check the app hash -root2=`curl -s $addr/status | jq .result.sync_info.latest_app_hash` - -if [[ "$root1" != "$root2" ]]; then - echo "App hash after fast sync does not match. Got $root2; expected $root1" - exit 1 -fi -echo "... fast sync successful" diff --git a/test/p2p/fast_sync/test.sh b/test/p2p/fast_sync/test.sh deleted file mode 100644 index 79655232fa..0000000000 --- a/test/p2p/fast_sync/test.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -PROXY_APP=$5 - -# run it on each of them -for i in `seq 1 $N`; do - bash test/p2p/fast_sync/test_peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $i $N $PROXY_APP -done - - diff --git a/test/p2p/fast_sync/test_peer.sh b/test/p2p/fast_sync/test_peer.sh deleted file mode 100644 index b4c34336fd..0000000000 --- a/test/p2p/fast_sync/test_peer.sh +++ /dev/null @@ -1,39 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -ID=$4 -N=$5 -PROXY_APP=$6 - -############################################################### -# this runs on each peer: -# kill peer -# bring it back online via fast sync -# wait for it to sync and check the app hash -############################################################### - - -echo "Testing fastsync on node $ID" - -# kill peer -set +e # circle sigh :( - docker rm -vf local_testnet_$ID - set -e - - # restart peer - should have an empty blockchain - PERSISTENT_PEERS="$(test/p2p/address.sh $IPV 1 26656 $DOCKER_IMAGE)" - for j in `seq 2 $N`; do - PERSISTENT_PEERS="$PERSISTENT_PEERS,$(test/p2p/address.sh $IPV $j 26656 $DOCKER_IMAGE)" - done - bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $ID $PROXY_APP "--p2p.persistent_peers $PERSISTENT_PEERS --p2p.pex --rpc.unsafe" - - # wait for peer to sync and check the app hash - bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV fs_$ID "test/p2p/fast_sync/check_peer.sh $IPV $ID" - - echo "" - echo "PASS" - echo "" - diff --git a/test/p2p/kill_all/check_peers.sh b/test/p2p/kill_all/check_peers.sh deleted file mode 100644 index 504cdeddd2..0000000000 --- a/test/p2p/kill_all/check_peers.sh +++ /dev/null @@ -1,50 +0,0 @@ -#! /bin/bash -set -eu - -IPV=$1 -NUM_OF_PEERS=$2 - -# how many attempts for each peer to catch up by height -MAX_ATTEMPTS_TO_CATCH_UP=120 - -echo "Waiting for nodes to come online" -set +e -for i in $(seq 1 "$NUM_OF_PEERS"); do - addr=$(test/p2p/address.sh $IPV $i 26657) - curl -s "$addr/status" > /dev/null - ERR=$? - while [ "$ERR" != 0 ]; do - sleep 1 - curl -s "$addr/status" > /dev/null - ERR=$? - done - echo "... node $i is up" -done -set -e - -# get the first peer's height -addr=$(test/p2p/address.sh $IPV 1 26657) -h1=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | sed -e "s/^\"\(.*\)\"$/\1/g") -echo "1st peer is on height $h1" - -echo "Waiting until other peers reporting a height higher than the 1st one" -for i in $(seq 2 "$NUM_OF_PEERS"); do - attempt=1 - hi=0 - - while [[ $hi -le $h1 ]] ; do - addr=$(test/p2p/address.sh $IPV $i 26657) - hi=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | sed -e "s/^\"\(.*\)\"$/\1/g") - - echo "... peer $i is on height $hi" - - ((attempt++)) - if [ "$attempt" -ge $MAX_ATTEMPTS_TO_CATCH_UP ] ; then - echo "$attempt unsuccessful attempts were made to catch up" - curl -s "$addr/dump_consensus_state" | jq .result - exit 1 - fi - - sleep 1 - done -done diff --git a/test/p2p/kill_all/test.sh b/test/p2p/kill_all/test.sh deleted file mode 100644 index 7556121309..0000000000 --- a/test/p2p/kill_all/test.sh +++ /dev/null @@ -1,31 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -NUM_OF_PEERS=$4 -NUM_OF_CRASHES=$5 - -############################################################### -# NUM_OF_CRASHES times: -# restart all peers -# wait for them to sync and check that they are making progress -############################################################### - -for i in $(seq 1 "$NUM_OF_CRASHES"); do - echo "" - echo "Restarting all peers! Take $i ..." - - # restart all peers - for j in $(seq 1 "$NUM_OF_PEERS"); do - docker stop "local_testnet_$j" - docker start "local_testnet_$j" - done - - bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" kill_all_$i "test/p2p/kill_all/check_peers.sh $IPV $NUM_OF_PEERS" -done - -echo "" -echo "PASS" -echo "" diff --git a/test/p2p/local_testnet_start.sh b/test/p2p/local_testnet_start.sh deleted file mode 100644 index 8da6be4bbe..0000000000 --- a/test/p2p/local_testnet_start.sh +++ /dev/null @@ -1,27 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -APP_PROXY=$5 - -set +u -PERSISTENT_PEERS=$6 -if [[ "$PERSISTENT_PEERS" != "" ]]; then - echo "PersistentPeers: $PERSISTENT_PEERS" - PERSISTENT_PEERS="--p2p.persistent_peers $PERSISTENT_PEERS" -fi -set -u - -# create docker network -if [[ $IPV == 6 ]]; then - docker network create --driver bridge --ipv6 --subnet fd80:b10c::/48 "$NETWORK_NAME" -else - docker network create --driver bridge --subnet 172.57.0.0/16 "$NETWORK_NAME" -fi - -for i in $(seq 1 "$N"); do - bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" $IPV "$i" "$APP_PROXY" "$PERSISTENT_PEERS --p2p.pex --rpc.unsafe" -done diff --git a/test/p2p/local_testnet_stop.sh b/test/p2p/local_testnet_stop.sh deleted file mode 100644 index 1dace4694f..0000000000 --- a/test/p2p/local_testnet_stop.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash -set -u - -NETWORK_NAME=$1 -N=$2 - -for i in $(seq 1 "$N"); do - docker stop "local_testnet_$i" - docker rm -vf "local_testnet_$i" -done - -docker network rm "$NETWORK_NAME" diff --git a/test/p2p/peer.sh b/test/p2p/peer.sh deleted file mode 100644 index bf146ca1be..0000000000 --- a/test/p2p/peer.sh +++ /dev/null @@ -1,53 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -ID=$4 -APP_PROXY=$5 - -set +u -NODE_FLAGS=$6 -set -u - -if [[ "$IPV" == 6 ]]; then - IP_SWITCH="--ip6" -else - IP_SWITCH="--ip" -fi - -echo "starting tendermint peer ID=$ID" -# start tendermint container on the network -# NOTE: $NODE_FLAGS should be unescaped (no quotes). otherwise it will be -# treated as one flag. - -# test/p2p/data/mach$((ID-1)) data is generated in test/docker/Dockerfile using -# the tendermint testnet command. -if [[ "$ID" == "x" ]]; then # Set "x" to "1" to print to console. - docker run \ - --net="$NETWORK_NAME" \ - $IP_SWITCH=$(test/p2p/address.sh $IPV $ID) \ - --name "local_testnet_$ID" \ - --entrypoint tendermint \ - -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1))" \ - -e GOMAXPROCS=1 \ - --log-driver=syslog \ - --log-opt syslog-address=udp://127.0.0.1:5514 \ - --log-opt syslog-facility=daemon \ - --log-opt tag="{{.Name}}" \ - "$DOCKER_IMAGE" node $NODE_FLAGS --log_level=debug --proxy_app="$APP_PROXY" & -else - docker run -d \ - --net="$NETWORK_NAME" \ - $IP_SWITCH=$(test/p2p/address.sh $IPV $ID) \ - --name "local_testnet_$ID" \ - --entrypoint tendermint \ - -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1))" \ - -e GOMAXPROCS=1 \ - --log-driver=syslog \ - --log-opt syslog-address=udp://127.0.0.1:5514 \ - --log-opt syslog-facility=daemon \ - --log-opt tag="{{.Name}}" \ - "$DOCKER_IMAGE" node $NODE_FLAGS --log_level=debug --proxy_app="$APP_PROXY" -fi diff --git a/test/p2p/persistent_peers.sh b/test/p2p/persistent_peers.sh deleted file mode 100644 index a1e76991a5..0000000000 --- a/test/p2p/persistent_peers.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash -set -eu - -IPV=$1 -N=$2 -DOCKER_IMAGE=$3 - -persistent_peers="$(test/p2p/address.sh $IPV 1 26656 $DOCKER_IMAGE)" -for i in $(seq 2 $N); do - persistent_peers="$persistent_peers,$(test/p2p/address.sh $IPV $i 26656 $DOCKER_IMAGE)" -done -echo "$persistent_peers" diff --git a/test/p2p/pex/check_peer.sh b/test/p2p/pex/check_peer.sh deleted file mode 100644 index 93499d9f9f..0000000000 --- a/test/p2p/pex/check_peer.sh +++ /dev/null @@ -1,18 +0,0 @@ -#! /bin/bash -set -u - -IPV=$1 -ID=$2 -N=$3 - -addr=$(test/p2p/address.sh $IPV "$ID" 26657) - -echo "2. wait until peer $ID connects to other nodes using pex reactor" -peers_count="0" -while [[ "$peers_count" -lt "$((N-1))" ]]; do - sleep 1 - peers_count=$(curl -s "$addr/net_info" | jq ".result.peers | length") - echo "... peers count = $peers_count, expected = $((N-1))" -done - -echo "... successful" diff --git a/test/p2p/pex/dial_peers.sh b/test/p2p/pex/dial_peers.sh deleted file mode 100644 index 8c4d40f442..0000000000 --- a/test/p2p/pex/dial_peers.sh +++ /dev/null @@ -1,22 +0,0 @@ -#! /bin/bash -set -u - -IPV=$1 -N=$2 -PEERS=$3 - -echo "Waiting for nodes to come online" -for i in $(seq 1 "$N"); do - addr=$(test/p2p/address.sh $IPV $i 26657) - curl -s "$addr/status" > /dev/null - ERR=$? - while [ "$ERR" != 0 ]; do - sleep 1 - curl -s "$addr/status" > /dev/null - ERR=$? - done - echo "... node $i is up" -done - -ADDR=$(test/p2p/address.sh $IPV 1 26657) -curl "$ADDR/dial_peers?persistent=true&peers=\\[$PEERS\\]" diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh deleted file mode 100644 index 1e87a9fa5a..0000000000 --- a/test/p2p/pex/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -PROXY_APP=$5 - -echo "Test reconnecting from the address book" -bash test/p2p/pex/test_addrbook.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" - -echo "Test connecting via /dial_peers" -bash test/p2p/pex/test_dial_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh deleted file mode 100644 index 06fc4215fa..0000000000 --- a/test/p2p/pex/test_addrbook.sh +++ /dev/null @@ -1,67 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -PROXY_APP=$5 - -ID=1 - -echo "----------------------------------------------------------------------" -echo "Testing pex creates the addrbook and uses it if persistent_peers are not provided" -echo "(assuming peers are started with pex enabled)" - -CLIENT_NAME="pex_addrbook_$ID" - -echo "1. restart peer $ID" -docker stop "local_testnet_$ID" -echo "stopped local_testnet_$ID" -# preserve addrbook.json -docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" "/tmp/addrbook.json" -set +e #CIRCLE -docker rm -vf "local_testnet_$ID" -set -e - -# NOTE that we do not provide persistent_peers -bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" $IPV "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" -echo "started local_testnet_$ID" - -# if the client runs forever, it means addrbook wasn't saved or was empty -bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $IPV $ID $N" - -# Now we know that the node is up. - -docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" -echo "with the following addrbook:" -cat /tmp/addrbook.json -# exec doesn't work on circle -# docker exec "local_testnet_$ID" cat "/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" -echo "" - -echo "----------------------------------------------------------------------" -echo "Testing other peers connect to us if we have neither persistent_peers nor the addrbook" -echo "(assuming peers are started with pex enabled)" - -CLIENT_NAME="pex_no_addrbook_$ID" - -echo "1. restart peer $ID" -docker stop "local_testnet_$ID" -echo "stopped local_testnet_$ID" -set +e #CIRCLE -docker rm -vf "local_testnet_$ID" -set -e - -# NOTE that we do not provide persistent_peers -bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" $IPV "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" -echo "started local_testnet_$ID" - -# if the client runs forever, it means other peers have removed us from their books (which should not happen) -bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $IPV $ID $N" - -# Now we know that the node is up. - -echo "" -echo "PASS" -echo "" diff --git a/test/p2p/pex/test_dial_peers.sh b/test/p2p/pex/test_dial_peers.sh deleted file mode 100644 index af76a56998..0000000000 --- a/test/p2p/pex/test_dial_peers.sh +++ /dev/null @@ -1,38 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=$2 -IPV=$3 -N=$4 -PROXY_APP=$5 - -ID=1 - -echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_peers call" -echo "(assuming peers are started with pex enabled)" - -# stop the existing testnet and remove local network -set +e -bash test/p2p/local_testnet_stop.sh $NETWORK_NAME $N -set -e - -# start the testnet on a local network -# NOTE we re-use the same network for all tests -bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $N $PROXY_APP "" - -PERSISTENT_PEERS="\"$(test/p2p/address.sh $IPV 1 26656 $DOCKER_IMAGE)\"" -for i in $(seq 2 $N); do - PERSISTENT_PEERS="$PERSISTENT_PEERS,\"$(test/p2p/address.sh $IPV $i 26656 $DOCKER_IMAGE)\"" -done -echo "$PERSISTENT_PEERS" - -# dial peers from one node -CLIENT_NAME="dial_peers" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $CLIENT_NAME "test/p2p/pex/dial_peers.sh $IPV $N $PERSISTENT_PEERS" - -# test basic connectivity and consensus -# start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_peers_basic" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $IPV $CLIENT_NAME "test/p2p/basic/test.sh $IPV $N" diff --git a/test/p2p/test.sh b/test/p2p/test.sh deleted file mode 100644 index 3703a67802..0000000000 --- a/test/p2p/test.sh +++ /dev/null @@ -1,45 +0,0 @@ -#! /bin/bash -set -eu - -DOCKER_IMAGE=$1 -NETWORK_NAME=local_testnet -N=4 -PROXY_APP=persistent_kvstore -IPV=${2:-4} # Default to IPv4 - -if [[ "$IPV" != "4" && "$IPV" != "6" ]]; then - echo "IP version must be 4 or 6" >&2 - exit 1 -fi - -# stop the existing testnet and remove local network -set +e -bash test/p2p/local_testnet_stop.sh "$NETWORK_NAME" "$N" -set -e - -PERSISTENT_PEERS=$(bash test/p2p/persistent_peers.sh $IPV $N $DOCKER_IMAGE) - -# start the testnet on a local network -# NOTE we re-use the same network for all tests -bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" "$PERSISTENT_PEERS" - -# test basic connectivity and consensus -# start client container and check the num peers and height for all nodes -bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" basic "test/p2p/basic/test.sh $IPV $N" - -# test atomic broadcast: -# start client container and test sending a tx to each node -bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" ab "test/p2p/atomic_broadcast/test.sh $IPV $N" - -# test fast sync (from current state of network): -# for each node, kill it and readd via fast sync -bash test/p2p/fast_sync/test.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" - -# test double sign risk reduction for each node -bash test/p2p/dsrr/test.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" - -# test killing all peers 3 times -bash test/p2p/kill_all/test.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" 3 - -# test pex -bash test/p2p/pex/test.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$IPV" "$N" "$PROXY_APP" diff --git a/test/persist/test_failure_indices.sh b/test/persist/test_failure_indices.sh deleted file mode 100644 index 4d523d943e..0000000000 --- a/test/persist/test_failure_indices.sh +++ /dev/null @@ -1,124 +0,0 @@ -#! /bin/bash - -export PATH="$GOBIN:$PATH" -export TMHOME=$HOME/.tendermint_persist - -rm -rf "$TMHOME" -tendermint init - -# use a unix socket so we can remove it -RPC_ADDR="$(pwd)/rpc.sock" - -TM_CMD="tendermint node --log_level=debug --rpc.laddr=unix://$RPC_ADDR" # &> tendermint_${name}.log" -DUMMY_CMD="abci-cli kvstore --persist $TMHOME/kvstore" # &> kvstore_${name}.log" - - -function start_procs(){ - name=$1 - indexToFail=$2 - echo "Starting persistent kvstore and tendermint" - if [[ "$CIRCLECI" == true ]]; then - $DUMMY_CMD & - else - $DUMMY_CMD &> "kvstore_${name}.log" & - fi - PID_DUMMY=$! - - # before starting tendermint, remove the rpc socket - rm -f $RPC_ADDR - if [[ "$indexToFail" == "" ]]; then - # run in background, dont fail - if [[ "$CIRCLECI" == true ]]; then - $TM_CMD & - else - $TM_CMD &> "tendermint_${name}.log" & - fi - PID_TENDERMINT=$! - else - # run in foreground, fail - if [[ "$CIRCLECI" == true ]]; then - FAIL_TEST_INDEX=$indexToFail $TM_CMD - else - FAIL_TEST_INDEX=$indexToFail $TM_CMD &> "tendermint_${name}.log" - fi - PID_TENDERMINT=$! - fi -} - -function kill_procs(){ - kill -9 "$PID_DUMMY" "$PID_TENDERMINT" - wait "$PID_DUMMY" - wait "$PID_TENDERMINT" -} - -# wait for port to be available -function wait_for_port() { - port=$1 - # this will succeed while port is bound - nc -z 127.0.0.1 $port - ERR=$? - i=0 - while [ "$ERR" == 0 ]; do - echo "... port $port is still bound. waiting ..." - sleep 1 - nc -z 127.0.0.1 $port - ERR=$? - i=$((i + 1)) - if [[ $i == 10 ]]; then - echo "Timed out waiting for port to be released" - exit 1 - fi - done - echo "... port $port is free!" -} - - -failsStart=0 -fails=$(grep -r "fail.Fail" --include \*.go . | wc -l) -failsEnd=$((fails-1)) - -for failIndex in $(seq $failsStart $failsEnd); do - echo "" - echo "* Test FailIndex $failIndex" - # test failure at failIndex - - bash $(dirname $0)/txs.sh "localhost:26657" & - start_procs 1 "$failIndex" - - # tendermint should already have exited when it hits the fail index - # but kill -9 for good measure - kill_procs - - start_procs 2 - - # wait for node to handshake and make a new block - # NOTE: --unix-socket is only available in curl v7.40+ - curl -s --unix-socket "$RPC_ADDR" http://localhost/status > /dev/null - ERR=$? - i=0 - while [ "$ERR" != 0 ]; do - sleep 1 - curl -s --unix-socket "$RPC_ADDR" http://localhost/status > /dev/null - ERR=$? - i=$((i + 1)) - if [[ $i == 20 ]]; then - echo "Timed out waiting for tendermint to start" - exit 1 - fi - done - - # wait for a new block - h1=$(curl -s --unix-socket "$RPC_ADDR" http://localhost/status | jq .result.sync_info.latest_block_height) - h2=$h1 - while [ "$h2" == "$h1" ]; do - sleep 1 - h2=$(curl -s --unix-socket "$RPC_ADDR" http://localhost/status | jq .result.sync_info.latest_block_height) - done - - kill_procs - - echo "* Passed Test for FailIndex $failIndex" - echo "" -done - -echo "Passed Test: Persistence" diff --git a/test/persist/test_simple.sh b/test/persist/test_simple.sh deleted file mode 100644 index 706e04c260..0000000000 --- a/test/persist/test_simple.sh +++ /dev/null @@ -1,70 +0,0 @@ -#! /bin/bash - - -export TMHOME=$HOME/.tendermint_persist - -rm -rf $TMHOME -tendermint init - -function start_procs(){ - name=$1 - echo "Starting persistent kvstore and tendermint" - abci-cli kvstore --persist $TMHOME/kvstore &> "kvstore_${name}.log" & - PID_DUMMY=$! - tendermint node &> tendermint_${name}.log & - PID_TENDERMINT=$! - sleep 5 -} - -function kill_procs(){ - kill -9 $PID_DUMMY $PID_TENDERMINT -} - - -function send_txs(){ - # send a bunch of txs over a few blocks - echo "Sending txs" - for i in `seq 1 5`; do - for j in `seq 1 100`; do - tx=`head -c 8 /dev/urandom | hexdump -ve '1/1 "%.2X"'` - curl -s 127.0.0.1:26657/broadcast_tx_async?tx=0x$tx &> /dev/null - done - sleep 1 - done -} - - -start_procs 1 -send_txs -kill_procs - -start_procs 2 - -# wait for node to handshake and make a new block -addr="localhost:26657" -curl -s $addr/status > /dev/null -ERR=$? -i=0 -while [ "$ERR" != 0 ]; do - sleep 1 - curl -s $addr/status > /dev/null - ERR=$? - i=$(($i + 1)) - if [[ $i == 10 ]]; then - echo "Timed out waiting for tendermint to start" - exit 1 - fi -done - -# wait for a new block -h1=`curl -s $addr/status | jq .result.sync_info.latest_block_height` -h2=$h1 -while [ "$h2" == "$h1" ]; do - sleep 1 - h2=`curl -s $addr/status | jq .result.sync_info.latest_block_height` -done - -kill_procs -sleep 2 - -echo "Passed Test: Persistence" diff --git a/test/persist/txs.sh b/test/persist/txs.sh deleted file mode 100644 index 120aa8a567..0000000000 --- a/test/persist/txs.sh +++ /dev/null @@ -1,23 +0,0 @@ -#! /bin/bash -set -u - -# wait till node is up, send txs -ADDR=$1 #="127.0.0.1:26657" -curl -s $ADDR/status > /dev/null -ERR=$? -while [ "$ERR" != 0 ]; do - sleep 1 - curl -s $ADDR/status > /dev/null - ERR=$? -done - -# send a bunch of txs over a few blocks -echo "Node is up, sending txs" -for i in $(seq 1 5); do - for _ in $(seq 1 100); do - tx=$(head -c 8 /dev/urandom | hexdump -ve '1/1 "%.2X"') - curl -s "$ADDR/broadcast_tx_async?tx=0x$tx" &> /dev/null - done - echo "sent 100" - sleep 1 -done diff --git a/tests.mk b/tests.mk index 56386d6c85..e4431d9357 100644 --- a/tests.mk +++ b/tests.mk @@ -33,45 +33,6 @@ test_abci_cli: @ bash abci/tests/test_cli/test.sh .PHONY: test_abci_cli -test_persistence: - # run the persistence tests using bash - # requires `abci-cli` installed - docker run --name run_persistence -t tester bash test/persist/test_failure_indices.sh - - # TODO undockerize - # bash test/persist/test_failure_indices.sh -.PHONY: test_persistence - -test_p2p: - docker rm -f rsyslog || true - rm -rf test/logs && mkdir -p test/logs - docker run -d -v "$(CURDIR)/test/logs:/var/log/" -p 127.0.0.1:5514:514/udp --name rsyslog voxxit/rsyslog - # requires 'tester' the image from above - bash test/p2p/test.sh tester - # the `docker cp` takes a really long time; uncomment for debugging - # - # mkdir -p test/p2p/logs && docker cp rsyslog:/var/log test/p2p/logs -.PHONY: test_p2p - -test_p2p_ipv6: - # IPv6 tests require Docker daemon with IPv6 enabled, e.g. in daemon.json: - # - # { - # "ipv6": true, - # "fixed-cidr-v6": "2001:db8:1::/64" - # } - # - # Docker for Mac can set this via Preferences -> Docker Engine. - docker rm -f rsyslog || true - rm -rf test/logs && mkdir -p test/logs - docker run -d -v "$(CURDIR)/test/logs:/var/log/" -p 127.0.0.1:5514:514/udp --name rsyslog voxxit/rsyslog - # requires 'tester' the image from above - bash test/p2p/test.sh tester 6 - # the `docker cp` takes a really long time; uncomment for debugging - # - # mkdir -p test/p2p/logs && docker cp rsyslog:/var/log test/p2p/logs -.PHONY: test_p2p_ipv6 - test_integrations: make build_docker_test_image make tools @@ -81,10 +42,6 @@ test_integrations: make test_abci_apps make test_abci_cli make test_libs - make test_persistence - make test_p2p - # Disabled by default since it requires Docker daemon with IPv6 enabled - #make test_p2p_ipv6 .PHONY: test_integrations test_release: From 3a4a6ae9ac1c2684f25fa060a8b9cfb41fbaf5a3 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 6 Oct 2020 08:00:20 +0200 Subject: [PATCH 029/108] test: add E2E test for node peering (#5465) This was a missing test case from the old P2P tests removed in #5453, which makes sure that all nodes are able to peer with each other regardless of how they discover peers. Fixes #2795, since the default CI testnet uses a combination of (partially meshed) persistent peers and PEX-based seed nodes. --- test/e2e/Makefile | 7 +++---- test/e2e/networks/ci.toml | 9 ++++++-- test/e2e/runner/main.go | 4 ++-- test/e2e/tests/net_test.go | 42 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 test/e2e/tests/net_test.go diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 858335e3f2..5fbbed91a4 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -1,9 +1,8 @@ +all: docker runner + docker: docker build --tag tendermint/e2e-node -f docker/Dockerfile ../.. -ci: runner - ./build/runner -f networks/ci.toml - # We need to build support for database backends into the app in # order to build a binary with a Tendermint node in it (for built-in # ABCI testing). @@ -13,4 +12,4 @@ app: runner: go build -o build/runner ./runner -.PHONY: app ci docker runner +.PHONY: app docker runner diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 94ddecb02b..e366bb6c58 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -26,6 +26,11 @@ validator05 = 50 [node.seed01] mode = "seed" +persistent_peers = ["seed02"] + +[node.seed02] +mode = "seed" +persistent_peers = ["seed01"] [node.validator01] seeds = ["seed01"] @@ -33,7 +38,7 @@ snapshot_interval = 5 perturb = ["disconnect"] [node.validator02] -seeds = ["seed01"] +seeds = ["seed02"] database = "boltdb" abci_protocol = "tcp" privval_protocol = "tcp" @@ -64,7 +69,7 @@ perturb = ["pause"] [node.validator05] start_at = 1005 # Becomes part of the validator set at 1010 -seeds = ["seed01"] +seeds = ["seed02"] database = "cleveldb" fast_sync = "v0" # FIXME Should use grpc, but it has race conditions diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index d58305c9b9..ab14349d01 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -68,7 +68,7 @@ func NewCLI() *CLI { if err := Perturb(cli.testnet); err != nil { return err } - if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle + if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through return err } @@ -76,7 +76,7 @@ func NewCLI() *CLI { if err := <-chLoadResult; err != nil { return err } - if err := Wait(cli.testnet, 3); err != nil { // wait for last txs to commit + if err := Wait(cli.testnet, 10); err != nil { // wait for network to settle before tests return err } if err := Test(cli.testnet); err != nil { diff --git a/test/e2e/tests/net_test.go b/test/e2e/tests/net_test.go new file mode 100644 index 0000000000..43d155236f --- /dev/null +++ b/test/e2e/tests/net_test.go @@ -0,0 +1,42 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +// Tests that all nodes have peered with each other, regardless of discovery method. +func TestNet_Peers(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + // Seed nodes shouldn't necessarily mesh with the entire network. + if node.Mode == e2e.ModeSeed { + return + } + + client, err := node.Client() + require.NoError(t, err) + netInfo, err := client.NetInfo(ctx) + require.NoError(t, err) + + require.Equal(t, len(node.Testnet.Nodes)-1, netInfo.NPeers, + "node is not fully meshed with peers") + + seen := map[string]bool{} + for _, n := range node.Testnet.Nodes { + seen[n.Name] = (n.Name == node.Name) // we've clearly seen ourself + } + for _, peerInfo := range netInfo.Peers { + peer := node.Testnet.LookupNode(peerInfo.NodeInfo.Moniker) + require.NotNil(t, peer, "unknown node %v", peerInfo.NodeInfo.Moniker) + require.Equal(t, peer.IP.String(), peerInfo.RemoteIP, + "unexpected IP address for peer %v", peer.Name) + seen[peerInfo.NodeInfo.Moniker] = true + } + + for name := range seen { + require.True(t, seen[name], "node %v not peered with %v", node.Name, name) + } + }) +} From e7568f9e0cc067a59ab66de686d4734f9871d33b Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 6 Oct 2020 14:29:29 +0200 Subject: [PATCH 030/108] ci/e2e: avoid running job when no go files are touched (#5471) --- .github/workflows/e2e.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c081070343..5743495791 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -27,10 +27,12 @@ jobs: working-directory: test/e2e # Run two make jobs in parallel, since we can't run steps in parallel. run: make -j2 docker runner + if: "env.GIT_DIFF != ''" - name: Run CI testnet working-directory: test/e2e run: sudo ./build/runner -f networks/ci.toml + if: "env.GIT_DIFF != ''" - name: Emit logs on failure if: ${{ failure() }} From f9bfb40d536a876fe3f35018e9eecd7fc237e97e Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 9 Oct 2020 14:33:48 +0200 Subject: [PATCH 031/108] test/e2e: add random testnet generator (#5479) Closes #5291. Adds a randomized testnet generator. Nightly CI job will be submitted separately. A few of the testnets can be a bit flaky, even after disabling known-faulty behavior and making minor tweaks, and the larger networks may be too resource-intensive to run in CI - this will be optimized separately. --- test/e2e/Makefile | 7 +- test/e2e/README.md | 20 ++- test/e2e/app/config.go | 1 + test/e2e/app/main.go | 18 ++- test/e2e/generator/generate.go | 227 ++++++++++++++++++++++++++++++ test/e2e/generator/main.go | 97 +++++++++++++ test/e2e/generator/random.go | 107 ++++++++++++++ test/e2e/generator/random_test.go | 31 ++++ test/e2e/pkg/manifest.go | 22 ++- test/e2e/pkg/testnet.go | 4 + test/e2e/run-multiple.sh | 35 +++++ test/e2e/runner/main.go | 2 +- test/e2e/runner/setup.go | 26 ++-- test/e2e/tests/net_test.go | 3 + 14 files changed, 570 insertions(+), 30 deletions(-) create mode 100644 test/e2e/generator/generate.go create mode 100644 test/e2e/generator/main.go create mode 100644 test/e2e/generator/random.go create mode 100644 test/e2e/generator/random_test.go create mode 100755 test/e2e/run-multiple.sh diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 5fbbed91a4..602de7547d 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -1,4 +1,4 @@ -all: docker runner +all: docker generator runner docker: docker build --tag tendermint/e2e-node -f docker/Dockerfile ../.. @@ -9,7 +9,10 @@ docker: app: go build -o build/app -tags badgerdb,boltdb,cleveldb,rocksdb ./app +generator: + go build -o build/generator ./generator + runner: go build -o build/runner ./runner -.PHONY: app docker runner +.PHONY: all app docker generator runner diff --git a/test/e2e/README.md b/test/e2e/README.md index a352959c6d..d178646112 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -3,8 +3,7 @@ Spins up and tests Tendermint networks in Docker Compose based on a testnet manifest. To run the CI testnet: ```sh -make docker -make runner +make ./build/runner -f networks/ci.toml ``` @@ -14,6 +13,23 @@ This creates and runs a testnet named `ci` under `networks/ci/` (determined by t Testnets are specified as TOML manifests. For an example see [`networks/ci.toml`](networks/ci.toml), and for documentation see [`pkg/manifest.go`](pkg/manifest.go). +## Random Testnet Generation + +Random (but deterministic) combinations of testnets can be generated with `generator`: + +```sh +./build/generator -d networks/generated/ + +# Split networks into 8 groups (by filename) +./build/generator -g 8 -d networks/generated/ +``` + +Multiple testnets can be run with the `run-multiple.sh` script: + +```sh +./run-multiple.sh networks/generated/gen-group3-*.toml +``` + ## Test Stages The test runner has the following stages, which can also be executed explicitly by running `./build/runner -f `: diff --git a/test/e2e/app/config.go b/test/e2e/app/config.go index bee8f59e58..20df6ce90f 100644 --- a/test/e2e/app/config.go +++ b/test/e2e/app/config.go @@ -1,3 +1,4 @@ +//nolint: goconst package main import ( diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go index d87eb40a7d..8a5ed95a39 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/app/main.go @@ -45,6 +45,17 @@ func run(configFile string) error { return err } + // Start remote signer (must start before node if running builtin). + if cfg.PrivValServer != "" { + if err = startSigner(cfg); err != nil { + return err + } + if cfg.Protocol == "builtin" { + time.Sleep(1 * time.Second) + } + } + + // Start app server. switch cfg.Protocol { case "socket", "grpc": err = startApp(cfg) @@ -57,13 +68,6 @@ func run(configFile string) error { return err } - // Start remote signer - if cfg.PrivValServer != "" { - if err = startSigner(cfg); err != nil { - return err - } - } - // Apparently there's no way to wait for the server, so we just sleep for { time.Sleep(1 * time.Hour) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go new file mode 100644 index 0000000000..0f8c79330f --- /dev/null +++ b/test/e2e/generator/generate.go @@ -0,0 +1,227 @@ +package main + +import ( + "fmt" + "math/rand" + "sort" + "strings" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" +) + +var ( + // testnetCombinations defines global testnet options, where we generate a + // separate testnet for each combination (Cartesian product) of options. + testnetCombinations = map[string][]interface{}{ + "topology": {"single", "quad", "large"}, + "ipv6": {false, true}, + "initialHeight": {0, 1000}, + "initialState": { + map[string]string{}, + map[string]string{"initial01": "a", "initial02": "b", "initial03": "c"}, + }, + "validators": {"genesis", "initchain"}, + } + + // The following specify randomly chosen values for testnet nodes. + nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} + // FIXME disabled grpc due to https://github.com/tendermint/tendermint/issues/5439 + nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin"} // "grpc" + nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} + // FIXME disabled v1 due to https://github.com/tendermint/tendermint/issues/5444 + nodeFastSyncs = uniformChoice{"", "v0", "v2"} // "v1" + nodeStateSyncs = uniformChoice{false, true} + nodePersistIntervals = uniformChoice{0, 1, 5} + nodeSnapshotIntervals = uniformChoice{0, 3} + nodeRetainBlocks = uniformChoice{0, 1, 5} + nodePerturbations = probSetChoice{ + "disconnect": 0.1, + "pause": 0.1, + // FIXME disabled due to https://github.com/tendermint/tendermint/issues/5422 + // "kill": 0.1, + // "restart": 0.1, + } +) + +// Generate generates random testnets using the given RNG. +func Generate(r *rand.Rand) ([]e2e.Manifest, error) { + manifests := []e2e.Manifest{} + for _, opt := range combinations(testnetCombinations) { + manifest, err := generateTestnet(r, opt) + if err != nil { + return nil, err + } + manifests = append(manifests, manifest) + } + return manifests, nil +} + +// generateTestnet generates a single testnet with the given options. +func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) { + manifest := e2e.Manifest{ + IPv6: opt["ipv6"].(bool), + InitialHeight: int64(opt["initialHeight"].(int)), + InitialState: opt["initialState"].(map[string]string), + Validators: &map[string]int64{}, + ValidatorUpdates: map[string]map[string]int64{}, + Nodes: map[string]*e2e.ManifestNode{}, + } + + var numSeeds, numValidators, numFulls int + switch opt["topology"].(string) { + case "single": + numValidators = 1 + case "quad": + numValidators = 4 + case "large": + // FIXME Networks are kept small since large ones use too much CPU. + numSeeds = r.Intn(4) + numValidators = 4 + r.Intn(7) + numFulls = r.Intn(5) + default: + return manifest, fmt.Errorf("unknown topology %q", opt["topology"]) + } + + // First we generate seed nodes, starting at the initial height. + for i := 1; i <= numSeeds; i++ { + manifest.Nodes[fmt.Sprintf("seed%02d", i)] = generateNode(r, e2e.ModeSeed, 0, false) + } + + // Next, we generate validators. We make sure a BFT quorum of validators start + // at the initial height, and that we have two archive nodes. We also set up + // the initial validator set, and validator set updates for delayed nodes. + nextStartAt := manifest.InitialHeight + 5 + quorum := numValidators*2/3 + 1 + for i := 1; i <= numValidators; i++ { + startAt := int64(0) + if i > quorum { + startAt = nextStartAt + nextStartAt += 5 + } + name := fmt.Sprintf("validator%02d", i) + manifest.Nodes[name] = generateNode(r, e2e.ModeValidator, startAt, i <= 2) + + if startAt == 0 { + (*manifest.Validators)[name] = int64(30 + r.Intn(71)) + } else { + manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{ + name: int64(30 + r.Intn(71)), + } + } + } + + // Move validators to InitChain if specified. + switch opt["validators"].(string) { + case "genesis": + case "initchain": + manifest.ValidatorUpdates["0"] = *manifest.Validators + manifest.Validators = &map[string]int64{} + default: + return manifest, fmt.Errorf("invalid validators option %q", opt["validators"]) + } + + // Finally, we generate random full nodes. + for i := 1; i <= numFulls; i++ { + startAt := int64(0) + if r.Float64() >= 0.5 { + startAt = nextStartAt + nextStartAt += 5 + } + manifest.Nodes[fmt.Sprintf("full%02d", i)] = generateNode(r, e2e.ModeFull, startAt, false) + } + + // We now set up peer discovery for nodes. Seed nodes are fully meshed with + // each other, while non-seed nodes either use a set of random seeds or a + // set of random peers that start before themselves. + var seedNames, peerNames []string + for name, node := range manifest.Nodes { + if node.Mode == string(e2e.ModeSeed) { + seedNames = append(seedNames, name) + } else { + peerNames = append(peerNames, name) + } + } + + for _, name := range seedNames { + for _, otherName := range seedNames { + if name != otherName { + manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName) + } + } + } + + sort.Slice(peerNames, func(i, j int) bool { + iName, jName := peerNames[i], peerNames[j] + switch { + case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt: + return true + case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt: + return false + default: + return strings.Compare(iName, jName) == -1 + } + }) + for i, name := range peerNames { + if len(seedNames) > 0 && (i == 0 || r.Float64() >= 0.5) { + manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r) + } else if i > 0 { + manifest.Nodes[name].PersistentPeers = uniformSetChoice(peerNames[:i]).Choose(r) + } + } + + return manifest, nil +} + +// generateNode randomly generates a node, with some constraints to avoid +// generating invalid configurations. We do not set Seeds or PersistentPeers +// here, since we need to know the overall network topology and startup +// sequencing. +func generateNode(r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) *e2e.ManifestNode { + node := e2e.ManifestNode{ + Mode: string(mode), + StartAt: startAt, + Database: nodeDatabases.Choose(r).(string), + ABCIProtocol: nodeABCIProtocols.Choose(r).(string), + PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string), + FastSync: nodeFastSyncs.Choose(r).(string), + StateSync: nodeStateSyncs.Choose(r).(bool) && startAt > 0, + PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))), + SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)), + RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)), + Perturb: nodePerturbations.Choose(r), + } + + // If this node is forced to be an archive node, retain all blocks and + // enable state sync snapshotting. + if forceArchive { + node.RetainBlocks = 0 + node.SnapshotInterval = 3 + } + + // If a node which does not persist state also does not retain blocks, randomly + // choose to either persist state or retain all blocks. + if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 { + if r.Float64() > 0.5 { + node.RetainBlocks = 0 + } else { + node.PersistInterval = ptrUint64(node.RetainBlocks) + } + } + + // If either PersistInterval or SnapshotInterval are greater than RetainBlocks, + // expand the block retention time. + if node.RetainBlocks > 0 { + if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval { + node.RetainBlocks = *node.PersistInterval + } + if node.RetainBlocks < node.SnapshotInterval { + node.RetainBlocks = node.SnapshotInterval + } + } + + return &node +} + +func ptrUint64(i uint64) *uint64 { + return &i +} diff --git a/test/e2e/generator/main.go b/test/e2e/generator/main.go new file mode 100644 index 0000000000..ce73ccd979 --- /dev/null +++ b/test/e2e/generator/main.go @@ -0,0 +1,97 @@ +//nolint: gosec +package main + +import ( + "fmt" + "math" + "math/rand" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + randomSeed int64 = 4827085738 +) + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + +func main() { + NewCLI().Run() +} + +// CLI is the Cobra-based command-line interface. +type CLI struct { + root *cobra.Command +} + +// NewCLI sets up the CLI. +func NewCLI() *CLI { + cli := &CLI{} + cli.root = &cobra.Command{ + Use: "generator", + Short: "End-to-end testnet generator", + SilenceUsage: true, + SilenceErrors: true, // we'll output them ourselves in Run() + RunE: func(cmd *cobra.Command, args []string) error { + dir, err := cmd.Flags().GetString("dir") + if err != nil { + return err + } + groups, err := cmd.Flags().GetInt("groups") + if err != nil { + return err + } + return cli.generate(dir, groups) + }, + } + + cli.root.PersistentFlags().StringP("dir", "d", "", "Output directory for manifests") + _ = cli.root.MarkPersistentFlagRequired("dir") + cli.root.PersistentFlags().IntP("groups", "g", 0, "Number of groups") + + return cli +} + +// generate generates manifests in a directory. +func (cli *CLI) generate(dir string, groups int) error { + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + manifests, err := Generate(rand.New(rand.NewSource(randomSeed))) + if err != nil { + return err + } + if groups <= 0 { + for i, manifest := range manifests { + err = manifest.Save(filepath.Join(dir, fmt.Sprintf("gen-%04d.toml", i))) + if err != nil { + return err + } + } + } else { + groupSize := int(math.Ceil(float64(len(manifests)) / float64(groups))) + for g := 0; g < groups; g++ { + for i := 0; i < groupSize && g*groupSize+i < len(manifests); i++ { + manifest := manifests[g*groupSize+i] + err = manifest.Save(filepath.Join(dir, fmt.Sprintf("gen-group%02d-%04d.toml", g, i))) + if err != nil { + return err + } + } + } + } + return nil +} + +// Run runs the CLI. +func (cli *CLI) Run() { + if err := cli.root.Execute(); err != nil { + logger.Error(err.Error()) + os.Exit(1) + } +} diff --git a/test/e2e/generator/random.go b/test/e2e/generator/random.go new file mode 100644 index 0000000000..04d1ac70de --- /dev/null +++ b/test/e2e/generator/random.go @@ -0,0 +1,107 @@ +package main + +import ( + "math/rand" + "sort" +) + +// combinations takes input in the form of a map of item lists, and returns a +// list of all combinations of each item for each key. E.g.: +// +// {"foo": [1, 2, 3], "bar": [4, 5, 6]} +// +// Will return the following maps: +// +// {"foo": 1, "bar": 4} +// {"foo": 1, "bar": 5} +// {"foo": 1, "bar": 6} +// {"foo": 2, "bar": 4} +// {"foo": 2, "bar": 5} +// {"foo": 2, "bar": 6} +// {"foo": 3, "bar": 4} +// {"foo": 3, "bar": 5} +// {"foo": 3, "bar": 6} +func combinations(items map[string][]interface{}) []map[string]interface{} { + keys := []string{} + for key := range items { + keys = append(keys, key) + } + sort.Strings(keys) + return combiner(map[string]interface{}{}, keys, items) +} + +// combiner is a utility function for combinations. +func combiner(head map[string]interface{}, pending []string, items map[string][]interface{}) []map[string]interface{} { + if len(pending) == 0 { + return []map[string]interface{}{head} + } + key, pending := pending[0], pending[1:] + + result := []map[string]interface{}{} + for _, value := range items[key] { + path := map[string]interface{}{} + for k, v := range head { + path[k] = v + } + path[key] = value + result = append(result, combiner(path, pending, items)...) + } + return result +} + +// uniformChoice chooses a single random item from the argument list, uniformly weighted. +type uniformChoice []interface{} + +func (uc uniformChoice) Choose(r *rand.Rand) interface{} { + return uc[r.Intn(len(uc))] +} + +// weightedChoice chooses a single random key from a map of keys and weights. +type weightedChoice map[interface{}]uint // nolint:unused + +func (wc weightedChoice) Choose(r *rand.Rand) interface{} { + total := 0 + choices := make([]interface{}, 0, len(wc)) + for choice, weight := range wc { + total += int(weight) + choices = append(choices, choice) + } + + rem := r.Intn(total) + for _, choice := range choices { + rem -= int(wc[choice]) + if rem <= 0 { + return choice + } + } + + return nil +} + +// probSetChoice picks a set of strings based on each string's probability (0-1). +type probSetChoice map[string]float64 + +func (pc probSetChoice) Choose(r *rand.Rand) []string { + choices := []string{} + for item, prob := range pc { + if r.Float64() <= prob { + choices = append(choices, item) + } + } + return choices +} + +// uniformSetChoice picks a set of strings with uniform probability, picking at least one. +type uniformSetChoice []string + +func (usc uniformSetChoice) Choose(r *rand.Rand) []string { + choices := []string{} + indexes := r.Perm(len(usc)) + if len(indexes) > 1 { + indexes = indexes[:1+r.Intn(len(indexes)-1)] + } + for _, i := range indexes { + choices = append(choices, usc[i]) + } + return choices +} diff --git a/test/e2e/generator/random_test.go b/test/e2e/generator/random_test.go new file mode 100644 index 0000000000..3fbb19ab5a --- /dev/null +++ b/test/e2e/generator/random_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCombinations(t *testing.T) { + input := map[string][]interface{}{ + "bool": {false, true}, + "int": {1, 2, 3}, + "string": {"foo", "bar"}, + } + + c := combinations(input) + assert.Equal(t, []map[string]interface{}{ + {"bool": false, "int": 1, "string": "foo"}, + {"bool": false, "int": 1, "string": "bar"}, + {"bool": false, "int": 2, "string": "foo"}, + {"bool": false, "int": 2, "string": "bar"}, + {"bool": false, "int": 3, "string": "foo"}, + {"bool": false, "int": 3, "string": "bar"}, + {"bool": true, "int": 1, "string": "foo"}, + {"bool": true, "int": 1, "string": "bar"}, + {"bool": true, "int": 2, "string": "foo"}, + {"bool": true, "int": 2, "string": "bar"}, + {"bool": true, "int": 3, "string": "foo"}, + {"bool": true, "int": 3, "string": "bar"}, + }, c) +} diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 2e06347119..c951d9409a 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -2,6 +2,7 @@ package e2e import ( "fmt" + "os" "github.com/BurntSushi/toml" ) @@ -27,7 +28,7 @@ type Manifest struct { // specifying an empty set will start with no validators in genesis, and // the application must return the validator set in InitChain via the // setting validator_update.0 (see below). - Validators *map[string]int64 + Validators *map[string]int64 `toml:"validators"` // ValidatorUpdates is a map of heights to validator names and their power, // and will be returned by the ABCI application. For example, the following @@ -44,7 +45,7 @@ type Manifest struct { ValidatorUpdates map[string]map[string]int64 `toml:"validator_update"` // Nodes specifies the network nodes. At least one node must be given. - Nodes map[string]ManifestNode `toml:"node"` + Nodes map[string]*ManifestNode `toml:"node"` } // ManifestNode represents a node in a testnet manifest. @@ -52,10 +53,10 @@ type ManifestNode struct { // Mode specifies the type of node: "validator", "full", or "seed". Defaults to // "validator". Full nodes do not get a signing key (a dummy key is generated), // and seed nodes run in seed mode with the PEX reactor enabled. - Mode string + Mode string `toml:"mode"` // Seeds is the list of node names to use as P2P seed nodes. Defaults to none. - Seeds []string + Seeds []string `toml:"seeds"` // PersistentPeers is a list of node names to maintain persistent P2P // connections to. If neither seeds nor persistent peers are specified, @@ -64,7 +65,7 @@ type ManifestNode struct { // Database specifies the database backend: "goleveldb", "cleveldb", // "rocksdb", "boltdb", or "badgerdb". Defaults to goleveldb. - Database string + Database string `toml:"database"` // ABCIProtocol specifies the protocol used to communicate with the ABCI // application: "unix", "tcp", "grpc", or "builtin". Defaults to unix. @@ -113,7 +114,16 @@ type ManifestNode struct { // kill: kills the node with SIGKILL then restarts it // pause: temporarily pauses (freezes) the node // restart: restarts the node, shutting it down with SIGTERM - Perturb []string + Perturb []string `toml:"perturb"` +} + +// Save saves the testnet manifest to a file. +func (m Manifest) Save(file string) error { + f, err := os.Create(file) + if err != nil { + return fmt.Errorf("failed to create manifest file %q: %w", file, err) + } + return toml.NewEncoder(f).Encode(m) } // LoadManifest loads a testnet manifest from a file. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 5dda076bc2..351f83378d 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -300,6 +300,10 @@ func (n Node) Validate(testnet Testnet) error { return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol) } + if n.StartAt > 0 && n.StartAt < n.Testnet.InitialHeight { + return fmt.Errorf("cannot start at height %v lower than initial height %v", + n.StartAt, n.Testnet.InitialHeight) + } if n.StateSync && n.StartAt == 0 { return errors.New("state synced nodes cannot start at the initial height") } diff --git a/test/e2e/run-multiple.sh b/test/e2e/run-multiple.sh new file mode 100755 index 0000000000..e9699cf1e7 --- /dev/null +++ b/test/e2e/run-multiple.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# This is a convenience script that takes a list of testnet manifests +# as arguments and runs each one of them sequentially. If a testnet +# fails, the container logs are dumped to stdout along with the testnet +# manifest. +# +# This is mostly used to run generated networks in nightly CI jobs. +# + +# Don't set -e, since we explicitly check status codes ourselves. +set -u + +if [[ $# == 0 ]]; then + echo "Usage: $0 [MANIFEST...]" >&2 + exit 1 +fi + +for MANIFEST in "$@"; do + START=$SECONDS + echo "==> Running testnet $MANIFEST..." + ./build/runner -f "$MANIFEST" + + if [[ $? -ne 0 ]]; then + echo "==> Testnet $MANIFEST failed, dumping manifest..." + cat "$MANIFEST" + + echo "==> Dumping container logs for $MANIFEST..." + ./build/runner -f "$MANIFEST" logs + exit 1 + fi + + echo "==> Completed testnet $MANIFEST in $(( SECONDS - START ))s" + echo "" +done diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index ab14349d01..733a57f3e5 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -76,7 +76,7 @@ func NewCLI() *CLI { if err := <-chLoadResult; err != nil { return err } - if err := Wait(cli.testnet, 10); err != nil { // wait for network to settle before tests + if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests return err } if err := Test(cli.testnet); err != nil { diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 00ee6594d7..e77fcd7ee2 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -310,18 +310,20 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { default: return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol) } - switch node.PrivvalProtocol { - case e2e.ProtocolFile: - case e2e.ProtocolTCP: - cfg["privval_server"] = PrivvalAddressTCP - cfg["privval_key"] = PrivvalKeyFile - cfg["privval_state"] = PrivvalStateFile - case e2e.ProtocolUNIX: - cfg["privval_server"] = PrivvalAddressUNIX - cfg["privval_key"] = PrivvalKeyFile - cfg["privval_state"] = PrivvalStateFile - default: - return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol) + if node.Mode == e2e.ModeValidator { + switch node.PrivvalProtocol { + case e2e.ProtocolFile: + case e2e.ProtocolTCP: + cfg["privval_server"] = PrivvalAddressTCP + cfg["privval_key"] = PrivvalKeyFile + cfg["privval_state"] = PrivvalStateFile + case e2e.ProtocolUNIX: + cfg["privval_server"] = PrivvalAddressUNIX + cfg["privval_key"] = PrivvalKeyFile + cfg["privval_state"] = PrivvalStateFile + default: + return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol) + } } if len(node.Testnet.ValidatorUpdates) > 0 { diff --git a/test/e2e/tests/net_test.go b/test/e2e/tests/net_test.go index 43d155236f..e0a84aeebf 100644 --- a/test/e2e/tests/net_test.go +++ b/test/e2e/tests/net_test.go @@ -9,6 +9,9 @@ import ( // Tests that all nodes have peered with each other, regardless of discovery method. func TestNet_Peers(t *testing.T) { + // FIXME Skip test since nodes aren't always able to fully mesh + t.SkipNow() + testNode(t, func(t *testing.T, node e2e.Node) { // Seed nodes shouldn't necessarily mesh with the entire network. if node.Mode == e2e.ModeSeed { From cbdc08932131aa45215da7a457d2818a9086996e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 13:25:32 +0200 Subject: [PATCH 032/108] build(deps): Bump actions/cache from v2.1.1 to v2.1.2 (#5487) Bumps [actions/cache](https://github.com/actions/cache) from v2.1.1 to v2.1.2. - [Release notes](https://github.com/actions/cache/releases) - [Commits](https://github.com/actions/cache/compare/v2.1.1...d1255ad9362389eac595a9ae406b8e8cb3331f16) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4cea1a7b39..38ed242709 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: - name: install run: make install install_abci if: "env.GIT_DIFF != ''" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -46,7 +46,7 @@ jobs: ${{ runner.os }}-go- if: "env.GIT_DIFF != ''" # Cache binaries for use by other jobs - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -69,14 +69,14 @@ jobs: - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: "env.GIT_DIFF != ''" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -103,14 +103,14 @@ jobs: - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: "env.GIT_DIFF != ''" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -136,14 +136,14 @@ jobs: - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: "env.GIT_DIFF != ''" - - uses: actions/cache@v2.1.1 + - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary From bf42bf0fd59410c446b4a15f66136b0d29898982 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 13:28:28 +0200 Subject: [PATCH 033/108] build(deps): Bump golangci/golangci-lint-action from v2.2.0 to v2.2.1 (#5486) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marko --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index db30ab1614..130f1024ca 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,7 +20,7 @@ jobs: .go .mod .sum - - uses: golangci/golangci-lint-action@v2.2.0 + - uses: golangci/golangci-lint-action@v2.2.1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: v1.30 From 51b8d3a1533ffb77744d7a248842ec0b46ad77f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:13:45 +0200 Subject: [PATCH 034/108] build(deps): Bump technote-space/get-diff-action from v3 to v4 (#5485) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marko Baricevic --- .github/workflows/coverage.yml | 80 +++++++++++++++------------------- .github/workflows/e2e.yml | 12 +++-- .github/workflows/lint.yaml | 12 ++--- .github/workflows/tests.yml | 70 +++++++++++++---------------- state/export_test.go | 3 +- 5 files changed, 80 insertions(+), 97 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7174c1b354..60495a3d13 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,101 +38,93 @@ jobs: needs: split-test-files steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 - with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum - uses: actions/download-artifact@v2 with: name: "${{ github.sha }}-aa" - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test & coverage report creation run: | cat xaa.txt | xargs go test -mod=readonly -timeout 8m -race -coverprofile=coverage.txt -covermode=atomic - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.txt - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test-coverage-part-2: runs-on: ubuntu-latest needs: split-test-files steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 - with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum - uses: actions/download-artifact@v2 with: name: "${{ github.sha }}-ab" - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test & coverage report creation run: | cat xab.txt | xargs go test -mod=readonly -timeout 5m -race -coverprofile=coverage.txt -covermode=atomic - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.txt - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test-coverage-part-3: runs-on: ubuntu-latest needs: split-test-files steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 - with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum - uses: actions/download-artifact@v2 with: name: "${{ github.sha }}-ac" - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test & coverage report creation run: | cat xac.txt | xargs go test -mod=readonly -timeout 10m -race -coverprofile=coverage.txt -covermode=atomic - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.txt - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test-coverage-part-4: runs-on: ubuntu-latest needs: split-test-files steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 - with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum - uses: actions/download-artifact@v2 with: name: "${{ github.sha }}-ad" - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test & coverage report creation run: | cat xad.txt | xargs go test -mod=readonly -timeout 5m -race -coverprofile=coverage.txt -covermode=atomic - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.txt - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5743495791..55a04d4a23 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -14,14 +14,12 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + PATTERNS: | + **/**.go + go.mod + go.sum - name: Build working-directory: test/e2e diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 130f1024ca..fb537d4896 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,16 +14,16 @@ jobs: timeout-minutes: 4 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum + PATTERNS: | + **/**.go + go.mod + go.sum - uses: golangci/golangci-lint-action@v2.2.1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: v1.30 args: --timeout 10m github-token: ${{ secrets.github_token }} - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 38ed242709..5e5e75e256 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,14 +24,12 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + PATTERNS: | + **/**.go + go.mod + go.sum - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" @@ -44,13 +42,13 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF # Cache binaries for use by other jobs - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test_abci_apps: runs-on: ubuntu-latest @@ -58,14 +56,12 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + PATTERNS: | + **/**.go + go.mod + go.sum - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" @@ -75,16 +71,16 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test_abci_apps run: abci/tests/test_app/test.sh shell: bash - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test_abci_cli: runs-on: ubuntu-latest @@ -92,14 +88,12 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + PATTERNS: | + **/**.go + go.mod + go.sum - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" @@ -109,15 +103,15 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - run: abci/tests/test_cli/test.sh shell: bash - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF test_apps: runs-on: ubuntu-latest @@ -125,14 +119,12 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v3 + - uses: technote-space/get-diff-action@v4 with: - SUFFIX_FILTER: | - .go - .mod - .sum - SET_ENV_NAME_INSERTIONS: 1 - SET_ENV_NAME_LINES: 1 + PATTERNS: | + **/**.go + go.mod + go.sum - name: Set GOBIN run: | echo "::add-path::$(go env GOPATH)/bin" @@ -142,13 +134,13 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - uses: actions/cache@v2.1.2 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF - name: test_apps run: test/app/test.sh shell: bash - if: "env.GIT_DIFF != ''" + if: env.GIT_DIFF diff --git a/state/export_test.go b/state/export_test.go index c7de6c5c25..56c3d764c5 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -1,11 +1,12 @@ package state import ( + dbm "github.com/tendermint/tm-db" + abci "github.com/tendermint/tendermint/abci/types" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" - dbm "github.com/tendermint/tm-db" ) // From 9379bc92fdbe344bbfa82b05c1854029edc56e46 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 13 Oct 2020 10:22:53 +0200 Subject: [PATCH 035/108] fix lint failures with 1.31 (#5489) --- .github/workflows/lint.yaml | 2 +- abci/example/kvstore/persistent_kvstore.go | 2 +- abci/tests/test_app/main.go | 2 +- abci/types/util.go | 4 +-- blockchain/v0/pool.go | 4 +-- blockchain/v0/pool_test.go | 2 +- blockchain/v0/reactor.go | 2 +- blockchain/v0/reactor_test.go | 2 +- blockchain/v1/reactor.go | 2 +- blockchain/v1/reactor_test.go | 4 +-- blockchain/v2/scheduler.go | 6 ++-- consensus/byzantine_test.go | 4 +-- consensus/common_test.go | 4 +-- consensus/reactor.go | 11 ++++---- consensus/reactor_test.go | 2 +- consensus/replay.go | 2 +- consensus/replay_test.go | 32 ++++++++-------------- consensus/state.go | 6 ++-- consensus/state_test.go | 4 +-- consensus/wal.go | 6 +--- consensus/wal_generator.go | 4 +-- consensus/wal_test.go | 2 +- libs/bytes/bytes.go | 2 +- libs/clist/clist_test.go | 26 +++++++++--------- libs/cmap/cmap_test.go | 2 +- libs/fail/fail.go | 2 +- libs/log/tracing_logger_test.go | 10 +++---- libs/math/fraction.go | 2 +- libs/protoio/io_test.go | 6 ++-- libs/pubsub/pubsub_test.go | 4 +-- node/id.go | 2 +- p2p/conn/secret_connection_test.go | 3 +- p2p/switch_test.go | 2 +- p2p/upnp/upnp.go | 2 +- privval/signer_listener_endpoint_test.go | 2 -- proxy/app_conn_test.go | 2 +- rpc/client/examples_test.go | 7 +++-- rpc/client/rpc_test.go | 2 +- rpc/jsonrpc/client/http_json_client.go | 2 +- rpc/jsonrpc/client/ws_client.go | 2 -- rpc/jsonrpc/jsonrpc_test.go | 2 +- rpc/jsonrpc/server/http_json_handler.go | 2 -- rpc/jsonrpc/server/http_uri_handler.go | 2 -- rpc/jsonrpc/server/rpc_func.go | 2 -- rpc/jsonrpc/server/ws_handler.go | 4 --- rpc/test/helpers.go | 2 +- scripts/wal2json/main.go | 5 +++- state/state.go | 2 +- state/state_test.go | 2 +- state/store.go | 4 +-- state/txindex/kv/kv.go | 1 - store/store.go | 2 +- types/events.go | 4 --- types/part_set_test.go | 2 +- types/priv_validator.go | 4 +-- types/validator_set.go | 3 -- types/validator_set_test.go | 8 ++---- types/vote.go | 2 +- 58 files changed, 98 insertions(+), 144 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index fb537d4896..3df22ee8a8 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -23,7 +23,7 @@ jobs: - uses: golangci/golangci-lint-action@v2.2.1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.30 + version: v1.31 args: --timeout 10m github-token: ${{ secrets.github_token }} if: env.GIT_DIFF diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index 93676bdded..c2232cc770 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -212,7 +212,7 @@ func isValidatorTx(tx []byte) bool { func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx { tx = tx[len(ValidatorSetChangePrefix):] - //get the pubkey and power + // get the pubkey and power pubKeyAndPower := strings.Split(string(tx), "!") if len(pubKeyAndPower) != 2 { return types.ResponseDeliverTx{ diff --git a/abci/tests/test_app/main.go b/abci/tests/test_app/main.go index 32d04311f1..1dcab1de18 100644 --- a/abci/tests/test_app/main.go +++ b/abci/tests/test_app/main.go @@ -69,7 +69,7 @@ func testCounter() { }() if err := ensureABCIIsUp(abciType, maxABCIConnectTries); err != nil { - log.Fatalf("echo failed: %v", err) + log.Fatalf("echo failed: %v", err) //nolint:gocritic } client := startClient(abciType) diff --git a/abci/types/util.go b/abci/types/util.go index 94a34c0c1d..8205fef7e9 100644 --- a/abci/types/util.go +++ b/abci/types/util.go @@ -27,7 +27,5 @@ func (v ValidatorUpdates) Less(i, j int) bool { } func (v ValidatorUpdates) Swap(i, j int) { - v1 := v[i] - v[i] = v[j] - v[j] = v1 + v[i], v[j] = v[j], v[i] } diff --git a/blockchain/v0/pool.go b/blockchain/v0/pool.go index 8cba75024c..69e0b55c4a 100644 --- a/blockchain/v0/pool.go +++ b/blockchain/v0/pool.go @@ -509,7 +509,7 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan p2p.ID //redo may send multitime, add peerId to identify repeat + redoCh chan p2p.ID // redo may send multitime, add peerId to identify repeat mtx tmsync.Mutex peerID p2p.ID @@ -601,7 +601,7 @@ OUTER_LOOP: } peer = bpr.pool.pickIncrAvailablePeer(bpr.height) if peer == nil { - //log.Info("No peers available", "height", height) + // log.Info("No peers available", "height", height) time.Sleep(requestIntervalMS * time.Millisecond) continue PICK_PEER_LOOP } diff --git a/blockchain/v0/pool_test.go b/blockchain/v0/pool_test.go index 36fb3e3c7f..1653fe74a3 100644 --- a/blockchain/v0/pool_test.go +++ b/blockchain/v0/pool_test.go @@ -22,7 +22,7 @@ type testPeer struct { id p2p.ID base int64 height int64 - inputChan chan inputData //make sure each peer's data is sequential + inputChan chan inputData // make sure each peer's data is sequential } type inputData struct { diff --git a/blockchain/v0/reactor.go b/blockchain/v0/reactor.go index 093e082419..440a6090c2 100644 --- a/blockchain/v0/reactor.go +++ b/blockchain/v0/reactor.go @@ -346,7 +346,7 @@ FOR_LOOP: // See if there are any blocks to sync. first, second := bcR.pool.PeekTwoBlocks() - //bcR.Logger.Info("TrySync peeked", "first", first, "second", second) + // bcR.Logger.Info("TrySync peeked", "first", first, "second", second) if first == nil || second == nil { // We need both to sync the first block. continue FOR_LOOP diff --git a/blockchain/v0/reactor_test.go b/blockchain/v0/reactor_test.go index 4451a1c741..a88b499f4e 100644 --- a/blockchain/v0/reactor_test.go +++ b/blockchain/v0/reactor_test.go @@ -247,7 +247,7 @@ func TestBadBlockStopsPeer(t *testing.T) { } } - //at this time, reactors[0-3] is the newest + // at this time, reactors[0-3] is the newest assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) // Mark reactorPairs[3] as an invalid peer. Fiddling with .store without a mutex is a data diff --git a/blockchain/v1/reactor.go b/blockchain/v1/reactor.go index 30bd4dd8a7..780e43738c 100644 --- a/blockchain/v1/reactor.go +++ b/blockchain/v1/reactor.go @@ -100,7 +100,7 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *st fsm := NewFSM(startHeight, bcR) bcR.fsm = fsm bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR) - //bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch) + // bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch) return bcR } diff --git a/blockchain/v1/reactor_test.go b/blockchain/v1/reactor_test.go index 6bc5646a06..c0f371905d 100644 --- a/blockchain/v1/reactor_test.go +++ b/blockchain/v1/reactor_test.go @@ -305,10 +305,10 @@ outerFor: break } - //at this time, reactors[0-3] is the newest + // at this time, reactors[0-3] is the newest assert.Equal(t, numNodes-1, reactorPairs[1].bcR.Switch.Peers().Size()) - //mark last reactorPair as an invalid peer + // mark last reactorPair as an invalid peer reactorPairs[numNodes-1].bcR.store = otherChain.bcR.store lastLogger := log.TestingLogger() diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go index b97ee2f643..811a74bae3 100644 --- a/blockchain/v2/scheduler.go +++ b/blockchain/v2/scheduler.go @@ -210,7 +210,7 @@ func newScheduler(initHeight int64, startTime time.Time) *scheduler { receivedBlocks: make(map[int64]p2p.ID), targetPending: 10, // TODO - pass as param peerTimeout: 15 * time.Second, // TODO - pass as param - minRecvRate: 0, //int64(7680), TODO - pass as param + minRecvRate: 0, // int64(7680), TODO - pass as param } return &sc @@ -532,9 +532,7 @@ func (peers PeerByID) Less(i, j int) bool { } func (peers PeerByID) Swap(i, j int) { - it := peers[i] - peers[i] = peers[j] - peers[j] = it + peers[i], peers[j] = peers[j], peers[i] } // Handlers diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 6a2f7cadd8..4d66602101 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -112,7 +112,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { require.NoError(t, err) blocksSubs = append(blocksSubs, blocksSub) - if css[i].state.LastBlockHeight == 0 { //simulate handle initChain in handshake + if css[i].state.LastBlockHeight == 0 { // simulate handle initChain in handshake err = css[i].blockExec.Store().Save(css[i].state) require.NoError(t, err) } @@ -276,7 +276,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { } reactors[i] = conRI - err = css[i].blockExec.Store().Save(css[i].state) //for save height 1's validators info + err = css[i].blockExec.Store().Save(css[i].state) // for save height 1's validators info require.NoError(t, err) } diff --git a/consensus/common_test.go b/consensus/common_test.go index 5a87ce402c..9488122254 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -391,7 +391,7 @@ func newStateWithConfigAndBlockStore( // Make State stateDB := blockDB stateStore := sm.NewStore(stateDB) - if err := stateStore.Save(state); err != nil { //for save height 1's validators info + if err := stateStore.Save(state); err != nil { // for save height 1's validators info panic(err) } @@ -749,7 +749,7 @@ func randConsensusNetWithPeers( state.Version.Consensus.App = kvstore.ProtocolVersion } app.InitChain(abci.RequestInitChain{Validators: vals}) - //sm.SaveState(stateDB,state) //height 1's validatorsInfo already saved in LoadStateFromDBOrGenesisDoc above + // sm.SaveState(stateDB,state) //height 1's validatorsInfo already saved in LoadStateFromDBOrGenesisDoc above css[i] = newStateWithConfig(thisConfig, state, privVal, app) css[i].SetTimeoutTicker(tickerFunc()) diff --git a/consensus/reactor.go b/consensus/reactor.go index 85bea723c3..c4a2ef9fda 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -540,7 +540,8 @@ OUTER_LOOP: // If height and round don't match, sleep. if (rs.Height != prs.Height) || (rs.Round != prs.Round) { - //logger.Info("Peer Height|Round mismatch, sleeping", "peerHeight", prs.Height, "peerRound", prs.Round, "peer", peer) + // logger.Info("Peer Height|Round mismatch, sleeping", + // "peerHeight", prs.Height, "peerRound", prs.Round, "peer", peer) time.Sleep(conR.conS.config.PeerGossipSleepDuration) continue OUTER_LOOP } @@ -622,7 +623,7 @@ func (conR *Reactor) gossipDataForCatchup(logger log.Logger, rs *cstypes.RoundSt } return } - //logger.Info("No parts to send in catch-up, sleeping") + // logger.Info("No parts to send in catch-up, sleeping") time.Sleep(conR.conS.config.PeerGossipSleepDuration) } @@ -649,8 +650,8 @@ OUTER_LOOP: sleeping = 0 } - //logger.Debug("gossipVotesRoutine", "rsHeight", rs.Height, "rsRound", rs.Round, - // "prsHeight", prs.Height, "prsRound", prs.Round, "prsStep", prs.Step) + // logger.Debug("gossipVotesRoutine", "rsHeight", rs.Height, "rsRound", rs.Round, + // "prsHeight", prs.Height, "prsRound", prs.Round, "prsStep", prs.Step) // If height matches, then send LastCommit, Prevotes, Precommits. if rs.Height == prs.Height { @@ -1488,7 +1489,7 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- // NewValidBlockMessage is sent when a validator observes a valid block B in some round r, -//i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. +// i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. // In case the block is also committed, then IsCommit flag is set to true. type NewValidBlockMessage struct { Height int64 diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index ce08daab43..192622e43b 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -65,7 +65,7 @@ func startConsensusNet(t *testing.T, css []*State, n int) ( require.NoError(t, err) blocksSubs = append(blocksSubs, blocksSub) - if css[i].state.LastBlockHeight == 0 { //simulate handle initChain in handshake + if css[i].state.LastBlockHeight == 0 { // simulate handle initChain in handshake if err := css[i].blockExec.Store().Save(css[i].state); err != nil { t.Error(err) } diff --git a/consensus/replay.go b/consensus/replay.go index 6b7cad83d6..bfec9e96d4 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -323,7 +323,7 @@ func (h *Handshaker) ReplayBlocks( appHash = res.AppHash - if stateBlockHeight == 0 { //we only update state when we are in initial state + if stateBlockHeight == 0 { // we only update state when we are in initial state // If the app did not return an app hash, we keep the one set from the genesis doc in // the state. We don't set appHash since we don't want the genesis doc app hash // recorded in the genesis block. We should probably just remove GenesisDoc.AppHash. diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 5f279e330c..2970f15ed7 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -353,9 +353,7 @@ func TestSimulateValidatorsChange(t *testing.T) { signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) ensureNewRound(newRoundCh, height+1, 0) - ///////////////////////////////////////////////////////////////////////////// // HEIGHT 2 - ///////////////////////////////////////////////////////////////////////////// height++ incrementHeight(vss...) newValidatorPubKey1, err := css[nVals].privValidator.GetPubKey() @@ -365,7 +363,7 @@ func TestSimulateValidatorsChange(t *testing.T) { newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower) err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx1, nil, mempl.TxInfo{}) assert.Nil(t, err) - propBlock, _ := css[0].createProposalBlock() //changeProposer(t, cs1, vs2) + propBlock, _ := css[0].createProposalBlock() // changeProposer(t, cs1, vs2) propBlockParts := propBlock.MakePartSet(partSize) blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} @@ -385,9 +383,7 @@ func TestSimulateValidatorsChange(t *testing.T) { signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) ensureNewRound(newRoundCh, height+1, 0) - ///////////////////////////////////////////////////////////////////////////// // HEIGHT 3 - ///////////////////////////////////////////////////////////////////////////// height++ incrementHeight(vss...) updateValidatorPubKey1, err := css[nVals].privValidator.GetPubKey() @@ -397,7 +393,7 @@ func TestSimulateValidatorsChange(t *testing.T) { updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25) err = assertMempool(css[0].txNotifier).CheckTx(updateValidatorTx1, nil, mempl.TxInfo{}) assert.Nil(t, err) - propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) + propBlock, _ = css[0].createProposalBlock() // changeProposer(t, cs1, vs2) propBlockParts = propBlock.MakePartSet(partSize) blockID = types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} @@ -417,9 +413,7 @@ func TestSimulateValidatorsChange(t *testing.T) { signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) ensureNewRound(newRoundCh, height+1, 0) - ///////////////////////////////////////////////////////////////////////////// // HEIGHT 4 - ///////////////////////////////////////////////////////////////////////////// height++ incrementHeight(vss...) newValidatorPubKey2, err := css[nVals+1].privValidator.GetPubKey() @@ -436,7 +430,7 @@ func TestSimulateValidatorsChange(t *testing.T) { newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower) err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx3, nil, mempl.TxInfo{}) assert.Nil(t, err) - propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) + propBlock, _ = css[0].createProposalBlock() // changeProposer(t, cs1, vs2) propBlockParts = propBlock.MakePartSet(partSize) blockID = types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} newVss := make([]*validatorStub, nVals+1) @@ -487,9 +481,7 @@ func TestSimulateValidatorsChange(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) - ///////////////////////////////////////////////////////////////////////////// // HEIGHT 5 - ///////////////////////////////////////////////////////////////////////////// height++ incrementHeight(vss...) // Reflect the changes to vss[nVals] at height 3 and resort newVss. @@ -507,15 +499,13 @@ func TestSimulateValidatorsChange(t *testing.T) { } ensureNewRound(newRoundCh, height+1, 0) - ///////////////////////////////////////////////////////////////////////////// // HEIGHT 6 - ///////////////////////////////////////////////////////////////////////////// height++ incrementHeight(vss...) removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0) err = assertMempool(css[0].txNotifier).CheckTx(removeValidatorTx3, nil, mempl.TxInfo{}) assert.Nil(t, err) - propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) + propBlock, _ = css[0].createProposalBlock() // changeProposer(t, cs1, vs2) propBlockParts = propBlock.MakePartSet(partSize) blockID = types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()} newVss = make([]*validatorStub, nVals+3) @@ -594,7 +584,7 @@ func TestHandshakeReplayNone(t *testing.T) { // Test mockProxyApp should not panic when app return ABCIResponses with some empty ResponseDeliverTx func TestMockProxyApp(t *testing.T) { - sim.CleanupFunc() //clean the test env created in TestSimulateValidatorsChange + sim.CleanupFunc() // clean the test env created in TestSimulateValidatorsChange logger := log.TestingLogger() var validTxs, invalidTxs = 0, 0 txIndex := 0 @@ -676,7 +666,7 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin chain = append([]*types.Block{}, sim.Chain...) // copy chain commits = sim.Commits store = newMockBlockStore(config, genesisState.ConsensusParams) - } else { //test single node + } else { // test single node testConfig := ResetConfig(fmt.Sprintf("%s_%v_s", t.Name(), mode)) defer os.RemoveAll(testConfig.RootDir) walBody, err := WALWithNBlocks(t, numBlocks) @@ -805,14 +795,14 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateStore sm.Store, } defer proxyApp.Stop() //nolint:errcheck // ignore - state.Version.Consensus.App = kvstore.ProtocolVersion //simulate handshake, receive app version + state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{ Validators: validators, }); err != nil { panic(err) } - if err := stateStore.Save(state); err != nil { //save height 1's validatorsInfo + if err := stateStore.Save(state); err != nil { // save height 1's validatorsInfo panic(err) } switch mode { @@ -853,16 +843,16 @@ func buildTMStateFromChain( if err := proxyApp.Start(); err != nil { panic(err) } - defer proxyApp.Stop() //nolint:errcheck //ignore + defer proxyApp.Stop() //nolint:errcheck - state.Version.Consensus.App = kvstore.ProtocolVersion //simulate handshake, receive app version + state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{ Validators: validators, }); err != nil { panic(err) } - if err := stateStore.Save(state); err != nil { //save height 1's validatorsInfo + if err := stateStore.Save(state); err != nil { // save height 1's validatorsInfo panic(err) } switch mode { diff --git a/consensus/state.go b/consensus/state.go index d59a4f81df..bed855e6c4 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -520,7 +520,7 @@ func (cs *State) updateRoundStep(round int32, step cstypes.RoundStepType) { // enterNewRound(height, 0) at cs.StartTime. func (cs *State) scheduleRound0(rs *cstypes.RoundState) { - //cs.Logger.Info("scheduleRound0", "now", tmtime.Now(), "startTime", cs.StartTime) + // cs.Logger.Info("scheduleRound0", "now", tmtime.Now(), "startTime", cs.StartTime) sleepDuration := rs.StartTime.Sub(tmtime.Now()) cs.scheduleTimeout(sleepDuration, rs.Height, 0, cstypes.RoundStepNewHeight) } @@ -2137,9 +2137,9 @@ func (cs *State) signAddVote(msgType tmproto.SignedMsgType, hash []byte, header cs.Logger.Info("Signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err) return vote } - //if !cs.replayMode { + // if !cs.replayMode { cs.Logger.Error("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err) - //} + // } return nil } diff --git a/consensus/state_test.go b/consensus/state_test.go index 85358ae503..fa2aafb56a 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -191,7 +191,7 @@ func TestStateBadProposal(t *testing.T) { proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) voteCh := subscribe(cs1.eventBus, types.EventQueryVote) - propBlock, _ := cs1.createProposalBlock() //changeProposer(t, cs1, vs2) + propBlock, _ := cs1.createProposalBlock() // changeProposer(t, cs1, vs2) // make the second validator the proposer by incrementing round round++ @@ -380,7 +380,7 @@ func TestStateFullRound2(t *testing.T) { signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propPartSetHeader, vs2) ensurePrevote(voteCh, height, round) // prevote - ensurePrecommit(voteCh, height, round) //precommit + ensurePrecommit(voteCh, height, round) // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) diff --git a/consensus/wal.go b/consensus/wal.go index abc2edf643..80f5e6b071 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -208,7 +208,7 @@ func (wal *BaseWAL) WriteSync(msg WALMessage) error { } if err := wal.FlushAndSync(); err != nil { - wal.Logger.Error(`WriteSync failed to flush consensus wal. + wal.Logger.Error(`WriteSync failed to flush consensus wal. WARNING: may result in creating alternative proposals / votes for the current height iff the node restarted`, "err", err) return err @@ -282,8 +282,6 @@ func (wal *BaseWAL) SearchForEndHeight( return nil, false, nil } -/////////////////////////////////////////////////////////////////////////////// - // A WALEncoder writes custom-encoded WAL messages to an output stream. // // Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value @@ -330,8 +328,6 @@ func (enc *WALEncoder) Encode(v *TimedWALMessage) error { return err } -/////////////////////////////////////////////////////////////////////////////// - // IsDataCorruptionError returns true if data has been corrupted inside WAL. func IsDataCorruptionError(err error) bool { _, ok := err.(DataCorruptionError) diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index f141c9201e..1c449717bd 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -35,7 +35,6 @@ func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { logger := log.TestingLogger().With("wal_generator", "wal_generator") logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) - ///////////////////////////////////////////////////////////////////////////// // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS // NOTE: we can't import node package because of circular dependency. // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. @@ -91,7 +90,6 @@ func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { consensusState.SetPrivValidator(privValidator) } // END OF COPY PASTE - ///////////////////////////////////////////////////////////////////////////// // set consensus wal to buffered WAL, which will write all incoming msgs to buffer numBlocksWritten := make(chan struct{}) @@ -121,7 +119,7 @@ func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { } } -//WALWithNBlocks returns a WAL content with numBlocks. +// WALWithNBlocks returns a WAL content with numBlocks. func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) { var b bytes.Buffer wr := bufio.NewWriter(&b) diff --git a/consensus/wal_test.go b/consensus/wal_test.go index ae0c7140f9..4ee8136091 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -60,7 +60,7 @@ func TestWALTruncate(t *testing.T) { err = WALGenerateNBlocks(t, wal.Group(), 60) require.NoError(t, err) - time.Sleep(1 * time.Millisecond) //wait groupCheckDuration, make sure RotateFile run + time.Sleep(1 * time.Millisecond) // wait groupCheckDuration, make sure RotateFile run if err := wal.FlushAndSync(); err != nil { t.Error(err) diff --git a/libs/bytes/bytes.go b/libs/bytes/bytes.go index d7682437be..809049e845 100644 --- a/libs/bytes/bytes.go +++ b/libs/bytes/bytes.go @@ -25,7 +25,7 @@ func (bz HexBytes) MarshalJSON() ([]byte, error) { s := strings.ToUpper(hex.EncodeToString(bz)) jbz := make([]byte, len(s)+2) jbz[0] = '"' - copy(jbz[1:], []byte(s)) + copy(jbz[1:], s) jbz[len(jbz)-1] = '"' return jbz, nil } diff --git a/libs/clist/clist_test.go b/libs/clist/clist_test.go index 14b7e37c0f..d10a1e5ae9 100644 --- a/libs/clist/clist_test.go +++ b/libs/clist/clist_test.go @@ -33,21 +33,21 @@ func TestSmall(t *testing.T) { t.Error("Expected len 3, got ", l.Len()) } - //fmt.Printf("%p %v\n", el1, el1) - //fmt.Printf("%p %v\n", el2, el2) - //fmt.Printf("%p %v\n", el3, el3) + // fmt.Printf("%p %v\n", el1, el1) + // fmt.Printf("%p %v\n", el2, el2) + // fmt.Printf("%p %v\n", el3, el3) r1 := l.Remove(el1) - //fmt.Printf("%p %v\n", el1, el1) - //fmt.Printf("%p %v\n", el2, el2) - //fmt.Printf("%p %v\n", el3, el3) + // fmt.Printf("%p %v\n", el1, el1) + // fmt.Printf("%p %v\n", el2, el2) + // fmt.Printf("%p %v\n", el3, el3) r2 := l.Remove(el2) - //fmt.Printf("%p %v\n", el1, el1) - //fmt.Printf("%p %v\n", el2, el2) - //fmt.Printf("%p %v\n", el3, el3) + // fmt.Printf("%p %v\n", el1, el1) + // fmt.Printf("%p %v\n", el2, el2) + // fmt.Printf("%p %v\n", el3, el3) r3 := l.Remove(el3) @@ -97,10 +97,10 @@ func _TestGCFifo(t *testing.T) { for el := l.Front(); el != nil; { l.Remove(el) - //oldEl := el + // oldEl := el el = el.Next() - //oldEl.DetachPrev() - //oldEl.DetachNext() + // oldEl.DetachPrev() + // oldEl.DetachNext() } runtime.GC() @@ -211,7 +211,7 @@ func TestScanRightDeleteRandom(t *testing.T) { // Remove it l.Remove(rmEl) - //fmt.Print(".") + // fmt.Print(".") // Insert a new element newEl := l.PushBack(-1*i - 1) diff --git a/libs/cmap/cmap_test.go b/libs/cmap/cmap_test.go index 669e50882a..bab78da965 100644 --- a/libs/cmap/cmap_test.go +++ b/libs/cmap/cmap_test.go @@ -22,7 +22,7 @@ func TestIterateKeysWithValues(t *testing.T) { // Iterating Keys, checking for matching Value for _, key := range cmap.Keys() { - val := strings.Replace(key, "key", "value", -1) + val := strings.ReplaceAll(key, "key", "value") assert.Equal(t, val, cmap.Get(key)) } diff --git a/libs/fail/fail.go b/libs/fail/fail.go index 03f4feda07..38cec9a296 100644 --- a/libs/fail/fail.go +++ b/libs/fail/fail.go @@ -23,7 +23,7 @@ func envSet() int { } // Fail when FAIL_TEST_INDEX == callIndex -var callIndex int //indexes Fail calls +var callIndex int // indexes Fail calls func Fail() { callIndexToFail := envSet() diff --git a/libs/log/tracing_logger_test.go b/libs/log/tracing_logger_test.go index 354476755b..6d6edc5ca3 100644 --- a/libs/log/tracing_logger_test.go +++ b/libs/log/tracing_logger_test.go @@ -22,16 +22,16 @@ func TestTracingLogger(t *testing.T) { err2 := errors.New("it does not matter how slowly you go, so long as you do not stop") logger1.With("err1", err1).Info("foo", "err2", err2) - want := strings.Replace( - strings.Replace( + want := strings.ReplaceAll( + strings.ReplaceAll( `{"_msg":"foo","err1":"`+ fmt.Sprintf("%+v", err1)+ `","err2":"`+ fmt.Sprintf("%+v", err2)+ `","level":"info"}`, - "\t", "", -1, - ), "\n", "", -1) - have := strings.Replace(strings.Replace(strings.TrimSpace(buf.String()), "\\n", "", -1), "\\t", "", -1) + "\t", "", + ), "\n", "") + have := strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(buf.String()), "\\n", ""), "\\t", "") if want != have { t.Errorf("\nwant '%s'\nhave '%s'", want, have) } diff --git a/libs/math/fraction.go b/libs/math/fraction.go index 4883c0d3d1..399bc1c186 100644 --- a/libs/math/fraction.go +++ b/libs/math/fraction.go @@ -25,7 +25,7 @@ func (fr Fraction) String() string { // to the equivalent fraction else returns an error. The format of the string must be // one number followed by a slash (/) and then the other number. func ParseFraction(f string) (Fraction, error) { - o := strings.SplitN(f, "/", -1) + o := strings.Split(f, "/") if len(o) != 2 { return Fraction{}, errors.New("incorrect formating: should be like \"1/3\"") } diff --git a/libs/protoio/io_test.go b/libs/protoio/io_test.go index 5ec5627d25..f4556b31f0 100644 --- a/libs/protoio/io_test.go +++ b/libs/protoio/io_test.go @@ -50,11 +50,11 @@ func iotest(writer protoio.WriteCloser, reader protoio.ReadCloser) error { r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := range msgs { msgs[i] = test.NewPopulatedNinOptNative(r, true) - //issue 31 + // issue 31 if i == 5 { msgs[i] = &test.NinOptNative{} } - //issue 31 + // issue 31 if i == 999 { msgs[i] = &test.NinOptNative{} } @@ -133,7 +133,7 @@ func TestVarintNoClose(t *testing.T) { } } -//issue 32 +// issue 32 func TestVarintMaxSize(t *testing.T) { buf := newBuffer() writer := protoio.NewDelimitedWriter(buf) diff --git a/libs/pubsub/pubsub_test.go b/libs/pubsub/pubsub_test.go index 10aaaf05ff..8482a13fa7 100644 --- a/libs/pubsub/pubsub_test.go +++ b/libs/pubsub/pubsub_test.go @@ -488,9 +488,7 @@ func benchmarkNClientsOneQuery(n int, b *testing.B) { } } -/////////////////////////////////////////////////////////////////////////////// -/// HELPERS -/////////////////////////////////////////////////////////////////////////////// +// HELPERS func assertReceive(t *testing.T, expected interface{}, ch <-chan pubsub.Message, msgAndArgs ...interface{}) { select { diff --git a/node/id.go b/node/id.go index 18e6aeb5d0..ffa162f815 100644 --- a/node/id.go +++ b/node/id.go @@ -30,6 +30,6 @@ type SignedNodeGreeting struct { } func (pnid *PrivNodeID) SignGreeting() *SignedNodeGreeting { - //greeting := NodeGreeting{} + // greeting := NodeGreeting{} return nil } diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index 70793b8a0c..d4997d81c4 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -380,7 +380,6 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection return fooSecConn, barSecConn } -/////////////////////////////////////////////////////////////////////////////// // Benchmarks func BenchmarkWriteSecretConnection(b *testing.B) { @@ -428,7 +427,7 @@ func BenchmarkWriteSecretConnection(b *testing.B) { if err := fooSecConn.Close(); err != nil { b.Error(err) } - //barSecConn.Close() race condition + // barSecConn.Close() race condition } func BenchmarkReadSecretConnection(b *testing.B) { diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 4601c2839e..538ccfcc39 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -75,7 +75,7 @@ func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { if tr.logMessages { tr.mtx.Lock() defer tr.mtx.Unlock() - //fmt.Printf("Received: %X, %X\n", chID, msgBytes) + // fmt.Printf("Received: %X, %X\n", chID, msgBytes) tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) tr.msgsCounter++ } diff --git a/p2p/upnp/upnp.go b/p2p/upnp/upnp.go index 9f69753d0c..c00530acae 100644 --- a/p2p/upnp/upnp.go +++ b/p2p/upnp/upnp.go @@ -269,7 +269,7 @@ func soapRequest(url, function, message, domain string) (r *http.Response, err e } req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - //req.Header.Set("Transfer-Encoding", "chunked") + // req.Header.Set("Transfer-Encoding", "chunked") req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"") req.Header.Set("Connection", "Close") req.Header.Set("Cache-Control", "no-cache") diff --git a/privval/signer_listener_endpoint_test.go b/privval/signer_listener_endpoint_test.go index 99d734ae34..02b19eff3d 100644 --- a/privval/signer_listener_endpoint_test.go +++ b/privval/signer_listener_endpoint_test.go @@ -145,8 +145,6 @@ func TestRetryConnToRemoteSigner(t *testing.T) { } } -/////////////////////////////////// - func newSignerListenerEndpoint(logger log.Logger, addr string, timeoutReadWrite time.Duration) *SignerListenerEndpoint { proto, address := tmnet.ProtocolAndAddress(addr) diff --git a/proxy/app_conn_test.go b/proxy/app_conn_test.go index 022bd8997f..683fe47e0d 100644 --- a/proxy/app_conn_test.go +++ b/proxy/app_conn_test.go @@ -123,7 +123,7 @@ func BenchmarkEcho(b *testing.B) { b.StopTimer() // info := proxy.InfoSync(types.RequestInfo{""}) - //b.Log("N: ", b.N, info) + // b.Log("N: ", b.N, info) } func TestInfo(t *testing.T) { diff --git a/rpc/client/examples_test.go b/rpc/client/examples_test.go index dc2792ea2e..474aba1b60 100644 --- a/rpc/client/examples_test.go +++ b/rpc/client/examples_test.go @@ -22,7 +22,7 @@ func ExampleHTTP_simple() { rpcAddr := rpctest.GetConfig().RPC.ListenAddress c, err := rpchttp.New(rpcAddr, "/websocket") if err != nil { - log.Fatal(err) + log.Fatal(err) //nolint:gocritic } // Create a transaction @@ -69,7 +69,6 @@ func ExampleHTTP_batching() { // Start a tendermint node (and kvstore) in the background to test against app := kvstore.NewApplication() node := rpctest.StartTendermint(app, rpctest.SuppressStdout, rpctest.RecreateConfig) - defer rpctest.StopTendermint(node) // Create our RPC client rpcAddr := rpctest.GetConfig().RPC.ListenAddress @@ -78,6 +77,8 @@ func ExampleHTTP_batching() { log.Fatal(err) } + defer rpctest.StopTendermint(node) + // Create our two transactions k1 := []byte("firstName") v1 := []byte("satoshi") @@ -97,7 +98,7 @@ func ExampleHTTP_batching() { // Broadcast the transaction and wait for it to commit (rather use // c.BroadcastTxSync though in production). if _, err := batch.BroadcastTxCommit(context.Background(), tx); err != nil { - log.Fatal(err) + log.Fatal(err) //nolint:gocritic } } diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 85b9c501db..49f26b85fc 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -82,7 +82,7 @@ func TestCustomHTTPClient(t *testing.T) { func TestCorsEnabled(t *testing.T) { origin := rpctest.GetConfig().RPC.CORSAllowedOrigins[0] - remote := strings.Replace(rpctest.GetConfig().RPC.ListenAddress, "tcp", "http", -1) + remote := strings.ReplaceAll(rpctest.GetConfig().RPC.ListenAddress, "tcp", "http") req, err := http.NewRequest("GET", remote, nil) require.Nil(t, err, "%+v", err) diff --git a/rpc/jsonrpc/client/http_json_client.go b/rpc/jsonrpc/client/http_json_client.go index 1cd704d7b8..c6e1af035a 100644 --- a/rpc/jsonrpc/client/http_json_client.go +++ b/rpc/jsonrpc/client/http_json_client.go @@ -66,7 +66,7 @@ func (u parsedURL) GetHostWithPath() string { // Get a trimmed address - useful for WS connections func (u parsedURL) GetTrimmedHostWithPath() string { // replace / with . for http requests (kvstore domain) - return strings.Replace(u.GetHostWithPath(), "/", ".", -1) + return strings.ReplaceAll(u.GetHostWithPath(), "/", ".") } // Get a trimmed address with protocol - useful as address in RPC connections diff --git a/rpc/jsonrpc/client/ws_client.go b/rpc/jsonrpc/client/ws_client.go index 832947909c..1c7ade6578 100644 --- a/rpc/jsonrpc/client/ws_client.go +++ b/rpc/jsonrpc/client/ws_client.go @@ -248,7 +248,6 @@ func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, param return c.Send(ctx, request) } -/////////////////////////////////////////////////////////////////////////////// // Private methods func (c *WSClient) nextRequestID() types.JSONRPCIntID { @@ -521,7 +520,6 @@ func (c *WSClient) readRoutine() { } } -/////////////////////////////////////////////////////////////////////////////// // Predefined methods // Subscribe to a query. Note the server must have a "subscribe" route diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index 24269df8e3..ec12f85d7d 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -404,5 +404,5 @@ func randBytes(t *testing.T) []byte { buf := make([]byte, n) _, err := crand.Read(buf) require.Nil(t, err) - return bytes.Replace(buf, []byte("="), []byte{100}, -1) + return bytes.ReplaceAll(buf, []byte("="), []byte{100}) } diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index ffc2445806..ffe9d133b0 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -14,9 +14,7 @@ import ( types "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) -/////////////////////////////////////////////////////////////////////////////// // HTTP + JSON handler -/////////////////////////////////////////////////////////////////////////////// // jsonrpc calls grab the given method's function info and runs reflect.Call func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.HandlerFunc { diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index 5efe0fbbec..3e6250183c 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -13,9 +13,7 @@ import ( types "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) -/////////////////////////////////////////////////////////////////////////////// // HTTP + URI handler -/////////////////////////////////////////////////////////////////////////////// var reInt = regexp.MustCompile(`^-?[0-9]+$`) diff --git a/rpc/jsonrpc/server/rpc_func.go b/rpc/jsonrpc/server/rpc_func.go index eed1fea8f0..e5855c3140 100644 --- a/rpc/jsonrpc/server/rpc_func.go +++ b/rpc/jsonrpc/server/rpc_func.go @@ -23,9 +23,7 @@ func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger lo mux.HandleFunc("/", handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, logger))) } -/////////////////////////////////////////////////////////////////////////////// // Function introspection -/////////////////////////////////////////////////////////////////////////////// // RPCFunc contains the introspected type information for a function type RPCFunc struct { diff --git a/rpc/jsonrpc/server/ws_handler.go b/rpc/jsonrpc/server/ws_handler.go index 7faf76a68d..e4e5d75041 100644 --- a/rpc/jsonrpc/server/ws_handler.go +++ b/rpc/jsonrpc/server/ws_handler.go @@ -17,9 +17,7 @@ import ( types "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) -/////////////////////////////////////////////////////////////////////////////// // WebSocket handler -/////////////////////////////////////////////////////////////////////////////// const ( defaultWSWriteChanCapacity = 100 @@ -100,9 +98,7 @@ func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Requ } } -/////////////////////////////////////////////////////////////////////////////// // WebSocket connection -/////////////////////////////////////////////////////////////////////////////// // A single websocket connection contains listener id, underlying ws // connection, and the event switch for subscribing to events. diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 5e212ec429..19099d5e5f 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -72,7 +72,7 @@ func makePathname() string { } // fmt.Println(p) sep := string(filepath.Separator) - return strings.Replace(p, sep, "_", -1) + return strings.ReplaceAll(p, sep, "_") } func randPort() int { diff --git a/scripts/wal2json/main.go b/scripts/wal2json/main.go index fa4f4e25d2..6fa890522f 100644 --- a/scripts/wal2json/main.go +++ b/scripts/wal2json/main.go @@ -46,14 +46,17 @@ func main() { if err == nil { _, err = os.Stdout.Write([]byte("\n")) } + if err == nil { if endMsg, ok := msg.Msg.(cs.EndHeightMessage); ok { _, err = os.Stdout.Write([]byte(fmt.Sprintf("ENDHEIGHT %d\n", endMsg.Height))) } } + if err != nil { fmt.Println("Failed to write message", err) - os.Exit(1) + os.Exit(1) //nolint:gocritic } + } } diff --git a/state/state.go b/state/state.go index 9c9bb6f98e..d9da840ca9 100644 --- a/state/state.go +++ b/state/state.go @@ -131,7 +131,7 @@ func (state State) IsEmpty() bool { return state.Validators == nil // XXX can't compare to Empty } -//ToProto takes the local state type and returns the equivalent proto type +// ToProto takes the local state type and returns the equivalent proto type func (state *State) ToProto() (*tmstate.State, error) { if state == nil { return nil, errors.New("state is nil") diff --git a/state/state_test.go b/state/state_test.go index 778bae196f..1632f4304b 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -59,7 +59,7 @@ func TestStateCopy(t *testing.T) { %v`, state)) } -//TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil. +// TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil. func TestMakeGenesisStateNilValidators(t *testing.T) { doc := types.GenesisDoc{ ChainID: "dummy", diff --git a/state/store.go b/state/store.go index 0b9cc6890c..5bd2d9fc75 100644 --- a/state/store.go +++ b/state/store.go @@ -70,7 +70,7 @@ type Store interface { PruneStates(int64, int64) error } -//dbStore wraps a db (github.com/tendermint/tm-db) +// dbStore wraps a db (github.com/tendermint/tm-db) type dbStore struct { db dbm.DB } @@ -390,7 +390,7 @@ func (store dbStore) LoadABCIResponses(height int64) (*tmstate.ABCIResponses, er // Exposed for testing. func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCIResponses) error { var dtxs []*abci.ResponseDeliverTx - //strip nil values, + // strip nil values, for _, tx := range abciResponses.DeliverTxs { if tx != nil { dtxs = append(dtxs, tx) diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index 11bbebbc94..b056e9dd42 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -613,7 +613,6 @@ LOOP: return filteredHashes } -/////////////////////////////////////////////////////////////////////////////// // Keys func isTagKey(key []byte) bool { diff --git a/store/store.go b/store/store.go index 6674e4b526..9ae4d555dd 100644 --- a/store/store.go +++ b/store/store.go @@ -488,7 +488,7 @@ func LoadBlockStoreState(db dbm.DB) tmstore.BlockStoreState { return bsj } -//mustEncode proto encodes a proto.message and panics if fails +// mustEncode proto encodes a proto.message and panics if fails func mustEncode(pb proto.Message) []byte { bz, err := proto.Marshal(pb) if err != nil { diff --git a/types/events.go b/types/events.go index 5ed2d6dfee..38b3569831 100644 --- a/types/events.go +++ b/types/events.go @@ -38,9 +38,7 @@ const ( EventVote = "Vote" ) -/////////////////////////////////////////////////////////////////////////////// // ENCODING / DECODING -/////////////////////////////////////////////////////////////////////////////// // TMEventData implements events.EventData. type TMEventData interface { @@ -127,9 +125,7 @@ type EventDataValidatorSetUpdates struct { ValidatorUpdates []*Validator `json:"validator_updates"` } -/////////////////////////////////////////////////////////////////////////////// // PUBSUB -/////////////////////////////////////////////////////////////////////////////// const ( // EventTypeKey is a reserved composite key for event name. diff --git a/types/part_set_test.go b/types/part_set_test.go index e7347e2f1d..c6ea0f4525 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -35,7 +35,7 @@ func TestBasicPartSet(t *testing.T) { assert.True(t, partSet2.HasHeader(partSet.Header())) for i := 0; i < int(partSet.Total()); i++ { part := partSet.GetPart(i) - //t.Logf("\n%v", part) + // t.Logf("\n%v", part) added, err := partSet2.AddPart(part) if !added || err != nil { t.Errorf("failed to add part %v, error: %v", i, err) diff --git a/types/priv_validator.go b/types/priv_validator.go index 394123164a..49211773a0 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -39,9 +39,7 @@ func (pvs PrivValidatorsByAddress) Less(i, j int) bool { } func (pvs PrivValidatorsByAddress) Swap(i, j int) { - it := pvs[i] - pvs[i] = pvs[j] - pvs[j] = it + pvs[i], pvs[j] = pvs[j], pvs[i] } //---------------------------------------- diff --git a/types/validator_set.go b/types/validator_set.go index cdd748476c..cc1a452a1d 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -708,9 +708,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, return nil } -/////////////////////////////////////////////////////////////////////////////// // LIGHT CLIENT VERIFICATION METHODS -/////////////////////////////////////////////////////////////////////////////// // VerifyCommitLight verifies +2/3 of the set had signed the given commit. // @@ -1026,7 +1024,6 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []Pr return NewValidatorSet(valz), privValidators } -/////////////////////////////////////////////////////////////////////////////// // safe addition/subtraction/multiplication func safeAdd(a, b int64) (int64, bool) { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 9ce5b6025e..84fdcdf489 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -1658,9 +1658,7 @@ func (valz validatorsByPriority) Less(i, j int) bool { } func (valz validatorsByPriority) Swap(i, j int) { - it := valz[i] - valz[i] = valz[j] - valz[j] = it + valz[i], valz[j] = valz[j], valz[i] } //------------------------------------- @@ -1679,9 +1677,7 @@ func (tvals testValsByVotingPower) Less(i, j int) bool { } func (tvals testValsByVotingPower) Swap(i, j int) { - it := tvals[i] - tvals[i] = tvals[j] - tvals[j] = it + tvals[i], tvals[j] = tvals[j], tvals[i] } //------------------------------------- diff --git a/types/vote.go b/types/vote.go index 29cfdd0519..7b841c28ad 100644 --- a/types/vote.go +++ b/types/vote.go @@ -220,7 +220,7 @@ func (vote *Vote) ToProto() *tmproto.Vote { } } -//FromProto converts a proto generetad type to a handwritten type +// FromProto converts a proto generetad type to a handwritten type // return type, nil if everything converts safely, otherwise nil, error func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { if pv == nil { From 5e6e6315ad1ee722df03e823827c09629f95f99b Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 14 Oct 2020 16:55:02 +0200 Subject: [PATCH 036/108] github: add nightly E2E testnet action (#5480) --- .github/workflows/e2e-nightly.yml | 43 +++++++++++++++++++++++++++++++ test/e2e/networks/ci.toml | 4 +-- test/e2e/runner/load.go | 14 +++++++--- test/e2e/runner/start.go | 4 +-- 4 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/e2e-nightly.yml diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml new file mode 100644 index 0000000000..96b406961c --- /dev/null +++ b/.github/workflows/e2e-nightly.yml @@ -0,0 +1,43 @@ +# Runs randomly generated E2E testnets nightly. +name: e2e-nightly +on: + workflow_dispatch: # allow running workflow manually + schedule: + - cron: '0 2 * * *' + +jobs: + e2e-nightly-test: + # Run parallel jobs for the listed testnet groups (must match the + # ./build/generator -g flag) + strategy: + fail-fast: false + matrix: + group: ['00', '01', '02', '03'] + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v2 + + - name: Build + working-directory: test/e2e + # Run make jobs in parallel, since we can't run steps in parallel. + run: make -j2 docker generator runner + + - name: Generate testnets + working-directory: test/e2e + run: ./build/generator -g 4 -d networks/nightly + + - name: Run testnets in group ${{ matrix.group }} + working-directory: test/e2e + run: sudo ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml + + - name: Notify Slack on failure + uses: rtCamp/action-slack-notify@e9db0ef + if: ${{ failure() }} + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_CHANNEL: tendermint-internal + SLACK_USERNAME: Nightly E2E Test Failure + SLACK_COLOR: danger + SLACK_MESSAGE: Nightly E2E test failed (group ${{ matrix.group }}) + SLACK_FOOTER: '' diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index e366bb6c58..12478ef99a 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -26,11 +26,11 @@ validator05 = 50 [node.seed01] mode = "seed" -persistent_peers = ["seed02"] +seeds = ["seed02"] [node.seed02] mode = "seed" -persistent_peers = ["seed01"] +seeds = ["seed01"] [node.validator01] seeds = ["seed01"] diff --git a/test/e2e/runner/load.go b/test/e2e/runner/load.go index a59b3f5032..495c573d31 100644 --- a/test/e2e/runner/load.go +++ b/test/e2e/runner/load.go @@ -16,9 +16,17 @@ import ( // Load generates transactions against the network until the given // context is cancelled. func Load(ctx context.Context, testnet *e2e.Testnet) error { - concurrency := 50 + // Since transactions are executed across all nodes in the network, we need + // to reduce transaction load for larger networks to avoid using too much + // CPU. This gives high-throughput small networks and low-throughput large ones. + // This also limits the number of TCP connections, since each worker has + // a connection to all nodes. + concurrency := 64 / len(testnet.Nodes) + if concurrency == 0 { + concurrency = 1 + } initialTimeout := 1 * time.Minute - stallTimeout := 15 * time.Second + stallTimeout := 30 * time.Second chTx := make(chan types.Tx) chSuccess := make(chan types.Tx) @@ -26,7 +34,7 @@ func Load(ctx context.Context, testnet *e2e.Testnet) error { defer cancel() // Spawn job generator and processors. - logger.Info("Starting transaction load...") + logger.Info(fmt.Sprintf("Starting transaction load (%v workers)...", concurrency)) started := time.Now() go loadGenerate(ctx, chTx) diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index bf52e190ef..915222faf5 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -24,7 +24,7 @@ func Start(testnet *e2e.Testnet) error { if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { return err } - if _, err := waitForNode(node, 0, 10*time.Second); err != nil { + if _, err := waitForNode(node, 0, 15*time.Second); err != nil { return err } logger.Info(fmt.Sprintf("Node %v up on http://127.0.0.1:%v", node.Name, node.ProxyPort)) @@ -56,7 +56,7 @@ func Start(testnet *e2e.Testnet) error { if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { return err } - status, err := waitForNode(node, node.StartAt, 30*time.Second) + status, err := waitForNode(node, node.StartAt, 1*time.Minute) if err != nil { return err } From 8ebb39eed6f335275b5d5927acaebf26d9f2cf92 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 14 Oct 2020 16:35:03 +0200 Subject: [PATCH 037/108] github: rename e2e jobs (#5502) --- .github/workflows/e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 55a04d4a23..f30638cdeb 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,4 @@ -name: e2e-tests +name: e2e # Runs the CI end-to-end test network on all pushes to master or release branches # and every pull request, but only if any Go files have been changed. on: @@ -9,7 +9,7 @@ on: - release/** jobs: - test: + e2e-test: runs-on: ubuntu-latest timeout-minutes: 15 steps: From 4e2e487c7afd3ce55fc05d59ec076398fcad23d2 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 15 Oct 2020 20:59:16 +0200 Subject: [PATCH 038/108] test: clean up E2E test volumes using a container (#5509) --- .github/workflows/e2e-nightly.yml | 2 +- .github/workflows/e2e.yml | 2 +- test/e2e/runner/cleanup.go | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml index 96b406961c..653b99827e 100644 --- a/.github/workflows/e2e-nightly.yml +++ b/.github/workflows/e2e-nightly.yml @@ -29,7 +29,7 @@ jobs: - name: Run testnets in group ${{ matrix.group }} working-directory: test/e2e - run: sudo ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml + run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml - name: Notify Slack on failure uses: rtCamp/action-slack-notify@e9db0ef diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f30638cdeb..fbc7488bd9 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,7 +29,7 @@ jobs: - name: Run CI testnet working-directory: test/e2e - run: sudo ./build/runner -f networks/ci.toml + run: ./build/runner -f networks/ci.toml if: "env.GIT_DIFF != ''" - name: Emit logs on failure diff --git a/test/e2e/runner/cleanup.go b/test/e2e/runner/cleanup.go index 3f7eff3e68..951e0d612f 100644 --- a/test/e2e/runner/cleanup.go +++ b/test/e2e/runner/cleanup.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) @@ -21,6 +22,24 @@ func Cleanup(testnet *e2e.Testnet) error { } logger.Info("Removing Docker containers and networks") + err = execCompose(testnet.Dir, "stop") + if err != nil { + return err + } + + // On Linux, some local files in the volume will be owned by root since Tendermint + // runs as root inside the container, so we need to clean them up from within a + // container running as root too. + absDir, err := filepath.Abs(testnet.Dir) + if err != nil { + return err + } + err = execDocker("run", "--entrypoint", "", "-v", fmt.Sprintf("%v:/network", absDir), + "tendermint/e2e-node", "sh", "-c", "rm -rf /network/*/") + if err != nil { + return err + } + err = execCompose(testnet.Dir, "down") if err != nil { return err From 6473f0178cab139bbc1a4a28cf0fbd146a2db379 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 16 Oct 2020 12:10:29 +0200 Subject: [PATCH 039/108] test: tweak E2E tests for nightly runs (#5512) --- .github/workflows/e2e-nightly.yml | 2 ++ test/e2e/generator/generate.go | 3 ++- test/e2e/runner/rpc.go | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml index 653b99827e..7a060669b0 100644 --- a/.github/workflows/e2e-nightly.yml +++ b/.github/workflows/e2e-nightly.yml @@ -25,6 +25,7 @@ jobs: - name: Generate testnets working-directory: test/e2e + # When changing -g, also change the matrix groups above run: ./build/generator -g 4 -d networks/nightly - name: Run testnets in group ${{ matrix.group }} @@ -38,6 +39,7 @@ jobs: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_CHANNEL: tendermint-internal SLACK_USERNAME: Nightly E2E Test Failure + SLACK_ICON_EMOJI: ':skull:' SLACK_COLOR: danger SLACK_MESSAGE: Nightly E2E test failed (group ${{ matrix.group }}) SLACK_FOOTER: '' diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 0f8c79330f..8581d63440 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -29,7 +29,8 @@ var ( nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin"} // "grpc" nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} // FIXME disabled v1 due to https://github.com/tendermint/tendermint/issues/5444 - nodeFastSyncs = uniformChoice{"", "v0", "v2"} // "v1" + // FIXME disabled v2 due to https://github.com/tendermint/tendermint/issues/5513 + nodeFastSyncs = uniformChoice{"", "v0"} // "v1", "v2" nodeStateSyncs = uniformChoice{false, true} nodePersistIntervals = uniformChoice{0, 1, 5} nodeSnapshotIntervals = uniformChoice{0, 3} diff --git a/test/e2e/runner/rpc.go b/test/e2e/runner/rpc.go index 82b1a8ec53..c50ab65421 100644 --- a/test/e2e/runner/rpc.go +++ b/test/e2e/runner/rpc.go @@ -55,7 +55,7 @@ func waitForHeight(testnet *e2e.Testnet, height int64) (*types.Block, *types.Blo if len(clients) == 0 { return nil, nil, errors.New("unable to connect to any network nodes") } - if time.Since(lastIncrease) >= 10*time.Second { + if time.Since(lastIncrease) >= 20*time.Second { if maxResult == nil { return nil, nil, errors.New("chain stalled at unknown height") } From b17b28a1639565bc79122498d279444dba427803 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 20 Oct 2020 10:27:58 +0200 Subject: [PATCH 040/108] test: enable ABCI gRPC client in E2E testnets (#5521) Once #5520 lands, we can re-enable gRPC ABCI protocol in the E2E testnets. --- test/e2e/generator/generate.go | 5 ++--- test/e2e/networks/ci.toml | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 8581d63440..4920dff425 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -24,9 +24,8 @@ var ( } // The following specify randomly chosen values for testnet nodes. - nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} - // FIXME disabled grpc due to https://github.com/tendermint/tendermint/issues/5439 - nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin"} // "grpc" + nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} + nodeABCIProtocols = uniformChoice{"unix", "tcp", "grpc", "builtin"} nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} // FIXME disabled v1 due to https://github.com/tendermint/tendermint/issues/5444 // FIXME disabled v2 due to https://github.com/tendermint/tendermint/issues/5513 diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 12478ef99a..522fb52662 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -50,9 +50,7 @@ persist_interval = 0 [node.validator03] seeds = ["seed01"] database = "badgerdb" -# FIXME Should use grpc, but it has race conditions -# https://github.com/tendermint/tendermint/issues/5439 -abci_protocol = "unix" +abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 retain_blocks = 3 @@ -72,9 +70,7 @@ start_at = 1005 # Becomes part of the validator set at 1010 seeds = ["seed02"] database = "cleveldb" fast_sync = "v0" -# FIXME Should use grpc, but it has race conditions -# https://github.com/tendermint/tendermint/issues/5439 -abci_protocol = "tcp" +abci_protocol = "grpc" privval_protocol = "tcp" # FIXME The WAL gets corrupted when killed # https://github.com/tendermint/tendermint/issues/5422 From d113da01cd16a15aa168d291c060a37da9207615 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 20 Oct 2020 13:44:22 +0200 Subject: [PATCH 041/108] test: enable blockchain v2 in E2E testnet generator (#5533) When #5499 and #5530 land, we can re-enable v2 in the E2E testnet generator (and thus the nightly E2E tests). --- test/e2e/generator/generate.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 4920dff425..e21d5d2fd5 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -27,9 +27,8 @@ var ( nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} nodeABCIProtocols = uniformChoice{"unix", "tcp", "grpc", "builtin"} nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} - // FIXME disabled v1 due to https://github.com/tendermint/tendermint/issues/5444 - // FIXME disabled v2 due to https://github.com/tendermint/tendermint/issues/5513 - nodeFastSyncs = uniformChoice{"", "v0"} // "v1", "v2" + // FIXME v1 disabled due to https://github.com/tendermint/tendermint/issues/5444 + nodeFastSyncs = uniformChoice{"", "v0", "v2"} // "v1", nodeStateSyncs = uniformChoice{false, true} nodePersistIntervals = uniformChoice{0, 1, 5} nodeSnapshotIntervals = uniformChoice{0, 3} From 2f72f553ac4edfe7a06ce406ab96195ecda25750 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 20 Oct 2020 21:30:07 +0200 Subject: [PATCH 042/108] test: enable restart/kill perturbations in E2E tests (#5537) When #5536 lands we can re-enable restart/kill perturbations in E2E tests. --- test/e2e/generator/generate.go | 5 ++--- test/e2e/networks/ci.toml | 20 +++++--------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index e21d5d2fd5..851dfeb600 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -36,9 +36,8 @@ var ( nodePerturbations = probSetChoice{ "disconnect": 0.1, "pause": 0.1, - // FIXME disabled due to https://github.com/tendermint/tendermint/issues/5422 - // "kill": 0.1, - // "restart": 0.1, + "kill": 0.1, + "restart": 0.1, } ) diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 522fb52662..9db2ca9698 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -43,9 +43,7 @@ database = "boltdb" abci_protocol = "tcp" privval_protocol = "tcp" persist_interval = 0 -# FIXME The WAL gets corrupted when restarted -# https://github.com/tendermint/tendermint/issues/5422 -#perturb = ["restart"] +perturb = ["restart"] [node.validator03] seeds = ["seed01"] @@ -54,9 +52,7 @@ abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 retain_blocks = 3 -# FIXME The WAL gets corrupted when killed -# https://github.com/tendermint/tendermint/issues/5422 -#perturb = ["kill"] +perturb = ["kill"] [node.validator04] persistent_peers = ["validator01"] @@ -72,9 +68,7 @@ database = "cleveldb" fast_sync = "v0" abci_protocol = "grpc" privval_protocol = "tcp" -# FIXME The WAL gets corrupted when killed -# https://github.com/tendermint/tendermint/issues/5422 -#perturb = ["kill", "pause", "disconnect", "restart"] +perturb = ["kill", "pause", "disconnect", "restart"] [node.full01] start_at = 1010 @@ -83,9 +77,7 @@ mode = "full" # https://github.com/tendermint/tendermint/issues/5444 fast_sync = "v2" persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] -# FIXME The WAL gets corrupted when restarted -# https://github.com/tendermint/tendermint/issues/5422 -#perturb = ["restart"] +perturb = ["restart"] [node.full02] start_at = 1015 @@ -93,6 +85,4 @@ mode = "full" fast_sync = "v2" state_sync = true seeds = ["seed01"] -# FIXME The WAL gets corrupted when restarted -# https://github.com/tendermint/tendermint/issues/5422 -#perturb = ["restart"] +perturb = ["restart"] From 8b4f0dba7018381a3aafab4fbdc8765fd2726dc8 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 22 Oct 2020 22:53:27 +0200 Subject: [PATCH 043/108] test: run remaining E2E testnets on run-multiple.sh failure (#5557) Fixes #5542. --- test/e2e/run-multiple.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/test/e2e/run-multiple.sh b/test/e2e/run-multiple.sh index e9699cf1e7..5d6a20ef95 100755 --- a/test/e2e/run-multiple.sh +++ b/test/e2e/run-multiple.sh @@ -3,33 +3,47 @@ # This is a convenience script that takes a list of testnet manifests # as arguments and runs each one of them sequentially. If a testnet # fails, the container logs are dumped to stdout along with the testnet -# manifest. +# manifest, but the remaining testnets are still run. # # This is mostly used to run generated networks in nightly CI jobs. # -# Don't set -e, since we explicitly check status codes ourselves. -set -u +set -euo pipefail if [[ $# == 0 ]]; then echo "Usage: $0 [MANIFEST...]" >&2 exit 1 fi +FAILED=() + for MANIFEST in "$@"; do START=$SECONDS echo "==> Running testnet $MANIFEST..." - ./build/runner -f "$MANIFEST" - if [[ $? -ne 0 ]]; then + if ! ./build/runner -f "$MANIFEST"; then echo "==> Testnet $MANIFEST failed, dumping manifest..." cat "$MANIFEST" echo "==> Dumping container logs for $MANIFEST..." ./build/runner -f "$MANIFEST" logs - exit 1 + + echo "==> Cleaning up failed testnet $MANIFEST..." + ./build/runner -f "$MANIFEST" cleanup + + FAILED+=("$MANIFEST") fi echo "==> Completed testnet $MANIFEST in $(( SECONDS - START ))s" echo "" done + +if [[ ${#FAILED[@]} -ne 0 ]]; then + echo "${#FAILED[@]} testnets failed:" + for MANIFEST in "${FAILED[@]}"; do + echo "- $MANIFEST" + done + exit 1 +else + echo "All testnets successful" +fi From 75879ab1d7f9b17e1fa7cee5eb5889bb8f3b3fbc Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 23 Oct 2020 10:17:15 +0200 Subject: [PATCH 044/108] test: tag E2E Docker resources and autoremove them (#5558) Fixes #5555. --- test/e2e/runner/cleanup.go | 59 ++++++++++++++++++++++++++++---------- test/e2e/runner/setup.go | 4 +++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/test/e2e/runner/cleanup.go b/test/e2e/runner/cleanup.go index 951e0d612f..0af49db7da 100644 --- a/test/e2e/runner/cleanup.go +++ b/test/e2e/runner/cleanup.go @@ -11,44 +11,73 @@ import ( // Cleanup removes the Docker Compose containers and testnet directory. func Cleanup(testnet *e2e.Testnet) error { - if testnet.Dir == "" { - return errors.New("no directory set") + err := cleanupDocker() + if err != nil { + return err } - _, err := os.Stat(testnet.Dir) - if os.IsNotExist(err) { - return nil - } else if err != nil { + err = cleanupDir(testnet.Dir) + if err != nil { return err } + return nil +} +// cleanupDocker removes all E2E resources (with label e2e=True), regardless +// of testnet. +func cleanupDocker() error { logger.Info("Removing Docker containers and networks") - err = execCompose(testnet.Dir, "stop") + + // GNU xargs requires the -r flag to not run when input is empty, macOS + // does this by default. Ugly, but works. + xargsR := `$(if [[ $OSTYPE == "linux-gnu"* ]]; then echo -n "-r"; fi)` + + err := exec("bash", "-c", fmt.Sprintf( + "docker container ls -q --filter label=e2e | xargs %v docker container rm -f", xargsR)) + if err != nil { + return err + } + + err = exec("bash", "-c", fmt.Sprintf( + "docker network ls -q --filter label=e2e | xargs %v docker network rm", xargsR)) if err != nil { return err } + return nil +} + +// cleanupDir cleans up a testnet directory +func cleanupDir(dir string) error { + if dir == "" { + return errors.New("no directory set") + } + + _, err := os.Stat(dir) + if os.IsNotExist(err) { + return nil + } else if err != nil { + return err + } + + logger.Info(fmt.Sprintf("Removing testnet directory %q", dir)) + // On Linux, some local files in the volume will be owned by root since Tendermint // runs as root inside the container, so we need to clean them up from within a // container running as root too. - absDir, err := filepath.Abs(testnet.Dir) + absDir, err := filepath.Abs(dir) if err != nil { return err } - err = execDocker("run", "--entrypoint", "", "-v", fmt.Sprintf("%v:/network", absDir), + err = execDocker("run", "--rm", "--entrypoint", "", "-v", fmt.Sprintf("%v:/network", absDir), "tendermint/e2e-node", "sh", "-c", "rm -rf /network/*/") if err != nil { return err } - err = execCompose(testnet.Dir, "down") + err = os.RemoveAll(dir) if err != nil { return err } - logger.Info(fmt.Sprintf("Removing testnet directory %q", testnet.Dir)) - err = os.RemoveAll(testnet.Dir) - if err != nil { - return err - } return nil } diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index e77fcd7ee2..c22eee7258 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -122,6 +122,8 @@ func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) { networks: {{ .Name }}: + labels: + e2e: true driver: bridge {{- if .IPv6 }} enable_ipv6: true @@ -134,6 +136,8 @@ networks: services: {{- range .Nodes }} {{ .Name }}: + labels: + e2e: true container_name: {{ .Name }} image: tendermint/e2e-node {{- if eq .ABCIProtocol "builtin" }} From dacbfbe1fef3285ae552c54364473b9a91c07394 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 23 Oct 2020 12:33:08 +0200 Subject: [PATCH 045/108] test: add evidence e2e tests (#5488) --- .gitignore | 1 + test/e2e/Makefile | 7 +- test/e2e/app/config.go | 1 + test/e2e/app/main.go | 109 +- test/e2e/docker/Dockerfile | 1 + test/e2e/docker/entrypoint-maverick | 10 + test/e2e/generator/generate.go | 31 +- test/e2e/generator/main.go | 1 + test/e2e/generator/random.go | 2 +- test/e2e/networks/ci.toml | 1 + test/e2e/networks/simple.toml | 1 + test/e2e/pkg/manifest.go | 10 + test/e2e/pkg/testnet.go | 30 + test/e2e/runner/main.go | 27 +- test/e2e/runner/setup.go | 26 +- test/e2e/runner/wait.go | 21 + test/e2e/tests/app_test.go | 1 + test/e2e/tests/block_test.go | 1 + test/e2e/tests/e2e_test.go | 1 + test/e2e/tests/evidence_test.go | 50 + test/e2e/tests/net_test.go | 1 + test/e2e/tests/validator_test.go | 1 + test/maverick/README.md | 51 + test/maverick/consensus/metrics.go | 220 +++ test/maverick/consensus/misbehavior.go | 398 +++++ test/maverick/consensus/msgs.go | 377 +++++ test/maverick/consensus/reactor.go | 1720 +++++++++++++++++++ test/maverick/consensus/replay.go | 533 ++++++ test/maverick/consensus/replay_file.go | 338 ++++ test/maverick/consensus/replay_stubs.go | 90 + test/maverick/consensus/state.go | 1976 ++++++++++++++++++++++ test/maverick/consensus/ticker.go | 134 ++ test/maverick/consensus/wal.go | 437 +++++ test/maverick/consensus/wal_fuzz.go | 31 + test/maverick/consensus/wal_generator.go | 229 +++ test/maverick/main.go | 237 +++ test/maverick/node/node.go | 1440 ++++++++++++++++ test/maverick/node/privval.go | 358 ++++ 38 files changed, 8864 insertions(+), 39 deletions(-) create mode 100755 test/e2e/docker/entrypoint-maverick create mode 100644 test/e2e/tests/evidence_test.go create mode 100644 test/maverick/README.md create mode 100644 test/maverick/consensus/metrics.go create mode 100644 test/maverick/consensus/misbehavior.go create mode 100644 test/maverick/consensus/msgs.go create mode 100644 test/maverick/consensus/reactor.go create mode 100644 test/maverick/consensus/replay.go create mode 100644 test/maverick/consensus/replay_file.go create mode 100644 test/maverick/consensus/replay_stubs.go create mode 100644 test/maverick/consensus/state.go create mode 100644 test/maverick/consensus/ticker.go create mode 100644 test/maverick/consensus/wal.go create mode 100644 test/maverick/consensus/wal_fuzz.go create mode 100644 test/maverick/consensus/wal_generator.go create mode 100644 test/maverick/main.go create mode 100644 test/maverick/node/node.go create mode 100644 test/maverick/node/privval.go diff --git a/.gitignore b/.gitignore index 8cd4bf484e..a1e17329e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ remote_dump vendor .vagrant test/e2e/build +test/maverick/maverick test/e2e/networks/*/ test/p2p/data/ test/logs diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 602de7547d..c9eb8bc192 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -8,6 +8,11 @@ docker: # ABCI testing). app: go build -o build/app -tags badgerdb,boltdb,cleveldb,rocksdb ./app + +# To be used primarily by the e2e docker instance. If you want to produce this binary +# elsewhere, then run go build in the maverick directory. +maverick: + go build -o build/maverick -tags badgerdb,boltdb,cleveldb,rocksdb ../maverick generator: go build -o build/generator ./generator @@ -15,4 +20,4 @@ generator: runner: go build -o build/runner ./runner -.PHONY: all app docker generator runner +.PHONY: all app docker generator maverick runner diff --git a/test/e2e/app/config.go b/test/e2e/app/config.go index 20df6ce90f..281419160d 100644 --- a/test/e2e/app/config.go +++ b/test/e2e/app/config.go @@ -21,6 +21,7 @@ type Config struct { PrivValServer string `toml:"privval_server"` PrivValKey string `toml:"privval_key"` PrivValState string `toml:"privval_state"` + Misbehaviors map[string]string `toml:"misbehaviors"` } // LoadConfig loads the configuration from disk. diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go index 8a5ed95a39..d37b09be4f 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/app/main.go @@ -5,9 +5,11 @@ import ( "fmt" "os" "path/filepath" + "strconv" "time" "github.com/spf13/viper" + "github.com/tendermint/tendermint/abci/server" "github.com/tendermint/tendermint/config" tmflags "github.com/tendermint/tendermint/libs/cli/flags" @@ -17,6 +19,8 @@ import ( "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" + mcs "github.com/tendermint/tendermint/test/maverick/consensus" + maverick "github.com/tendermint/tendermint/test/maverick/node" ) var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) @@ -60,7 +64,11 @@ func run(configFile string) error { case "socket", "grpc": err = startApp(cfg) case "builtin": - err = startNode(cfg) + if len(cfg.Misbehaviors) == 0 { + err = startNode(cfg) + } else { + err = startMaverick(cfg) + } default: err = fmt.Errorf("invalid protocol %q", cfg.Protocol) } @@ -102,51 +110,59 @@ func startNode(cfg *Config) error { return err } - home := os.Getenv("TMHOME") - if home == "" { - return errors.New("TMHOME not set") - } - viper.AddConfigPath(filepath.Join(home, "config")) - viper.SetConfigName("config") - err = viper.ReadInConfig() + tmcfg, nodeLogger, nodeKey, err := setupNode() if err != nil { - return err + return fmt.Errorf("failed to setup config: %w", err) } - tmcfg := config.DefaultConfig() - err = viper.Unmarshal(tmcfg) + + n, err := node.NewNode(tmcfg, + privval.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + node.DefaultGenesisDocProviderFunc(tmcfg), + node.DefaultDBProvider, + node.DefaultMetricsProvider(tmcfg.Instrumentation), + nodeLogger, + ) if err != nil { return err } - tmcfg.SetRoot(home) - if err = tmcfg.ValidateBasic(); err != nil { - return fmt.Errorf("error in config file: %v", err) - } - if tmcfg.LogFormat == config.LogFormatJSON { - logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) - } - logger, err = tmflags.ParseLogLevel(tmcfg.LogLevel, logger, config.DefaultLogLevel()) + return n.Start() +} + +// startMaverick starts a Maverick node that runs the application directly. It assumes the Tendermint +// configuration is in $TMHOME/config/tendermint.toml. +func startMaverick(cfg *Config) error { + app, err := NewApplication(cfg) if err != nil { return err } - logger = logger.With("module", "main") - nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) + tmcfg, logger, nodeKey, err := setupNode() if err != nil { - return fmt.Errorf("failed to load or gen node key %s: %w", tmcfg.NodeKeyFile(), err) + return fmt.Errorf("failed to setup config: %w", err) } - n, err := node.NewNode(tmcfg, - privval.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), + misbehaviors := make(map[int64]mcs.Misbehavior, len(cfg.Misbehaviors)) + for heightString, misbehaviorString := range cfg.Misbehaviors { + height, _ := strconv.ParseInt(heightString, 10, 64) + misbehaviors[height] = mcs.MisbehaviorList[misbehaviorString] + } + + n, err := maverick.NewNode(tmcfg, + maverick.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(app), - node.DefaultGenesisDocProviderFunc(tmcfg), - node.DefaultDBProvider, - node.DefaultMetricsProvider(tmcfg.Instrumentation), + maverick.DefaultGenesisDocProviderFunc(tmcfg), + maverick.DefaultDBProvider, + maverick.DefaultMetricsProvider(tmcfg.Instrumentation), logger, + misbehaviors, ) if err != nil { return err } + return n.Start() } @@ -175,3 +191,42 @@ func startSigner(cfg *Config) error { logger.Info(fmt.Sprintf("Remote signer connecting to %v", cfg.PrivValServer)) return nil } + +func setupNode() (*config.Config, log.Logger, *p2p.NodeKey, error) { + var tmcfg *config.Config + + home := os.Getenv("TMHOME") + if home == "" { + return nil, nil, nil, errors.New("TMHOME not set") + } + viper.AddConfigPath(filepath.Join(home, "config")) + viper.SetConfigName("config") + err := viper.ReadInConfig() + if err != nil { + return nil, nil, nil, err + } + tmcfg = config.DefaultConfig() + err = viper.Unmarshal(tmcfg) + if err != nil { + return nil, nil, nil, err + } + tmcfg.SetRoot(home) + if err = tmcfg.ValidateBasic(); err != nil { + return nil, nil, nil, fmt.Errorf("error in config file: %w", err) + } + if tmcfg.LogFormat == config.LogFormatJSON { + logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) + } + nodeLogger, err := tmflags.ParseLogLevel(tmcfg.LogLevel, logger, config.DefaultLogLevel()) + if err != nil { + return nil, nil, nil, err + } + nodeLogger = nodeLogger.With("module", "main") + + nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile()) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to load or gen node key %s: %w", tmcfg.NodeKeyFile(), err) + } + + return tmcfg, nodeLogger, nodeKey, nil +} diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile index 273bd07c6c..825aa7f0d1 100644 --- a/test/e2e/docker/Dockerfile +++ b/test/e2e/docker/Dockerfile @@ -18,6 +18,7 @@ RUN go mod download COPY . . RUN make build && cp build/tendermint /usr/bin/tendermint COPY test/e2e/docker/entrypoint* /usr/bin/ +RUN cd test/e2e && make maverick && cp build/maverick /usr/bin/maverick RUN cd test/e2e && make app && cp build/app /usr/bin/app # Set up runtime directory. We don't use a separate runtime image since we need diff --git a/test/e2e/docker/entrypoint-maverick b/test/e2e/docker/entrypoint-maverick new file mode 100755 index 0000000000..9469e24473 --- /dev/null +++ b/test/e2e/docker/entrypoint-maverick @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Forcibly remove any stray UNIX sockets left behind from previous runs +rm -rf /var/run/privval.sock /var/run/app.sock + +/usr/bin/app /tendermint/config/app.toml & + +sleep 1 + +/usr/bin/maverick "$@" diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 851dfeb600..2ccbd3a6bf 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -4,6 +4,7 @@ import ( "fmt" "math/rand" "sort" + "strconv" "strings" e2e "github.com/tendermint/tendermint/test/e2e/pkg" @@ -39,6 +40,10 @@ var ( "kill": 0.1, "restart": 0.1, } + nodeMisbehaviors = weightedChoice{ + misbehaviorOption{"double-prevote"}: 1, + misbehaviorOption{}: 9, + } ) // Generate generates random testnets using the given RNG. @@ -91,7 +96,7 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er nextStartAt := manifest.InitialHeight + 5 quorum := numValidators*2/3 + 1 for i := 1; i <= numValidators; i++ { - startAt := int64(0) + startAt := manifest.InitialHeight if i > quorum { startAt = nextStartAt nextStartAt += 5 @@ -174,7 +179,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er // generating invalid configurations. We do not set Seeds or PersistentPeers // here, since we need to know the overall network topology and startup // sequencing. -func generateNode(r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) *e2e.ManifestNode { +func generateNode( + r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) *e2e.ManifestNode { node := e2e.ManifestNode{ Mode: string(mode), StartAt: startAt, @@ -196,6 +202,14 @@ func generateNode(r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) node.SnapshotInterval = 3 } + if node.Mode == "validator" { + node.Misbehaviors = nodeMisbehaviors.Choose(r).(misbehaviorOption). + atHeight(startAt + 5 + int64(r.Intn(10))) + if len(node.Misbehaviors) != 0 { + node.PrivvalProtocol = "file" + } + } + // If a node which does not persist state also does not retain blocks, randomly // choose to either persist state or retain all blocks. if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 { @@ -223,3 +237,16 @@ func generateNode(r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) func ptrUint64(i uint64) *uint64 { return &i } + +type misbehaviorOption struct { + misbehavior string +} + +func (m misbehaviorOption) atHeight(height int64) map[string]string { + misbehaviorMap := make(map[string]string) + if m.misbehavior == "" { + return misbehaviorMap + } + misbehaviorMap[strconv.Itoa(int(height))] = m.misbehavior + return misbehaviorMap +} diff --git a/test/e2e/generator/main.go b/test/e2e/generator/main.go index ce73ccd979..f17b4f3f4f 100644 --- a/test/e2e/generator/main.go +++ b/test/e2e/generator/main.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/log" ) diff --git a/test/e2e/generator/random.go b/test/e2e/generator/random.go index 04d1ac70de..ec59a01b2a 100644 --- a/test/e2e/generator/random.go +++ b/test/e2e/generator/random.go @@ -57,7 +57,7 @@ func (uc uniformChoice) Choose(r *rand.Rand) interface{} { } // weightedChoice chooses a single random key from a map of keys and weights. -type weightedChoice map[interface{}]uint // nolint:unused +type weightedChoice map[interface{}]uint func (wc weightedChoice) Choose(r *rand.Rand) interface{} { total := 0 diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 9db2ca9698..9085736a40 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -36,6 +36,7 @@ seeds = ["seed01"] seeds = ["seed01"] snapshot_interval = 5 perturb = ["disconnect"] +misbehaviors = { 1012 = "double-prevote", 1018 = "double-prevote" } [node.validator02] seeds = ["seed02"] diff --git a/test/e2e/networks/simple.toml b/test/e2e/networks/simple.toml index 96b81f79fe..37f711a91f 100644 --- a/test/e2e/networks/simple.toml +++ b/test/e2e/networks/simple.toml @@ -2,3 +2,4 @@ [node.validator02] [node.validator03] [node.validator04] + diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index c951d9409a..8316e57e6a 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -115,6 +115,16 @@ type ManifestNode struct { // pause: temporarily pauses (freezes) the node // restart: restarts the node, shutting it down with SIGTERM Perturb []string `toml:"perturb"` + + // Misbehaviors sets how a validator behaves during consensus at a + // certain height. Multiple misbehaviors at different heights can be used + // + // An example of misbehaviors + // { 10 = "double-prevote", 20 = "double-prevote"} + // + // For more information, look at the readme in the maverick folder. + // A list of all behaviors can be found in ../maverick/consensus/behavior.go + Misbehaviors map[string]string `toml:"misbehaviors"` } // Save saves the testnet manifest to a file. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 351f83378d..c2f55bc3d0 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -15,6 +15,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" rpchttp "github.com/tendermint/tendermint/rpc/client/http" + mcs "github.com/tendermint/tendermint/test/maverick/consensus" ) const ( @@ -78,6 +79,7 @@ type Node struct { Seeds []*Node PersistentPeers []*Node Perturbations []Perturbation + Misbehaviors map[int64]string } // LoadTestnet loads a testnet from a manifest file, using the filename to @@ -147,6 +149,7 @@ func LoadTestnet(file string) (*Testnet, error) { SnapshotInterval: nodeManifest.SnapshotInterval, RetainBlocks: nodeManifest.RetainBlocks, Perturbations: []Perturbation{}, + Misbehaviors: make(map[int64]string), } if nodeManifest.Mode != "" { node.Mode = Mode(nodeManifest.Mode) @@ -166,6 +169,13 @@ func LoadTestnet(file string) (*Testnet, error) { for _, p := range nodeManifest.Perturb { node.Perturbations = append(node.Perturbations, Perturbation(p)) } + for heightString, misbehavior := range nodeManifest.Misbehaviors { + height, err := strconv.ParseInt(heightString, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse height %s to int64: %w", heightString, err) + } + node.Misbehaviors[height] = misbehavior + } testnet.Nodes = append(testnet.Nodes, node) } @@ -324,6 +334,26 @@ func (n Node) Validate(testnet Testnet) error { return fmt.Errorf("invalid perturbation %q", perturbation) } } + + if (n.PrivvalProtocol != "file" || n.Mode != "validator") && len(n.Misbehaviors) != 0 { + return errors.New("must be using \"file\" privval protocol to implement misbehaviors") + } + + for height, misbehavior := range n.Misbehaviors { + if height < n.StartAt { + return fmt.Errorf("misbehavior height %d is before start height %d", height, n.StartAt) + } + exists := false + for possibleBehaviors := range mcs.MisbehaviorList { + if possibleBehaviors == misbehavior { + exists = true + } + } + if !exists { + return fmt.Errorf("misbehavior %s does not exist", misbehavior) + } + } + return nil } diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index 733a57f3e5..bcca5b8999 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -6,11 +6,14 @@ import ( "os" "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/log" e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) -var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) +var ( + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) +) func main() { NewCLI().Run() @@ -18,8 +21,9 @@ func main() { // CLI is the Cobra-based command-line interface. type CLI struct { - root *cobra.Command - testnet *e2e.Testnet + root *cobra.Command + testnet *e2e.Testnet + preserve bool } // NewCLI sets up the CLI. @@ -65,10 +69,13 @@ func NewCLI() *CLI { if err := Start(cli.testnet); err != nil { return err } + if err := waitForAllMisbehaviors(cli.testnet); err != nil { + return err + } if err := Perturb(cli.testnet); err != nil { return err } - if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through + if err := Wait(cli.testnet, interphaseWaitPeriod); err != nil { // allow some txs to go through return err } @@ -76,14 +83,17 @@ func NewCLI() *CLI { if err := <-chLoadResult; err != nil { return err } - if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests + // wait for network to settle before tests + if err := Wait(cli.testnet, interphaseWaitPeriod); err != nil { return err } if err := Test(cli.testnet); err != nil { return err } - if err := Cleanup(cli.testnet); err != nil { - return err + if !cli.preserve { + if err := Cleanup(cli.testnet); err != nil { + return err + } } return nil }, @@ -92,6 +102,9 @@ func NewCLI() *CLI { cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest") _ = cli.root.MarkPersistentFlagRequired("file") + cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false, + "Preserves the running of the test net after tests are completed") + cli.root.AddCommand(&cobra.Command{ Use: "setup", Short: "Generates the testnet directory and configuration", diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index c22eee7258..8c641d9f64 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -12,11 +12,13 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "text/template" "time" "github.com/BurntSushi/toml" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/p2p" @@ -118,7 +120,20 @@ func Setup(testnet *e2e.Testnet) error { // MakeDockerCompose generates a Docker Compose config for a testnet. func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) { // Must use version 2 Docker Compose format, to support IPv6. - tmpl, err := template.New("docker-compose").Parse(`version: '2.4' + tmpl, err := template.New("docker-compose").Funcs(template.FuncMap{ + "misbehaviorsToString": func(misbehaviors map[int64]string) string { + str := "" + for height, misbehavior := range misbehaviors { + // after the first behavior set, a comma must be prepended + if str != "" { + str += "," + } + heightString := strconv.Itoa(int(height)) + str += misbehavior + "," + heightString + } + return str + }, + }).Parse(`version: '2.4' networks: {{ .Name }}: @@ -142,6 +157,9 @@ services: image: tendermint/e2e-node {{- if eq .ABCIProtocol "builtin" }} entrypoint: /usr/bin/entrypoint-builtin +{{- else if .Misbehaviors }} + entrypoint: /usr/bin/entrypoint-maverick + command: ["node", "--misbehaviors", "{{ misbehaviorsToString .Misbehaviors }}"] {{- end }} init: true ports: @@ -330,6 +348,12 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { } } + misbehaviors := make(map[string]string) + for height, misbehavior := range node.Misbehaviors { + misbehaviors[strconv.Itoa(int(height))] = misbehavior + } + cfg["misbehaviors"] = misbehaviors + if len(node.Testnet.ValidatorUpdates) > 0 { validatorUpdates := map[string]map[string]int64{} for height, validators := range node.Testnet.ValidatorUpdates { diff --git a/test/e2e/runner/wait.go b/test/e2e/runner/wait.go index fd3474c5c5..c53032c307 100644 --- a/test/e2e/runner/wait.go +++ b/test/e2e/runner/wait.go @@ -7,6 +7,8 @@ import ( e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) +const interphaseWaitPeriod = 5 + // Wait waits for a number of blocks to be produced, and for all nodes to catch // up with it. func Wait(testnet *e2e.Testnet, blocks int64) error { @@ -22,3 +24,22 @@ func Wait(testnet *e2e.Testnet, blocks int64) error { } return nil } + +// WaitForAllMisbehaviors calculates the height of the last misbehavior and ensures the entire +// testnet has surpassed this height before moving on to the next phase +func waitForAllMisbehaviors(testnet *e2e.Testnet) error { + _, _, err := waitForHeight(testnet, lastMisbehaviorHeight(testnet)) + return err +} + +func lastMisbehaviorHeight(testnet *e2e.Testnet) int64 { + lastHeight := testnet.InitialHeight + for _, n := range testnet.Nodes { + for height := range n.Misbehaviors { + if height > lastHeight { + lastHeight = height + } + } + } + return lastHeight + interphaseWaitPeriod +} diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 60018cace5..33eac1b407 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" "github.com/tendermint/tendermint/types" ) diff --git a/test/e2e/tests/block_test.go b/test/e2e/tests/block_test.go index 688d7bd6c6..23653d1e4a 100644 --- a/test/e2e/tests/block_test.go +++ b/test/e2e/tests/block_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) diff --git a/test/e2e/tests/e2e_test.go b/test/e2e/tests/e2e_test.go index 80b43229ca..15c747b5b7 100644 --- a/test/e2e/tests/e2e_test.go +++ b/test/e2e/tests/e2e_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/require" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" rpctypes "github.com/tendermint/tendermint/rpc/core/types" e2e "github.com/tendermint/tendermint/test/e2e/pkg" diff --git a/test/e2e/tests/evidence_test.go b/test/e2e/tests/evidence_test.go new file mode 100644 index 0000000000..b982249407 --- /dev/null +++ b/test/e2e/tests/evidence_test.go @@ -0,0 +1,50 @@ +package e2e_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" +) + +// assert that all nodes that have blocks during the height (or height + 1) of a misbehavior has evidence +// for that misbehavior +func TestEvidence_Misbehavior(t *testing.T) { + blocks := fetchBlockChain(t) + testNode(t, func(t *testing.T, node e2e.Node) { + for _, block := range blocks { + // Find any evidence blaming this node in this block + var nodeEvidence types.Evidence + for _, evidence := range block.Evidence.Evidence { + switch evidence := evidence.(type) { + case *types.DuplicateVoteEvidence: + if bytes.Equal(evidence.VoteA.ValidatorAddress, node.Key.PubKey().Address()) { + nodeEvidence = evidence + } + default: + t.Fatalf("unexpected evidence type %T", evidence) + } + } + + // Check that evidence was as expected (evidence is submitted in following height) + misbehavior, ok := node.Misbehaviors[block.Height-1] + if !ok { + require.Nil(t, nodeEvidence, "found unexpected evidence %v in height %v", + nodeEvidence, block.Height) + continue + } + require.NotNil(t, nodeEvidence, "no evidence found for misbehavior %v in height %v", + misbehavior, block.Height) + + switch misbehavior { + case "double-prevote": + require.IsType(t, &types.DuplicateVoteEvidence{}, nodeEvidence, "unexpected evidence type") + default: + t.Fatalf("unknown misbehavior %v", misbehavior) + } + } + }) +} diff --git a/test/e2e/tests/net_test.go b/test/e2e/tests/net_test.go index e0a84aeebf..1ca43fa055 100644 --- a/test/e2e/tests/net_test.go +++ b/test/e2e/tests/net_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) diff --git a/test/e2e/tests/validator_test.go b/test/e2e/tests/validator_test.go index 2398d0e629..47eb1555ab 100644 --- a/test/e2e/tests/validator_test.go +++ b/test/e2e/tests/validator_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/require" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" "github.com/tendermint/tendermint/types" ) diff --git a/test/maverick/README.md b/test/maverick/README.md new file mode 100644 index 0000000000..3082755364 --- /dev/null +++ b/test/maverick/README.md @@ -0,0 +1,51 @@ +# Maverick + +![](https://assets.rollingstone.com/assets/2015/article/tom-cruise-to-fight-drones-in-top-gun-sequel-20150629/201166/large_rect/1435581755/1401x788-Top-Gun-3.jpg) + +A byzantine node used to test Tendermint consensus against a plethora of different faulty misbehaviors. Designed to easily create new faulty misbehaviors to examine how a Tendermint network reacts to the misbehavior. Can also be used for fuzzy testing with different network arrangements. + +## Misbehaviors + +A misbehavior allows control at the following stages as highlighted by the struct below + +```go +type Misbehavior struct { + String string + + EnterPropose func(cs *State, height int64, round int32) + + EnterPrevote func(cs *State, height int64, round int32) + + EnterPrecommit func(cs *State, height int64, round int32) + + ReceivePrevote func(cs *State, prevote *types.Vote) + + ReceivePrecommit func(cs *State, precommit *types.Vote) + + ReceiveProposal func(cs *State, proposal *types.Proposal) error +} +``` + +At each of these events, the node can exhibit a different misbehavior. To create a new misbehavior define a function that builds off the existing default misbehavior and then overrides one or more of these functions. Then append it to the misbehaviors list so the node recognizes it like so: + +```go +var MisbehaviorList = map[string]Misbehavior{ + "double-prevote": DoublePrevoteMisbehavior(), +} +``` + +## Setup + +The maverick node takes most of the functionality from the existing Tendermint CLI. To install this, in the directory of this readme, run: + +```bash +go build +``` + +Use `maverick init` to initialize a single node and `maverick node` to run it. This will run it normally unless you use the misbehaviors flag as follows: + +```bash +maverick node --proxy_app persistent_kvstore --misbehaviors double-vote,10 +``` + +This would cause the node to vote twice in every round at height 10. To add more misbehaviors at different heights, append the next misbehavior and height after the first (with comma separation). diff --git a/test/maverick/consensus/metrics.go b/test/maverick/consensus/metrics.go new file mode 100644 index 0000000000..bbd823a3fc --- /dev/null +++ b/test/maverick/consensus/metrics.go @@ -0,0 +1,220 @@ +package consensus + +import ( + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/discard" + + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +const ( + // MetricsSubsystem is a subsystem shared by all metrics exposed by this + // package. + MetricsSubsystem = "consensus" +) + +// Metrics contains metrics exposed by this package. +type Metrics struct { + // Height of the chain. + Height metrics.Gauge + + // ValidatorLastSignedHeight of a validator. + ValidatorLastSignedHeight metrics.Gauge + + // Number of rounds. + Rounds metrics.Gauge + + // Number of validators. + Validators metrics.Gauge + // Total power of all validators. + ValidatorsPower metrics.Gauge + // Power of a validator. + ValidatorPower metrics.Gauge + // Amount of blocks missed by a validator. + ValidatorMissedBlocks metrics.Gauge + // Number of validators who did not sign. + MissingValidators metrics.Gauge + // Total power of the missing validators. + MissingValidatorsPower metrics.Gauge + // Number of validators who tried to double sign. + ByzantineValidators metrics.Gauge + // Total power of the byzantine validators. + ByzantineValidatorsPower metrics.Gauge + + // Time between this and the last block. + BlockIntervalSeconds metrics.Histogram + + // Number of transactions. + NumTxs metrics.Gauge + // Size of the block. + BlockSizeBytes metrics.Gauge + // Total number of transactions. + TotalTxs metrics.Gauge + // The latest block height. + CommittedHeight metrics.Gauge + // Whether or not a node is fast syncing. 1 if yes, 0 if no. + FastSyncing metrics.Gauge + // Whether or not a node is state syncing. 1 if yes, 0 if no. + StateSyncing metrics.Gauge + + // Number of blockparts transmitted by peer. + BlockParts metrics.Counter +} + +// PrometheusMetrics returns Metrics build using Prometheus client library. +// Optionally, labels can be provided along with their values ("foo", +// "fooValue"). +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + Height: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "height", + Help: "Height of the chain.", + }, labels).With(labelsAndValues...), + Rounds: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "rounds", + Help: "Number of rounds.", + }, labels).With(labelsAndValues...), + + Validators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validators", + Help: "Number of validators.", + }, labels).With(labelsAndValues...), + ValidatorLastSignedHeight: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validator_last_signed_height", + Help: "Last signed height for a validator", + }, append(labels, "validator_address")).With(labelsAndValues...), + ValidatorMissedBlocks: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validator_missed_blocks", + Help: "Total missed blocks for a validator", + }, append(labels, "validator_address")).With(labelsAndValues...), + ValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validators_power", + Help: "Total power of all validators.", + }, labels).With(labelsAndValues...), + ValidatorPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validator_power", + Help: "Power of a validator", + }, append(labels, "validator_address")).With(labelsAndValues...), + MissingValidators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "missing_validators", + Help: "Number of validators who did not sign.", + }, labels).With(labelsAndValues...), + MissingValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "missing_validators_power", + Help: "Total power of the missing validators.", + }, labels).With(labelsAndValues...), + ByzantineValidators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "byzantine_validators", + Help: "Number of validators who tried to double sign.", + }, labels).With(labelsAndValues...), + ByzantineValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "byzantine_validators_power", + Help: "Total power of the byzantine validators.", + }, labels).With(labelsAndValues...), + BlockIntervalSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "block_interval_seconds", + Help: "Time between this and the last block.", + }, labels).With(labelsAndValues...), + NumTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "num_txs", + Help: "Number of transactions.", + }, labels).With(labelsAndValues...), + BlockSizeBytes: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "block_size_bytes", + Help: "Size of the block.", + }, labels).With(labelsAndValues...), + TotalTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "total_txs", + Help: "Total number of transactions.", + }, labels).With(labelsAndValues...), + CommittedHeight: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "latest_block_height", + Help: "The latest block height.", + }, labels).With(labelsAndValues...), + FastSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "fast_syncing", + Help: "Whether or not a node is fast syncing. 1 if yes, 0 if no.", + }, labels).With(labelsAndValues...), + StateSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "state_syncing", + Help: "Whether or not a node is state syncing. 1 if yes, 0 if no.", + }, labels).With(labelsAndValues...), + BlockParts: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "block_parts", + Help: "Number of blockparts transmitted by peer.", + }, append(labels, "peer_id")).With(labelsAndValues...), + } +} + +// NopMetrics returns no-op Metrics. +func NopMetrics() *Metrics { + return &Metrics{ + Height: discard.NewGauge(), + + ValidatorLastSignedHeight: discard.NewGauge(), + + Rounds: discard.NewGauge(), + + Validators: discard.NewGauge(), + ValidatorsPower: discard.NewGauge(), + ValidatorPower: discard.NewGauge(), + ValidatorMissedBlocks: discard.NewGauge(), + MissingValidators: discard.NewGauge(), + MissingValidatorsPower: discard.NewGauge(), + ByzantineValidators: discard.NewGauge(), + ByzantineValidatorsPower: discard.NewGauge(), + + BlockIntervalSeconds: discard.NewHistogram(), + + NumTxs: discard.NewGauge(), + BlockSizeBytes: discard.NewGauge(), + TotalTxs: discard.NewGauge(), + CommittedHeight: discard.NewGauge(), + FastSyncing: discard.NewGauge(), + StateSyncing: discard.NewGauge(), + BlockParts: discard.NewCounter(), + } +} diff --git a/test/maverick/consensus/misbehavior.go b/test/maverick/consensus/misbehavior.go new file mode 100644 index 0000000000..75d2bd2788 --- /dev/null +++ b/test/maverick/consensus/misbehavior.go @@ -0,0 +1,398 @@ +package consensus + +import ( + "fmt" + + cstypes "github.com/tendermint/tendermint/consensus/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +// MisbehaviorList encompasses a list of all possible behaviors +var MisbehaviorList = map[string]Misbehavior{ + "double-prevote": DoublePrevoteMisbehavior(), +} + +type Misbehavior struct { + Name string + + EnterPropose func(cs *State, height int64, round int32) + + EnterPrevote func(cs *State, height int64, round int32) + + EnterPrecommit func(cs *State, height int64, round int32) + + ReceivePrevote func(cs *State, prevote *types.Vote) + + ReceivePrecommit func(cs *State, precommit *types.Vote) + + ReceiveProposal func(cs *State, proposal *types.Proposal) error +} + +// BEHAVIORS + +func DefaultMisbehavior() Misbehavior { + return Misbehavior{ + Name: "default", + EnterPropose: defaultEnterPropose, + EnterPrevote: defaultEnterPrevote, + EnterPrecommit: defaultEnterPrecommit, + ReceivePrevote: defaultReceivePrevote, + ReceivePrecommit: defaultReceivePrecommit, + ReceiveProposal: defaultReceiveProposal, + } +} + +// DoublePrevoteMisbehavior will make a node prevote both nil and a block in the same +// height and round. +func DoublePrevoteMisbehavior() Misbehavior { + b := DefaultMisbehavior() + b.Name = "double-prevote" + b.EnterPrevote = func(cs *State, height int64, round int32) { + + // If a block is locked, prevote that. + if cs.LockedBlock != nil { + cs.Logger.Info("enterPrevote: Already locked on a block, prevoting locked block") + cs.signAddVote(tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) + return + } + + // If ProposalBlock is nil, prevote nil. + if cs.ProposalBlock == nil { + cs.Logger.Info("enterPrevote: ProposalBlock is nil") + cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + return + } + + // Validate proposal block + err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock) + if err != nil { + // ProposalBlock is invalid, prevote nil. + cs.Logger.Error("enterPrevote: ProposalBlock is invalid", "err", err) + cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + return + } + + if cs.sw == nil { + cs.Logger.Error("nil switch") + return + } + + prevote, err := cs.signVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header()) + if err != nil { + cs.Logger.Error("enterPrevote: Unable to sign block", "err", err) + } + + nilPrevote, err := cs.signVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + if err != nil { + cs.Logger.Error("enterPrevote: Unable to sign block", "err", err) + } + + // add our own vote + cs.sendInternalMessage(msgInfo{&VoteMessage{prevote}, ""}) + + cs.Logger.Info("Sending conflicting votes") + peers := cs.sw.Peers().List() + // there has to be at least two other peers connected else this behavior works normally + for idx, peer := range peers { + if idx%2 == 0 { // sign the proposal block + peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote})) + } else { // sign a nil block + peer.Send(VoteChannel, MustEncode(&VoteMessage{nilPrevote})) + } + } + } + return b +} + +// DEFAULTS + +func defaultEnterPropose(cs *State, height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + // If we don't get the proposal and all block parts quick enough, enterPrevote + cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose) + + // Nothing more to do if we're not a validator + if cs.privValidator == nil { + logger.Debug("This node is not a validator") + return + } + logger.Debug("This node is a validator") + + pubKey, err := cs.privValidator.GetPubKey() + if err != nil { + // If this node is a validator & proposer in the currentx round, it will + // miss the opportunity to create a block. + logger.Error("Error on retrival of pubkey", "err", err) + return + } + address := pubKey.Address() + + // if not a validator, we're done + if !cs.Validators.HasAddress(address) { + logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators) + return + } + + if cs.isProposer(address) { + logger.Info("enterPropose: Our turn to propose", + "proposer", + address, + "privValidator", + cs.privValidator) + cs.decideProposal(height, round) + } else { + logger.Info("enterPropose: Not our turn to propose", + "proposer", + cs.Validators.GetProposer().Address, + "privValidator", + cs.privValidator) + } +} + +func defaultEnterPrevote(cs *State, height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + // If a block is locked, prevote that. + if cs.LockedBlock != nil { + logger.Info("enterPrevote: Already locked on a block, prevoting locked block") + cs.signAddVote(tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) + return + } + + // If ProposalBlock is nil, prevote nil. + if cs.ProposalBlock == nil { + logger.Info("enterPrevote: ProposalBlock is nil") + cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + return + } + + // Validate proposal block + err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock) + if err != nil { + // ProposalBlock is invalid, prevote nil. + logger.Error("enterPrevote: ProposalBlock is invalid", "err", err) + cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) + return + } + + // Prevote cs.ProposalBlock + // NOTE: the proposal signature is validated when it is received, + // and the proposal block parts are validated as they are received (against the merkle hash in the proposal) + logger.Info("enterPrevote: ProposalBlock is valid") + cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header()) +} + +func defaultEnterPrecommit(cs *State, height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + // check for a polka + blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() + + // If we don't have a polka, we must precommit nil. + if !ok { + if cs.LockedBlock != nil { + logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit while we're locked. Precommitting nil") + } else { + logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit. Precommitting nil.") + } + cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{}) + return + } + + // At this point +2/3 prevoted for a particular block or nil. + _ = cs.eventBus.PublishEventPolka(cs.RoundStateEvent()) + + // the latest POLRound should be this round. + polRound, _ := cs.Votes.POLInfo() + if polRound < round { + panic(fmt.Sprintf("This POLRound should be %v but got %v", round, polRound)) + } + + // +2/3 prevoted nil. Unlock and precommit nil. + if len(blockID.Hash) == 0 { + if cs.LockedBlock == nil { + logger.Info("enterPrecommit: +2/3 prevoted for nil.") + } else { + logger.Info("enterPrecommit: +2/3 prevoted for nil. Unlocking") + cs.LockedRound = -1 + cs.LockedBlock = nil + cs.LockedBlockParts = nil + _ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) + } + cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{}) + return + } + + // At this point, +2/3 prevoted for a particular block. + + // If we're already locked on that block, precommit it, and update the LockedRound + if cs.LockedBlock.HashesTo(blockID.Hash) { + logger.Info("enterPrecommit: +2/3 prevoted locked block. Relocking") + cs.LockedRound = round + _ = cs.eventBus.PublishEventRelock(cs.RoundStateEvent()) + cs.signAddVote(tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader) + return + } + + // If +2/3 prevoted for proposal block, stage and precommit it + if cs.ProposalBlock.HashesTo(blockID.Hash) { + logger.Info("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash) + // Validate the block. + if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil { + panic(fmt.Sprintf("enterPrecommit: +2/3 prevoted for an invalid block: %v", err)) + } + cs.LockedRound = round + cs.LockedBlock = cs.ProposalBlock + cs.LockedBlockParts = cs.ProposalBlockParts + _ = cs.eventBus.PublishEventLock(cs.RoundStateEvent()) + cs.signAddVote(tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader) + return + } + + // There was a polka in this round for a block we don't have. + // Fetch that block, unlock, and precommit nil. + // The +2/3 prevotes for this round is the POL for our unlock. + logger.Info("enterPrecommit: +2/3 prevotes for a block we don't have. Voting nil", "blockID", blockID) + cs.LockedRound = -1 + cs.LockedBlock = nil + cs.LockedBlockParts = nil + if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) { + cs.ProposalBlock = nil + cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) + } + _ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) + cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{}) +} + +func defaultReceivePrevote(cs *State, vote *types.Vote) { + height := cs.Height + prevotes := cs.Votes.Prevotes(vote.Round) + + // If +2/3 prevotes for a block or nil for *any* round: + if blockID, ok := prevotes.TwoThirdsMajority(); ok { + + // There was a polka! + // If we're locked but this is a recent polka, unlock. + // If it matches our ProposalBlock, update the ValidBlock + + // Unlock if `cs.LockedRound < vote.Round <= cs.Round` + // NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round + if (cs.LockedBlock != nil) && + (cs.LockedRound < vote.Round) && + (vote.Round <= cs.Round) && + !cs.LockedBlock.HashesTo(blockID.Hash) { + + cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round) + cs.LockedRound = -1 + cs.LockedBlock = nil + cs.LockedBlockParts = nil + _ = cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) + } + + // Update Valid* if we can. + // NOTE: our proposal block may be nil or not what received a polka.. + if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) { + + if cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.Logger.Info( + "Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round) + cs.ValidRound = vote.Round + cs.ValidBlock = cs.ProposalBlock + cs.ValidBlockParts = cs.ProposalBlockParts + } else { + cs.Logger.Info( + "Valid block we don't know about. Set ProposalBlock=nil", + "proposal", cs.ProposalBlock.Hash(), "blockID", blockID.Hash) + // We're getting the wrong block. + cs.ProposalBlock = nil + } + if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) { + cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) + } + cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) + _ = cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()) + } + } + + // If +2/3 prevotes for *anything* for future round: + switch { + case cs.Round < vote.Round && prevotes.HasTwoThirdsAny(): + // Round-skip if there is any 2/3+ of votes ahead of us + cs.enterNewRound(height, vote.Round) + case cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step: // current round + blockID, ok := prevotes.TwoThirdsMajority() + if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) { + cs.enterPrecommit(height, vote.Round) + } else if prevotes.HasTwoThirdsAny() { + cs.enterPrevoteWait(height, vote.Round) + } + case cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round: + // If the proposal is now complete, enter prevote of cs.Round. + if cs.isProposalComplete() { + cs.enterPrevote(height, cs.Round) + } + } + +} + +func defaultReceivePrecommit(cs *State, vote *types.Vote) { + height := cs.Height + precommits := cs.Votes.Precommits(vote.Round) + cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort()) + + blockID, ok := precommits.TwoThirdsMajority() + if ok { + // Executed as TwoThirdsMajority could be from a higher round + cs.enterNewRound(height, vote.Round) + cs.enterPrecommit(height, vote.Round) + if len(blockID.Hash) != 0 { + cs.enterCommit(height, vote.Round) + if cs.config.SkipTimeoutCommit && precommits.HasAll() { + cs.enterNewRound(cs.Height, 0) + } + } else { + cs.enterPrecommitWait(height, vote.Round) + } + } else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { + cs.enterNewRound(height, vote.Round) + cs.enterPrecommitWait(height, vote.Round) + } +} + +func defaultReceiveProposal(cs *State, proposal *types.Proposal) error { + // Already have one + // TODO: possibly catch double proposals + if cs.Proposal != nil { + return nil + } + + // Does not apply + if proposal.Height != cs.Height || proposal.Round != cs.Round { + return nil + } + + // Verify POLRound, which must be -1 or in range [0, proposal.Round). + if proposal.POLRound < -1 || + (proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) { + return ErrInvalidProposalPOLRound + } + + p := proposal.ToProto() + // Verify signature + if !cs.Validators.GetProposer().PubKey.VerifySignature( + types.ProposalSignBytes(cs.state.ChainID, p), proposal.Signature) { + return ErrInvalidProposalSignature + } + + proposal.Signature = p.Signature + cs.Proposal = proposal + // We don't update cs.ProposalBlockParts if it is already set. + // This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round. + // TODO: We can check if Proposal is for a different block as this is a sign of misbehavior! + if cs.ProposalBlockParts == nil { + cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartSetHeader) + } + cs.Logger.Info("Received proposal", "proposal", proposal) + return nil +} diff --git a/test/maverick/consensus/msgs.go b/test/maverick/consensus/msgs.go new file mode 100644 index 0000000000..4de96b5f40 --- /dev/null +++ b/test/maverick/consensus/msgs.go @@ -0,0 +1,377 @@ +package consensus + +import ( + "errors" + "fmt" + + "github.com/gogo/protobuf/proto" + + cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/libs/bits" + tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/p2p" + tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +// MsgToProto takes a consensus message type and returns the proto defined consensus message +func MsgToProto(msg Message) (*tmcons.Message, error) { + if msg == nil { + return nil, errors.New("consensus: message is nil") + } + var pb tmcons.Message + + switch msg := msg.(type) { + case *NewRoundStepMessage: + pb = tmcons.Message{ + Sum: &tmcons.Message_NewRoundStep{ + NewRoundStep: &tmcons.NewRoundStep{ + Height: msg.Height, + Round: msg.Round, + Step: uint32(msg.Step), + SecondsSinceStartTime: msg.SecondsSinceStartTime, + LastCommitRound: msg.LastCommitRound, + }, + }, + } + case *NewValidBlockMessage: + pbPartSetHeader := msg.BlockPartSetHeader.ToProto() + pbBits := msg.BlockParts.ToProto() + pb = tmcons.Message{ + Sum: &tmcons.Message_NewValidBlock{ + NewValidBlock: &tmcons.NewValidBlock{ + Height: msg.Height, + Round: msg.Round, + BlockPartSetHeader: pbPartSetHeader, + BlockParts: pbBits, + IsCommit: msg.IsCommit, + }, + }, + } + case *ProposalMessage: + pbP := msg.Proposal.ToProto() + pb = tmcons.Message{ + Sum: &tmcons.Message_Proposal{ + Proposal: &tmcons.Proposal{ + Proposal: *pbP, + }, + }, + } + case *ProposalPOLMessage: + pbBits := msg.ProposalPOL.ToProto() + pb = tmcons.Message{ + Sum: &tmcons.Message_ProposalPol{ + ProposalPol: &tmcons.ProposalPOL{ + Height: msg.Height, + ProposalPolRound: msg.ProposalPOLRound, + ProposalPol: *pbBits, + }, + }, + } + case *BlockPartMessage: + parts, err := msg.Part.ToProto() + if err != nil { + return nil, fmt.Errorf("msg to proto error: %w", err) + } + pb = tmcons.Message{ + Sum: &tmcons.Message_BlockPart{ + BlockPart: &tmcons.BlockPart{ + Height: msg.Height, + Round: msg.Round, + Part: *parts, + }, + }, + } + case *VoteMessage: + vote := msg.Vote.ToProto() + pb = tmcons.Message{ + Sum: &tmcons.Message_Vote{ + Vote: &tmcons.Vote{ + Vote: vote, + }, + }, + } + case *HasVoteMessage: + pb = tmcons.Message{ + Sum: &tmcons.Message_HasVote{ + HasVote: &tmcons.HasVote{ + Height: msg.Height, + Round: msg.Round, + Type: msg.Type, + Index: msg.Index, + }, + }, + } + case *VoteSetMaj23Message: + bi := msg.BlockID.ToProto() + pb = tmcons.Message{ + Sum: &tmcons.Message_VoteSetMaj23{ + VoteSetMaj23: &tmcons.VoteSetMaj23{ + Height: msg.Height, + Round: msg.Round, + Type: msg.Type, + BlockID: bi, + }, + }, + } + case *VoteSetBitsMessage: + bi := msg.BlockID.ToProto() + bits := msg.Votes.ToProto() + + vsb := &tmcons.Message_VoteSetBits{ + VoteSetBits: &tmcons.VoteSetBits{ + Height: msg.Height, + Round: msg.Round, + Type: msg.Type, + BlockID: bi, + }, + } + + if bits != nil { + vsb.VoteSetBits.Votes = *bits + } + + pb = tmcons.Message{ + Sum: vsb, + } + + default: + return nil, fmt.Errorf("consensus: message not recognized: %T", msg) + } + + return &pb, nil +} + +// MsgFromProto takes a consensus proto message and returns the native go type +func MsgFromProto(msg *tmcons.Message) (Message, error) { + if msg == nil { + return nil, errors.New("consensus: nil message") + } + var pb Message + + switch msg := msg.Sum.(type) { + case *tmcons.Message_NewRoundStep: + rs, err := tmmath.SafeConvertUint8(int64(msg.NewRoundStep.Step)) + // deny message based on possible overflow + if err != nil { + return nil, fmt.Errorf("denying message due to possible overflow: %w", err) + } + pb = &NewRoundStepMessage{ + Height: msg.NewRoundStep.Height, + Round: msg.NewRoundStep.Round, + Step: cstypes.RoundStepType(rs), + SecondsSinceStartTime: msg.NewRoundStep.SecondsSinceStartTime, + LastCommitRound: msg.NewRoundStep.LastCommitRound, + } + case *tmcons.Message_NewValidBlock: + pbPartSetHeader, err := types.PartSetHeaderFromProto(&msg.NewValidBlock.BlockPartSetHeader) + if err != nil { + return nil, fmt.Errorf("parts to proto error: %w", err) + } + + pbBits := new(bits.BitArray) + pbBits.FromProto(msg.NewValidBlock.BlockParts) + + pb = &NewValidBlockMessage{ + Height: msg.NewValidBlock.Height, + Round: msg.NewValidBlock.Round, + BlockPartSetHeader: *pbPartSetHeader, + BlockParts: pbBits, + IsCommit: msg.NewValidBlock.IsCommit, + } + case *tmcons.Message_Proposal: + pbP, err := types.ProposalFromProto(&msg.Proposal.Proposal) + if err != nil { + return nil, fmt.Errorf("proposal msg to proto error: %w", err) + } + + pb = &ProposalMessage{ + Proposal: pbP, + } + case *tmcons.Message_ProposalPol: + pbBits := new(bits.BitArray) + pbBits.FromProto(&msg.ProposalPol.ProposalPol) + pb = &ProposalPOLMessage{ + Height: msg.ProposalPol.Height, + ProposalPOLRound: msg.ProposalPol.ProposalPolRound, + ProposalPOL: pbBits, + } + case *tmcons.Message_BlockPart: + parts, err := types.PartFromProto(&msg.BlockPart.Part) + if err != nil { + return nil, fmt.Errorf("blockpart msg to proto error: %w", err) + } + pb = &BlockPartMessage{ + Height: msg.BlockPart.Height, + Round: msg.BlockPart.Round, + Part: parts, + } + case *tmcons.Message_Vote: + vote, err := types.VoteFromProto(msg.Vote.Vote) + if err != nil { + return nil, fmt.Errorf("vote msg to proto error: %w", err) + } + + pb = &VoteMessage{ + Vote: vote, + } + case *tmcons.Message_HasVote: + pb = &HasVoteMessage{ + Height: msg.HasVote.Height, + Round: msg.HasVote.Round, + Type: msg.HasVote.Type, + Index: msg.HasVote.Index, + } + case *tmcons.Message_VoteSetMaj23: + bi, err := types.BlockIDFromProto(&msg.VoteSetMaj23.BlockID) + if err != nil { + return nil, fmt.Errorf("voteSetMaj23 msg to proto error: %w", err) + } + pb = &VoteSetMaj23Message{ + Height: msg.VoteSetMaj23.Height, + Round: msg.VoteSetMaj23.Round, + Type: msg.VoteSetMaj23.Type, + BlockID: *bi, + } + case *tmcons.Message_VoteSetBits: + bi, err := types.BlockIDFromProto(&msg.VoteSetBits.BlockID) + if err != nil { + return nil, fmt.Errorf("voteSetBits msg to proto error: %w", err) + } + bits := new(bits.BitArray) + bits.FromProto(&msg.VoteSetBits.Votes) + + pb = &VoteSetBitsMessage{ + Height: msg.VoteSetBits.Height, + Round: msg.VoteSetBits.Round, + Type: msg.VoteSetBits.Type, + BlockID: *bi, + Votes: bits, + } + default: + return nil, fmt.Errorf("consensus: message not recognized: %T", msg) + } + + if err := pb.ValidateBasic(); err != nil { + return nil, err + } + + return pb, nil +} + +// MustEncode takes the reactors msg, makes it proto and marshals it +// this mimics `MustMarshalBinaryBare` in that is panics on error +func MustEncode(msg Message) []byte { + pb, err := MsgToProto(msg) + if err != nil { + panic(err) + } + enc, err := proto.Marshal(pb) + if err != nil { + panic(err) + } + return enc +} + +// WALToProto takes a WAL message and return a proto walMessage and error +func WALToProto(msg WALMessage) (*tmcons.WALMessage, error) { + var pb tmcons.WALMessage + + switch msg := msg.(type) { + case types.EventDataRoundState: + pb = tmcons.WALMessage{ + Sum: &tmcons.WALMessage_EventDataRoundState{ + EventDataRoundState: &tmproto.EventDataRoundState{ + Height: msg.Height, + Round: msg.Round, + Step: msg.Step, + }, + }, + } + case msgInfo: + consMsg, err := MsgToProto(msg.Msg) + if err != nil { + return nil, err + } + pb = tmcons.WALMessage{ + Sum: &tmcons.WALMessage_MsgInfo{ + MsgInfo: &tmcons.MsgInfo{ + Msg: *consMsg, + PeerID: string(msg.PeerID), + }, + }, + } + case timeoutInfo: + pb = tmcons.WALMessage{ + Sum: &tmcons.WALMessage_TimeoutInfo{ + TimeoutInfo: &tmcons.TimeoutInfo{ + Duration: msg.Duration, + Height: msg.Height, + Round: msg.Round, + Step: uint32(msg.Step), + }, + }, + } + case EndHeightMessage: + pb = tmcons.WALMessage{ + Sum: &tmcons.WALMessage_EndHeight{ + EndHeight: &tmcons.EndHeight{ + Height: msg.Height, + }, + }, + } + default: + return nil, fmt.Errorf("to proto: wal message not recognized: %T", msg) + } + + return &pb, nil +} + +// WALFromProto takes a proto wal message and return a consensus walMessage and error +func WALFromProto(msg *tmcons.WALMessage) (WALMessage, error) { + if msg == nil { + return nil, errors.New("nil WAL message") + } + var pb WALMessage + + switch msg := msg.Sum.(type) { + case *tmcons.WALMessage_EventDataRoundState: + pb = types.EventDataRoundState{ + Height: msg.EventDataRoundState.Height, + Round: msg.EventDataRoundState.Round, + Step: msg.EventDataRoundState.Step, + } + case *tmcons.WALMessage_MsgInfo: + walMsg, err := MsgFromProto(&msg.MsgInfo.Msg) + if err != nil { + return nil, fmt.Errorf("msgInfo from proto error: %w", err) + } + pb = msgInfo{ + Msg: walMsg, + PeerID: p2p.ID(msg.MsgInfo.PeerID), + } + + case *tmcons.WALMessage_TimeoutInfo: + tis, err := tmmath.SafeConvertUint8(int64(msg.TimeoutInfo.Step)) + // deny message based on possible overflow + if err != nil { + return nil, fmt.Errorf("denying message due to possible overflow: %w", err) + } + pb = timeoutInfo{ + Duration: msg.TimeoutInfo.Duration, + Height: msg.TimeoutInfo.Height, + Round: msg.TimeoutInfo.Round, + Step: cstypes.RoundStepType(tis), + } + return pb, nil + case *tmcons.WALMessage_EndHeight: + pb := EndHeightMessage{ + Height: msg.EndHeight.Height, + } + return pb, nil + default: + return nil, fmt.Errorf("from proto: wal message not recognized: %T", msg) + } + return pb, nil +} diff --git a/test/maverick/consensus/reactor.go b/test/maverick/consensus/reactor.go new file mode 100644 index 0000000000..c82656115e --- /dev/null +++ b/test/maverick/consensus/reactor.go @@ -0,0 +1,1720 @@ +package consensus + +import ( + "errors" + "fmt" + "reflect" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + + cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/libs/bits" + tmevents "github.com/tendermint/tendermint/libs/events" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmsync "github.com/tendermint/tendermint/libs/sync" + "github.com/tendermint/tendermint/p2p" + tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +const ( + StateChannel = byte(0x20) + DataChannel = byte(0x21) + VoteChannel = byte(0x22) + VoteSetBitsChannel = byte(0x23) + + maxMsgSize = 1048576 // 1MB; NOTE/TODO: keep in sync with types.PartSet sizes. + + blocksToContributeToBecomeGoodPeer = 10000 + votesToContributeToBecomeGoodPeer = 10000 +) + +//----------------------------------------------------------------------------- + +// Reactor defines a reactor for the consensus service. +type Reactor struct { + p2p.BaseReactor // BaseService + p2p.Switch + + conS *State + + mtx tmsync.RWMutex + waitSync bool + eventBus *types.EventBus + + Metrics *Metrics +} + +type ReactorOption func(*Reactor) + +// NewReactor returns a new Reactor with the given +// consensusState. +func NewReactor(consensusState *State, waitSync bool, options ...ReactorOption) *Reactor { + conR := &Reactor{ + conS: consensusState, + waitSync: waitSync, + Metrics: NopMetrics(), + } + conR.BaseReactor = *p2p.NewBaseReactor("Consensus", conR) + + for _, option := range options { + option(conR) + } + + return conR +} + +// OnStart implements BaseService by subscribing to events, which later will be +// broadcasted to other peers and starting state if we're not in fast sync. +func (conR *Reactor) OnStart() error { + conR.Logger.Info("Reactor ", "waitSync", conR.WaitSync()) + + // start routine that computes peer statistics for evaluating peer quality + go conR.peerStatsRoutine() + + conR.subscribeToBroadcastEvents() + + if !conR.WaitSync() { + conR.conS.SetSwitch(conR.Switch) + err := conR.conS.Start() + if err != nil { + return err + } + } + + return nil +} + +// OnStop implements BaseService by unsubscribing from events and stopping +// state. +func (conR *Reactor) OnStop() { + conR.unsubscribeFromBroadcastEvents() + if err := conR.conS.Stop(); err != nil { + conR.Logger.Error("Error stopping consensus state", "err", err) + } + if !conR.WaitSync() { + conR.conS.Wait() + } +} + +// SwitchToConsensus switches from fast_sync mode to consensus mode. +// It resets the state, turns off fast_sync, and starts the consensus state-machine +func (conR *Reactor) SwitchToConsensus(state sm.State, skipWAL bool) { + conR.Logger.Info("SwitchToConsensus") + + // We have no votes, so reconstruct LastCommit from SeenCommit. + if state.LastBlockHeight > 0 { + conR.conS.reconstructLastCommit(state) + } + + // NOTE: The line below causes broadcastNewRoundStepRoutine() to broadcast a + // NewRoundStepMessage. + conR.conS.updateToState(state) + + conR.mtx.Lock() + conR.waitSync = false + conR.mtx.Unlock() + conR.Metrics.FastSyncing.Set(0) + conR.Metrics.StateSyncing.Set(0) + + if skipWAL { + conR.conS.doWALCatchup = false + } + conR.conS.SetSwitch(conR.Switch) + err := conR.conS.Start() + if err != nil { + panic(fmt.Sprintf(`Failed to start consensus state: %v + +conS: +%+v + +conR: +%+v`, err, conR.conS, conR)) + } +} + +// GetChannels implements Reactor +func (conR *Reactor) GetChannels() []*p2p.ChannelDescriptor { + // TODO optimize + return []*p2p.ChannelDescriptor{ + { + ID: StateChannel, + Priority: 5, + SendQueueCapacity: 100, + RecvMessageCapacity: maxMsgSize, + }, + { + ID: DataChannel, // maybe split between gossiping current block and catchup stuff + // once we gossip the whole block there's nothing left to send until next height or round + Priority: 10, + SendQueueCapacity: 100, + RecvBufferCapacity: 50 * 4096, + RecvMessageCapacity: maxMsgSize, + }, + { + ID: VoteChannel, + Priority: 5, + SendQueueCapacity: 100, + RecvBufferCapacity: 100 * 100, + RecvMessageCapacity: maxMsgSize, + }, + { + ID: VoteSetBitsChannel, + Priority: 1, + SendQueueCapacity: 2, + RecvBufferCapacity: 1024, + RecvMessageCapacity: maxMsgSize, + }, + } +} + +// InitPeer implements Reactor by creating a state for the peer. +func (conR *Reactor) InitPeer(peer p2p.Peer) p2p.Peer { + peerState := NewPeerState(peer).SetLogger(conR.Logger) + peer.Set(types.PeerStateKey, peerState) + return peer +} + +// AddPeer implements Reactor by spawning multiple gossiping goroutines for the +// peer. +func (conR *Reactor) AddPeer(peer p2p.Peer) { + if !conR.IsRunning() { + return + } + + peerState, ok := peer.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("peer %v has no state", peer)) + } + // Begin routines for this peer. + go conR.gossipDataRoutine(peer, peerState) + go conR.gossipVotesRoutine(peer, peerState) + go conR.queryMaj23Routine(peer, peerState) + + // Send our state to peer. + // If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). + if !conR.WaitSync() { + conR.sendNewRoundStepMessage(peer) + } +} + +// RemovePeer is a noop. +func (conR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { + if !conR.IsRunning() { + return + } + // TODO + // ps, ok := peer.Get(PeerStateKey).(*PeerState) + // if !ok { + // panic(fmt.Sprintf("Peer %v has no state", peer)) + // } + // ps.Disconnect() +} + +// Receive implements Reactor +// NOTE: We process these messages even when we're fast_syncing. +// Messages affect either a peer state or the consensus state. +// Peer state updates can happen in parallel, but processing of +// proposals, block parts, and votes are ordered by the receiveRoutine +// NOTE: blocks on consensus state for proposals, block parts, and votes +func (conR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { + if !conR.IsRunning() { + conR.Logger.Debug("Receive", "src", src, "chId", chID, "bytes", msgBytes) + return + } + + msg, err := decodeMsg(msgBytes) + if err != nil { + conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + conR.Switch.StopPeerForError(src, err) + return + } + + if err = msg.ValidateBasic(); err != nil { + conR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) + conR.Switch.StopPeerForError(src, err) + return + } + + conR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) + + // Get peer states + ps, ok := src.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", src)) + } + + switch chID { + case StateChannel: + switch msg := msg.(type) { + case *NewRoundStepMessage: + conR.conS.mtx.Lock() + initialHeight := conR.conS.state.InitialHeight + conR.conS.mtx.Unlock() + if err = msg.ValidateHeight(initialHeight); err != nil { + conR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) + conR.Switch.StopPeerForError(src, err) + return + } + ps.ApplyNewRoundStepMessage(msg) + case *NewValidBlockMessage: + ps.ApplyNewValidBlockMessage(msg) + case *HasVoteMessage: + ps.ApplyHasVoteMessage(msg) + case *VoteSetMaj23Message: + cs := conR.conS + cs.mtx.Lock() + height, votes := cs.Height, cs.Votes + cs.mtx.Unlock() + if height != msg.Height { + return + } + // Peer claims to have a maj23 for some BlockID at H,R,S, + err := votes.SetPeerMaj23(msg.Round, msg.Type, ps.peer.ID(), msg.BlockID) + if err != nil { + conR.Switch.StopPeerForError(src, err) + return + } + // Respond with a VoteSetBitsMessage showing which votes we have. + // (and consequently shows which we don't have) + var ourVotes *bits.BitArray + switch msg.Type { + case tmproto.PrevoteType: + ourVotes = votes.Prevotes(msg.Round).BitArrayByBlockID(msg.BlockID) + case tmproto.PrecommitType: + ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) + default: + panic("Bad VoteSetBitsMessage field Type. Forgot to add a check in ValidateBasic?") + } + src.TrySend(VoteSetBitsChannel, MustEncode(&VoteSetBitsMessage{ + Height: msg.Height, + Round: msg.Round, + Type: msg.Type, + BlockID: msg.BlockID, + Votes: ourVotes, + })) + default: + conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) + } + + case DataChannel: + if conR.WaitSync() { + conR.Logger.Info("Ignoring message received during sync", "msg", msg) + return + } + switch msg := msg.(type) { + case *ProposalMessage: + ps.SetHasProposal(msg.Proposal) + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} + case *ProposalPOLMessage: + ps.ApplyProposalPOLMessage(msg) + case *BlockPartMessage: + ps.SetHasProposalBlockPart(msg.Height, msg.Round, int(msg.Part.Index)) + conR.Metrics.BlockParts.With("peer_id", string(src.ID())).Add(1) + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} + default: + conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) + } + + case VoteChannel: + if conR.WaitSync() { + conR.Logger.Info("Ignoring message received during sync", "msg", msg) + return + } + switch msg := msg.(type) { + case *VoteMessage: + cs := conR.conS + cs.mtx.RLock() + height, valSize, lastCommitSize := cs.Height, cs.Validators.Size(), cs.LastCommit.Size() + cs.mtx.RUnlock() + ps.EnsureVoteBitArrays(height, valSize) + ps.EnsureVoteBitArrays(height-1, lastCommitSize) + ps.SetHasVote(msg.Vote) + + cs.peerMsgQueue <- msgInfo{msg, src.ID()} + + default: + // don't punish (leave room for soft upgrades) + conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) + } + + case VoteSetBitsChannel: + if conR.WaitSync() { + conR.Logger.Info("Ignoring message received during sync", "msg", msg) + return + } + switch msg := msg.(type) { + case *VoteSetBitsMessage: + cs := conR.conS + cs.mtx.Lock() + height, votes := cs.Height, cs.Votes + cs.mtx.Unlock() + + if height == msg.Height { + var ourVotes *bits.BitArray + switch msg.Type { + case tmproto.PrevoteType: + ourVotes = votes.Prevotes(msg.Round).BitArrayByBlockID(msg.BlockID) + case tmproto.PrecommitType: + ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) + default: + panic("Bad VoteSetBitsMessage field Type. Forgot to add a check in ValidateBasic?") + } + ps.ApplyVoteSetBitsMessage(msg, ourVotes) + } else { + ps.ApplyVoteSetBitsMessage(msg, nil) + } + default: + // don't punish (leave room for soft upgrades) + conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) + } + + default: + conR.Logger.Error(fmt.Sprintf("Unknown chId %X", chID)) + } +} + +// SetEventBus sets event bus. +func (conR *Reactor) SetEventBus(b *types.EventBus) { + conR.eventBus = b + conR.conS.SetEventBus(b) +} + +// WaitSync returns whether the consensus reactor is waiting for state/fast sync. +func (conR *Reactor) WaitSync() bool { + conR.mtx.RLock() + defer conR.mtx.RUnlock() + return conR.waitSync +} + +//-------------------------------------- + +// subscribeToBroadcastEvents subscribes for new round steps and votes +// using internal pubsub defined on state to broadcast +// them to peers upon receiving. +func (conR *Reactor) subscribeToBroadcastEvents() { + const subscriber = "consensus-reactor" + if err := conR.conS.evsw.AddListenerForEvent(subscriber, types.EventNewRoundStep, + func(data tmevents.EventData) { + conR.broadcastNewRoundStepMessage(data.(*cstypes.RoundState)) + }); err != nil { + conR.Logger.Error("Error adding listener for events", "err", err) + } + + if err := conR.conS.evsw.AddListenerForEvent(subscriber, types.EventValidBlock, + func(data tmevents.EventData) { + conR.broadcastNewValidBlockMessage(data.(*cstypes.RoundState)) + }); err != nil { + conR.Logger.Error("Error adding listener for events", "err", err) + } + + if err := conR.conS.evsw.AddListenerForEvent(subscriber, types.EventVote, + func(data tmevents.EventData) { + conR.broadcastHasVoteMessage(data.(*types.Vote)) + }); err != nil { + conR.Logger.Error("Error adding listener for events", "err", err) + } + +} + +func (conR *Reactor) unsubscribeFromBroadcastEvents() { + const subscriber = "consensus-reactor" + conR.conS.evsw.RemoveListener(subscriber) +} + +func (conR *Reactor) broadcastNewRoundStepMessage(rs *cstypes.RoundState) { + nrsMsg := makeRoundStepMessage(rs) + conR.Switch.Broadcast(StateChannel, MustEncode(nrsMsg)) +} + +func (conR *Reactor) broadcastNewValidBlockMessage(rs *cstypes.RoundState) { + csMsg := &NewValidBlockMessage{ + Height: rs.Height, + Round: rs.Round, + BlockPartSetHeader: rs.ProposalBlockParts.Header(), + BlockParts: rs.ProposalBlockParts.BitArray(), + IsCommit: rs.Step == cstypes.RoundStepCommit, + } + conR.Switch.Broadcast(StateChannel, MustEncode(csMsg)) +} + +// Broadcasts HasVoteMessage to peers that care. +func (conR *Reactor) broadcastHasVoteMessage(vote *types.Vote) { + msg := &HasVoteMessage{ + Height: vote.Height, + Round: vote.Round, + Type: vote.Type, + Index: vote.ValidatorIndex, + } + conR.Switch.Broadcast(StateChannel, MustEncode(msg)) + /* + // TODO: Make this broadcast more selective. + for _, peer := range conR.Switch.Peers().List() { + ps, ok := peer.Get(PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } + prs := ps.GetRoundState() + if prs.Height == vote.Height { + // TODO: Also filter on round? + peer.TrySend(StateChannel, struct{ ConsensusMessage }{msg}) + } else { + // Height doesn't match + // TODO: check a field, maybe CatchupCommitRound? + // TODO: But that requires changing the struct field comment. + } + } + */ +} + +func makeRoundStepMessage(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage) { + nrsMsg = &NewRoundStepMessage{ + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, + SecondsSinceStartTime: int64(time.Since(rs.StartTime).Seconds()), + LastCommitRound: rs.LastCommit.GetRound(), + } + return +} + +func (conR *Reactor) sendNewRoundStepMessage(peer p2p.Peer) { + rs := conR.conS.GetRoundState() + nrsMsg := makeRoundStepMessage(rs) + peer.Send(StateChannel, MustEncode(nrsMsg)) +} + +func (conR *Reactor) gossipDataRoutine(peer p2p.Peer, ps *PeerState) { + logger := conR.Logger.With("peer", peer) + +OUTER_LOOP: + for { + // Manage disconnects from self or peer. + if !peer.IsRunning() || !conR.IsRunning() { + logger.Info("Stopping gossipDataRoutine for peer") + return + } + rs := conR.conS.GetRoundState() + prs := ps.GetRoundState() + + // Send proposal Block parts? + if rs.ProposalBlockParts.HasHeader(prs.ProposalBlockPartSetHeader) { + if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockParts.Copy()).PickRandom(); ok { + part := rs.ProposalBlockParts.GetPart(index) + msg := &BlockPartMessage{ + Height: rs.Height, // This tells peer that this part applies to us. + Round: rs.Round, // This tells peer that this part applies to us. + Part: part, + } + logger.Debug("Sending block part", "height", prs.Height, "round", prs.Round) + if peer.Send(DataChannel, MustEncode(msg)) { + ps.SetHasProposalBlockPart(prs.Height, prs.Round, index) + } + continue OUTER_LOOP + } + } + + // If the peer is on a previous height that we have, help catch up. + if (0 < prs.Height) && (prs.Height < rs.Height) && (prs.Height >= conR.conS.blockStore.Base()) { + heightLogger := logger.With("height", prs.Height) + + // if we never received the commit message from the peer, the block parts wont be initialized + if prs.ProposalBlockParts == nil { + blockMeta := conR.conS.blockStore.LoadBlockMeta(prs.Height) + if blockMeta == nil { + heightLogger.Error("Failed to load block meta", + "blockstoreBase", conR.conS.blockStore.Base(), "blockstoreHeight", conR.conS.blockStore.Height()) + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + } else { + ps.InitProposalBlockParts(blockMeta.BlockID.PartSetHeader) + } + // continue the loop since prs is a copy and not effected by this initialization + continue OUTER_LOOP + } + conR.gossipDataForCatchup(heightLogger, rs, prs, ps, peer) + continue OUTER_LOOP + } + + // If height and round don't match, sleep. + if (rs.Height != prs.Height) || (rs.Round != prs.Round) { + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + continue OUTER_LOOP + } + + // By here, height and round match. + // Proposal block parts were already matched and sent if any were wanted. + // (These can match on hash so the round doesn't matter) + // Now consider sending other things, like the Proposal itself. + + // Send Proposal && ProposalPOL BitArray? + if rs.Proposal != nil && !prs.Proposal { + // Proposal: share the proposal metadata with peer. + { + msg := &ProposalMessage{Proposal: rs.Proposal} + logger.Debug("Sending proposal", "height", prs.Height, "round", prs.Round) + if peer.Send(DataChannel, MustEncode(msg)) { + // NOTE[ZM]: A peer might have received different proposal msg so this Proposal msg will be rejected! + ps.SetHasProposal(rs.Proposal) + } + } + // ProposalPOL: lets peer know which POL votes we have so far. + // Peer must receive ProposalMessage first. + // rs.Proposal was validated, so rs.Proposal.POLRound <= rs.Round, + // so we definitely have rs.Votes.Prevotes(rs.Proposal.POLRound). + if 0 <= rs.Proposal.POLRound { + msg := &ProposalPOLMessage{ + Height: rs.Height, + ProposalPOLRound: rs.Proposal.POLRound, + ProposalPOL: rs.Votes.Prevotes(rs.Proposal.POLRound).BitArray(), + } + logger.Debug("Sending POL", "height", prs.Height, "round", prs.Round) + peer.Send(DataChannel, MustEncode(msg)) + } + continue OUTER_LOOP + } + + // Nothing to do. Sleep. + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + continue OUTER_LOOP + } +} + +func (conR *Reactor) gossipDataForCatchup(logger log.Logger, rs *cstypes.RoundState, + prs *cstypes.PeerRoundState, ps *PeerState, peer p2p.Peer) { + + if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { + // Ensure that the peer's PartSetHeader is correct + blockMeta := conR.conS.blockStore.LoadBlockMeta(prs.Height) + if blockMeta == nil { + logger.Error("Failed to load block meta", "ourHeight", rs.Height, + "blockstoreBase", conR.conS.blockStore.Base(), "blockstoreHeight", conR.conS.blockStore.Height()) + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + return + } else if !blockMeta.BlockID.PartSetHeader.Equals(prs.ProposalBlockPartSetHeader) { + logger.Info("Peer ProposalBlockPartSetHeader mismatch, sleeping", + "blockPartSetHeader", blockMeta.BlockID.PartSetHeader, "peerBlockPartSetHeader", prs.ProposalBlockPartSetHeader) + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + return + } + // Load the part + part := conR.conS.blockStore.LoadBlockPart(prs.Height, index) + if part == nil { + logger.Error("Could not load part", "index", index, + "blockPartSetHeader", blockMeta.BlockID.PartSetHeader, "peerBlockPartSetHeader", prs.ProposalBlockPartSetHeader) + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + return + } + // Send the part + msg := &BlockPartMessage{ + Height: prs.Height, // Not our height, so it doesn't matter. + Round: prs.Round, // Not our height, so it doesn't matter. + Part: part, + } + logger.Debug("Sending block part for catchup", "round", prs.Round, "index", index) + if peer.Send(DataChannel, MustEncode(msg)) { + ps.SetHasProposalBlockPart(prs.Height, prs.Round, index) + } else { + logger.Debug("Sending block part for catchup failed") + } + return + } + time.Sleep(conR.conS.config.PeerGossipSleepDuration) +} + +func (conR *Reactor) gossipVotesRoutine(peer p2p.Peer, ps *PeerState) { + logger := conR.Logger.With("peer", peer) + + // Simple hack to throttle logs upon sleep. + var sleeping = 0 + +OUTER_LOOP: + for { + // Manage disconnects from self or peer. + if !peer.IsRunning() || !conR.IsRunning() { + logger.Info("Stopping gossipVotesRoutine for peer") + return + } + rs := conR.conS.GetRoundState() + prs := ps.GetRoundState() + + switch sleeping { + case 1: // First sleep + sleeping = 2 + case 2: // No more sleep + sleeping = 0 + } + + // If height matches, then send LastCommit, Prevotes, Precommits. + if rs.Height == prs.Height { + heightLogger := logger.With("height", prs.Height) + if conR.gossipVotesForHeight(heightLogger, rs, prs, ps) { + continue OUTER_LOOP + } + } + + // Special catchup logic. + // If peer is lagging by height 1, send LastCommit. + if prs.Height != 0 && rs.Height == prs.Height+1 { + if ps.PickSendVote(rs.LastCommit) { + logger.Debug("Picked rs.LastCommit to send", "height", prs.Height) + continue OUTER_LOOP + } + } + + // Catchup logic + // If peer is lagging by more than 1, send Commit. + if prs.Height != 0 && rs.Height >= prs.Height+2 && prs.Height >= conR.conS.blockStore.Base() { + // Load the block commit for prs.Height, + // which contains precommit signatures for prs.Height. + if commit := conR.conS.blockStore.LoadBlockCommit(prs.Height); commit != nil { + if ps.PickSendVote(commit) { + logger.Debug("Picked Catchup commit to send", "height", prs.Height) + continue OUTER_LOOP + } + } + } + + if sleeping == 0 { + // We sent nothing. Sleep... + sleeping = 1 + logger.Debug("No votes to send, sleeping", "rs.Height", rs.Height, "prs.Height", prs.Height, + "localPV", rs.Votes.Prevotes(rs.Round).BitArray(), "peerPV", prs.Prevotes, + "localPC", rs.Votes.Precommits(rs.Round).BitArray(), "peerPC", prs.Precommits) + } else if sleeping == 2 { + // Continued sleep... + sleeping = 1 + } + + time.Sleep(conR.conS.config.PeerGossipSleepDuration) + continue OUTER_LOOP + } +} + +func (conR *Reactor) gossipVotesForHeight( + logger log.Logger, + rs *cstypes.RoundState, + prs *cstypes.PeerRoundState, + ps *PeerState, +) bool { + + // If there are lastCommits to send... + if prs.Step == cstypes.RoundStepNewHeight { + if ps.PickSendVote(rs.LastCommit) { + logger.Debug("Picked rs.LastCommit to send") + return true + } + } + // If there are POL prevotes to send... + if prs.Step <= cstypes.RoundStepPropose && prs.Round != -1 && prs.Round <= rs.Round && prs.ProposalPOLRound != -1 { + if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { + if ps.PickSendVote(polPrevotes) { + logger.Debug("Picked rs.Prevotes(prs.ProposalPOLRound) to send", + "round", prs.ProposalPOLRound) + return true + } + } + } + // If there are prevotes to send... + if prs.Step <= cstypes.RoundStepPrevoteWait && prs.Round != -1 && prs.Round <= rs.Round { + if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { + logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round) + return true + } + } + // If there are precommits to send... + if prs.Step <= cstypes.RoundStepPrecommitWait && prs.Round != -1 && prs.Round <= rs.Round { + if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) { + logger.Debug("Picked rs.Precommits(prs.Round) to send", "round", prs.Round) + return true + } + } + // If there are prevotes to send...Needed because of validBlock mechanism + if prs.Round != -1 && prs.Round <= rs.Round { + if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { + logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round) + return true + } + } + // If there are POLPrevotes to send... + if prs.ProposalPOLRound != -1 { + if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { + if ps.PickSendVote(polPrevotes) { + logger.Debug("Picked rs.Prevotes(prs.ProposalPOLRound) to send", + "round", prs.ProposalPOLRound) + return true + } + } + } + + return false +} + +// NOTE: `queryMaj23Routine` has a simple crude design since it only comes +// into play for liveness when there's a signature DDoS attack happening. +func (conR *Reactor) queryMaj23Routine(peer p2p.Peer, ps *PeerState) { + logger := conR.Logger.With("peer", peer) + +OUTER_LOOP: + for { + // Manage disconnects from self or peer. + if !peer.IsRunning() || !conR.IsRunning() { + logger.Info("Stopping queryMaj23Routine for peer") + return + } + + // Maybe send Height/Round/Prevotes + { + rs := conR.conS.GetRoundState() + prs := ps.GetRoundState() + if rs.Height == prs.Height { + if maj23, ok := rs.Votes.Prevotes(prs.Round).TwoThirdsMajority(); ok { + peer.TrySend(StateChannel, MustEncode(&VoteSetMaj23Message{ + Height: prs.Height, + Round: prs.Round, + Type: tmproto.PrevoteType, + BlockID: maj23, + })) + time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) + } + } + } + + // Maybe send Height/Round/Precommits + { + rs := conR.conS.GetRoundState() + prs := ps.GetRoundState() + if rs.Height == prs.Height { + if maj23, ok := rs.Votes.Precommits(prs.Round).TwoThirdsMajority(); ok { + peer.TrySend(StateChannel, MustEncode(&VoteSetMaj23Message{ + Height: prs.Height, + Round: prs.Round, + Type: tmproto.PrecommitType, + BlockID: maj23, + })) + time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) + } + } + } + + // Maybe send Height/Round/ProposalPOL + { + rs := conR.conS.GetRoundState() + prs := ps.GetRoundState() + if rs.Height == prs.Height && prs.ProposalPOLRound >= 0 { + if maj23, ok := rs.Votes.Prevotes(prs.ProposalPOLRound).TwoThirdsMajority(); ok { + peer.TrySend(StateChannel, MustEncode(&VoteSetMaj23Message{ + Height: prs.Height, + Round: prs.ProposalPOLRound, + Type: tmproto.PrevoteType, + BlockID: maj23, + })) + time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) + } + } + } + + // Little point sending LastCommitRound/LastCommit, + // These are fleeting and non-blocking. + + // Maybe send Height/CatchupCommitRound/CatchupCommit. + { + prs := ps.GetRoundState() + if prs.CatchupCommitRound != -1 && prs.Height > 0 && prs.Height <= conR.conS.blockStore.Height() && + prs.Height >= conR.conS.blockStore.Base() { + if commit := conR.conS.LoadCommit(prs.Height); commit != nil { + peer.TrySend(StateChannel, MustEncode(&VoteSetMaj23Message{ + Height: prs.Height, + Round: commit.Round, + Type: tmproto.PrecommitType, + BlockID: commit.BlockID, + })) + time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) + } + } + } + + time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) + + continue OUTER_LOOP + } +} + +func (conR *Reactor) peerStatsRoutine() { + for { + if !conR.IsRunning() { + conR.Logger.Info("Stopping peerStatsRoutine") + return + } + + select { + case msg := <-conR.conS.statsMsgQueue: + // Get peer + peer := conR.Switch.Peers().Get(msg.PeerID) + if peer == nil { + conR.Logger.Debug("Attempt to update stats for non-existent peer", + "peer", msg.PeerID) + continue + } + // Get peer state + ps, ok := peer.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } + switch msg.Msg.(type) { + case *VoteMessage: + if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 { + conR.Switch.MarkPeerAsGood(peer) + } + case *BlockPartMessage: + if numParts := ps.RecordBlockPart(); numParts%blocksToContributeToBecomeGoodPeer == 0 { + conR.Switch.MarkPeerAsGood(peer) + } + } + case <-conR.conS.Quit(): + return + + case <-conR.Quit(): + return + } + } +} + +// String returns a string representation of the Reactor. +// NOTE: For now, it is just a hard-coded string to avoid accessing unprotected shared variables. +// TODO: improve! +func (conR *Reactor) String() string { + // better not to access shared variables + return "ConsensusReactor" // conR.StringIndented("") +} + +// StringIndented returns an indented string representation of the Reactor +func (conR *Reactor) StringIndented(indent string) string { + s := "ConsensusReactor{\n" + s += indent + " " + conR.conS.StringIndented(indent+" ") + "\n" + for _, peer := range conR.Switch.Peers().List() { + ps, ok := peer.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } + s += indent + " " + ps.StringIndented(indent+" ") + "\n" + } + s += indent + "}" + return s +} + +// ReactorMetrics sets the metrics +func ReactorMetrics(metrics *Metrics) ReactorOption { + return func(conR *Reactor) { conR.Metrics = metrics } +} + +//----------------------------------------------------------------------------- + +var ( + ErrPeerStateHeightRegression = errors.New("error peer state height regression") + ErrPeerStateInvalidStartTime = errors.New("error peer state invalid startTime") +) + +// PeerState contains the known state of a peer, including its connection and +// threadsafe access to its PeerRoundState. +// NOTE: THIS GETS DUMPED WITH rpc/core/consensus.go. +// Be mindful of what you Expose. +type PeerState struct { + peer p2p.Peer + logger log.Logger + + mtx sync.Mutex // NOTE: Modify below using setters, never directly. + PRS cstypes.PeerRoundState `json:"round_state"` // Exposed. + Stats *peerStateStats `json:"stats"` // Exposed. +} + +// peerStateStats holds internal statistics for a peer. +type peerStateStats struct { + Votes int `json:"votes"` + BlockParts int `json:"block_parts"` +} + +func (pss peerStateStats) String() string { + return fmt.Sprintf("peerStateStats{votes: %d, blockParts: %d}", + pss.Votes, pss.BlockParts) +} + +// NewPeerState returns a new PeerState for the given Peer +func NewPeerState(peer p2p.Peer) *PeerState { + return &PeerState{ + peer: peer, + logger: log.NewNopLogger(), + PRS: cstypes.PeerRoundState{ + Round: -1, + ProposalPOLRound: -1, + LastCommitRound: -1, + CatchupCommitRound: -1, + }, + Stats: &peerStateStats{}, + } +} + +// SetLogger allows to set a logger on the peer state. Returns the peer state +// itself. +func (ps *PeerState) SetLogger(logger log.Logger) *PeerState { + ps.logger = logger + return ps +} + +// GetRoundState returns an shallow copy of the PeerRoundState. +// There's no point in mutating it since it won't change PeerState. +func (ps *PeerState) GetRoundState() *cstypes.PeerRoundState { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + prs := ps.PRS // copy + return &prs +} + +// ToJSON returns a json of PeerState. +func (ps *PeerState) ToJSON() ([]byte, error) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + return tmjson.Marshal(ps) +} + +// GetHeight returns an atomic snapshot of the PeerRoundState's height +// used by the mempool to ensure peers are caught up before broadcasting new txs +func (ps *PeerState) GetHeight() int64 { + ps.mtx.Lock() + defer ps.mtx.Unlock() + return ps.PRS.Height +} + +// SetHasProposal sets the given proposal as known for the peer. +func (ps *PeerState) SetHasProposal(proposal *types.Proposal) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.Height != proposal.Height || ps.PRS.Round != proposal.Round { + return + } + + if ps.PRS.Proposal { + return + } + + ps.PRS.Proposal = true + + // ps.PRS.ProposalBlockParts is set due to NewValidBlockMessage + if ps.PRS.ProposalBlockParts != nil { + return + } + + ps.PRS.ProposalBlockPartSetHeader = proposal.BlockID.PartSetHeader + ps.PRS.ProposalBlockParts = bits.NewBitArray(int(proposal.BlockID.PartSetHeader.Total)) + ps.PRS.ProposalPOLRound = proposal.POLRound + ps.PRS.ProposalPOL = nil // Nil until ProposalPOLMessage received. +} + +// InitProposalBlockParts initializes the peer's proposal block parts header and bit array. +func (ps *PeerState) InitProposalBlockParts(partSetHeader types.PartSetHeader) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.ProposalBlockParts != nil { + return + } + + ps.PRS.ProposalBlockPartSetHeader = partSetHeader + ps.PRS.ProposalBlockParts = bits.NewBitArray(int(partSetHeader.Total)) +} + +// SetHasProposalBlockPart sets the given block part index as known for the peer. +func (ps *PeerState) SetHasProposalBlockPart(height int64, round int32, index int) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.Height != height || ps.PRS.Round != round { + return + } + + ps.PRS.ProposalBlockParts.SetIndex(index, true) +} + +// PickSendVote picks a vote and sends it to the peer. +// Returns true if vote was sent. +func (ps *PeerState) PickSendVote(votes types.VoteSetReader) bool { + if vote, ok := ps.PickVoteToSend(votes); ok { + msg := &VoteMessage{vote} + ps.logger.Debug("Sending vote message", "ps", ps, "vote", vote) + if ps.peer.Send(VoteChannel, MustEncode(msg)) { + ps.SetHasVote(vote) + return true + } + return false + } + return false +} + +// PickVoteToSend picks a vote to send to the peer. +// Returns true if a vote was picked. +// NOTE: `votes` must be the correct Size() for the Height(). +func (ps *PeerState) PickVoteToSend(votes types.VoteSetReader) (vote *types.Vote, ok bool) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if votes.Size() == 0 { + return nil, false + } + + height, round, votesType, size := + votes.GetHeight(), votes.GetRound(), tmproto.SignedMsgType(votes.Type()), votes.Size() + + // Lazily set data using 'votes'. + if votes.IsCommit() { + ps.ensureCatchupCommitRound(height, round, size) + } + ps.ensureVoteBitArrays(height, size) + + psVotes := ps.getVoteBitArray(height, round, votesType) + if psVotes == nil { + return nil, false // Not something worth sending + } + if index, ok := votes.BitArray().Sub(psVotes).PickRandom(); ok { + return votes.GetByIndex(int32(index)), true + } + return nil, false +} + +func (ps *PeerState) getVoteBitArray(height int64, round int32, votesType tmproto.SignedMsgType) *bits.BitArray { + if !types.IsVoteTypeValid(votesType) { + return nil + } + + if ps.PRS.Height == height { + if ps.PRS.Round == round { + switch votesType { + case tmproto.PrevoteType: + return ps.PRS.Prevotes + case tmproto.PrecommitType: + return ps.PRS.Precommits + } + } + if ps.PRS.CatchupCommitRound == round { + switch votesType { + case tmproto.PrevoteType: + return nil + case tmproto.PrecommitType: + return ps.PRS.CatchupCommit + } + } + if ps.PRS.ProposalPOLRound == round { + switch votesType { + case tmproto.PrevoteType: + return ps.PRS.ProposalPOL + case tmproto.PrecommitType: + return nil + } + } + return nil + } + if ps.PRS.Height == height+1 { + if ps.PRS.LastCommitRound == round { + switch votesType { + case tmproto.PrevoteType: + return nil + case tmproto.PrecommitType: + return ps.PRS.LastCommit + } + } + return nil + } + return nil +} + +// 'round': A round for which we have a +2/3 commit. +func (ps *PeerState) ensureCatchupCommitRound(height int64, round int32, numValidators int) { + if ps.PRS.Height != height { + return + } + /* + NOTE: This is wrong, 'round' could change. + e.g. if orig round is not the same as block LastCommit round. + if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { + panic(fmt.Sprintf( + "Conflicting CatchupCommitRound. Height: %v, + Orig: %v, + New: %v", + height, + ps.CatchupCommitRound, + round)) + } + */ + if ps.PRS.CatchupCommitRound == round { + return // Nothing to do! + } + ps.PRS.CatchupCommitRound = round + if round == ps.PRS.Round { + ps.PRS.CatchupCommit = ps.PRS.Precommits + } else { + ps.PRS.CatchupCommit = bits.NewBitArray(numValidators) + } +} + +// EnsureVoteBitArrays ensures the bit-arrays have been allocated for tracking +// what votes this peer has received. +// NOTE: It's important to make sure that numValidators actually matches +// what the node sees as the number of validators for height. +func (ps *PeerState) EnsureVoteBitArrays(height int64, numValidators int) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + ps.ensureVoteBitArrays(height, numValidators) +} + +func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) { + if ps.PRS.Height == height { + if ps.PRS.Prevotes == nil { + ps.PRS.Prevotes = bits.NewBitArray(numValidators) + } + if ps.PRS.Precommits == nil { + ps.PRS.Precommits = bits.NewBitArray(numValidators) + } + if ps.PRS.CatchupCommit == nil { + ps.PRS.CatchupCommit = bits.NewBitArray(numValidators) + } + if ps.PRS.ProposalPOL == nil { + ps.PRS.ProposalPOL = bits.NewBitArray(numValidators) + } + } else if ps.PRS.Height == height+1 { + if ps.PRS.LastCommit == nil { + ps.PRS.LastCommit = bits.NewBitArray(numValidators) + } + } +} + +// RecordVote increments internal votes related statistics for this peer. +// It returns the total number of added votes. +func (ps *PeerState) RecordVote() int { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + ps.Stats.Votes++ + + return ps.Stats.Votes +} + +// VotesSent returns the number of blocks for which peer has been sending us +// votes. +func (ps *PeerState) VotesSent() int { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + return ps.Stats.Votes +} + +// RecordBlockPart increments internal block part related statistics for this peer. +// It returns the total number of added block parts. +func (ps *PeerState) RecordBlockPart() int { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + ps.Stats.BlockParts++ + return ps.Stats.BlockParts +} + +// BlockPartsSent returns the number of useful block parts the peer has sent us. +func (ps *PeerState) BlockPartsSent() int { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + return ps.Stats.BlockParts +} + +// SetHasVote sets the given vote as known by the peer +func (ps *PeerState) SetHasVote(vote *types.Vote) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + ps.setHasVote(vote.Height, vote.Round, vote.Type, vote.ValidatorIndex) +} + +func (ps *PeerState) setHasVote(height int64, round int32, voteType tmproto.SignedMsgType, index int32) { + logger := ps.logger.With( + "peerH/R", + fmt.Sprintf("%d/%d", ps.PRS.Height, ps.PRS.Round), + "H/R", + fmt.Sprintf("%d/%d", height, round)) + logger.Debug("setHasVote", "type", voteType, "index", index) + + // NOTE: some may be nil BitArrays -> no side effects. + psVotes := ps.getVoteBitArray(height, round, voteType) + if psVotes != nil { + psVotes.SetIndex(int(index), true) + } +} + +// ApplyNewRoundStepMessage updates the peer state for the new round. +func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + // Ignore duplicates or decreases + if CompareHRS(msg.Height, msg.Round, msg.Step, ps.PRS.Height, ps.PRS.Round, ps.PRS.Step) <= 0 { + return + } + + // Just remember these values. + psHeight := ps.PRS.Height + psRound := ps.PRS.Round + psCatchupCommitRound := ps.PRS.CatchupCommitRound + psCatchupCommit := ps.PRS.CatchupCommit + + startTime := tmtime.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second) + ps.PRS.Height = msg.Height + ps.PRS.Round = msg.Round + ps.PRS.Step = msg.Step + ps.PRS.StartTime = startTime + if psHeight != msg.Height || psRound != msg.Round { + ps.PRS.Proposal = false + ps.PRS.ProposalBlockPartSetHeader = types.PartSetHeader{} + ps.PRS.ProposalBlockParts = nil + ps.PRS.ProposalPOLRound = -1 + ps.PRS.ProposalPOL = nil + // We'll update the BitArray capacity later. + ps.PRS.Prevotes = nil + ps.PRS.Precommits = nil + } + if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound { + // Peer caught up to CatchupCommitRound. + // Preserve psCatchupCommit! + // NOTE: We prefer to use prs.Precommits if + // pr.Round matches pr.CatchupCommitRound. + ps.PRS.Precommits = psCatchupCommit + } + if psHeight != msg.Height { + // Shift Precommits to LastCommit. + if psHeight+1 == msg.Height && psRound == msg.LastCommitRound { + ps.PRS.LastCommitRound = msg.LastCommitRound + ps.PRS.LastCommit = ps.PRS.Precommits + } else { + ps.PRS.LastCommitRound = msg.LastCommitRound + ps.PRS.LastCommit = nil + } + // We'll update the BitArray capacity later. + ps.PRS.CatchupCommitRound = -1 + ps.PRS.CatchupCommit = nil + } +} + +// ApplyNewValidBlockMessage updates the peer state for the new valid block. +func (ps *PeerState) ApplyNewValidBlockMessage(msg *NewValidBlockMessage) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.Height != msg.Height { + return + } + + if ps.PRS.Round != msg.Round && !msg.IsCommit { + return + } + + ps.PRS.ProposalBlockPartSetHeader = msg.BlockPartSetHeader + ps.PRS.ProposalBlockParts = msg.BlockParts +} + +// ApplyProposalPOLMessage updates the peer state for the new proposal POL. +func (ps *PeerState) ApplyProposalPOLMessage(msg *ProposalPOLMessage) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.Height != msg.Height { + return + } + if ps.PRS.ProposalPOLRound != msg.ProposalPOLRound { + return + } + + // TODO: Merge onto existing ps.PRS.ProposalPOL? + // We might have sent some prevotes in the meantime. + ps.PRS.ProposalPOL = msg.ProposalPOL +} + +// ApplyHasVoteMessage updates the peer state for the new vote. +func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.PRS.Height != msg.Height { + return + } + + ps.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) +} + +// ApplyVoteSetBitsMessage updates the peer state for the bit-array of votes +// it claims to have for the corresponding BlockID. +// `ourVotes` is a BitArray of votes we have for msg.BlockID +// NOTE: if ourVotes is nil (e.g. msg.Height < rs.Height), +// we conservatively overwrite ps's votes w/ msg.Votes. +func (ps *PeerState) ApplyVoteSetBitsMessage(msg *VoteSetBitsMessage, ourVotes *bits.BitArray) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + votes := ps.getVoteBitArray(msg.Height, msg.Round, msg.Type) + if votes != nil { + if ourVotes == nil { + votes.Update(msg.Votes) + } else { + otherVotes := votes.Sub(ourVotes) + hasVotes := otherVotes.Or(msg.Votes) + votes.Update(hasVotes) + } + } +} + +// String returns a string representation of the PeerState +func (ps *PeerState) String() string { + return ps.StringIndented("") +} + +// StringIndented returns a string representation of the PeerState +func (ps *PeerState) StringIndented(indent string) string { + ps.mtx.Lock() + defer ps.mtx.Unlock() + return fmt.Sprintf(`PeerState{ +%s Key %v +%s RoundState %v +%s Stats %v +%s}`, + indent, ps.peer.ID(), + indent, ps.PRS.StringIndented(indent+" "), + indent, ps.Stats, + indent) +} + +//----------------------------------------------------------------------------- +// Messages + +// Message is a message that can be sent and received on the Reactor +type Message interface { + ValidateBasic() error +} + +// func init() { +// tmjson.RegisterType(&NewRoundStepMessage{}, "tendermint/NewRoundStepMessage") +// tmjson.RegisterType(&NewValidBlockMessage{}, "tendermint/NewValidBlockMessage") +// tmjson.RegisterType(&ProposalMessage{}, "tendermint/Proposal") +// tmjson.RegisterType(&ProposalPOLMessage{}, "tendermint/ProposalPOL") +// tmjson.RegisterType(&BlockPartMessage{}, "tendermint/BlockPart") +// tmjson.RegisterType(&VoteMessage{}, "tendermint/Vote") +// tmjson.RegisterType(&HasVoteMessage{}, "tendermint/HasVote") +// tmjson.RegisterType(&VoteSetMaj23Message{}, "tendermint/VoteSetMaj23") +// tmjson.RegisterType(&VoteSetBitsMessage{}, "tendermint/VoteSetBits") +// } + +func decodeMsg(bz []byte) (msg Message, err error) { + pb := &tmcons.Message{} + if err = proto.Unmarshal(bz, pb); err != nil { + return msg, err + } + + return MsgFromProto(pb) +} + +//------------------------------------- + +// NewRoundStepMessage is sent for every step taken in the ConsensusState. +// For every height/round/step transition +type NewRoundStepMessage struct { + Height int64 + Round int32 + Step cstypes.RoundStepType + SecondsSinceStartTime int64 + LastCommitRound int32 +} + +// ValidateBasic performs basic validation. +func (m *NewRoundStepMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.Round < 0 { + return errors.New("negative Round") + } + if !m.Step.IsValid() { + return errors.New("invalid Step") + } + + // NOTE: SecondsSinceStartTime may be negative + + // LastCommitRound will be -1 for the initial height, but we don't know what height this is + // since it can be specified in genesis. The reactor will have to validate this via + // ValidateHeight(). + if m.LastCommitRound < -1 { + return errors.New("invalid LastCommitRound (cannot be < -1)") + } + + return nil +} + +// ValidateHeight validates the height given the chain's initial height. +func (m *NewRoundStepMessage) ValidateHeight(initialHeight int64) error { + if m.Height < initialHeight { + return fmt.Errorf("invalid Height %v (lower than initial height %v)", + m.Height, initialHeight) + } + if m.Height == initialHeight && m.LastCommitRound != -1 { + return fmt.Errorf("invalid LastCommitRound %v (must be -1 for initial height %v)", + m.LastCommitRound, initialHeight) + } + if m.Height > initialHeight && m.LastCommitRound < 0 { + return fmt.Errorf("LastCommitRound can only be negative for initial height %v", // nolint + initialHeight) + } + return nil +} + +// String returns a string representation. +func (m *NewRoundStepMessage) String() string { + return fmt.Sprintf("[NewRoundStep H:%v R:%v S:%v LCR:%v]", + m.Height, m.Round, m.Step, m.LastCommitRound) +} + +//------------------------------------- + +// NewValidBlockMessage is sent when a validator observes a valid block B in some round r, +// i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. +// In case the block is also committed, then IsCommit flag is set to true. +type NewValidBlockMessage struct { + Height int64 + Round int32 + BlockPartSetHeader types.PartSetHeader + BlockParts *bits.BitArray + IsCommit bool +} + +// ValidateBasic performs basic validation. +func (m *NewValidBlockMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.Round < 0 { + return errors.New("negative Round") + } + if err := m.BlockPartSetHeader.ValidateBasic(); err != nil { + return fmt.Errorf("wrong BlockPartSetHeader: %v", err) + } + if m.BlockParts.Size() == 0 { + return errors.New("empty blockParts") + } + if m.BlockParts.Size() != int(m.BlockPartSetHeader.Total) { + return fmt.Errorf("blockParts bit array size %d not equal to BlockPartSetHeader.Total %d", + m.BlockParts.Size(), + m.BlockPartSetHeader.Total) + } + if m.BlockParts.Size() > int(types.MaxBlockPartsCount) { + return fmt.Errorf("blockParts bit array is too big: %d, max: %d", m.BlockParts.Size(), types.MaxBlockPartsCount) + } + return nil +} + +// String returns a string representation. +func (m *NewValidBlockMessage) String() string { + return fmt.Sprintf("[ValidBlockMessage H:%v R:%v BP:%v BA:%v IsCommit:%v]", + m.Height, m.Round, m.BlockPartSetHeader, m.BlockParts, m.IsCommit) +} + +//------------------------------------- + +// ProposalMessage is sent when a new block is proposed. +type ProposalMessage struct { + Proposal *types.Proposal +} + +// ValidateBasic performs basic validation. +func (m *ProposalMessage) ValidateBasic() error { + return m.Proposal.ValidateBasic() +} + +// String returns a string representation. +func (m *ProposalMessage) String() string { + return fmt.Sprintf("[Proposal %v]", m.Proposal) +} + +//------------------------------------- + +// ProposalPOLMessage is sent when a previous proposal is re-proposed. +type ProposalPOLMessage struct { + Height int64 + ProposalPOLRound int32 + ProposalPOL *bits.BitArray +} + +// ValidateBasic performs basic validation. +func (m *ProposalPOLMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.ProposalPOLRound < 0 { + return errors.New("negative ProposalPOLRound") + } + if m.ProposalPOL.Size() == 0 { + return errors.New("empty ProposalPOL bit array") + } + if m.ProposalPOL.Size() > types.MaxVotesCount { + return fmt.Errorf("proposalPOL bit array is too big: %d, max: %d", m.ProposalPOL.Size(), types.MaxVotesCount) + } + return nil +} + +// String returns a string representation. +func (m *ProposalPOLMessage) String() string { + return fmt.Sprintf("[ProposalPOL H:%v POLR:%v POL:%v]", m.Height, m.ProposalPOLRound, m.ProposalPOL) +} + +//------------------------------------- + +// BlockPartMessage is sent when gossipping a piece of the proposed block. +type BlockPartMessage struct { + Height int64 + Round int32 + Part *types.Part +} + +// ValidateBasic performs basic validation. +func (m *BlockPartMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.Round < 0 { + return errors.New("negative Round") + } + if err := m.Part.ValidateBasic(); err != nil { + return fmt.Errorf("wrong Part: %v", err) + } + return nil +} + +// String returns a string representation. +func (m *BlockPartMessage) String() string { + return fmt.Sprintf("[BlockPart H:%v R:%v P:%v]", m.Height, m.Round, m.Part) +} + +//------------------------------------- + +// VoteMessage is sent when voting for a proposal (or lack thereof). +type VoteMessage struct { + Vote *types.Vote +} + +// ValidateBasic performs basic validation. +func (m *VoteMessage) ValidateBasic() error { + return m.Vote.ValidateBasic() +} + +// String returns a string representation. +func (m *VoteMessage) String() string { + return fmt.Sprintf("[Vote %v]", m.Vote) +} + +//------------------------------------- + +// HasVoteMessage is sent to indicate that a particular vote has been received. +type HasVoteMessage struct { + Height int64 + Round int32 + Type tmproto.SignedMsgType + Index int32 +} + +// ValidateBasic performs basic validation. +func (m *HasVoteMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.Round < 0 { + return errors.New("negative Round") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("invalid Type") + } + if m.Index < 0 { + return errors.New("negative Index") + } + return nil +} + +// String returns a string representation. +func (m *HasVoteMessage) String() string { + return fmt.Sprintf("[HasVote VI:%v V:{%v/%02d/%v}]", m.Index, m.Height, m.Round, m.Type) +} + +//------------------------------------- + +// VoteSetMaj23Message is sent to indicate that a given BlockID has seen +2/3 votes. +type VoteSetMaj23Message struct { + Height int64 + Round int32 + Type tmproto.SignedMsgType + BlockID types.BlockID +} + +// ValidateBasic performs basic validation. +func (m *VoteSetMaj23Message) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if m.Round < 0 { + return errors.New("negative Round") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("invalid Type") + } + if err := m.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("wrong BlockID: %v", err) + } + return nil +} + +// String returns a string representation. +func (m *VoteSetMaj23Message) String() string { + return fmt.Sprintf("[VSM23 %v/%02d/%v %v]", m.Height, m.Round, m.Type, m.BlockID) +} + +//------------------------------------- + +// VoteSetBitsMessage is sent to communicate the bit-array of votes seen for the BlockID. +type VoteSetBitsMessage struct { + Height int64 + Round int32 + Type tmproto.SignedMsgType + BlockID types.BlockID + Votes *bits.BitArray +} + +// ValidateBasic performs basic validation. +func (m *VoteSetBitsMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("negative Height") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("invalid Type") + } + if err := m.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("wrong BlockID: %v", err) + } + // NOTE: Votes.Size() can be zero if the node does not have any + if m.Votes.Size() > types.MaxVotesCount { + return fmt.Errorf("votes bit array is too big: %d, max: %d", m.Votes.Size(), types.MaxVotesCount) + } + return nil +} + +// String returns a string representation. +func (m *VoteSetBitsMessage) String() string { + return fmt.Sprintf("[VSB %v/%02d/%v %v %v]", m.Height, m.Round, m.Type, m.BlockID, m.Votes) +} + +//------------------------------------- diff --git a/test/maverick/consensus/replay.go b/test/maverick/consensus/replay.go new file mode 100644 index 0000000000..bfec9e96d4 --- /dev/null +++ b/test/maverick/consensus/replay.go @@ -0,0 +1,533 @@ +package consensus + +import ( + "bytes" + "fmt" + "hash/crc32" + "io" + "reflect" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +var crc32c = crc32.MakeTable(crc32.Castagnoli) + +// Functionality to replay blocks and messages on recovery from a crash. +// There are two general failure scenarios: +// +// 1. failure during consensus +// 2. failure while applying the block +// +// The former is handled by the WAL, the latter by the proxyApp Handshake on +// restart, which ultimately hands off the work to the WAL. + +//----------------------------------------- +// 1. Recover from failure during consensus +// (by replaying messages from the WAL) +//----------------------------------------- + +// Unmarshal and apply a single message to the consensus state as if it were +// received in receiveRoutine. Lines that start with "#" are ignored. +// NOTE: receiveRoutine should not be running. +func (cs *State) readReplayMessage(msg *TimedWALMessage, newStepSub types.Subscription) error { + // Skip meta messages which exist for demarcating boundaries. + if _, ok := msg.Msg.(EndHeightMessage); ok { + return nil + } + + // for logging + switch m := msg.Msg.(type) { + case types.EventDataRoundState: + cs.Logger.Info("Replay: New Step", "height", m.Height, "round", m.Round, "step", m.Step) + // these are playback checks + ticker := time.After(time.Second * 2) + if newStepSub != nil { + select { + case stepMsg := <-newStepSub.Out(): + m2 := stepMsg.Data().(types.EventDataRoundState) + if m.Height != m2.Height || m.Round != m2.Round || m.Step != m2.Step { + return fmt.Errorf("roundState mismatch. Got %v; Expected %v", m2, m) + } + case <-newStepSub.Cancelled(): + return fmt.Errorf("failed to read off newStepSub.Out(). newStepSub was cancelled") + case <-ticker: + return fmt.Errorf("failed to read off newStepSub.Out()") + } + } + case msgInfo: + peerID := m.PeerID + if peerID == "" { + peerID = "local" + } + switch msg := m.Msg.(type) { + case *ProposalMessage: + p := msg.Proposal + cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header", + p.BlockID.PartSetHeader, "pol", p.POLRound, "peer", peerID) + case *BlockPartMessage: + cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerID) + case *VoteMessage: + v := msg.Vote + cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type, + "blockID", v.BlockID, "peer", peerID) + } + + cs.handleMsg(m) + case timeoutInfo: + cs.Logger.Info("Replay: Timeout", "height", m.Height, "round", m.Round, "step", m.Step, "dur", m.Duration) + cs.handleTimeout(m, cs.RoundState) + default: + return fmt.Errorf("replay: Unknown TimedWALMessage type: %v", reflect.TypeOf(msg.Msg)) + } + return nil +} + +// Replay only those messages since the last block. `timeoutRoutine` should +// run concurrently to read off tickChan. +func (cs *State) catchupReplay(csHeight int64) error { + + // Set replayMode to true so we don't log signing errors. + cs.replayMode = true + defer func() { cs.replayMode = false }() + + // Ensure that #ENDHEIGHT for this height doesn't exist. + // NOTE: This is just a sanity check. As far as we know things work fine + // without it, and Handshake could reuse State if it weren't for + // this check (since we can crash after writing #ENDHEIGHT). + // + // Ignore data corruption errors since this is a sanity check. + gr, found, err := cs.wal.SearchForEndHeight(csHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true}) + if err != nil { + return err + } + if gr != nil { + if err := gr.Close(); err != nil { + return err + } + } + if found { + return fmt.Errorf("wal should not contain #ENDHEIGHT %d", csHeight) + } + + // Search for last height marker. + // + // Ignore data corruption errors in previous heights because we only care about last height + if csHeight < cs.state.InitialHeight { + return fmt.Errorf("cannot replay height %v, below initial height %v", csHeight, cs.state.InitialHeight) + } + endHeight := csHeight - 1 + if csHeight == cs.state.InitialHeight { + endHeight = 0 + } + gr, found, err = cs.wal.SearchForEndHeight(endHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true}) + if err == io.EOF { + cs.Logger.Error("Replay: wal.group.Search returned EOF", "#ENDHEIGHT", endHeight) + } else if err != nil { + return err + } + if !found { + return fmt.Errorf("cannot replay height %d. WAL does not contain #ENDHEIGHT for %d", csHeight, endHeight) + } + defer gr.Close() + + cs.Logger.Info("Catchup by replaying consensus messages", "height", csHeight) + + var msg *TimedWALMessage + dec := WALDecoder{gr} + +LOOP: + for { + msg, err = dec.Decode() + switch { + case err == io.EOF: + break LOOP + case IsDataCorruptionError(err): + cs.Logger.Error("data has been corrupted in last height of consensus WAL", "err", err, "height", csHeight) + return err + case err != nil: + return err + } + + // NOTE: since the priv key is set when the msgs are received + // it will attempt to eg double sign but we can just ignore it + // since the votes will be replayed and we'll get to the next step + if err := cs.readReplayMessage(msg, nil); err != nil { + return err + } + } + cs.Logger.Info("Replay: Done") + return nil +} + +//-------------------------------------------------------------------------------- + +// Parses marker lines of the form: +// #ENDHEIGHT: 12345 +/* +func makeHeightSearchFunc(height int64) auto.SearchFunc { + return func(line string) (int, error) { + line = strings.TrimRight(line, "\n") + parts := strings.Split(line, " ") + if len(parts) != 2 { + return -1, errors.New("line did not have 2 parts") + } + i, err := strconv.Atoi(parts[1]) + if err != nil { + return -1, errors.New("failed to parse INFO: " + err.Error()) + } + if height < i { + return 1, nil + } else if height == i { + return 0, nil + } else { + return -1, nil + } + } +}*/ + +//--------------------------------------------------- +// 2. Recover from failure while applying the block. +// (by handshaking with the app to figure out where +// we were last, and using the WAL to recover there.) +//--------------------------------------------------- + +type Handshaker struct { + stateStore sm.Store + initialState sm.State + store sm.BlockStore + eventBus types.BlockEventPublisher + genDoc *types.GenesisDoc + logger log.Logger + + nBlocks int // number of blocks applied to the state +} + +func NewHandshaker(stateStore sm.Store, state sm.State, + store sm.BlockStore, genDoc *types.GenesisDoc) *Handshaker { + + return &Handshaker{ + stateStore: stateStore, + initialState: state, + store: store, + eventBus: types.NopEventBus{}, + genDoc: genDoc, + logger: log.NewNopLogger(), + nBlocks: 0, + } +} + +func (h *Handshaker) SetLogger(l log.Logger) { + h.logger = l +} + +// SetEventBus - sets the event bus for publishing block related events. +// If not called, it defaults to types.NopEventBus. +func (h *Handshaker) SetEventBus(eventBus types.BlockEventPublisher) { + h.eventBus = eventBus +} + +// NBlocks returns the number of blocks applied to the state. +func (h *Handshaker) NBlocks() int { + return h.nBlocks +} + +// TODO: retry the handshake/replay if it fails ? +func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { + + // Handshake is done via ABCI Info on the query conn. + res, err := proxyApp.Query().InfoSync(proxy.RequestInfo) + if err != nil { + return fmt.Errorf("error calling Info: %v", err) + } + + blockHeight := res.LastBlockHeight + if blockHeight < 0 { + return fmt.Errorf("got a negative last block height (%d) from the app", blockHeight) + } + appHash := res.LastBlockAppHash + + h.logger.Info("ABCI Handshake App Info", + "height", blockHeight, + "hash", fmt.Sprintf("%X", appHash), + "software-version", res.Version, + "protocol-version", res.AppVersion, + ) + + // Only set the version if there is no existing state. + if h.initialState.LastBlockHeight == 0 { + h.initialState.Version.Consensus.App = res.AppVersion + } + + // Replay blocks up to the latest in the blockstore. + _, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp) + if err != nil { + return fmt.Errorf("error on replay: %v", err) + } + + h.logger.Info("Completed ABCI Handshake - Tendermint and App are synced", + "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash)) + + // TODO: (on restart) replay mempool + + return nil +} + +// ReplayBlocks replays all blocks since appBlockHeight and ensures the result +// matches the current state. +// Returns the final AppHash or an error. +func (h *Handshaker) ReplayBlocks( + state sm.State, + appHash []byte, + appBlockHeight int64, + proxyApp proxy.AppConns, +) ([]byte, error) { + storeBlockBase := h.store.Base() + storeBlockHeight := h.store.Height() + stateBlockHeight := state.LastBlockHeight + h.logger.Info( + "ABCI Replay Blocks", + "appHeight", + appBlockHeight, + "storeHeight", + storeBlockHeight, + "stateHeight", + stateBlockHeight) + + // If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain. + if appBlockHeight == 0 { + validators := make([]*types.Validator, len(h.genDoc.Validators)) + for i, val := range h.genDoc.Validators { + validators[i] = types.NewValidator(val.PubKey, val.Power) + } + validatorSet := types.NewValidatorSet(validators) + nextVals := types.TM2PB.ValidatorUpdates(validatorSet) + csParams := types.TM2PB.ConsensusParams(h.genDoc.ConsensusParams) + req := abci.RequestInitChain{ + Time: h.genDoc.GenesisTime, + ChainId: h.genDoc.ChainID, + InitialHeight: h.genDoc.InitialHeight, + ConsensusParams: csParams, + Validators: nextVals, + AppStateBytes: h.genDoc.AppState, + } + res, err := proxyApp.Consensus().InitChainSync(req) + if err != nil { + return nil, err + } + + appHash = res.AppHash + + if stateBlockHeight == 0 { // we only update state when we are in initial state + // If the app did not return an app hash, we keep the one set from the genesis doc in + // the state. We don't set appHash since we don't want the genesis doc app hash + // recorded in the genesis block. We should probably just remove GenesisDoc.AppHash. + if len(res.AppHash) > 0 { + state.AppHash = res.AppHash + } + // If the app returned validators or consensus params, update the state. + if len(res.Validators) > 0 { + vals, err := types.PB2TM.ValidatorUpdates(res.Validators) + if err != nil { + return nil, err + } + state.Validators = types.NewValidatorSet(vals) + state.NextValidators = types.NewValidatorSet(vals).CopyIncrementProposerPriority(1) + } else if len(h.genDoc.Validators) == 0 { + // If validator set is not set in genesis and still empty after InitChain, exit. + return nil, fmt.Errorf("validator set is nil in genesis and still empty after InitChain") + } + + if res.ConsensusParams != nil { + state.ConsensusParams = types.UpdateConsensusParams(state.ConsensusParams, res.ConsensusParams) + state.Version.Consensus.App = state.ConsensusParams.Version.AppVersion + } + // We update the last results hash with the empty hash, to conform with RFC-6962. + state.LastResultsHash = merkle.HashFromByteSlices(nil) + if err := h.stateStore.Save(state); err != nil { + return nil, err + } + } + } + + // First handle edge cases and constraints on the storeBlockHeight and storeBlockBase. + switch { + case storeBlockHeight == 0: + assertAppHashEqualsOneFromState(appHash, state) + return appHash, nil + + case appBlockHeight == 0 && state.InitialHeight < storeBlockBase: + // the app has no state, and the block store is truncated above the initial height + return appHash, sm.ErrAppBlockHeightTooLow{AppHeight: appBlockHeight, StoreBase: storeBlockBase} + + case appBlockHeight > 0 && appBlockHeight < storeBlockBase-1: + // the app is too far behind truncated store (can be 1 behind since we replay the next) + return appHash, sm.ErrAppBlockHeightTooLow{AppHeight: appBlockHeight, StoreBase: storeBlockBase} + + case storeBlockHeight < appBlockHeight: + // the app should never be ahead of the store (but this is under app's control) + return appHash, sm.ErrAppBlockHeightTooHigh{CoreHeight: storeBlockHeight, AppHeight: appBlockHeight} + + case storeBlockHeight < stateBlockHeight: + // the state should never be ahead of the store (this is under tendermint's control) + panic(fmt.Sprintf("StateBlockHeight (%d) > StoreBlockHeight (%d)", stateBlockHeight, storeBlockHeight)) + + case storeBlockHeight > stateBlockHeight+1: + // store should be at most one ahead of the state (this is under tendermint's control) + panic(fmt.Sprintf("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1)) + } + + var err error + // Now either store is equal to state, or one ahead. + // For each, consider all cases of where the app could be, given app <= store + if storeBlockHeight == stateBlockHeight { + // Tendermint ran Commit and saved the state. + // Either the app is asking for replay, or we're all synced up. + if appBlockHeight < storeBlockHeight { + // the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store) + return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, false) + + } else if appBlockHeight == storeBlockHeight { + // We're good! + assertAppHashEqualsOneFromState(appHash, state) + return appHash, nil + } + + } else if storeBlockHeight == stateBlockHeight+1 { + // We saved the block in the store but haven't updated the state, + // so we'll need to replay a block using the WAL. + switch { + case appBlockHeight < stateBlockHeight: + // the app is further behind than it should be, so replay blocks + // but leave the last block to go through the WAL + return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, true) + + case appBlockHeight == stateBlockHeight: + // We haven't run Commit (both the state and app are one block behind), + // so replayBlock with the real app. + // NOTE: We could instead use the cs.WAL on cs.Start, + // but we'd have to allow the WAL to replay a block that wrote it's #ENDHEIGHT + h.logger.Info("Replay last block using real app") + state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus()) + return state.AppHash, err + + case appBlockHeight == storeBlockHeight: + // We ran Commit, but didn't save the state, so replayBlock with mock app. + abciResponses, err := h.stateStore.LoadABCIResponses(storeBlockHeight) + if err != nil { + return nil, err + } + mockApp := newMockProxyApp(appHash, abciResponses) + h.logger.Info("Replay last block using mock app") + state, err = h.replayBlock(state, storeBlockHeight, mockApp) + return state.AppHash, err + } + + } + + panic(fmt.Sprintf("uncovered case! appHeight: %d, storeHeight: %d, stateHeight: %d", + appBlockHeight, storeBlockHeight, stateBlockHeight)) +} + +func (h *Handshaker) replayBlocks( + state sm.State, + proxyApp proxy.AppConns, + appBlockHeight, + storeBlockHeight int64, + mutateState bool) ([]byte, error) { + // App is further behind than it should be, so we need to replay blocks. + // We replay all blocks from appBlockHeight+1. + // + // Note that we don't have an old version of the state, + // so we by-pass state validation/mutation using sm.ExecCommitBlock. + // This also means we won't be saving validator sets if they change during this period. + // TODO: Load the historical information to fix this and just use state.ApplyBlock + // + // If mutateState == true, the final block is replayed with h.replayBlock() + + var appHash []byte + var err error + finalBlock := storeBlockHeight + if mutateState { + finalBlock-- + } + firstBlock := appBlockHeight + 1 + if firstBlock == 1 { + firstBlock = state.InitialHeight + } + for i := firstBlock; i <= finalBlock; i++ { + h.logger.Info("Applying block", "height", i) + block := h.store.LoadBlock(i) + // Extra check to ensure the app was not changed in a way it shouldn't have. + if len(appHash) > 0 { + assertAppHashEqualsOneFromBlock(appHash, block) + } + + appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.stateStore, h.genDoc.InitialHeight) + if err != nil { + return nil, err + } + + h.nBlocks++ + } + + if mutateState { + // sync the final block + state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus()) + if err != nil { + return nil, err + } + appHash = state.AppHash + } + + assertAppHashEqualsOneFromState(appHash, state) + return appHash, nil +} + +// ApplyBlock on the proxyApp with the last block. +func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.AppConnConsensus) (sm.State, error) { + block := h.store.LoadBlock(height) + meta := h.store.LoadBlockMeta(height) + + // Use stubs for both mempool and evidence pool since no transactions nor + // evidence are needed here - block already exists. + blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, proxyApp, emptyMempool{}, sm.EmptyEvidencePool{}) + blockExec.SetEventBus(h.eventBus) + + var err error + state, _, err = blockExec.ApplyBlock(state, meta.BlockID, block) + if err != nil { + return sm.State{}, err + } + + h.nBlocks++ + + return state, nil +} + +func assertAppHashEqualsOneFromBlock(appHash []byte, block *types.Block) { + if !bytes.Equal(appHash, block.AppHash) { + panic(fmt.Sprintf(`block.AppHash does not match AppHash after replay. Got %X, expected %X. + +Block: %v +`, + appHash, block.AppHash, block)) + } +} + +func assertAppHashEqualsOneFromState(appHash []byte, state sm.State) { + if !bytes.Equal(appHash, state.AppHash) { + panic(fmt.Sprintf(`state.AppHash does not match AppHash after replay. Got +%X, expected %X. + +State: %v + +Did you reset Tendermint without resetting your application's data?`, + appHash, state.AppHash, state)) + } +} diff --git a/test/maverick/consensus/replay_file.go b/test/maverick/consensus/replay_file.go new file mode 100644 index 0000000000..0a02031f81 --- /dev/null +++ b/test/maverick/consensus/replay_file.go @@ -0,0 +1,338 @@ +package consensus + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + + dbm "github.com/tendermint/tm-db" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/store" + "github.com/tendermint/tendermint/types" +) + +const ( + // event bus subscriber + subscriber = "replay-file" +) + +//-------------------------------------------------------- +// replay messages interactively or all at once + +// replay the wal file +func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console bool) { + consensusState := newConsensusStateForReplay(config, csConfig) + + if err := consensusState.ReplayFile(csConfig.WalFile(), console); err != nil { + tmos.Exit(fmt.Sprintf("Error during consensus replay: %v", err)) + } +} + +// Replay msgs in file or start the console +func (cs *State) ReplayFile(file string, console bool) error { + + if cs.IsRunning() { + return errors.New("cs is already running, cannot replay") + } + if cs.wal != nil { + return errors.New("cs wal is open, cannot replay") + } + + cs.startForReplay() + + // ensure all new step events are regenerated as expected + + ctx := context.Background() + newStepSub, err := cs.eventBus.Subscribe(ctx, subscriber, types.EventQueryNewRoundStep) + if err != nil { + return fmt.Errorf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep) + } + defer func() { + if err := cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep); err != nil { + cs.Logger.Error("Error unsubscribing to event bus", "err", err) + } + }() + + // just open the file for reading, no need to use wal + fp, err := os.OpenFile(file, os.O_RDONLY, 0600) + if err != nil { + return err + } + + pb := newPlayback(file, fp, cs, cs.state.Copy()) + defer pb.fp.Close() + + var nextN int // apply N msgs in a row + var msg *TimedWALMessage + for { + if nextN == 0 && console { + nextN = pb.replayConsoleLoop() + } + + msg, err = pb.dec.Decode() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + + if err := pb.cs.readReplayMessage(msg, newStepSub); err != nil { + return err + } + + if nextN > 0 { + nextN-- + } + pb.count++ + } +} + +//------------------------------------------------ +// playback manager + +type playback struct { + cs *State + + fp *os.File + dec *WALDecoder + count int // how many lines/msgs into the file are we + + // replays can be reset to beginning + fileName string // so we can close/reopen the file + genesisState sm.State // so the replay session knows where to restart from +} + +func newPlayback(fileName string, fp *os.File, cs *State, genState sm.State) *playback { + return &playback{ + cs: cs, + fp: fp, + fileName: fileName, + genesisState: genState, + dec: NewWALDecoder(fp), + } +} + +// go back count steps by resetting the state and running (pb.count - count) steps +func (pb *playback) replayReset(count int, newStepSub types.Subscription) error { + if err := pb.cs.Stop(); err != nil { + return err + } + pb.cs.Wait() + + newCS := NewState(pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec, + pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool, map[int64]Misbehavior{}) + newCS.SetEventBus(pb.cs.eventBus) + newCS.startForReplay() + + if err := pb.fp.Close(); err != nil { + return err + } + fp, err := os.OpenFile(pb.fileName, os.O_RDONLY, 0600) + if err != nil { + return err + } + pb.fp = fp + pb.dec = NewWALDecoder(fp) + count = pb.count - count + fmt.Printf("Reseting from %d to %d\n", pb.count, count) + pb.count = 0 + pb.cs = newCS + var msg *TimedWALMessage + for i := 0; i < count; i++ { + msg, err = pb.dec.Decode() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + if err := pb.cs.readReplayMessage(msg, newStepSub); err != nil { + return err + } + pb.count++ + } + return nil +} + +func (cs *State) startForReplay() { + cs.Logger.Error("Replay commands are disabled until someone updates them and writes tests") + /* TODO:! + // since we replay tocks we just ignore ticks + go func() { + for { + select { + case <-cs.tickChan: + case <-cs.Quit: + return + } + } + }()*/ +} + +// console function for parsing input and running commands +func (pb *playback) replayConsoleLoop() int { + for { + fmt.Printf("> ") + bufReader := bufio.NewReader(os.Stdin) + line, more, err := bufReader.ReadLine() + if more { + tmos.Exit("input is too long") + } else if err != nil { + tmos.Exit(err.Error()) + } + + tokens := strings.Split(string(line), " ") + if len(tokens) == 0 { + continue + } + + switch tokens[0] { + case "next": + // "next" -> replay next message + // "next N" -> replay next N messages + + if len(tokens) == 1 { + return 0 + } + i, err := strconv.Atoi(tokens[1]) + if err != nil { + fmt.Println("next takes an integer argument") + } else { + return i + } + + case "back": + // "back" -> go back one message + // "back N" -> go back N messages + + // NOTE: "back" is not supported in the state machine design, + // so we restart and replay up to + + ctx := context.Background() + // ensure all new step events are regenerated as expected + + newStepSub, err := pb.cs.eventBus.Subscribe(ctx, subscriber, types.EventQueryNewRoundStep) + if err != nil { + tmos.Exit(fmt.Sprintf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep)) + } + defer func() { + if err := pb.cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep); err != nil { + pb.cs.Logger.Error("Error unsubscribing from eventBus", "err", err) + } + }() + + if len(tokens) == 1 { + if err := pb.replayReset(1, newStepSub); err != nil { + pb.cs.Logger.Error("Replay reset error", "err", err) + } + } else { + i, err := strconv.Atoi(tokens[1]) + if err != nil { + fmt.Println("back takes an integer argument") + } else if i > pb.count { + fmt.Printf("argument to back must not be larger than the current count (%d)\n", pb.count) + } else if err := pb.replayReset(i, newStepSub); err != nil { + pb.cs.Logger.Error("Replay reset error", "err", err) + } + } + + case "rs": + // "rs" -> print entire round state + // "rs short" -> print height/round/step + // "rs " -> print another field of the round state + + rs := pb.cs.RoundState + if len(tokens) == 1 { + fmt.Println(rs) + } else { + switch tokens[1] { + case "short": + fmt.Printf("%v/%v/%v\n", rs.Height, rs.Round, rs.Step) + case "validators": + fmt.Println(rs.Validators) + case "proposal": + fmt.Println(rs.Proposal) + case "proposal_block": + fmt.Printf("%v %v\n", rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort()) + case "locked_round": + fmt.Println(rs.LockedRound) + case "locked_block": + fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort()) + case "votes": + fmt.Println(rs.Votes.StringIndented(" ")) + + default: + fmt.Println("Unknown option", tokens[1]) + } + } + case "n": + fmt.Println(pb.count) + } + } +} + +//-------------------------------------------------------------------------------- + +// convenience for replay mode +func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig) *State { + dbType := dbm.BackendType(config.DBBackend) + // Get BlockStore + blockStoreDB, err := dbm.NewDB("blockstore", dbType, config.DBDir()) + if err != nil { + tmos.Exit(err.Error()) + } + blockStore := store.NewBlockStore(blockStoreDB) + + // Get State + stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) + if err != nil { + tmos.Exit(err.Error()) + } + stateStore := sm.NewStore(stateDB) + gdoc, err := sm.MakeGenesisDocFromFile(config.GenesisFile()) + if err != nil { + tmos.Exit(err.Error()) + } + state, err := sm.MakeGenesisState(gdoc) + if err != nil { + tmos.Exit(err.Error()) + } + + // Create proxyAppConn connection (consensus, mempool, query) + clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()) + proxyApp := proxy.NewAppConns(clientCreator) + err = proxyApp.Start() + if err != nil { + tmos.Exit(fmt.Sprintf("Error starting proxy app conns: %v", err)) + } + + eventBus := types.NewEventBus() + if err := eventBus.Start(); err != nil { + tmos.Exit(fmt.Sprintf("Failed to start event bus: %v", err)) + } + + handshaker := NewHandshaker(stateStore, state, blockStore, gdoc) + handshaker.SetEventBus(eventBus) + err = handshaker.Handshake(proxyApp) + if err != nil { + tmos.Exit(fmt.Sprintf("Error on handshake: %v", err)) + } + + mempool, evpool := emptyMempool{}, sm.EmptyEvidencePool{} + blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) + + consensusState := NewState(csConfig, state.Copy(), blockExec, + blockStore, mempool, evpool, map[int64]Misbehavior{}) + + consensusState.SetEventBus(eventBus) + return consensusState +} diff --git a/test/maverick/consensus/replay_stubs.go b/test/maverick/consensus/replay_stubs.go new file mode 100644 index 0000000000..08974a67e2 --- /dev/null +++ b/test/maverick/consensus/replay_stubs.go @@ -0,0 +1,90 @@ +package consensus + +import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/clist" + mempl "github.com/tendermint/tendermint/mempool" + tmstate "github.com/tendermint/tendermint/proto/tendermint/state" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------------------------------- + +type emptyMempool struct{} + +var _ mempl.Mempool = emptyMempool{} + +func (emptyMempool) Lock() {} +func (emptyMempool) Unlock() {} +func (emptyMempool) Size() int { return 0 } +func (emptyMempool) CheckTx(_ types.Tx, _ func(*abci.Response), _ mempl.TxInfo) error { + return nil +} +func (emptyMempool) ReapMaxBytesMaxGas(_, _ int64) types.Txs { return types.Txs{} } +func (emptyMempool) ReapMaxTxs(n int) types.Txs { return types.Txs{} } +func (emptyMempool) Update( + _ int64, + _ types.Txs, + _ []*abci.ResponseDeliverTx, + _ mempl.PreCheckFunc, + _ mempl.PostCheckFunc, +) error { + return nil +} +func (emptyMempool) Flush() {} +func (emptyMempool) FlushAppConn() error { return nil } +func (emptyMempool) TxsAvailable() <-chan struct{} { return make(chan struct{}) } +func (emptyMempool) EnableTxsAvailable() {} +func (emptyMempool) TxsBytes() int64 { return 0 } + +func (emptyMempool) TxsFront() *clist.CElement { return nil } +func (emptyMempool) TxsWaitChan() <-chan struct{} { return nil } + +func (emptyMempool) InitWAL() error { return nil } +func (emptyMempool) CloseWAL() {} + +//----------------------------------------------------------------------------- +// mockProxyApp uses ABCIResponses to give the right results. +// +// Useful because we don't want to call Commit() twice for the same block on +// the real app. + +func newMockProxyApp(appHash []byte, abciResponses *tmstate.ABCIResponses) proxy.AppConnConsensus { + clientCreator := proxy.NewLocalClientCreator(&mockProxyApp{ + appHash: appHash, + abciResponses: abciResponses, + }) + cli, _ := clientCreator.NewABCIClient() + err := cli.Start() + if err != nil { + panic(err) + } + return proxy.NewAppConnConsensus(cli) +} + +type mockProxyApp struct { + abci.BaseApplication + + appHash []byte + txCount int + abciResponses *tmstate.ABCIResponses +} + +func (mock *mockProxyApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + r := mock.abciResponses.DeliverTxs[mock.txCount] + mock.txCount++ + if r == nil { + return abci.ResponseDeliverTx{} + } + return *r +} + +func (mock *mockProxyApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { + mock.txCount = 0 + return *mock.abciResponses.EndBlock +} + +func (mock *mockProxyApp) Commit() abci.ResponseCommit { + return abci.ResponseCommit{Data: mock.appHash} +} diff --git a/test/maverick/consensus/state.go b/test/maverick/consensus/state.go new file mode 100644 index 0000000000..b12d21edf6 --- /dev/null +++ b/test/maverick/consensus/state.go @@ -0,0 +1,1976 @@ +package consensus + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "reflect" + "runtime/debug" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + + cfg "github.com/tendermint/tendermint/config" + cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/crypto" + tmevents "github.com/tendermint/tendermint/libs/events" + "github.com/tendermint/tendermint/libs/fail" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmmath "github.com/tendermint/tendermint/libs/math" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/libs/service" + "github.com/tendermint/tendermint/p2p" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// State handles execution of the consensus algorithm. +// It processes votes and proposals, and upon reaching agreement, +// commits blocks to the chain and executes them against the application. +// The internal state machine receives input from peers, the internal validator, and from a timer. +type State struct { + service.BaseService + + // config details + config *cfg.ConsensusConfig + privValidator types.PrivValidator // for signing votes + + // store blocks and commits + blockStore sm.BlockStore + + // create and execute blocks + blockExec *sm.BlockExecutor + + // notify us if txs are available + txNotifier txNotifier + + // add evidence to the pool + // when it's detected + evpool evidencePool + + // internal state + mtx sync.RWMutex + cstypes.RoundState + state sm.State // State until height-1. + + // state changes may be triggered by: msgs from peers, + // msgs from ourself, or by timeouts + peerMsgQueue chan msgInfo + internalMsgQueue chan msgInfo + timeoutTicker TimeoutTicker + // privValidator pubkey, memoized for the duration of one block + // to avoid extra requests to HSM + privValidatorPubKey crypto.PubKey + + // information about about added votes and block parts are written on this channel + // so statistics can be computed by reactor + statsMsgQueue chan msgInfo + + // we use eventBus to trigger msg broadcasts in the reactor, + // and to notify external subscribers, eg. through a websocket + eventBus *types.EventBus + + // a Write-Ahead Log ensures we can recover from any kind of crash + // and helps us avoid signing conflicting votes + wal WAL + replayMode bool // so we don't log signing errors during replay + doWALCatchup bool // determines if we even try to do the catchup + + // for tests where we want to limit the number of transitions the state makes + nSteps int + + // some functions can be overwritten for testing + decideProposal func(height int64, round int32) + + // closed when we finish shutting down + done chan struct{} + + // synchronous pubsub between consensus state and reactor. + // state only emits EventNewRoundStep and EventVote + evsw tmevents.EventSwitch + + // for reporting metrics + metrics *Metrics + + // misbehaviors mapped for each height (can't have more than one misbehavior per height) + misbehaviors map[int64]Misbehavior + + // the switch is passed to the state so that maveick misbehaviors can directly control which + // information they send to which nodes + sw *p2p.Switch +} + +// StateOption sets an optional parameter on the State. +type StateOption func(*State) + +// NewState returns a new State. +func NewState( + config *cfg.ConsensusConfig, + state sm.State, + blockExec *sm.BlockExecutor, + blockStore sm.BlockStore, + txNotifier txNotifier, + evpool evidencePool, + misbehaviors map[int64]Misbehavior, + options ...StateOption, +) *State { + cs := &State{ + config: config, + blockExec: blockExec, + blockStore: blockStore, + txNotifier: txNotifier, + peerMsgQueue: make(chan msgInfo, msgQueueSize), + internalMsgQueue: make(chan msgInfo, msgQueueSize), + timeoutTicker: NewTimeoutTicker(), + statsMsgQueue: make(chan msgInfo, msgQueueSize), + done: make(chan struct{}), + doWALCatchup: true, + wal: nilWAL{}, + evpool: evpool, + evsw: tmevents.NewEventSwitch(), + metrics: NopMetrics(), + misbehaviors: misbehaviors, + } + // set function defaults (may be overwritten before calling Start) + cs.decideProposal = cs.defaultDecideProposal + + // We have no votes, so reconstruct LastCommit from SeenCommit. + if state.LastBlockHeight > 0 { + cs.reconstructLastCommit(state) + } + + cs.updateToState(state) + + // Don't call scheduleRound0 yet. + // We do that upon Start(). + + cs.BaseService = *service.NewBaseService(nil, "State", cs) + for _, option := range options { + option(cs) + } + return cs +} + +// I know this is not great but the maverick consensus state needs access to the peers +func (cs *State) SetSwitch(sw *p2p.Switch) { + cs.sw = sw +} + +// state transitions on complete-proposal, 2/3-any, 2/3-one +func (cs *State) handleMsg(mi msgInfo) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + + var ( + added bool + err error + ) + msg, peerID := mi.Msg, mi.PeerID + switch msg := msg.(type) { + case *ProposalMessage: + // will not cause transition. + // once proposal is set, we can receive block parts + // err = cs.setProposal(msg.Proposal) + if b, ok := cs.misbehaviors[cs.Height]; ok { + err = b.ReceiveProposal(cs, msg.Proposal) + } else { + err = defaultReceiveProposal(cs, msg.Proposal) + } + case *BlockPartMessage: + // if the proposal is complete, we'll enterPrevote or tryFinalizeCommit + added, err = cs.addProposalBlockPart(msg, peerID) + if added { + cs.statsMsgQueue <- mi + } + + if err != nil && msg.Round != cs.Round { + cs.Logger.Debug( + "Received block part from wrong round", + "height", + cs.Height, + "csRound", + cs.Round, + "blockRound", + msg.Round) + err = nil + } + case *VoteMessage: + // attempt to add the vote and dupeout the validator if its a duplicate signature + // if the vote gives us a 2/3-any or 2/3-one, we transition + added, err = cs.tryAddVote(msg.Vote, peerID) + if added { + cs.statsMsgQueue <- mi + } + + // if err == ErrAddingVote { + // TODO: punish peer + // We probably don't want to stop the peer here. The vote does not + // necessarily comes from a malicious peer but can be just broadcasted by + // a typical peer. + // https://github.com/tendermint/tendermint/issues/1281 + // } + + // NOTE: the vote is broadcast to peers by the reactor listening + // for vote events + + // TODO: If rs.Height == vote.Height && rs.Round < vote.Round, + // the peer is sending us CatchupCommit precommits. + // We could make note of this and help filter in broadcastHasVoteMessage(). + default: + cs.Logger.Error("Unknown msg type", "type", reflect.TypeOf(msg)) + return + } + + if err != nil { + cs.Logger.Error("Error with msg", "height", cs.Height, "round", cs.Round, + "peer", peerID, "err", err, "msg", msg) + } +} + +// Enter (CreateEmptyBlocks): from enterNewRound(height,round) +// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): +// after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval +// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool +func (cs *State) enterPropose(height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) { + logger.Debug(fmt.Sprintf( + "enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + round, + cs.Height, + cs.Round, + cs.Step)) + return + } + logger.Info(fmt.Sprintf("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPropose: + cs.updateRoundStep(round, cstypes.RoundStepPropose) + cs.newStep() + + // If we have the whole proposal + POL, then goto Prevote now. + // else, we'll enterPrevote when the rest of the proposal is received (in AddProposalBlockPart), + // or else after timeoutPropose + if cs.isProposalComplete() { + cs.enterPrevote(height, cs.Round) + } + }() + + if b, ok := cs.misbehaviors[cs.Height]; ok { + b.EnterPropose(cs, height, round) + } else { + defaultEnterPropose(cs, height, round) + } +} + +// Enter: `timeoutPropose` after entering Propose. +// Enter: proposal block and POL is ready. +// Prevote for LockedBlock if we're locked, or ProposalBlock if valid. +// Otherwise vote nil. +func (cs *State) enterPrevote(height int64, round int32) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevote <= cs.Step) { + cs.Logger.Debug(fmt.Sprintf( + "enterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + round, + cs.Height, + cs.Round, + cs.Step)) + return + } + + defer func() { + // Done enterPrevote: + cs.updateRoundStep(round, cstypes.RoundStepPrevote) + cs.newStep() + }() + + cs.Logger.Info(fmt.Sprintf("enterPrevote(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + // Sign and broadcast vote as necessary + if b, ok := cs.misbehaviors[cs.Height]; ok { + b.EnterPrevote(cs, height, round) + } else { + defaultEnterPrevote(cs, height, round) + } + + // Once `addVote` hits any +2/3 prevotes, we will go to PrevoteWait + // (so we have more time to try and collect +2/3 prevotes for a single block) +} + +// Enter: `timeoutPrevote` after any +2/3 prevotes. +// Enter: `timeoutPrecommit` after any +2/3 precommits. +// Enter: +2/3 precomits for block or nil. +// Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round) +// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, +// else, precommit nil otherwise. +func (cs *State) enterPrecommit(height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommit <= cs.Step) { + logger.Debug(fmt.Sprintf( + "enterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + round, + cs.Height, + cs.Round, + cs.Step)) + return + } + + logger.Info(fmt.Sprintf("enterPrecommit(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrecommit: + cs.updateRoundStep(round, cstypes.RoundStepPrecommit) + cs.newStep() + }() + + if b, ok := cs.misbehaviors[cs.Height]; ok { + b.EnterPrecommit(cs, height, round) + } else { + defaultEnterPrecommit(cs, height, round) + } + +} + +func (cs *State) addVote( + vote *types.Vote, + peerID p2p.ID) (added bool, err error) { + cs.Logger.Debug( + "addVote", + "voteHeight", + vote.Height, + "voteType", + vote.Type, + "valIndex", + vote.ValidatorIndex, + "csHeight", + cs.Height, + ) + + // A precommit for the previous height? + // These come in while we wait timeoutCommit + if vote.Height+1 == cs.Height && vote.Type == tmproto.PrecommitType { + if cs.Step != cstypes.RoundStepNewHeight { + // Late precommit at prior height is ignored + cs.Logger.Debug("Precommit vote came in after commit timeout and has been ignored", "vote", vote) + return + } + added, err = cs.LastCommit.AddVote(vote) + if !added { + return + } + + cs.Logger.Info(fmt.Sprintf("Added to lastPrecommits: %v", cs.LastCommit.StringShort())) + _ = cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote}) + cs.evsw.FireEvent(types.EventVote, vote) + + // if we can skip timeoutCommit and have all the votes now, + if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { + // go straight to new round (skip timeout commit) + // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) + cs.enterNewRound(cs.Height, 0) + } + + return + } + + // Height mismatch is ignored. + // Not necessarily a bad peer, but not favourable behaviour. + if vote.Height != cs.Height { + cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "peerID", peerID) + return + } + + added, err = cs.Votes.AddVote(vote, peerID) + if !added { + // Either duplicate, or error upon cs.Votes.AddByIndex() + return + } + + _ = cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote}) + cs.evsw.FireEvent(types.EventVote, vote) + + switch vote.Type { + case tmproto.PrevoteType: + if b, ok := cs.misbehaviors[cs.Height]; ok { + b.ReceivePrevote(cs, vote) + } else { + defaultReceivePrevote(cs, vote) + } + + case tmproto.PrecommitType: + if b, ok := cs.misbehaviors[cs.Height]; ok { + b.ReceivePrecommit(cs, vote) + } + defaultReceivePrecommit(cs, vote) + + default: + panic(fmt.Sprintf("Unexpected vote type %v", vote.Type)) + } + + return added, err +} + +//----------------------------------------------------------------------------- +// Errors + +var ( + ErrInvalidProposalSignature = errors.New("error invalid proposal signature") + ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round") + ErrAddingVote = errors.New("error adding vote") + ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key") + + errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors") +) + +//----------------------------------------------------------------------------- + +var ( + msgQueueSize = 1000 +) + +// msgs from the reactor which may update the state +type msgInfo struct { + Msg Message `json:"msg"` + PeerID p2p.ID `json:"peer_key"` +} + +// internally generated messages which may update the state +type timeoutInfo struct { + Duration time.Duration `json:"duration"` + Height int64 `json:"height"` + Round int32 `json:"round"` + Step cstypes.RoundStepType `json:"step"` +} + +func (ti *timeoutInfo) String() string { + return fmt.Sprintf("%v ; %d/%d %v", ti.Duration, ti.Height, ti.Round, ti.Step) +} + +// interface to the mempool +type txNotifier interface { + TxsAvailable() <-chan struct{} +} + +// interface to the evidence pool +type evidencePool interface { + // Adds consensus based evidence to the evidence pool where time is the time + // of the block where the offense occurred and the validator set is the current one. + AddEvidenceFromConsensus(types.Evidence, time.Time, *types.ValidatorSet) error +} + +//---------------------------------------- +// Public interface + +// SetLogger implements Service. +func (cs *State) SetLogger(l log.Logger) { + cs.BaseService.Logger = l + cs.timeoutTicker.SetLogger(l) +} + +// SetEventBus sets event bus. +func (cs *State) SetEventBus(b *types.EventBus) { + cs.eventBus = b + cs.blockExec.SetEventBus(b) +} + +// StateMetrics sets the metrics. +func StateMetrics(metrics *Metrics) StateOption { + return func(cs *State) { cs.metrics = metrics } +} + +// String returns a string. +func (cs *State) String() string { + // better not to access shared variables + return "ConsensusState" +} + +// GetState returns a copy of the chain state. +func (cs *State) GetState() sm.State { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return cs.state.Copy() +} + +// GetLastHeight returns the last height committed. +// If there were no blocks, returns 0. +func (cs *State) GetLastHeight() int64 { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return cs.RoundState.Height - 1 +} + +// GetRoundState returns a shallow copy of the internal consensus state. +func (cs *State) GetRoundState() *cstypes.RoundState { + cs.mtx.RLock() + rs := cs.RoundState // copy + cs.mtx.RUnlock() + return &rs +} + +// GetRoundStateJSON returns a json of RoundState. +func (cs *State) GetRoundStateJSON() ([]byte, error) { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return tmjson.Marshal(cs.RoundState) +} + +// GetRoundStateSimpleJSON returns a json of RoundStateSimple +func (cs *State) GetRoundStateSimpleJSON() ([]byte, error) { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return tmjson.Marshal(cs.RoundState.RoundStateSimple()) +} + +// GetValidators returns a copy of the current validators. +func (cs *State) GetValidators() (int64, []*types.Validator) { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + return cs.state.LastBlockHeight, cs.state.Validators.Copy().Validators +} + +// SetPrivValidator sets the private validator account for signing votes. It +// immediately requests pubkey and caches it. +func (cs *State) SetPrivValidator(priv types.PrivValidator) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + + cs.privValidator = priv + + if err := cs.updatePrivValidatorPubKey(); err != nil { + cs.Logger.Error("Can't get private validator pubkey", "err", err) + } +} + +// SetTimeoutTicker sets the local timer. It may be useful to overwrite for testing. +func (cs *State) SetTimeoutTicker(timeoutTicker TimeoutTicker) { + cs.mtx.Lock() + cs.timeoutTicker = timeoutTicker + cs.mtx.Unlock() +} + +// LoadCommit loads the commit for a given height. +func (cs *State) LoadCommit(height int64) *types.Commit { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + if height == cs.blockStore.Height() { + return cs.blockStore.LoadSeenCommit(height) + } + return cs.blockStore.LoadBlockCommit(height) +} + +// OnStart loads the latest state via the WAL, and starts the timeout and +// receive routines. +func (cs *State) OnStart() error { + // We may set the WAL in testing before calling Start, so only OpenWAL if its + // still the nilWAL. + if _, ok := cs.wal.(nilWAL); ok { + if err := cs.loadWalFile(); err != nil { + return err + } + } + + // We may have lost some votes if the process crashed reload from consensus + // log to catchup. + if cs.doWALCatchup { + repairAttempted := false + LOOP: + for { + err := cs.catchupReplay(cs.Height) + switch { + case err == nil: + break LOOP + case !IsDataCorruptionError(err): + cs.Logger.Error("Error on catchup replay. Proceeding to start State anyway", "err", err) + break LOOP + case repairAttempted: + return err + } + + cs.Logger.Info("WAL file is corrupted. Attempting repair", "err", err) + + // 1) prep work + if err := cs.wal.Stop(); err != nil { + return err + } + repairAttempted = true + + // 2) backup original WAL file + corruptedFile := fmt.Sprintf("%s.CORRUPTED", cs.config.WalFile()) + if err := tmos.CopyFile(cs.config.WalFile(), corruptedFile); err != nil { + return err + } + cs.Logger.Info("Backed up WAL file", "src", cs.config.WalFile(), "dst", corruptedFile) + + // 3) try to repair (WAL file will be overwritten!) + if err := repairWalFile(corruptedFile, cs.config.WalFile()); err != nil { + cs.Logger.Error("Repair failed", "err", err) + return err + } + cs.Logger.Info("Successful repair") + + // reload WAL file + if err := cs.loadWalFile(); err != nil { + return err + } + } + } + + if err := cs.evsw.Start(); err != nil { + return err + } + + // we need the timeoutRoutine for replay so + // we don't block on the tick chan. + // NOTE: we will get a build up of garbage go routines + // firing on the tockChan until the receiveRoutine is started + // to deal with them (by that point, at most one will be valid) + if err := cs.timeoutTicker.Start(); err != nil { + return err + } + + // Double Signing Risk Reduction + if err := cs.checkDoubleSigningRisk(cs.Height); err != nil { + return err + } + + // now start the receiveRoutine + go cs.receiveRoutine(0) + + // schedule the first round! + // use GetRoundState so we don't race the receiveRoutine for access + cs.scheduleRound0(cs.GetRoundState()) + + return nil +} + +// loadWalFile loads WAL data from file. It overwrites cs.wal. +func (cs *State) loadWalFile() error { + wal, err := cs.OpenWAL(cs.config.WalFile()) + if err != nil { + cs.Logger.Error("Error loading State wal", "err", err) + return err + } + cs.wal = wal + return nil +} + +// OnStop implements service.Service. +func (cs *State) OnStop() { + if err := cs.evsw.Stop(); err != nil { + cs.Logger.Error("error trying to stop eventSwitch", "error", err) + } + if err := cs.timeoutTicker.Stop(); err != nil { + cs.Logger.Error("error trying to stop timeoutTicket", "error", err) + } + // WAL is stopped in receiveRoutine. +} + +// Wait waits for the the main routine to return. +// NOTE: be sure to Stop() the event switch and drain +// any event channels or this may deadlock +func (cs *State) Wait() { + <-cs.done +} + +// OpenWAL opens a file to log all consensus messages and timeouts for +// deterministic accountability. +func (cs *State) OpenWAL(walFile string) (WAL, error) { + wal, err := NewWAL(walFile) + if err != nil { + cs.Logger.Error("Failed to open WAL", "file", walFile, "err", err) + return nil, err + } + wal.SetLogger(cs.Logger.With("wal", walFile)) + if err := wal.Start(); err != nil { + cs.Logger.Error("Failed to start WAL", "err", err) + return nil, err + } + return wal, nil +} + +//------------------------------------------------------------ +// Public interface for passing messages into the consensus state, possibly causing a state transition. +// If peerID == "", the msg is considered internal. +// Messages are added to the appropriate queue (peer or internal). +// If the queue is full, the function may block. +// TODO: should these return anything or let callers just use events? + +// AddVote inputs a vote. +func (cs *State) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { + if peerID == "" { + cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} + } else { + cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID} + } + + // TODO: wait for event?! + return false, nil +} + +// SetProposal inputs a proposal. +func (cs *State) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { + + if peerID == "" { + cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} + } else { + cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID} + } + + // TODO: wait for event?! + return nil +} + +// AddProposalBlockPart inputs a part of the proposal block. +func (cs *State) AddProposalBlockPart(height int64, round int32, part *types.Part, peerID p2p.ID) error { + + if peerID == "" { + cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} + } else { + cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID} + } + + // TODO: wait for event?! + return nil +} + +// SetProposalAndBlock inputs the proposal and all block parts. +func (cs *State) SetProposalAndBlock( + proposal *types.Proposal, + block *types.Block, + parts *types.PartSet, + peerID p2p.ID, +) error { + if err := cs.SetProposal(proposal, peerID); err != nil { + return err + } + for i := 0; i < int(parts.Total()); i++ { + part := parts.GetPart(i) + if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil { + return err + } + } + return nil +} + +//------------------------------------------------------------ +// internal functions for managing the state + +func (cs *State) updateHeight(height int64) { + cs.metrics.Height.Set(float64(height)) + cs.Height = height +} + +func (cs *State) updateRoundStep(round int32, step cstypes.RoundStepType) { + cs.Round = round + cs.Step = step +} + +// enterNewRound(height, 0) at cs.StartTime. +func (cs *State) scheduleRound0(rs *cstypes.RoundState) { + // cs.Logger.Info("scheduleRound0", "now", tmtime.Now(), "startTime", cs.StartTime) + sleepDuration := rs.StartTime.Sub(tmtime.Now()) + cs.scheduleTimeout(sleepDuration, rs.Height, 0, cstypes.RoundStepNewHeight) +} + +// Attempt to schedule a timeout (by sending timeoutInfo on the tickChan) +func (cs *State) scheduleTimeout(duration time.Duration, height int64, round int32, step cstypes.RoundStepType) { + cs.timeoutTicker.ScheduleTimeout(timeoutInfo{duration, height, round, step}) +} + +// send a msg into the receiveRoutine regarding our own proposal, block part, or vote +func (cs *State) sendInternalMessage(mi msgInfo) { + select { + case cs.internalMsgQueue <- mi: + default: + // NOTE: using the go-routine means our votes can + // be processed out of order. + // TODO: use CList here for strict determinism and + // attempt push to internalMsgQueue in receiveRoutine + cs.Logger.Info("Internal msg queue is full. Using a go-routine") + go func() { cs.internalMsgQueue <- mi }() + } +} + +// Reconstruct LastCommit from SeenCommit, which we saved along with the block, +// (which happens even before saving the state) +func (cs *State) reconstructLastCommit(state sm.State) { + seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) + if seenCommit == nil { + panic(fmt.Sprintf("Failed to reconstruct LastCommit: seen commit for height %v not found", + state.LastBlockHeight)) + } + + lastPrecommits := types.CommitToVoteSet(state.ChainID, seenCommit, state.LastValidators) + if !lastPrecommits.HasTwoThirdsMajority() { + panic("Failed to reconstruct LastCommit: Does not have +2/3 maj") + } + + cs.LastCommit = lastPrecommits +} + +// Updates State and increments height to match that of state. +// The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight. +func (cs *State) updateToState(state sm.State) { + if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { + panic(fmt.Sprintf("updateToState() expected state height of %v but found %v", + cs.Height, state.LastBlockHeight)) + } + if !cs.state.IsEmpty() { + if cs.state.LastBlockHeight > 0 && cs.state.LastBlockHeight+1 != cs.Height { + // This might happen when someone else is mutating cs.state. + // Someone forgot to pass in state.Copy() somewhere?! + panic(fmt.Sprintf("Inconsistent cs.state.LastBlockHeight+1 %v vs cs.Height %v", + cs.state.LastBlockHeight+1, cs.Height)) + } + if cs.state.LastBlockHeight > 0 && cs.Height == cs.state.InitialHeight { + panic(fmt.Sprintf("Inconsistent cs.state.LastBlockHeight %v, expected 0 for initial height %v", + cs.state.LastBlockHeight, cs.state.InitialHeight)) + } + + // If state isn't further out than cs.state, just ignore. + // This happens when SwitchToConsensus() is called in the reactor. + // We don't want to reset e.g. the Votes, but we still want to + // signal the new round step, because other services (eg. txNotifier) + // depend on having an up-to-date peer state! + if state.LastBlockHeight <= cs.state.LastBlockHeight { + cs.Logger.Info( + "Ignoring updateToState()", + "newHeight", + state.LastBlockHeight+1, + "oldHeight", + cs.state.LastBlockHeight+1) + cs.newStep() + return + } + } + + // Reset fields based on state. + validators := state.Validators + + switch { + case state.LastBlockHeight == 0: // Very first commit should be empty. + cs.LastCommit = (*types.VoteSet)(nil) + case cs.CommitRound > -1 && cs.Votes != nil: // Otherwise, use cs.Votes + if !cs.Votes.Precommits(cs.CommitRound).HasTwoThirdsMajority() { + panic(fmt.Sprintf("Wanted to form a Commit, but Precommits (H/R: %d/%d) didn't have 2/3+: %v", + state.LastBlockHeight, + cs.CommitRound, + cs.Votes.Precommits(cs.CommitRound))) + } + cs.LastCommit = cs.Votes.Precommits(cs.CommitRound) + case cs.LastCommit == nil: + // NOTE: when Tendermint starts, it has no votes. reconstructLastCommit + // must be called to reconstruct LastCommit from SeenCommit. + panic(fmt.Sprintf("LastCommit cannot be empty after initial block (H:%d)", + state.LastBlockHeight+1, + )) + } + + // Next desired block height + height := state.LastBlockHeight + 1 + if height == 1 { + height = state.InitialHeight + } + + // RoundState fields + cs.updateHeight(height) + cs.updateRoundStep(0, cstypes.RoundStepNewHeight) + if cs.CommitTime.IsZero() { + // "Now" makes it easier to sync up dev nodes. + // We add timeoutCommit to allow transactions + // to be gathered for the first block. + // And alternative solution that relies on clocks: + // cs.StartTime = state.LastBlockTime.Add(timeoutCommit) + cs.StartTime = cs.config.Commit(tmtime.Now()) + } else { + cs.StartTime = cs.config.Commit(cs.CommitTime) + } + + cs.Validators = validators + cs.Proposal = nil + cs.ProposalBlock = nil + cs.ProposalBlockParts = nil + cs.LockedRound = -1 + cs.LockedBlock = nil + cs.LockedBlockParts = nil + cs.ValidRound = -1 + cs.ValidBlock = nil + cs.ValidBlockParts = nil + cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators) + cs.CommitRound = -1 + cs.LastValidators = state.LastValidators + cs.TriggeredTimeoutPrecommit = false + + cs.state = state + + // Finally, broadcast RoundState + cs.newStep() +} + +func (cs *State) newStep() { + rs := cs.RoundStateEvent() + if err := cs.wal.Write(rs); err != nil { + cs.Logger.Error("Error writing to wal", "err", err) + } + cs.nSteps++ + // newStep is called by updateToState in NewState before the eventBus is set! + if cs.eventBus != nil { + if err := cs.eventBus.PublishEventNewRoundStep(rs); err != nil { + cs.Logger.Error("Error publishing new round step", "err", err) + } + cs.evsw.FireEvent(types.EventNewRoundStep, &cs.RoundState) + } +} + +//----------------------------------------- +// the main go routines + +// receiveRoutine handles messages which may cause state transitions. +// it's argument (n) is the number of messages to process before exiting - use 0 to run forever +// It keeps the RoundState and is the only thing that updates it. +// Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. +// State must be locked before any internal state is updated. +func (cs *State) receiveRoutine(maxSteps int) { + onExit := func(cs *State) { + // NOTE: the internalMsgQueue may have signed messages from our + // priv_val that haven't hit the WAL, but its ok because + // priv_val tracks LastSig + + // close wal now that we're done writing to it + if err := cs.wal.Stop(); err != nil { + cs.Logger.Error("error trying to stop wal", "error", err) + } + cs.wal.Wait() + + close(cs.done) + } + + defer func() { + if r := recover(); r != nil { + cs.Logger.Error("CONSENSUS FAILURE!!!", "err", r, "stack", string(debug.Stack())) + // stop gracefully + // + // NOTE: We most probably shouldn't be running any further when there is + // some unexpected panic. Some unknown error happened, and so we don't + // know if that will result in the validator signing an invalid thing. It + // might be worthwhile to explore a mechanism for manual resuming via + // some console or secure RPC system, but for now, halting the chain upon + // unexpected consensus bugs sounds like the better option. + onExit(cs) + } + }() + + for { + if maxSteps > 0 { + if cs.nSteps >= maxSteps { + cs.Logger.Info("reached max steps. exiting receive routine") + cs.nSteps = 0 + return + } + } + rs := cs.RoundState + var mi msgInfo + + select { + case <-cs.txNotifier.TxsAvailable(): + cs.handleTxsAvailable() + case mi = <-cs.peerMsgQueue: + if err := cs.wal.Write(mi); err != nil { + cs.Logger.Error("Error writing to wal", "err", err) + } + // handles proposals, block parts, votes + // may generate internal events (votes, complete proposals, 2/3 majorities) + cs.handleMsg(mi) + case mi = <-cs.internalMsgQueue: + err := cs.wal.WriteSync(mi) // NOTE: fsync + if err != nil { + panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node", mi, err)) + } + + if _, ok := mi.Msg.(*VoteMessage); ok { + // we actually want to simulate failing during + // the previous WriteSync, but this isn't easy to do. + // Equivalent would be to fail here and manually remove + // some bytes from the end of the wal. + fail.Fail() // XXX + } + + // handles proposals, block parts, votes + cs.handleMsg(mi) + case ti := <-cs.timeoutTicker.Chan(): // tockChan: + if err := cs.wal.Write(ti); err != nil { + cs.Logger.Error("Error writing to wal", "err", err) + } + // if the timeout is relevant to the rs + // go to the next step + cs.handleTimeout(ti, rs) + case <-cs.Quit(): + onExit(cs) + return + } + } +} + +func (cs *State) handleTimeout(ti timeoutInfo, rs cstypes.RoundState) { + cs.Logger.Debug("Received tock", "timeout", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + + // timeouts must be for current height, round, step + if ti.Height != rs.Height || ti.Round < rs.Round || (ti.Round == rs.Round && ti.Step < rs.Step) { + cs.Logger.Debug("Ignoring tock because we're ahead", "height", rs.Height, "round", rs.Round, "step", rs.Step) + return + } + + // the timeout will now cause a state transition + cs.mtx.Lock() + defer cs.mtx.Unlock() + + switch ti.Step { + case cstypes.RoundStepNewHeight: + // NewRound event fired from enterNewRound. + // XXX: should we fire timeout here (for timeout commit)? + cs.enterNewRound(ti.Height, 0) + case cstypes.RoundStepNewRound: + cs.enterPropose(ti.Height, 0) + case cstypes.RoundStepPropose: + if err := cs.eventBus.PublishEventTimeoutPropose(cs.RoundStateEvent()); err != nil { + cs.Logger.Error("Error publishing timeout propose", "err", err) + } + cs.enterPrevote(ti.Height, ti.Round) + case cstypes.RoundStepPrevoteWait: + if err := cs.eventBus.PublishEventTimeoutWait(cs.RoundStateEvent()); err != nil { + cs.Logger.Error("Error publishing timeout wait", "err", err) + } + cs.enterPrecommit(ti.Height, ti.Round) + case cstypes.RoundStepPrecommitWait: + if err := cs.eventBus.PublishEventTimeoutWait(cs.RoundStateEvent()); err != nil { + cs.Logger.Error("Error publishing timeout wait", "err", err) + } + cs.enterPrecommit(ti.Height, ti.Round) + cs.enterNewRound(ti.Height, ti.Round+1) + default: + panic(fmt.Sprintf("Invalid timeout step: %v", ti.Step)) + } + +} + +func (cs *State) handleTxsAvailable() { + cs.mtx.Lock() + defer cs.mtx.Unlock() + + // We only need to do this for round 0. + if cs.Round != 0 { + return + } + + switch cs.Step { + case cstypes.RoundStepNewHeight: // timeoutCommit phase + if cs.needProofBlock(cs.Height) { + // enterPropose will be called by enterNewRound + return + } + + // +1ms to ensure RoundStepNewRound timeout always happens after RoundStepNewHeight + timeoutCommit := cs.StartTime.Sub(tmtime.Now()) + 1*time.Millisecond + cs.scheduleTimeout(timeoutCommit, cs.Height, 0, cstypes.RoundStepNewRound) + case cstypes.RoundStepNewRound: // after timeoutCommit + cs.enterPropose(cs.Height, 0) + } +} + +//----------------------------------------------------------------------------- +// State functions +// Used internally by handleTimeout and handleMsg to make state transitions + +// Enter: `timeoutNewHeight` by startTime (commitTime+timeoutCommit), +// or, if SkipTimeoutCommit==true, after receiving all precommits from (height,round-1) +// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) +// Enter: +2/3 precommits for nil at (height,round-1) +// Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round) +// NOTE: cs.StartTime was already set for height. +func (cs *State) enterNewRound(height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != cstypes.RoundStepNewHeight) { + logger.Debug(fmt.Sprintf( + "enterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + round, + cs.Height, + cs.Round, + cs.Step)) + return + } + + if now := tmtime.Now(); cs.StartTime.After(now) { + logger.Info("Need to set a buffer and log message here for sanity.", "startTime", cs.StartTime, "now", now) + } + + logger.Info(fmt.Sprintf("enterNewRound(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + // Increment validators if necessary + validators := cs.Validators + if cs.Round < round { + validators = validators.Copy() + validators.IncrementProposerPriority(tmmath.SafeSubInt32(round, cs.Round)) + } + + // Setup new round + // we don't fire newStep for this step, + // but we fire an event, so update the round step first + cs.updateRoundStep(round, cstypes.RoundStepNewRound) + cs.Validators = validators + if round == 0 { + // We've already reset these upon new height, + // and meanwhile we might have received a proposal + // for round 0. + } else { + logger.Info("Resetting Proposal info") + cs.Proposal = nil + cs.ProposalBlock = nil + cs.ProposalBlockParts = nil + } + cs.Votes.SetRound(tmmath.SafeAddInt32(round, 1)) // also track next round (round+1) to allow round-skipping + cs.TriggeredTimeoutPrecommit = false + + if err := cs.eventBus.PublishEventNewRound(cs.NewRoundEvent()); err != nil { + cs.Logger.Error("Error publishing new round", "err", err) + } + cs.metrics.Rounds.Set(float64(round)) + + // Wait for txs to be available in the mempool + // before we enterPropose in round 0. If the last block changed the app hash, + // we may need an empty "proof" block, and enterPropose immediately. + waitForTxs := cs.config.WaitForTxs() && round == 0 && !cs.needProofBlock(height) + if waitForTxs { + if cs.config.CreateEmptyBlocksInterval > 0 { + cs.scheduleTimeout(cs.config.CreateEmptyBlocksInterval, height, round, + cstypes.RoundStepNewRound) + } + } else { + cs.enterPropose(height, round) + } +} + +// needProofBlock returns true on the first height (so the genesis app hash is signed right away) +// and where the last block (height-1) caused the app hash to change +func (cs *State) needProofBlock(height int64) bool { + if height == cs.state.InitialHeight { + return true + } + + lastBlockMeta := cs.blockStore.LoadBlockMeta(height - 1) + if lastBlockMeta == nil { + panic(fmt.Sprintf("needProofBlock: last block meta for height %d not found", height-1)) + } + return !bytes.Equal(cs.state.AppHash, lastBlockMeta.Header.AppHash) +} + +func (cs *State) isProposer(address []byte) bool { + return bytes.Equal(cs.Validators.GetProposer().Address, address) +} + +func (cs *State) defaultDecideProposal(height int64, round int32) { + var block *types.Block + var blockParts *types.PartSet + + // Decide on block + if cs.ValidBlock != nil { + // If there is valid block, choose that. + block, blockParts = cs.ValidBlock, cs.ValidBlockParts + } else { + // Create a new proposal block from state/txs from the mempool. + block, blockParts = cs.createProposalBlock() + if block == nil { + return + } + } + + // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, + // and the privValidator will refuse to sign anything. + if err := cs.wal.FlushAndSync(); err != nil { + cs.Logger.Error("Error flushing to disk") + } + + // Make proposal + propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()} + proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID) + p := proposal.ToProto() + if err := cs.privValidator.SignProposal(cs.state.ChainID, p); err == nil { + proposal.Signature = p.Signature + + // send proposal and block parts on internal msg queue + cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) + for i := 0; i < int(blockParts.Total()); i++ { + part := blockParts.GetPart(i) + cs.sendInternalMessage(msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""}) + } + cs.Logger.Info("Signed proposal", "height", height, "round", round, "proposal", proposal) + cs.Logger.Debug(fmt.Sprintf("Signed proposal block: %v", block)) + } else if !cs.replayMode { + cs.Logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err) + } +} + +// Returns true if the proposal block is complete && +// (if POLRound was proposed, we have +2/3 prevotes from there). +func (cs *State) isProposalComplete() bool { + if cs.Proposal == nil || cs.ProposalBlock == nil { + return false + } + // we have the proposal. if there's a POLRound, + // make sure we have the prevotes from it too + if cs.Proposal.POLRound < 0 { + return true + } + // if this is false the proposer is lying or we haven't received the POL yet + return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority() + +} + +// Create the next block to propose and return it. Returns nil block upon error. +// +// We really only need to return the parts, but the block is returned for +// convenience so we can log the proposal block. +// +// NOTE: keep it side-effect free for clarity. +// CONTRACT: cs.privValidator is not nil. +func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.PartSet) { + if cs.privValidator == nil { + panic("entered createProposalBlock with privValidator being nil") + } + + var commit *types.Commit + switch { + case cs.Height == cs.state.InitialHeight: + // We're creating a proposal for the first block. + // The commit is empty, but not nil. + commit = types.NewCommit(0, 0, types.BlockID{}, nil) + case cs.LastCommit.HasTwoThirdsMajority(): + // Make the commit from LastCommit + commit = cs.LastCommit.MakeCommit() + default: // This shouldn't happen. + cs.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") + return + } + + if cs.privValidatorPubKey == nil { + // If this node is a validator & proposer in the current round, it will + // miss the opportunity to create a block. + cs.Logger.Error(fmt.Sprintf("enterPropose: %v", errPubKeyIsNotSet)) + return + } + proposerAddr := cs.privValidatorPubKey.Address() + + return cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr) +} + +// Enter: any +2/3 prevotes at next round. +func (cs *State) enterPrevoteWait(height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) { + logger.Debug(fmt.Sprintf( + "enterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + round, + cs.Height, + cs.Round, + cs.Step)) + return + } + if !cs.Votes.Prevotes(round).HasTwoThirdsAny() { + panic(fmt.Sprintf("enterPrevoteWait(%v/%v), but Prevotes does not have any +2/3 votes", height, round)) + } + logger.Info(fmt.Sprintf("enterPrevoteWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrevoteWait: + cs.updateRoundStep(round, cstypes.RoundStepPrevoteWait) + cs.newStep() + }() + + // Wait for some more prevotes; enterPrecommit + cs.scheduleTimeout(cs.config.Prevote(round), height, round, cstypes.RoundStepPrevoteWait) +} + +// Enter: any +2/3 precommits for next round. +func (cs *State) enterPrecommitWait(height int64, round int32) { + logger := cs.Logger.With("height", height, "round", round) + + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) { + logger.Debug( + fmt.Sprintf( + "enterPrecommitWait(%v/%v): Invalid args. "+ + "Current state is Height/Round: %v/%v/, TriggeredTimeoutPrecommit:%v", + height, round, cs.Height, cs.Round, cs.TriggeredTimeoutPrecommit)) + return + } + if !cs.Votes.Precommits(round).HasTwoThirdsAny() { + panic(fmt.Sprintf("enterPrecommitWait(%v/%v), but Precommits does not have any +2/3 votes", height, round)) + } + logger.Info(fmt.Sprintf("enterPrecommitWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterPrecommitWait: + cs.TriggeredTimeoutPrecommit = true + cs.newStep() + }() + + // Wait for some more precommits; enterNewRound + cs.scheduleTimeout(cs.config.Precommit(round), height, round, cstypes.RoundStepPrecommitWait) +} + +// Enter: +2/3 precommits for block +func (cs *State) enterCommit(height int64, commitRound int32) { + logger := cs.Logger.With("height", height, "commitRound", commitRound) + + if cs.Height != height || cstypes.RoundStepCommit <= cs.Step { + logger.Debug(fmt.Sprintf( + "enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", + height, + commitRound, + cs.Height, + cs.Round, + cs.Step)) + return + } + logger.Info(fmt.Sprintf("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step)) + + defer func() { + // Done enterCommit: + // keep cs.Round the same, commitRound points to the right Precommits set. + cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) + cs.CommitRound = commitRound + cs.CommitTime = tmtime.Now() + cs.newStep() + + // Maybe finalize immediately. + cs.tryFinalizeCommit(height) + }() + + blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority() + if !ok { + panic("RunActionCommit() expects +2/3 precommits") + } + + // The Locked* fields no longer matter. + // Move them over to ProposalBlock if they match the commit hash, + // otherwise they'll be cleared in updateToState. + if cs.LockedBlock.HashesTo(blockID.Hash) { + logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash) + cs.ProposalBlock = cs.LockedBlock + cs.ProposalBlockParts = cs.LockedBlockParts + } + + // If we don't have the block being committed, set up to get it. + if !cs.ProposalBlock.HashesTo(blockID.Hash) { + if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) { + logger.Info( + "Commit is for a block we don't know about. Set ProposalBlock=nil", + "proposal", + cs.ProposalBlock.Hash(), + "commit", + blockID.Hash) + // We're getting the wrong block. + // Set up ProposalBlockParts and keep waiting. + cs.ProposalBlock = nil + cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) + if err := cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()); err != nil { + cs.Logger.Error("Error publishing valid block", "err", err) + } + cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) + } + // else { + // We just need to keep waiting. + // } + } +} + +// If we have the block AND +2/3 commits for it, finalize. +func (cs *State) tryFinalizeCommit(height int64) { + logger := cs.Logger.With("height", height) + + if cs.Height != height { + panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) + } + + blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() + if !ok || len(blockID.Hash) == 0 { + logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for .") + return + } + if !cs.ProposalBlock.HashesTo(blockID.Hash) { + // TODO: this happens every time if we're not a validator (ugly logs) + // TODO: ^^ wait, why does it matter that we're a validator? + logger.Info( + "Attempt to finalize failed. We don't have the commit block.", + "proposal-block", + cs.ProposalBlock.Hash(), + "commit-block", + blockID.Hash) + return + } + + // go + cs.finalizeCommit(height) +} + +// Increment height and goto cstypes.RoundStepNewHeight +func (cs *State) finalizeCommit(height int64) { + if cs.Height != height || cs.Step != cstypes.RoundStepCommit { + cs.Logger.Debug(fmt.Sprintf( + "finalizeCommit(%v): Invalid args. Current step: %v/%v/%v", + height, + cs.Height, + cs.Round, + cs.Step)) + return + } + + blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() + block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts + + if !ok { + panic("Cannot finalizeCommit, commit does not have two thirds majority") + } + if !blockParts.HasHeader(blockID.PartSetHeader) { + panic("Expected ProposalBlockParts header to be commit header") + } + if !block.HashesTo(blockID.Hash) { + panic("Cannot finalizeCommit, ProposalBlock does not hash to commit hash") + } + if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil { + panic(fmt.Errorf("+2/3 committed an invalid block: %w", err)) + } + + cs.Logger.Info("Finalizing commit of block with N txs", + "height", block.Height, + "hash", block.Hash(), + "root", block.AppHash, + "N", len(block.Txs)) + cs.Logger.Info(fmt.Sprintf("%v", block)) + + fail.Fail() // XXX + + // Save to blockStore. + if cs.blockStore.Height() < block.Height { + // NOTE: the seenCommit is local justification to commit this block, + // but may differ from the LastCommit included in the next block + precommits := cs.Votes.Precommits(cs.CommitRound) + seenCommit := precommits.MakeCommit() + cs.blockStore.SaveBlock(block, blockParts, seenCommit) + } else { + // Happens during replay if we already saved the block but didn't commit + cs.Logger.Info("Calling finalizeCommit on already stored block", "height", block.Height) + } + + fail.Fail() // XXX + + // Write EndHeightMessage{} for this height, implying that the blockstore + // has saved the block. + // + // If we crash before writing this EndHeightMessage{}, we will recover by + // running ApplyBlock during the ABCI handshake when we restart. If we + // didn't save the block to the blockstore before writing + // EndHeightMessage{}, we'd have to change WAL replay -- currently it + // complains about replaying for heights where an #ENDHEIGHT entry already + // exists. + // + // Either way, the State should not be resumed until we + // successfully call ApplyBlock (ie. later here, or in Handshake after + // restart). + endMsg := EndHeightMessage{height} + if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync + panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node", + endMsg, err)) + } + + fail.Fail() // XXX + + // Create a copy of the state for staging and an event cache for txs. + stateCopy := cs.state.Copy() + + // Execute and commit the block, update and save the state, and update the mempool. + // NOTE The block.AppHash wont reflect these txs until the next block. + var err error + var retainHeight int64 + stateCopy, retainHeight, err = cs.blockExec.ApplyBlock( + stateCopy, + types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()}, + block) + if err != nil { + cs.Logger.Error("Error on ApplyBlock", "err", err) + return + } + + fail.Fail() // XXX + + // Prune old heights, if requested by ABCI app. + if retainHeight > 0 { + pruned, err := cs.pruneBlocks(retainHeight) + if err != nil { + cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err) + } else { + cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight) + } + } + + // must be called before we update state + cs.recordMetrics(height, block) + + // NewHeightStep! + cs.updateToState(stateCopy) + + fail.Fail() // XXX + + // Private validator might have changed it's key pair => refetch pubkey. + if err := cs.updatePrivValidatorPubKey(); err != nil { + cs.Logger.Error("Can't get private validator pubkey", "err", err) + } + + // cs.StartTime is already set. + // Schedule Round0 to start soon. + cs.scheduleRound0(&cs.RoundState) + + // By here, + // * cs.Height has been increment to height+1 + // * cs.Step is now cstypes.RoundStepNewHeight + // * cs.StartTime is set to when we will start round0. +} + +func (cs *State) pruneBlocks(retainHeight int64) (uint64, error) { + base := cs.blockStore.Base() + if retainHeight <= base { + return 0, nil + } + pruned, err := cs.blockStore.PruneBlocks(retainHeight) + if err != nil { + return 0, fmt.Errorf("failed to prune block store: %w", err) + } + err = cs.blockExec.Store().PruneStates(base, retainHeight) + if err != nil { + return 0, fmt.Errorf("failed to prune state database: %w", err) + } + return pruned, nil +} + +func (cs *State) recordMetrics(height int64, block *types.Block) { + cs.metrics.Validators.Set(float64(cs.Validators.Size())) + cs.metrics.ValidatorsPower.Set(float64(cs.Validators.TotalVotingPower())) + + var ( + missingValidators int + missingValidatorsPower int64 + ) + // height=0 -> MissingValidators and MissingValidatorsPower are both 0. + // Remember that the first LastCommit is intentionally empty, so it's not + // fair to increment missing validators number. + if height > cs.state.InitialHeight { + // Sanity check that commit size matches validator set size - only applies + // after first block. + var ( + commitSize = block.LastCommit.Size() + valSetLen = len(cs.LastValidators.Validators) + address types.Address + ) + if commitSize != valSetLen { + panic(fmt.Sprintf("commit size (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v", + commitSize, valSetLen, block.Height, block.LastCommit.Signatures, cs.LastValidators.Validators)) + } + + if cs.privValidator != nil { + if cs.privValidatorPubKey == nil { + // Metrics won't be updated, but it's not critical. + cs.Logger.Error(fmt.Sprintf("recordMetrics: %v", errPubKeyIsNotSet)) + } else { + address = cs.privValidatorPubKey.Address() + } + } + + for i, val := range cs.LastValidators.Validators { + commitSig := block.LastCommit.Signatures[i] + if commitSig.Absent() { + missingValidators++ + missingValidatorsPower += val.VotingPower + } + + if bytes.Equal(val.Address, address) { + label := []string{ + "validator_address", val.Address.String(), + } + cs.metrics.ValidatorPower.With(label...).Set(float64(val.VotingPower)) + if commitSig.ForBlock() { + cs.metrics.ValidatorLastSignedHeight.With(label...).Set(float64(height)) + } else { + cs.metrics.ValidatorMissedBlocks.With(label...).Add(float64(1)) + } + } + + } + } + cs.metrics.MissingValidators.Set(float64(missingValidators)) + cs.metrics.MissingValidatorsPower.Set(float64(missingValidatorsPower)) + + // NOTE: byzantine validators power and count is only for consensus evidence i.e. duplicate vote + var ( + byzantineValidatorsPower = int64(0) + byzantineValidatorsCount = int64(0) + ) + for _, ev := range block.Evidence.Evidence { + if dve, ok := ev.(*types.DuplicateVoteEvidence); ok { + if _, val := cs.Validators.GetByAddress(dve.VoteA.ValidatorAddress); val != nil { + byzantineValidatorsCount++ + byzantineValidatorsPower += val.VotingPower + } + } + } + cs.metrics.ByzantineValidators.Set(float64(byzantineValidatorsCount)) + cs.metrics.ByzantineValidatorsPower.Set(float64(byzantineValidatorsPower)) + + if height > 1 { + lastBlockMeta := cs.blockStore.LoadBlockMeta(height - 1) + if lastBlockMeta != nil { + cs.metrics.BlockIntervalSeconds.Observe( + block.Time.Sub(lastBlockMeta.Header.Time).Seconds(), + ) + } + } + + cs.metrics.NumTxs.Set(float64(len(block.Data.Txs))) + cs.metrics.TotalTxs.Add(float64(len(block.Data.Txs))) + cs.metrics.BlockSizeBytes.Set(float64(block.Size())) + cs.metrics.CommittedHeight.Set(float64(block.Height)) +} + +//----------------------------------------------------------------------------- + +// NOTE: block is not necessarily valid. +// Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, +// once we have the full block. +func (cs *State) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (added bool, err error) { + height, round, part := msg.Height, msg.Round, msg.Part + + // Blocks might be reused, so round mismatch is OK + if cs.Height != height { + cs.Logger.Debug("Received block part from wrong height", "height", height, "round", round) + return false, nil + } + + // We're not expecting a block part. + if cs.ProposalBlockParts == nil { + // NOTE: this can happen when we've gone to a higher round and + // then receive parts from the previous round - not necessarily a bad peer. + cs.Logger.Info("Received a block part when we're not expecting any", + "height", height, "round", round, "index", part.Index, "peer", peerID) + return false, nil + } + + added, err = cs.ProposalBlockParts.AddPart(part) + if err != nil { + return added, err + } + if cs.ProposalBlockParts.ByteSize() > cs.state.ConsensusParams.Block.MaxBytes { + return added, fmt.Errorf("total size of proposal block parts exceeds maximum block bytes (%d > %d)", + cs.ProposalBlockParts.ByteSize(), cs.state.ConsensusParams.Block.MaxBytes, + ) + } + if added && cs.ProposalBlockParts.IsComplete() { + bz, err := ioutil.ReadAll(cs.ProposalBlockParts.GetReader()) + if err != nil { + return added, err + } + + var pbb = new(tmproto.Block) + err = proto.Unmarshal(bz, pbb) + if err != nil { + return added, err + } + + block, err := types.BlockFromProto(pbb) + if err != nil { + return added, err + } + + cs.ProposalBlock = block + // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal + cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) + if err := cs.eventBus.PublishEventCompleteProposal(cs.CompleteProposalEvent()); err != nil { + cs.Logger.Error("Error publishing event complete proposal", "err", err) + } + + // Update Valid* if we can. + prevotes := cs.Votes.Prevotes(cs.Round) + blockID, hasTwoThirds := prevotes.TwoThirdsMajority() + if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) { + if cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.Logger.Info("Updating valid block to new proposal block", + "valid-round", cs.Round, "valid-block-hash", cs.ProposalBlock.Hash()) + cs.ValidRound = cs.Round + cs.ValidBlock = cs.ProposalBlock + cs.ValidBlockParts = cs.ProposalBlockParts + } + // TODO: In case there is +2/3 majority in Prevotes set for some + // block and cs.ProposalBlock contains different block, either + // proposer is faulty or voting power of faulty processes is more + // than 1/3. We should trigger in the future accountability + // procedure at this point. + } + + if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() { + // Move onto the next step + cs.enterPrevote(height, cs.Round) + if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added + cs.enterPrecommit(height, cs.Round) + } + } else if cs.Step == cstypes.RoundStepCommit { + // If we're waiting on the proposal block... + cs.tryFinalizeCommit(height) + } + return added, nil + } + return added, nil +} + +// Attempt to add the vote. if its a duplicate signature, dupeout the validator +func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { + added, err := cs.addVote(vote, peerID) + if err != nil { + // If the vote height is off, we'll just ignore it, + // But if it's a conflicting sig, add it to the cs.evpool. + // If it's otherwise invalid, punish peer. + // nolint: gocritic + if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok { + if cs.privValidatorPubKey == nil { + return false, errPubKeyIsNotSet + } + + if bytes.Equal(vote.ValidatorAddress, cs.privValidatorPubKey.Address()) { + cs.Logger.Error( + "Found conflicting vote from ourselves. Did you unsafe_reset a validator?", + "height", + vote.Height, + "round", + vote.Round, + "type", + vote.Type) + return added, err + } + var timestamp time.Time + if voteErr.VoteA.Height == cs.state.InitialHeight { + timestamp = cs.state.LastBlockTime // genesis time + } else { + timestamp = sm.MedianTime(cs.LastCommit.MakeCommit(), cs.LastValidators) + } + evidenceErr := cs.evpool.AddEvidenceFromConsensus( + types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB), timestamp, cs.Validators) + if evidenceErr != nil { + cs.Logger.Error("Failed to add evidence to the evidence pool", "err", evidenceErr) + } + return added, err + } else if err == types.ErrVoteNonDeterministicSignature { + cs.Logger.Debug("Vote has non-deterministic signature", "err", err) + } else { + // Either + // 1) bad peer OR + // 2) not a bad peer? this can also err sometimes with "Unexpected step" OR + // 3) tmkms use with multiple validators connecting to a single tmkms instance + // (https://github.com/tendermint/tendermint/issues/3839). + cs.Logger.Info("Error attempting to add vote", "err", err) + return added, ErrAddingVote + } + } + return added, nil +} + +//----------------------------------------------------------------------------- + +// CONTRACT: cs.privValidator is not nil. +func (cs *State) signVote( + msgType tmproto.SignedMsgType, + hash []byte, + header types.PartSetHeader, +) (*types.Vote, error) { + // Flush the WAL. Otherwise, we may not recompute the same vote to sign, + // and the privValidator will refuse to sign anything. + if err := cs.wal.FlushAndSync(); err != nil { + return nil, err + } + + if cs.privValidatorPubKey == nil { + return nil, errPubKeyIsNotSet + } + addr := cs.privValidatorPubKey.Address() + valIdx, _ := cs.Validators.GetByAddress(addr) + + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: valIdx, + Height: cs.Height, + Round: cs.Round, + Timestamp: cs.voteTime(), + Type: msgType, + BlockID: types.BlockID{Hash: hash, PartSetHeader: header}, + } + v := vote.ToProto() + err := cs.privValidator.SignVote(cs.state.ChainID, v) + vote.Signature = v.Signature + + return vote, err +} + +func (cs *State) voteTime() time.Time { + now := tmtime.Now() + minVoteTime := now + // TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil, + // even if cs.LockedBlock != nil. See https://docs.tendermint.com/master/spec/. + timeIota := time.Duration(cs.state.ConsensusParams.Block.TimeIotaMs) * time.Millisecond + if cs.LockedBlock != nil { + // See the BFT time spec https://docs.tendermint.com/master/spec/consensus/bft-time.html + minVoteTime = cs.LockedBlock.Time.Add(timeIota) + } else if cs.ProposalBlock != nil { + minVoteTime = cs.ProposalBlock.Time.Add(timeIota) + } + + if now.After(minVoteTime) { + return now + } + return minVoteTime +} + +// sign the vote and publish on internalMsgQueue +func (cs *State) signAddVote(msgType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { + if cs.privValidator == nil { // the node does not have a key + return nil + } + + if cs.privValidatorPubKey == nil { + // Vote won't be signed, but it's not critical. + cs.Logger.Error(fmt.Sprintf("signAddVote: %v", errPubKeyIsNotSet)) + return nil + } + + // If the node not in the validator set, do nothing. + if !cs.Validators.HasAddress(cs.privValidatorPubKey.Address()) { + return nil + } + + // TODO: pass pubKey to signVote + vote, err := cs.signVote(msgType, hash, header) + if err == nil { + cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""}) + cs.Logger.Info("Signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote) + return vote + } + // if !cs.replayMode { + cs.Logger.Error("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err) + //} + return nil +} + +// updatePrivValidatorPubKey get's the private validator public key and +// memoizes it. This func returns an error if the private validator is not +// responding or responds with an error. +func (cs *State) updatePrivValidatorPubKey() error { + if cs.privValidator == nil { + return nil + } + + pubKey, err := cs.privValidator.GetPubKey() + if err != nil { + return err + } + cs.privValidatorPubKey = pubKey + return nil +} + +// look back to check existence of the node's consensus votes before joining consensus +func (cs *State) checkDoubleSigningRisk(height int64) error { + if cs.privValidator != nil && cs.privValidatorPubKey != nil && cs.config.DoubleSignCheckHeight > 0 && height > 0 { + valAddr := cs.privValidatorPubKey.Address() + doubleSignCheckHeight := cs.config.DoubleSignCheckHeight + if doubleSignCheckHeight > height { + doubleSignCheckHeight = height + } + for i := int64(1); i < doubleSignCheckHeight; i++ { + lastCommit := cs.blockStore.LoadSeenCommit(height - i) + if lastCommit != nil { + for sigIdx, s := range lastCommit.Signatures { + if s.BlockIDFlag == types.BlockIDFlagCommit && bytes.Equal(s.ValidatorAddress, valAddr) { + cs.Logger.Info("Found signature from the same key", "sig", s, "idx", sigIdx, "height", height-i) + return ErrSignatureFoundInPastBlocks + } + } + } + } + } + return nil +} + +//--------------------------------------------------------- + +func CompareHRS(h1 int64, r1 int32, s1 cstypes.RoundStepType, h2 int64, r2 int32, s2 cstypes.RoundStepType) int { + if h1 < h2 { + return -1 + } else if h1 > h2 { + return 1 + } + if r1 < r2 { + return -1 + } else if r1 > r2 { + return 1 + } + if s1 < s2 { + return -1 + } else if s1 > s2 { + return 1 + } + return 0 +} + +// repairWalFile decodes messages from src (until the decoder errors) and +// writes them to dst. +func repairWalFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Open(dst) + if err != nil { + return err + } + defer out.Close() + + var ( + dec = NewWALDecoder(in) + enc = NewWALEncoder(out) + ) + + // best-case repair (until first error is encountered) + for { + msg, err := dec.Decode() + if err != nil { + break + } + + err = enc.Encode(msg) + if err != nil { + return fmt.Errorf("failed to encode msg: %w", err) + } + } + + return nil +} diff --git a/test/maverick/consensus/ticker.go b/test/maverick/consensus/ticker.go new file mode 100644 index 0000000000..fb3571ac86 --- /dev/null +++ b/test/maverick/consensus/ticker.go @@ -0,0 +1,134 @@ +package consensus + +import ( + "time" + + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/service" +) + +var ( + tickTockBufferSize = 10 +) + +// TimeoutTicker is a timer that schedules timeouts +// conditional on the height/round/step in the timeoutInfo. +// The timeoutInfo.Duration may be non-positive. +type TimeoutTicker interface { + Start() error + Stop() error + Chan() <-chan timeoutInfo // on which to receive a timeout + ScheduleTimeout(ti timeoutInfo) // reset the timer + + SetLogger(log.Logger) +} + +// timeoutTicker wraps time.Timer, +// scheduling timeouts only for greater height/round/step +// than what it's already seen. +// Timeouts are scheduled along the tickChan, +// and fired on the tockChan. +type timeoutTicker struct { + service.BaseService + + timer *time.Timer + tickChan chan timeoutInfo // for scheduling timeouts + tockChan chan timeoutInfo // for notifying about them +} + +// NewTimeoutTicker returns a new TimeoutTicker. +func NewTimeoutTicker() TimeoutTicker { + tt := &timeoutTicker{ + timer: time.NewTimer(0), + tickChan: make(chan timeoutInfo, tickTockBufferSize), + tockChan: make(chan timeoutInfo, tickTockBufferSize), + } + tt.BaseService = *service.NewBaseService(nil, "TimeoutTicker", tt) + tt.stopTimer() // don't want to fire until the first scheduled timeout + return tt +} + +// OnStart implements service.Service. It starts the timeout routine. +func (t *timeoutTicker) OnStart() error { + + go t.timeoutRoutine() + + return nil +} + +// OnStop implements service.Service. It stops the timeout routine. +func (t *timeoutTicker) OnStop() { + t.BaseService.OnStop() + t.stopTimer() +} + +// Chan returns a channel on which timeouts are sent. +func (t *timeoutTicker) Chan() <-chan timeoutInfo { + return t.tockChan +} + +// ScheduleTimeout schedules a new timeout by sending on the internal tickChan. +// The timeoutRoutine is always available to read from tickChan, so this won't block. +// The scheduling may fail if the timeoutRoutine has already scheduled a timeout for a later height/round/step. +func (t *timeoutTicker) ScheduleTimeout(ti timeoutInfo) { + t.tickChan <- ti +} + +//------------------------------------------------------------- + +// stop the timer and drain if necessary +func (t *timeoutTicker) stopTimer() { + // Stop() returns false if it was already fired or was stopped + if !t.timer.Stop() { + select { + case <-t.timer.C: + default: + t.Logger.Debug("Timer already stopped") + } + } +} + +// send on tickChan to start a new timer. +// timers are interupted and replaced by new ticks from later steps +// timeouts of 0 on the tickChan will be immediately relayed to the tockChan +func (t *timeoutTicker) timeoutRoutine() { + t.Logger.Debug("Starting timeout routine") + var ti timeoutInfo + for { + select { + case newti := <-t.tickChan: + t.Logger.Debug("Received tick", "old_ti", ti, "new_ti", newti) + + // ignore tickers for old height/round/step + if newti.Height < ti.Height { + continue + } else if newti.Height == ti.Height { + if newti.Round < ti.Round { + continue + } else if newti.Round == ti.Round { + if ti.Step > 0 && newti.Step <= ti.Step { + continue + } + } + } + + // stop the last timer + t.stopTimer() + + // update timeoutInfo and reset timer + // NOTE time.Timer allows duration to be non-positive + ti = newti + t.timer.Reset(ti.Duration) + t.Logger.Debug("Scheduled timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + case <-t.timer.C: + t.Logger.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) + // go routine here guarantees timeoutRoutine doesn't block. + // Determinism comes from playback in the receiveRoutine. + // We can eliminate it by merging the timeoutRoutine into receiveRoutine + // and managing the timeouts ourselves with a millisecond ticker + go func(toi timeoutInfo) { t.tockChan <- toi }(ti) + case <-t.Quit(): + return + } + } +} diff --git a/test/maverick/consensus/wal.go b/test/maverick/consensus/wal.go new file mode 100644 index 0000000000..7d698713f4 --- /dev/null +++ b/test/maverick/consensus/wal.go @@ -0,0 +1,437 @@ +package consensus + +import ( + "encoding/binary" + "errors" + "fmt" + "hash/crc32" + "io" + "path/filepath" + "time" + + "github.com/gogo/protobuf/proto" + + auto "github.com/tendermint/tendermint/libs/autofile" + // tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/libs/service" + tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus" + tmtime "github.com/tendermint/tendermint/types/time" +) + +const ( + // time.Time + max consensus msg size + maxMsgSizeBytes = maxMsgSize + 24 + + // how often the WAL should be sync'd during period sync'ing + walDefaultFlushInterval = 2 * time.Second +) + +//-------------------------------------------------------- +// types and functions for savings consensus messages + +// TimedWALMessage wraps WALMessage and adds Time for debugging purposes. +type TimedWALMessage struct { + Time time.Time `json:"time"` + Msg WALMessage `json:"msg"` +} + +// EndHeightMessage marks the end of the given height inside WAL. +// @internal used by scripts/wal2json util. +type EndHeightMessage struct { + Height int64 `json:"height"` +} + +type WALMessage interface{} + +// func init() { +// tmjson.RegisterType(msgInfo{}, "tendermint/wal/MsgInfo") +// tmjson.RegisterType(timeoutInfo{}, "tendermint/wal/TimeoutInfo") +// tmjson.RegisterType(EndHeightMessage{}, "tendermint/wal/EndHeightMessage") +// } + +//-------------------------------------------------------- +// Simple write-ahead logger + +// WAL is an interface for any write-ahead logger. +type WAL interface { + Write(WALMessage) error + WriteSync(WALMessage) error + FlushAndSync() error + + SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) + + // service methods + Start() error + Stop() error + Wait() +} + +// Write ahead logger writes msgs to disk before they are processed. +// Can be used for crash-recovery and deterministic replay. +// TODO: currently the wal is overwritten during replay catchup, give it a mode +// so it's either reading or appending - must read to end to start appending +// again. +type BaseWAL struct { + service.BaseService + + group *auto.Group + + enc *WALEncoder + + flushTicker *time.Ticker + flushInterval time.Duration +} + +var _ WAL = &BaseWAL{} + +// NewWAL returns a new write-ahead logger based on `baseWAL`, which implements +// WAL. It's flushed and synced to disk every 2s and once when stopped. +func NewWAL(walFile string, groupOptions ...func(*auto.Group)) (*BaseWAL, error) { + err := tmos.EnsureDir(filepath.Dir(walFile), 0700) + if err != nil { + return nil, fmt.Errorf("failed to ensure WAL directory is in place: %w", err) + } + + group, err := auto.OpenGroup(walFile, groupOptions...) + if err != nil { + return nil, err + } + wal := &BaseWAL{ + group: group, + enc: NewWALEncoder(group), + flushInterval: walDefaultFlushInterval, + } + wal.BaseService = *service.NewBaseService(nil, "baseWAL", wal) + return wal, nil +} + +// SetFlushInterval allows us to override the periodic flush interval for the WAL. +func (wal *BaseWAL) SetFlushInterval(i time.Duration) { + wal.flushInterval = i +} + +func (wal *BaseWAL) Group() *auto.Group { + return wal.group +} + +func (wal *BaseWAL) SetLogger(l log.Logger) { + wal.BaseService.Logger = l + wal.group.SetLogger(l) +} + +func (wal *BaseWAL) OnStart() error { + size, err := wal.group.Head.Size() + if err != nil { + return err + } else if size == 0 { + if err := wal.WriteSync(EndHeightMessage{0}); err != nil { + return err + } + } + err = wal.group.Start() + if err != nil { + return err + } + wal.flushTicker = time.NewTicker(wal.flushInterval) + go wal.processFlushTicks() + return nil +} + +func (wal *BaseWAL) processFlushTicks() { + for { + select { + case <-wal.flushTicker.C: + if err := wal.FlushAndSync(); err != nil { + wal.Logger.Error("Periodic WAL flush failed", "err", err) + } + case <-wal.Quit(): + return + } + } +} + +// FlushAndSync flushes and fsync's the underlying group's data to disk. +// See auto#FlushAndSync +func (wal *BaseWAL) FlushAndSync() error { + return wal.group.FlushAndSync() +} + +// Stop the underlying autofile group. +// Use Wait() to ensure it's finished shutting down +// before cleaning up files. +func (wal *BaseWAL) OnStop() { + wal.flushTicker.Stop() + if err := wal.FlushAndSync(); err != nil { + wal.Logger.Error("error on flush data to disk", "error", err) + } + if err := wal.group.Stop(); err != nil { + wal.Logger.Error("error trying to stop wal", "error", err) + } + wal.group.Close() +} + +// Wait for the underlying autofile group to finish shutting down +// so it's safe to cleanup files. +func (wal *BaseWAL) Wait() { + wal.group.Wait() +} + +// Write is called in newStep and for each receive on the +// peerMsgQueue and the timeoutTicker. +// NOTE: does not call fsync() +func (wal *BaseWAL) Write(msg WALMessage) error { + if wal == nil { + return nil + } + + if err := wal.enc.Encode(&TimedWALMessage{tmtime.Now(), msg}); err != nil { + wal.Logger.Error("Error writing msg to consensus wal. WARNING: recover may not be possible for the current height", + "err", err, "msg", msg) + return err + } + + return nil +} + +// WriteSync is called when we receive a msg from ourselves +// so that we write to disk before sending signed messages. +// NOTE: calls fsync() +func (wal *BaseWAL) WriteSync(msg WALMessage) error { + if wal == nil { + return nil + } + + if err := wal.Write(msg); err != nil { + return err + } + + if err := wal.FlushAndSync(); err != nil { + wal.Logger.Error(`WriteSync failed to flush consensus wal. + WARNING: may result in creating alternative proposals / votes for the current height iff the node restarted`, + "err", err) + return err + } + + return nil +} + +// WALSearchOptions are optional arguments to SearchForEndHeight. +type WALSearchOptions struct { + // IgnoreDataCorruptionErrors set to true will result in skipping data corruption errors. + IgnoreDataCorruptionErrors bool +} + +// SearchForEndHeight searches for the EndHeightMessage with the given height +// and returns an auto.GroupReader, whenever it was found or not and an error. +// Group reader will be nil if found equals false. +// +// CONTRACT: caller must close group reader. +func (wal *BaseWAL) SearchForEndHeight( + height int64, + options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { + var ( + msg *TimedWALMessage + gr *auto.GroupReader + ) + lastHeightFound := int64(-1) + + // NOTE: starting from the last file in the group because we're usually + // searching for the last height. See replay.go + min, max := wal.group.MinIndex(), wal.group.MaxIndex() + wal.Logger.Info("Searching for height", "height", height, "min", min, "max", max) + for index := max; index >= min; index-- { + gr, err = wal.group.NewReader(index) + if err != nil { + return nil, false, err + } + + dec := NewWALDecoder(gr) + for { + msg, err = dec.Decode() + if err == io.EOF { + // OPTIMISATION: no need to look for height in older files if we've seen h < height + if lastHeightFound > 0 && lastHeightFound < height { + gr.Close() + return nil, false, nil + } + // check next file + break + } + if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) { + wal.Logger.Error("Corrupted entry. Skipping...", "err", err) + // do nothing + continue + } else if err != nil { + gr.Close() + return nil, false, err + } + + if m, ok := msg.Msg.(EndHeightMessage); ok { + lastHeightFound = m.Height + if m.Height == height { // found + wal.Logger.Info("Found", "height", height, "index", index) + return gr, true, nil + } + } + } + gr.Close() + } + + return nil, false, nil +} + +// ///////////////////////////////////////////////////////////////////////////// + +// A WALEncoder writes custom-encoded WAL messages to an output stream. +// +// Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value +type WALEncoder struct { + wr io.Writer +} + +// NewWALEncoder returns a new encoder that writes to wr. +func NewWALEncoder(wr io.Writer) *WALEncoder { + return &WALEncoder{wr} +} + +// Encode writes the custom encoding of v to the stream. It returns an error if +// the encoded size of v is greater than 1MB. Any error encountered +// during the write is also returned. +func (enc *WALEncoder) Encode(v *TimedWALMessage) error { + pbMsg, err := WALToProto(v.Msg) + if err != nil { + return err + } + pv := tmcons.TimedWALMessage{ + Time: v.Time, + Msg: pbMsg, + } + + data, err := proto.Marshal(&pv) + if err != nil { + panic(fmt.Errorf("encode timed wall message failure: %w", err)) + } + + crc := crc32.Checksum(data, crc32c) + length := uint32(len(data)) + if length > maxMsgSizeBytes { + return fmt.Errorf("msg is too big: %d bytes, max: %d bytes", length, maxMsgSizeBytes) + } + totalLength := 8 + int(length) + + msg := make([]byte, totalLength) + binary.BigEndian.PutUint32(msg[0:4], crc) + binary.BigEndian.PutUint32(msg[4:8], length) + copy(msg[8:], data) + + _, err = enc.wr.Write(msg) + return err +} + +// ///////////////////////////////////////////////////////////////////////////// + +// IsDataCorruptionError returns true if data has been corrupted inside WAL. +func IsDataCorruptionError(err error) bool { + _, ok := err.(DataCorruptionError) + return ok +} + +// DataCorruptionError is an error that occures if data on disk was corrupted. +type DataCorruptionError struct { + cause error +} + +func (e DataCorruptionError) Error() string { + return fmt.Sprintf("DataCorruptionError[%v]", e.cause) +} + +func (e DataCorruptionError) Cause() error { + return e.cause +} + +// A WALDecoder reads and decodes custom-encoded WAL messages from an input +// stream. See WALEncoder for the format used. +// +// It will also compare the checksums and make sure data size is equal to the +// length from the header. If that is not the case, error will be returned. +type WALDecoder struct { + rd io.Reader +} + +// NewWALDecoder returns a new decoder that reads from rd. +func NewWALDecoder(rd io.Reader) *WALDecoder { + return &WALDecoder{rd} +} + +// Decode reads the next custom-encoded value from its reader and returns it. +func (dec *WALDecoder) Decode() (*TimedWALMessage, error) { + b := make([]byte, 4) + + _, err := dec.rd.Read(b) + if errors.Is(err, io.EOF) { + return nil, err + } + if err != nil { + return nil, DataCorruptionError{fmt.Errorf("failed to read checksum: %v", err)} + } + crc := binary.BigEndian.Uint32(b) + + b = make([]byte, 4) + _, err = dec.rd.Read(b) + if err != nil { + return nil, DataCorruptionError{fmt.Errorf("failed to read length: %v", err)} + } + length := binary.BigEndian.Uint32(b) + + if length > maxMsgSizeBytes { + return nil, DataCorruptionError{fmt.Errorf( + "length %d exceeded maximum possible value of %d bytes", + length, + maxMsgSizeBytes)} + } + + data := make([]byte, length) + n, err := dec.rd.Read(data) + if err != nil { + return nil, DataCorruptionError{fmt.Errorf("failed to read data: %v (read: %d, wanted: %d)", err, n, length)} + } + + // check checksum before decoding data + actualCRC := crc32.Checksum(data, crc32c) + if actualCRC != crc { + return nil, DataCorruptionError{fmt.Errorf("checksums do not match: read: %v, actual: %v", crc, actualCRC)} + } + + var res = new(tmcons.TimedWALMessage) + err = proto.Unmarshal(data, res) + if err != nil { + return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)} + } + + walMsg, err := WALFromProto(res.Msg) + if err != nil { + return nil, DataCorruptionError{fmt.Errorf("failed to convert from proto: %w", err)} + } + tMsgWal := &TimedWALMessage{ + Time: res.Time, + Msg: walMsg, + } + + return tMsgWal, err +} + +type nilWAL struct{} + +var _ WAL = nilWAL{} + +func (nilWAL) Write(m WALMessage) error { return nil } +func (nilWAL) WriteSync(m WALMessage) error { return nil } +func (nilWAL) FlushAndSync() error { return nil } +func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { + return nil, false, nil +} +func (nilWAL) Start() error { return nil } +func (nilWAL) Stop() error { return nil } +func (nilWAL) Wait() {} diff --git a/test/maverick/consensus/wal_fuzz.go b/test/maverick/consensus/wal_fuzz.go new file mode 100644 index 0000000000..e15097c305 --- /dev/null +++ b/test/maverick/consensus/wal_fuzz.go @@ -0,0 +1,31 @@ +// +build gofuzz + +package consensus + +import ( + "bytes" + "io" +) + +func Fuzz(data []byte) int { + dec := NewWALDecoder(bytes.NewReader(data)) + for { + msg, err := dec.Decode() + if err == io.EOF { + break + } + if err != nil { + if msg != nil { + panic("msg != nil on error") + } + return 0 + } + var w bytes.Buffer + enc := NewWALEncoder(&w) + err = enc.Encode(msg) + if err != nil { + panic(err) + } + } + return 1 +} diff --git a/test/maverick/consensus/wal_generator.go b/test/maverick/consensus/wal_generator.go new file mode 100644 index 0000000000..fde9064b85 --- /dev/null +++ b/test/maverick/consensus/wal_generator.go @@ -0,0 +1,229 @@ +package consensus + +import ( + "bufio" + "bytes" + "fmt" + "io" + "path/filepath" + "testing" + "time" + + db "github.com/tendermint/tm-db" + + "github.com/tendermint/tendermint/abci/example/kvstore" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/store" + "github.com/tendermint/tendermint/types" +) + +// WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a +// stripped down version of node (proxy app, event bus, consensus state) with a +// persistent kvstore application and special consensus wal instance +// (byteBufferWAL) and waits until numBlocks are created. +// If the node fails to produce given numBlocks, it returns an error. +func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { + config := getConfig(t) + + app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator")) + + logger := log.TestingLogger().With("wal_generator", "wal_generator") + logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) + + // /////////////////////////////////////////////////////////////////////////// + // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS + // NOTE: we can't import node package because of circular dependency. + // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. + privValidatorKeyFile := config.PrivValidatorKeyFile() + privValidatorStateFile := config.PrivValidatorStateFile() + privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) + genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) + if err != nil { + return fmt.Errorf("failed to read genesis file: %w", err) + } + blockStoreDB := db.NewMemDB() + stateDB := blockStoreDB + stateStore := sm.NewStore(stateDB) + state, err := sm.MakeGenesisState(genDoc) + if err != nil { + return fmt.Errorf("failed to make genesis state: %w", err) + } + state.Version.Consensus.App = kvstore.ProtocolVersion + if err = stateStore.Save(state); err != nil { + t.Error(err) + } + + blockStore := store.NewBlockStore(blockStoreDB) + + proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app)) + proxyApp.SetLogger(logger.With("module", "proxy")) + if err := proxyApp.Start(); err != nil { + return fmt.Errorf("failed to start proxy app connections: %w", err) + } + t.Cleanup(func() { + if err := proxyApp.Stop(); err != nil { + t.Error(err) + } + }) + + eventBus := types.NewEventBus() + eventBus.SetLogger(logger.With("module", "events")) + if err := eventBus.Start(); err != nil { + return fmt.Errorf("failed to start event bus: %w", err) + } + t.Cleanup(func() { + if err := eventBus.Stop(); err != nil { + t.Error(err) + } + }) + mempool := emptyMempool{} + evpool := sm.EmptyEvidencePool{} + blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) + consensusState := NewState(config.Consensus, state.Copy(), + blockExec, blockStore, mempool, evpool, map[int64]Misbehavior{}) + consensusState.SetLogger(logger) + consensusState.SetEventBus(eventBus) + if privValidator != nil { + consensusState.SetPrivValidator(privValidator) + } + // END OF COPY PASTE + // /////////////////////////////////////////////////////////////////////////// + + // set consensus wal to buffered WAL, which will write all incoming msgs to buffer + numBlocksWritten := make(chan struct{}) + wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten) + // see wal.go#103 + if err := wal.Write(EndHeightMessage{0}); err != nil { + t.Error(err) + } + + consensusState.wal = wal + + if err := consensusState.Start(); err != nil { + return fmt.Errorf("failed to start consensus state: %w", err) + } + + select { + case <-numBlocksWritten: + if err := consensusState.Stop(); err != nil { + t.Error(err) + } + return nil + case <-time.After(1 * time.Minute): + if err := consensusState.Stop(); err != nil { + t.Error(err) + } + return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks) + } +} + +// WALWithNBlocks returns a WAL content with numBlocks. +func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) { + var b bytes.Buffer + wr := bufio.NewWriter(&b) + + if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil { + return []byte{}, err + } + + wr.Flush() + return b.Bytes(), nil +} + +func randPort() int { + // returns between base and base + spread + base, spread := 20000, 20000 + return base + tmrand.Intn(spread) +} + +func makeAddrs() (string, string, string) { + start := randPort() + return fmt.Sprintf("tcp://127.0.0.1:%d", start), + fmt.Sprintf("tcp://127.0.0.1:%d", start+1), + fmt.Sprintf("tcp://127.0.0.1:%d", start+2) +} + +// getConfig returns a config for test cases +func getConfig(t *testing.T) *cfg.Config { + c := cfg.ResetTestRoot(t.Name()) + + // and we use random ports to run in parallel + tm, rpc, grpc := makeAddrs() + c.P2P.ListenAddress = tm + c.RPC.ListenAddress = rpc + c.RPC.GRPCListenAddress = grpc + return c +} + +// byteBufferWAL is a WAL which writes all msgs to a byte buffer. Writing stops +// when the heightToStop is reached. Client will be notified via +// signalWhenStopsTo channel. +type byteBufferWAL struct { + enc *WALEncoder + stopped bool + heightToStop int64 + signalWhenStopsTo chan<- struct{} + + logger log.Logger +} + +// needed for determinism +var fixedTime, _ = time.Parse(time.RFC3339, "2017-01-02T15:04:05Z") + +func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalStop chan<- struct{}) *byteBufferWAL { + return &byteBufferWAL{ + enc: enc, + heightToStop: nBlocks, + signalWhenStopsTo: signalStop, + logger: logger, + } +} + +// Save writes message to the internal buffer except when heightToStop is +// reached, in which case it will signal the caller via signalWhenStopsTo and +// skip writing. +func (w *byteBufferWAL) Write(m WALMessage) error { + if w.stopped { + w.logger.Debug("WAL already stopped. Not writing message", "msg", m) + return nil + } + + if endMsg, ok := m.(EndHeightMessage); ok { + w.logger.Debug("WAL write end height message", "height", endMsg.Height, "stopHeight", w.heightToStop) + if endMsg.Height == w.heightToStop { + w.logger.Debug("Stopping WAL at height", "height", endMsg.Height) + w.signalWhenStopsTo <- struct{}{} + w.stopped = true + return nil + } + } + + w.logger.Debug("WAL Write Message", "msg", m) + err := w.enc.Encode(&TimedWALMessage{fixedTime, m}) + if err != nil { + panic(fmt.Sprintf("failed to encode the msg %v", m)) + } + + return nil +} + +func (w *byteBufferWAL) WriteSync(m WALMessage) error { + return w.Write(m) +} + +func (w *byteBufferWAL) FlushAndSync() error { return nil } + +func (w *byteBufferWAL) SearchForEndHeight( + height int64, + options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { + return nil, false, nil +} + +func (w *byteBufferWAL) Start() error { return nil } +func (w *byteBufferWAL) Stop() error { return nil } +func (w *byteBufferWAL) Wait() {} diff --git a/test/maverick/main.go b/test/maverick/main.go new file mode 100644 index 0000000000..6a337b3fdd --- /dev/null +++ b/test/maverick/main.go @@ -0,0 +1,237 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tendermint/cmd/tendermint/commands/debug" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/cli" + tmflags "github.com/tendermint/tendermint/libs/cli/flags" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/p2p" + cs "github.com/tendermint/tendermint/test/maverick/consensus" + nd "github.com/tendermint/tendermint/test/maverick/node" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +var ( + config = cfg.DefaultConfig() + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + misbehaviorFlag = "" +) + +func init() { + registerFlagsRootCmd(RootCmd) +} + +func registerFlagsRootCmd(command *cobra.Command) { + command.PersistentFlags().String("log_level", config.LogLevel, "Log level") +} + +func ParseConfig() (*cfg.Config, error) { + conf := cfg.DefaultConfig() + err := viper.Unmarshal(conf) + if err != nil { + return nil, err + } + conf.SetRoot(conf.RootDir) + cfg.EnsureRoot(conf.RootDir) + if err = conf.ValidateBasic(); err != nil { + return nil, fmt.Errorf("error in config file: %v", err) + } + return conf, err +} + +// RootCmd is the root command for Tendermint core. +var RootCmd = &cobra.Command{ + Use: "maverick", + Short: "Tendermint Maverick Node", + Long: "Tendermint Maverick Node for testing with faulty consensus misbehaviors in a testnet. Contains " + + "all the functionality of a normal node but custom misbehaviors can be injected when running the node " + + "through a flag. See maverick node --help for how the misbehavior flag is constructured", + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + fmt.Printf("use: %v, args: %v", cmd.Use, cmd.Args) + config, err = ParseConfig() + if err != nil { + return err + } + if config.LogFormat == cfg.LogFormatJSON { + logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) + } + logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) + if err != nil { + return err + } + if viper.GetBool(cli.TraceFlag) { + logger = log.NewTracingLogger(logger) + } + logger = logger.With("module", "main") + return nil + }, +} + +func main() { + rootCmd := RootCmd + rootCmd.AddCommand( + ListMisbehaviorCmd, + cmd.GenValidatorCmd, + InitFilesCmd, + cmd.ProbeUpnpCmd, + cmd.ReplayCmd, + cmd.ReplayConsoleCmd, + cmd.ResetAllCmd, + cmd.ResetPrivValidatorCmd, + cmd.ShowValidatorCmd, + cmd.ShowNodeIDCmd, + cmd.GenNodeKeyCmd, + cmd.VersionCmd, + debug.DebugCmd, + cli.NewCompletionCmd(rootCmd, true), + ) + + nodeCmd := &cobra.Command{ + Use: "node", + Short: "Run the maverick node", + RunE: func(command *cobra.Command, args []string) error { + return startNode(config, logger, misbehaviorFlag) + }, + } + + cmd.AddNodeFlags(nodeCmd) + + // Create & start node + rootCmd.AddCommand(nodeCmd) + + // add special flag for misbehaviors + nodeCmd.Flags().StringVar( + &misbehaviorFlag, + "misbehaviors", + "", + "Select the misbehaviors of the node (comma-separated, no spaces in between): \n"+ + "e.g. --misbehaviors double-prevote,3\n"+ + "You can also have multiple misbehaviors: e.g. double-prevote,3,no-vote,5") + + cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv(filepath.Join("$HOME", cfg.DefaultTendermintDir))) + if err := cmd.Execute(); err != nil { + panic(err) + } +} + +func startNode(config *cfg.Config, logger log.Logger, misbehaviorFlag string) error { + misbehaviors, err := nd.ParseMisbehaviors(misbehaviorFlag) + if err != nil { + return err + } + + node, err := nd.DefaultNewNode(config, logger, misbehaviors) + if err != nil { + return fmt.Errorf("failed to create node: %w", err) + } + + if err := node.Start(); err != nil { + return fmt.Errorf("failed to start node: %w", err) + } + + logger.Info("Started node", "nodeInfo", node.Switch().NodeInfo()) + + // Stop upon receiving SIGTERM or CTRL-C. + tmos.TrapSignal(logger, func() { + if node.IsRunning() { + if err := node.Stop(); err != nil { + logger.Error("unable to stop the node", "error", err) + } + } + }) + + // Run forever. + select {} +} + +var InitFilesCmd = &cobra.Command{ + Use: "init", + Short: "Initialize Tendermint", + RunE: initFiles, +} + +func initFiles(cmd *cobra.Command, args []string) error { + return initFilesWithConfig(config) +} + +func initFilesWithConfig(config *cfg.Config) error { + // private validator + privValKeyFile := config.PrivValidatorKeyFile() + privValStateFile := config.PrivValidatorStateFile() + var pv *nd.FilePV + if tmos.FileExists(privValKeyFile) { + pv = nd.LoadFilePV(privValKeyFile, privValStateFile) + logger.Info("Found private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } else { + pv = nd.GenFilePV(privValKeyFile, privValStateFile) + pv.Save() + logger.Info("Generated private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } + + nodeKeyFile := config.NodeKeyFile() + if tmos.FileExists(nodeKeyFile) { + logger.Info("Found node key", "path", nodeKeyFile) + } else { + if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil { + return err + } + logger.Info("Generated node key", "path", nodeKeyFile) + } + + // genesis file + genFile := config.GenesisFile() + if tmos.FileExists(genFile) { + logger.Info("Found genesis file", "path", genFile) + } else { + genDoc := types.GenesisDoc{ + ChainID: fmt.Sprintf("test-chain-%v", tmrand.Str(6)), + GenesisTime: tmtime.Now(), + ConsensusParams: types.DefaultConsensusParams(), + } + pubKey, err := pv.GetPubKey() + if err != nil { + return fmt.Errorf("can't get pubkey: %w", err) + } + genDoc.Validators = []types.GenesisValidator{{ + Address: pubKey.Address(), + PubKey: pubKey, + Power: 10, + }} + + if err := genDoc.SaveAs(genFile); err != nil { + return err + } + logger.Info("Generated genesis file", "path", genFile) + } + + return nil +} + +var ListMisbehaviorCmd = &cobra.Command{ + Use: "misbehaviors", + Short: "Lists possible misbehaviors", + RunE: listMisbehaviors, +} + +func listMisbehaviors(cmd *cobra.Command, args []string) error { + str := "Currently registered misbehaviors: \n" + for key := range cs.MisbehaviorList { + str += fmt.Sprintf("- %s\n", key) + } + fmt.Println(str) + return nil +} diff --git a/test/maverick/node/node.go b/test/maverick/node/node.go new file mode 100644 index 0000000000..e1f41b6fba --- /dev/null +++ b/test/maverick/node/node.go @@ -0,0 +1,1440 @@ +package node + +import ( + "bytes" + "context" + "errors" + "fmt" + "net" + "net/http" + _ "net/http/pprof" // nolint: gosec // securely exposed on separate, optional port + "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/cors" + + dbm "github.com/tendermint/tm-db" + + abci "github.com/tendermint/tendermint/abci/types" + bcv0 "github.com/tendermint/tendermint/blockchain/v0" + bcv1 "github.com/tendermint/tendermint/blockchain/v1" + bcv2 "github.com/tendermint/tendermint/blockchain/v2" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/consensus" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/evidence" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmpubsub "github.com/tendermint/tendermint/libs/pubsub" + "github.com/tendermint/tendermint/libs/service" + "github.com/tendermint/tendermint/light" + mempl "github.com/tendermint/tendermint/mempool" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/pex" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + rpccore "github.com/tendermint/tendermint/rpc/core" + grpccore "github.com/tendermint/tendermint/rpc/grpc" + rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/state/txindex" + "github.com/tendermint/tendermint/state/txindex/kv" + "github.com/tendermint/tendermint/state/txindex/null" + "github.com/tendermint/tendermint/statesync" + "github.com/tendermint/tendermint/store" + cs "github.com/tendermint/tendermint/test/maverick/consensus" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/version" +) + +//------------------------------------------------------------------------------ + +// ParseMisbehaviors is a util function that converts a comma separated string into +// a map of misbehaviors to be executed by the maverick node +func ParseMisbehaviors(str string) (map[int64]cs.Misbehavior, error) { + // check if string is empty in which case we run a normal node + var misbehaviors = make(map[int64]cs.Misbehavior) + if str == "" { + return misbehaviors, nil + } + strs := strings.Split(str, ",") + if len(strs)%2 != 0 { + return misbehaviors, errors.New("missing either height or misbehavior name in the misbehavior flag") + } +OUTER_LOOP: + for i := 0; i < len(strs); i += 2 { + height, err := strconv.ParseInt(strs[i+1], 10, 64) + if err != nil { + return misbehaviors, fmt.Errorf("failed to parse misbehavior height: %w", err) + } + for key, misbehavior := range cs.MisbehaviorList { + if key == strs[i] { + misbehaviors[height] = misbehavior + continue OUTER_LOOP + } + } + return misbehaviors, fmt.Errorf("received unknown misbehavior: %s. Did you forget to add it?", strs[i]) + } + + return misbehaviors, nil +} + +// DBContext specifies config information for loading a new DB. +type DBContext struct { + ID string + Config *cfg.Config +} + +// DBProvider takes a DBContext and returns an instantiated DB. +type DBProvider func(*DBContext) (dbm.DB, error) + +// DefaultDBProvider returns a database using the DBBackend and DBDir +// specified in the ctx.Config. +func DefaultDBProvider(ctx *DBContext) (dbm.DB, error) { + dbType := dbm.BackendType(ctx.Config.DBBackend) + return dbm.NewDB(ctx.ID, dbType, ctx.Config.DBDir()) +} + +// GenesisDocProvider returns a GenesisDoc. +// It allows the GenesisDoc to be pulled from sources other than the +// filesystem, for instance from a distributed key-value store cluster. +type GenesisDocProvider func() (*types.GenesisDoc, error) + +// DefaultGenesisDocProviderFunc returns a GenesisDocProvider that loads +// the GenesisDoc from the config.GenesisFile() on the filesystem. +func DefaultGenesisDocProviderFunc(config *cfg.Config) GenesisDocProvider { + return func() (*types.GenesisDoc, error) { + return types.GenesisDocFromFile(config.GenesisFile()) + } +} + +// Provider takes a config and a logger and returns a ready to go Node. +type Provider func(*cfg.Config, log.Logger) (*Node, error) + +// DefaultNewNode returns a Tendermint node with default settings for the +// PrivValidator, ClientCreator, GenesisDoc, and DBProvider. +// It implements NodeProvider. +func DefaultNewNode(config *cfg.Config, logger log.Logger, misbehaviors map[int64]cs.Misbehavior) (*Node, error) { + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return nil, fmt.Errorf("failed to load or gen node key %s, err: %w", config.NodeKeyFile(), err) + } + + return NewNode(config, + LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), + nodeKey, + proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), + DefaultGenesisDocProviderFunc(config), + DefaultDBProvider, + DefaultMetricsProvider(config.Instrumentation), + logger, + misbehaviors, + ) + +} + +// MetricsProvider returns a consensus, p2p and mempool Metrics. +type MetricsProvider func(chainID string) (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) + +// DefaultMetricsProvider returns Metrics build using Prometheus client library +// if Prometheus is enabled. Otherwise, it returns no-op Metrics. +func DefaultMetricsProvider(config *cfg.InstrumentationConfig) MetricsProvider { + return func(chainID string) (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) { + if config.Prometheus { + return cs.PrometheusMetrics(config.Namespace, "chain_id", chainID), + p2p.PrometheusMetrics(config.Namespace, "chain_id", chainID), + mempl.PrometheusMetrics(config.Namespace, "chain_id", chainID), + sm.PrometheusMetrics(config.Namespace, "chain_id", chainID) + } + return cs.NopMetrics(), p2p.NopMetrics(), mempl.NopMetrics(), sm.NopMetrics() + } +} + +// Option sets a parameter for the node. +type Option func(*Node) + +// Temporary interface for switching to fast sync, we should get rid of v0 and v1 reactors. +// See: https://github.com/tendermint/tendermint/issues/4595 +type fastSyncReactor interface { + SwitchToFastSync(sm.State) error +} + +// CustomReactors allows you to add custom reactors (name -> p2p.Reactor) to +// the node's Switch. +// +// WARNING: using any name from the below list of the existing reactors will +// result in replacing it with the custom one. +// +// - MEMPOOL +// - BLOCKCHAIN +// - CONSENSUS +// - EVIDENCE +// - PEX +// - STATESYNC +func CustomReactors(reactors map[string]p2p.Reactor) Option { + return func(n *Node) { + for name, reactor := range reactors { + if existingReactor := n.sw.Reactor(name); existingReactor != nil { + n.sw.Logger.Info("Replacing existing reactor with a custom one", + "name", name, "existing", existingReactor, "custom", reactor) + n.sw.RemoveReactor(name, existingReactor) + } + n.sw.AddReactor(name, reactor) + } + } +} + +func CustomReactorsAsConstructors(reactors map[string]func(n *Node) p2p.Reactor) Option { + return func(n *Node) { + for name, customReactor := range reactors { + if existingReactor := n.sw.Reactor(name); existingReactor != nil { + n.sw.Logger.Info("Replacing existing reactor with a custom one", + "name", name) + n.sw.RemoveReactor(name, existingReactor) + } + n.sw.AddReactor(name, customReactor(n)) + } + } +} + +// StateProvider overrides the state provider used by state sync to retrieve trusted app hashes and +// build a State object for bootstrapping the node. +// WARNING: this interface is considered unstable and subject to change. +func StateProvider(stateProvider statesync.StateProvider) Option { + return func(n *Node) { + n.stateSyncProvider = stateProvider + } +} + +//------------------------------------------------------------------------------ + +// Node is the highest level interface to a full Tendermint node. +// It includes all configuration information and running services. +type Node struct { + service.BaseService + + // config + config *cfg.Config + genesisDoc *types.GenesisDoc // initial validator set + privValidator types.PrivValidator // local node's validator key + + // network + transport *p2p.MultiplexTransport + sw *p2p.Switch // p2p connections + addrBook pex.AddrBook // known peers + nodeInfo p2p.NodeInfo + nodeKey *p2p.NodeKey // our node privkey + isListening bool + + // services + eventBus *types.EventBus // pub/sub for services + stateStore sm.Store + blockStore *store.BlockStore // store the blockchain to disk + bcReactor p2p.Reactor // for fast-syncing + mempoolReactor *mempl.Reactor // for gossipping transactions + mempool mempl.Mempool + stateSync bool // whether the node should state sync on startup + stateSyncReactor *statesync.Reactor // for hosting and restoring state sync snapshots + stateSyncProvider statesync.StateProvider // provides state data for bootstrapping a node + stateSyncGenesis sm.State // provides the genesis state for state sync + consensusState *cs.State // latest consensus state + consensusReactor *cs.Reactor // for participating in the consensus + pexReactor *pex.Reactor // for exchanging peer addresses + evidencePool *evidence.Pool // tracking evidence + proxyApp proxy.AppConns // connection to the application + rpcListeners []net.Listener // rpc servers + txIndexer txindex.TxIndexer + indexerService *txindex.IndexerService + prometheusSrv *http.Server +} + +func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) { + var blockStoreDB dbm.DB + blockStoreDB, err = dbProvider(&DBContext{"blockstore", config}) + if err != nil { + return + } + blockStore = store.NewBlockStore(blockStoreDB) + + stateDB, err = dbProvider(&DBContext{"state", config}) + if err != nil { + return + } + + return +} + +func createAndStartProxyAppConns(clientCreator proxy.ClientCreator, logger log.Logger) (proxy.AppConns, error) { + proxyApp := proxy.NewAppConns(clientCreator) + proxyApp.SetLogger(logger.With("module", "proxy")) + if err := proxyApp.Start(); err != nil { + return nil, fmt.Errorf("error starting proxy app connections: %v", err) + } + return proxyApp, nil +} + +func createAndStartEventBus(logger log.Logger) (*types.EventBus, error) { + eventBus := types.NewEventBus() + eventBus.SetLogger(logger.With("module", "events")) + if err := eventBus.Start(); err != nil { + return nil, err + } + return eventBus, nil +} + +func createAndStartIndexerService(config *cfg.Config, dbProvider DBProvider, + eventBus *types.EventBus, logger log.Logger) (*txindex.IndexerService, txindex.TxIndexer, error) { + + var txIndexer txindex.TxIndexer + switch config.TxIndex.Indexer { + case "kv": + store, err := dbProvider(&DBContext{"tx_index", config}) + if err != nil { + return nil, nil, err + } + txIndexer = kv.NewTxIndex(store) + default: + txIndexer = &null.TxIndex{} + } + + indexerService := txindex.NewIndexerService(txIndexer, eventBus) + indexerService.SetLogger(logger.With("module", "txindex")) + if err := indexerService.Start(); err != nil { + return nil, nil, err + } + return indexerService, txIndexer, nil +} + +func doHandshake( + stateStore sm.Store, + state sm.State, + blockStore sm.BlockStore, + genDoc *types.GenesisDoc, + eventBus types.BlockEventPublisher, + proxyApp proxy.AppConns, + consensusLogger log.Logger) error { + + handshaker := cs.NewHandshaker(stateStore, state, blockStore, genDoc) + handshaker.SetLogger(consensusLogger) + handshaker.SetEventBus(eventBus) + if err := handshaker.Handshake(proxyApp); err != nil { + return fmt.Errorf("error during handshake: %v", err) + } + return nil +} + +func logNodeStartupInfo(state sm.State, pubKey crypto.PubKey, logger, consensusLogger log.Logger) { + // Log the version info. + logger.Info("Version info", + "software", version.TMCoreSemVer, + "block", version.BlockProtocol, + "p2p", version.P2PProtocol, + ) + + // If the state and software differ in block version, at least log it. + if state.Version.Consensus.Block != version.BlockProtocol { + logger.Info("Software and state have different block protocols", + "software", version.BlockProtocol, + "state", state.Version.Consensus.Block, + ) + } + + addr := pubKey.Address() + // Log whether this node is a validator or an observer + if state.Validators.HasAddress(addr) { + consensusLogger.Info("This node is a validator", "addr", addr, "pubKey", pubKey) + } else { + consensusLogger.Info("This node is not a validator", "addr", addr, "pubKey", pubKey) + } +} + +func onlyValidatorIsUs(state sm.State, pubKey crypto.PubKey) bool { + if state.Validators.Size() > 1 { + return false + } + addr, _ := state.Validators.GetByIndex(0) + return bytes.Equal(pubKey.Address(), addr) +} + +func createMempoolAndMempoolReactor(config *cfg.Config, proxyApp proxy.AppConns, + state sm.State, memplMetrics *mempl.Metrics, logger log.Logger) (*mempl.Reactor, *mempl.CListMempool) { + + mempool := mempl.NewCListMempool( + config.Mempool, + proxyApp.Mempool(), + state.LastBlockHeight, + mempl.WithMetrics(memplMetrics), + mempl.WithPreCheck(sm.TxPreCheck(state)), + mempl.WithPostCheck(sm.TxPostCheck(state)), + ) + mempoolLogger := logger.With("module", "mempool") + mempoolReactor := mempl.NewReactor(config.Mempool, mempool) + mempoolReactor.SetLogger(mempoolLogger) + + if config.Consensus.WaitForTxs() { + mempool.EnableTxsAvailable() + } + return mempoolReactor, mempool +} + +func createEvidenceReactor(config *cfg.Config, dbProvider DBProvider, + stateDB dbm.DB, blockStore *store.BlockStore, logger log.Logger) (*evidence.Reactor, *evidence.Pool, error) { + + evidenceDB, err := dbProvider(&DBContext{"evidence", config}) + if err != nil { + return nil, nil, err + } + evidenceLogger := logger.With("module", "evidence") + evidencePool, err := evidence.NewPool(evidenceDB, sm.NewStore(stateDB), blockStore) + if err != nil { + return nil, nil, err + } + evidenceReactor := evidence.NewReactor(evidencePool) + evidenceReactor.SetLogger(evidenceLogger) + return evidenceReactor, evidencePool, nil +} + +func createBlockchainReactor(config *cfg.Config, + state sm.State, + blockExec *sm.BlockExecutor, + blockStore *store.BlockStore, + fastSync bool, + logger log.Logger) (bcReactor p2p.Reactor, err error) { + + switch config.FastSync.Version { + case "v0": + bcReactor = bcv0.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + case "v1": + bcReactor = bcv1.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + case "v2": + bcReactor = bcv2.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + default: + return nil, fmt.Errorf("unknown fastsync version %s", config.FastSync.Version) + } + + bcReactor.SetLogger(logger.With("module", "blockchain")) + return bcReactor, nil +} + +func createConsensusReactor(config *cfg.Config, + state sm.State, + blockExec *sm.BlockExecutor, + blockStore sm.BlockStore, + mempool *mempl.CListMempool, + evidencePool *evidence.Pool, + privValidator types.PrivValidator, + csMetrics *cs.Metrics, + waitSync bool, + eventBus *types.EventBus, + consensusLogger log.Logger, + misbehaviors map[int64]cs.Misbehavior) (*cs.Reactor, *cs.State) { + + consensusState := cs.NewState( + config.Consensus, + state.Copy(), + blockExec, + blockStore, + mempool, + evidencePool, + misbehaviors, + cs.StateMetrics(csMetrics), + ) + consensusState.SetLogger(consensusLogger) + if privValidator != nil { + consensusState.SetPrivValidator(privValidator) + } + consensusReactor := cs.NewReactor(consensusState, waitSync, cs.ReactorMetrics(csMetrics)) + consensusReactor.SetLogger(consensusLogger) + // services which will be publishing and/or subscribing for messages (events) + // consensusReactor will set it on consensusState and blockExecutor + consensusReactor.SetEventBus(eventBus) + return consensusReactor, consensusState +} + +func createTransport( + config *cfg.Config, + nodeInfo p2p.NodeInfo, + nodeKey *p2p.NodeKey, + proxyApp proxy.AppConns, +) ( + *p2p.MultiplexTransport, + []p2p.PeerFilterFunc, +) { + var ( + mConnConfig = p2p.MConnConfig(config.P2P) + transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) + connFilters = []p2p.ConnFilterFunc{} + peerFilters = []p2p.PeerFilterFunc{} + ) + + if !config.P2P.AllowDuplicateIP { + connFilters = append(connFilters, p2p.ConnDuplicateIPFilter()) + } + + // Filter peers by addr or pubkey with an ABCI query. + // If the query return code is OK, add peer. + if config.FilterPeers { + connFilters = append( + connFilters, + // ABCI query for address filtering. + func(_ p2p.ConnSet, c net.Conn, _ []net.IP) error { + res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ + Path: fmt.Sprintf("/p2p/filter/addr/%s", c.RemoteAddr().String()), + }) + if err != nil { + return err + } + if res.IsErr() { + return fmt.Errorf("error querying abci app: %v", res) + } + + return nil + }, + ) + + peerFilters = append( + peerFilters, + // ABCI query for ID filtering. + func(_ p2p.IPeerSet, p p2p.Peer) error { + res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ + Path: fmt.Sprintf("/p2p/filter/id/%s", p.ID()), + }) + if err != nil { + return err + } + if res.IsErr() { + return fmt.Errorf("error querying abci app: %v", res) + } + + return nil + }, + ) + } + + p2p.MultiplexTransportConnFilters(connFilters...)(transport) + + // Limit the number of incoming connections. + max := config.P2P.MaxNumInboundPeers + len(splitAndTrimEmpty(config.P2P.UnconditionalPeerIDs, ",", " ")) + p2p.MultiplexTransportMaxIncomingConnections(max)(transport) + + return transport, peerFilters +} + +func createSwitch(config *cfg.Config, + transport p2p.Transport, + p2pMetrics *p2p.Metrics, + peerFilters []p2p.PeerFilterFunc, + mempoolReactor *mempl.Reactor, + bcReactor p2p.Reactor, + stateSyncReactor *statesync.Reactor, + consensusReactor *cs.Reactor, + evidenceReactor *evidence.Reactor, + nodeInfo p2p.NodeInfo, + nodeKey *p2p.NodeKey, + p2pLogger log.Logger) *p2p.Switch { + + sw := p2p.NewSwitch( + config.P2P, + transport, + p2p.WithMetrics(p2pMetrics), + p2p.SwitchPeerFilters(peerFilters...), + ) + sw.SetLogger(p2pLogger) + sw.AddReactor("MEMPOOL", mempoolReactor) + sw.AddReactor("BLOCKCHAIN", bcReactor) + sw.AddReactor("CONSENSUS", consensusReactor) + sw.AddReactor("EVIDENCE", evidenceReactor) + sw.AddReactor("STATESYNC", stateSyncReactor) + + sw.SetNodeInfo(nodeInfo) + sw.SetNodeKey(nodeKey) + + p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) + return sw +} + +func createAddrBookAndSetOnSwitch(config *cfg.Config, sw *p2p.Switch, + p2pLogger log.Logger, nodeKey *p2p.NodeKey) (pex.AddrBook, error) { + + addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) + addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile())) + + // Add ourselves to addrbook to prevent dialing ourselves + if config.P2P.ExternalAddress != "" { + addr, err := p2p.NewNetAddressString(p2p.IDAddressString(nodeKey.ID(), config.P2P.ExternalAddress)) + if err != nil { + return nil, fmt.Errorf("p2p.external_address is incorrect: %w", err) + } + addrBook.AddOurAddress(addr) + } + if config.P2P.ListenAddress != "" { + addr, err := p2p.NewNetAddressString(p2p.IDAddressString(nodeKey.ID(), config.P2P.ListenAddress)) + if err != nil { + return nil, fmt.Errorf("p2p.laddr is incorrect: %w", err) + } + addrBook.AddOurAddress(addr) + } + + sw.SetAddrBook(addrBook) + + return addrBook, nil +} + +func createPEXReactorAndAddToSwitch(addrBook pex.AddrBook, config *cfg.Config, + sw *p2p.Switch, logger log.Logger) *pex.Reactor { + + // TODO persistent peers ? so we can have their DNS addrs saved + pexReactor := pex.NewReactor(addrBook, + &pex.ReactorConfig{ + 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, + PersistentPeersMaxDialPeriod: config.P2P.PersistentPeersMaxDialPeriod, + }) + pexReactor.SetLogger(logger.With("module", "pex")) + sw.AddReactor("PEX", pexReactor) + return pexReactor +} + +// startStateSync starts an asynchronous state sync process, then switches to fast sync mode. +func startStateSync(ssR *statesync.Reactor, bcR fastSyncReactor, conR *cs.Reactor, + stateProvider statesync.StateProvider, config *cfg.StateSyncConfig, fastSync bool, + stateStore sm.Store, blockStore *store.BlockStore, state sm.State) error { + ssR.Logger.Info("Starting state sync") + + if stateProvider == nil { + var err error + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + stateProvider, err = statesync.NewLightClientStateProvider( + ctx, + state.ChainID, state.Version, state.InitialHeight, + config.RPCServers, light.TrustOptions{ + Period: config.TrustPeriod, + Height: config.TrustHeight, + Hash: config.TrustHashBytes(), + }, ssR.Logger.With("module", "light")) + if err != nil { + return fmt.Errorf("failed to set up light client state provider: %w", err) + } + } + + go func() { + state, commit, err := ssR.Sync(stateProvider, config.DiscoveryTime) + if err != nil { + ssR.Logger.Error("State sync failed", "err", err) + return + } + err = stateStore.Bootstrap(state) + if err != nil { + ssR.Logger.Error("Failed to bootstrap node with new state", "err", err) + return + } + err = blockStore.SaveSeenCommit(state.LastBlockHeight, commit) + if err != nil { + ssR.Logger.Error("Failed to store last seen commit", "err", err) + return + } + + if fastSync { + // FIXME Very ugly to have these metrics bleed through here. + conR.Metrics.StateSyncing.Set(0) + conR.Metrics.FastSyncing.Set(1) + err = bcR.SwitchToFastSync(state) + if err != nil { + ssR.Logger.Error("Failed to switch to fast sync", "err", err) + return + } + } else { + conR.SwitchToConsensus(state, true) + } + }() + return nil +} + +// NewNode returns a new, ready to go, Tendermint Node. +func NewNode(config *cfg.Config, + privValidator types.PrivValidator, + nodeKey *p2p.NodeKey, + clientCreator proxy.ClientCreator, + genesisDocProvider GenesisDocProvider, + dbProvider DBProvider, + metricsProvider MetricsProvider, + logger log.Logger, + misbehaviors map[int64]cs.Misbehavior, + options ...Option) (*Node, error) { + + blockStore, stateDB, err := initDBs(config, dbProvider) + if err != nil { + return nil, err + } + + stateStore := sm.NewStore(stateDB) + + state, genDoc, err := LoadStateFromDBOrGenesisDocProvider(stateDB, genesisDocProvider) + if err != nil { + return nil, err + } + + // Create the proxyApp and establish connections to the ABCI app (consensus, mempool, query). + proxyApp, err := createAndStartProxyAppConns(clientCreator, logger) + if err != nil { + return nil, err + } + + // EventBus and IndexerService must be started before the handshake because + // we might need to index the txs of the replayed block as this might not have happened + // when the node stopped last time (i.e. the node stopped after it saved the block + // but before it indexed the txs, or, endblocker panicked) + eventBus, err := createAndStartEventBus(logger) + if err != nil { + return nil, err + } + + // Transaction indexing + indexerService, txIndexer, err := createAndStartIndexerService(config, dbProvider, eventBus, logger) + if err != nil { + return nil, err + } + + // If an address is provided, listen on the socket for a connection from an + // external signing process. + if config.PrivValidatorListenAddr != "" { + // FIXME: we should start services inside OnStart + privValidator, err = createAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, genDoc.ChainID, logger) + if err != nil { + return nil, fmt.Errorf("error with private validator socket client: %w", err) + } + } + + pubKey, err := privValidator.GetPubKey() + if err != nil { + return nil, fmt.Errorf("can't get pubkey: %w", err) + } + + // Determine whether we should do state and/or fast sync. + // We don't fast-sync when the only validator is us. + fastSync := config.FastSyncMode && !onlyValidatorIsUs(state, pubKey) + stateSync := config.StateSync.Enable && !onlyValidatorIsUs(state, pubKey) + if stateSync && state.LastBlockHeight > 0 { + logger.Info("Found local state with non-zero height, skipping state sync") + stateSync = false + } + + // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, + // and replays any blocks as necessary to sync tendermint with the app. + consensusLogger := logger.With("module", "consensus") + if !stateSync { + if err := doHandshake(stateStore, state, blockStore, genDoc, eventBus, proxyApp, consensusLogger); err != nil { + return nil, err + } + + // Reload the state. It will have the Version.Consensus.App set by the + // Handshake, and may have other modifications as well (ie. depending on + // what happened during block replay). + state, err = stateStore.Load() + if err != nil { + return nil, fmt.Errorf("cannot load state: %w", err) + } + } + + logNodeStartupInfo(state, pubKey, logger, consensusLogger) + + csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider(genDoc.ChainID) + + // Make MempoolReactor + mempoolReactor, mempool := createMempoolAndMempoolReactor(config, proxyApp, state, memplMetrics, logger) + + // Make Evidence Reactor + evidenceReactor, evidencePool, err := createEvidenceReactor(config, dbProvider, stateDB, blockStore, logger) + if err != nil { + return nil, err + } + + // make block executor for consensus and blockchain reactors to execute blocks + blockExec := sm.NewBlockExecutor( + stateStore, + logger.With("module", "state"), + proxyApp.Consensus(), + mempool, + evidencePool, + sm.BlockExecutorWithMetrics(smMetrics), + ) + + // Make BlockchainReactor. Don't start fast sync if we're doing a state sync first. + bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, fastSync && !stateSync, logger) + if err != nil { + return nil, fmt.Errorf("could not create blockchain reactor: %w", err) + } + + // Make ConsensusReactor. Don't enable fully if doing a state sync and/or fast sync first. + // FIXME We need to update metrics here, since other reactors don't have access to them. + if stateSync { + csMetrics.StateSyncing.Set(1) + } else if fastSync { + csMetrics.FastSyncing.Set(1) + } + + logger.Info("Setting up maverick consensus reactor", "Misbehaviors", misbehaviors) + consensusReactor, consensusState := createConsensusReactor( + config, state, blockExec, blockStore, mempool, evidencePool, + privValidator, csMetrics, stateSync || fastSync, eventBus, consensusLogger, misbehaviors) + + // Set up state sync reactor, and schedule a sync if requested. + // FIXME The way we do phased startups (e.g. replay -> fast sync -> consensus) is very messy, + // we should clean this whole thing up. See: + // https://github.com/tendermint/tendermint/issues/4644 + stateSyncReactor := statesync.NewReactor(proxyApp.Snapshot(), proxyApp.Query(), + config.StateSync.TempDir) + stateSyncReactor.SetLogger(logger.With("module", "statesync")) + + nodeInfo, err := makeNodeInfo(config, nodeKey, txIndexer, genDoc, state) + if err != nil { + return nil, err + } + + // Setup Transport. + transport, peerFilters := createTransport(config, nodeInfo, nodeKey, proxyApp) + + // Setup Switch. + p2pLogger := logger.With("module", "p2p") + sw := createSwitch( + config, transport, p2pMetrics, peerFilters, mempoolReactor, bcReactor, + stateSyncReactor, consensusReactor, evidenceReactor, nodeInfo, nodeKey, p2pLogger, + ) + + err = sw.AddPersistentPeers(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) + if err != nil { + return nil, fmt.Errorf("could not add peers from persistent_peers field: %w", err) + } + + err = sw.AddUnconditionalPeerIDs(splitAndTrimEmpty(config.P2P.UnconditionalPeerIDs, ",", " ")) + if err != nil { + return nil, fmt.Errorf("could not add peer ids from unconditional_peer_ids field: %w", err) + } + + addrBook, err := createAddrBookAndSetOnSwitch(config, sw, p2pLogger, nodeKey) + if err != nil { + return nil, fmt.Errorf("could not create addrbook: %w", err) + } + + // Optionally, start the pex reactor + // + // TODO: + // + // We need to set Seeds and PersistentPeers on the switch, + // since it needs to be able to use these (and their DNS names) + // even if the PEX is off. We can include the DNS name in the NetAddress, + // but it would still be nice to have a clear list of the current "PersistentPeers" + // somewhere that we can return with net_info. + // + // If PEX is on, it should handle dialing the seeds. Otherwise the switch does it. + // Note we currently use the addrBook regardless at least for AddOurAddress + var pexReactor *pex.Reactor + if config.P2P.PexReactor { + pexReactor = createPEXReactorAndAddToSwitch(addrBook, config, sw, logger) + } + + if config.RPC.PprofListenAddress != "" { + go func() { + logger.Info("Starting pprof server", "laddr", config.RPC.PprofListenAddress) + logger.Error("pprof server error", "err", http.ListenAndServe(config.RPC.PprofListenAddress, nil)) + }() + } + + node := &Node{ + config: config, + genesisDoc: genDoc, + privValidator: privValidator, + + transport: transport, + sw: sw, + addrBook: addrBook, + nodeInfo: nodeInfo, + nodeKey: nodeKey, + + stateStore: stateStore, + blockStore: blockStore, + bcReactor: bcReactor, + mempoolReactor: mempoolReactor, + mempool: mempool, + consensusState: consensusState, + consensusReactor: consensusReactor, + stateSyncReactor: stateSyncReactor, + stateSync: stateSync, + stateSyncGenesis: state, // Shouldn't be necessary, but need a way to pass the genesis state + pexReactor: pexReactor, + evidencePool: evidencePool, + proxyApp: proxyApp, + txIndexer: txIndexer, + indexerService: indexerService, + eventBus: eventBus, + } + node.BaseService = *service.NewBaseService(logger, "Node", node) + + for _, option := range options { + option(node) + } + + return node, nil +} + +// OnStart starts the Node. It implements service.Service. +func (n *Node) OnStart() error { + now := tmtime.Now() + genTime := n.genesisDoc.GenesisTime + if genTime.After(now) { + n.Logger.Info("Genesis time is in the future. Sleeping until then...", "genTime", genTime) + time.Sleep(genTime.Sub(now)) + } + + // Add private IDs to addrbook to block those peers being added + n.addrBook.AddPrivateIDs(splitAndTrimEmpty(n.config.P2P.PrivatePeerIDs, ",", " ")) + + // Start the RPC server before the P2P server + // so we can eg. receive txs for the first block + if n.config.RPC.ListenAddress != "" { + listeners, err := n.startRPC() + if err != nil { + return err + } + n.rpcListeners = listeners + } + + if n.config.Instrumentation.Prometheus && + n.config.Instrumentation.PrometheusListenAddr != "" { + n.prometheusSrv = n.startPrometheusServer(n.config.Instrumentation.PrometheusListenAddr) + } + + // Start the transport. + addr, err := p2p.NewNetAddressString(p2p.IDAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) + if err != nil { + return err + } + if err := n.transport.Listen(*addr); err != nil { + return err + } + + n.isListening = true + + if n.config.Mempool.WalEnabled() { + err = n.mempool.InitWAL() + if err != nil { + return fmt.Errorf("init mempool WAL: %w", err) + } + } + + // Start the switch (the P2P server). + err = n.sw.Start() + if err != nil { + return err + } + + // Always connect to persistent peers + err = n.sw.DialPeersAsync(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) + if err != nil { + return fmt.Errorf("could not dial peers from persistent_peers field: %w", err) + } + + // Run state sync + if n.stateSync { + bcR, ok := n.bcReactor.(fastSyncReactor) + if !ok { + return fmt.Errorf("this blockchain reactor does not support switching from state sync") + } + err := startStateSync(n.stateSyncReactor, bcR, n.consensusReactor, n.stateSyncProvider, + n.config.StateSync, n.config.FastSyncMode, n.stateStore, n.blockStore, n.stateSyncGenesis) + if err != nil { + return fmt.Errorf("failed to start state sync: %w", err) + } + } + + return nil +} + +// OnStop stops the Node. It implements service.Service. +func (n *Node) OnStop() { + n.BaseService.OnStop() + + n.Logger.Info("Stopping Node") + + // first stop the non-reactor services + if err := n.eventBus.Stop(); err != nil { + n.Logger.Error("Error closing eventBus", "err", err) + } + if err := n.indexerService.Stop(); err != nil { + n.Logger.Error("Error closing indexerService", "err", err) + } + + // now stop the reactors + if err := n.sw.Stop(); err != nil { + n.Logger.Error("Error closing switch", "err", err) + } + + // stop mempool WAL + if n.config.Mempool.WalEnabled() { + n.mempool.CloseWAL() + } + + if err := n.transport.Close(); err != nil { + n.Logger.Error("Error closing transport", "err", err) + } + + n.isListening = false + + // finally stop the listeners / external services + for _, l := range n.rpcListeners { + n.Logger.Info("Closing rpc listener", "listener", l) + if err := l.Close(); err != nil { + n.Logger.Error("Error closing listener", "listener", l, "err", err) + } + } + + if pvsc, ok := n.privValidator.(service.Service); ok { + if err := pvsc.Stop(); err != nil { + n.Logger.Error("Error closing private validator", "err", err) + } + } + + if n.prometheusSrv != nil { + if err := n.prometheusSrv.Shutdown(context.Background()); err != nil { + // Error from closing listeners, or context timeout: + n.Logger.Error("Prometheus HTTP server Shutdown", "err", err) + } + } +} + +// ConfigureRPC makes sure RPC has all the objects it needs to operate. +func (n *Node) ConfigureRPC() error { + pubKey, err := n.privValidator.GetPubKey() + if err != nil { + return fmt.Errorf("can't get pubkey: %w", err) + } + rpccore.SetEnvironment(&rpccore.Environment{ + ProxyAppQuery: n.proxyApp.Query(), + ProxyAppMempool: n.proxyApp.Mempool(), + + StateStore: n.stateStore, + BlockStore: n.blockStore, + EvidencePool: n.evidencePool, + ConsensusState: n.consensusState, + P2PPeers: n.sw, + P2PTransport: n, + + PubKey: pubKey, + GenDoc: n.genesisDoc, + TxIndexer: n.txIndexer, + ConsensusReactor: &consensus.Reactor{}, + EventBus: n.eventBus, + Mempool: n.mempool, + + Logger: n.Logger.With("module", "rpc"), + + Config: *n.config.RPC, + }) + return nil +} + +func (n *Node) startRPC() ([]net.Listener, error) { + err := n.ConfigureRPC() + if err != nil { + return nil, err + } + + listenAddrs := splitAndTrimEmpty(n.config.RPC.ListenAddress, ",", " ") + + if n.config.RPC.Unsafe { + rpccore.AddUnsafeRoutes() + } + + config := rpcserver.DefaultConfig() + config.MaxBodyBytes = n.config.RPC.MaxBodyBytes + config.MaxHeaderBytes = n.config.RPC.MaxHeaderBytes + config.MaxOpenConnections = n.config.RPC.MaxOpenConnections + // If necessary adjust global WriteTimeout to ensure it's greater than + // TimeoutBroadcastTxCommit. + // See https://github.com/tendermint/tendermint/issues/3435 + if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit { + config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second + } + + // we may expose the rpc over both a unix and tcp socket + listeners := make([]net.Listener, len(listenAddrs)) + for i, listenAddr := range listenAddrs { + mux := http.NewServeMux() + rpcLogger := n.Logger.With("module", "rpc-server") + wmLogger := rpcLogger.With("protocol", "websocket") + wm := rpcserver.NewWebsocketManager(rpccore.Routes, + rpcserver.OnDisconnect(func(remoteAddr string) { + err := n.eventBus.UnsubscribeAll(context.Background(), remoteAddr) + if err != nil && err != tmpubsub.ErrSubscriptionNotFound { + wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err) + } + }), + rpcserver.ReadLimit(config.MaxBodyBytes), + ) + wm.SetLogger(wmLogger) + mux.HandleFunc("/websocket", wm.WebsocketHandler) + rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, rpcLogger) + listener, err := rpcserver.Listen( + listenAddr, + config, + ) + if err != nil { + return nil, err + } + + var rootHandler http.Handler = mux + if n.config.RPC.IsCorsEnabled() { + corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: n.config.RPC.CORSAllowedOrigins, + AllowedMethods: n.config.RPC.CORSAllowedMethods, + AllowedHeaders: n.config.RPC.CORSAllowedHeaders, + }) + rootHandler = corsMiddleware.Handler(mux) + } + if n.config.RPC.IsTLSEnabled() { + go func() { + if err := rpcserver.ServeTLS( + listener, + rootHandler, + n.config.RPC.CertFile(), + n.config.RPC.KeyFile(), + rpcLogger, + config, + ); err != nil { + n.Logger.Error("Error serving server with TLS", "err", err) + } + }() + } else { + go func() { + if err := rpcserver.Serve( + listener, + rootHandler, + rpcLogger, + config, + ); err != nil { + n.Logger.Error("Error serving server", "err", err) + } + }() + } + + listeners[i] = listener + } + + // we expose a simplified api over grpc for convenience to app devs + grpcListenAddr := n.config.RPC.GRPCListenAddress + if grpcListenAddr != "" { + config := rpcserver.DefaultConfig() + config.MaxBodyBytes = n.config.RPC.MaxBodyBytes + config.MaxHeaderBytes = n.config.RPC.MaxHeaderBytes + // NOTE: GRPCMaxOpenConnections is used, not MaxOpenConnections + config.MaxOpenConnections = n.config.RPC.GRPCMaxOpenConnections + // If necessary adjust global WriteTimeout to ensure it's greater than + // TimeoutBroadcastTxCommit. + // See https://github.com/tendermint/tendermint/issues/3435 + if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit { + config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second + } + listener, err := rpcserver.Listen(grpcListenAddr, config) + if err != nil { + return nil, err + } + go func() { + if err := grpccore.StartGRPCServer(listener); err != nil { + n.Logger.Error("Error starting gRPC server", "err", err) + } + }() + listeners = append(listeners, listener) + } + + return listeners, nil +} + +// startPrometheusServer starts a Prometheus HTTP server, listening for metrics +// collectors on addr. +func (n *Node) startPrometheusServer(addr string) *http.Server { + srv := &http.Server{ + Addr: addr, + Handler: promhttp.InstrumentMetricHandler( + prometheus.DefaultRegisterer, promhttp.HandlerFor( + prometheus.DefaultGatherer, + promhttp.HandlerOpts{MaxRequestsInFlight: n.config.Instrumentation.MaxOpenConnections}, + ), + ), + } + go func() { + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + // Error starting or closing listener: + n.Logger.Error("Prometheus HTTP server ListenAndServe", "err", err) + } + }() + return srv +} + +// Switch returns the Node's Switch. +func (n *Node) Switch() *p2p.Switch { + return n.sw +} + +// BlockStore returns the Node's BlockStore. +func (n *Node) BlockStore() *store.BlockStore { + return n.blockStore +} + +// ConsensusState returns the Node's ConsensusState. +func (n *Node) ConsensusState() *cs.State { + return n.consensusState +} + +// ConsensusReactor returns the Node's ConsensusReactor. +func (n *Node) ConsensusReactor() *cs.Reactor { + return n.consensusReactor +} + +// MempoolReactor returns the Node's mempool reactor. +func (n *Node) MempoolReactor() *mempl.Reactor { + return n.mempoolReactor +} + +// Mempool returns the Node's mempool. +func (n *Node) Mempool() mempl.Mempool { + return n.mempool +} + +// PEXReactor returns the Node's PEXReactor. It returns nil if PEX is disabled. +func (n *Node) PEXReactor() *pex.Reactor { + return n.pexReactor +} + +// EvidencePool returns the Node's EvidencePool. +func (n *Node) EvidencePool() *evidence.Pool { + return n.evidencePool +} + +// EventBus returns the Node's EventBus. +func (n *Node) EventBus() *types.EventBus { + return n.eventBus +} + +// PrivValidator returns the Node's PrivValidator. +// XXX: for convenience only! +func (n *Node) PrivValidator() types.PrivValidator { + return n.privValidator +} + +// GenesisDoc returns the Node's GenesisDoc. +func (n *Node) GenesisDoc() *types.GenesisDoc { + return n.genesisDoc +} + +// ProxyApp returns the Node's AppConns, representing its connections to the ABCI application. +func (n *Node) ProxyApp() proxy.AppConns { + return n.proxyApp +} + +// Config returns the Node's config. +func (n *Node) Config() *cfg.Config { + return n.config +} + +//------------------------------------------------------------------------------ + +func (n *Node) Listeners() []string { + return []string{ + fmt.Sprintf("Listener(@%v)", n.config.P2P.ExternalAddress), + } +} + +func (n *Node) IsListening() bool { + return n.isListening +} + +// NodeInfo returns the Node's Info from the Switch. +func (n *Node) NodeInfo() p2p.NodeInfo { + return n.nodeInfo +} + +func makeNodeInfo( + config *cfg.Config, + nodeKey *p2p.NodeKey, + txIndexer txindex.TxIndexer, + genDoc *types.GenesisDoc, + state sm.State, +) (p2p.NodeInfo, error) { + txIndexerStatus := "on" + if _, ok := txIndexer.(*null.TxIndex); ok { + txIndexerStatus = "off" + } + + var bcChannel byte + switch config.FastSync.Version { + case "v0": + bcChannel = bcv0.BlockchainChannel + case "v1": + bcChannel = bcv1.BlockchainChannel + case "v2": + bcChannel = bcv2.BlockchainChannel + default: + return nil, fmt.Errorf("unknown fastsync version %s", config.FastSync.Version) + } + + nodeInfo := p2p.DefaultNodeInfo{ + ProtocolVersion: p2p.NewProtocolVersion( + version.P2PProtocol, // global + state.Version.Consensus.Block, + state.Version.Consensus.App, + ), + DefaultNodeID: nodeKey.ID(), + Network: genDoc.ChainID, + Version: version.TMCoreSemVer, + Channels: []byte{ + bcChannel, + cs.StateChannel, cs.DataChannel, cs.VoteChannel, cs.VoteSetBitsChannel, + mempl.MempoolChannel, + evidence.EvidenceChannel, + statesync.SnapshotChannel, statesync.ChunkChannel, + }, + Moniker: config.Moniker, + Other: p2p.DefaultNodeInfoOther{ + TxIndex: txIndexerStatus, + RPCAddress: config.RPC.ListenAddress, + }, + } + + if config.P2P.PexReactor { + nodeInfo.Channels = append(nodeInfo.Channels, pex.PexChannel) + } + + lAddr := config.P2P.ExternalAddress + + if lAddr == "" { + lAddr = config.P2P.ListenAddress + } + + nodeInfo.ListenAddr = lAddr + + err := nodeInfo.Validate() + return nodeInfo, err +} + +//------------------------------------------------------------------------------ + +var ( + genesisDocKey = []byte("genesisDoc") +) + +// LoadStateFromDBOrGenesisDocProvider attempts to load the state from the +// database, or creates one using the given genesisDocProvider and persists the +// result to the database. On success this also returns the genesis doc loaded +// through the given provider. +func LoadStateFromDBOrGenesisDocProvider( + stateDB dbm.DB, + genesisDocProvider GenesisDocProvider, +) (sm.State, *types.GenesisDoc, error) { + // Get genesis doc + genDoc, err := loadGenesisDoc(stateDB) + if err != nil { + genDoc, err = genesisDocProvider() + if err != nil { + return sm.State{}, nil, err + } + // save genesis doc to prevent a certain class of user errors (e.g. when it + // was changed, accidentally or not). Also good for audit trail. + saveGenesisDoc(stateDB, genDoc) + } + stateStore := sm.NewStore(stateDB) + state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) + if err != nil { + return sm.State{}, nil, err + } + return state, genDoc, nil +} + +// panics if failed to unmarshal bytes +func loadGenesisDoc(db dbm.DB) (*types.GenesisDoc, error) { + b, err := db.Get(genesisDocKey) + if err != nil { + panic(err) + } + if len(b) == 0 { + return nil, errors.New("genesis doc not found") + } + var genDoc *types.GenesisDoc + err = tmjson.Unmarshal(b, &genDoc) + if err != nil { + panic(fmt.Sprintf("Failed to load genesis doc due to unmarshaling error: %v (bytes: %X)", err, b)) + } + return genDoc, nil +} + +// panics if failed to marshal the given genesis document +func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) { + b, err := tmjson.Marshal(genDoc) + if err != nil { + panic(fmt.Sprintf("Failed to save genesis doc due to marshaling error: %v", err)) + } + if err := db.SetSync(genesisDocKey, b); err != nil { + panic(fmt.Sprintf("Failed to save genesis doc: %v", err)) + } +} + +func createAndStartPrivValidatorSocketClient( + listenAddr, + chainID string, + logger log.Logger, +) (types.PrivValidator, error) { + pve, err := privval.NewSignerListener(listenAddr, logger) + if err != nil { + return nil, fmt.Errorf("failed to start private validator: %w", err) + } + + pvsc, err := privval.NewSignerClient(pve, chainID) + if err != nil { + return nil, fmt.Errorf("failed to start private validator: %w", err) + } + + // try to get a pubkey from private validate first time + _, err = pvsc.GetPubKey() + if err != nil { + return nil, fmt.Errorf("can't get pubkey: %w", err) + } + + const ( + retries = 50 // 50 * 100ms = 5s total + timeout = 100 * time.Millisecond + ) + pvscWithRetries := privval.NewRetrySignerClient(pvsc, retries, timeout) + + return pvscWithRetries, nil +} + +// splitAndTrimEmpty slices s into all subslices separated by sep and returns a +// slice of the string s with all leading and trailing Unicode code points +// contained in cutset removed. If sep is empty, SplitAndTrim splits after each +// UTF-8 sequence. First part is equivalent to strings.SplitN with a count of +// -1. also filter out empty strings, only return non-empty strings. +func splitAndTrimEmpty(s, sep, cutset string) []string { + if s == "" { + return []string{} + } + + spl := strings.Split(s, sep) + nonEmptyStrings := make([]string, 0, len(spl)) + for i := 0; i < len(spl); i++ { + element := strings.Trim(spl[i], cutset) + if element != "" { + nonEmptyStrings = append(nonEmptyStrings, element) + } + } + return nonEmptyStrings +} diff --git a/test/maverick/node/privval.go b/test/maverick/node/privval.go new file mode 100644 index 0000000000..441b6ca9da --- /dev/null +++ b/test/maverick/node/privval.go @@ -0,0 +1,358 @@ +package node + +import ( + "errors" + "fmt" + "io/ioutil" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmjson "github.com/tendermint/tendermint/libs/json" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/libs/tempfile" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +// ******************************************************************************************************************* +// +// WARNING: FOR TESTING ONLY. DO NOT USE THIS FILE OUTSIDE MAVERICK +// +// ******************************************************************************************************************* + +const ( + stepNone int8 = 0 // Used to distinguish the initial state + stepPropose int8 = 1 + stepPrevote int8 = 2 + stepPrecommit int8 = 3 +) + +// A vote is either stepPrevote or stepPrecommit. +func voteToStep(vote *tmproto.Vote) int8 { + switch vote.Type { + case tmproto.PrevoteType: + return stepPrevote + case tmproto.PrecommitType: + return stepPrecommit + default: + panic(fmt.Sprintf("Unknown vote type: %v", vote.Type)) + } +} + +//------------------------------------------------------------------------------- + +// FilePVKey stores the immutable part of PrivValidator. +type FilePVKey struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` + + filePath string +} + +// Save persists the FilePVKey to its filePath. +func (pvKey FilePVKey) Save() { + outFile := pvKey.filePath + if outFile == "" { + panic("cannot save PrivValidator key: filePath not set") + } + + jsonBytes, err := tmjson.MarshalIndent(pvKey, "", " ") + if err != nil { + panic(err) + } + err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } + +} + +//------------------------------------------------------------------------------- + +// FilePVLastSignState stores the mutable part of PrivValidator. +type FilePVLastSignState struct { + Height int64 `json:"height"` + Round int32 `json:"round"` + Step int8 `json:"step"` + Signature []byte `json:"signature,omitempty"` + SignBytes tmbytes.HexBytes `json:"signbytes,omitempty"` + + filePath string +} + +// CheckHRS checks the given height, round, step (HRS) against that of the +// FilePVLastSignState. It returns an error if the arguments constitute a regression, +// or if they match but the SignBytes are empty. +// The returned boolean indicates whether the last Signature should be reused - +// it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating +// we have already signed for this HRS, and can reuse the existing signature). +// It panics if the HRS matches the arguments, there's a SignBytes, but no Signature. +func (lss *FilePVLastSignState) CheckHRS(height int64, round int32, step int8) (bool, error) { + + if lss.Height > height { + return false, fmt.Errorf("height regression. Got %v, last height %v", height, lss.Height) + } + + if lss.Height == height { + if lss.Round > round { + return false, fmt.Errorf("round regression at height %v. Got %v, last round %v", height, round, lss.Round) + } + + if lss.Round == round { + if lss.Step > step { + return false, fmt.Errorf( + "step regression at height %v round %v. Got %v, last step %v", + height, + round, + step, + lss.Step, + ) + } else if lss.Step == step { + if lss.SignBytes != nil { + if lss.Signature == nil { + panic("pv: Signature is nil but SignBytes is not!") + } + return true, nil + } + return false, errors.New("no SignBytes found") + } + } + } + return false, nil +} + +// Save persists the FilePvLastSignState to its filePath. +func (lss *FilePVLastSignState) Save() { + outFile := lss.filePath + if outFile == "" { + panic("cannot save FilePVLastSignState: filePath not set") + } + jsonBytes, err := tmjson.MarshalIndent(lss, "", " ") + if err != nil { + panic(err) + } + err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } +} + +//------------------------------------------------------------------------------- + +// FilePV implements PrivValidator using data persisted to disk +// to prevent double signing. +// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. +// It includes the LastSignature and LastSignBytes so we don't lose the signature +// if the process crashes after signing but before the resulting consensus message is processed. +type FilePV struct { + Key FilePVKey + LastSignState FilePVLastSignState +} + +// GenFilePV generates a new validator with randomly generated private key +// and sets the filePaths, but does not call Save(). +func GenFilePV(keyFilePath, stateFilePath string) *FilePV { + privKey := ed25519.GenPrivKey() + + return &FilePV{ + Key: FilePVKey{ + Address: privKey.PubKey().Address(), + PubKey: privKey.PubKey(), + PrivKey: privKey, + filePath: keyFilePath, + }, + LastSignState: FilePVLastSignState{ + Step: stepNone, + filePath: stateFilePath, + }, + } +} + +// LoadFilePV loads a FilePV from the filePaths. The FilePV handles double +// signing prevention by persisting data to the stateFilePath. If either file path +// does not exist, the program will exit. +func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, true) +} + +// LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState. +// If the keyFilePath does not exist, the program will exit. +func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, false) +} + +// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState. +func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV { + keyJSONBytes, err := ioutil.ReadFile(keyFilePath) + if err != nil { + tmos.Exit(err.Error()) + } + pvKey := FilePVKey{} + err = tmjson.Unmarshal(keyJSONBytes, &pvKey) + if err != nil { + tmos.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err)) + } + + // overwrite pubkey and address for convenience + pvKey.PubKey = pvKey.PrivKey.PubKey() + pvKey.Address = pvKey.PubKey.Address() + pvKey.filePath = keyFilePath + + pvState := FilePVLastSignState{} + + if loadState { + stateJSONBytes, err := ioutil.ReadFile(stateFilePath) + if err != nil { + tmos.Exit(err.Error()) + } + err = tmjson.Unmarshal(stateJSONBytes, &pvState) + if err != nil { + tmos.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err)) + } + } + + pvState.filePath = stateFilePath + + return &FilePV{ + Key: pvKey, + LastSignState: pvState, + } +} + +// LoadOrGenFilePV loads a FilePV from the given filePaths +// or else generates a new one and saves it to the filePaths. +func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV { + var pv *FilePV + if tmos.FileExists(keyFilePath) { + pv = LoadFilePV(keyFilePath, stateFilePath) + } else { + pv = GenFilePV(keyFilePath, stateFilePath) + pv.Save() + } + return pv +} + +// GetAddress returns the address of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetAddress() types.Address { + return pv.Key.Address +} + +// GetPubKey returns the public key of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetPubKey() (crypto.PubKey, error) { + return pv.Key.PubKey, nil +} + +// SignVote signs a canonical representation of the vote, along with the +// chainID. Implements PrivValidator. +func (pv *FilePV) SignVote(chainID string, vote *tmproto.Vote) error { + if err := pv.signVote(chainID, vote); err != nil { + return fmt.Errorf("error signing vote: %v", err) + } + return nil +} + +// SignProposal signs a canonical representation of the proposal, along with +// the chainID. Implements PrivValidator. +func (pv *FilePV) SignProposal(chainID string, proposal *tmproto.Proposal) error { + if err := pv.signProposal(chainID, proposal); err != nil { + return fmt.Errorf("error signing proposal: %v", err) + } + return nil +} + +// Save persists the FilePV to disk. +func (pv *FilePV) Save() { + pv.Key.Save() + pv.LastSignState.Save() +} + +// Reset resets all fields in the FilePV. +// NOTE: Unsafe! +func (pv *FilePV) Reset() { + var sig []byte + pv.LastSignState.Height = 0 + pv.LastSignState.Round = 0 + pv.LastSignState.Step = 0 + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = nil + pv.Save() +} + +// String returns a string representation of the FilePV. +func (pv *FilePV) String() string { + return fmt.Sprintf( + "PrivValidator{%v LH:%v, LR:%v, LS:%v}", + pv.GetAddress(), + pv.LastSignState.Height, + pv.LastSignState.Round, + pv.LastSignState.Step, + ) +} + +//------------------------------------------------------------------------------------ + +// signVote checks if the vote is good to sign and sets the vote signature. +// It may need to set the timestamp as well if the vote is otherwise the same as +// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). +func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { + height, round, step := vote.Height, vote.Round, voteToStep(vote) + + lss := pv.LastSignState + + _, err := lss.CheckHRS(height, round, step) + if err != nil { + return err + } + + signBytes := types.VoteSignBytes(chainID, vote) + + // It passed the checks. Sign the vote + sig, err := pv.Key.PrivKey.Sign(signBytes) + if err != nil { + return err + } + pv.saveSigned(height, round, step, signBytes, sig) + vote.Signature = sig + return nil +} + +// signProposal checks if the proposal is good to sign and sets the proposal signature. +// It may need to set the timestamp as well if the proposal is otherwise the same as +// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL). +func (pv *FilePV) signProposal(chainID string, proposal *tmproto.Proposal) error { + height, round, step := proposal.Height, proposal.Round, stepPropose + + lss := pv.LastSignState + + _, err := lss.CheckHRS(height, round, step) + if err != nil { + return err + } + + signBytes := types.ProposalSignBytes(chainID, proposal) + + // It passed the checks. Sign the proposal + sig, err := pv.Key.PrivKey.Sign(signBytes) + if err != nil { + return err + } + pv.saveSigned(height, round, step, signBytes, sig) + proposal.Signature = sig + return nil +} + +// Persist height/round/step and signature +func (pv *FilePV) saveSigned(height int64, round int32, step int8, + signBytes []byte, sig []byte) { + + pv.LastSignState.Height = height + pv.LastSignState.Round = round + pv.LastSignState.Step = step + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = signBytes + pv.LastSignState.Save() +} From a0f08686fbadfc4b8e621336d7575f22593c0671 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 23 Oct 2020 00:14:59 +0200 Subject: [PATCH 046/108] github: only notify nightly E2E failures once (#5559) --- .github/workflows/e2e-nightly.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml index 7a060669b0..9042d7f4ca 100644 --- a/.github/workflows/e2e-nightly.yml +++ b/.github/workflows/e2e-nightly.yml @@ -32,14 +32,18 @@ jobs: working-directory: test/e2e run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml + e2e-nightly-fail: + needs: e2e-nightly-test + if: ${{ failure() }} + runs-on: ubuntu-latest + steps: - name: Notify Slack on failure uses: rtCamp/action-slack-notify@e9db0ef - if: ${{ failure() }} env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_CHANNEL: tendermint-internal - SLACK_USERNAME: Nightly E2E Test Failure + SLACK_USERNAME: Nightly E2E Tests SLACK_ICON_EMOJI: ':skull:' SLACK_COLOR: danger - SLACK_MESSAGE: Nightly E2E test failed (group ${{ matrix.group }}) + SLACK_MESSAGE: Nightly E2E tests failed SLACK_FOOTER: '' From c4f1b2d7dbd23e97e8729d77cbcc28fa836e77cf Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 26 Oct 2020 10:18:59 +0100 Subject: [PATCH 047/108] block: fix max commit sig size (#5567) --- CHANGELOG_PENDING.md | 1 + state/tx_filter_test.go | 4 ++-- types/block.go | 6 ++++-- types/block_test.go | 37 ++++++++++++++----------------------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 122bcac87d..75062dc035 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -30,3 +30,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) - [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) - [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) +- [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index c1bbfd3463..7936d94c75 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -25,8 +25,8 @@ func TestTxFilter(t *testing.T) { tx types.Tx isErr bool }{ - {types.Tx(tmrand.Bytes(2187)), false}, - {types.Tx(tmrand.Bytes(2188)), true}, + {types.Tx(tmrand.Bytes(2155)), false}, + {types.Tx(tmrand.Bytes(2156)), true}, {types.Tx(tmrand.Bytes(3000)), true}, } diff --git a/types/block.go b/types/block.go index bf48abad87..0bdb2be675 100644 --- a/types/block.go +++ b/types/block.go @@ -24,6 +24,8 @@ import ( const ( // MaxHeaderBytes is a maximum header size. + // NOTE: Because app hash can be of arbitrary size, the header is therefore not + // capped in size and thus this number should be seen as a soft max MaxHeaderBytes int64 = 626 // MaxOverheadForBlock - maximum overhead to encode a block (up to @@ -583,9 +585,9 @@ const ( const ( // Max size of commit without any commitSigs -> 82 for BlockID, 8 for Height, 4 for Round. MaxCommitOverheadBytes int64 = 94 - // Commit sig size is made up of 32 bytes for the signature, 20 bytes for the address, + // Commit sig size is made up of 64 bytes for the signature, 20 bytes for the address, // 1 byte for the flag and 14 bytes for the timestamp - MaxCommitSigBytes int64 = 77 + MaxCommitSigBytes int64 = 109 ) // CommitSig is a part of the Vote included in a Commit. diff --git a/types/block_test.go b/types/block_test.go index 1565f5be0a..2bdeac7f0e 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -257,33 +257,22 @@ func TestCommitValidateBasic(t *testing.T) { } } -func TestMaxCommitSigBytes(t *testing.T) { +func TestMaxCommitBytes(t *testing.T) { // time is varint encoded so need to pick the max. // year int, month Month, day, hour, min, sec, nsec int, loc *Location timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - cs := &CommitSig{ - BlockIDFlag: BlockIDFlagNil, - ValidatorAddress: crypto.AddressHash([]byte("validator_address")), - Timestamp: timestamp, - Signature: tmhash.Sum([]byte("signature")), - } - - pb := cs.ToProto() - - assert.EqualValues(t, MaxCommitSigBytes, pb.Size()) -} - -func TestMaxCommitBytes(t *testing.T) { - timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - cs := CommitSig{ BlockIDFlag: BlockIDFlagNil, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), Timestamp: timestamp, - Signature: tmhash.Sum([]byte("signature")), + Signature: crypto.CRandBytes(MaxSignatureSize), } + pbSig := cs.ToProto() + // test that a single commit sig doesn't exceed max commit sig bytes + assert.EqualValues(t, MaxCommitSigBytes, pbSig.Size()) + // check size with a single commit commit := &Commit{ Height: math.MaxInt64, @@ -463,9 +452,11 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {809, 1, 0, true, 0}, - 3: {810, 1, 0, false, 0}, - 4: {811, 1, 0, false, 1}, + 2: {841, 1, 0, true, 0}, + 3: {842, 1, 0, false, 0}, + 4: {843, 1, 0, false, 1}, + 5: {954, 2, 0, false, 1}, + 6: {1053, 2, 100, false, 0}, } for i, tc := range testCases { @@ -492,9 +483,9 @@ func TestBlockMaxDataBytesNoEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {809, 1, true, 0}, - 3: {810, 1, false, 0}, - 4: {811, 1, false, 1}, + 2: {841, 1, true, 0}, + 3: {842, 1, false, 0}, + 4: {843, 1, false, 1}, } for i, tc := range testCases { From ceea64ec28ce3d572d626e519d6a59e6cc3573b6 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 26 Oct 2020 11:09:46 +0100 Subject: [PATCH 048/108] test: fix handling of start height in generated E2E testnets (#5563) In #5488 the E2E testnet generator changed to setting explicit `StartAt` heights for initial nodes. This broke the runner, which expected all initial nodes to have `StartAt: 0`, as well as validator set scheduling in the generator. Testnet loading now normalizes initial nodes to have `StartAt: 0`. This also tweaks waiting for misbehavior heights to only use an additional wait if there actually is any misbehavior in the testnet, and to output information when waiting. --- test/e2e/generator/generate.go | 2 +- test/e2e/pkg/testnet.go | 23 ++++++++++++++++++++++- test/e2e/runner/main.go | 11 +++++++---- test/e2e/runner/start.go | 6 ++++++ test/e2e/runner/wait.go | 31 +++++++------------------------ 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 2ccbd3a6bf..a7da445965 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -104,7 +104,7 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er name := fmt.Sprintf("validator%02d", i) manifest.Nodes[name] = generateNode(r, e2e.ModeValidator, startAt, i <= 2) - if startAt == 0 { + if startAt == manifest.InitialHeight { (*manifest.Validators)[name] = int64(30 + r.Intn(71)) } else { manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{ diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index c2f55bc3d0..3425d70417 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -151,6 +151,9 @@ func LoadTestnet(file string) (*Testnet, error) { Perturbations: []Perturbation{}, Misbehaviors: make(map[int64]string), } + if node.StartAt == testnet.InitialHeight { + node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this + } if nodeManifest.Mode != "" { node.Mode = Mode(nodeManifest.Mode) } @@ -341,7 +344,12 @@ func (n Node) Validate(testnet Testnet) error { for height, misbehavior := range n.Misbehaviors { if height < n.StartAt { - return fmt.Errorf("misbehavior height %d is before start height %d", height, n.StartAt) + return fmt.Errorf("misbehavior height %d is below node start height %d", + height, n.StartAt) + } + if height < testnet.InitialHeight { + return fmt.Errorf("misbehavior height %d is below network initial height %d", + height, testnet.InitialHeight) } exists := false for possibleBehaviors := range mcs.MisbehaviorList { @@ -395,6 +403,19 @@ func (t Testnet) IPv6() bool { return t.IP.IP.To4() == nil } +// LastMisbehaviorHeight returns the height of the last misbehavior. +func (t Testnet) LastMisbehaviorHeight() int64 { + lastHeight := int64(0) + for _, node := range t.Nodes { + for height := range node.Misbehaviors { + if height > lastHeight { + lastHeight = height + } + } + } + return lastHeight +} + // Address returns a P2P endpoint address for the node. func (n Node) AddressP2P(withID bool) string { ip := n.IP.String() diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index bcca5b8999..b20454e6b0 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -69,13 +69,16 @@ func NewCLI() *CLI { if err := Start(cli.testnet); err != nil { return err } - if err := waitForAllMisbehaviors(cli.testnet); err != nil { - return err + if lastMisbehavior := cli.testnet.LastMisbehaviorHeight(); lastMisbehavior > 0 { + // wait for misbehaviors before starting perturbations + if err := WaitUntil(cli.testnet, lastMisbehavior+5); err != nil { + return err + } } if err := Perturb(cli.testnet); err != nil { return err } - if err := Wait(cli.testnet, interphaseWaitPeriod); err != nil { // allow some txs to go through + if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through return err } @@ -84,7 +87,7 @@ func NewCLI() *CLI { return err } // wait for network to settle before tests - if err := Wait(cli.testnet, interphaseWaitPeriod); err != nil { + if err := Wait(cli.testnet, 5); err != nil { return err } if err := Test(cli.testnet); err != nil { diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index 915222faf5..b755f8965f 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -15,6 +15,12 @@ func Start(testnet *e2e.Testnet) error { sort.SliceStable(nodeQueue, func(i, j int) bool { return nodeQueue[i].StartAt < nodeQueue[j].StartAt }) + if len(nodeQueue) == 0 { + return fmt.Errorf("no nodes in testnet") + } + if nodeQueue[0].StartAt > 0 { + return fmt.Errorf("no initial nodes in testnet") + } // Start initial nodes (StartAt: 0) logger.Info("Starting initial network nodes...") diff --git a/test/e2e/runner/wait.go b/test/e2e/runner/wait.go index c53032c307..8e9030856e 100644 --- a/test/e2e/runner/wait.go +++ b/test/e2e/runner/wait.go @@ -7,8 +7,6 @@ import ( e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) -const interphaseWaitPeriod = 5 - // Wait waits for a number of blocks to be produced, and for all nodes to catch // up with it. func Wait(testnet *e2e.Testnet, blocks int64) error { @@ -16,30 +14,15 @@ func Wait(testnet *e2e.Testnet, blocks int64) error { if err != nil { return err } - waitFor := block.Height + blocks - logger.Info(fmt.Sprintf("Waiting for all nodes to reach height %v...", waitFor)) - _, err = waitForAllNodes(testnet, waitFor, 20*time.Second) + return WaitUntil(testnet, block.Height+blocks) +} + +// WaitUntil waits until a given height has been reached. +func WaitUntil(testnet *e2e.Testnet, height int64) error { + logger.Info(fmt.Sprintf("Waiting for all nodes to reach height %v...", height)) + _, err := waitForAllNodes(testnet, height, 20*time.Second) if err != nil { return err } return nil } - -// WaitForAllMisbehaviors calculates the height of the last misbehavior and ensures the entire -// testnet has surpassed this height before moving on to the next phase -func waitForAllMisbehaviors(testnet *e2e.Testnet) error { - _, _, err := waitForHeight(testnet, lastMisbehaviorHeight(testnet)) - return err -} - -func lastMisbehaviorHeight(testnet *e2e.Testnet) int64 { - lastHeight := testnet.InitialHeight - for _, n := range testnet.Nodes { - for height := range n.Misbehaviors { - if height > lastHeight { - lastHeight = height - } - } - } - return lastHeight + interphaseWaitPeriod -} From f093d5837bc6e0993443947b0cded27e8698ff70 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 26 Oct 2020 11:28:11 +0100 Subject: [PATCH 049/108] test: disable E2E misbehaviors due to bugs (#5569) Disables misbehaviors in E2E testnets due to failures caused by #5554 and #5560. Should be re-enabled once these are fixed. --- test/e2e/generator/generate.go | 7 +++++-- test/e2e/networks/ci.toml | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index a7da445965..37779248e3 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -41,8 +41,11 @@ var ( "restart": 0.1, } nodeMisbehaviors = weightedChoice{ - misbehaviorOption{"double-prevote"}: 1, - misbehaviorOption{}: 9, + // FIXME Disabled due to: + // https://github.com/tendermint/tendermint/issues/5554 + // https://github.com/tendermint/tendermint/issues/5560 + // misbehaviorOption{"double-prevote"}: 1, + misbehaviorOption{}: 9, } ) diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 9085736a40..67cd6fbb63 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -36,7 +36,10 @@ seeds = ["seed01"] seeds = ["seed01"] snapshot_interval = 5 perturb = ["disconnect"] -misbehaviors = { 1012 = "double-prevote", 1018 = "double-prevote" } +# FIXME Evidence handling causes panics and halts +# https://github.com/tendermint/tendermint/issues/5554 +# https://github.com/tendermint/tendermint/issues/5560 +#misbehaviors = { 1012 = "double-prevote", 1018 = "double-prevote" } [node.validator02] seeds = ["seed02"] From 8329d12c18a31f9d387b82256e37b15d5375eab1 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 23 Oct 2020 14:52:01 +0200 Subject: [PATCH 050/108] abci/grpc: fix ordering of sync/async callback combinations (#5556) Fixes #5540, fixes #2965. This is a hack that patches over the problem, but really the whole async handling in gRPC should be redesigned, as should ReqRes callback dispatch. --- abci/client/grpc_client.go | 64 ++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 265b55532c..0f3aa75a50 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -3,6 +3,7 @@ package abcicli import ( "fmt" "net" + "sync" "time" "golang.org/x/net/context" @@ -65,6 +66,9 @@ func (cli *grpcClient) OnStart() error { cli.mtx.Lock() defer cli.mtx.Unlock() + reqres.SetDone() + reqres.Done() + // Notify client listener if set if cli.resCb != nil { cli.resCb(reqres.Request, reqres.Response) @@ -298,15 +302,43 @@ func (cli *grpcClient) ApplySnapshotChunkAsync(params types.RequestApplySnapshot return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_ApplySnapshotChunk{ApplySnapshotChunk: res}}) } +// finishAsyncCall creates a ReqRes for an async call, and immediately populates it +// with the response. We don't complete it until it's been ordered via the channel. func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes { reqres := NewReqRes(req) - reqres.Response = res // Set response - reqres.Done() // Release waiters - reqres.SetDone() // so reqRes.SetCallback will run the callback + reqres.Response = res cli.chReqRes <- reqres // use channel for async responses, since they must be ordered return reqres } +// finishSyncCall waits for an async call to complete. It is necessary to call all +// sync calls asynchronously as well, to maintain call and response ordering via +// the channel, and this method will wait until the async call completes. +func (cli *grpcClient) finishSyncCall(reqres *ReqRes) *types.Response { + // It's possible that the callback is called twice, since the callback can + // be called immediately on SetCallback() in addition to after it has been + // set. This is because completing the ReqRes happens in a separate critical + // section from the one where the callback is called: there is a race where + // SetCallback() is called between completing the ReqRes and dispatching the + // callback. + // + // We also buffer the channel with 1 response, since SetCallback() will be + // called synchronously if the reqres is already completed, in which case + // it will block on sending to the channel since it hasn't gotten around to + // receiving from it yet. + // + // ReqRes should really handle callback dispatch internally, to guarantee + // that it's only called once and avoid the above race conditions. + var once sync.Once + ch := make(chan *types.Response, 1) + reqres.SetCallback(func(res *types.Response) { + once.Do(func() { + ch <- res + }) + }) + return <-ch +} + //---------------------------------------- func (cli *grpcClient) FlushSync() error { @@ -316,12 +348,12 @@ func (cli *grpcClient) FlushSync() error { func (cli *grpcClient) EchoSync(msg string) (*types.ResponseEcho, error) { reqres := cli.EchoAsync(msg) // StopForError should already have been called if error is set - return reqres.Response.GetEcho(), cli.Error() + return cli.finishSyncCall(reqres).GetEcho(), cli.Error() } func (cli *grpcClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) { reqres := cli.InfoAsync(req) - return reqres.Response.GetInfo(), cli.Error() + return cli.finishSyncCall(reqres).GetInfo(), cli.Error() } func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) { @@ -331,57 +363,57 @@ func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.Respons func (cli *grpcClient) DeliverTxSync(params types.RequestDeliverTx) (*types.ResponseDeliverTx, error) { reqres := cli.DeliverTxAsync(params) - return reqres.Response.GetDeliverTx(), cli.Error() + return cli.finishSyncCall(reqres).GetDeliverTx(), cli.Error() } func (cli *grpcClient) CheckTxSync(params types.RequestCheckTx) (*types.ResponseCheckTx, error) { reqres := cli.CheckTxAsync(params) - return reqres.Response.GetCheckTx(), cli.Error() + return cli.finishSyncCall(reqres).GetCheckTx(), cli.Error() } func (cli *grpcClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) { reqres := cli.QueryAsync(req) - return reqres.Response.GetQuery(), cli.Error() + return cli.finishSyncCall(reqres).GetQuery(), cli.Error() } func (cli *grpcClient) CommitSync() (*types.ResponseCommit, error) { reqres := cli.CommitAsync() - return reqres.Response.GetCommit(), cli.Error() + return cli.finishSyncCall(reqres).GetCommit(), cli.Error() } func (cli *grpcClient) InitChainSync(params types.RequestInitChain) (*types.ResponseInitChain, error) { reqres := cli.InitChainAsync(params) - return reqres.Response.GetInitChain(), cli.Error() + return cli.finishSyncCall(reqres).GetInitChain(), cli.Error() } func (cli *grpcClient) BeginBlockSync(params types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { reqres := cli.BeginBlockAsync(params) - return reqres.Response.GetBeginBlock(), cli.Error() + return cli.finishSyncCall(reqres).GetBeginBlock(), cli.Error() } func (cli *grpcClient) EndBlockSync(params types.RequestEndBlock) (*types.ResponseEndBlock, error) { reqres := cli.EndBlockAsync(params) - return reqres.Response.GetEndBlock(), cli.Error() + return cli.finishSyncCall(reqres).GetEndBlock(), cli.Error() } func (cli *grpcClient) ListSnapshotsSync(params types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { reqres := cli.ListSnapshotsAsync(params) - return reqres.Response.GetListSnapshots(), cli.Error() + return cli.finishSyncCall(reqres).GetListSnapshots(), cli.Error() } func (cli *grpcClient) OfferSnapshotSync(params types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { reqres := cli.OfferSnapshotAsync(params) - return reqres.Response.GetOfferSnapshot(), cli.Error() + return cli.finishSyncCall(reqres).GetOfferSnapshot(), cli.Error() } func (cli *grpcClient) LoadSnapshotChunkSync( params types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { reqres := cli.LoadSnapshotChunkAsync(params) - return reqres.Response.GetLoadSnapshotChunk(), cli.Error() + return cli.finishSyncCall(reqres).GetLoadSnapshotChunk(), cli.Error() } func (cli *grpcClient) ApplySnapshotChunkSync( params types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { reqres := cli.ApplySnapshotChunkAsync(params) - return reqres.Response.GetApplySnapshotChunk(), cli.Error() + return cli.finishSyncCall(reqres).GetApplySnapshotChunk(), cli.Error() } From 4947333e676f2770149bfea68d42342ff09d47a5 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 20 Oct 2020 18:47:40 +0200 Subject: [PATCH 051/108] evidence: don't gossip consensus evidence too soon (#5528) and don't return errors on seeing the same evidence twice --- consensus/state.go | 7 +++++-- evidence/pool.go | 9 ++++++--- evidence/pool_test.go | 13 +++++++------ evidence/reactor.go | 2 +- evidence/reactor_test.go | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index bed855e6c4..9397527343 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1871,10 +1871,13 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { } else { timestamp = sm.MedianTime(cs.LastCommit.MakeCommit(), cs.LastValidators) } - evidenceErr := cs.evpool.AddEvidenceFromConsensus( - types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB), timestamp, cs.Validators) + evidence := types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB) + evidenceErr := cs.evpool.AddEvidenceFromConsensus(evidence, timestamp, cs.Validators) + if evidenceErr != nil { cs.Logger.Error("Failed to add evidence to the evidence pool", "err", evidenceErr) + } else { + cs.Logger.Debug("Added evidence to the evidence pool", "evidence", evidence) } return added, err } else if err == types.ErrVoteNonDeterministicSignature { diff --git a/evidence/pool.go b/evidence/pool.go index 87ae46e3dd..74d9560193 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -122,7 +122,8 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { // We have already verified this piece of evidence - no need to do it again if evpool.isPending(ev) { - return errors.New("evidence already verified and added") + evpool.logger.Info("Evidence already pending, ignoring this one", "ev", ev) + return nil } // 1) Verify against state. @@ -152,8 +153,10 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, totalPower int64 ) + // we already have this evidence, log this but don't return an error. if evpool.isPending(ev) { - return errors.New("evidence already verified and added") // we already have this evidence + evpool.logger.Info("Evidence already pending, ignoring this one", "ev", ev) + return nil } switch ev := ev.(type) { @@ -175,7 +178,7 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, if err := evpool.addPendingEvidence(evInfo); err != nil { return fmt.Errorf("can't add evidence to pending list: %w", err) } - + // add evidence to be gossiped with peers evpool.evidenceList.PushBack(ev) evpool.logger.Info("Verified new evidence of byzantine behavior", "evidence", ev) diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 40949ef891..9dc657221b 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -88,7 +88,7 @@ func TestEvidencePoolBasic(t *testing.T) { assert.Equal(t, int64(357), size) // check that the size of the single evidence in bytes is correct // shouldn't be able to add evidence twice - assert.Error(t, pool.AddEvidence(ev)) + assert.NoError(t, pool.AddEvidence(ev)) evs, _ = pool.PendingEvidence(defaultEvidenceMaxBytes) assert.Equal(t, 1, len(evs)) @@ -147,17 +147,18 @@ func TestAddEvidenceFromConsensus(t *testing.T) { var height int64 = 10 pool, val := defaultTestPool(height) ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) - err := pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime, - types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(2)})) + valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(2)}) + err := pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime, valSet) assert.NoError(t, err) next := pool.EvidenceFront() assert.Equal(t, ev, next.Value.(types.Evidence)) + // shouldn't be able to submit the same evidence twice err = pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime.Add(-1*time.Second), types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(3)})) - if assert.Error(t, err) { - assert.Equal(t, "evidence already verified and added", err.Error()) - } + assert.NoError(t, err) + evs, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) + assert.Equal(t, 1, len(evs)) } func TestEvidencePoolUpdate(t *testing.T) { diff --git a/evidence/reactor.go b/evidence/reactor.go index a984008ba9..aa2ce6ed2d 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -177,7 +177,7 @@ func (evR Reactor) checkSendEvidenceMessage( ageNumBlocks = peerHeight - evHeight ) - if peerHeight < evHeight { // peer is behind. sleep while he catches up + if peerHeight <= evHeight { // peer is behind. sleep while he catches up return nil, true } else if ageNumBlocks > params.MaxAgeNumBlocks { // evidence is too old relative to the peer, skip diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index 8b05ab0fd0..2980a692d7 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -215,7 +215,7 @@ func TestReactorSelectiveBroadcast(t *testing.T) { evList := sendEvidence(t, pools[0], val, numEvidence) // only ones less than the peers height should make it through - waitForEvidence(t, evList[:numEvidence/2], pools[1:2]) + waitForEvidence(t, evList[:numEvidence/2-1], pools[1:2]) // peers should still be connected peers := reactors[1].Switch.Peers().List() From 5cfe035362ce9a64b2fd78250aa0ffbcbe943cc6 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 27 Oct 2020 17:11:58 +0100 Subject: [PATCH 052/108] evidence: don't send committed evidence and ignore inbound evidence that is already committed (#5574) --- CHANGELOG_PENDING.md | 1 + evidence/pool.go | 23 +++- evidence/reactor.go | 37 ++++--- evidence/reactor_test.go | 229 ++++++++++++++++++++++++++------------- evidence/verify.go | 5 - 5 files changed, 198 insertions(+), 97 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 75062dc035..fbf3a7fd1a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -31,3 +31,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) - [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) - [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) +- [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) diff --git a/evidence/pool.go b/evidence/pool.go index 74d9560193..580f8c9e8c 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -83,7 +83,7 @@ func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, // PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence. func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) { - if atomic.LoadUint32(&evpool.evidenceSize) == 0 { + if evpool.Size() == 0 { return []types.Evidence{}, 0 } evidence, size, err := evpool.listEvidence(baseKeyPending, maxBytes) @@ -110,7 +110,7 @@ func (evpool *Pool) Update(state sm.State) { evpool.updateState(state) // prune pending evidence when it has expired. This also updates when the next evidence will expire - if atomic.LoadUint32(&evpool.evidenceSize) > 0 && state.LastBlockHeight > evpool.pruningHeight && + if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight && state.LastBlockTime.After(evpool.pruningTime) { evpool.pruningHeight, evpool.pruningTime = evpool.removeExpiredPendingEvidence() } @@ -126,6 +126,14 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { return nil } + // check that the evidence isn't already committed + if evpool.isCommitted(ev) { + // this can happen if the peer that sent us the evidence is behind so we shouldn't + // punish the peer. + evpool.logger.Debug("Evidence was already committed, ignoring this one", "ev", ev) + return nil + } + // 1) Verify against state. evInfo, err := evpool.verify(ev) if err != nil { @@ -197,12 +205,19 @@ func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error { ok := evpool.fastCheck(ev) if !ok { + // check that the evidence isn't already committed + if evpool.isCommitted(ev) { + return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("evidence was already committed")} + } + evInfo, err := evpool.verify(ev) if err != nil { return &types.ErrInvalidEvidence{Evidence: ev, Reason: err} } if err := evpool.addPendingEvidence(evInfo); err != nil { + // Something went wrong with adding the evidence but we already know it is valid + // hence we log an error and continue evpool.logger.Error("Can't add evidence to pending list", "err", err, "evInfo", evInfo) } @@ -315,6 +330,10 @@ func (evpool *Pool) SetLogger(l log.Logger) { evpool.logger = l } +func (evpool *Pool) Size() uint32 { + return atomic.LoadUint32(&evpool.evidenceSize) +} + // State returns the current state of the evpool. func (evpool *Pool) State() sm.State { evpool.mtx.Lock() diff --git a/evidence/reactor.go b/evidence/reactor.go index aa2ce6ed2d..e9003ded18 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -17,8 +17,13 @@ const ( maxMsgSize = 1048576 // 1MB TODO make it configurable - broadcastEvidenceIntervalS = 60 // broadcast uncommitted evidence this often - peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount + // broadcast all uncommitted evidence this often. This sets when the reactor + // goes back to the start of the list and begins sending the evidence again. + // Most evidence should be committed in the very next block that is why we wait + // just over the block production rate before sending evidence again. + broadcastEvidenceIntervalS = 10 + // If a message fails wait this much before sending it again + peerRetryMessageIntervalMS = 100 ) // Reactor handles evpool evidence broadcasting amongst peers. @@ -117,7 +122,7 @@ func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) { } ev := next.Value.(types.Evidence) - evis, retry := evR.checkSendEvidenceMessage(peer, ev) + evis := evR.prepareEvidenceMessage(peer, ev) if len(evis) > 0 { msgBytes, err := encodeMsg(evis) if err != nil { @@ -125,12 +130,10 @@ func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) { } success := peer.Send(EvidenceChannel, msgBytes) - retry = !success - } - - if retry { - time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) - continue + if !success { + time.Sleep(peerRetryMessageIntervalMS * time.Millisecond) + continue + } } afterCh := time.After(time.Second * broadcastEvidenceIntervalS) @@ -150,12 +153,12 @@ func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) { } } -// Returns the message to send the peer, or nil if the evidence is invalid for the peer. -// If message is nil, return true if we should sleep and try again. -func (evR Reactor) checkSendEvidenceMessage( +// Returns the message to send to the peer, or nil if the evidence is invalid for the peer. +// If message is nil, we should sleep and try again. +func (evR Reactor) prepareEvidenceMessage( peer p2p.Peer, ev types.Evidence, -) (evis []types.Evidence, retry bool) { +) (evis []types.Evidence) { // make sure the peer is up to date evHeight := ev.Height() @@ -166,7 +169,7 @@ func (evR Reactor) checkSendEvidenceMessage( // different every time due to us using a map. Sometimes other reactors // will be initialized before the consensus reactor. We should wait a few // milliseconds and retry. - return nil, true + return nil } // NOTE: We only send evidence to peers where @@ -178,7 +181,7 @@ func (evR Reactor) checkSendEvidenceMessage( ) if peerHeight <= evHeight { // peer is behind. sleep while he catches up - return nil, true + return nil } else if ageNumBlocks > params.MaxAgeNumBlocks { // evidence is too old relative to the peer, skip // NOTE: if evidence is too old for an honest peer, then we're behind and @@ -192,11 +195,11 @@ func (evR Reactor) checkSendEvidenceMessage( "peer", peer, ) - return nil, false + return nil } // send evidence - return []types.Evidence{ev}, false + return []types.Evidence{ev} } // PeerState describes the state of a peer. diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index 2980a692d7..03a250ed6c 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -27,6 +27,162 @@ import ( "github.com/tendermint/tendermint/types" ) +var ( + numEvidence = 10 + timeout = 120 * time.Second // ridiculously high because CircleCI is slow +) + +// We have N evidence reactors connected to one another. The first reactor +// receives a number of evidence at varying heights. We test that all +// other reactors receive the evidence and add it to their own respective +// evidence pools. +func TestReactorBroadcastEvidence(t *testing.T) { + config := cfg.TestConfig() + N := 7 + + // create statedb for everyone + stateDBs := make([]sm.Store, N) + val := types.NewMockPV() + // we need validators saved for heights at least as high as we have evidence for + height := int64(numEvidence) + 10 + for i := 0; i < N; i++ { + stateDBs[i] = initializeValidatorState(val, height) + } + + // make reactors from statedb + reactors, pools := makeAndConnectReactorsAndPools(config, stateDBs) + + // set the peer height on each reactor + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + ps := peerState{height} + peer.Set(types.PeerStateKey, ps) + } + } + + // send a bunch of valid evidence to the first reactor's evpool + // and wait for them all to be received in the others + evList := sendEvidence(t, pools[0], val, numEvidence) + waitForEvidence(t, evList, pools) +} + +// We have two evidence reactors connected to one another but are at different heights. +// Reactor 1 which is ahead receives a number of evidence. It should only send the evidence +// that is below the height of the peer to that peer. +func TestReactorSelectiveBroadcast(t *testing.T) { + config := cfg.TestConfig() + + val := types.NewMockPV() + height1 := int64(numEvidence) + 10 + height2 := int64(numEvidence) / 2 + + // DB1 is ahead of DB2 + stateDB1 := initializeValidatorState(val, height1) + stateDB2 := initializeValidatorState(val, height2) + + // make reactors from statedb + reactors, pools := makeAndConnectReactorsAndPools(config, []sm.Store{stateDB1, stateDB2}) + + // set the peer height on each reactor + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + ps := peerState{height1} + peer.Set(types.PeerStateKey, ps) + } + } + + // update the first reactor peer's height to be very small + peer := reactors[0].Switch.Peers().List()[0] + ps := peerState{height2} + peer.Set(types.PeerStateKey, ps) + + // send a bunch of valid evidence to the first reactor's evpool + evList := sendEvidence(t, pools[0], val, numEvidence) + + // only ones less than the peers height should make it through + waitForEvidence(t, evList[:numEvidence/2-1], []*evidence.Pool{pools[1]}) + + // peers should still be connected + peers := reactors[1].Switch.Peers().List() + assert.Equal(t, 1, len(peers)) +} + +// This tests aims to ensure that reactors don't send evidence that they have committed or that ar +// not ready for the peer through three scenarios. +// First, committed evidence to a newly connected peer +// Second, evidence to a peer that is behind +// Third, evidence that was pending and became committed just before the peer caught up +func TestReactorsGossipNoCommittedEvidence(t *testing.T) { + config := cfg.TestConfig() + + val := types.NewMockPV() + var height int64 = 10 + + // DB1 is ahead of DB2 + stateDB1 := initializeValidatorState(val, height) + stateDB2 := initializeValidatorState(val, height-2) + + // make reactors from statedb + reactors, pools := makeAndConnectReactorsAndPools(config, []sm.Store{stateDB1, stateDB2}) + + evList := sendEvidence(t, pools[0], val, 2) + abciEvs := pools[0].ABCIEvidence(height, evList) + require.EqualValues(t, 2, len(abciEvs)) + require.EqualValues(t, uint32(0), pools[0].Size()) + + time.Sleep(100 * time.Millisecond) + + peer := reactors[0].Switch.Peers().List()[0] + ps := peerState{height - 2} + peer.Set(types.PeerStateKey, ps) + + peer = reactors[1].Switch.Peers().List()[0] + ps = peerState{height} + peer.Set(types.PeerStateKey, ps) + + // wait to see that no evidence comes through + time.Sleep(300 * time.Millisecond) + + // the second pool should not have received any evidence because it has already been committed + assert.Equal(t, uint32(0), pools[1].Size(), "second reactor should not have received evidence") + + // the first reactor receives three more evidence + evList = make([]types.Evidence, 3) + for i := 0; i < 3; i++ { + ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), + time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID) + err := pools[0].AddEvidence(ev) + require.NoError(t, err) + evList[i] = ev + } + + // wait to see that only one evidence is sent + time.Sleep(300 * time.Millisecond) + + // the second pool should only have received the first evidence because it is behind + peerEv, _ := pools[1].PendingEvidence(1000) + assert.EqualValues(t, []types.Evidence{evList[0]}, peerEv) + + // the last evidence is committed and the second reactor catches up in state to the first + // reactor. We therefore expect that the second reactor only receives one more evidence, the + // one that is still pending and not the evidence that has already been committed. + _ = pools[0].ABCIEvidence(height, []types.Evidence{evList[2]}) + // the first reactor should have the two remaining pending evidence + require.EqualValues(t, uint32(2), pools[0].Size()) + + // now update the state of the second reactor + pools[1].Update(sm.State{LastBlockHeight: height}) + peer = reactors[0].Switch.Peers().List()[0] + ps = peerState{height} + peer.Set(types.PeerStateKey, ps) + + // wait to see that only two evidence is sent + time.Sleep(300 * time.Millisecond) + + peerEv, _ = pools[1].PendingEvidence(1000) + assert.EqualValues(t, evList[0:1], peerEv) +} + // evidenceLogger is a TestingLogger which uses a different // color for each validator ("validator" key must exist). func evidenceLogger() log.Logger { @@ -141,41 +297,6 @@ func sendEvidence(t *testing.T, evpool *evidence.Pool, val types.PrivValidator, return evList } -var ( - numEvidence = 10 - timeout = 120 * time.Second // ridiculously high because CircleCI is slow -) - -func TestReactorBroadcastEvidence(t *testing.T) { - config := cfg.TestConfig() - N := 7 - - // create statedb for everyone - stateDBs := make([]sm.Store, N) - val := types.NewMockPV() - // we need validators saved for heights at least as high as we have evidence for - height := int64(numEvidence) + 10 - for i := 0; i < N; i++ { - stateDBs[i] = initializeValidatorState(val, height) - } - - // make reactors from statedb - reactors, pools := makeAndConnectReactorsAndPools(config, stateDBs) - - // set the peer height on each reactor - for _, r := range reactors { - for _, peer := range r.Switch.Peers().List() { - ps := peerState{height} - peer.Set(types.PeerStateKey, ps) - } - } - - // send a bunch of valid evidence to the first reactor's evpool - // and wait for them all to be received in the others - evList := sendEvidence(t, pools[0], val, numEvidence) - waitForEvidence(t, evList, pools) -} - type peerState struct { height int64 } @@ -184,44 +305,6 @@ func (ps peerState) GetHeight() int64 { return ps.height } -func TestReactorSelectiveBroadcast(t *testing.T) { - config := cfg.TestConfig() - - val := types.NewMockPV() - height1 := int64(numEvidence) + 10 - height2 := int64(numEvidence) / 2 - - // DB1 is ahead of DB2 - stateDB1 := initializeValidatorState(val, height1) - stateDB2 := initializeValidatorState(val, height2) - - // make reactors from statedb - reactors, pools := makeAndConnectReactorsAndPools(config, []sm.Store{stateDB1, stateDB2}) - - // set the peer height on each reactor - for _, r := range reactors { - for _, peer := range r.Switch.Peers().List() { - ps := peerState{height1} - peer.Set(types.PeerStateKey, ps) - } - } - - // update the first reactor peer's height to be very small - peer := reactors[0].Switch.Peers().List()[0] - ps := peerState{height2} - peer.Set(types.PeerStateKey, ps) - - // send a bunch of valid evidence to the first reactor's evpool - evList := sendEvidence(t, pools[0], val, numEvidence) - - // only ones less than the peers height should make it through - waitForEvidence(t, evList[:numEvidence/2-1], pools[1:2]) - - // peers should still be connected - peers := reactors[1].Switch.Peers().List() - assert.Equal(t, 1, len(peers)) -} - func exampleVote(t byte) *types.Vote { var stamp, err = time.Parse(types.TimeFormat, "2017-12-25T03:00:01.234Z") if err != nil { diff --git a/evidence/verify.go b/evidence/verify.go index f0806b1b40..53717d4be0 100644 --- a/evidence/verify.go +++ b/evidence/verify.go @@ -24,11 +24,6 @@ func (evpool *Pool) verify(evidence types.Evidence) (*info, error) { ageNumBlocks = height - evidence.Height() ) - // check that the evidence isn't already committed - if evpool.isCommitted(evidence) { - return nil, errors.New("evidence was already committed") - } - // verify the time of the evidence blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height()) if blockMeta == nil { From 96dda8810dad2d7b9d755e10fac65507d060217b Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 27 Oct 2020 13:54:53 +0100 Subject: [PATCH 053/108] ci: add goreleaser (#5527) Co-authored-by: Erik Grinaker Co-authored-by: Alessio Treglia --- .circleci/config.yml | 87 ------------------- .github/workflows/release.yml | 30 +++++++ .goreleaser.yml | 27 ++++++ CONTRIBUTING.md | 62 +++++++++---- Makefile | 3 +- cmd/tendermint/commands/version.go | 2 +- proxy/version.go | 2 +- release_notes.md | 1 + scripts/dist.sh | 4 +- scripts/publish.sh | 20 ----- scripts/release.sh | 53 ----------- scripts/release_management/README.md | 64 -------------- scripts/release_management/bump-semver.py | 62 ------------- 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 ---------- tools/tm-signer-harness/Makefile | 3 +- tools/tm-signer-harness/main.go | 2 +- version/version.go | 21 +---- 23 files changed, 114 insertions(+), 670 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yml create mode 100644 release_notes.md delete mode 100755 scripts/publish.sh delete mode 100755 scripts/release.sh delete mode 100644 scripts/release_management/README.md delete mode 100755 scripts/release_management/bump-semver.py delete mode 100755 scripts/release_management/github-draft.py delete mode 100755 scripts/release_management/github-openpr.py delete mode 100644 scripts/release_management/github-public-newbranch.bash delete mode 100755 scripts/release_management/github-publish.py delete mode 100755 scripts/release_management/github-upload.py delete mode 100755 scripts/release_management/sha-files.py delete mode 100755 scripts/release_management/zip-file.py diff --git a/.circleci/config.yml b/.circleci/config.yml index cfcd257564..a387846a86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,70 +118,6 @@ jobs: paths: - "/go/pkg/mod" - build_artifacts: - executor: golang - parallelism: 5 - steps: - - restore_cache: - name: "Restore source code cache" - keys: - - go-src-v1-{{ .Revision }} - - checkout - - restore_cache: - name: "Restore release dependencies cache" - keys: - - v2-release-deps-{{ checksum "go.sum" }} - - 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 - if test ${CIRCLE_NODE_INDEX:-0} == 4 ;then export GOOS=linux GOARCH=arm64 && 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: - executor: golang - steps: - - restore_cache: - name: "Restore source code cache" - keys: - - go-src-v1-{{ .Revision }} - - 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}" - export GOOS=linux GOARCH=arm64 && 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}" - # # Test RPC implementation against the swagger documented specs # contract_tests: # working_directory: /home/circleci/.go_workspace/src/github.com/tendermint/tendermint @@ -230,26 +166,3 @@ workflows: # - contract_tests: # requires: # - setup_dependencies - - release: - jobs: - - prepare_build: - filters: - branches: - only: - - /v[0-9]+\.[0-9]+/ - - build_artifacts: - requires: - - prepare_build - filters: - branches: - only: - - /v[0-9]+\.[0-9]+/ - - release_artifacts: - requires: - - prepare_build - - build_artifacts - filters: - branches: - only: - - /v[0-9]+\.[0-9]+/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..f4aecbab06 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: "Release" + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + + - run: echo https://github.com/tendermint/tendermint/blob/${GITHUB_REF#refs/tags/}/CHANGELOG.md#${GITHUB_REF#refs/tags/} > ../release_notes.md + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist --release-notes=../release_notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000000..9fb5933af4 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,27 @@ +project_name: Tendermint + +env: + # Require use of Go modules. + - GO111MODULE=on + +builds: + - id: "Tendermint" + main: ./cmd/tendermint/main.go + ldflags: + - -s -w -X github.com/tendermint/tendermint/version.TMCoreSemVer={{ .Version }} + env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + goarch: + - amd64 + - arm + - arm64 + +checksum: + name_template: SHA256SUMS-{{.Version}}.txt + algorithm: sha256 + +release: + name_template: "{{.Version}} (WARNING: BETA SOFTWARE)" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8097676518..cabaf8b3c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -246,22 +246,26 @@ Each PR should have one commit once it lands on `master`; this can be accomplish #### Major Release -1. start on `master` -2. run integration tests (see `test_integrations` in Makefile) -3. prepare release in a pull request against `master` (to be squash merged): - - copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md` - - run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for +1. Start on `master` +2. Run integration tests (see `test_integrations` in Makefile) +3. Prepare release in a pull request against `master` (to be squash merged): + - Copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md`; if this release + had release candidates, squash all the RC updates into one + - Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues - run `bash ./scripts/authors.sh` to get a list of authors since the latest release, and add the github aliases of external contributors to the top of the changelog. To lookup an alias from an email, try `bash ./scripts/authors.sh ` - - reset the `CHANGELOG_PENDING.md` - - bump Tendermint version in `version.go` - - bump P2P and block protocol versions in `version.go`, if necessary - - bump ABCI protocol version in `version.go`, if necessary - - make sure all significant breaking changes are covered in `UPGRADING.md` -4. push your changes with prepared release details to `vX.X` (this will trigger the release `vX.X.0`) -5. merge back to master (don't squash merge!) + - Reset the `CHANGELOG_PENDING.md` + - Bump P2P and block protocol versions in `version.go`, if necessary + - Bump ABCI protocol version in `version.go`, if necessary + - Make sure all significant breaking changes are covered in `UPGRADING.md` + - Add any release notes you would like to be added to the body of the release to `release_notes.md`. +4. Push a tag with prepared release details (this will trigger the release `vX.X.0`) + - `git tag -a vX.X.x -m 'Release vX.X.x'` + - `git push origin vX.X.x` +5. Update the changelog.md file on master with the releases changelog. +6. Delete any RC branches and tags for this release (if applicable) #### Minor Release @@ -274,15 +278,17 @@ Minor releases are done differently from major releases: They are built off of l - run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues - run `bash ./scripts/authors.sh` to get a list of authors since the latest release, and add the GitHub aliases of external contributors to the top of the CHANGELOG. To lookup an alias from an email, try `bash ./scripts/authors.sh ` - reset the `CHANGELOG_PENDING.md` - - bump Tendermint version in `version.go` - bump P2P and block protocol versions in `version.go`, if necessary - bump ABCI protocol version in `version.go`, if necessary - make sure all significant breaking changes are covered in `UPGRADING.md` + - Add any release notes you would like to be added to the body of the release to `release_notes.md`. 4. Create a release branch `release/vX.X.x` off the release candidate branch: - `git checkout -b release/vX.X.x` - `git push -u origin release/vX.X.x` - Note that all branches prefixed with `release` are protected once pushed. You will need admin help to make any changes to the branch. -5. Open a pull request of the new minor release branch onto the latest major release branch `vX.X` and then rebase to merge. This will start the release process. +5. Once the release branch has been approved, make sure to pull it locally, then push a tag. + - `git tag -a vX.X.x -m 'Release vX.X.x'` + - `git push origin vX.X.x` 6. Create a pull request back to master with the CHANGELOG & version changes from the latest release. - Remove all `R:minor` labels from the pull requests that were included in the release. - Do not merge the release branch into master. @@ -293,10 +299,30 @@ Minor releases are done differently from major releases: They are built off of l 1. start from the existing release branch you want to backport changes to (e.g. v0.30) Branch to a release/vX.X.X branch locally (e.g. release/v0.30.7) -2. cherry pick the commit(s) that contain the changes you want to backport (usually these commits are from squash-merged PRs which were already reviewed) -3. steps 2 and 3 from [Major Release](#major-release) -4. push changes to release/vX.X.X branch -5. open a PR against the existing vX.X branch +2. Cherry pick the commit(s) that contain the changes you want to backport (usually these commits are from squash-merged PRs which were already reviewed) +3. Follow steps 2 and 3 from [Major Release](#major-release) +4. Push changes to release/vX.X.X branch +5. Open a PR against the existing vX.X branch + +#### Release Candidates + +Before creating an official release, especially a major release, we may want to create a +release candidate (RC) for our friends and partners to test out. We use git tags to +create RCs, and we build them off of RC branches. RC branches typically have names formatted +like `RCX/vX.X.X` (or, concretely, `RC0/v0.34.0`), while the tags themselves follow +the "standard" release naming conventions, with `-rcX` at the end (`vX.X.X-rcX`). + +(Note that branches and tags _cannot_ have the same names, so it's important that these branches +have distinct names from the tags/release names.) + +1. Start from the RC branch (e.g. `RC0/v0.34.0`). +2. Create the new tag, specifying a name and a tag "message": + `git tag -a v0.34.0-rc0 -m "Release Candidate v0.34.0-rc0` +3. Push the tag back up to origin: + `git push origin v0.34.0-rc4` + Now the tag should be available on the repo's releases page. +4. Create a new release candidate branch for any possible updates to the RC: + `git checkout -b RC1/v0.34.0; git push origin RC1/v0.34.0` ## Testing diff --git a/Makefile b/Makefile index 5ecb527009..e7111551b7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ PACKAGES=$(shell go list ./...) OUTPUT?=build/tendermint BUILD_TAGS?=tendermint -LD_FLAGS = -X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD` +VERSION := $(shell git describe --always) +LD_FLAGS = -X github.com/tendermint/tendermint/version.TMCoreSemVer=$(VERSION) BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)" HTTPS_GIT := https://github.com/tendermint/tendermint.git DOCKER_BUF := docker run -v $(shell pwd):/workspace --workdir /workspace bufbuild/buf diff --git a/cmd/tendermint/commands/version.go b/cmd/tendermint/commands/version.go index f9f545e596..d1a7fba582 100644 --- a/cmd/tendermint/commands/version.go +++ b/cmd/tendermint/commands/version.go @@ -13,6 +13,6 @@ var VersionCmd = &cobra.Command{ Use: "version", Short: "Show version info", Run: func(cmd *cobra.Command, args []string) { - fmt.Println(version.Version) + fmt.Println(version.TMCoreSemVer) }, } diff --git a/proxy/version.go b/proxy/version.go index 2109a45eb0..be890e12e0 100644 --- a/proxy/version.go +++ b/proxy/version.go @@ -9,7 +9,7 @@ import ( // the abci.RequestInfo message during handshake with the app. // It contains only compile-time version information. var RequestInfo = abci.RequestInfo{ - Version: version.Version, + Version: version.TMCoreSemVer, BlockVersion: version.BlockProtocol, P2PVersion: version.P2PProtocol, } diff --git a/release_notes.md b/release_notes.md new file mode 100644 index 0000000000..a537871c58 --- /dev/null +++ b/release_notes.md @@ -0,0 +1 @@ + diff --git a/scripts/dist.sh b/scripts/dist.sh index 81fdf9813f..2343804034 100755 --- a/scripts/dist.sh +++ b/scripts/dist.sh @@ -20,7 +20,7 @@ rm -rf build/pkg mkdir -p build/pkg # Get the git commit -GIT_COMMIT="$(git rev-parse --short=8 HEAD)" +VERSION := "$(shell git describe --always)" GIT_IMPORT="github.com/tendermint/tendermint/version" # Determine the arch/os combos we're building for @@ -41,7 +41,7 @@ for arch in "${arch_list[@]}"; do for os in "${os_list[@]}"; do if [[ "$XC_EXCLUDE" != *" $os/$arch "* ]]; then echo "--> $os/$arch" - GOOS=${os} GOARCH=${arch} go build -ldflags "-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}" -tags="${BUILD_TAGS}" -o "build/pkg/${os}_${arch}/tendermint" ./cmd/tendermint + GOOS=${os} GOARCH=${arch} go build -ldflags "-s -w -X ${GIT_IMPORT}.TMCoreSemVer=${VERSION}" -tags="${BUILD_TAGS}" -o "build/pkg/${os}_${arch}/tendermint" ./cmd/tendermint fi done done diff --git a/scripts/publish.sh b/scripts/publish.sh deleted file mode 100755 index 7da299aafa..0000000000 --- a/scripts/publish.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -e - -VERSION=$1 -DIST_DIR=./build/dist - -# Get the version from the environment, or try to figure it out. -if [ -z $VERSION ]; then - VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go) -fi -if [ -z "$VERSION" ]; then - echo "Please specify a version." - exit 1 -fi -echo "==> Copying ${DIST_DIR} to S3..." - -# copy to s3 -aws s3 cp --recursive ${DIST_DIR} s3://tendermint/binaries/tendermint/v${VERSION} --acl public-read - -exit 0 diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 8c40d36b6c..0000000000 --- a/scripts/release.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Get the version from the environment, or try to figure it out. -if [ -z $VERSION ]; then - VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go) -fi -if [ -z "$VERSION" ]; then - echo "Please specify a version." - exit 1 -fi -echo "==> Releasing version $VERSION..." - -# Get the parent directory of where this script is. -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" - -# Change into that dir because we expect that. -cd "$DIR" - -# Building binaries -sh -c "'$DIR/scripts/dist.sh'" - -# Pushing binaries to S3 -sh -c "'$DIR/scripts/publish.sh'" - -# echo "==> Crafting a Github release" -# today=$(date +"%B-%d-%Y") -# ghr -b "https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#${VERSION//.}-${today,}" "v$VERSION" "$DIR/build/dist" - -# Build and push Docker image - -## Get SHA256SUM of the linux archive -SHA256SUM=$(shasum -a256 "${DIR}/build/dist/tendermint_${VERSION}_linux_amd64.zip" | awk '{print $1;}') - -## Replace TM_VERSION and TM_SHA256SUM with the new values -sed -i -e "s/TM_VERSION .*/TM_VERSION $VERSION/g" "$DIR/DOCKER/Dockerfile" -sed -i -e "s/TM_SHA256SUM .*/TM_SHA256SUM $SHA256SUM/g" "$DIR/DOCKER/Dockerfile" -git commit -m "update Dockerfile" -a "$DIR/DOCKER/Dockerfile" -echo "==> TODO: update DOCKER/README.md (latest Dockerfile's hash is $(git rev-parse HEAD)) and copy it's content to https://store.docker.com/community/images/tendermint/tendermint" - -pushd "$DIR/DOCKER" - -## Build Docker image -TAG=$VERSION sh -c "'./build.sh'" - -## Push Docker image -TAG=$VERSION sh -c "'./push.sh'" - -popd - -exit 0 diff --git a/scripts/release_management/README.md b/scripts/release_management/README.md deleted file mode 100644 index c0d49e2e87..0000000000 --- a/scripts/release_management/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# 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 deleted file mode 100755 index ce56d8d7c1..0000000000 --- a/scripts/release_management/bump-semver.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/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 -import sys - - -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 - - -def get_tendermint_version(): - """Extracts the current Tendermint version from version/version.go""" - pattern = re.compile(r"TMCoreSemVer = \"(?P([0-9.]+)+)\"") - with open("version/version.go", "rt") as version_file: - for line in version_file: - m = pattern.search(line) - if m: - return m.group('version') - - return None - - -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 - - expected_version = "{0}.{1}".format(majorminorprefix, patch) - # if we're doing a release - if expected_version != "v0.0.0": - cur_version = get_tendermint_version() - if not cur_version: - print("Failed to obtain Tendermint version from version/version.go") - sys.exit(1) - expected_version_noprefix = expected_version.lstrip("v") - if expected_version_noprefix != "0.0.0" and expected_version_noprefix != cur_version: - print("Expected version/version.go#TMCoreSemVer to be {0}, but was {1}".format(expected_version_noprefix, cur_version)) - sys.exit(1) - - print(expected_version) diff --git a/scripts/release_management/github-draft.py b/scripts/release_management/github-draft.py deleted file mode 100755 index 8a189d53e3..0000000000 --- a/scripts/release_management/github-draft.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/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 deleted file mode 100755 index af0434f02d..0000000000 --- a/scripts/release_management/github-openpr.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/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 deleted file mode 100644 index ca2fa13141..0000000000 --- a/scripts/release_management/github-public-newbranch.bash +++ /dev/null @@ -1,28 +0,0 @@ -#!/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 deleted file mode 100755 index 31071aecdb..0000000000 --- a/scripts/release_management/github-publish.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/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 deleted file mode 100755 index 77c76a7554..0000000000 --- a/scripts/release_management/github-upload.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/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 deleted file mode 100755 index 2a9ee0d594..0000000000 --- a/scripts/release_management/sha-files.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/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 deleted file mode 100755 index 5d2f5b2c81..0000000000 --- a/scripts/release_management/zip-file.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/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/tools/tm-signer-harness/Makefile b/tools/tm-signer-harness/Makefile index 47cd036502..1c404ebf81 100644 --- a/tools/tm-signer-harness/Makefile +++ b/tools/tm-signer-harness/Makefile @@ -2,7 +2,8 @@ TENDERMINT_VERSION?=latest BUILD_TAGS?='tendermint' -BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" +VERSION := $(shell git describe --always) +BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.TMCoreSemVer=$(VERSION) .DEFAULT_GOAL := build diff --git a/tools/tm-signer-harness/main.go b/tools/tm-signer-harness/main.go index 06b7000cc2..d624234ae0 100644 --- a/tools/tm-signer-harness/main.go +++ b/tools/tm-signer-harness/main.go @@ -181,7 +181,7 @@ func main() { } extractKey(flagTMHome, flagKeyOutputPath) case "version": - fmt.Println(version.Version) + fmt.Println(version.TMCoreSemVer) default: fmt.Printf("Unrecognized command: %s\n", flag.Arg(0)) os.Exit(1) diff --git a/version/version.go b/version/version.go index e87ceba78a..5082d73c19 100644 --- a/version/version.go +++ b/version/version.go @@ -1,27 +1,12 @@ package version var ( - // GitCommit is the current HEAD set using ldflags. - GitCommit string - - // Version is the built softwares version. - Version = TMCoreSemVer -) - -func init() { - if GitCommit != "" { - Version += "-" + GitCommit - } -} - -const ( // TMCoreSemVer is the current version of Tendermint Core. // It's the Semantic Version of the software. - // 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.34.0" + TMCoreSemVer string +) +const ( // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.17.0" From 0022779e0787ad0730de75c31d0bb92b765939e1 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 27 Oct 2020 15:44:19 +0100 Subject: [PATCH 054/108] ci: tests (#5577) - use matrix builds to run multiple test jobs - upload code coverage once not 4 times (produce more accurate codecov reports) --- .github/workflows/coverage.yml | 89 +++++++++++----------------------- codecov.yml | 4 +- 2 files changed, 30 insertions(+), 63 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 60495a3d13..d4e4f7a963 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,28 +14,32 @@ jobs: - name: Create a file with all the pkgs run: go list ./... > pkgs.txt - name: Split pkgs into 4 files - run: split -n l/4 --additional-suffix=.txt ./pkgs.txt + run: split -d -n l/4 pkgs.txt pkgs.txt.part. # cache multiple - uses: actions/upload-artifact@v2 with: - name: "${{ github.sha }}-aa" - path: ./xaa.txt + name: "${{ github.sha }}-00" + path: ./pkgs.txt.part.00 - uses: actions/upload-artifact@v2 with: - name: "${{ github.sha }}-ab" - path: ./xab.txt + name: "${{ github.sha }}-01" + path: ./pkgs.txt.part.01 - uses: actions/upload-artifact@v2 with: - name: "${{ github.sha }}-ac" - path: ./xac.txt + name: "${{ github.sha }}-02" + path: ./pkgs.txt.part.02 - uses: actions/upload-artifact@v2 with: - name: "${{ github.sha }}-ad" - path: ./xad.txt + name: "${{ github.sha }}-03" + path: ./pkgs.txt.part.03 - test-coverage-part-1: + tests: runs-on: ubuntu-latest needs: split-test-files + strategy: + fail-fast: false + matrix: + part: ["00", "01", "02", "03"] steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 @@ -46,20 +50,20 @@ jobs: go.sum - uses: actions/download-artifact@v2 with: - name: "${{ github.sha }}-aa" + name: "${{ github.sha }}-${{ matrix.part }}" if: env.GIT_DIFF - name: test & coverage report creation run: | - cat xaa.txt | xargs go test -mod=readonly -timeout 8m -race -coverprofile=coverage.txt -covermode=atomic + cat pkgs.txt.part.${{ matrix.part }} | xargs go test -mod=readonly -timeout 8m -race -coverprofile=${{ matrix.part }}profile.out -covermode=atomic if: env.GIT_DIFF - - uses: codecov/codecov-action@v1.0.13 + - uses: actions/upload-artifact@v2 with: - file: ./coverage.txt - if: env.GIT_DIFF + name: "${{ github.sha }}-${{ matrix.part }}-coverage" + path: ./${{ matrix.part }}profile.out - test-coverage-part-2: + upload-coverage-report: runs-on: ubuntu-latest - needs: split-test-files + needs: tests steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 @@ -70,59 +74,22 @@ jobs: go.sum - uses: actions/download-artifact@v2 with: - name: "${{ github.sha }}-ab" + name: "${{ github.sha }}-00-coverage" if: env.GIT_DIFF - - name: test & coverage report creation - run: | - cat xab.txt | xargs go test -mod=readonly -timeout 5m -race -coverprofile=coverage.txt -covermode=atomic - if: env.GIT_DIFF - - uses: codecov/codecov-action@v1.0.13 - with: - file: ./coverage.txt - if: env.GIT_DIFF - - test-coverage-part-3: - runs-on: ubuntu-latest - needs: split-test-files - steps: - - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v4 - with: - PATTERNS: | - **/**.go - go.mod - go.sum - uses: actions/download-artifact@v2 with: - name: "${{ github.sha }}-ac" - if: env.GIT_DIFF - - name: test & coverage report creation - run: | - cat xac.txt | xargs go test -mod=readonly -timeout 10m -race -coverprofile=coverage.txt -covermode=atomic + name: "${{ github.sha }}-01-coverage" if: env.GIT_DIFF - - uses: codecov/codecov-action@v1.0.13 + - uses: actions/download-artifact@v2 with: - file: ./coverage.txt + name: "${{ github.sha }}-02-coverage" if: env.GIT_DIFF - - test-coverage-part-4: - runs-on: ubuntu-latest - needs: split-test-files - steps: - - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v4 - with: - PATTERNS: | - **/**.go - go.mod - go.sum - uses: actions/download-artifact@v2 with: - name: "${{ github.sha }}-ad" + name: "${{ github.sha }}-03-coverage" if: env.GIT_DIFF - - name: test & coverage report creation - run: | - cat xad.txt | xargs go test -mod=readonly -timeout 5m -race -coverprofile=coverage.txt -covermode=atomic + - run: | + cat ./*profile.out | grep -v "mode: atomic" >> coverage.txt if: env.GIT_DIFF - uses: codecov/codecov-action@v1.0.13 with: diff --git a/codecov.yml b/codecov.yml index a6cfbd1675..a133d31453 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,8 +2,8 @@ coverage: precision: 2 round: down range: "70...100" - notify: - after_n_builds: 4 + github_checks: + annotations: false status: project: From ad4f54e9b211fff86a1113ea6a0e44ae12ce106b Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 28 Oct 2020 16:16:38 +0100 Subject: [PATCH 055/108] privval: make response values non nullable (#5583) make response values non nullable in privval Does this need a changelog for master? Closes: #5581 cc @tarcieri --- CHANGELOG_PENDING.md | 1 + privval/msgs_test.go | 13 +- privval/signer_client.go | 6 +- privval/signer_client_test.go | 7 +- privval/signer_requestHandler.go | 20 +-- proto/tendermint/privval/types.pb.go | 195 ++++++++++++--------------- proto/tendermint/privval/types.proto | 7 +- 7 files changed, 118 insertions(+), 131 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index fbf3a7fd1a..32cff9bda9 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,3 +32,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) - [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) - [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) +- [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) diff --git a/privval/msgs_test.go b/privval/msgs_test.go index f6f1681dba..bf532bd7b9 100644 --- a/privval/msgs_test.go +++ b/privval/msgs_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/tmhash" + cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" privproto "github.com/tendermint/tendermint/proto/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" @@ -81,14 +82,14 @@ func TestPrivvalVectors(t *testing.T) { {"ping request", &privproto.PingRequest{}, "3a00"}, {"ping response", &privproto.PingResponse{}, "4200"}, {"pubKey request", &privproto.PubKeyRequest{}, "0a00"}, - {"pubKey response", &privproto.PubKeyResponse{PubKey: &ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"}, - {"pubKey response with error", &privproto.PubKeyResponse{PubKey: nil, Error: remoteError}, "121212100801120c697427732061206572726f72"}, + {"pubKey response", &privproto.PubKeyResponse{PubKey: ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"}, + {"pubKey response with error", &privproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: remoteError}, "12140a0012100801120c697427732061206572726f72"}, {"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1a760a74080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"}, - {"Vote Response", &privproto.SignedVoteResponse{Vote: votepb, Error: nil}, "22760a74080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"}, - {"Vote Response with error", &privproto.SignedVoteResponse{Vote: nil, Error: remoteError}, "221212100801120c697427732061206572726f72"}, + {"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "22760a74080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"}, + {"Vote Response with error", &privproto.SignedVoteResponse{Vote: tmproto.Vote{}, Error: remoteError}, "22250a11220212002a0b088092b8c398feffffff0112100801120c697427732061206572726f72"}, {"Proposal Request", &privproto.SignProposalRequest{Proposal: proposalpb}, "2a700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, - {"Proposal Response", &privproto.SignedProposalResponse{Proposal: proposalpb, Error: nil}, "32700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, - {"Proposal Response with error", &privproto.SignedProposalResponse{Proposal: nil, Error: remoteError}, "321212100801120c697427732061206572726f72"}, + {"Proposal Response", &privproto.SignedProposalResponse{Proposal: *proposalpb, Error: nil}, "32700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, + {"Proposal Response with error", &privproto.SignedProposalResponse{Proposal: tmproto.Proposal{}, Error: remoteError}, "32250a112a021200320b088092b8c398feffffff0112100801120c697427732061206572726f72"}, } for _, tc := range testCases { diff --git a/privval/signer_client.go b/privval/signer_client.go index 50c462da1b..aecb0381ef 100644 --- a/privval/signer_client.go +++ b/privval/signer_client.go @@ -82,7 +82,7 @@ func (sc *SignerClient) GetPubKey() (crypto.PubKey, error) { return nil, &RemoteSignerError{Code: int(resp.Error.Code), Description: resp.Error.Description} } - pk, err := cryptoenc.PubKeyFromProto(*resp.PubKey) + pk, err := cryptoenc.PubKeyFromProto(resp.PubKey) if err != nil { return nil, err } @@ -105,7 +105,7 @@ func (sc *SignerClient) SignVote(chainID string, vote *tmproto.Vote) error { return &RemoteSignerError{Code: int(resp.Error.Code), Description: resp.Error.Description} } - *vote = *resp.Vote + *vote = resp.Vote return nil } @@ -127,7 +127,7 @@ func (sc *SignerClient) SignProposal(chainID string, proposal *tmproto.Proposal) return &RemoteSignerError{Code: int(resp.Error.Code), Description: resp.Error.Description} } - *proposal = *resp.Proposal + *proposal = resp.Proposal return nil } diff --git a/privval/signer_client_test.go b/privval/signer_client_test.go index d169a875ae..019fd2c96a 100644 --- a/privval/signer_client_test.go +++ b/privval/signer_client_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" + cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" @@ -396,11 +397,11 @@ func brokenHandler(privVal types.PrivValidator, request privvalproto.Message, switch r := request.Sum.(type) { // This is broken and will answer most requests with a pubkey response case *privvalproto.Message_PubKeyRequest: - res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: nil, Error: nil}) + res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: nil}) case *privvalproto.Message_SignVoteRequest: - res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: nil, Error: nil}) + res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: nil}) case *privvalproto.Message_SignProposalRequest: - res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: nil, Error: nil}) + res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: nil}) case *privvalproto.Message_PingRequest: err, res = nil, mustWrapMsg(&privvalproto.PingResponse{}) default: diff --git a/privval/signer_requestHandler.go b/privval/signer_requestHandler.go index 075193541d..ea020b6829 100644 --- a/privval/signer_requestHandler.go +++ b/privval/signer_requestHandler.go @@ -5,7 +5,9 @@ import ( "github.com/tendermint/tendermint/crypto" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" + cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) @@ -23,7 +25,7 @@ func DefaultValidationRequestHandler( case *privvalproto.Message_PubKeyRequest: if r.PubKeyRequest.GetChainId() != chainID { res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: nil, Error: &privvalproto.RemoteSignerError{ + Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{ Code: 0, Description: "unable to provide pubkey"}}) return res, fmt.Errorf("want chainID: %s, got chainID: %s", r.PubKeyRequest.GetChainId(), chainID) } @@ -37,15 +39,15 @@ func DefaultValidationRequestHandler( if err != nil { res = mustWrapMsg(&privvalproto.PubKeyResponse{ - PubKey: nil, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) + PubKey: cryptoproto.PublicKey{}, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) } else { - res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: &pk, Error: nil}) + res = mustWrapMsg(&privvalproto.PubKeyResponse{PubKey: pk, Error: nil}) } case *privvalproto.Message_SignVoteRequest: if r.SignVoteRequest.ChainId != chainID { res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: nil, Error: &privvalproto.RemoteSignerError{ + Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{ Code: 0, Description: "unable to sign vote"}}) return res, fmt.Errorf("want chainID: %s, got chainID: %s", r.SignVoteRequest.GetChainId(), chainID) } @@ -55,15 +57,15 @@ func DefaultValidationRequestHandler( err = privVal.SignVote(chainID, vote) if err != nil { res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: nil, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) + Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) } else { - res = mustWrapMsg(&privvalproto.SignedVoteResponse{Vote: vote, Error: nil}) + res = mustWrapMsg(&privvalproto.SignedVoteResponse{Vote: *vote, Error: nil}) } case *privvalproto.Message_SignProposalRequest: if r.SignProposalRequest.GetChainId() != chainID { res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: nil, Error: &privvalproto.RemoteSignerError{ + Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{ Code: 0, Description: "unable to sign proposal"}}) return res, fmt.Errorf("want chainID: %s, got chainID: %s", r.SignProposalRequest.GetChainId(), chainID) @@ -74,9 +76,9 @@ func DefaultValidationRequestHandler( err = privVal.SignProposal(chainID, proposal) if err != nil { res = mustWrapMsg(&privvalproto.SignedProposalResponse{ - Proposal: nil, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) + Proposal: tmproto.Proposal{}, Error: &privvalproto.RemoteSignerError{Code: 0, Description: err.Error()}}) } else { - res = mustWrapMsg(&privvalproto.SignedProposalResponse{Proposal: proposal, Error: nil}) + res = mustWrapMsg(&privvalproto.SignedProposalResponse{Proposal: *proposal, Error: nil}) } case *privvalproto.Message_PingRequest: err, res = nil, mustWrapMsg(&privvalproto.PingResponse{}) diff --git a/proto/tendermint/privval/types.pb.go b/proto/tendermint/privval/types.pb.go index f376d168f8..cab9975b14 100644 --- a/proto/tendermint/privval/types.pb.go +++ b/proto/tendermint/privval/types.pb.go @@ -5,6 +5,7 @@ package privval import ( fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" crypto "github.com/tendermint/tendermint/proto/tendermint/crypto" types "github.com/tendermint/tendermint/proto/tendermint/types" @@ -160,7 +161,7 @@ func (m *PubKeyRequest) GetChainId() string { // PubKeyResponse is a response message containing the public key. type PubKeyResponse struct { - PubKey *crypto.PublicKey `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` + PubKey crypto.PublicKey `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key"` Error *RemoteSignerError `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } @@ -197,11 +198,11 @@ func (m *PubKeyResponse) XXX_DiscardUnknown() { var xxx_messageInfo_PubKeyResponse proto.InternalMessageInfo -func (m *PubKeyResponse) GetPubKey() *crypto.PublicKey { +func (m *PubKeyResponse) GetPubKey() crypto.PublicKey { if m != nil { return m.PubKey } - return nil + return crypto.PublicKey{} } func (m *PubKeyResponse) GetError() *RemoteSignerError { @@ -266,7 +267,7 @@ func (m *SignVoteRequest) GetChainId() string { // SignedVoteResponse is a response containing a signed vote or an error type SignedVoteResponse struct { - Vote *types.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote,omitempty"` + Vote types.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote"` Error *RemoteSignerError `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } @@ -303,11 +304,11 @@ func (m *SignedVoteResponse) XXX_DiscardUnknown() { var xxx_messageInfo_SignedVoteResponse proto.InternalMessageInfo -func (m *SignedVoteResponse) GetVote() *types.Vote { +func (m *SignedVoteResponse) GetVote() types.Vote { if m != nil { return m.Vote } - return nil + return types.Vote{} } func (m *SignedVoteResponse) GetError() *RemoteSignerError { @@ -372,7 +373,7 @@ func (m *SignProposalRequest) GetChainId() string { // SignedProposalResponse is response containing a signed proposal or an error type SignedProposalResponse struct { - Proposal *types.Proposal `protobuf:"bytes,1,opt,name=proposal,proto3" json:"proposal,omitempty"` + Proposal types.Proposal `protobuf:"bytes,1,opt,name=proposal,proto3" json:"proposal"` Error *RemoteSignerError `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } @@ -409,11 +410,11 @@ func (m *SignedProposalResponse) XXX_DiscardUnknown() { var xxx_messageInfo_SignedProposalResponse proto.InternalMessageInfo -func (m *SignedProposalResponse) GetProposal() *types.Proposal { +func (m *SignedProposalResponse) GetProposal() types.Proposal { if m != nil { return m.Proposal } - return nil + return types.Proposal{} } func (m *SignedProposalResponse) GetError() *RemoteSignerError { @@ -677,53 +678,54 @@ func init() { func init() { proto.RegisterFile("tendermint/privval/types.proto", fileDescriptor_cb4e437a5328cf9c) } var fileDescriptor_cb4e437a5328cf9c = []byte{ - // 724 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4d, 0x4f, 0xdb, 0x4a, - 0x14, 0xb5, 0x21, 0x1f, 0x70, 0x43, 0x42, 0x18, 0x78, 0xbc, 0x80, 0x78, 0x7e, 0x79, 0x79, 0x6a, - 0x8b, 0xb2, 0x48, 0x24, 0xaa, 0x56, 0xaa, 0xba, 0x2a, 0x60, 0x35, 0x11, 0xc2, 0x4e, 0x27, 0xa1, - 0x20, 0xa4, 0xca, 0xca, 0xc7, 0x34, 0x58, 0x10, 0xcf, 0xd4, 0xe3, 0x20, 0x65, 0xd1, 0x5d, 0xb7, - 0x95, 0xfa, 0x33, 0xba, 0xee, 0xaf, 0xe8, 0x92, 0x65, 0x97, 0x15, 0xfc, 0x91, 0x2a, 0xe3, 0x89, - 0x63, 0xe7, 0x03, 0xa9, 0x65, 0x97, 0xb9, 0xe7, 0xde, 0x73, 0xcf, 0xf1, 0x9c, 0x68, 0x40, 0xf3, - 0x88, 0xd3, 0x21, 0x6e, 0xcf, 0x76, 0xbc, 0x32, 0x73, 0xed, 0xeb, 0xeb, 0xe6, 0x55, 0xd9, 0x1b, - 0x30, 0xc2, 0x4b, 0xcc, 0xa5, 0x1e, 0x45, 0x68, 0x8c, 0x97, 0x24, 0xbe, 0xbd, 0x13, 0x9a, 0x69, - 0xbb, 0x03, 0xe6, 0xd1, 0xf2, 0x25, 0x19, 0xc8, 0x89, 0x08, 0x2a, 0x98, 0xc2, 0x7c, 0x85, 0x2a, - 0xac, 0x61, 0xd2, 0xa3, 0x1e, 0xa9, 0xdb, 0x5d, 0x87, 0xb8, 0xba, 0xeb, 0x52, 0x17, 0x21, 0x88, - 0xb5, 0x69, 0x87, 0xe4, 0xd4, 0xbc, 0xba, 0x1b, 0xc7, 0xe2, 0x37, 0xca, 0x43, 0xaa, 0x43, 0x78, - 0xdb, 0xb5, 0x99, 0x67, 0x53, 0x27, 0xb7, 0x90, 0x57, 0x77, 0x97, 0x71, 0xb8, 0x54, 0x28, 0x42, - 0xba, 0xd6, 0x6f, 0x1d, 0x91, 0x01, 0x26, 0x1f, 0xfa, 0x84, 0x7b, 0x68, 0x0b, 0x96, 0xda, 0x17, - 0x4d, 0xdb, 0xb1, 0xec, 0x8e, 0xa0, 0x5a, 0xc6, 0x49, 0x71, 0xae, 0x76, 0x0a, 0x9f, 0x54, 0xc8, - 0x8c, 0x9a, 0x39, 0xa3, 0x0e, 0x27, 0xe8, 0x19, 0x24, 0x59, 0xbf, 0x65, 0x5d, 0x92, 0x81, 0x68, - 0x4e, 0xed, 0xed, 0x94, 0x42, 0x5e, 0x7d, 0x5f, 0xa5, 0x5a, 0xbf, 0x75, 0x65, 0xb7, 0x87, 0x63, - 0x09, 0x26, 0xc6, 0xd1, 0x4b, 0x88, 0x93, 0xa1, 0x68, 0xa1, 0x28, 0xb5, 0xf7, 0xa8, 0x34, 0xfd, - 0x81, 0x4a, 0x53, 0x0e, 0xb1, 0x3f, 0x53, 0x38, 0x83, 0xd5, 0x61, 0xf5, 0x2d, 0xf5, 0xc8, 0x48, - 0x74, 0x11, 0x62, 0xd7, 0xd4, 0x23, 0x52, 0xc3, 0x66, 0x98, 0xce, 0xff, 0x6e, 0xa2, 0x59, 0xf4, - 0x44, 0x0c, 0x2e, 0x44, 0x0d, 0x7e, 0x04, 0x24, 0xf6, 0x75, 0x7c, 0x6e, 0xe9, 0xf1, 0x77, 0xc8, - 0x1f, 0x64, 0xec, 0x02, 0xd6, 0x87, 0xd5, 0x9a, 0x4b, 0x19, 0xe5, 0xcd, 0xab, 0x91, 0xb9, 0xe7, - 0xb0, 0xc4, 0x64, 0x49, 0x6a, 0xd8, 0x9e, 0xd6, 0x10, 0x0c, 0x05, 0xbd, 0xf7, 0x19, 0xfd, 0xac, - 0xc2, 0xa6, 0xef, 0x74, 0xbc, 0x4c, 0xba, 0xfd, 0xd3, 0x6d, 0x0f, 0x72, 0x9e, 0x86, 0x54, 0xcd, - 0x76, 0xba, 0xd2, 0x71, 0x21, 0x03, 0x2b, 0xfe, 0xd1, 0xd7, 0x54, 0xf8, 0x16, 0x87, 0xe4, 0x31, - 0xe1, 0xbc, 0xd9, 0x25, 0xe8, 0x08, 0x56, 0x65, 0xe2, 0x2c, 0xd7, 0x6f, 0x97, 0x32, 0xff, 0x9b, - 0xb5, 0x31, 0x92, 0xed, 0x8a, 0x82, 0xd3, 0x2c, 0x12, 0x76, 0x03, 0xb2, 0x63, 0x32, 0x7f, 0x99, - 0xd4, 0x5f, 0xb8, 0x8f, 0xcd, 0xef, 0xac, 0x28, 0x38, 0xc3, 0xa2, 0x7f, 0x87, 0x37, 0xb0, 0xc6, - 0xed, 0xae, 0x63, 0x0d, 0xb3, 0x10, 0xc8, 0x5b, 0x14, 0x84, 0xff, 0xcf, 0x22, 0x9c, 0xc8, 0x71, - 0x45, 0xc1, 0xab, 0x7c, 0x22, 0xda, 0xe7, 0xb0, 0xc1, 0xc5, 0x4d, 0x8d, 0x48, 0xa5, 0xcc, 0x98, - 0x60, 0x7d, 0x3c, 0x8f, 0x35, 0x9a, 0xe1, 0x8a, 0x82, 0x11, 0x9f, 0x4e, 0xf6, 0x3b, 0xf8, 0x4b, - 0xc8, 0x1d, 0x5d, 0x62, 0x20, 0x39, 0x2e, 0xc8, 0x9f, 0xcc, 0x23, 0x9f, 0x48, 0x68, 0x45, 0xc1, - 0xeb, 0x7c, 0x46, 0x70, 0xdf, 0x43, 0x4e, 0x4a, 0x0f, 0x2d, 0x90, 0xf2, 0x13, 0x62, 0x43, 0x71, - 0xbe, 0xfc, 0xc9, 0x60, 0x56, 0x14, 0xbc, 0xc9, 0x67, 0x47, 0xf6, 0x10, 0x56, 0x98, 0xed, 0x74, - 0x03, 0xf5, 0x49, 0xc1, 0xfd, 0xef, 0xcc, 0x1b, 0x1c, 0xa7, 0xac, 0xa2, 0xe0, 0x14, 0x1b, 0x1f, - 0xd1, 0x6b, 0x48, 0x4b, 0x16, 0x29, 0x71, 0x49, 0xd0, 0xe4, 0xe7, 0xd3, 0x04, 0xc2, 0x56, 0x58, - 0xe8, 0xbc, 0x1f, 0x87, 0x45, 0xde, 0xef, 0x15, 0xbf, 0xaa, 0x90, 0x10, 0x21, 0xe7, 0x08, 0x41, - 0x46, 0xc7, 0xd8, 0xc4, 0x75, 0xeb, 0xc4, 0x38, 0x32, 0xcc, 0x53, 0x23, 0xab, 0x20, 0x0d, 0xb6, - 0x83, 0x9a, 0x7e, 0x56, 0xd3, 0x0f, 0x1a, 0xfa, 0xa1, 0x85, 0xf5, 0x7a, 0xcd, 0x34, 0xea, 0x7a, - 0x56, 0x45, 0x39, 0xd8, 0x90, 0xb8, 0x61, 0x5a, 0x07, 0xa6, 0x61, 0xe8, 0x07, 0x8d, 0xaa, 0x69, - 0x64, 0x17, 0xd0, 0x3f, 0xb0, 0x25, 0x91, 0x71, 0xd9, 0x6a, 0x54, 0x8f, 0x75, 0xf3, 0xa4, 0x91, - 0x5d, 0x44, 0x7f, 0xc3, 0xba, 0x84, 0xb1, 0xfe, 0xea, 0x30, 0x00, 0x62, 0x21, 0xc6, 0x53, 0x5c, - 0x6d, 0xe8, 0x01, 0x12, 0xdf, 0xaf, 0x7f, 0xbf, 0xd5, 0xd4, 0x9b, 0x5b, 0x4d, 0xfd, 0x79, 0xab, - 0xa9, 0x5f, 0xee, 0x34, 0xe5, 0xe6, 0x4e, 0x53, 0x7e, 0xdc, 0x69, 0xca, 0xf9, 0x8b, 0xae, 0xed, - 0x5d, 0xf4, 0x5b, 0xa5, 0x36, 0xed, 0x95, 0xc3, 0x4f, 0x52, 0xf8, 0xbd, 0xa3, 0x1e, 0x2d, 0x4f, - 0x3f, 0x80, 0xad, 0x84, 0x40, 0x9e, 0xfe, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xb0, 0xa7, 0xec, - 0x1d, 0x07, 0x00, 0x00, + // 750 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4d, 0x4f, 0x13, 0x41, + 0x18, 0xde, 0x85, 0x7e, 0xc0, 0x5b, 0x5a, 0xca, 0x80, 0x58, 0x1a, 0x5c, 0x6a, 0x8d, 0x4a, 0x7a, + 0x68, 0x0d, 0x26, 0x26, 0x06, 0x2f, 0x02, 0x1b, 0xdb, 0x34, 0x6c, 0xeb, 0xb4, 0x08, 0x21, 0x31, + 0x9b, 0x7e, 0x8c, 0xcb, 0x06, 0xba, 0x3b, 0xee, 0x6c, 0x49, 0x7a, 0xf6, 0xe6, 0xc9, 0xc4, 0x3f, + 0xe1, 0xd9, 0x5f, 0xc1, 0x91, 0xa3, 0x27, 0x63, 0xe0, 0x8f, 0x98, 0xce, 0x4e, 0xb7, 0xdb, 0x2f, + 0xa2, 0xe1, 0xb6, 0xf3, 0xbe, 0xef, 0x3c, 0x1f, 0x33, 0xcf, 0x66, 0x40, 0x71, 0x89, 0xd5, 0x26, + 0x4e, 0xc7, 0xb4, 0xdc, 0x02, 0x75, 0xcc, 0xcb, 0xcb, 0xc6, 0x45, 0xc1, 0xed, 0x51, 0xc2, 0xf2, + 0xd4, 0xb1, 0x5d, 0x1b, 0xa1, 0x61, 0x3f, 0x2f, 0xfa, 0xe9, 0xcd, 0xc0, 0x9e, 0x96, 0xd3, 0xa3, + 0xae, 0x5d, 0x38, 0x27, 0x3d, 0xb1, 0x63, 0xa4, 0xcb, 0x91, 0x82, 0x78, 0xe9, 0x35, 0xc3, 0x36, + 0x6c, 0xfe, 0x59, 0xe8, 0x7f, 0x79, 0xd5, 0x6c, 0x09, 0x56, 0x30, 0xe9, 0xd8, 0x2e, 0xa9, 0x99, + 0x86, 0x45, 0x1c, 0xd5, 0x71, 0x6c, 0x07, 0x21, 0x08, 0xb5, 0xec, 0x36, 0x49, 0xc9, 0x19, 0x79, + 0x3b, 0x8c, 0xf9, 0x37, 0xca, 0x40, 0xac, 0x4d, 0x58, 0xcb, 0x31, 0xa9, 0x6b, 0xda, 0x56, 0x6a, + 0x2e, 0x23, 0x6f, 0x2f, 0xe2, 0x60, 0x29, 0x9b, 0x83, 0x78, 0xb5, 0xdb, 0x2c, 0x93, 0x1e, 0x26, + 0x9f, 0xbb, 0x84, 0xb9, 0x68, 0x03, 0x16, 0x5a, 0x67, 0x0d, 0xd3, 0xd2, 0xcd, 0x36, 0x87, 0x5a, + 0xc4, 0x51, 0xbe, 0x2e, 0xb5, 0xb3, 0x5f, 0x65, 0x48, 0x0c, 0x86, 0x19, 0xb5, 0x2d, 0x46, 0xd0, + 0x2e, 0x44, 0x69, 0xb7, 0xa9, 0x9f, 0x93, 0x1e, 0x1f, 0x8e, 0xed, 0x6c, 0xe6, 0x03, 0x27, 0xe0, + 0xb9, 0xcd, 0x57, 0xbb, 0xcd, 0x0b, 0xb3, 0x55, 0x26, 0xbd, 0xbd, 0xd0, 0xd5, 0xef, 0x2d, 0x09, + 0x47, 0x28, 0x07, 0x41, 0xbb, 0x10, 0x26, 0x7d, 0xe9, 0x5c, 0x57, 0x6c, 0xe7, 0x69, 0x7e, 0xf2, + 0xf0, 0xf2, 0x13, 0x3e, 0xb1, 0xb7, 0x27, 0x7b, 0x02, 0xcb, 0xfd, 0xea, 0x07, 0xdb, 0x25, 0x03, + 0xe9, 0x39, 0x08, 0x5d, 0xda, 0x2e, 0x11, 0x4a, 0xd6, 0x83, 0x70, 0xde, 0x99, 0xf2, 0x61, 0x3e, + 0x33, 0x62, 0x73, 0x6e, 0xd4, 0xe6, 0x17, 0x19, 0x10, 0x27, 0x6c, 0x7b, 0xe0, 0xc2, 0xea, 0x8b, + 0x7f, 0x41, 0x17, 0x0e, 0x3d, 0x8e, 0x7b, 0xf9, 0x3b, 0x83, 0xd5, 0x7e, 0xb5, 0xea, 0xd8, 0xd4, + 0x66, 0x8d, 0x8b, 0x81, 0xc7, 0x57, 0xb0, 0x40, 0x45, 0x49, 0x28, 0x49, 0x4f, 0x2a, 0xf1, 0x37, + 0xf9, 0xb3, 0x77, 0xf9, 0xfd, 0x2e, 0xc3, 0xba, 0xe7, 0x77, 0x48, 0x26, 0x3c, 0xbf, 0xf9, 0x1f, + 0x36, 0xe1, 0x7d, 0xc8, 0x79, 0x2f, 0xff, 0x71, 0x88, 0x55, 0x4d, 0xcb, 0x10, 0xbe, 0xb3, 0x09, + 0x58, 0xf2, 0x96, 0x9e, 0xb2, 0xec, 0xcf, 0x30, 0x44, 0x0f, 0x09, 0x63, 0x0d, 0x83, 0xa0, 0x32, + 0x2c, 0x8b, 0x10, 0xea, 0x8e, 0x37, 0x2e, 0xc4, 0x3e, 0x9e, 0xc6, 0x38, 0x12, 0xf7, 0xa2, 0x84, + 0xe3, 0x74, 0x24, 0xff, 0x1a, 0x24, 0x87, 0x60, 0x1e, 0x99, 0xd0, 0x9f, 0xbd, 0x0b, 0xcd, 0x9b, + 0x2c, 0x4a, 0x38, 0x41, 0x47, 0xff, 0x90, 0xf7, 0xb0, 0xc2, 0x4c, 0xc3, 0xd2, 0xfb, 0x89, 0xf0, + 0xe5, 0xcd, 0x73, 0xc0, 0x27, 0xd3, 0x00, 0xc7, 0x42, 0x5d, 0x94, 0xf0, 0x32, 0x1b, 0xcb, 0xf9, + 0x29, 0xac, 0x31, 0x7e, 0x5f, 0x03, 0x50, 0x21, 0x33, 0xc4, 0x51, 0x9f, 0xcd, 0x42, 0x1d, 0xcd, + 0x73, 0x51, 0xc2, 0x88, 0x4d, 0xa6, 0xfc, 0x23, 0x3c, 0xe0, 0x72, 0x07, 0x97, 0xe8, 0x4b, 0x0e, + 0x73, 0xf0, 0xe7, 0xb3, 0xc0, 0xc7, 0x72, 0x5a, 0x94, 0xf0, 0x2a, 0x9b, 0x12, 0xdf, 0x4f, 0x90, + 0x12, 0xd2, 0x03, 0x04, 0x42, 0x7e, 0x84, 0x33, 0xe4, 0x66, 0xcb, 0x1f, 0x8f, 0x67, 0x51, 0xc2, + 0xeb, 0x6c, 0x7a, 0x70, 0x0f, 0x60, 0x89, 0x9a, 0x96, 0xe1, 0xab, 0x8f, 0x72, 0xec, 0xad, 0xa9, + 0x37, 0x38, 0x4c, 0x59, 0x51, 0xc2, 0x31, 0x3a, 0x5c, 0xa2, 0x77, 0x10, 0x17, 0x28, 0x42, 0xe2, + 0x02, 0x87, 0xc9, 0xcc, 0x86, 0xf1, 0x85, 0x2d, 0xd1, 0xc0, 0x7a, 0x2f, 0x0c, 0xf3, 0xac, 0xdb, + 0xc9, 0xfd, 0x90, 0x21, 0xc2, 0x43, 0xce, 0x10, 0x82, 0x84, 0x8a, 0x71, 0x05, 0xd7, 0xf4, 0x23, + 0xad, 0xac, 0x55, 0x8e, 0xb5, 0xa4, 0x84, 0x14, 0x48, 0xfb, 0x35, 0xf5, 0xa4, 0xaa, 0xee, 0xd7, + 0xd5, 0x03, 0x1d, 0xab, 0xb5, 0x6a, 0x45, 0xab, 0xa9, 0x49, 0x19, 0xa5, 0x60, 0x4d, 0xf4, 0xb5, + 0x8a, 0xbe, 0x5f, 0xd1, 0x34, 0x75, 0xbf, 0x5e, 0xaa, 0x68, 0xc9, 0x39, 0xf4, 0x08, 0x36, 0x44, + 0x67, 0x58, 0xd6, 0xeb, 0xa5, 0x43, 0xb5, 0x72, 0x54, 0x4f, 0xce, 0xa3, 0x87, 0xb0, 0x2a, 0xda, + 0x58, 0x7d, 0x7b, 0xe0, 0x37, 0x42, 0x01, 0xc4, 0x63, 0x5c, 0xaa, 0xab, 0x7e, 0x27, 0xbc, 0x57, + 0xbb, 0xba, 0x51, 0xe4, 0xeb, 0x1b, 0x45, 0xfe, 0x73, 0xa3, 0xc8, 0xdf, 0x6e, 0x15, 0xe9, 0xfa, + 0x56, 0x91, 0x7e, 0xdd, 0x2a, 0xd2, 0xe9, 0x6b, 0xc3, 0x74, 0xcf, 0xba, 0xcd, 0x7c, 0xcb, 0xee, + 0x14, 0x82, 0x6f, 0x57, 0xf0, 0x61, 0xec, 0xbf, 0x57, 0x93, 0x2f, 0x65, 0x33, 0xc2, 0x3b, 0x2f, + 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x2a, 0xe5, 0x4a, 0x46, 0x07, 0x00, 0x00, } func (m *RemoteSignerError) Marshal() (dAtA []byte, err error) { @@ -823,18 +825,16 @@ func (m *PubKeyResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if m.PubKey != nil { - { - size, err := m.PubKey.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) + { + size, err := m.PubKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } - i-- - dAtA[i] = 0xa + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -912,18 +912,16 @@ func (m *SignedVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if m.Vote != nil { - { - size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) + { + size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } - i-- - dAtA[i] = 0xa + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -1001,18 +999,16 @@ func (m *SignedProposalResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x12 } - if m.Proposal != nil { - { - size, err := m.Proposal.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) + { + size, err := m.Proposal.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } - i-- - dAtA[i] = 0xa + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -1308,10 +1304,8 @@ func (m *PubKeyResponse) Size() (n int) { } var l int _ = l - if m.PubKey != nil { - l = m.PubKey.Size() - n += 1 + l + sovTypes(uint64(l)) - } + l = m.PubKey.Size() + n += 1 + l + sovTypes(uint64(l)) if m.Error != nil { l = m.Error.Size() n += 1 + l + sovTypes(uint64(l)) @@ -1342,10 +1336,8 @@ func (m *SignedVoteResponse) Size() (n int) { } var l int _ = l - if m.Vote != nil { - l = m.Vote.Size() - n += 1 + l + sovTypes(uint64(l)) - } + l = m.Vote.Size() + n += 1 + l + sovTypes(uint64(l)) if m.Error != nil { l = m.Error.Size() n += 1 + l + sovTypes(uint64(l)) @@ -1376,10 +1368,8 @@ func (m *SignedProposalResponse) Size() (n int) { } var l int _ = l - if m.Proposal != nil { - l = m.Proposal.Size() - n += 1 + l + sovTypes(uint64(l)) - } + l = m.Proposal.Size() + n += 1 + l + sovTypes(uint64(l)) if m.Error != nil { l = m.Error.Size() n += 1 + l + sovTypes(uint64(l)) @@ -1767,9 +1757,6 @@ func (m *PubKeyResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.PubKey == nil { - m.PubKey = &crypto.PublicKey{} - } if err := m.PubKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -2013,9 +2000,6 @@ func (m *SignedVoteResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Vote == nil { - m.Vote = &types.Vote{} - } if err := m.Vote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -2259,9 +2243,6 @@ func (m *SignedProposalResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Proposal == nil { - m.Proposal = &types.Proposal{} - } if err := m.Proposal.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/proto/tendermint/privval/types.proto b/proto/tendermint/privval/types.proto index a0bba297d1..0fc8b61dc9 100644 --- a/proto/tendermint/privval/types.proto +++ b/proto/tendermint/privval/types.proto @@ -3,6 +3,7 @@ package tendermint.privval; import "tendermint/crypto/keys.proto"; import "tendermint/types/types.proto"; +import "gogoproto/gogo.proto"; option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval"; @@ -27,7 +28,7 @@ message PubKeyRequest { // PubKeyResponse is a response message containing the public key. message PubKeyResponse { - tendermint.crypto.PublicKey pub_key = 1; + tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; RemoteSignerError error = 2; } @@ -39,7 +40,7 @@ message SignVoteRequest { // SignedVoteResponse is a response containing a signed vote or an error message SignedVoteResponse { - tendermint.types.Vote vote = 1; + tendermint.types.Vote vote = 1 [(gogoproto.nullable) = false]; RemoteSignerError error = 2; } @@ -51,7 +52,7 @@ message SignProposalRequest { // SignedProposalResponse is response containing a signed proposal or an error message SignedProposalResponse { - tendermint.types.Proposal proposal = 1; + tendermint.types.Proposal proposal = 1 [(gogoproto.nullable) = false]; RemoteSignerError error = 2; } From 70a62be5c6ddda149042ffd5bc3f414c67c07c2c Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 2 Nov 2020 14:39:50 +0400 Subject: [PATCH 056/108] light: run detector for sequentially validating light client (#5538) (#5601) Closes #5445 Backport of #5538 --- light/client.go | 27 ++++++--- light/detector.go | 3 +- light/detector_test.go | 134 ++++++++++++++++++++++------------------- light/errors.go | 4 +- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/light/client.go b/light/client.go index 78b01f94ac..17c665b2bb 100644 --- a/light/client.go +++ b/light/client.go @@ -123,7 +123,7 @@ type Client struct { providerMutex tmsync.Mutex // Primary provider of new headers. primary provider.Provider - // See Witnesses option + // Providers used to "witness" new headers. witnesses []provider.Provider // Where trusted light blocks are stored. @@ -218,7 +218,7 @@ func NewClientFromTrustedStore( } // Validate the number of witnesses. - if len(c.witnesses) < 1 && c.verificationMode == skipping { + if len(c.witnesses) < 1 { return nil, errNoWitnesses{} } @@ -363,10 +363,8 @@ func (c *Client) initializeWithTrustOptions(ctx context.Context, options TrustOp } // 3) Cross-verify with witnesses to ensure everybody has the same state. - if len(c.witnesses) > 0 { - if err := c.compareFirstHeaderWithWitnesses(ctx, l.SignedHeader); err != nil { - return err - } + if err := c.compareFirstHeaderWithWitnesses(ctx, l.SignedHeader); err != nil { + return err } // 4) Persist both of them and continue. @@ -443,7 +441,7 @@ func (c *Client) Update(ctx context.Context, now time.Time) (*types.LightBlock, } // VerifyLightBlockAtHeight fetches the light block at the given height -// and calls verifyLightBlock. It returns the block immediately if it exists in +// and verifies it. It returns the block immediately if it exists in // the trustedStore (no verification is needed). // // height must be > 0. @@ -600,6 +598,7 @@ func (c *Client) verifySequential( verifiedBlock = trustedBlock interimBlock *types.LightBlock err error + trace = []*types.LightBlock{trustedBlock} ) for height := trustedBlock.Height + 1; height <= newLightBlock.Height; height++ { @@ -669,9 +668,17 @@ func (c *Client) verifySequential( // 3) Update verifiedBlock verifiedBlock = interimBlock + + // 4) Add verifiedBlock to trace + trace = append(trace, verifiedBlock) } - return nil + // Compare header with the witnesses to ensure it's not a fork. + // More witnesses we have, more chance to notice one. + // + // CORRECTNESS ASSUMPTION: there's at least 1 correct full node + // (primary or one of the witnesses). + return c.detectDivergence(ctx, trace, now) } // see VerifyHeader @@ -995,6 +1002,10 @@ func (c *Client) compareFirstHeaderWithWitnesses(ctx context.Context, h *types.S compareCtx, cancel := context.WithCancel(ctx) defer cancel() + if len(c.witnesses) < 1 { + return errNoWitnesses{} + } + errc := make(chan error, len(c.witnesses)) for i, witness := range c.witnesses { go c.compareNewHeaderWithWitness(compareCtx, errc, h, witness, i) diff --git a/light/detector.go b/light/detector.go index 9e58c1bc35..43f7a3f231 100644 --- a/light/detector.go +++ b/light/detector.go @@ -15,8 +15,7 @@ import ( // More info here: // tendermint/docs/architecture/adr-047-handling-evidence-from-light-client.md -// detectDivergence is a second wall of defense for the light client and is used -// only in the case of skipping verification which employs the trust level mechanism. +// detectDivergence is a second wall of defense for the light client. // // It takes the target verified header and compares it with the headers of a set of // witness providers that the light client is connected to. If a conflicting header diff --git a/light/detector_test.go b/light/detector_test.go index dc08234767..4788759e0b 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -90,76 +90,86 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { } func TestLightClientAttackEvidence_Equivocation(t *testing.T) { - // primary performs an equivocation attack - var ( - latestHeight = int64(10) - valSize = 5 - divergenceHeight = int64(6) - primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) - primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) - ) - // validators don't change in this network (however we still use a map just for convenience) - witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime) - witness := mockp.New(chainID, witnessHeaders, witnessValidators) + verificationOptions := map[string]light.Option{ + "sequential": light.SequentialVerification(), + "skipping": light.SkippingVerification(light.DefaultTrustLevel), + } - for height := int64(1); height <= latestHeight; height++ { - if height < divergenceHeight { - primaryHeaders[height] = witnessHeaders[height] + for s, verificationOption := range verificationOptions { + t.Log("==> verification", s) + + // primary performs an equivocation attack + var ( + latestHeight = int64(10) + valSize = 5 + divergenceHeight = int64(6) + primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) + primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) + ) + // validators don't change in this network (however we still use a map just for convenience) + witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime) + witness := mockp.New(chainID, witnessHeaders, witnessValidators) + + for height := int64(1); height <= latestHeight; height++ { + if height < divergenceHeight { + primaryHeaders[height] = witnessHeaders[height] + primaryValidators[height] = witnessValidators[height] + continue + } + // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for + // a different block (which we do by adding txs) + primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, + bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, + witnessValidators[height], witnessValidators[height+1], hash("app_hash"), + hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1) primaryValidators[height] = witnessValidators[height] - continue } - // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for - // a different block (which we do by adding txs) - primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, - bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, - witnessValidators[height], witnessValidators[height+1], hash("app_hash"), - hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1) - primaryValidators[height] = witnessValidators[height] - } - primary := mockp.New(chainID, primaryHeaders, primaryValidators) + primary := mockp.New(chainID, primaryHeaders, primaryValidators) - c, err := light.NewClient( - ctx, - chainID, - light.TrustOptions{ - Period: 4 * time.Hour, - Height: 1, - Hash: primaryHeaders[1].Hash(), - }, - primary, - []provider.Provider{witness}, - dbs.New(dbm.NewMemDB(), chainID), - light.Logger(log.TestingLogger()), - light.MaxRetryAttempts(1), - ) - require.NoError(t, err) + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Period: 4 * time.Hour, + Height: 1, + Hash: primaryHeaders[1].Hash(), + }, + primary, + []provider.Provider{witness}, + dbs.New(dbm.NewMemDB(), chainID), + light.Logger(log.TestingLogger()), + light.MaxRetryAttempts(1), + verificationOption, + ) + require.NoError(t, err) - // Check verification returns an error. - _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) - if assert.Error(t, err) { - assert.Contains(t, err.Error(), "does not match primary") - } + // Check verification returns an error. + _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "does not match primary") + } - // Check evidence was sent to both full nodes. - // Common height should be set to the height of the divergent header in the instance - // of an equivocation attack and the validator sets are the same as what the witness has - evAgainstPrimary := &types.LightClientAttackEvidence{ - ConflictingBlock: &types.LightBlock{ - SignedHeader: primaryHeaders[divergenceHeight], - ValidatorSet: primaryValidators[divergenceHeight], - }, - CommonHeight: divergenceHeight, - } - assert.True(t, witness.HasEvidence(evAgainstPrimary)) + // Check evidence was sent to both full nodes. + // Common height should be set to the height of the divergent header in the instance + // of an equivocation attack and the validator sets are the same as what the witness has + evAgainstPrimary := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: primaryHeaders[divergenceHeight], + ValidatorSet: primaryValidators[divergenceHeight], + }, + CommonHeight: divergenceHeight, + } + assert.True(t, witness.HasEvidence(evAgainstPrimary)) - evAgainstWitness := &types.LightClientAttackEvidence{ - ConflictingBlock: &types.LightBlock{ - SignedHeader: witnessHeaders[divergenceHeight], - ValidatorSet: witnessValidators[divergenceHeight], - }, - CommonHeight: divergenceHeight, + evAgainstWitness := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: witnessHeaders[divergenceHeight], + ValidatorSet: witnessValidators[divergenceHeight], + }, + CommonHeight: divergenceHeight, + } + assert.True(t, primary.HasEvidence(evAgainstWitness)) } - assert.True(t, primary.HasEvidence(evAgainstWitness)) } // 1. Different nodes therefore a divergent header is produced. diff --git a/light/errors.go b/light/errors.go index 7c9e7b36da..390d35c585 100644 --- a/light/errors.go +++ b/light/errors.go @@ -60,9 +60,7 @@ func (e ErrVerificationFailed) Unwrap() error { } func (e ErrVerificationFailed) Error() string { - return fmt.Sprintf( - "verify from #%d to #%d failed: %v", - e.From, e.To, e.Reason) + return fmt.Sprintf("verify from #%d to #%d failed: %v", e.From, e.To, e.Reason) } // ----------------------------- INTERNAL ERRORS --------------------------------- From 9d354c842ed3b71a6ff4577ccfbb45a699dd9302 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Wed, 4 Nov 2020 17:14:48 +0100 Subject: [PATCH 057/108] evidence: structs can independently form abci evidence (#5610) --- CHANGELOG_PENDING.md | 1 + consensus/byzantine_test.go | 4 +- consensus/reactor_test.go | 4 +- consensus/state.go | 15 +- evidence/pool.go | 340 ++++------- evidence/pool_test.go | 96 ++-- evidence/reactor.go | 14 +- evidence/reactor_test.go | 42 +- evidence/verify.go | 148 ++--- evidence/verify_test.go | 136 ++--- light/detector.go | 62 +- node/node_test.go | 7 +- proto/tendermint/evidence/types.pb.go | 668 ---------------------- proto/tendermint/evidence/types.proto | 20 - proto/tendermint/types/block.pb.go | 22 +- proto/tendermint/types/block.proto | 2 +- proto/tendermint/types/evidence.pb.go | 783 +++++++++++++++++--------- proto/tendermint/types/evidence.proto | 35 +- rpc/client/evidence_test.go | 5 +- state/execution.go | 23 +- state/execution_test.go | 74 ++- state/mocks/evidence_pool.go | 34 +- state/services.go | 13 +- state/validation.go | 5 +- state/validation_test.go | 2 +- test/e2e/generator/generate.go | 7 +- test/e2e/networks/ci.toml | 7 +- test/e2e/tests/evidence_test.go | 27 +- test/maverick/consensus/state.go | 6 +- types/block.go | 6 +- types/block_test.go | 7 +- types/evidence.go | 254 +++++++-- types/evidence_test.go | 10 +- types/protobuf.go | 26 - types/protobuf_test.go | 20 - 35 files changed, 1270 insertions(+), 1655 deletions(-) delete mode 100644 proto/tendermint/evidence/types.pb.go delete mode 100644 proto/tendermint/evidence/types.proto diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 32cff9bda9..0d5dba43f5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -33,3 +33,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) - [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) - [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) +- [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 4d66602101..fa945c310c 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -162,12 +162,12 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) // Evidence should be submitted and committed at the third height but - // we will check the first five just in case + // we will check the first six just in case evidenceFromEachValidator := make([]types.Evidence, nValidators) wg := new(sync.WaitGroup) wg.Add(4) - for height := 1; height < 5; height++ { + for height := 1; height < 6; height++ { for i := 0; i < nValidators; i++ { go func(j int) { msg := <-blocksSubs[j].Out() diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 192622e43b..f23ec727dc 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -172,9 +172,7 @@ func TestReactorWithEvidence(t *testing.T) { evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return([]types.Evidence{ ev}, int64(len(ev.Bytes()))) - evpool.On("Update", mock.AnythingOfType("state.State")).Return() - evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return( - []abci.Evidence{}) + evpool.On("Update", mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool2 := sm.EmptyEvidencePool{} diff --git a/consensus/state.go b/consensus/state.go index 9397527343..a046840e28 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -73,9 +73,9 @@ type txNotifier interface { // interface to the evidence pool type evidencePool interface { - // Adds consensus based evidence to the evidence pool where time is the time - // of the block where the offense occurred and the validator set is the current one. - AddEvidenceFromConsensus(types.Evidence, time.Time, *types.ValidatorSet) error + // Adds consensus based evidence to the evidence pool. This function differs to + // AddEvidence by bypassing verification and adding it immediately to the pool + AddEvidenceFromConsensus(types.Evidence) error } // State handles execution of the consensus algorithm. @@ -1871,13 +1871,14 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { } else { timestamp = sm.MedianTime(cs.LastCommit.MakeCommit(), cs.LastValidators) } - evidence := types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB) - evidenceErr := cs.evpool.AddEvidenceFromConsensus(evidence, timestamp, cs.Validators) - + // form duplicate vote evidence from the conflicting votes and send it across to the + // evidence pool + ev := types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB, timestamp, cs.Validators) + evidenceErr := cs.evpool.AddEvidenceFromConsensus(ev) if evidenceErr != nil { cs.Logger.Error("Failed to add evidence to the evidence pool", "err", evidenceErr) } else { - cs.Logger.Debug("Added evidence to the evidence pool", "evidence", evidence) + cs.Logger.Debug("Added evidence to the evidence pool", "ev", ev) } return added, err } else if err == types.ErrVoteNonDeterministicSignature { diff --git a/evidence/pool.go b/evidence/pool.go index 580f8c9e8c..77dbf1a395 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" "fmt" - "reflect" + "sort" "sync" "sync/atomic" "time" @@ -13,10 +13,8 @@ import ( gogotypes "github.com/gogo/protobuf/types" dbm "github.com/tendermint/tm-db" - abci "github.com/tendermint/tendermint/abci/types" clist "github.com/tendermint/tendermint/libs/clist" "github.com/tendermint/tendermint/libs/log" - evproto "github.com/tendermint/tendermint/proto/tendermint/evidence" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -94,7 +92,7 @@ func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) { } // Update pulls the latest state to be used for expiration and evidence params and then prunes all expired evidence -func (evpool *Pool) Update(state sm.State) { +func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) { // sanity check if state.LastBlockHeight <= evpool.state.LastBlockHeight { panic(fmt.Sprintf( @@ -109,6 +107,8 @@ func (evpool *Pool) Update(state sm.State) { // update the state evpool.updateState(state) + evpool.markEvidenceAsCommitted(ev) + // prune pending evidence when it has expired. This also updates when the next evidence will expire if evpool.Size() > 0 && state.LastBlockHeight > evpool.pruningHeight && state.LastBlockTime.After(evpool.pruningTime) { @@ -135,13 +135,13 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { } // 1) Verify against state. - evInfo, err := evpool.verify(ev) + err := evpool.verify(ev) if err != nil { return types.NewErrInvalidEvidence(ev, err) } // 2) Save to store. - if err := evpool.addPendingEvidence(evInfo); err != nil { + if err := evpool.addPendingEvidence(ev); err != nil { return fmt.Errorf("can't add evidence to pending list: %w", err) } @@ -153,13 +153,9 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { return nil } -// AddEvidenceFromConsensus should be exposed only to the consensus so it can add evidence to the pool -// directly without the need for verification. -func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, valSet *types.ValidatorSet) error { - var ( - vals []*types.Validator - totalPower int64 - ) +// AddEvidenceFromConsensus should be exposed only to the consensus reactor so it can add evidence +// to the pool directly without the need for verification. +func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence) error { // we already have this evidence, log this but don't return an error. if evpool.isPending(ev) { @@ -167,23 +163,7 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence, time time.Time, return nil } - switch ev := ev.(type) { - case *types.DuplicateVoteEvidence: - _, val := valSet.GetByAddress(ev.VoteA.ValidatorAddress) - vals = append(vals, val) - totalPower = valSet.TotalVotingPower() - default: - return fmt.Errorf("unrecognized evidence type: %T", ev) - } - - evInfo := &info{ - Evidence: ev, - Time: time, - Validators: vals, - TotalVotingPower: totalPower, - } - - if err := evpool.addPendingEvidence(evInfo); err != nil { + if err := evpool.addPendingEvidence(ev); err != nil { return fmt.Errorf("can't add evidence to pending list: %w", err) } // add evidence to be gossiped with peers @@ -210,15 +190,15 @@ func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error { return &types.ErrInvalidEvidence{Evidence: ev, Reason: errors.New("evidence was already committed")} } - evInfo, err := evpool.verify(ev) + err := evpool.verify(ev) if err != nil { return &types.ErrInvalidEvidence{Evidence: ev, Reason: err} } - if err := evpool.addPendingEvidence(evInfo); err != nil { + if err := evpool.addPendingEvidence(ev); err != nil { // Something went wrong with adding the evidence but we already know it is valid // hence we log an error and continue - evpool.logger.Error("Can't add evidence to pending list", "err", err, "evInfo", evInfo) + evpool.logger.Error("Can't add evidence to pending list", "err", err, "ev", ev) } evpool.logger.Info("Verified new evidence of byzantine behavior", "evidence", ev) @@ -236,85 +216,6 @@ func (evpool *Pool) CheckEvidence(evList types.EvidenceList) error { return nil } -// ABCIEvidence processes all the evidence in the block, marking it as committed and removing it -// from the pending database. It then forms the individual abci evidence that will be passed back to -// the application. -func (evpool *Pool) ABCIEvidence(height int64, evidence []types.Evidence) []abci.Evidence { - // make a map of committed evidence to remove from the clist - blockEvidenceMap := make(map[string]struct{}, len(evidence)) - abciEvidence := make([]abci.Evidence, 0) - for _, ev := range evidence { - - // get entire evidence info from pending list - infoBytes, err := evpool.evidenceStore.Get(keyPending(ev)) - if err != nil { - evpool.logger.Error("Unable to retrieve evidence to pass to ABCI. "+ - "Evidence pool should have seen this evidence before", - "evidence", ev, "err", err) - continue - } - var infoProto evproto.Info - err = infoProto.Unmarshal(infoBytes) - if err != nil { - evpool.logger.Error("Decoding evidence info failed", "err", err, "height", ev.Height(), "hash", ev.Hash()) - continue - } - evInfo, err := infoFromProto(&infoProto) - if err != nil { - evpool.logger.Error("Converting evidence info from proto failed", "err", err, "height", ev.Height(), - "hash", ev.Hash()) - continue - } - - var evType abci.EvidenceType - switch ev.(type) { - case *types.DuplicateVoteEvidence: - evType = abci.EvidenceType_DUPLICATE_VOTE - case *types.LightClientAttackEvidence: - evType = abci.EvidenceType_LIGHT_CLIENT_ATTACK - default: - evpool.logger.Error("Unknown evidence type", "T", reflect.TypeOf(ev)) - continue - } - for _, val := range evInfo.Validators { - abciEv := abci.Evidence{ - Type: evType, - Validator: types.TM2PB.Validator(val), - Height: ev.Height(), - Time: evInfo.Time, - TotalVotingPower: evInfo.TotalVotingPower, - } - abciEvidence = append(abciEvidence, abciEv) - evpool.logger.Info("Created ABCI evidence", "ev", abciEv) - } - - // we can now remove the evidence from the pending list and the clist that we use for gossiping - evpool.removePendingEvidence(ev) - blockEvidenceMap[evMapKey(ev)] = struct{}{} - - // Add evidence to the committed list - // As the evidence is stored in the block store we only need to record the height that it was saved at. - key := keyCommitted(ev) - - h := gogotypes.Int64Value{Value: height} - evBytes, err := proto.Marshal(&h) - if err != nil { - panic(err) - } - - if err := evpool.evidenceStore.Set(key, evBytes); err != nil { - evpool.logger.Error("Unable to add committed evidence", "err", err) - } - } - - // remove committed evidence from the clist - if len(blockEvidenceMap) != 0 { - evpool.removeEvidenceFromList(blockEvidenceMap) - } - - return abciEvidence -} - // EvidenceFront goes to the first evidence in the clist func (evpool *Pool) EvidenceFront() *clist.CElement { return evpool.evidenceList.Front() @@ -330,6 +231,7 @@ func (evpool *Pool) SetLogger(l log.Logger) { evpool.logger = l } +// Size returns the number of evidence in the pool. func (evpool *Pool) Size() uint32 { return atomic.LoadUint32(&evpool.evidenceSize) } @@ -343,106 +245,59 @@ func (evpool *Pool) State() sm.State { //-------------------------------------------------------------------------- -// Info is a wrapper around the evidence that the evidence pool receives with extensive -// information of what validators were malicious, the time of the attack and the total voting power -// This is saved as a form of cache so that the evidence pool can easily produce the ABCI Evidence -// needed to be sent to the application. -type info struct { - Evidence types.Evidence - Time time.Time - Validators []*types.Validator - TotalVotingPower int64 - ByteSize int64 -} - -// ToProto encodes into protobuf -func (ei info) ToProto() (*evproto.Info, error) { - evpb, err := types.EvidenceToProto(ei.Evidence) - if err != nil { - return nil, err - } - - valsProto := make([]*tmproto.Validator, len(ei.Validators)) - for i := 0; i < len(ei.Validators); i++ { - valp, err := ei.Validators[i].ToProto() - if err != nil { - return nil, err - } - valsProto[i] = valp - } - - return &evproto.Info{ - Evidence: *evpb, - Time: ei.Time, - Validators: valsProto, - TotalVotingPower: ei.TotalVotingPower, - }, nil -} - -// InfoFromProto decodes from protobuf into Info -func infoFromProto(proto *evproto.Info) (info, error) { - if proto == nil { - return info{}, errors.New("nil evidence info") - } - - ev, err := types.EvidenceFromProto(&proto.Evidence) - if err != nil { - return info{}, err - } - - vals := make([]*types.Validator, len(proto.Validators)) - for i := 0; i < len(proto.Validators); i++ { - val, err := types.ValidatorFromProto(proto.Validators[i]) - if err != nil { - return info{}, err - } - vals[i] = val - } - - return info{ - Evidence: ev, - Time: proto.Time, - Validators: vals, - TotalVotingPower: proto.TotalVotingPower, - ByteSize: int64(proto.Evidence.Size()), - }, nil - -} - -//-------------------------------------------------------------------------- - // fastCheck leverages the fact that the evidence pool may have already verified the evidence to see if it can // quickly conclude that the evidence is already valid. func (evpool *Pool) fastCheck(ev types.Evidence) bool { - key := keyPending(ev) if lcae, ok := ev.(*types.LightClientAttackEvidence); ok { + key := keyPending(ev) evBytes, err := evpool.evidenceStore.Get(key) if evBytes == nil { // the evidence is not in the nodes pending list return false } if err != nil { - evpool.logger.Error("Failed to load evidence", "err", err, "evidence", lcae) + evpool.logger.Error("Failed to load light client attack evidence", "err", err, "key(height/hash)", key) return false } - evInfo, err := bytesToInfo(evBytes) + var trustedPb tmproto.LightClientAttackEvidence + err = trustedPb.Unmarshal(evBytes) if err != nil { - evpool.logger.Error("Failed to convert evidence from proto", "err", err, "evidence", lcae) + evpool.logger.Error("Failed to convert light client attack evidence from bytes", + "err", err, "key(height/hash)", key) return false } - // ensure that all the validators that the evidence pool have found to be malicious - // are present in the list of commit signatures in the conflicting block - OUTER: - for _, sig := range lcae.ConflictingBlock.Commit.Signatures { - for _, val := range evInfo.Validators { - if bytes.Equal(val.Address, sig.ValidatorAddress) { - continue OUTER - } - } - // a validator we know is malicious is not included in the commit - evpool.logger.Info("Fast check failed: a validator we know is malicious is not " + - "in the commit sigs. Reverting to full verification") + trustedEv, err := types.LightClientAttackEvidenceFromProto(&trustedPb) + if err != nil { + evpool.logger.Error("Failed to convert light client attack evidence from protobuf", + "err", err, "key(height/hash)", key) + return false + } + // ensure that all the byzantine validators that the evidence pool has match the byzantine validators + // in this evidence + if trustedEv.ByzantineValidators == nil && lcae.ByzantineValidators != nil { return false } + + if len(trustedEv.ByzantineValidators) != len(lcae.ByzantineValidators) { + return false + } + + byzValsCopy := make([]*types.Validator, len(lcae.ByzantineValidators)) + for i, v := range lcae.ByzantineValidators { + byzValsCopy[i] = v.Copy() + } + + // ensure that both validator arrays are in the same order + sort.Sort(types.ValidatorsByVotingPower(byzValsCopy)) + + for idx, val := range trustedEv.ByzantineValidators { + if !bytes.Equal(byzValsCopy[idx].Address, val.Address) { + return false + } + if byzValsCopy[idx].VotingPower != val.VotingPower { + return false + } + } + return true } @@ -482,8 +337,8 @@ func (evpool *Pool) isPending(evidence types.Evidence) bool { return ok } -func (evpool *Pool) addPendingEvidence(evInfo *info) error { - evpb, err := evInfo.ToProto() +func (evpool *Pool) addPendingEvidence(ev types.Evidence) error { + evpb, err := types.EvidenceToProto(ev) if err != nil { return fmt.Errorf("unable to convert to proto, err: %w", err) } @@ -493,7 +348,7 @@ func (evpool *Pool) addPendingEvidence(evInfo *info) error { return fmt.Errorf("unable to marshal evidence: %w", err) } - key := keyPending(evInfo.Evidence) + key := keyPending(ev) err = evpool.evidenceStore.Set(key, evBytes) if err != nil { @@ -513,31 +368,80 @@ func (evpool *Pool) removePendingEvidence(evidence types.Evidence) { } } +// markEvidenceAsCommitted processes all the evidence in the block, marking it as +// committed and removing it from the pending database. +func (evpool *Pool) markEvidenceAsCommitted(evidence types.EvidenceList) { + blockEvidenceMap := make(map[string]struct{}, len(evidence)) + for _, ev := range evidence { + if evpool.isPending(ev) { + evpool.removePendingEvidence(ev) + blockEvidenceMap[evMapKey(ev)] = struct{}{} + } + + // Add evidence to the committed list. As the evidence is stored in the block store + // we only need to record the height that it was saved at. + key := keyCommitted(ev) + + h := gogotypes.Int64Value{Value: ev.Height()} + evBytes, err := proto.Marshal(&h) + if err != nil { + evpool.logger.Error("failed to marshal committed evidence", "err", err, "key(height/hash)", key) + continue + } + + if err := evpool.evidenceStore.Set(key, evBytes); err != nil { + evpool.logger.Error("Unable to save committed evidence", "err", err, "key(height/hash)", key) + } + } + + // remove committed evidence from the clist + if len(blockEvidenceMap) != 0 { + evpool.removeEvidenceFromList(blockEvidenceMap) + } +} + // listEvidence retrieves lists evidence from oldest to newest within maxBytes. // If maxBytes is -1, there's no cap on the size of returned evidence. func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Evidence, int64, error) { - var totalSize int64 - var evidence []types.Evidence + var ( + evSize int64 + totalSize int64 + evidence []types.Evidence + evList tmproto.EvidenceList // used for calculating the bytes size + ) + iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey}) if err != nil { return nil, totalSize, fmt.Errorf("database error: %v", err) } defer iter.Close() for ; iter.Valid(); iter.Next() { - evInfo, err := bytesToInfo(iter.Value()) + var evpb tmproto.Evidence + err := evpb.Unmarshal(iter.Value()) if err != nil { - return nil, totalSize, err + return evidence, totalSize, err + } + evList.Evidence = append(evList.Evidence, evpb) + evSize = int64(evList.Size()) + if maxBytes != -1 && evSize > maxBytes { + if err := iter.Error(); err != nil { + return evidence, totalSize, err + } + return evidence, totalSize, nil } - totalSize += evInfo.ByteSize - - if maxBytes != -1 && totalSize > maxBytes { - return evidence, totalSize - evInfo.ByteSize, nil + ev, err := types.EvidenceFromProto(&evpb) + if err != nil { + return nil, totalSize, err } - evidence = append(evidence, evInfo.Evidence) + totalSize = evSize + evidence = append(evidence, ev) } + if err := iter.Error(); err != nil { + return evidence, totalSize, err + } return evidence, totalSize, nil } @@ -550,22 +454,22 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { defer iter.Close() blockEvidenceMap := make(map[string]struct{}) for ; iter.Valid(); iter.Next() { - evInfo, err := bytesToInfo(iter.Value()) + ev, err := bytesToEv(iter.Value()) if err != nil { evpool.logger.Error("Error in transition evidence from protobuf", "err", err) continue } - if !evpool.isExpired(evInfo.Evidence.Height(), evInfo.Time) { + if !evpool.isExpired(ev.Height(), ev.Time()) { if len(blockEvidenceMap) != 0 { evpool.removeEvidenceFromList(blockEvidenceMap) } // return the height and time with which this evidence will have expired so we know when to prune next - return evInfo.Evidence.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1, - evInfo.Time.Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second) + return ev.Height() + evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks + 1, + ev.Time().Add(evpool.State().ConsensusParams.Evidence.MaxAgeDuration).Add(time.Second) } - evpool.removePendingEvidence(evInfo.Evidence) - blockEvidenceMap[evMapKey(evInfo.Evidence)] = struct{}{} + evpool.removePendingEvidence(ev) + blockEvidenceMap[evMapKey(ev)] = struct{}{} } // We either have no pending evidence or all evidence has expired if len(blockEvidenceMap) != 0 { @@ -593,14 +497,14 @@ func (evpool *Pool) updateState(state sm.State) { evpool.state = state } -func bytesToInfo(evBytes []byte) (info, error) { - var evpb evproto.Info +func bytesToEv(evBytes []byte) (types.Evidence, error) { + var evpb tmproto.Evidence err := evpb.Unmarshal(evBytes) if err != nil { - return info{}, err + return &types.DuplicateVoteEvidence{}, err } - return infoFromProto(&evpb) + return types.EvidenceFromProto(&evpb) } func evMapKey(ev types.Evidence) string { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 9dc657221b..046f4efc53 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -11,7 +11,6 @@ import ( dbm "github.com/tendermint/tm-db" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/evidence" "github.com/tendermint/tendermint/evidence/mocks" "github.com/tendermint/tendermint/libs/log" @@ -45,7 +44,7 @@ func TestEvidencePoolBasic(t *testing.T) { blockStore = &mocks.BlockStore{} ) - valSet, privVals := types.RandValidatorSet(3, 10) + valSet, privVals := types.RandValidatorSet(1, 10) blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return( &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}}, @@ -83,9 +82,10 @@ func TestEvidencePoolBasic(t *testing.T) { next := pool.EvidenceFront() assert.Equal(t, ev, next.Value.(types.Evidence)) - evs, size = pool.PendingEvidence(defaultEvidenceMaxBytes) + const evidenceBytes int64 = 372 + evs, size = pool.PendingEvidence(evidenceBytes) assert.Equal(t, 1, len(evs)) - assert.Equal(t, int64(357), size) // check that the size of the single evidence in bytes is correct + assert.Equal(t, evidenceBytes, size) // check that the size of the single evidence in bytes is correct // shouldn't be able to add evidence twice assert.NoError(t, pool.AddEvidence(ev)) @@ -108,7 +108,7 @@ func TestAddExpiredEvidence(t *testing.T) { blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(func(h int64) *types.BlockMeta { if h == height || h == expiredHeight { - return &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime.Add(time.Duration(height) * time.Minute)}} + return &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}} } return &types.BlockMeta{Header: types.Header{Time: expiredEvidenceTime}} }) @@ -127,6 +127,7 @@ func TestAddExpiredEvidence(t *testing.T) { {height - 1, expiredEvidenceTime, false, "valid evidence (despite old time)"}, {expiredHeight - 1, expiredEvidenceTime, true, "evidence from height 1 (created at: 2019-01-01 00:00:00 +0000 UTC) is too old"}, + {height, defaultEvidenceTime.Add(1 * time.Minute), true, "evidence time and block time is different"}, } for _, tc := range testCases { @@ -147,15 +148,13 @@ func TestAddEvidenceFromConsensus(t *testing.T) { var height int64 = 10 pool, val := defaultTestPool(height) ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) - valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(2)}) - err := pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime, valSet) + err := pool.AddEvidenceFromConsensus(ev) assert.NoError(t, err) next := pool.EvidenceFront() assert.Equal(t, ev, next.Value.(types.Evidence)) // shouldn't be able to submit the same evidence twice - err = pool.AddEvidenceFromConsensus(ev, defaultEvidenceTime.Add(-1*time.Second), - types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(3)})) + err = pool.AddEvidenceFromConsensus(ev) assert.NoError(t, err) evs, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) assert.Equal(t, 1, len(evs)) @@ -167,11 +166,12 @@ func TestEvidencePoolUpdate(t *testing.T) { state := pool.State() // create new block (no need to save it to blockStore) - prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime, + prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) err := pool.AddEvidence(prunedEv) require.NoError(t, err) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) + ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute), + val, evidenceChainID) lastCommit := makeCommit(height, val.PrivKey.PubKey().Address()) block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{ev}) // update state (partially) @@ -180,22 +180,7 @@ func TestEvidencePoolUpdate(t *testing.T) { err = pool.CheckEvidence(types.EvidenceList{ev}) require.NoError(t, err) - byzVals := pool.ABCIEvidence(block.Height, block.Evidence.Evidence) - expectedByzVals := []abci.Evidence{ - { - Type: abci.EvidenceType_DUPLICATE_VOTE, - Validator: types.TM2PB.Validator(val.ExtractIntoValidator(10)), - Height: height, - Time: defaultEvidenceTime.Add(time.Duration(height) * time.Minute), - TotalVotingPower: 10, - }, - } - assert.Equal(t, expectedByzVals, byzVals) - evList, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) - assert.Equal(t, 1, len(evList)) - - pool.Update(state) - + pool.Update(state, block.Evidence.Evidence) // a) Update marks evidence as committed so pending evidence should be empty evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes) assert.Empty(t, evList) @@ -206,14 +191,13 @@ func TestEvidencePoolUpdate(t *testing.T) { if assert.Error(t, err) { assert.Equal(t, "evidence was already committed", err.(*types.ErrInvalidEvidence).Reason.Error()) } - - assert.Empty(t, pool.ABCIEvidence(height, []types.Evidence{})) } func TestVerifyPendingEvidencePasses(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) + ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + val, evidenceChainID) err := pool.AddEvidence(ev) require.NoError(t, err) @@ -224,20 +208,27 @@ func TestVerifyPendingEvidencePasses(t *testing.T) { func TestVerifyDuplicatedEvidenceFails(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) + ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + val, evidenceChainID) err := pool.CheckEvidence(types.EvidenceList{ev, ev}) if assert.Error(t, err) { assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error()) } } -// check that +// check that valid light client evidence is correctly validated and stored in +// evidence pool func TestCheckEvidenceWithLightClientAttack(t *testing.T) { - nValidators := 5 - conflictingVals, conflictingPrivVals := types.RandValidatorSet(nValidators, 10) - trustedHeader := makeHeaderRandom(10) + var ( + nValidators = 5 + validatorPower int64 = 10 + height int64 = 10 + ) + conflictingVals, conflictingPrivVals := types.RandValidatorSet(nValidators, validatorPower) + trustedHeader := makeHeaderRandom(height) + trustedHeader.Time = defaultEvidenceTime - conflictingHeader := makeHeaderRandom(10) + conflictingHeader := makeHeaderRandom(height) conflictingHeader.ValidatorsHash = conflictingVals.Hash() trustedHeader.ValidatorsHash = conflictingHeader.ValidatorsHash @@ -249,8 +240,8 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) { // for simplicity we are simulating a duplicate vote attack where all the validators in the // conflictingVals set voted twice blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash")) - voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) - commit, err := types.MakeCommit(blockID, 10, 1, voteSet, conflictingPrivVals, defaultEvidenceTime) + voteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals) + commit, err := types.MakeCommit(blockID, height, 1, voteSet, conflictingPrivVals, defaultEvidenceTime) require.NoError(t, err) ev := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ @@ -260,12 +251,16 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) { }, ValidatorSet: conflictingVals, }, - CommonHeight: 10, + CommonHeight: 10, + TotalVotingPower: int64(nValidators) * validatorPower, + ByzantineValidators: conflictingVals.Validators, + Timestamp: defaultEvidenceTime, } trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) - trustedVoteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) - trustedCommit, err := types.MakeCommit(trustedBlockID, 10, 1, trustedVoteSet, conflictingPrivVals, defaultEvidenceTime) + trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals) + trustedCommit, err := types.MakeCommit(trustedBlockID, height, 1, trustedVoteSet, conflictingPrivVals, + defaultEvidenceTime) require.NoError(t, err) state := sm.State{ @@ -274,11 +269,11 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) { ConsensusParams: *types.DefaultConsensusParams(), } stateStore := &smmocks.Store{} - stateStore.On("LoadValidators", int64(10)).Return(conflictingVals, nil) + stateStore.On("LoadValidators", height).Return(conflictingVals, nil) stateStore.On("Load").Return(state, nil) blockStore := &mocks.BlockStore{} - blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader}) - blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit) + blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trustedHeader}) + blockStore.On("LoadBlockCommit", height).Return(trustedCommit) pool, err := evidence.NewPool(dbm.NewMemDB(), stateStore, blockStore) require.NoError(t, err) @@ -291,17 +286,14 @@ func TestCheckEvidenceWithLightClientAttack(t *testing.T) { assert.NoError(t, err) // take away the last signature -> there are less validators then what we have detected, - // hence we move to full verification where the evidence should still pass + // hence this should fail commit.Signatures = append(commit.Signatures[:nValidators-1], types.NewCommitSigAbsent()) err = pool.CheckEvidence(types.EvidenceList{ev}) - assert.NoError(t, err) - - // take away the last two signatures -> should fail due to insufficient power - commit.Signatures = append(commit.Signatures[:nValidators-2], types.NewCommitSigAbsent(), types.NewCommitSigAbsent()) - err = pool.CheckEvidence(types.EvidenceList{ev}) assert.Error(t, err) } +// Tests that restarting the evidence pool after a potential failure will recover the +// pending evidence and continue to gossip it func TestRecoverPendingEvidence(t *testing.T) { height := int64(10) val := types.NewMockPV() @@ -316,9 +308,9 @@ func TestRecoverPendingEvidence(t *testing.T) { require.NoError(t, err) pool.SetLogger(log.TestingLogger()) goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height, - defaultEvidenceTime, val, evidenceChainID) + defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID) expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), - defaultEvidenceTime, val, evidenceChainID) + defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) err = pool.AddEvidence(goodEvidence) require.NoError(t, err) err = pool.AddEvidence(expiredEvidence) diff --git a/evidence/reactor.go b/evidence/reactor.go index e9003ded18..421e4bc18a 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -7,7 +7,6 @@ import ( clist "github.com/tendermint/tendermint/libs/clist" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" - ep "github.com/tendermint/tendermint/proto/tendermint/evidence" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) @@ -128,7 +127,7 @@ func (evR *Reactor) broadcastEvidenceRoutine(peer p2p.Peer) { if err != nil { panic(err) } - + evR.Logger.Debug("Gossiping evidence to peer", "ev", ev, "peer", peer.ID()) success := peer.Send(EvidenceChannel, msgBytes) if !success { time.Sleep(peerRetryMessageIntervalMS * time.Millisecond) @@ -210,16 +209,15 @@ type PeerState interface { // encodemsg takes a array of evidence // returns the byte encoding of the List Message func encodeMsg(evis []types.Evidence) ([]byte, error) { - evi := make([]*tmproto.Evidence, len(evis)) + evi := make([]tmproto.Evidence, len(evis)) for i := 0; i < len(evis); i++ { ev, err := types.EvidenceToProto(evis[i]) if err != nil { return nil, err } - evi[i] = ev + evi[i] = *ev } - - epl := ep.List{ + epl := tmproto.EvidenceList{ Evidence: evi, } @@ -229,14 +227,14 @@ func encodeMsg(evis []types.Evidence) ([]byte, error) { // decodemsg takes an array of bytes // returns an array of evidence func decodeMsg(bz []byte) (evis []types.Evidence, err error) { - lm := ep.List{} + lm := tmproto.EvidenceList{} if err := lm.Unmarshal(bz); err != nil { return nil, err } evis = make([]types.Evidence, len(lm.Evidence)) for i := 0; i < len(lm.Evidence); i++ { - ev, err := types.EvidenceFromProto(lm.Evidence[i]) + ev, err := types.EvidenceFromProto(&lm.Evidence[i]) if err != nil { return nil, err } diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index 03a250ed6c..170b45348e 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -21,7 +21,6 @@ import ( "github.com/tendermint/tendermint/evidence/mocks" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" - ep "github.com/tendermint/tendermint/proto/tendermint/evidence" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -119,15 +118,17 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { var height int64 = 10 // DB1 is ahead of DB2 - stateDB1 := initializeValidatorState(val, height) + stateDB1 := initializeValidatorState(val, height-1) stateDB2 := initializeValidatorState(val, height-2) + state, err := stateDB1.Load() + require.NoError(t, err) + state.LastBlockHeight++ // make reactors from statedb reactors, pools := makeAndConnectReactorsAndPools(config, []sm.Store{stateDB1, stateDB2}) evList := sendEvidence(t, pools[0], val, 2) - abciEvs := pools[0].ABCIEvidence(height, evList) - require.EqualValues(t, 2, len(abciEvs)) + pools[0].Update(state, evList) require.EqualValues(t, uint32(0), pools[0].Size()) time.Sleep(100 * time.Millisecond) @@ -150,7 +151,7 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { evList = make([]types.Evidence, 3) for i := 0; i < 3; i++ { ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), - time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID) + time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, state.ChainID) err := pools[0].AddEvidence(ev) require.NoError(t, err) evList[i] = ev @@ -160,18 +161,19 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { time.Sleep(300 * time.Millisecond) // the second pool should only have received the first evidence because it is behind - peerEv, _ := pools[1].PendingEvidence(1000) + peerEv, _ := pools[1].PendingEvidence(10000) assert.EqualValues(t, []types.Evidence{evList[0]}, peerEv) // the last evidence is committed and the second reactor catches up in state to the first // reactor. We therefore expect that the second reactor only receives one more evidence, the // one that is still pending and not the evidence that has already been committed. - _ = pools[0].ABCIEvidence(height, []types.Evidence{evList[2]}) + state.LastBlockHeight++ + pools[0].Update(state, []types.Evidence{evList[2]}) // the first reactor should have the two remaining pending evidence require.EqualValues(t, uint32(2), pools[0].Size()) // now update the state of the second reactor - pools[1].Update(sm.State{LastBlockHeight: height}) + pools[1].Update(state, types.EvidenceList{}) peer = reactors[0].Switch.Peers().List()[0] ps = peerState{height} peer.Set(types.PeerStateKey, ps) @@ -180,7 +182,7 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { time.Sleep(300 * time.Millisecond) peerEv, _ = pools[1].PendingEvidence(1000) - assert.EqualValues(t, evList[0:1], peerEv) + assert.EqualValues(t, []types.Evidence{evList[0], evList[1]}, peerEv) } // evidenceLogger is a TestingLogger which uses a different @@ -331,27 +333,39 @@ func exampleVote(t byte) *types.Vote { // nolint:lll //ignore line length for tests func TestEvidenceVectors(t *testing.T) { - dupl := types.NewDuplicateVoteEvidence(exampleVote(1), exampleVote(2)) + val := &types.Validator{ + Address: crypto.AddressHash([]byte("validator_address")), + VotingPower: 10, + } + + valSet := types.NewValidatorSet([]*types.Validator{val}) + + dupl := types.NewDuplicateVoteEvidence( + exampleVote(1), + exampleVote(2), + defaultEvidenceTime, + valSet, + ) testCases := []struct { testName string evidenceList []types.Evidence expBytes string }{ - {"DuplicateVoteEvidence", []types.Evidence{dupl}, "0af9010af6010a79080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb031279080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03"}, + {"DuplicateVoteEvidence", []types.Evidence{dupl}, "0a85020a82020a79080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb031279080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0b08b1d381d20510809dca6f32146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb03180a200a2a060880dbaae105"}, } for _, tc := range testCases { tc := tc - evi := make([]*tmproto.Evidence, len(tc.evidenceList)) + evi := make([]tmproto.Evidence, len(tc.evidenceList)) for i := 0; i < len(tc.evidenceList); i++ { ev, err := types.EvidenceToProto(tc.evidenceList[i]) require.NoError(t, err, tc.testName) - evi[i] = ev + evi[i] = *ev } - epl := ep.List{ + epl := tmproto.EvidenceList{ Evidence: evi, } diff --git a/evidence/verify.go b/evidence/verify.go index 53717d4be0..0721ade9a1 100644 --- a/evidence/verify.go +++ b/evidence/verify.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "sort" "time" "github.com/tendermint/tendermint/light" @@ -16,7 +17,7 @@ import ( // - it is from a key who was a validator at the given height // - it is internally consistent with state // - it was properly signed by the alleged equivocator and meets the individual evidence verification requirements -func (evpool *Pool) verify(evidence types.Evidence) (*info, error) { +func (evpool *Pool) verify(evidence types.Evidence) error { var ( state = evpool.State() height = state.LastBlockHeight @@ -27,14 +28,18 @@ func (evpool *Pool) verify(evidence types.Evidence) (*info, error) { // verify the time of the evidence blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height()) if blockMeta == nil { - return nil, fmt.Errorf("don't have header at height #%d", evidence.Height()) + return fmt.Errorf("don't have header #%d", evidence.Height()) } evTime := blockMeta.Header.Time + if evidence.Time() != evTime { + return fmt.Errorf("evidence has a different time to the block it is associated with (%v != %v)", + evidence.Time(), evTime) + } ageDuration := state.LastBlockTime.Sub(evTime) // check that the evidence hasn't expired if ageDuration > evidenceParams.MaxAgeDuration && ageNumBlocks > evidenceParams.MaxAgeNumBlocks { - return nil, fmt.Errorf( + return fmt.Errorf( "evidence from height %d (created at: %v) is too old; min height is %d and evidence can not be older than %v", evidence.Height(), evTime, @@ -48,62 +53,66 @@ func (evpool *Pool) verify(evidence types.Evidence) (*info, error) { case *types.DuplicateVoteEvidence: valSet, err := evpool.stateDB.LoadValidators(evidence.Height()) if err != nil { - return nil, err + return err } - err = VerifyDuplicateVote(ev, state.ChainID, valSet) - if err != nil { - return nil, fmt.Errorf("verifying duplicate vote evidence: %w", err) - } - - _, val := valSet.GetByAddress(ev.VoteA.ValidatorAddress) - - return &info{ - Evidence: evidence, - Time: evTime, - Validators: []*types.Validator{val}, // just a single validator for duplicate vote evidence - TotalVotingPower: valSet.TotalVotingPower(), - }, nil + return VerifyDuplicateVote(ev, state.ChainID, valSet) case *types.LightClientAttackEvidence: commonHeader, err := getSignedHeader(evpool.blockStore, evidence.Height()) if err != nil { - return nil, err + return err } commonVals, err := evpool.stateDB.LoadValidators(evidence.Height()) if err != nil { - return nil, err + return err } trustedHeader := commonHeader // in the case of lunatic the trusted header is different to the common header if evidence.Height() != ev.ConflictingBlock.Height { trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height) if err != nil { - return nil, err + return err } } err = VerifyLightClientAttack(ev, commonHeader, trustedHeader, commonVals, state.LastBlockTime, state.ConsensusParams.Evidence.MaxAgeDuration) if err != nil { - return nil, err + return err } // find out what type of attack this was and thus extract the malicious validators. Note in the case of an // Amnesia attack we don't have any malicious validators. - validators, attackType := getMaliciousValidators(ev, commonVals, trustedHeader) - totalVotingPower := ev.ConflictingBlock.ValidatorSet.TotalVotingPower() - if attackType == lunaticType { - totalVotingPower = commonVals.TotalVotingPower() + validators := ev.GetByzantineValidators(commonVals, trustedHeader) + // ensure this matches the validators that are listed in the evidence. They should be ordered based on power. + if validators == nil && ev.ByzantineValidators != nil { + return fmt.Errorf("expected nil validators from an amnesia light client attack but got %d", + len(ev.ByzantineValidators)) + } + + if exp, got := len(validators), len(ev.ByzantineValidators); exp != got { + return fmt.Errorf("expected %d byzantine validators from evidence but got %d", + exp, got) + } + + // ensure that both validator arrays are in the same order + sort.Sort(types.ValidatorsByVotingPower(ev.ByzantineValidators)) + + for idx, val := range validators { + if !bytes.Equal(ev.ByzantineValidators[idx].Address, val.Address) { + return fmt.Errorf("evidence contained a different byzantine validator address to the one we were expecting."+ + "Expected %v, got %v", val.Address, ev.ByzantineValidators[idx].Address) + } + if ev.ByzantineValidators[idx].VotingPower != val.VotingPower { + return fmt.Errorf("evidence contained a byzantine validator with a different power to the one we were expecting."+ + "Expected %d, got %d", val.VotingPower, ev.ByzantineValidators[idx].VotingPower) + } } - return &info{ - Evidence: evidence, - Time: evTime, - Validators: validators, - TotalVotingPower: totalVotingPower, - }, nil + return nil default: - return nil, fmt.Errorf("unrecognized evidence type: %T", evidence) + return fmt.Errorf("unrecognized evidence type: %T", evidence) } + } // VerifyLightClientAttack verifies LightClientAttackEvidence against the state of the full node. This involves @@ -134,8 +143,13 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, t } } + if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal { + return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", + evTotal, valsTotal) + } + if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { - return fmt.Errorf("trusted header hash matches the evidence conflicting header hash: %X", + return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X", trustedHeader.Hash()) } @@ -186,6 +200,17 @@ func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)", addr, pubKey, pubKey.Address()) } + + // validator voting power and total voting power must match + if val.VotingPower != e.ValidatorPower { + return fmt.Errorf("validator power from evidence and our validator set does not match (%d != %d)", + e.ValidatorPower, val.VotingPower) + } + if valSet.TotalVotingPower() != e.TotalVotingPower { + return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", + e.TotalVotingPower, valSet.TotalVotingPower()) + } + va := e.VoteA.ToProto() vb := e.VoteB.ToProto() // Signatures must be valid @@ -214,55 +239,6 @@ func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader, }, nil } -// getMaliciousValidators finds out what style of attack LightClientAttackEvidence was and then works out who -// the malicious validators were and returns them. -func getMaliciousValidators(evidence *types.LightClientAttackEvidence, commonVals *types.ValidatorSet, - trusted *types.SignedHeader) ([]*types.Validator, lightClientAttackType) { - var validators []*types.Validator - // First check if the header is invalid. This means that it is a lunatic attack and therefore we take the - // validators who are in the commonVals and voted for the lunatic header - if isInvalidHeader(trusted.Header, evidence.ConflictingBlock.Header) { - for _, commitSig := range evidence.ConflictingBlock.Commit.Signatures { - if !commitSig.ForBlock() { - continue - } - - _, val := commonVals.GetByAddress(commitSig.ValidatorAddress) - if val == nil { - // validator wasn't in the common validator set - continue - } - validators = append(validators, val) - } - return validators, lunaticType - // Next, check to see if it is an equivocation attack and both commits are in the same round. If this is the - // case then we take the validators from the conflicting light block validator set that voted in both headers. - } else if trusted.Commit.Round == evidence.ConflictingBlock.Commit.Round { - // validator hashes are the same therefore the indexing order of validators are the same and thus we - // only need a single loop to find the validators that voted twice. - for i := 0; i < len(evidence.ConflictingBlock.Commit.Signatures); i++ { - sigA := evidence.ConflictingBlock.Commit.Signatures[i] - if sigA.Absent() { - continue - } - - sigB := trusted.Commit.Signatures[i] - if sigB.Absent() { - continue - } - - _, val := evidence.ConflictingBlock.ValidatorSet.GetByAddress(sigA.ValidatorAddress) - validators = append(validators, val) - } - return validators, equivocationType - - } - // if the rounds are different then this is an amnesia attack. Unfortunately, given the nature of the attack, - // we aren't able yet to deduce which are malicious validators and which are not hence we return an - // empty validator set. - return validators, amnesiaType -} - // isInvalidHeader takes a trusted header and matches it againt a conflicting header // to determine whether the conflicting header was the product of a valid state transition // or not. If it is then all the deterministic fields of the header should be the same. @@ -274,11 +250,3 @@ func isInvalidHeader(trusted, conflicting *types.Header) bool { !bytes.Equal(trusted.AppHash, conflicting.AppHash) || !bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash) } - -type lightClientAttackType int - -const ( - lunaticType lightClientAttackType = iota + 1 - equivocationType - amnesiaType -) diff --git a/evidence/verify_test.go b/evidence/verify_test.go index e344cd4967..0e72582b29 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -9,7 +9,6 @@ import ( dbm "github.com/tendermint/tm-db" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/evidence" @@ -33,14 +32,14 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { conflictingPrivVals := append(commonPrivVals, newPrivVal) commonHeader := makeHeaderRandom(4) - commonHeader.Time = defaultEvidenceTime.Add(-1 * time.Hour) + commonHeader.Time = defaultEvidenceTime trustedHeader := makeHeaderRandom(10) conflictingHeader := makeHeaderRandom(10) + conflictingHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) conflictingHeader.ValidatorsHash = conflictingVals.Hash() - // we are simulating a duplicate vote attack where all the validators in the conflictingVals set - // vote twice + // we are simulating a lunatic light client attack blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash")) voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals) commit, err := types.MakeCommit(blockID, 10, 1, voteSet, conflictingPrivVals, defaultEvidenceTime) @@ -53,7 +52,10 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { }, ValidatorSet: conflictingVals, }, - CommonHeight: 4, + CommonHeight: 4, + TotalVotingPower: 20, + ByzantineValidators: commonVals.Validators, + Timestamp: defaultEvidenceTime, } commonSignedHeader := &types.SignedHeader{ @@ -72,16 +74,23 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { // good pass -> no error err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, trustedSignedHeader, commonVals, - defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) + defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour) assert.NoError(t, err) // trusted and conflicting hashes are the same -> an error should be returned err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, ev.ConflictingBlock.SignedHeader, commonVals, - defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) + defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour) assert.Error(t, err) + // evidence with different total validator power should fail + ev.TotalVotingPower = 1 + err = evidence.VerifyLightClientAttack(ev, commonSignedHeader, trustedSignedHeader, commonVals, + defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour) + assert.Error(t, err) + ev.TotalVotingPower = 20 + state := sm.State{ - LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute), + LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour), LastBlockHeight: 11, ConsensusParams: *types.DefaultConsensusParams(), } @@ -105,27 +114,18 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) - pubKey, err := newPrivVal.GetPubKey() - require.NoError(t, err) - lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address()) - block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev}) - - abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence) - expectedAbciEv := make([]abci.Evidence, len(commonVals.Validators)) - - // we expect evidence to be made for all validators in the common validator set - for idx, val := range commonVals.Validators { - ev := abci.Evidence{ - Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, - Validator: types.TM2PB.Validator(val), - Height: commonHeader.Height, - Time: commonHeader.Time, - TotalVotingPower: commonVals.TotalVotingPower(), - } - expectedAbciEv[idx] = ev - } + // if we submit evidence only against a single byzantine validator when we see there are more validators then this + // should return an error + ev.ByzantineValidators = []*types.Validator{commonVals.Validators[0]} + err = pool.CheckEvidence(evList) + assert.Error(t, err) + ev.ByzantineValidators = commonVals.Validators // restore evidence + + // If evidence is submitted with an altered timestamp it should return an error + ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute) + err = pool.CheckEvidence(evList) + assert.Error(t, err) - assert.Equal(t, expectedAbciEv, abciEv) } func TestVerifyLightClientAttack_Equivocation(t *testing.T) { @@ -155,7 +155,10 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { }, ValidatorSet: conflictingVals, }, - CommonHeight: 10, + CommonHeight: 10, + ByzantineValidators: conflictingVals.Validators[:4], + TotalVotingPower: 50, + Timestamp: defaultEvidenceTime, } trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) @@ -168,12 +171,12 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { } // good pass -> no error - err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil, + err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) assert.NoError(t, err) // trusted and conflicting hashes are the same -> an error should be returned - err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, nil, + err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) assert.Error(t, err) @@ -208,31 +211,6 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) - - pubKey, err := conflictingPrivVals[0].GetPubKey() - require.NoError(t, err) - lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address()) - block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev}) - - abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence) - expectedAbciEv := make([]abci.Evidence, len(conflictingVals.Validators)-1) - - // we expect evidence to be made for all validators except the last one - for idx, val := range conflictingVals.Validators { - if idx == 4 { // skip the last validator - continue - } - ev := abci.Evidence{ - Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, - Validator: types.TM2PB.Validator(val), - Height: ev.ConflictingBlock.Height, - Time: ev.ConflictingBlock.Time, - TotalVotingPower: ev.ConflictingBlock.ValidatorSet.TotalVotingPower(), - } - expectedAbciEv[idx] = ev - } - - assert.Equal(t, expectedAbciEv, abciEv) } func TestVerifyLightClientAttack_Amnesia(t *testing.T) { @@ -261,7 +239,10 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { }, ValidatorSet: conflictingVals, }, - CommonHeight: 10, + CommonHeight: 10, + ByzantineValidators: nil, // with amnesia evidence no validators are submitted as abci evidence + TotalVotingPower: 50, + Timestamp: defaultEvidenceTime, } trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash")) @@ -274,12 +255,12 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { } // good pass -> no error - err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil, + err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) assert.NoError(t, err) // trusted and conflicting hashes are the same -> an error should be returned - err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, nil, + err = evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour) assert.Error(t, err) @@ -305,19 +286,6 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) assert.Equal(t, 1, len(pendingEvs)) - - pubKey, err := conflictingPrivVals[0].GetPubKey() - require.NoError(t, err) - lastCommit := makeCommit(state.LastBlockHeight, pubKey.Address()) - block := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{ev}) - - abciEv := pool.ABCIEvidence(block.Height, block.Evidence.Evidence) - // as we are unable to find out which subset of validators in the commit were malicious, no information - // is sent to the application. We expect the array to be empty - emptyEvidenceBlock := types.MakeBlock(state.LastBlockHeight, []types.Tx{}, lastCommit, []types.Evidence{}) - expectedAbciEv := pool.ABCIEvidence(emptyEvidenceBlock.Height, emptyEvidenceBlock.Evidence.Evidence) - - assert.Equal(t, expectedAbciEv, abciEv) } type voteData struct { @@ -368,8 +336,11 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { require.NoError(t, err) for _, c := range cases { ev := &types.DuplicateVoteEvidence{ - VoteA: c.vote1, - VoteB: c.vote2, + VoteA: c.vote1, + VoteB: c.vote2, + ValidatorPower: 1, + TotalVotingPower: 1, + Timestamp: defaultEvidenceTime, } if c.valid { assert.Nil(t, evidence.VerifyDuplicateVote(ev, chainID, valSet), "evidence should be valid") @@ -378,7 +349,14 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { } } + // create good evidence and correct validator power goodEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + goodEv.ValidatorPower = 1 + goodEv.TotalVotingPower = 1 + badEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + badTimeEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID) + badTimeEv.ValidatorPower = 1 + badTimeEv.TotalVotingPower = 1 state := sm.State{ ChainID: chainID, LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute), @@ -397,6 +375,16 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { evList := types.EvidenceList{goodEv} err = pool.CheckEvidence(evList) assert.NoError(t, err) + + // evidence with a different validator power should fail + evList = types.EvidenceList{badEv} + err = pool.CheckEvidence(evList) + assert.Error(t, err) + + // evidence with a different timestamp should fail + evList = types.EvidenceList{badTimeEv} + err = pool.CheckEvidence(evList) + assert.Error(t, err) } func makeVote( diff --git a/light/detector.go b/light/detector.go index 43f7a3f231..612f186d35 100644 --- a/light/detector.go +++ b/light/detector.go @@ -78,24 +78,13 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) continue } - // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we - // return the height of the conflicting block else if it is a lunatic attack and the validator sets - // are not the same then we send the height of the common header. - commonHeight := primaryBlock.Height - if isInvalidHeader(witnessTrace[len(witnessTrace)-1].Header, primaryBlock.Header) { - // height of the common header - commonHeight = witnessTrace[0].Height - } // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth // and generate evidence against the primary that we can send to the witness - ev := &types.LightClientAttackEvidence{ - ConflictingBlock: primaryBlock, - CommonHeight: commonHeight, // the first block in the bisection is common to both providers - } - c.logger.Error("Attack detected. Sending evidence againt primary by witness", "ev", ev, + primaryEv := newLightClientAttackEvidence(primaryBlock, witnessTrace[len(witnessTrace)-1], witnessTrace[0]) + c.logger.Error("Attempted attack detected. Sending evidence againt primary by witness", "ev", primaryEv, "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, ev, supportingWitness) + c.sendEvidence(ctx, primaryEv, supportingWitness) // This may not be valid because the witness itself is at fault. So now we reverse it, examining the // trace provided by the witness and holding the primary as the source of truth. Note: primary may not @@ -111,23 +100,12 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) continue } - // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we - // return the height of the conflicting block else if it is a lunatic attack and the validator sets - // are not the same then we send the height of the common header. - commonHeight = primaryBlock.Height - if isInvalidHeader(primaryTrace[len(primaryTrace)-1].Header, witnessBlock.Header) { - // height of the common header - commonHeight = primaryTrace[0].Height - } // We now use the primary trace to create evidence against the witness and send it to the primary - ev = &types.LightClientAttackEvidence{ - ConflictingBlock: witnessBlock, - CommonHeight: commonHeight, // the first block in the bisection is common to both providers - } - c.logger.Error("Sending evidence against witness by primary", "ev", ev, + witnessEv := newLightClientAttackEvidence(witnessBlock, primaryTrace[len(primaryTrace)-1], primaryTrace[0]) + c.logger.Error("Sending evidence against witness by primary", "ev", witnessEv, "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, ev, c.primary) + c.sendEvidence(ctx, witnessEv, c.primary) // We return the error and don't process anymore witnesses return e @@ -245,14 +223,22 @@ func (c *Client) examineConflictingHeaderAgainstTrace( } -// isInvalidHeader takes a trusted header and matches it againt a conflicting header -// to determine whether the conflicting header was the product of a valid state transition -// or not. If it is then all the deterministic fields of the header should be the same. -// If not, it is an invalid header and constitutes a lunatic attack. -func isInvalidHeader(trusted, conflicting *types.Header) bool { - return !bytes.Equal(trusted.ValidatorsHash, conflicting.ValidatorsHash) || - !bytes.Equal(trusted.NextValidatorsHash, conflicting.NextValidatorsHash) || - !bytes.Equal(trusted.ConsensusHash, conflicting.ConsensusHash) || - !bytes.Equal(trusted.AppHash, conflicting.AppHash) || - !bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash) +// newLightClientAttackEvidence determines the type of attack and then forms the evidence filling out +// all the fields such that it is ready to be sent to a full node. +func newLightClientAttackEvidence(conflicted, trusted, common *types.LightBlock) *types.LightClientAttackEvidence { + ev := &types.LightClientAttackEvidence{ConflictingBlock: conflicted} + // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we + // return the height of the conflicting block else if it is a lunatic attack and the validator sets + // are not the same then we send the height of the common header. + if ev.ConflictingHeaderIsInvalid(trusted.Header) { + ev.CommonHeight = common.Height + ev.Timestamp = common.Time + ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower() + } else { + ev.CommonHeight = trusted.Height + ev.Timestamp = trusted.Time + ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() + } + ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader) + return ev } diff --git a/node/node_test.go b/node/node_test.go index f25e6243d9..e94da4da56 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -265,10 +265,15 @@ func TestCreateProposalBlock(t *testing.T) { for currentBytes <= maxEvidenceBytes { ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") currentBytes += int64(len(ev.Bytes())) - err := evidencePool.AddEvidenceFromConsensus(ev, time.Now(), state.Validators) + err := evidencePool.AddEvidenceFromConsensus(ev) require.NoError(t, err) } + evList, size := evidencePool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) + require.Less(t, size, state.ConsensusParams.Evidence.MaxBytes+1) + evData := &types.EvidenceData{Evidence: evList} + require.EqualValues(t, size, evData.ByteSize()) + // fill the mempool with more txs // than can fit in a block txLength := 100 diff --git a/proto/tendermint/evidence/types.pb.go b/proto/tendermint/evidence/types.pb.go deleted file mode 100644 index 396b2ee4a4..0000000000 --- a/proto/tendermint/evidence/types.pb.go +++ /dev/null @@ -1,668 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: tendermint/evidence/types.proto - -package evidence - -import ( - fmt "fmt" - _ "github.com/gogo/protobuf/gogoproto" - proto "github.com/gogo/protobuf/proto" - _ "github.com/gogo/protobuf/types" - github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" - types "github.com/tendermint/tendermint/proto/tendermint/types" - io "io" - math "math" - math_bits "math/bits" - time "time" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf -var _ = time.Kitchen - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -type List struct { - Evidence []*types.Evidence `protobuf:"bytes,1,rep,name=evidence,proto3" json:"evidence,omitempty"` -} - -func (m *List) Reset() { *m = List{} } -func (m *List) String() string { return proto.CompactTextString(m) } -func (*List) ProtoMessage() {} -func (*List) Descriptor() ([]byte, []int) { - return fileDescriptor_5e804d1c041a0e47, []int{0} -} -func (m *List) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *List) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_List.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *List) XXX_Merge(src proto.Message) { - xxx_messageInfo_List.Merge(m, src) -} -func (m *List) XXX_Size() int { - return m.Size() -} -func (m *List) XXX_DiscardUnknown() { - xxx_messageInfo_List.DiscardUnknown(m) -} - -var xxx_messageInfo_List proto.InternalMessageInfo - -func (m *List) GetEvidence() []*types.Evidence { - if m != nil { - return m.Evidence - } - return nil -} - -type Info struct { - Evidence types.Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence"` - Time time.Time `protobuf:"bytes,2,opt,name=time,proto3,stdtime" json:"time"` - Validators []*types.Validator `protobuf:"bytes,3,rep,name=validators,proto3" json:"validators,omitempty"` - TotalVotingPower int64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` -} - -func (m *Info) Reset() { *m = Info{} } -func (m *Info) String() string { return proto.CompactTextString(m) } -func (*Info) ProtoMessage() {} -func (*Info) Descriptor() ([]byte, []int) { - return fileDescriptor_5e804d1c041a0e47, []int{1} -} -func (m *Info) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Info) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Info.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Info) XXX_Merge(src proto.Message) { - xxx_messageInfo_Info.Merge(m, src) -} -func (m *Info) XXX_Size() int { - return m.Size() -} -func (m *Info) XXX_DiscardUnknown() { - xxx_messageInfo_Info.DiscardUnknown(m) -} - -var xxx_messageInfo_Info proto.InternalMessageInfo - -func (m *Info) GetEvidence() types.Evidence { - if m != nil { - return m.Evidence - } - return types.Evidence{} -} - -func (m *Info) GetTime() time.Time { - if m != nil { - return m.Time - } - return time.Time{} -} - -func (m *Info) GetValidators() []*types.Validator { - if m != nil { - return m.Validators - } - return nil -} - -func (m *Info) GetTotalVotingPower() int64 { - if m != nil { - return m.TotalVotingPower - } - return 0 -} - -func init() { - proto.RegisterType((*List)(nil), "tendermint.evidence.List") - proto.RegisterType((*Info)(nil), "tendermint.evidence.Info") -} - -func init() { proto.RegisterFile("tendermint/evidence/types.proto", fileDescriptor_5e804d1c041a0e47) } - -var fileDescriptor_5e804d1c041a0e47 = []byte{ - // 329 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2f, 0x49, 0xcd, 0x4b, - 0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x4f, 0x2d, 0xcb, 0x4c, 0x49, 0xcd, 0x4b, 0x4e, 0xd5, - 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x46, 0x28, 0xd0, - 0x83, 0x29, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, 0x58, 0x10, 0xa5, 0x52, - 0xf2, 0xe9, 0xf9, 0xf9, 0xe9, 0x39, 0xa9, 0xfa, 0x60, 0x5e, 0x52, 0x69, 0x9a, 0x7e, 0x49, 0x66, - 0x6e, 0x6a, 0x71, 0x49, 0x62, 0x6e, 0x01, 0x54, 0x81, 0x02, 0x92, 0x65, 0x60, 0x3b, 0xf4, 0xcb, - 0x12, 0x73, 0x32, 0x53, 0x12, 0x4b, 0xf2, 0x8b, 0x60, 0x46, 0x60, 0xa8, 0x80, 0xd9, 0x09, 0x51, - 0xa0, 0x64, 0xc7, 0xc5, 0xe2, 0x93, 0x59, 0x5c, 0x22, 0x64, 0xc6, 0xc5, 0x01, 0x93, 0x91, 0x60, - 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x92, 0xd2, 0x43, 0x72, 0x29, 0xc4, 0x07, 0xae, 0x50, 0x15, 0x41, - 0x70, 0xb5, 0x4a, 0x2f, 0x19, 0xb9, 0x58, 0x3c, 0xf3, 0xd2, 0xf2, 0x85, 0x6c, 0x50, 0x0c, 0x60, - 0xc4, 0x6f, 0x80, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x08, 0x63, 0x84, 0x2c, 0xb8, 0x58, 0x40, - 0x9e, 0x93, 0x60, 0x82, 0xea, 0x84, 0xf8, 0x5c, 0x0f, 0xe6, 0x73, 0xbd, 0x10, 0x98, 0xcf, 0x9d, - 0x38, 0x40, 0x3a, 0x27, 0xdc, 0x97, 0x67, 0x0c, 0x02, 0xeb, 0x10, 0xb2, 0xe6, 0xe2, 0x82, 0x7b, - 0xba, 0x58, 0x82, 0x19, 0xec, 0x74, 0x69, 0x4c, 0x9b, 0xc3, 0x60, 0x6a, 0x82, 0x90, 0x94, 0x0b, - 0xe9, 0x70, 0x09, 0x95, 0xe4, 0x97, 0x24, 0xe6, 0xc4, 0x97, 0xe5, 0x97, 0x64, 0xe6, 0xa5, 0xc7, - 0x17, 0xe4, 0x97, 0xa7, 0x16, 0x49, 0xb0, 0x28, 0x30, 0x6a, 0x30, 0x07, 0x09, 0x80, 0x65, 0xc2, - 0xc0, 0x12, 0x01, 0x20, 0x71, 0xa7, 0x90, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, - 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, - 0x88, 0xb2, 0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x47, 0x0e, 0x71, - 0x04, 0x13, 0x12, 0xb9, 0x58, 0x12, 0x47, 0x12, 0x1b, 0x58, 0xca, 0x18, 0x10, 0x00, 0x00, 0xff, - 0xff, 0x89, 0xbc, 0x3a, 0x32, 0x3a, 0x02, 0x00, 0x00, -} - -func (m *List) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *List) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *List) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Evidence) > 0 { - for iNdEx := len(m.Evidence) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Evidence[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *Info) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Info) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Info) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.TotalVotingPower != 0 { - i = encodeVarintTypes(dAtA, i, uint64(m.TotalVotingPower)) - i-- - dAtA[i] = 0x20 - } - if len(m.Validators) > 0 { - for iNdEx := len(m.Validators) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Validators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } - n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err1 != nil { - return 0, err1 - } - i -= n1 - i = encodeVarintTypes(dAtA, i, uint64(n1)) - i-- - dAtA[i] = 0x12 - { - size, err := m.Evidence.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func encodeVarintTypes(dAtA []byte, offset int, v uint64) int { - offset -= sovTypes(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *List) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Evidence) > 0 { - for _, e := range m.Evidence { - l = e.Size() - n += 1 + l + sovTypes(uint64(l)) - } - } - return n -} - -func (m *Info) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Evidence.Size() - n += 1 + l + sovTypes(uint64(l)) - l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time) - n += 1 + l + sovTypes(uint64(l)) - if len(m.Validators) > 0 { - for _, e := range m.Validators { - l = e.Size() - n += 1 + l + sovTypes(uint64(l)) - } - } - if m.TotalVotingPower != 0 { - n += 1 + sovTypes(uint64(m.TotalVotingPower)) - } - return n -} - -func sovTypes(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozTypes(x uint64) (n int) { - return sovTypes(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *List) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: List: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: List: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Evidence = append(m.Evidence, &types.Evidence{}) - if err := m.Evidence[len(m.Evidence)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Info) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Info: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Info: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Evidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Validators", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Validators = append(m.Validators, &types.Validator{}) - if err := m.Validators[len(m.Validators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TotalVotingPower", wireType) - } - m.TotalVotingPower = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.TotalVotingPower |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipTypes(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTypes - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTypes - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTypes - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthTypes - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupTypes - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthTypes - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupTypes = fmt.Errorf("proto: unexpected end of group") -) diff --git a/proto/tendermint/evidence/types.proto b/proto/tendermint/evidence/types.proto deleted file mode 100644 index 64148beec1..0000000000 --- a/proto/tendermint/evidence/types.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; -package tendermint.evidence; - -option go_package = "github.com/tendermint/tendermint/proto/tendermint/evidence"; - -import "gogoproto/gogo.proto"; -import "google/protobuf/timestamp.proto"; -import "tendermint/types/validator.proto"; -import "tendermint/types/evidence.proto"; - -message List { - repeated tendermint.types.Evidence evidence = 1; -} - -message Info { - tendermint.types.Evidence evidence = 1 [(gogoproto.nullable) = false]; - google.protobuf.Timestamp time = 2 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - repeated tendermint.types.Validator validators = 3; - int64 total_voting_power = 4; -} diff --git a/proto/tendermint/types/block.pb.go b/proto/tendermint/types/block.pb.go index 206da91aa3..aacb90fab7 100644 --- a/proto/tendermint/types/block.pb.go +++ b/proto/tendermint/types/block.pb.go @@ -26,7 +26,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Block struct { Header Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header"` Data Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data"` - Evidence EvidenceData `protobuf:"bytes,3,opt,name=evidence,proto3" json:"evidence"` + Evidence EvidenceList `protobuf:"bytes,3,opt,name=evidence,proto3" json:"evidence"` LastCommit *Commit `protobuf:"bytes,4,opt,name=last_commit,json=lastCommit,proto3" json:"last_commit,omitempty"` } @@ -77,11 +77,11 @@ func (m *Block) GetData() Data { return Data{} } -func (m *Block) GetEvidence() EvidenceData { +func (m *Block) GetEvidence() EvidenceList { if m != nil { return m.Evidence } - return EvidenceData{} + return EvidenceList{} } func (m *Block) GetLastCommit() *Commit { @@ -98,7 +98,7 @@ func init() { func init() { proto.RegisterFile("tendermint/types/block.proto", fileDescriptor_70840e82f4357ab1) } var fileDescriptor_70840e82f4357ab1 = []byte{ - // 262 bytes of a gzipped FileDescriptorProto + // 266 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x29, 0x49, 0xcd, 0x4b, 0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x4f, 0xca, 0xc9, 0x4f, 0xce, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x40, 0xc8, 0xea, 0x81, 0x65, 0xa5, @@ -109,13 +109,13 @@ var fileDescriptor_70840e82f4357ab1 = []byte{ 0xf2, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0x55, 0x0b, 0x19, 0x70, 0xb1, 0xa4, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x81, 0x75, 0x89, 0x61, 0xea, 0x72, 0x49, 0x2c, 0x49, 0x84, 0xea, 0x01, 0xab, 0x14, 0x72, 0xe0, 0xe2, 0x80, 0xb9, 0x42, 0x82, 0x19, 0xac, 0x4b, 0x0e, 0x53, 0x97, 0x2b, - 0x54, 0x05, 0x92, 0x6e, 0xb8, 0x2e, 0x21, 0x4b, 0x2e, 0xee, 0x9c, 0xc4, 0xe2, 0x92, 0xf8, 0xe4, - 0xfc, 0xdc, 0xdc, 0xcc, 0x12, 0x09, 0x16, 0x5c, 0x0e, 0x76, 0x06, 0xcb, 0x07, 0x71, 0x81, 0x14, - 0x43, 0xd8, 0x4e, 0x81, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, - 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x9e, - 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0x1c, 0x6c, 0x08, 0x26, 0x24, - 0xf0, 0xd1, 0x83, 0x34, 0x89, 0x0d, 0x2c, 0x6e, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x7a, 0x4b, - 0x9b, 0x9a, 0xd1, 0x01, 0x00, 0x00, + 0x54, 0x85, 0x4f, 0x66, 0x71, 0x09, 0x54, 0x37, 0x5c, 0x97, 0x90, 0x25, 0x17, 0x77, 0x4e, 0x62, + 0x71, 0x49, 0x7c, 0x72, 0x7e, 0x6e, 0x6e, 0x66, 0x89, 0x04, 0x0b, 0x2e, 0x07, 0x3b, 0x83, 0xe5, + 0x83, 0xb8, 0x40, 0x8a, 0x21, 0x6c, 0xa7, 0xc0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, + 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, + 0x63, 0x88, 0x32, 0x4f, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x47, 0x0e, + 0x36, 0x04, 0x13, 0x12, 0xf8, 0xe8, 0x41, 0x9a, 0xc4, 0x06, 0x16, 0x37, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x79, 0x8c, 0xb5, 0x43, 0xd1, 0x01, 0x00, 0x00, } func (m *Block) Marshal() (dAtA []byte, err error) { diff --git a/proto/tendermint/types/block.proto b/proto/tendermint/types/block.proto index 18984c06a7..84e9bb15d8 100644 --- a/proto/tendermint/types/block.proto +++ b/proto/tendermint/types/block.proto @@ -10,6 +10,6 @@ import "tendermint/types/evidence.proto"; message Block { Header header = 1 [(gogoproto.nullable) = false]; Data data = 2 [(gogoproto.nullable) = false]; - tendermint.types.EvidenceData evidence = 3 [(gogoproto.nullable) = false]; + tendermint.types.EvidenceList evidence = 3 [(gogoproto.nullable) = false]; Commit last_commit = 4; } diff --git a/proto/tendermint/types/evidence.pb.go b/proto/tendermint/types/evidence.pb.go index c1ed2f09f0..3d9e8f2c5d 100644 --- a/proto/tendermint/types/evidence.pb.go +++ b/proto/tendermint/types/evidence.pb.go @@ -7,15 +7,19 @@ import ( fmt "fmt" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" + _ "github.com/gogo/protobuf/types" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" io "io" math "math" math_bits "math/bits" + time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. @@ -23,18 +27,105 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// DuplicateVoteEvidence contains evidence a validator signed two conflicting -// votes. +type Evidence struct { + // Types that are valid to be assigned to Sum: + // *Evidence_DuplicateVoteEvidence + // *Evidence_LightClientAttackEvidence + Sum isEvidence_Sum `protobuf_oneof:"sum"` +} + +func (m *Evidence) Reset() { *m = Evidence{} } +func (m *Evidence) String() string { return proto.CompactTextString(m) } +func (*Evidence) ProtoMessage() {} +func (*Evidence) Descriptor() ([]byte, []int) { + return fileDescriptor_6825fabc78e0a168, []int{0} +} +func (m *Evidence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Evidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Evidence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Evidence) XXX_Merge(src proto.Message) { + xxx_messageInfo_Evidence.Merge(m, src) +} +func (m *Evidence) XXX_Size() int { + return m.Size() +} +func (m *Evidence) XXX_DiscardUnknown() { + xxx_messageInfo_Evidence.DiscardUnknown(m) +} + +var xxx_messageInfo_Evidence proto.InternalMessageInfo + +type isEvidence_Sum interface { + isEvidence_Sum() + MarshalTo([]byte) (int, error) + Size() int +} + +type Evidence_DuplicateVoteEvidence struct { + DuplicateVoteEvidence *DuplicateVoteEvidence `protobuf:"bytes,1,opt,name=duplicate_vote_evidence,json=duplicateVoteEvidence,proto3,oneof" json:"duplicate_vote_evidence,omitempty"` +} +type Evidence_LightClientAttackEvidence struct { + LightClientAttackEvidence *LightClientAttackEvidence `protobuf:"bytes,2,opt,name=light_client_attack_evidence,json=lightClientAttackEvidence,proto3,oneof" json:"light_client_attack_evidence,omitempty"` +} + +func (*Evidence_DuplicateVoteEvidence) isEvidence_Sum() {} +func (*Evidence_LightClientAttackEvidence) isEvidence_Sum() {} + +func (m *Evidence) GetSum() isEvidence_Sum { + if m != nil { + return m.Sum + } + return nil +} + +func (m *Evidence) GetDuplicateVoteEvidence() *DuplicateVoteEvidence { + if x, ok := m.GetSum().(*Evidence_DuplicateVoteEvidence); ok { + return x.DuplicateVoteEvidence + } + return nil +} + +func (m *Evidence) GetLightClientAttackEvidence() *LightClientAttackEvidence { + if x, ok := m.GetSum().(*Evidence_LightClientAttackEvidence); ok { + return x.LightClientAttackEvidence + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*Evidence) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*Evidence_DuplicateVoteEvidence)(nil), + (*Evidence_LightClientAttackEvidence)(nil), + } +} + +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. type DuplicateVoteEvidence struct { - VoteA *Vote `protobuf:"bytes,1,opt,name=vote_a,json=voteA,proto3" json:"vote_a,omitempty"` - VoteB *Vote `protobuf:"bytes,2,opt,name=vote_b,json=voteB,proto3" json:"vote_b,omitempty"` + VoteA *Vote `protobuf:"bytes,1,opt,name=vote_a,json=voteA,proto3" json:"vote_a,omitempty"` + VoteB *Vote `protobuf:"bytes,2,opt,name=vote_b,json=voteB,proto3" json:"vote_b,omitempty"` + TotalVotingPower int64 `protobuf:"varint,3,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` + ValidatorPower int64 `protobuf:"varint,4,opt,name=validator_power,json=validatorPower,proto3" json:"validator_power,omitempty"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` } func (m *DuplicateVoteEvidence) Reset() { *m = DuplicateVoteEvidence{} } func (m *DuplicateVoteEvidence) String() string { return proto.CompactTextString(m) } func (*DuplicateVoteEvidence) ProtoMessage() {} func (*DuplicateVoteEvidence) Descriptor() ([]byte, []int) { - return fileDescriptor_6825fabc78e0a168, []int{0} + return fileDescriptor_6825fabc78e0a168, []int{1} } func (m *DuplicateVoteEvidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -77,16 +168,41 @@ func (m *DuplicateVoteEvidence) GetVoteB() *Vote { return nil } +func (m *DuplicateVoteEvidence) GetTotalVotingPower() int64 { + if m != nil { + return m.TotalVotingPower + } + return 0 +} + +func (m *DuplicateVoteEvidence) GetValidatorPower() int64 { + if m != nil { + return m.ValidatorPower + } + return 0 +} + +func (m *DuplicateVoteEvidence) GetTimestamp() time.Time { + if m != nil { + return m.Timestamp + } + return time.Time{} +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. type LightClientAttackEvidence struct { - ConflictingBlock *LightBlock `protobuf:"bytes,1,opt,name=conflicting_block,json=conflictingBlock,proto3" json:"conflicting_block,omitempty"` - CommonHeight int64 `protobuf:"varint,2,opt,name=common_height,json=commonHeight,proto3" json:"common_height,omitempty"` + ConflictingBlock *LightBlock `protobuf:"bytes,1,opt,name=conflicting_block,json=conflictingBlock,proto3" json:"conflicting_block,omitempty"` + CommonHeight int64 `protobuf:"varint,2,opt,name=common_height,json=commonHeight,proto3" json:"common_height,omitempty"` + ByzantineValidators []*Validator `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators,omitempty"` + TotalVotingPower int64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` } func (m *LightClientAttackEvidence) Reset() { *m = LightClientAttackEvidence{} } func (m *LightClientAttackEvidence) String() string { return proto.CompactTextString(m) } func (*LightClientAttackEvidence) ProtoMessage() {} func (*LightClientAttackEvidence) Descriptor() ([]byte, []int) { - return fileDescriptor_6825fabc78e0a168, []int{1} + return fileDescriptor_6825fabc78e0a168, []int{2} } func (m *LightClientAttackEvidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -129,108 +245,43 @@ func (m *LightClientAttackEvidence) GetCommonHeight() int64 { return 0 } -type Evidence struct { - // Types that are valid to be assigned to Sum: - // *Evidence_DuplicateVoteEvidence - // *Evidence_LightClientAttackEvidence - Sum isEvidence_Sum `protobuf_oneof:"sum"` -} - -func (m *Evidence) Reset() { *m = Evidence{} } -func (m *Evidence) String() string { return proto.CompactTextString(m) } -func (*Evidence) ProtoMessage() {} -func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_6825fabc78e0a168, []int{2} -} -func (m *Evidence) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Evidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Evidence.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Evidence) XXX_Merge(src proto.Message) { - xxx_messageInfo_Evidence.Merge(m, src) -} -func (m *Evidence) XXX_Size() int { - return m.Size() -} -func (m *Evidence) XXX_DiscardUnknown() { - xxx_messageInfo_Evidence.DiscardUnknown(m) -} - -var xxx_messageInfo_Evidence proto.InternalMessageInfo - -type isEvidence_Sum interface { - isEvidence_Sum() - MarshalTo([]byte) (int, error) - Size() int -} - -type Evidence_DuplicateVoteEvidence struct { - DuplicateVoteEvidence *DuplicateVoteEvidence `protobuf:"bytes,1,opt,name=duplicate_vote_evidence,json=duplicateVoteEvidence,proto3,oneof" json:"duplicate_vote_evidence,omitempty"` -} -type Evidence_LightClientAttackEvidence struct { - LightClientAttackEvidence *LightClientAttackEvidence `protobuf:"bytes,2,opt,name=light_client_attack_evidence,json=lightClientAttackEvidence,proto3,oneof" json:"light_client_attack_evidence,omitempty"` -} - -func (*Evidence_DuplicateVoteEvidence) isEvidence_Sum() {} -func (*Evidence_LightClientAttackEvidence) isEvidence_Sum() {} - -func (m *Evidence) GetSum() isEvidence_Sum { +func (m *LightClientAttackEvidence) GetByzantineValidators() []*Validator { if m != nil { - return m.Sum - } - return nil -} - -func (m *Evidence) GetDuplicateVoteEvidence() *DuplicateVoteEvidence { - if x, ok := m.GetSum().(*Evidence_DuplicateVoteEvidence); ok { - return x.DuplicateVoteEvidence + return m.ByzantineValidators } return nil } -func (m *Evidence) GetLightClientAttackEvidence() *LightClientAttackEvidence { - if x, ok := m.GetSum().(*Evidence_LightClientAttackEvidence); ok { - return x.LightClientAttackEvidence +func (m *LightClientAttackEvidence) GetTotalVotingPower() int64 { + if m != nil { + return m.TotalVotingPower } - return nil + return 0 } -// XXX_OneofWrappers is for the internal use of the proto package. -func (*Evidence) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*Evidence_DuplicateVoteEvidence)(nil), - (*Evidence_LightClientAttackEvidence)(nil), +func (m *LightClientAttackEvidence) GetTimestamp() time.Time { + if m != nil { + return m.Timestamp } + return time.Time{} } -// EvidenceData contains any evidence of malicious wrong-doing by validators -type EvidenceData struct { +type EvidenceList struct { Evidence []Evidence `protobuf:"bytes,1,rep,name=evidence,proto3" json:"evidence"` } -func (m *EvidenceData) Reset() { *m = EvidenceData{} } -func (m *EvidenceData) String() string { return proto.CompactTextString(m) } -func (*EvidenceData) ProtoMessage() {} -func (*EvidenceData) Descriptor() ([]byte, []int) { +func (m *EvidenceList) Reset() { *m = EvidenceList{} } +func (m *EvidenceList) String() string { return proto.CompactTextString(m) } +func (*EvidenceList) ProtoMessage() {} +func (*EvidenceList) Descriptor() ([]byte, []int) { return fileDescriptor_6825fabc78e0a168, []int{3} } -func (m *EvidenceData) XXX_Unmarshal(b []byte) error { +func (m *EvidenceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *EvidenceData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *EvidenceList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_EvidenceData.Marshal(b, m, deterministic) + return xxx_messageInfo_EvidenceList.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -240,19 +291,19 @@ func (m *EvidenceData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return b[:n], nil } } -func (m *EvidenceData) XXX_Merge(src proto.Message) { - xxx_messageInfo_EvidenceData.Merge(m, src) +func (m *EvidenceList) XXX_Merge(src proto.Message) { + xxx_messageInfo_EvidenceList.Merge(m, src) } -func (m *EvidenceData) XXX_Size() int { +func (m *EvidenceList) XXX_Size() int { return m.Size() } -func (m *EvidenceData) XXX_DiscardUnknown() { - xxx_messageInfo_EvidenceData.DiscardUnknown(m) +func (m *EvidenceList) XXX_DiscardUnknown() { + xxx_messageInfo_EvidenceList.DiscardUnknown(m) } -var xxx_messageInfo_EvidenceData proto.InternalMessageInfo +var xxx_messageInfo_EvidenceList proto.InternalMessageInfo -func (m *EvidenceData) GetEvidence() []Evidence { +func (m *EvidenceList) GetEvidence() []Evidence { if m != nil { return m.Evidence } @@ -260,44 +311,53 @@ func (m *EvidenceData) GetEvidence() []Evidence { } func init() { + proto.RegisterType((*Evidence)(nil), "tendermint.types.Evidence") proto.RegisterType((*DuplicateVoteEvidence)(nil), "tendermint.types.DuplicateVoteEvidence") proto.RegisterType((*LightClientAttackEvidence)(nil), "tendermint.types.LightClientAttackEvidence") - proto.RegisterType((*Evidence)(nil), "tendermint.types.Evidence") - proto.RegisterType((*EvidenceData)(nil), "tendermint.types.EvidenceData") + proto.RegisterType((*EvidenceList)(nil), "tendermint.types.EvidenceList") } func init() { proto.RegisterFile("tendermint/types/evidence.proto", fileDescriptor_6825fabc78e0a168) } var fileDescriptor_6825fabc78e0a168 = []byte{ - // 388 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xc1, 0x6a, 0xea, 0x40, - 0x14, 0x4d, 0x9e, 0x4f, 0x91, 0xd1, 0x07, 0xbe, 0xf0, 0x7c, 0x55, 0x91, 0x58, 0xd2, 0x45, 0x85, - 0xd2, 0x04, 0xec, 0xa2, 0x9b, 0x6e, 0x4c, 0x2d, 0x58, 0x70, 0xd3, 0x2c, 0xba, 0xe8, 0x26, 0x24, - 0x93, 0x69, 0x1c, 0x4c, 0x66, 0x44, 0x6f, 0x84, 0x7e, 0x43, 0x37, 0xfd, 0x2c, 0x97, 0x2e, 0xbb, - 0x2a, 0x45, 0xfb, 0x21, 0x25, 0x13, 0x8d, 0x62, 0x94, 0x6e, 0x86, 0xe1, 0xdc, 0x73, 0xef, 0xb9, - 0xe7, 0x70, 0x51, 0x0b, 0x08, 0xf3, 0xc8, 0x24, 0xa4, 0x0c, 0x0c, 0x78, 0x19, 0x93, 0xa9, 0x41, - 0x66, 0xd4, 0x23, 0x0c, 0x13, 0x7d, 0x3c, 0xe1, 0xc0, 0x95, 0xca, 0x96, 0xa0, 0x0b, 0x42, 0xe3, - 0x9f, 0xcf, 0x7d, 0x2e, 0x8a, 0x46, 0xfc, 0x4b, 0x78, 0x8d, 0x66, 0x66, 0x90, 0x78, 0x93, 0xaa, - 0x16, 0xa1, 0x6a, 0x2f, 0x1a, 0x07, 0x14, 0x3b, 0x40, 0x1e, 0x39, 0x90, 0xbb, 0xb5, 0x88, 0x72, - 0x89, 0x0a, 0x33, 0x0e, 0xc4, 0x76, 0x6a, 0xf2, 0xa9, 0xdc, 0x2e, 0x75, 0xfe, 0xeb, 0xfb, 0x7a, - 0x7a, 0xcc, 0xb7, 0xf2, 0x31, 0xab, 0x9b, 0xd2, 0xdd, 0xda, 0xaf, 0x9f, 0xe9, 0xa6, 0xf6, 0x2a, - 0xa3, 0xfa, 0x80, 0xfa, 0x43, 0xb8, 0x0d, 0x28, 0x61, 0xd0, 0x05, 0x70, 0xf0, 0x28, 0xd5, 0xbe, - 0x47, 0x7f, 0x31, 0x67, 0xcf, 0x01, 0xc5, 0x40, 0x99, 0x6f, 0xbb, 0x01, 0xc7, 0xa3, 0xf5, 0x1a, - 0xcd, 0xec, 0x5c, 0x31, 0xc7, 0x8c, 0x39, 0x56, 0x65, 0xa7, 0x4d, 0x20, 0xca, 0x19, 0xfa, 0x83, - 0x79, 0x18, 0x72, 0x66, 0x0f, 0x49, 0xcc, 0x13, 0xeb, 0xe5, 0xac, 0x72, 0x02, 0xf6, 0x05, 0xa6, - 0x7d, 0xc9, 0xa8, 0x98, 0x8a, 0x3b, 0xe8, 0xc4, 0xdb, 0x24, 0x62, 0x0b, 0x4f, 0x9b, 0xe0, 0xd7, - 0x2b, 0x9c, 0x67, 0x57, 0x38, 0x18, 0x61, 0x5f, 0xb2, 0xaa, 0xde, 0xc1, 0x6c, 0x19, 0x6a, 0x06, - 0xb1, 0xb0, 0x8d, 0x85, 0x7b, 0xdb, 0x11, 0xf6, 0xb7, 0x3a, 0x49, 0x84, 0x17, 0x47, 0xac, 0x1e, - 0x8a, 0xac, 0x2f, 0x59, 0xf5, 0xe0, 0x58, 0xd1, 0xcc, 0xa3, 0xdc, 0x34, 0x0a, 0xb5, 0x01, 0x2a, - 0x6f, 0xa0, 0x9e, 0x03, 0x8e, 0x72, 0x83, 0x8a, 0x3b, 0xd6, 0x72, 0xed, 0x52, 0xa7, 0x91, 0x95, - 0x4c, 0x87, 0xfc, 0x9e, 0x7f, 0xb4, 0x24, 0x2b, 0xed, 0x30, 0x1f, 0xe6, 0x4b, 0x55, 0x5e, 0x2c, - 0x55, 0xf9, 0x73, 0xa9, 0xca, 0x6f, 0x2b, 0x55, 0x5a, 0xac, 0x54, 0xe9, 0x7d, 0xa5, 0x4a, 0x4f, - 0xd7, 0x3e, 0x85, 0x61, 0xe4, 0xea, 0x98, 0x87, 0xc6, 0xee, 0xf1, 0x6d, 0xbf, 0xc9, 0x91, 0xee, - 0x1f, 0xa6, 0x5b, 0x10, 0xf8, 0xd5, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x7f, 0xe9, 0x7a, - 0xfc, 0x02, 0x00, 0x00, + // 532 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xed, 0x3a, 0xa9, 0xc2, 0xb6, 0x40, 0x58, 0x5a, 0x48, 0x43, 0xe4, 0x44, 0xe1, 0xd0, + 0x48, 0x80, 0x2d, 0x95, 0x03, 0x17, 0x2e, 0x35, 0x20, 0x15, 0x29, 0x42, 0x60, 0xa1, 0x1e, 0xb8, + 0x58, 0x6b, 0x7b, 0xeb, 0xac, 0x6a, 0xef, 0x5a, 0xf1, 0x24, 0xa8, 0x3c, 0x45, 0x1e, 0xab, 0x17, + 0xa4, 0x1e, 0x39, 0x01, 0x4a, 0x78, 0x10, 0xe4, 0xf5, 0x9f, 0x44, 0x75, 0xcc, 0x89, 0x4b, 0xe4, + 0xcc, 0xfc, 0xbe, 0x9d, 0x99, 0xcf, 0xb3, 0x46, 0x7d, 0xa0, 0xdc, 0xa7, 0xd3, 0x88, 0x71, 0x30, + 0xe1, 0x2a, 0xa6, 0x89, 0x49, 0xe7, 0xcc, 0xa7, 0xdc, 0xa3, 0x46, 0x3c, 0x15, 0x20, 0x70, 0x7b, + 0x0d, 0x18, 0x12, 0xe8, 0x1e, 0x04, 0x22, 0x10, 0x32, 0x69, 0xa6, 0x4f, 0x19, 0xd7, 0xed, 0x07, + 0x42, 0x04, 0x21, 0x35, 0xe5, 0x3f, 0x77, 0x76, 0x61, 0x02, 0x8b, 0x68, 0x02, 0x24, 0x8a, 0x73, + 0xa0, 0x57, 0xa9, 0x24, 0x7f, 0xf3, 0xec, 0xa0, 0x92, 0x9d, 0x93, 0x90, 0xf9, 0x04, 0xc4, 0x34, + 0x23, 0x86, 0x7f, 0x54, 0xd4, 0x7a, 0x97, 0xf7, 0x86, 0x09, 0x7a, 0xec, 0xcf, 0xe2, 0x90, 0x79, + 0x04, 0xa8, 0x33, 0x17, 0x40, 0x9d, 0xa2, 0xed, 0x8e, 0x3a, 0x50, 0x47, 0x7b, 0x27, 0xc7, 0xc6, + 0xed, 0xbe, 0x8d, 0xb7, 0x85, 0xe0, 0x5c, 0x00, 0x2d, 0x4e, 0x3a, 0x53, 0xec, 0x43, 0x7f, 0x5b, + 0x02, 0x73, 0xd4, 0x0b, 0x59, 0x30, 0x01, 0xc7, 0x0b, 0x19, 0xe5, 0xe0, 0x10, 0x00, 0xe2, 0x5d, + 0xae, 0xeb, 0xec, 0xc8, 0x3a, 0xcf, 0xaa, 0x75, 0xc6, 0xa9, 0xea, 0x8d, 0x14, 0x9d, 0x4a, 0xcd, + 0x46, 0xad, 0xa3, 0xb0, 0x2e, 0x69, 0x35, 0x91, 0x96, 0xcc, 0xa2, 0xe1, 0x62, 0x07, 0x1d, 0x6e, + 0xed, 0x14, 0xbf, 0x40, 0xbb, 0x72, 0x52, 0x92, 0x8f, 0xf8, 0xa8, 0x5a, 0x3a, 0xe5, 0xed, 0x66, + 0x4a, 0x9d, 0x96, 0xb8, 0x9b, 0x77, 0xfa, 0x4f, 0xdc, 0xc2, 0xcf, 0x11, 0x06, 0x01, 0x24, 0x4c, + 0xdd, 0x64, 0x3c, 0x70, 0x62, 0xf1, 0x95, 0x4e, 0x3b, 0xda, 0x40, 0x1d, 0x69, 0x76, 0x5b, 0x66, + 0xce, 0x65, 0xe2, 0x63, 0x1a, 0xc7, 0xc7, 0xe8, 0x7e, 0xf9, 0x7e, 0x72, 0xb4, 0x21, 0xd1, 0x7b, + 0x65, 0x38, 0x03, 0x2d, 0x74, 0xa7, 0x5c, 0x84, 0x4e, 0x53, 0x36, 0xd2, 0x35, 0xb2, 0x55, 0x31, + 0x8a, 0x55, 0x31, 0x3e, 0x17, 0x84, 0xd5, 0xba, 0xfe, 0xd9, 0x57, 0x16, 0xbf, 0xfa, 0xaa, 0xbd, + 0x96, 0x0d, 0xbf, 0xef, 0xa0, 0xa3, 0x5a, 0x53, 0xf1, 0x7b, 0xf4, 0xc0, 0x13, 0xfc, 0x22, 0x64, + 0x9e, 0xec, 0xdb, 0x0d, 0x85, 0x77, 0x99, 0x3b, 0xd4, 0xab, 0x79, 0x39, 0x56, 0xca, 0xd8, 0xed, + 0x0d, 0x99, 0x8c, 0xe0, 0xa7, 0xe8, 0xae, 0x27, 0xa2, 0x48, 0x70, 0x67, 0x42, 0x53, 0x4e, 0x3a, + 0xa7, 0xd9, 0xfb, 0x59, 0xf0, 0x4c, 0xc6, 0xf0, 0x07, 0x74, 0xe0, 0x5e, 0x7d, 0x23, 0x1c, 0x18, + 0xa7, 0x4e, 0x39, 0x6d, 0xd2, 0xd1, 0x06, 0xda, 0x68, 0xef, 0xe4, 0xc9, 0x16, 0x97, 0x0b, 0xc6, + 0x7e, 0x58, 0x0a, 0xcb, 0x58, 0x52, 0x63, 0x7c, 0xa3, 0xc6, 0xf8, 0xff, 0xe1, 0xe7, 0x18, 0xed, + 0x17, 0xee, 0x8d, 0x59, 0x02, 0xf8, 0x35, 0x6a, 0x6d, 0xdc, 0x1e, 0x4d, 0x1e, 0x59, 0x99, 0xa2, + 0xdc, 0xd3, 0x46, 0x7a, 0xa4, 0x5d, 0x2a, 0xac, 0x4f, 0xd7, 0x4b, 0x5d, 0xbd, 0x59, 0xea, 0xea, + 0xef, 0xa5, 0xae, 0x2e, 0x56, 0xba, 0x72, 0xb3, 0xd2, 0x95, 0x1f, 0x2b, 0x5d, 0xf9, 0xf2, 0x2a, + 0x60, 0x30, 0x99, 0xb9, 0x86, 0x27, 0x22, 0x73, 0xf3, 0x7a, 0xaf, 0x1f, 0xb3, 0xaf, 0xc8, 0xed, + 0xab, 0xef, 0xee, 0xca, 0xf8, 0xcb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x21, 0x16, 0x68, + 0x9d, 0x04, 0x00, 0x00, } -func (m *DuplicateVoteEvidence) Marshal() (dAtA []byte, err error) { +func (m *Evidence) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -307,19 +367,38 @@ func (m *DuplicateVoteEvidence) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *DuplicateVoteEvidence) MarshalTo(dAtA []byte) (int, error) { +func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.VoteB != nil { + if m.Sum != nil { { - size, err := m.VoteB.MarshalToSizedBuffer(dAtA[:i]) + size := m.Sum.Size() + i -= size + if _, err := m.Sum.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *Evidence_DuplicateVoteEvidence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Evidence_DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.DuplicateVoteEvidence != nil { + { + size, err := m.DuplicateVoteEvidence.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -327,11 +406,20 @@ func (m *DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintEvidence(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0xa } - if m.VoteA != nil { + return len(dAtA) - i, nil +} +func (m *Evidence_LightClientAttackEvidence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Evidence_LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.LightClientAttackEvidence != nil { { - size, err := m.VoteA.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.LightClientAttackEvidence.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -339,12 +427,11 @@ func (m *DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintEvidence(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 } return len(dAtA) - i, nil } - -func (m *LightClientAttackEvidence) Marshal() (dAtA []byte, err error) { +func (m *DuplicateVoteEvidence) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -354,24 +441,49 @@ func (m *LightClientAttackEvidence) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *LightClientAttackEvidence) MarshalTo(dAtA []byte) (int, error) { +func (m *DuplicateVoteEvidence) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.CommonHeight != 0 { - i = encodeVarintEvidence(dAtA, i, uint64(m.CommonHeight)) + n3, err3 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintEvidence(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x2a + if m.ValidatorPower != 0 { + i = encodeVarintEvidence(dAtA, i, uint64(m.ValidatorPower)) i-- - dAtA[i] = 0x10 + dAtA[i] = 0x20 } - if m.ConflictingBlock != nil { + if m.TotalVotingPower != 0 { + i = encodeVarintEvidence(dAtA, i, uint64(m.TotalVotingPower)) + i-- + dAtA[i] = 0x18 + } + if m.VoteB != nil { { - size, err := m.ConflictingBlock.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.VoteB.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvidence(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.VoteA != nil { + { + size, err := m.VoteA.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -384,7 +496,7 @@ func (m *LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *Evidence) Marshal() (dAtA []byte, err error) { +func (m *LightClientAttackEvidence) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -394,59 +506,51 @@ func (m *Evidence) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { +func (m *LightClientAttackEvidence) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Sum != nil { - { - size := m.Sum.Size() - i -= size - if _, err := m.Sum.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - } + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err6 != nil { + return 0, err6 } - return len(dAtA) - i, nil -} - -func (m *Evidence_DuplicateVoteEvidence) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Evidence_DuplicateVoteEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.DuplicateVoteEvidence != nil { - { - size, err := m.DuplicateVoteEvidence.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + i -= n6 + i = encodeVarintEvidence(dAtA, i, uint64(n6)) + i-- + dAtA[i] = 0x2a + if m.TotalVotingPower != 0 { + i = encodeVarintEvidence(dAtA, i, uint64(m.TotalVotingPower)) + i-- + dAtA[i] = 0x20 + } + if len(m.ByzantineValidators) > 0 { + for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvidence(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintEvidence(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a } + } + if m.CommonHeight != 0 { + i = encodeVarintEvidence(dAtA, i, uint64(m.CommonHeight)) i-- - dAtA[i] = 0xa + dAtA[i] = 0x10 } - return len(dAtA) - i, nil -} -func (m *Evidence_LightClientAttackEvidence) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Evidence_LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.LightClientAttackEvidence != nil { + if m.ConflictingBlock != nil { { - size, err := m.LightClientAttackEvidence.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ConflictingBlock.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -454,11 +558,12 @@ func (m *Evidence_LightClientAttackEvidence) MarshalToSizedBuffer(dAtA []byte) ( i = encodeVarintEvidence(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *EvidenceData) Marshal() (dAtA []byte, err error) { + +func (m *EvidenceList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -468,12 +573,12 @@ func (m *EvidenceData) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *EvidenceData) MarshalTo(dAtA []byte) (int, error) { +func (m *EvidenceList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *EvidenceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *EvidenceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -506,76 +611,95 @@ func encodeVarintEvidence(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *DuplicateVoteEvidence) Size() (n int) { +func (m *Evidence) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.VoteA != nil { - l = m.VoteA.Size() - n += 1 + l + sovEvidence(uint64(l)) - } - if m.VoteB != nil { - l = m.VoteB.Size() - n += 1 + l + sovEvidence(uint64(l)) + if m.Sum != nil { + n += m.Sum.Size() } return n } -func (m *LightClientAttackEvidence) Size() (n int) { +func (m *Evidence_DuplicateVoteEvidence) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.ConflictingBlock != nil { - l = m.ConflictingBlock.Size() + if m.DuplicateVoteEvidence != nil { + l = m.DuplicateVoteEvidence.Size() n += 1 + l + sovEvidence(uint64(l)) } - if m.CommonHeight != 0 { - n += 1 + sovEvidence(uint64(m.CommonHeight)) - } return n } - -func (m *Evidence) Size() (n int) { +func (m *Evidence_LightClientAttackEvidence) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.Sum != nil { - n += m.Sum.Size() + if m.LightClientAttackEvidence != nil { + l = m.LightClientAttackEvidence.Size() + n += 1 + l + sovEvidence(uint64(l)) } return n } - -func (m *Evidence_DuplicateVoteEvidence) Size() (n int) { +func (m *DuplicateVoteEvidence) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.DuplicateVoteEvidence != nil { - l = m.DuplicateVoteEvidence.Size() + if m.VoteA != nil { + l = m.VoteA.Size() + n += 1 + l + sovEvidence(uint64(l)) + } + if m.VoteB != nil { + l = m.VoteB.Size() n += 1 + l + sovEvidence(uint64(l)) } + if m.TotalVotingPower != 0 { + n += 1 + sovEvidence(uint64(m.TotalVotingPower)) + } + if m.ValidatorPower != 0 { + n += 1 + sovEvidence(uint64(m.ValidatorPower)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp) + n += 1 + l + sovEvidence(uint64(l)) return n } -func (m *Evidence_LightClientAttackEvidence) Size() (n int) { + +func (m *LightClientAttackEvidence) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.LightClientAttackEvidence != nil { - l = m.LightClientAttackEvidence.Size() + if m.ConflictingBlock != nil { + l = m.ConflictingBlock.Size() n += 1 + l + sovEvidence(uint64(l)) } + if m.CommonHeight != 0 { + n += 1 + sovEvidence(uint64(m.CommonHeight)) + } + if len(m.ByzantineValidators) > 0 { + for _, e := range m.ByzantineValidators { + l = e.Size() + n += 1 + l + sovEvidence(uint64(l)) + } + } + if m.TotalVotingPower != 0 { + n += 1 + sovEvidence(uint64(m.TotalVotingPower)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp) + n += 1 + l + sovEvidence(uint64(l)) return n } -func (m *EvidenceData) Size() (n int) { + +func (m *EvidenceList) Size() (n int) { if m == nil { return 0 } @@ -596,7 +720,7 @@ func sovEvidence(x uint64) (n int) { func sozEvidence(x uint64) (n int) { return sovEvidence(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { +func (m *Evidence) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -619,15 +743,15 @@ func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DuplicateVoteEvidence: wiretype end group for non-group") + return fmt.Errorf("proto: Evidence: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DuplicateVoteEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteA", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DuplicateVoteEvidence", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -654,16 +778,15 @@ func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.VoteA == nil { - m.VoteA = &Vote{} - } - if err := m.VoteA.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + v := &DuplicateVoteEvidence{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } + m.Sum = &Evidence_DuplicateVoteEvidence{v} iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteB", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LightClientAttackEvidence", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -690,12 +813,11 @@ func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.VoteB == nil { - m.VoteB = &Vote{} - } - if err := m.VoteB.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + v := &LightClientAttackEvidence{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } + m.Sum = &Evidence_LightClientAttackEvidence{v} iNdEx = postIndex default: iNdEx = preIndex @@ -721,7 +843,7 @@ func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { } return nil } -func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { +func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -744,15 +866,15 @@ func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: LightClientAttackEvidence: wiretype end group for non-group") + return fmt.Errorf("proto: DuplicateVoteEvidence: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: LightClientAttackEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DuplicateVoteEvidence: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ConflictingBlock", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field VoteA", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -779,18 +901,54 @@ func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ConflictingBlock == nil { - m.ConflictingBlock = &LightBlock{} + if m.VoteA == nil { + m.VoteA = &Vote{} } - if err := m.ConflictingBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.VoteA.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteB", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvidence + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvidence + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VoteB == nil { + m.VoteB = &Vote{} + } + if err := m.VoteB.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CommonHeight", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TotalVotingPower", wireType) } - m.CommonHeight = 0 + m.TotalVotingPower = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvidence @@ -800,11 +958,63 @@ func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.CommonHeight |= int64(b&0x7F) << shift + m.TotalVotingPower |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorPower", wireType) + } + m.ValidatorPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ValidatorPower |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } + if msglen < 0 { + return ErrInvalidLengthEvidence + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvidence + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Timestamp, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvidence(dAtA[iNdEx:]) @@ -829,7 +1039,7 @@ func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { } return nil } -func (m *Evidence) Unmarshal(dAtA []byte) error { +func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -852,15 +1062,15 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + return fmt.Errorf("proto: LightClientAttackEvidence: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: LightClientAttackEvidence: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DuplicateVoteEvidence", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ConflictingBlock", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -887,15 +1097,35 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &DuplicateVoteEvidence{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.ConflictingBlock == nil { + m.ConflictingBlock = &LightBlock{} + } + if err := m.ConflictingBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.Sum = &Evidence_DuplicateVoteEvidence{v} iNdEx = postIndex case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CommonHeight", wireType) + } + m.CommonHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CommonHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LightClientAttackEvidence", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -922,11 +1152,62 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &LightClientAttackEvidence{} - if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ByzantineValidators = append(m.ByzantineValidators, &Validator{}) + if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalVotingPower", wireType) + } + m.TotalVotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalVotingPower |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvidence + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvidence + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvidence + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Timestamp, dAtA[iNdEx:postIndex]); err != nil { return err } - m.Sum = &Evidence_LightClientAttackEvidence{v} iNdEx = postIndex default: iNdEx = preIndex @@ -952,7 +1233,7 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { } return nil } -func (m *EvidenceData) Unmarshal(dAtA []byte) error { +func (m *EvidenceList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -975,10 +1256,10 @@ func (m *EvidenceData) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EvidenceData: wiretype end group for non-group") + return fmt.Errorf("proto: EvidenceList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EvidenceData: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EvidenceList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/proto/tendermint/types/evidence.proto b/proto/tendermint/types/evidence.proto index 5f7d860bf4..3b234571ba 100644 --- a/proto/tendermint/types/evidence.proto +++ b/proto/tendermint/types/evidence.proto @@ -4,19 +4,9 @@ package tendermint.types; option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; import "tendermint/types/types.proto"; - -// DuplicateVoteEvidence contains evidence a validator signed two conflicting -// votes. -message DuplicateVoteEvidence { - Vote vote_a = 1; - Vote vote_b = 2; -} - -message LightClientAttackEvidence { - LightBlock conflicting_block = 1; - int64 common_height = 2; -} +import "tendermint/types/validator.proto"; message Evidence { oneof sum { @@ -25,7 +15,24 @@ message Evidence { } } -// EvidenceData contains any evidence of malicious wrong-doing by validators -message EvidenceData { +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. +message DuplicateVoteEvidence { + tendermint.types.Vote vote_a = 1; + tendermint.types.Vote vote_b = 2; + int64 total_voting_power = 3; + int64 validator_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. +message LightClientAttackEvidence { + tendermint.types.LightBlock conflicting_block = 1; + int64 common_height = 2; + repeated tendermint.types.Validator byzantine_validators = 3; + int64 total_voting_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +message EvidenceList { repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; } diff --git a/rpc/client/evidence_test.go b/rpc/client/evidence_test.go index 351c51bc97..527b8a9b55 100644 --- a/rpc/client/evidence_test.go +++ b/rpc/client/evidence_test.go @@ -42,7 +42,10 @@ func newEvidence(t *testing.T, val *privval.FilePV, vote2.Signature, err = val.Key.PrivKey.Sign(types.VoteSignBytes(chainID, v2)) require.NoError(t, err) - return types.NewDuplicateVoteEvidence(vote, vote2) + validator := types.NewValidator(val.Key.PubKey, 10) + valSet := types.NewValidatorSet([]*types.Validator{validator}) + + return types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet) } func makeEvidences( diff --git a/state/execution.go b/state/execution.go index 009fdea227..241c15e93a 100644 --- a/state/execution.go +++ b/state/execution.go @@ -115,7 +115,11 @@ func (blockExec *BlockExecutor) CreateProposalBlock( // Validation does not mutate state, but does require historical information from the stateDB, // ie. to verify evidence from a validator at an old height. func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error { - return validateBlock(blockExec.evpool, state, block) + err := validateBlock(state, block) + if err != nil { + return err + } + return blockExec.evpool.CheckEvidence(block.Evidence.Evidence) } // ApplyBlock validates the block against the state, executes it against the app, @@ -128,16 +132,13 @@ func (blockExec *BlockExecutor) ApplyBlock( state State, blockID types.BlockID, block *types.Block, ) (State, int64, error) { - if err := blockExec.ValidateBlock(state, block); err != nil { + if err := validateBlock(state, block); err != nil { return state, 0, ErrInvalidBlock(err) } - // Update evpool with the block and state and get any byzantine validators for that block - byzVals := blockExec.evpool.ABCIEvidence(block.Height, block.Evidence.Evidence) - startTime := time.Now().UnixNano() abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, - blockExec.store, state.InitialHeight, byzVals) + blockExec.store, state.InitialHeight) endTime := time.Now().UnixNano() blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000) if err != nil { @@ -180,7 +181,7 @@ func (blockExec *BlockExecutor) ApplyBlock( } // Update evpool with the latest state. - blockExec.evpool.Update(state) + blockExec.evpool.Update(state, block.Evidence.Evidence) fail.Fail() // XXX @@ -262,7 +263,6 @@ func execBlockOnProxyApp( block *types.Block, store Store, initialHeight int64, - byzVals []abci.Evidence, ) (*tmstate.ABCIResponses, error) { var validTxs, invalidTxs = 0, 0 @@ -292,6 +292,11 @@ func execBlockOnProxyApp( commitInfo := getBeginBlockValidatorInfo(block, store, initialHeight) + byzVals := make([]abci.Evidence, 0) + for _, evidence := range block.Evidence.Evidence { + byzVals = append(byzVals, evidence.ABCI()...) + } + // Begin block var err error pbh := block.Header.ToProto() @@ -526,7 +531,7 @@ func ExecCommitBlock( store Store, initialHeight int64, ) ([]byte, error) { - _, err := execBlockOnProxyApp(logger, appConnConsensus, block, store, initialHeight, []abci.Evidence{}) + _, err := execBlockOnProxyApp(logger, appConnConsensus, block, store, initialHeight) if err != nil { logger.Error("Error executing block on proxy app", "height", block.Height, "err", err) return nil, err diff --git a/state/execution_test.go b/state/execution_test.go index c1ce7ebd32..3d7fa93ab3 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -10,16 +10,20 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" mmock "github.com/tendermint/tendermint/mempool/mock" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/mocks" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/version" ) var ( @@ -125,10 +129,52 @@ func TestBeginBlockByzantineValidators(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() //nolint:errcheck // ignore for tests - state, stateDB, _ := makeState(1, 1) + state, stateDB, privVals := makeState(1, 1) stateStore := sm.NewStore(stateDB) defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) + privVal := privVals[state.Validators.Validators[0].Address.String()] + blockID := makeBlockID([]byte("headerhash"), 1000, []byte("partshash")) + header := &types.Header{ + Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1}, + ChainID: state.ChainID, + Height: 10, + Time: defaultEvidenceTime, + LastBlockID: blockID, + LastCommitHash: crypto.CRandBytes(tmhash.Size), + DataHash: crypto.CRandBytes(tmhash.Size), + ValidatorsHash: state.Validators.Hash(), + NextValidatorsHash: state.Validators.Hash(), + ConsensusHash: crypto.CRandBytes(tmhash.Size), + AppHash: crypto.CRandBytes(tmhash.Size), + LastResultsHash: crypto.CRandBytes(tmhash.Size), + EvidenceHash: crypto.CRandBytes(tmhash.Size), + ProposerAddress: crypto.CRandBytes(crypto.AddressSize), + } + + // we don't need to worry about validating the evidence as long as they pass validate basic + dve := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) + dve.ValidatorPower = 1000 + lcae := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: header, + Commit: types.NewCommit(10, 0, makeBlockID(header.Hash(), 100, []byte("partshash")), []types.CommitSig{{ + BlockIDFlag: types.BlockIDFlagNil, + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), + Timestamp: defaultEvidenceTime, + Signature: crypto.CRandBytes(types.MaxSignatureSize), + }}), + }, + ValidatorSet: state.Validators, + }, + CommonHeight: 8, + ByzantineValidators: []*types.Validator{state.Validators.Validators[0]}, + TotalVotingPower: 12, + Timestamp: defaultEvidenceTime, + } + + ev := []types.Evidence{dve, lcae} abciEv := []abci.Evidence{ { @@ -136,7 +182,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { Height: 3, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), - TotalVotingPower: 33, + TotalVotingPower: 10, }, { Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, @@ -148,15 +194,17 @@ func TestBeginBlockByzantineValidators(t *testing.T) { } evpool := &mocks.EvidencePool{} - evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(abciEv) - evpool.On("Update", mock.AnythingOfType("state.State")).Return() + evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return(ev, int64(100)) + evpool.On("Update", mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mmock.Mempool{}, evpool) block := makeBlock(state, 1) - blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()} + block.Evidence = types.EvidenceData{Evidence: ev} + block.Header.EvidenceHash = block.Evidence.Hash() + blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()} state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block) require.Nil(t, err) @@ -400,3 +448,19 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { assert.NotNil(t, err) assert.NotEmpty(t, state.NextValidators.Validators) } + +func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { + var ( + h = make([]byte, tmhash.Size) + psH = make([]byte, tmhash.Size) + ) + copy(h, hash) + copy(psH, partSetHash) + return types.BlockID{ + Hash: h, + PartSetHeader: types.PartSetHeader{ + Total: partSetSize, + Hash: psH, + }, + } +} diff --git a/state/mocks/evidence_pool.go b/state/mocks/evidence_pool.go index bfd82e5964..7292991ca5 100644 --- a/state/mocks/evidence_pool.go +++ b/state/mocks/evidence_pool.go @@ -4,8 +4,6 @@ package mocks import ( mock "github.com/stretchr/testify/mock" - abcitypes "github.com/tendermint/tendermint/abci/types" - state "github.com/tendermint/tendermint/state" types "github.com/tendermint/tendermint/types" @@ -16,22 +14,6 @@ type EvidencePool struct { mock.Mock } -// ABCIEvidence provides a mock function with given fields: _a0, _a1 -func (_m *EvidencePool) ABCIEvidence(_a0 int64, _a1 []types.Evidence) []abcitypes.Evidence { - ret := _m.Called(_a0, _a1) - - var r0 []abcitypes.Evidence - if rf, ok := ret.Get(0).(func(int64, []types.Evidence) []abcitypes.Evidence); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]abcitypes.Evidence) - } - } - - return r0 -} - // AddEvidence provides a mock function with given fields: _a0 func (_m *EvidencePool) AddEvidence(_a0 types.Evidence) error { ret := _m.Called(_a0) @@ -60,13 +42,13 @@ func (_m *EvidencePool) CheckEvidence(_a0 types.EvidenceList) error { return r0 } -// PendingEvidence provides a mock function with given fields: _a0 -func (_m *EvidencePool) PendingEvidence(_a0 int64) ([]types.Evidence, int64) { - ret := _m.Called(_a0) +// PendingEvidence provides a mock function with given fields: maxBytes +func (_m *EvidencePool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) { + ret := _m.Called(maxBytes) var r0 []types.Evidence if rf, ok := ret.Get(0).(func(int64) []types.Evidence); ok { - r0 = rf(_a0) + r0 = rf(maxBytes) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]types.Evidence) @@ -75,7 +57,7 @@ func (_m *EvidencePool) PendingEvidence(_a0 int64) ([]types.Evidence, int64) { var r1 int64 if rf, ok := ret.Get(1).(func(int64) int64); ok { - r1 = rf(_a0) + r1 = rf(maxBytes) } else { r1 = ret.Get(1).(int64) } @@ -83,7 +65,7 @@ func (_m *EvidencePool) PendingEvidence(_a0 int64) ([]types.Evidence, int64) { return r0, r1 } -// Update provides a mock function with given fields: _a0 -func (_m *EvidencePool) Update(_a0 state.State) { - _m.Called(_a0) +// Update provides a mock function with given fields: _a0, _a1 +func (_m *EvidencePool) Update(_a0 state.State, _a1 types.EvidenceList) { + _m.Called(_a0, _a1) } diff --git a/state/services.go b/state/services.go index e2f12b2378..5213f8fdd4 100644 --- a/state/services.go +++ b/state/services.go @@ -1,9 +1,6 @@ package state import ( - "time" - - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/types" ) @@ -45,9 +42,8 @@ type BlockStore interface { type EvidencePool interface { PendingEvidence(maxBytes int64) (ev []types.Evidence, size int64) AddEvidence(types.Evidence) error - Update(State) + Update(State, types.EvidenceList) CheckEvidence(types.EvidenceList) error - ABCIEvidence(int64, []types.Evidence) []abci.Evidence } // EmptyEvidencePool is an empty implementation of EvidencePool, useful for testing. It also complies @@ -58,11 +54,8 @@ func (EmptyEvidencePool) PendingEvidence(maxBytes int64) (ev []types.Evidence, s return nil, 0 } func (EmptyEvidencePool) AddEvidence(types.Evidence) error { return nil } -func (EmptyEvidencePool) Update(State) {} +func (EmptyEvidencePool) Update(State, types.EvidenceList) {} func (EmptyEvidencePool) CheckEvidence(evList types.EvidenceList) error { return nil } -func (EmptyEvidencePool) ABCIEvidence(int64, []types.Evidence) []abci.Evidence { - return []abci.Evidence{} -} -func (EmptyEvidencePool) AddEvidenceFromConsensus(types.Evidence, time.Time, *types.ValidatorSet) error { +func (EmptyEvidencePool) AddEvidenceFromConsensus(evidence types.Evidence) error { return nil } diff --git a/state/validation.go b/state/validation.go index 779edf2734..0ebe37bf90 100644 --- a/state/validation.go +++ b/state/validation.go @@ -12,7 +12,7 @@ import ( //----------------------------------------------------- // Validate block -func validateBlock(evidencePool EvidencePool, state State, block *types.Block) error { +func validateBlock(state State, block *types.Block) error { // Validate internal consistency. if err := block.ValidateBasic(); err != nil { return err @@ -147,6 +147,5 @@ func validateBlock(evidencePool EvidencePool, state State, block *types.Block) e return types.NewErrEvidenceOverflow(max, got) } - // Validate all evidence. - return evidencePool.CheckEvidence(block.Evidence.Evidence) + return nil } diff --git a/state/validation_test.go b/state/validation_test.go index 8fa5f89b3d..2b556b2682 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -218,7 +218,7 @@ func TestValidateBlockEvidence(t *testing.T) { evpool := &mocks.EvidencePool{} evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) - evpool.On("Update", mock.AnythingOfType("state.State")).Return() + evpool.On("Update", mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return( []abci.Evidence{}) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 37779248e3..a7da445965 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -41,11 +41,8 @@ var ( "restart": 0.1, } nodeMisbehaviors = weightedChoice{ - // FIXME Disabled due to: - // https://github.com/tendermint/tendermint/issues/5554 - // https://github.com/tendermint/tendermint/issues/5560 - // misbehaviorOption{"double-prevote"}: 1, - misbehaviorOption{}: 9, + misbehaviorOption{"double-prevote"}: 1, + misbehaviorOption{}: 9, } ) diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 67cd6fbb63..e2de60a75b 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -36,10 +36,7 @@ seeds = ["seed01"] seeds = ["seed01"] snapshot_interval = 5 perturb = ["disconnect"] -# FIXME Evidence handling causes panics and halts -# https://github.com/tendermint/tendermint/issues/5554 -# https://github.com/tendermint/tendermint/issues/5560 -#misbehaviors = { 1012 = "double-prevote", 1018 = "double-prevote" } +misbehaviors = { 1018 = "double-prevote" } [node.validator02] seeds = ["seed02"] @@ -62,7 +59,6 @@ perturb = ["kill"] persistent_peers = ["validator01"] database = "rocksdb" abci_protocol = "builtin" -retain_blocks = 1 perturb = ["pause"] [node.validator05] @@ -81,6 +77,7 @@ mode = "full" # https://github.com/tendermint/tendermint/issues/5444 fast_sync = "v2" persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] +retain_blocks = 1 perturb = ["restart"] [node.full02] diff --git a/test/e2e/tests/evidence_test.go b/test/e2e/tests/evidence_test.go index b982249407..8abb361c84 100644 --- a/test/e2e/tests/evidence_test.go +++ b/test/e2e/tests/evidence_test.go @@ -10,11 +10,12 @@ import ( "github.com/tendermint/tendermint/types" ) -// assert that all nodes that have blocks during the height (or height + 1) of a misbehavior has evidence +// assert that all nodes that have blocks at the height of a misbehavior has evidence // for that misbehavior func TestEvidence_Misbehavior(t *testing.T) { blocks := fetchBlockChain(t) testNode(t, func(t *testing.T, node e2e.Node) { + seenEvidence := make(map[int64]struct{}) for _, block := range blocks { // Find any evidence blaming this node in this block var nodeEvidence types.Evidence @@ -28,16 +29,14 @@ func TestEvidence_Misbehavior(t *testing.T) { t.Fatalf("unexpected evidence type %T", evidence) } } - - // Check that evidence was as expected (evidence is submitted in following height) - misbehavior, ok := node.Misbehaviors[block.Height-1] - if !ok { - require.Nil(t, nodeEvidence, "found unexpected evidence %v in height %v", - nodeEvidence, block.Height) - continue + if nodeEvidence == nil { + continue // no evidence for the node at this height } - require.NotNil(t, nodeEvidence, "no evidence found for misbehavior %v in height %v", - misbehavior, block.Height) + + // Check that evidence was as expected + misbehavior, ok := node.Misbehaviors[nodeEvidence.Height()] + require.True(t, ok, "found unexpected evidence %v in height %v", + nodeEvidence, block.Height) switch misbehavior { case "double-prevote": @@ -45,6 +44,14 @@ func TestEvidence_Misbehavior(t *testing.T) { default: t.Fatalf("unknown misbehavior %v", misbehavior) } + + seenEvidence[nodeEvidence.Height()] = struct{}{} + } + // see if there is any evidence that we were expecting but didn't see + for height, misbehavior := range node.Misbehaviors { + _, ok := seenEvidence[height] + require.True(t, ok, "expected evidence for %v misbehavior at height %v by node but was never found", + misbehavior, height) } }) } diff --git a/test/maverick/consensus/state.go b/test/maverick/consensus/state.go index b12d21edf6..a1385bd1ea 100644 --- a/test/maverick/consensus/state.go +++ b/test/maverick/consensus/state.go @@ -467,7 +467,7 @@ type txNotifier interface { type evidencePool interface { // Adds consensus based evidence to the evidence pool where time is the time // of the block where the offense occurred and the validator set is the current one. - AddEvidenceFromConsensus(types.Evidence, time.Time, *types.ValidatorSet) error + AddEvidenceFromConsensus(evidence types.Evidence) error } //---------------------------------------- @@ -1773,8 +1773,8 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { } else { timestamp = sm.MedianTime(cs.LastCommit.MakeCommit(), cs.LastValidators) } - evidenceErr := cs.evpool.AddEvidenceFromConsensus( - types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB), timestamp, cs.Validators) + ev := types.NewDuplicateVoteEvidence(voteErr.VoteA, voteErr.VoteB, timestamp, cs.Validators) + evidenceErr := cs.evpool.AddEvidenceFromConsensus(ev) if evidenceErr != nil { cs.Logger.Error("Failed to add evidence to the evidence pool", "err", evidenceErr) } diff --git a/types/block.go b/types/block.go index 0bdb2be675..71cbe6f82c 100644 --- a/types/block.go +++ b/types/block.go @@ -1118,12 +1118,12 @@ func (data *EvidenceData) StringIndented(indent string) string { } // ToProto converts EvidenceData to protobuf -func (data *EvidenceData) ToProto() (*tmproto.EvidenceData, error) { +func (data *EvidenceData) ToProto() (*tmproto.EvidenceList, error) { if data == nil { return nil, errors.New("nil evidence data") } - evi := new(tmproto.EvidenceData) + evi := new(tmproto.EvidenceList) eviBzs := make([]tmproto.Evidence, len(data.Evidence)) for i := range data.Evidence { protoEvi, err := EvidenceToProto(data.Evidence[i]) @@ -1138,7 +1138,7 @@ func (data *EvidenceData) ToProto() (*tmproto.EvidenceData, error) { } // FromProto sets a protobuf EvidenceData to the given pointer. -func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceData) error { +func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceList) error { if eviData == nil { return errors.New("nil evidenceData") } diff --git a/types/block_test.go b/types/block_test.go index 2bdeac7f0e..2355cb0f1b 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -698,13 +698,8 @@ func TestDataProtoBuf(t *testing.T) { // TestEvidenceDataProtoBuf ensures parity in converting to and from proto. func TestEvidenceDataProtoBuf(t *testing.T) { - val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) const chainID = "mychain" - v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, time.Now()) - v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, time.Now()) - ev := NewDuplicateVoteEvidence(v2, v) + ev := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) data := &EvidenceData{Evidence: EvidenceList{ev}} _ = data.ByteSize() testCases := []struct { diff --git a/types/evidence.go b/types/evidence.go index 5b450f9e1c..8007763b71 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -5,9 +5,11 @@ import ( "encoding/binary" "errors" "fmt" + "sort" "strings" "time" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/crypto/tmhash" tmjson "github.com/tendermint/tendermint/libs/json" @@ -18,31 +20,42 @@ import ( // Evidence represents any provable malicious activity by a validator. // Verification logic for each evidence is part of the evidence module. type Evidence interface { - Height() int64 // height of the infraction - Bytes() []byte // bytes which comprise the evidence - Hash() []byte // hash of the evidence - ValidateBasic() error // basic consistency check - String() string // string format of the evidence + ABCI() []abci.Evidence // forms individual evidence to be sent to the application + Bytes() []byte // bytes which comprise the evidence + Hash() []byte // hash of the evidence + Height() int64 // height of the infraction + String() string // string format of the evidence + Time() time.Time // time of the infraction + ValidateBasic() error // basic consistency check } //-------------------------------------------------------------------------------------- -// DuplicateVoteEvidence contains evidence a validator signed two conflicting -// votes. +// DuplicateVoteEvidence contains evidence of a single validator signing two conflicting votes. type DuplicateVoteEvidence struct { VoteA *Vote `json:"vote_a"` VoteB *Vote `json:"vote_b"` + + // abci specific information + TotalVotingPower int64 + ValidatorPower int64 + Timestamp time.Time } var _ Evidence = &DuplicateVoteEvidence{} // NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given // two conflicting votes. If one of the votes is nil, evidence returned is nil as well -func NewDuplicateVoteEvidence(vote1, vote2 *Vote) *DuplicateVoteEvidence { +func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet) *DuplicateVoteEvidence { var voteA, voteB *Vote - if vote1 == nil || vote2 == nil { + if vote1 == nil || vote2 == nil || valSet == nil { + return nil + } + idx, val := valSet.GetByAddress(vote1.ValidatorAddress) + if idx == -1 { return nil } + if strings.Compare(vote1.BlockID.Key(), vote2.BlockID.Key()) == -1 { voteA = vote1 voteB = vote2 @@ -51,19 +64,26 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote) *DuplicateVoteEvidence { voteB = vote1 } return &DuplicateVoteEvidence{ - VoteA: voteA, - VoteB: voteB, + VoteA: voteA, + VoteB: voteB, + TotalVotingPower: valSet.TotalVotingPower(), + ValidatorPower: val.VotingPower, + Timestamp: blockTime, } } -// String returns a string representation of the evidence. -func (dve *DuplicateVoteEvidence) String() string { - return fmt.Sprintf("DuplicateVoteEvidence{VoteA: %v, VoteB: %v}", dve.VoteA, dve.VoteB) -} - -// Height returns the height this evidence refers to. -func (dve *DuplicateVoteEvidence) Height() int64 { - return dve.VoteA.Height +// ABCI returns the application relevant representation of the evidence +func (dve *DuplicateVoteEvidence) ABCI() []abci.Evidence { + return []abci.Evidence{{ + Type: abci.EvidenceType_DUPLICATE_VOTE, + Validator: abci.Validator{ + Address: dve.VoteA.ValidatorAddress, + Power: dve.ValidatorPower, + }, + Height: dve.VoteA.Height, + Time: dve.Timestamp, + TotalVotingPower: dve.TotalVotingPower, + }} } // Bytes returns the proto-encoded evidence as a byte array. @@ -82,6 +102,21 @@ func (dve *DuplicateVoteEvidence) Hash() []byte { return tmhash.Sum(dve.Bytes()) } +// Height returns the height of the infraction +func (dve *DuplicateVoteEvidence) Height() int64 { + return dve.VoteA.Height +} + +// String returns a string representation of the evidence. +func (dve *DuplicateVoteEvidence) String() string { + return fmt.Sprintf("DuplicateVoteEvidence{VoteA: %v, VoteB: %v}", dve.VoteA, dve.VoteB) +} + +// Time returns the time of the infraction +func (dve *DuplicateVoteEvidence) Time() time.Time { + return dve.Timestamp +} + // ValidateBasic performs basic validation. func (dve *DuplicateVoteEvidence) ValidateBasic() error { if dve == nil { @@ -109,8 +144,11 @@ func (dve *DuplicateVoteEvidence) ToProto() *tmproto.DuplicateVoteEvidence { voteB := dve.VoteB.ToProto() voteA := dve.VoteA.ToProto() tp := tmproto.DuplicateVoteEvidence{ - VoteA: voteA, - VoteB: voteB, + VoteA: voteA, + VoteB: voteB, + TotalVotingPower: dve.TotalVotingPower, + ValidatorPower: dve.ValidatorPower, + Timestamp: dve.Timestamp, } return &tp } @@ -131,7 +169,13 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica return nil, err } - dve := NewDuplicateVoteEvidence(vA, vB) + dve := &DuplicateVoteEvidence{ + VoteA: vA, + VoteB: vB, + TotalVotingPower: pb.TotalVotingPower, + ValidatorPower: pb.ValidatorPower, + Timestamp: pb.Timestamp, + } return dve, dve.ValidateBasic() } @@ -146,15 +190,28 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica type LightClientAttackEvidence struct { ConflictingBlock *LightBlock CommonHeight int64 + + // abci specific information + ByzantineValidators []*Validator // validators in the validator set that misbehaved in creating the conflicting block + TotalVotingPower int64 // total voting power of the validator set at the common height + Timestamp time.Time // timestamp of the block at the common height } var _ Evidence = &LightClientAttackEvidence{} -// Height returns the last height at which the primary provider and witness provider had the same header. -// We use this as the height of the infraction rather than the actual conflicting header because we know -// that the malicious validators were bonded at this height which is important for evidence expiry -func (l *LightClientAttackEvidence) Height() int64 { - return l.CommonHeight +// ABCI forms an array of abci evidence for each byzantine validator +func (l *LightClientAttackEvidence) ABCI() []abci.Evidence { + abciEv := make([]abci.Evidence, len(l.ByzantineValidators)) + for idx, val := range l.ByzantineValidators { + abciEv[idx] = abci.Evidence{ + Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, + Validator: TM2PB.Validator(val), + Height: l.Height(), + Time: l.Timestamp, + TotalVotingPower: l.TotalVotingPower, + } + } + return abciEv } // Bytes returns the proto-encoded evidence as a byte array @@ -170,10 +227,75 @@ func (l *LightClientAttackEvidence) Bytes() []byte { return bz } -// Hash returns the hash of the header and the commonHeight. This is designed to cause hash collisions with evidence -// that have the same conflicting header and common height but different permutations of validator commit signatures. -// The reason for this is that we don't want to allow several permutations of the same evidence to be committed on -// chain. Ideally we commit the header with the most commit signatures but anything greater than 1/3 is sufficient. +// GetByzantineValidators finds out what style of attack LightClientAttackEvidence was and then works out who +// the malicious validators were and returns them. This is used both for forming the ByzantineValidators +// field and for validating that it is correct. Validators are ordered based on validator power +func (l *LightClientAttackEvidence) GetByzantineValidators(commonVals *ValidatorSet, + trusted *SignedHeader) []*Validator { + var validators []*Validator + // First check if the header is invalid. This means that it is a lunatic attack and therefore we take the + // validators who are in the commonVals and voted for the lunatic header + if l.ConflictingHeaderIsInvalid(trusted.Header) { + for _, commitSig := range l.ConflictingBlock.Commit.Signatures { + if !commitSig.ForBlock() { + continue + } + + _, val := commonVals.GetByAddress(commitSig.ValidatorAddress) + if val == nil { + // validator wasn't in the common validator set + continue + } + validators = append(validators, val) + } + sort.Sort(ValidatorsByVotingPower(validators)) + return validators + } else if trusted.Commit.Round == l.ConflictingBlock.Commit.Round { + // This is an equivocation attack as both commits are in the same round. We then find the validators + // from the conflicting light block validator set that voted in both headers. + // Validator hashes are the same therefore the indexing order of validators are the same and thus we + // only need a single loop to find the validators that voted twice. + for i := 0; i < len(l.ConflictingBlock.Commit.Signatures); i++ { + sigA := l.ConflictingBlock.Commit.Signatures[i] + if sigA.Absent() { + continue + } + + sigB := trusted.Commit.Signatures[i] + if sigB.Absent() { + continue + } + + _, val := l.ConflictingBlock.ValidatorSet.GetByAddress(sigA.ValidatorAddress) + validators = append(validators, val) + } + sort.Sort(ValidatorsByVotingPower(validators)) + return validators + } + // if the rounds are different then this is an amnesia attack. Unfortunately, given the nature of the attack, + // we aren't able yet to deduce which are malicious validators and which are not hence we return an + // empty validator set. + return validators +} + +// ConflictingHeaderIsInvalid takes a trusted header and matches it againt a conflicting header +// to determine whether the conflicting header was the product of a valid state transition +// or not. If it is then all the deterministic fields of the header should be the same. +// If not, it is an invalid header and constitutes a lunatic attack. +func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *Header) bool { + return !bytes.Equal(trustedHeader.ValidatorsHash, l.ConflictingBlock.ValidatorsHash) || + !bytes.Equal(trustedHeader.NextValidatorsHash, l.ConflictingBlock.NextValidatorsHash) || + !bytes.Equal(trustedHeader.ConsensusHash, l.ConflictingBlock.ConsensusHash) || + !bytes.Equal(trustedHeader.AppHash, l.ConflictingBlock.AppHash) || + !bytes.Equal(trustedHeader.LastResultsHash, l.ConflictingBlock.LastResultsHash) + +} + +// Hash returns the hash of the header and the commonHeight. This is designed to cause hash collisions +// with evidence that have the same conflicting header and common height but different permutations +// of validator commit signatures. The reason for this is that we don't want to allow several +// permutations of the same evidence to be committed on chain. Ideally we commit the header with the +// most commit signatures (captures the most byzantine validators) but anything greater than 1/3 is sufficient. func (l *LightClientAttackEvidence) Hash() []byte { buf := make([]byte, binary.MaxVarintLen64) n := binary.PutVarint(buf, l.CommonHeight) @@ -183,6 +305,24 @@ func (l *LightClientAttackEvidence) Hash() []byte { return tmhash.Sum(bz) } +// Height returns the last height at which the primary provider and witness provider had the same header. +// We use this as the height of the infraction rather than the actual conflicting header because we know +// that the malicious validators were bonded at this height which is important for evidence expiry +func (l *LightClientAttackEvidence) Height() int64 { + return l.CommonHeight +} + +// String returns a string representation of LightClientAttackEvidence +func (l *LightClientAttackEvidence) String() string { + return fmt.Sprintf("LightClientAttackEvidence{ConflictingBlock: %v, CommonHeight: %d}", + l.ConflictingBlock.String(), l.CommonHeight) +} + +// Time returns the time of the common block where the infraction leveraged off. +func (l *LightClientAttackEvidence) Time() time.Time { + return l.Timestamp +} + // ValidateBasic performs basic validation such that the evidence is consistent and can now be used for verification. func (l *LightClientAttackEvidence) ValidateBasic() error { if l.ConflictingBlock == nil { @@ -213,12 +353,6 @@ func (l *LightClientAttackEvidence) ValidateBasic() error { return nil } -// String returns a string representation of LightClientAttackEvidence -func (l *LightClientAttackEvidence) String() string { - return fmt.Sprintf("LightClientAttackEvidence{ConflictingBlock: %v, CommonHeight: %d}", - l.ConflictingBlock.String(), l.CommonHeight) -} - // ToProto encodes LightClientAttackEvidence to protobuf func (l *LightClientAttackEvidence) ToProto() (*tmproto.LightClientAttackEvidence, error) { conflictingBlock, err := l.ConflictingBlock.ToProto() @@ -226,29 +360,53 @@ func (l *LightClientAttackEvidence) ToProto() (*tmproto.LightClientAttackEvidenc return nil, err } + byzVals := make([]*tmproto.Validator, len(l.ByzantineValidators)) + for idx, val := range l.ByzantineValidators { + valpb, err := val.ToProto() + if err != nil { + return nil, err + } + byzVals[idx] = valpb + } + return &tmproto.LightClientAttackEvidence{ - ConflictingBlock: conflictingBlock, - CommonHeight: l.CommonHeight, + ConflictingBlock: conflictingBlock, + CommonHeight: l.CommonHeight, + ByzantineValidators: byzVals, + TotalVotingPower: l.TotalVotingPower, + Timestamp: l.Timestamp, }, nil } // LightClientAttackEvidenceFromProto decodes protobuf -func LightClientAttackEvidenceFromProto(l *tmproto.LightClientAttackEvidence) (*LightClientAttackEvidence, error) { - if l == nil { +func LightClientAttackEvidenceFromProto(lpb *tmproto.LightClientAttackEvidence) (*LightClientAttackEvidence, error) { + if lpb == nil { return nil, errors.New("empty light client attack evidence") } - conflictingBlock, err := LightBlockFromProto(l.ConflictingBlock) + conflictingBlock, err := LightBlockFromProto(lpb.ConflictingBlock) if err != nil { return nil, err } - le := &LightClientAttackEvidence{ - ConflictingBlock: conflictingBlock, - CommonHeight: l.CommonHeight, + byzVals := make([]*Validator, len(lpb.ByzantineValidators)) + for idx, valpb := range lpb.ByzantineValidators { + val, err := ValidatorFromProto(valpb) + if err != nil { + return nil, err + } + byzVals[idx] = val + } + + l := &LightClientAttackEvidence{ + ConflictingBlock: conflictingBlock, + CommonHeight: lpb.CommonHeight, + ByzantineValidators: byzVals, + TotalVotingPower: lpb.TotalVotingPower, + Timestamp: lpb.Timestamp, } - return le, le.ValidateBasic() + return l, l.ValidateBasic() } //------------------------------------------------------------------------------------------ @@ -386,9 +544,11 @@ func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) return NewMockDuplicateVoteEvidenceWithValidator(height, time, val, chainID) } +// assumes voting power to be 10 and validator to be the only one in the set func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, pv PrivValidator, chainID string) *DuplicateVoteEvidence { pubKey, _ := pv.GetPubKey() + val := NewValidator(pubKey, 10) voteA := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vA := voteA.ToProto() _ = pv.SignVote(chainID, vA) @@ -397,7 +557,7 @@ func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, vB := voteB.ToProto() _ = pv.SignVote(chainID, vB) voteB.Signature = vB.Signature - return NewDuplicateVoteEvidence(voteA, voteB) + return NewDuplicateVoteEvidence(voteA, voteB, time, NewValidatorSet([]*Validator{val})) } func makeMockVote(height int64, round, index int32, addr Address, diff --git a/types/evidence_test.go b/types/evidence_test.go index 4982a7d90d..2e61f6a9c4 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -33,8 +33,11 @@ func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) const chainID = "mychain" return &DuplicateVoteEvidence{ - VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), - VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)), + VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), + VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)), + TotalVotingPower: 30, + ValidatorPower: 10, + Timestamp: defaultVoteTime, } } @@ -78,7 +81,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) - ev := NewDuplicateVoteEvidence(vote1, vote2) + valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)}) + ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) tc.malleateEvidence(ev) assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) diff --git a/types/protobuf.go b/types/protobuf.go index 1e633338b7..1ee094a9f0 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -1,9 +1,6 @@ package types import ( - "fmt" - "reflect" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -111,29 +108,6 @@ func (tm2pb) ConsensusParams(params *tmproto.ConsensusParams) *abci.ConsensusPar } } -// ABCI Evidence includes information from the past that's not included in the evidence itself -// so Evidence types stays compact. -// XXX: panics on nil or unknown pubkey type -func (tm2pb) Evidence(ev Evidence, valSet *ValidatorSet) abci.Evidence { - - // set type - var evType abci.EvidenceType - switch ev.(type) { - case *DuplicateVoteEvidence: - evType = abci.EvidenceType_DUPLICATE_VOTE - case *LightClientAttackEvidence: - evType = abci.EvidenceType_LIGHT_CLIENT_ATTACK - default: - panic(fmt.Sprintf("unknown evidence type: %v %v", ev, reflect.TypeOf(ev))) - } - - return abci.Evidence{ - Type: evType, - Height: ev.Height(), - TotalVotingPower: valSet.TotalVotingPower(), - } -} - // XXX: panics on nil or unknown pubkey type func (tm2pb) NewValidatorUpdate(pubkey crypto.PubKey, power int64) abci.ValidatorUpdate { pubkeyABCI, err := cryptoenc.PubKeyToProto(pubkey) diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 6809591ed3..c617751e4e 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -60,26 +60,6 @@ func TestABCIConsensusParams(t *testing.T) { assert.Equal(t, *cp, cp2) } -func TestABCIEvidence(t *testing.T) { - val := NewMockPV() - blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) - blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) - const chainID = "mychain" - pubKey, err := val.GetPubKey() - require.NoError(t, err) - ev := &DuplicateVoteEvidence{ - VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), - VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime), - } - abciEv := TM2PB.Evidence( - ev, - NewValidatorSet([]*Validator{NewValidator(pubKey, 10)}), - ) - - assert.Equal(t, abci.EvidenceType_DUPLICATE_VOTE, abciEv.Type) - assert.Equal(t, ev.Height(), abciEv.GetHeight()) -} - type pubKeyEddie struct{} func (pubKeyEddie) Address() Address { return []byte{} } From 59f3f63d33bcd375e1c5b5f41e1f2b58f370f3ef Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 27 Oct 2020 17:22:00 +0100 Subject: [PATCH 058/108] test: fix various E2E test issues (#5576) * Don't use state sync for nodes starting at initial height. * Also remove stopped containers when cleaning up. * Start nodes in order of startAt, mode, name to avoid full nodes starting before their seeds. * Tweak network waiting to avoid halts caused by validator changes and perturbations. * Disable most tests for seed nodes, which aren't always able to join consensus. * Disable `blockchain/v2` due to known bugs. --- test/e2e/generator/generate.go | 28 +++++++++++++++++++--------- test/e2e/networks/ci.toml | 9 ++++++--- test/e2e/pkg/testnet.go | 10 ++++++++++ test/e2e/runner/cleanup.go | 2 +- test/e2e/runner/main.go | 22 +++++++++++++++------- test/e2e/runner/start.go | 15 ++++++++++++++- test/e2e/tests/app_test.go | 11 +++++++++++ test/e2e/tests/block_test.go | 8 ++++++++ test/e2e/tests/validator_test.go | 4 ++++ 9 files changed, 88 insertions(+), 21 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index a7da445965..f509ed1fd2 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -29,7 +29,10 @@ var ( nodeABCIProtocols = uniformChoice{"unix", "tcp", "grpc", "builtin"} nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} // FIXME v1 disabled due to https://github.com/tendermint/tendermint/issues/5444 - nodeFastSyncs = uniformChoice{"", "v0", "v2"} // "v1", + // FIXME v2 disabled due to: + // https://github.com/tendermint/tendermint/issues/5513 + // https://github.com/tendermint/tendermint/issues/5541 + nodeFastSyncs = uniformChoice{"", "v0"} // "v1", "v2" nodeStateSyncs = uniformChoice{false, true} nodePersistIntervals = uniformChoice{0, 1, 5} nodeSnapshotIntervals = uniformChoice{0, 3} @@ -87,7 +90,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er // First we generate seed nodes, starting at the initial height. for i := 1; i <= numSeeds; i++ { - manifest.Nodes[fmt.Sprintf("seed%02d", i)] = generateNode(r, e2e.ModeSeed, 0, false) + manifest.Nodes[fmt.Sprintf("seed%02d", i)] = generateNode( + r, e2e.ModeSeed, 0, manifest.InitialHeight, false) } // Next, we generate validators. We make sure a BFT quorum of validators start @@ -96,15 +100,16 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er nextStartAt := manifest.InitialHeight + 5 quorum := numValidators*2/3 + 1 for i := 1; i <= numValidators; i++ { - startAt := manifest.InitialHeight + startAt := int64(0) if i > quorum { startAt = nextStartAt nextStartAt += 5 } name := fmt.Sprintf("validator%02d", i) - manifest.Nodes[name] = generateNode(r, e2e.ModeValidator, startAt, i <= 2) + manifest.Nodes[name] = generateNode( + r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2) - if startAt == manifest.InitialHeight { + if startAt == 0 { (*manifest.Validators)[name] = int64(30 + r.Intn(71)) } else { manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{ @@ -130,7 +135,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er startAt = nextStartAt nextStartAt += 5 } - manifest.Nodes[fmt.Sprintf("full%02d", i)] = generateNode(r, e2e.ModeFull, startAt, false) + manifest.Nodes[fmt.Sprintf("full%02d", i)] = generateNode( + r, e2e.ModeFull, startAt, manifest.InitialHeight, false) } // We now set up peer discovery for nodes. Seed nodes are fully meshed with @@ -180,7 +186,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er // here, since we need to know the overall network topology and startup // sequencing. func generateNode( - r *rand.Rand, mode e2e.Mode, startAt int64, forceArchive bool) *e2e.ManifestNode { + r *rand.Rand, mode e2e.Mode, startAt int64, initialHeight int64, forceArchive bool, +) *e2e.ManifestNode { node := e2e.ManifestNode{ Mode: string(mode), StartAt: startAt, @@ -203,8 +210,11 @@ func generateNode( } if node.Mode == "validator" { - node.Misbehaviors = nodeMisbehaviors.Choose(r).(misbehaviorOption). - atHeight(startAt + 5 + int64(r.Intn(10))) + misbehaveAt := startAt + 5 + int64(r.Intn(10)) + if startAt == 0 { + misbehaveAt += initialHeight - 1 + } + node.Misbehaviors = nodeMisbehaviors.Choose(r).(misbehaviorOption).atHeight(misbehaveAt) if len(node.Misbehaviors) != 0 { node.PrivvalProtocol = "file" } diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index e2de60a75b..67e06bea19 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -1,4 +1,4 @@ -# This testnet is (will be) run by CI, and attempts to cover a broad range of +# This testnet is run by CI, and attempts to cover a broad range of # functionality with a single network. initial_height = 1000 @@ -75,7 +75,7 @@ start_at = 1010 mode = "full" # FIXME Should use v1, but it won't catch up since some nodes don't have all blocks # https://github.com/tendermint/tendermint/issues/5444 -fast_sync = "v2" +fast_sync = "v0" persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] retain_blocks = 1 perturb = ["restart"] @@ -83,7 +83,10 @@ perturb = ["restart"] [node.full02] start_at = 1015 mode = "full" -fast_sync = "v2" +# FIXME Should use v2, but it has concurrency bugs causing panics or halts +# https://github.com/tendermint/tendermint/issues/5513 +# https://github.com/tendermint/tendermint/issues/5541 +fast_sync = "v0" state_sync = true seeds = ["seed01"] perturb = ["restart"] diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 3425d70417..fa559fe615 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -403,6 +403,16 @@ func (t Testnet) IPv6() bool { return t.IP.IP.To4() == nil } +// HasPerturbations returns whether the network has any perturbations. +func (t Testnet) HasPerturbations() bool { + for _, node := range t.Nodes { + if len(node.Perturbations) > 0 { + return true + } + } + return false +} + // LastMisbehaviorHeight returns the height of the last misbehavior. func (t Testnet) LastMisbehaviorHeight() int64 { lastHeight := int64(0) diff --git a/test/e2e/runner/cleanup.go b/test/e2e/runner/cleanup.go index 0af49db7da..d99ca54cf9 100644 --- a/test/e2e/runner/cleanup.go +++ b/test/e2e/runner/cleanup.go @@ -32,7 +32,7 @@ func cleanupDocker() error { xargsR := `$(if [[ $OSTYPE == "linux-gnu"* ]]; then echo -n "-r"; fi)` err := exec("bash", "-c", fmt.Sprintf( - "docker container ls -q --filter label=e2e | xargs %v docker container rm -f", xargsR)) + "docker container ls -qa --filter label=e2e | xargs %v docker container rm -f", xargsR)) if err != nil { return err } diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index b20454e6b0..d55fd95f2c 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -69,25 +69,33 @@ func NewCLI() *CLI { if err := Start(cli.testnet); err != nil { return err } + if lastMisbehavior := cli.testnet.LastMisbehaviorHeight(); lastMisbehavior > 0 { - // wait for misbehaviors before starting perturbations - if err := WaitUntil(cli.testnet, lastMisbehavior+5); err != nil { + // wait for misbehaviors before starting perturbations. We do a separate + // wait for another 5 blocks, since the last misbehavior height may be + // in the past depending on network startup ordering. + if err := WaitUntil(cli.testnet, lastMisbehavior); err != nil { return err } } - if err := Perturb(cli.testnet); err != nil { - return err - } if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through return err } + if cli.testnet.HasPerturbations() { + if err := Perturb(cli.testnet); err != nil { + return err + } + if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through + return err + } + } + loadCancel() if err := <-chLoadResult; err != nil { return err } - // wait for network to settle before tests - if err := Wait(cli.testnet, 5); err != nil { + if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests return err } if err := Test(cli.testnet); err != nil { diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index b755f8965f..53acdd8210 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -10,8 +10,21 @@ import ( func Start(testnet *e2e.Testnet) error { - // Sort nodes by starting order + // Nodes are already sorted by name. Sort them by name then startAt, + // which gives the overall order startAt, mode, name. nodeQueue := testnet.Nodes + sort.SliceStable(nodeQueue, func(i, j int) bool { + a, b := nodeQueue[i], nodeQueue[j] + switch { + case a.Mode == b.Mode: + return false + case a.Mode == e2e.ModeSeed: + return true + case a.Mode == e2e.ModeValidator && b.Mode == e2e.ModeFull: + return true + } + return false + }) sort.SliceStable(nodeQueue, func(i, j int) bool { return nodeQueue[i].StartAt < nodeQueue[j].StartAt }) diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 33eac1b407..82e788ebd3 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -16,6 +16,9 @@ import ( // Tests that any initial state given in genesis has made it into the app. func TestApp_InitialState(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } if len(node.Testnet.InitialState) == 0 { return } @@ -35,6 +38,10 @@ func TestApp_InitialState(t *testing.T) { // block and the node sync status. func TestApp_Hash(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } + client, err := node.Client() require.NoError(t, err) info, err := client.ABCIInfo(ctx) @@ -56,6 +63,10 @@ func TestApp_Hash(t *testing.T) { // Tests that we can set a value and retrieve it. func TestApp_Tx(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } + client, err := node.Client() require.NoError(t, err) diff --git a/test/e2e/tests/block_test.go b/test/e2e/tests/block_test.go index 23653d1e4a..369b49d615 100644 --- a/test/e2e/tests/block_test.go +++ b/test/e2e/tests/block_test.go @@ -13,6 +13,10 @@ import ( func TestBlock_Header(t *testing.T) { blocks := fetchBlockChain(t) testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } + client, err := node.Client() require.NoError(t, err) status, err := client.Status(ctx) @@ -42,6 +46,10 @@ func TestBlock_Header(t *testing.T) { // Tests that the node contains the expected block range. func TestBlock_Range(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } + client, err := node.Client() require.NoError(t, err) status, err := client.Status(ctx) diff --git a/test/e2e/tests/validator_test.go b/test/e2e/tests/validator_test.go index 47eb1555ab..29f63bd926 100644 --- a/test/e2e/tests/validator_test.go +++ b/test/e2e/tests/validator_test.go @@ -14,6 +14,10 @@ import ( // scheduled validator updates. func TestValidator_Sets(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { + if node.Mode == e2e.ModeSeed { + return + } + client, err := node.Client() require.NoError(t, err) status, err := client.Status(ctx) From 25fafb30b57c69534c5156d5559fc883ab60d7d0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 30 Oct 2020 14:31:22 +0400 Subject: [PATCH 059/108] blockchain/v2: make the removal of an already removed peer a noop (#5553) also, since multiple StopPeerForError calls may be executed in parallel, only execute StopPeerForError once Closes #5541 --- CHANGELOG_PENDING.md | 1 + blockchain/v2/scheduler.go | 60 ++++++++++----------------------- blockchain/v2/scheduler_test.go | 11 +++--- p2p/switch.go | 4 +++ 4 files changed, 26 insertions(+), 50 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0d5dba43f5..4d6021ad62 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -31,6 +31,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) - [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) - [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) +- [blockchain/v2] \#5553 Make the removal of an already removed peer a noop (@melekes) - [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) - [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) - [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go index 811a74bae3..2e0a5f12d1 100644 --- a/blockchain/v2/scheduler.go +++ b/blockchain/v2/scheduler.go @@ -238,14 +238,13 @@ func (sc *scheduler) touchPeer(peerID p2p.ID, time time.Time) error { return nil } -func (sc *scheduler) removePeer(peerID p2p.ID) error { +func (sc *scheduler) removePeer(peerID p2p.ID) { peer, ok := sc.peers[peerID] if !ok { - return nil + return } - if peer.state == peerStateRemoved { - return fmt.Errorf("tried to remove peer %s in peerStateRemoved", peerID) + return } for height, pendingPeerID := range sc.pendingBlocks { @@ -279,8 +278,6 @@ func (sc *scheduler) removePeer(peerID p2p.ID) error { delete(sc.blockStates, h) } } - - return nil } // check if the blockPool is running low and add new blocks in New state to be requested. @@ -309,16 +306,12 @@ func (sc *scheduler) setPeerRange(peerID p2p.ID, base int64, height int64) error } if height < peer.height { - if err := sc.removePeer(peerID); err != nil { - return err - } + sc.removePeer(peerID) return fmt.Errorf("cannot move peer height lower. from %d to %d", peer.height, height) } if base > height { - if err := sc.removePeer(peerID); err != nil { - return err - } + sc.removePeer(peerID) return fmt.Errorf("cannot set peer base higher than its height") } @@ -372,15 +365,9 @@ func (sc *scheduler) setStateAtHeight(height int64, state blockState) { sc.blockStates[height] = state } +// CONTRACT: peer exists and in Ready state. func (sc *scheduler) markReceived(peerID p2p.ID, height int64, size int64, now time.Time) error { - peer, ok := sc.peers[peerID] - if !ok { - return fmt.Errorf("received block from unknown peer %s", peerID) - } - - if peer.state != peerStateReady { - return fmt.Errorf("cannot receive blocks from not ready peer %s", peerID) - } + peer := sc.peers[peerID] if state := sc.getStateAtHeight(height); state != blockStatePending || sc.pendingBlocks[height] != peerID { return fmt.Errorf("received block %d from peer %s without being requested", height, peerID) @@ -541,12 +528,13 @@ func (peers PeerByID) Swap(i, j int) { func (sc *scheduler) handleBlockResponse(event bcBlockResponse) (Event, error) { err := sc.touchPeer(event.peerID, event.time) if err != nil { - return scPeerError{peerID: event.peerID, reason: err}, nil + // peer does not exist OR not ready + return noOp, nil } err = sc.markReceived(event.peerID, event.block.Height, event.size, event.time) if err != nil { - _ = sc.removePeer(event.peerID) + sc.removePeer(event.peerID) return scPeerError{peerID: event.peerID, reason: err}, nil } @@ -561,7 +549,7 @@ func (sc *scheduler) handleNoBlockResponse(event bcNoBlockResponse) (Event, erro } // The peer may have been just removed due to errors, low speed or timeouts. - _ = sc.removePeer(event.peerID) + sc.removePeer(event.peerID) return scPeerError{peerID: event.peerID, reason: fmt.Errorf("peer %v with base %d height %d claims no block for %d", @@ -588,13 +576,10 @@ func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error) // Handles an error from the processor. The processor had already cleaned the blocks from // the peers included in this event. Just attempt to remove the peers. func (sc *scheduler) handleBlockProcessError(event pcBlockVerificationFailure) (Event, error) { - if len(sc.peers) == 0 { - return noOp, nil - } // The peers may have been just removed due to errors, low speed or timeouts. - _ = sc.removePeer(event.firstPeerID) + sc.removePeer(event.firstPeerID) if event.firstPeerID != event.secondPeerID { - _ = sc.removePeer(event.secondPeerID) + sc.removePeer(event.secondPeerID) } if sc.allBlocksProcessed() { @@ -610,12 +595,8 @@ func (sc *scheduler) handleAddNewPeer(event bcAddNewPeer) (Event, error) { } func (sc *scheduler) handleRemovePeer(event bcRemovePeer) (Event, error) { - err := sc.removePeer(event.peerID) - if err != nil { - // XXX - It is possible that the removePeer fails here for legitimate reasons - // for example if a peer timeout or error was handled just before this. - return scSchedulerFail{reason: err}, nil - } + sc.removePeer(event.peerID) + if sc.allBlocksProcessed() { return scFinishedEv{reason: "removed peer"}, nil } @@ -633,9 +614,7 @@ func (sc *scheduler) handleTryPrunePeer(event rTryPrunePeer) (Event, error) { // from that peer within sc.peerTimeout. Remove the peer. This is to ensure that a peer // will be timed out even if it sends blocks at higher heights but prevents progress by // not sending the block at current height. - if err := sc.removePeer(sc.pendingBlocks[sc.height]); err != nil { - return nil, err - } + sc.removePeer(sc.pendingBlocks[sc.height]) } prunablePeers := sc.prunablePeers(sc.peerTimeout, sc.minRecvRate, event.time) @@ -643,11 +622,7 @@ func (sc *scheduler) handleTryPrunePeer(event rTryPrunePeer) (Event, error) { return noOp, nil } for _, peerID := range prunablePeers { - err := sc.removePeer(peerID) - if err != nil { - // Should never happen as prunablePeers() returns only existing peers in Ready state. - panic("scheduler data corruption") - } + sc.removePeer(peerID) } // If all blocks are processed we should finish. @@ -656,7 +631,6 @@ func (sc *scheduler) handleTryPrunePeer(event rTryPrunePeer) (Event, error) { } return scPeersPruned{peers: prunablePeers}, nil - } func (sc *scheduler) handleResetState(event bcResetState) (Event, error) { diff --git a/blockchain/v2/scheduler_test.go b/blockchain/v2/scheduler_test.go index 60984c42de..c582a599d9 100644 --- a/blockchain/v2/scheduler_test.go +++ b/blockchain/v2/scheduler_test.go @@ -418,7 +418,6 @@ func TestScRemovePeer(t *testing.T) { "P1": {height: 10, state: peerStateRemoved}, "P2": {height: 11, state: peerStateReady}}, allB: []int64{8, 9, 10, 11}}, - wantErr: true, }, { name: "remove Ready peer with blocks requested", @@ -492,9 +491,7 @@ func TestScRemovePeer(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { sc := newTestScheduler(tt.fields) - if err := sc.removePeer(tt.args.peerID); (err != nil) != tt.wantErr { - t.Errorf("removePeer() wantErr %v, error = %v", tt.wantErr, err) - } + sc.removePeer(tt.args.peerID) wantSc := newTestScheduler(tt.wantFields) assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers) }) @@ -1413,13 +1410,13 @@ func TestScHandleBlockResponse(t *testing.T) { name: "empty scheduler", fields: scTestParams{}, args: args{event: block6FromP1}, - wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { name: "block from removed peer", fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, args: args{event: block6FromP1}, - wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { name: "block we haven't asked for", @@ -1438,7 +1435,7 @@ func TestScHandleBlockResponse(t *testing.T) { pendingTime: map[int64]time.Time{6: now}, }, args: args{event: block6FromP1}, - wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { name: "block with bad timestamp", diff --git a/p2p/switch.go b/p2p/switch.go index 9d84e37f72..f4f335b32f 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -322,6 +322,10 @@ func (sw *Switch) Peers() IPeerSet { // If the peer is persistent, it will attempt to reconnect. // TODO: make record depending on reason. func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { + if !peer.IsRunning() { + return + } + sw.Logger.Error("Stopping peer for error", "peer", peer, "err", reason) sw.stopAndRemovePeer(peer, reason) From 54a0940e40e547f8cf6fe7748ab43c4fe812c050 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 5 Nov 2020 16:24:48 +0400 Subject: [PATCH 060/108] blockchain/v2: remove peers from the processor (#5607) after they were pruned by the scheduler Closes #5513 --- blockchain/v2/reactor.go | 4 ++++ blockchain/v2/scheduler.go | 2 +- blockchain/v2/scheduler_test.go | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index 8cb33f5420..05ed0b157f 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -389,6 +389,10 @@ func (r *BlockchainReactor) demux(events <-chan Event) { case scSchedulerFail: r.logger.Error("Scheduler failure", "err", event.reason.Error()) case scPeersPruned: + // Remove peers from the processor. + for _, peerID := range event.peers { + r.processor.send(scPeerError{peerID: peerID, reason: errors.New("peer was pruned")}) + } r.logger.Debug("Pruned peers", "count", len(event.peers)) case noOpEvent: default: diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go index 2e0a5f12d1..75fe9d46dc 100644 --- a/blockchain/v2/scheduler.go +++ b/blockchain/v2/scheduler.go @@ -302,7 +302,7 @@ func (sc *scheduler) setPeerRange(peerID p2p.ID, base int64, height int64) error peer := sc.ensurePeer(peerID) if peer.state == peerStateRemoved { - return fmt.Errorf("cannot set peer height for a peer in peerStateRemoved") + return nil // noop } if height < peer.height { diff --git a/blockchain/v2/scheduler_test.go b/blockchain/v2/scheduler_test.go index c582a599d9..762ffd2c53 100644 --- a/blockchain/v2/scheduler_test.go +++ b/blockchain/v2/scheduler_test.go @@ -531,7 +531,6 @@ func TestScSetPeerRange(t *testing.T) { peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, args: args{peerID: "P1", height: 4}, wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, - wantErr: true, }, { name: "decrease height of single peer", @@ -1995,7 +1994,7 @@ func TestScHandleStatusResponse(t *testing.T) { name: "increase height of removed peer", fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, args: args{event: statusRespP1Ev}, - wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, + wantEvent: noOpEvent{}, }, { From a2addecb3d51d3f9ce696409e74f4b999e478348 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2020 16:33:55 +0000 Subject: [PATCH 061/108] build(deps): Bump github.com/golang/protobuf from 1.4.2 to 1.4.3 (#5506) Bumps [github.com/golang/protobuf](https://github.com/golang/protobuf) from 1.4.2 to 1.4.3.
Release notes

Sourced from github.com/golang/protobuf's releases.

v1.4.3

Notable changes:

(#1221) jsonpb: Fix marshaling of Duration (#1210) proto: convert integer to rune before converting to string

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/golang/protobuf&package-manager=go_modules&previous-version=1.4.2&new-version=1.4.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ee8ae5d190..2430f5f25f 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/go-kit/kit v0.10.0 github.com/go-logfmt/logfmt v0.5.0 github.com/gogo/protobuf v1.3.1 - github.com/golang/protobuf v1.4.2 + github.com/golang/protobuf v1.4.3 github.com/gorilla/websocket v1.4.2 github.com/gtank/merlin v0.1.1 github.com/libp2p/go-buffer-pool v0.0.2 diff --git a/go.sum b/go.sum index 3788a68aae..7bddb2c8af 100644 --- a/go.sum +++ b/go.sum @@ -188,6 +188,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= From 0bdc76a78c5987c27fa1e784f59902876f13fc4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 09:25:18 +0000 Subject: [PATCH 062/108] build(deps): Bump github.com/spf13/cobra from 1.0.0 to 1.1.0 (#5505) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.0.0 to 1.1.0.
Release notes

Sourced from github.com/spf13/cobra's releases.

v1.1.0

Notable Changes

  • Extend Go completions and revamp zsh comp (#1070)
  • Add completion for help command (#1136)
  • Complete subcommands when TraverseChildren is set (#1171)
  • Fix stderr printing functions (#894)
  • fix: fish output redirection (#1247)
Changelog

Sourced from github.com/spf13/cobra's changelog.

Cobra Changelog

Pending

  • Fix man page doc generation - no auto generated tag when cmd.DisableAutoGenTag = true @jpmcb
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/spf13/cobra&package-manager=go_modules&previous-version=1.0.0&new-version=1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2430f5f25f..bacd456ab7 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/rs/cors v1.7.0 github.com/sasha-s/go-deadlock v0.2.0 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 github.com/tendermint/tm-db v0.6.2 diff --git a/go.sum b/go.sum index 7bddb2c8af..a2985760bf 100644 --- a/go.sum +++ b/go.sum @@ -464,13 +464,18 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= +github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= From 0a4432baf508b0aec20ef5630e5ed4340ed22574 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 13:58:26 +0000 Subject: [PATCH 063/108] build(deps): Bump github.com/prometheus/client_golang from 1.7.1 to 1.8.0 (#5515) Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.7.1 to 1.8.0.
Release notes

Sourced from github.com/prometheus/client_golang's releases.

1.8.0 / 2020-10-15

  • [CHANGE] API client: Use time.Time rather than string for timestamps in RuntimeinfoResult. #777
  • [FEATURE] Export MetricVec to facilitate implementation of vectors of custom Metric types. #803
  • [FEATURE API client: Support /status/tsdb endpoint. #773
  • [ENHANCEMENT] API client: Enable GET fallback on status code 501. #802
  • [ENHANCEMENT] Remove Metric references after reslicing to free up more memory. #784
Changelog

Sourced from github.com/prometheus/client_golang's changelog.

1.8.0 / 2020-10-15

  • [CHANGE] API client: Use time.Time rather than string for timestamps in RuntimeinfoResult. #777
  • [FEATURE] Export MetricVec to facilitate implementation of vectors of custom Metric types. #803
  • [FEATURE API client: Support /status/tsdb endpoint. #773
  • [ENHANCEMENT] API client: Enable GET fallback on status code 501. #802
  • [ENHANCEMENT] Remove Metric references after reslicing to free up more memory. #784
Commits
  • 47cfdc9 Merge pull request #806 from prometheus/beorn7/release
  • 67f573a Cut v1.8.0
  • ded2474 Update dependencies
  • 3d1759b Run check for unused/missing Go packages only against latest Go version
  • e6ea98b Merge pull request #803 from prometheus/beorn7/vec
  • 85aa957 Export MetricVec (again)
  • 6007b2b Merge pull request #802 from prometheus/beorn7/fallback
  • 64b4a9c API client: Enable fallback on status code 501, too
  • 65c5578 Merge pull request #800 from prometheus/beorn7/doc
  • b54b73c Remove spurious commas from links to the docs site
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/prometheus/client_golang&package-manager=go_modules&previous-version=1.7.1&new-version=1.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index bacd456ab7..54404ad7bb 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/libp2p/go-buffer-pool v0.0.2 github.com/minio/highwayhash v1.0.1 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.7.1 + github.com/prometheus/client_golang v1.8.0 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 github.com/rs/cors v1.7.0 github.com/sasha-s/go-deadlock v0.2.0 diff --git a/go.sum b/go.sum index a2985760bf..0d2dfd4fed 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -274,6 +275,7 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -283,11 +285,13 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -329,6 +333,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -394,6 +399,8 @@ github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -414,6 +421,8 @@ github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLy github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -424,6 +433,8 @@ github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLk github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -444,6 +455,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -590,6 +602,7 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -634,8 +647,12 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From de0bef5db5cb78e64730da147d9f44fcdad2691b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 11:49:51 +0000 Subject: [PATCH 064/108] build(deps): Bump github.com/spf13/cobra from 1.1.0 to 1.1.1 (#5526) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.1.0 to 1.1.1.
Release notes

Sourced from github.com/spf13/cobra's releases.

v1.1.1

  • Fix: yaml.v2 2.3.0 contained a unintended breaking change. This release reverts to yaml.v2 v2.2.8 which has recent critical CVE fixes, but does not have the breaking changes. See spf13/cobra#1259 for context.
  • Fix: correct internal formatting for go-md2man v2 (which caused man page generation to be broken). See spf13/cobra#1049 for context.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/spf13/cobra&package-manager=go_modules&previous-version=1.1.0&new-version=1.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54404ad7bb..9262929b38 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/rs/cors v1.7.0 github.com/sasha-s/go-deadlock v0.2.0 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa - github.com/spf13/cobra v1.1.0 + github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 github.com/tendermint/tm-db v0.6.2 diff --git a/go.sum b/go.sum index 0d2dfd4fed..07b31972f7 100644 --- a/go.sum +++ b/go.sum @@ -476,8 +476,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= -github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -767,6 +767,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= From 9c32ad4a0233ba6c78ecfac935e75381ad6fc271 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 18:25:08 +0200 Subject: [PATCH 065/108] build(deps): Bump google.golang.org/grpc from 1.32.0 to 1.33.1 (#5544) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.32.0 to 1.33.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.32.0...v1.33.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Erik Grinaker --- go.mod | 2 +- go.sum | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9262929b38..abb5a5d649 100644 --- a/go.mod +++ b/go.mod @@ -31,5 +31,5 @@ require ( github.com/tendermint/tm-db v0.6.2 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc - google.golang.org/grpc v1.32.0 + google.golang.org/grpc v1.33.1 ) diff --git a/go.sum b/go.sum index 07b31972f7..1e58403f44 100644 --- a/go.sum +++ b/go.sum @@ -211,6 +211,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -735,6 +736,8 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 24d13479feb12e68a94b0e33dd3c9b7ddd37cc8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2020 10:50:42 +0000 Subject: [PATCH 066/108] build(deps): Bump google.golang.org/grpc from 1.33.1 to 1.33.2 (#5635) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.33.1 to 1.33.2.
Release notes

Sourced from google.golang.org/grpc's releases.

Release 1.33.2

  • protobuf: update all generated code to google.golang.org/protobuf (#3932)
  • xdsclient: populate error details for NACK (#3975)
  • internal/credentials: fix a bug and add one more helper function SPIFFEIDFromCert (#3929)
Commits
  • 56d6328 github: remove advancedtls examples test
  • 6396e4b vet: ignore proto deprecation warnings
  • 0afe9d2 github: add Github Actions workflow for tests; support in vet.sh (#4005)
  • 8a0ca33 Change version to 1.33.2
  • c1989b5 protobuf: update all generated code to google.golang.org/protobuf (#3932)
  • b205df6 xdsclient: populate error details for NACK (#3975)
  • 75e2768 internal/credentials: fix a bug and add one more helper function SPIFFEIDFrom...
  • 17493ac Change version to 1.33.2-dev
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.33.1&new-version=1.33.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index abb5a5d649..177788253d 100644 --- a/go.mod +++ b/go.mod @@ -31,5 +31,5 @@ require ( github.com/tendermint/tm-db v0.6.2 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc - google.golang.org/grpc v1.33.1 + google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index 1e58403f44..c4f38ef365 100644 --- a/go.sum +++ b/go.sum @@ -204,6 +204,8 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -736,8 +738,8 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -748,6 +750,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From a447c507e4e9c8702080878f520194a454dea5bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2020 11:05:17 +0000 Subject: [PATCH 067/108] build(deps): Bump github.com/tendermint/tm-db from 0.6.2 to 0.6.3 Bumps [github.com/tendermint/tm-db](https://github.com/tendermint/tm-db) from 0.6.2 to 0.6.3. - [Release notes](https://github.com/tendermint/tm-db/releases) - [Changelog](https://github.com/tendermint/tm-db/blob/master/CHANGELOG.md) - [Commits](https://github.com/tendermint/tm-db/compare/v0.6.2...v0.6.3) Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 177788253d..0665195d86 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 - github.com/tendermint/tm-db v0.6.2 + github.com/tendermint/tm-db v0.6.3 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc google.golang.org/grpc v1.33.2 diff --git a/go.sum b/go.sum index c4f38ef365..4521ab4abb 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger/v2 v2.2007.1 h1:t36VcBCpo4SsmAD5M8wVv1ieVzcALyGfaJ92z4ccULM= github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= +github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -515,6 +517,8 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7 github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4= github.com/tendermint/tm-db v0.6.2 h1:DOn8jwCdjJblrCFJbtonEIPD1IuJWpbRUUdR8GWE4RM= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= +github.com/tendermint/tm-db v0.6.3 h1:ZkhQcKnB8/2jr5EaZwGndN4owkPsGezW2fSisS9zGbg= +github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= From 637d76254d8a99fc4276f60105d16c88dee9f0e2 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 13 Nov 2020 12:30:34 +0100 Subject: [PATCH 068/108] go.mod: upgrade iavl and deps (#5657) Bumps IAVL, which pulled in some other upgrades as well. I think they should be fine though. --- go.mod | 8 ++++---- go.sum | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0665195d86..5e157fa8a8 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/Workiva/go-datastructures v1.0.52 github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 - github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb - github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd + github.com/confio/ics23/go v0.6.3 + github.com/cosmos/iavl v0.15.0-rc5 github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.10.0 github.com/go-logfmt/logfmt v0.5.0 @@ -29,7 +29,7 @@ require ( github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 github.com/tendermint/tm-db v0.6.3 - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a - golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20200822124328-c89045814202 google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index 4521ab4abb..c3cd9f6c21 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb h1:+7FsS1gZ1Km5LRjGV2hztpier/5i6ngNjvNpxbWP5I0= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.6.3 h1:PuGK2V1NJWZ8sSkNDq91jgT/cahFEW9RGp4Y5jxulf0= +github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= @@ -97,6 +99,8 @@ github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fj github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd h1:K3bmPkMDnd2KVQ7xoGmgp+pxoXcBW58vMWaMl9ZWx3c= github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd/go.mod h1:3xOIaNNX19p0QrX0VqWa6voPRoJRGGYtny+DH8NEPvE= +github.com/cosmos/iavl v0.15.0-rc5 h1:AMKgaAjXwGANWv56NL4q4hV+a0puSkLYD6cCQAv3i44= +github.com/cosmos/iavl v0.15.0-rc5/go.mod h1:WqoPL9yPTQ85QBMT45OOUzPxG/U/JcJoN7uMjgxke/I= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -231,12 +235,15 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.1/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.7 h1:Nk5kuHrnWUTf/0GL1a/vchH/om9Ap2/HnVna+jYZgTY= github.com/grpc-ecosystem/grpc-gateway v1.14.7/go.mod h1:oYZKL012gGh6LMyg/xA7Q2yq6j8bu0wa+9w14EEthWU= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= @@ -515,6 +522,7 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKk github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4= +github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg= github.com/tendermint/tm-db v0.6.2 h1:DOn8jwCdjJblrCFJbtonEIPD1IuJWpbRUUdR8GWE4RM= github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tendermint/tm-db v0.6.3 h1:ZkhQcKnB8/2jr5EaZwGndN4owkPsGezW2fSisS9zGbg= @@ -565,6 +573,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -612,6 +622,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -724,6 +736,8 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6 h1:iRN4+t0lvZX/l9gH14ARF9i58tsVa5a97k6aH95rC3Y= +google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -742,6 +756,7 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -762,6 +777,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= From 9567477d55ec29c810a105c802ddd3afc11dcb84 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Nov 2020 17:49:56 +0400 Subject: [PATCH 069/108] =?UTF-8?q?privval:=20increase=20read/write=20time?= =?UTF-8?q?out=20to=205s=20and=20calculate=20ping=20interva=E2=80=A6=20(#5?= =?UTF-8?q?666)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …l based on it (#5638) Partially closes #5550 --- CHANGELOG_PENDING.md | 1 + privval/signer_dialer_endpoint.go | 20 ++++++------ privval/signer_endpoint.go | 2 +- privval/signer_listener_endpoint.go | 39 ++++++++++++++++++------ privval/signer_listener_endpoint_test.go | 6 +++- privval/socket_listeners.go | 3 +- 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 4d6021ad62..8fa12ab0b5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -35,3 +35,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) - [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) - [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) +- [privval] \#5638 Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) diff --git a/privval/signer_dialer_endpoint.go b/privval/signer_dialer_endpoint.go index bd98314b60..93d26b0439 100644 --- a/privval/signer_dialer_endpoint.go +++ b/privval/signer_dialer_endpoint.go @@ -15,24 +15,26 @@ const ( // SignerServiceEndpointOption sets an optional parameter on the SignerDialerEndpoint. type SignerServiceEndpointOption func(*SignerDialerEndpoint) -// SignerDialerEndpointTimeoutReadWrite sets the read and write timeout for connections -// from external signing processes. +// SignerDialerEndpointTimeoutReadWrite sets the read and write timeout for +// connections from client processes. func SignerDialerEndpointTimeoutReadWrite(timeout time.Duration) SignerServiceEndpointOption { return func(ss *SignerDialerEndpoint) { ss.timeoutReadWrite = timeout } } -// SignerDialerEndpointConnRetries sets the amount of attempted retries to acceptNewConnection. +// SignerDialerEndpointConnRetries sets the amount of attempted retries to +// acceptNewConnection. func SignerDialerEndpointConnRetries(retries int) SignerServiceEndpointOption { return func(ss *SignerDialerEndpoint) { ss.maxConnRetries = retries } } -// SignerDialerEndpointRetryWaitInterval sets the retry wait interval to a custom value +// SignerDialerEndpointRetryWaitInterval sets the retry wait interval to a +// custom value. func SignerDialerEndpointRetryWaitInterval(interval time.Duration) SignerServiceEndpointOption { return func(ss *SignerDialerEndpoint) { ss.retryWait = interval } } -// SignerDialerEndpoint dials using its dialer and responds to any -// signature requests using its privVal. +// SignerDialerEndpoint dials using its dialer and responds to any signature +// requests using its privVal. type SignerDialerEndpoint struct { signerEndpoint @@ -57,13 +59,13 @@ func NewSignerDialerEndpoint( maxConnRetries: defaultMaxDialRetries, } + sd.BaseService = *service.NewBaseService(logger, "SignerDialerEndpoint", sd) + sd.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second + for _, optionFunc := range options { optionFunc(sd) } - sd.BaseService = *service.NewBaseService(logger, "SignerDialerEndpoint", sd) - sd.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second - return sd } diff --git a/privval/signer_endpoint.go b/privval/signer_endpoint.go index 8ea5e2f8aa..eb2ed442fb 100644 --- a/privval/signer_endpoint.go +++ b/privval/signer_endpoint.go @@ -12,7 +12,7 @@ import ( ) const ( - defaultTimeoutReadWriteSeconds = 3 + defaultTimeoutReadWriteSeconds = 5 ) type signerEndpoint struct { diff --git a/privval/signer_listener_endpoint.go b/privval/signer_listener_endpoint.go index 1f05995fac..be5acb2789 100644 --- a/privval/signer_listener_endpoint.go +++ b/privval/signer_listener_endpoint.go @@ -11,11 +11,22 @@ import ( privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" ) -// SignerValidatorEndpointOption sets an optional parameter on the SocketVal. -type SignerValidatorEndpointOption func(*SignerListenerEndpoint) +// SignerListenerEndpointOption sets an optional parameter on the SignerListenerEndpoint. +type SignerListenerEndpointOption func(*SignerListenerEndpoint) + +// SignerListenerEndpointTimeoutReadWrite sets the read and write timeout for +// connections from external signing processes. +// +// Default: 5s +func SignerListenerEndpointTimeoutReadWrite(timeout time.Duration) SignerListenerEndpointOption { + return func(sl *SignerListenerEndpoint) { sl.signerEndpoint.timeoutReadWrite = timeout } +} -// SignerListenerEndpoint listens for an external process to dial in -// and keeps the connection alive by dropping and reconnecting +// SignerListenerEndpoint listens for an external process to dial in and keeps +// the connection alive by dropping and reconnecting. +// +// The process will send pings every ~3s (read/write timeout * 2/3) to keep the +// connection alive. type SignerListenerEndpoint struct { signerEndpoint @@ -25,6 +36,7 @@ type SignerListenerEndpoint struct { timeoutAccept time.Duration pingTimer *time.Ticker + pingInterval time.Duration instanceMtx tmsync.Mutex // Ensures instance public methods access, i.e. SendRequest } @@ -33,15 +45,21 @@ type SignerListenerEndpoint struct { func NewSignerListenerEndpoint( logger log.Logger, listener net.Listener, + options ...SignerListenerEndpointOption, ) *SignerListenerEndpoint { - sc := &SignerListenerEndpoint{ + sl := &SignerListenerEndpoint{ listener: listener, timeoutAccept: defaultTimeoutAcceptSeconds * time.Second, } - sc.BaseService = *service.NewBaseService(logger, "SignerListenerEndpoint", sc) - sc.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second - return sc + sl.BaseService = *service.NewBaseService(logger, "SignerListenerEndpoint", sl) + sl.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second + + for _, optionFunc := range options { + optionFunc(sl) + } + + return sl } // OnStart implements service.Service. @@ -49,7 +67,9 @@ func (sl *SignerListenerEndpoint) OnStart() error { sl.connectRequestCh = make(chan struct{}) sl.connectionAvailableCh = make(chan net.Conn) - sl.pingTimer = time.NewTicker(defaultPingPeriodMilliseconds * time.Millisecond) + // NOTE: ping timeout must be less than read/write timeout + sl.pingInterval = time.Duration(sl.signerEndpoint.timeoutReadWrite.Milliseconds()*2/3) * time.Millisecond + sl.pingTimer = time.NewTicker(sl.pingInterval) go sl.serviceLoop() go sl.pingLoop() @@ -117,6 +137,7 @@ func (sl *SignerListenerEndpoint) ensureConnection(maxWait time.Duration) error } // block until connected or timeout + sl.Logger.Info("SignerListener: Blocking for connection") sl.triggerConnect() err := sl.WaitConnection(sl.connectionAvailableCh, maxWait) if err != nil { diff --git a/privval/signer_listener_endpoint_test.go b/privval/signer_listener_endpoint_test.go index 02b19eff3d..cbd45e6cee 100644 --- a/privval/signer_listener_endpoint_test.go +++ b/privval/signer_listener_endpoint_test.go @@ -168,7 +168,11 @@ func newSignerListenerEndpoint(logger log.Logger, addr string, timeoutReadWrite listener = tcpLn } - return NewSignerListenerEndpoint(logger, listener) + return NewSignerListenerEndpoint( + logger, + listener, + SignerListenerEndpointTimeoutReadWrite(testTimeoutReadWrite), + ) } func startListenerEndpointAsync(t *testing.T, sle *SignerListenerEndpoint, endpointIsOpenCh chan struct{}) { diff --git a/privval/socket_listeners.go b/privval/socket_listeners.go index 908e05e2eb..ad49adac43 100644 --- a/privval/socket_listeners.go +++ b/privval/socket_listeners.go @@ -9,8 +9,7 @@ import ( ) const ( - defaultTimeoutAcceptSeconds = 3 - defaultPingPeriodMilliseconds = 100 + defaultTimeoutAcceptSeconds = 3 ) // timeoutError can be used to check if an error returned from the netp package From 047b5ea85ea0070fab10b806aa99f36ed960c5b2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Nov 2020 17:58:55 +0400 Subject: [PATCH 070/108] bump go version to 1.15 (#5639) (#5667) --- .github/workflows/coverage.yml | 3 +++ .github/workflows/e2e-nightly.yml | 4 ++++ .github/workflows/e2e.yml | 3 +++ .github/workflows/release.yml | 5 ++--- .github/workflows/tests.yml | 12 ++++++++++++ README.md | 4 ++-- Vagrantfile | 6 +++--- go.mod | 2 +- networks/remote/integration.sh | 4 ++-- test/docker/Dockerfile | 2 +- 10 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d4e4f7a963..dbadb4d85f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -41,6 +41,9 @@ jobs: matrix: part: ["00", "01", "02", "03"] steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: diff --git a/.github/workflows/e2e-nightly.yml b/.github/workflows/e2e-nightly.yml index 9042d7f4ca..99b56af9d8 100644 --- a/.github/workflows/e2e-nightly.yml +++ b/.github/workflows/e2e-nightly.yml @@ -16,6 +16,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' + - uses: actions/checkout@v2 - name: Build diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fbc7488bd9..6ac2077acd 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,6 +13,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4aecbab06..6411a2d3f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,9 @@ jobs: with: fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v2 + - uses: actions/setup-go@v2 with: - go-version: 1.15 + go-version: '^1.15.4' - run: echo https://github.com/tendermint/tendermint/blob/${GITHUB_REF#refs/tags/}/CHANGELOG.md#${GITHUB_REF#refs/tags/} > ../release_notes.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e5e75e256..1cae87b2ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,6 +23,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -55,6 +58,9 @@ jobs: needs: Build timeout-minutes: 5 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -87,6 +93,9 @@ jobs: needs: Build timeout-minutes: 5 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -118,6 +127,9 @@ jobs: needs: Build timeout-minutes: 5 steps: + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.4' - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: diff --git a/README.md b/README.md index 80cdf0585d..efb4f13c4a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Or [Blockchain](), for shor [![version](https://img.shields.io/github/tag/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/releases/latest) [![API Reference](https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667)](https://pkg.go.dev/github.com/tendermint/tendermint) -[![Go version](https://img.shields.io/badge/go-1.14-blue.svg)](https://github.com/moovweb/gvm) +[![Go version](https://img.shields.io/badge/go-1.15-blue.svg)](https://github.com/moovweb/gvm) [![Discord chat](https://img.shields.io/discord/669268347736686612.svg)](https://discord.gg/AzefAFd) [![license](https://img.shields.io/github/license/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/blob/master/LICENSE) [![tendermint/tendermint](https://tokei.rs/b1/github/tendermint/tendermint?category=lines)](https://github.com/tendermint/tendermint) @@ -48,7 +48,7 @@ For examples of the kinds of bugs we're looking for, see [our security policy](S | Requirement | Notes | | ----------- | ---------------- | -| Go version | Go1.14 or higher | +| Go version | Go1.15 or higher | ## Documentation diff --git a/Vagrantfile b/Vagrantfile index 00fd2b310e..00e5c55c75 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -33,10 +33,10 @@ Vagrant.configure("2") do |config| usermod -aG docker vagrant # install go - wget -q https://dl.google.com/go/go1.14.linux-amd64.tar.gz - tar -xvf go1.14.linux-amd64.tar.gz + wget -q https://dl.google.com/go/go1.15.linux-amd64.tar.gz + tar -xvf go1.15.linux-amd64.tar.gz mv go /usr/local - rm -f go1.14.linux-amd64.tar.gz + rm -f go1.15.linux-amd64.tar.gz # install nodejs (for docs) curl -sL https://deb.nodesource.com/setup_11.x | bash - diff --git a/go.mod b/go.mod index 5e157fa8a8..1ed8a007e4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tendermint/tendermint -go 1.14 +go 1.15 require ( github.com/BurntSushi/toml v0.3.1 diff --git a/networks/remote/integration.sh b/networks/remote/integration.sh index 972ae60657..07382ba713 100644 --- a/networks/remote/integration.sh +++ b/networks/remote/integration.sh @@ -10,8 +10,8 @@ sudo apt-get upgrade -y sudo apt-get install -y jq unzip python-pip software-properties-common make # get and unpack golang -curl -O https://dl.google.com/go/go1.14.4.linux-amd64.tar.gz -tar -xvf go1.14.4.linux-amd64.tar.gz +curl -O https://dl.google.com/go/go1.15.4.linux-amd64.tar.gz +tar -xvf go1.15.4.linux-amd64.tar.gz ## move binary and add to path mv go /usr/local diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index a2d6ea7b0f..3628be9f99 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14 +FROM golang:1.15 # Grab deps (jq, hexdump, xxd, killall) RUN apt-get update && \ From e0cf94f5b0dd1854c101e4b25583ab17565c8936 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Nov 2020 18:10:49 +0400 Subject: [PATCH 071/108] privval: reset pingTimer to avoid sending unnecessary pings (#5642) (#5668) Refs #5550 --- privval/signer_listener_endpoint.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/privval/signer_listener_endpoint.go b/privval/signer_listener_endpoint.go index be5acb2789..fefa683172 100644 --- a/privval/signer_listener_endpoint.go +++ b/privval/signer_listener_endpoint.go @@ -123,6 +123,9 @@ func (sl *SignerListenerEndpoint) SendRequest(request privvalproto.Message) (*pr return nil, err } + // Reset pingTimer to avoid sending unnecessary pings. + sl.pingTimer.Reset(sl.pingInterval) + return &res, nil } From 53463b3fefc647952c4fa0dbf3b628845e08d6d2 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 16 Nov 2020 10:54:34 -0500 Subject: [PATCH 072/108] rpc: fix content-type header --- CHANGELOG.md | 23 +++++++++++++++++++++++ CHANGELOG_PENDING.md | 15 ++------------- rpc/jsonrpc/client/http_json_client.go | 23 +++++++++++++++++------ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a668bc35..9a8c1fc7ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## v0.34.0-rc6 + +*November 5, 2020* + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### IMPROVEMENTS + +- [statesync] \#5516 Check that all heights necessary to rebuild state for a snapshot exist before adding the snapshot to the pool. (@erikgrinaker) + +### BUG FIXES + +- [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) +- [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) +- [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) +- [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) +- [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) +- [blockchain/v2] \#5553 Make the removal of an already removed peer a noop (@melekes) +- [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) +- [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) +- [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) +- [privval] \#5638 Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) + ## v0.34.0-rc5 *October 13, 2020* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 8fa12ab0b5..78e78e56a3 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Unreleased Changes -## v0.34.0-rc6 +## v0.34.0-rc7 Special thanks to external contributors on this release: @@ -22,17 +22,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS -- [statesync] \#5516 Check that all heights necessary to rebuild state for a snapshot exist before adding the snapshot to the pool. (@erikgrinaker) - ### BUG FIXES -- [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) -- [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) -- [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) -- [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) -- [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) -- [blockchain/v2] \#5553 Make the removal of an already removed peer a noop (@melekes) -- [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) -- [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) -- [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) -- [privval] \#5638 Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) +- [rpc] \#5660 Set `application/json` as the `Content-Type` header in RPC responses. diff --git a/rpc/jsonrpc/client/http_json_client.go b/rpc/jsonrpc/client/http_json_client.go index c6e1af035a..59727390a5 100644 --- a/rpc/jsonrpc/client/http_json_client.go +++ b/rpc/jsonrpc/client/http_json_client.go @@ -151,10 +151,13 @@ func NewWithHTTPClient(remote string, client *http.Client) (*Client, error) { } // Call issues a POST HTTP request. Requests are JSON encoded. Content-Type: -// text/json. -func (c *Client) Call(ctx context.Context, method string, - params map[string]interface{}, result interface{}) (interface{}, error) { - +// application/json. +func (c *Client) Call( + ctx context.Context, + method string, + params map[string]interface{}, + result interface{}, +) (interface{}, error) { id := c.nextRequestID() request, err := types.MapToRequest(id, method, params) @@ -172,14 +175,18 @@ func (c *Client) Call(ctx context.Context, method string, if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - httpRequest.Header.Set("Content-Type", "text/json") + + httpRequest.Header.Set("Content-Type", "application/json") + if c.username != "" || c.password != "" { httpRequest.SetBasicAuth(c.username, c.password) } + httpResponse, err := c.client.Do(httpRequest) if err != nil { return nil, fmt.Errorf("post failed: %w", err) } + defer httpResponse.Body.Close() responseBytes, err := ioutil.ReadAll(httpResponse.Body) @@ -216,14 +223,18 @@ func (c *Client) sendBatch(ctx context.Context, requests []*jsonRPCBufferedReque if err != nil { return nil, fmt.Errorf("new request: %w", err) } - httpRequest.Header.Set("Content-Type", "text/json") + + httpRequest.Header.Set("Content-Type", "application/json") + if c.username != "" || c.password != "" { httpRequest.SetBasicAuth(c.username, c.password) } + httpResponse, err := c.client.Do(httpRequest) if err != nil { return nil, fmt.Errorf("post: %w", err) } + defer httpResponse.Body.Close() responseBytes, err := ioutil.ReadAll(httpResponse.Body) From 26493bbbd86d3a2e9b982d8c9f45323e81c2aea1 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 16 Nov 2020 12:31:32 +0100 Subject: [PATCH 073/108] test/e2e: fix secp failures (#5649) --- abci/example/kvstore/helpers.go | 2 +- abci/example/kvstore/persistent_kvstore.go | 2 +- abci/tests/server/client.go | 2 +- abci/types/pubkey.go | 28 ++++++++++++++++++---- crypto/encoding/codec.go | 2 +- test/e2e/app/app.go | 4 +++- test/e2e/app/config.go | 1 + test/e2e/pkg/testnet.go | 8 ++++--- test/e2e/runner/setup.go | 11 +++++---- test/e2e/tests/evidence_test.go | 2 +- test/e2e/tests/validator_test.go | 6 ++--- 11 files changed, 47 insertions(+), 21 deletions(-) diff --git a/abci/example/kvstore/helpers.go b/abci/example/kvstore/helpers.go index d1334b312d..e59fee279c 100644 --- a/abci/example/kvstore/helpers.go +++ b/abci/example/kvstore/helpers.go @@ -10,7 +10,7 @@ import ( func RandVal(i int) types.ValidatorUpdate { pubkey := tmrand.Bytes(32) power := tmrand.Uint16() + 1 - v := types.Ed25519ValidatorUpdate(pubkey, int64(power)) + v := types.UpdateValidator(pubkey, int64(power), "") return v } diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index c2232cc770..320918b5b1 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -238,7 +238,7 @@ func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.Respon } // update - return app.updateValidator(types.Ed25519ValidatorUpdate(pubkey, power)) + return app.updateValidator(types.UpdateValidator(pubkey, power, "")) } // add, update, or remove a validator diff --git a/abci/tests/server/client.go b/abci/tests/server/client.go index 36989f6ace..beaa3da894 100644 --- a/abci/tests/server/client.go +++ b/abci/tests/server/client.go @@ -16,7 +16,7 @@ func InitChain(client abcicli.Client) error { for i := 0; i < total; i++ { pubkey := tmrand.Bytes(33) power := tmrand.Int() - vals[i] = types.Ed25519ValidatorUpdate(pubkey, int64(power)) + vals[i] = types.UpdateValidator(pubkey, int64(power), "") } _, err := client.InitChainSync(types.RequestInitChain{ Validators: vals, diff --git a/abci/types/pubkey.go b/abci/types/pubkey.go index aaff6debbf..8530d95383 100644 --- a/abci/types/pubkey.go +++ b/abci/types/pubkey.go @@ -1,16 +1,16 @@ package types import ( + fmt "fmt" + "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" -) - -const ( - PubKeyEd25519 = "ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" ) func Ed25519ValidatorUpdate(pk []byte, power int64) ValidatorUpdate { pke := ed25519.PubKey(pk) + pkp, err := cryptoenc.PubKeyToProto(pke) if err != nil { panic(err) @@ -22,3 +22,23 @@ func Ed25519ValidatorUpdate(pk []byte, power int64) ValidatorUpdate { Power: power, } } + +func UpdateValidator(pk []byte, power int64, keyType string) ValidatorUpdate { + switch keyType { + case "", ed25519.KeyType: + return Ed25519ValidatorUpdate(pk, power) + case secp256k1.KeyType: + pke := secp256k1.PubKey(pk) + pkp, err := cryptoenc.PubKeyToProto(pke) + if err != nil { + panic(err) + } + return ValidatorUpdate{ + // Address: + PubKey: pkp, + Power: power, + } + default: + panic(fmt.Sprintf("key type %s not supported", keyType)) + } +} diff --git a/crypto/encoding/codec.go b/crypto/encoding/codec.go index f9c721131b..3c552ed235 100644 --- a/crypto/encoding/codec.go +++ b/crypto/encoding/codec.go @@ -51,7 +51,7 @@ func PubKeyFromProto(k pc.PublicKey) (crypto.PubKey, error) { return pk, nil case *pc.PublicKey_Secp256K1: if len(k.Secp256K1) != secp256k1.PubKeySize { - return nil, fmt.Errorf("invalid size for PubKeyEd25519. Got %d, expected %d", + return nil, fmt.Errorf("invalid size for PubKeySecp256k1. Got %d, expected %d", len(k.Secp256K1), secp256k1.PubKeySize) } pk := make(secp256k1.PubKey, secp256k1.PubKeySize) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 415fc7ad70..13002a7083 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -193,13 +193,15 @@ func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, if len(updates) == 0 { return nil, nil } + valUpdates := abci.ValidatorUpdates{} for keyString, power := range updates { + keyBytes, err := base64.StdEncoding.DecodeString(keyString) if err != nil { return nil, fmt.Errorf("invalid base64 pubkey value %q: %w", keyString, err) } - valUpdates = append(valUpdates, abci.Ed25519ValidatorUpdate(keyBytes, int64(power))) + valUpdates = append(valUpdates, abci.UpdateValidator(keyBytes, int64(power), app.cfg.KeyType)) } return valUpdates, nil } diff --git a/test/e2e/app/config.go b/test/e2e/app/config.go index 281419160d..38c967916d 100644 --- a/test/e2e/app/config.go +++ b/test/e2e/app/config.go @@ -22,6 +22,7 @@ type Config struct { PrivValKey string `toml:"privval_key"` PrivValState string `toml:"privval_state"` Misbehaviors map[string]string `toml:"misbehaviors"` + KeyType string `toml:"key_type"` } // LoadConfig loads the configuration from disk. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index fa559fe615..d4f788dbc0 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -64,7 +64,8 @@ type Node struct { Name string Testnet *Testnet Mode Mode - Key crypto.PrivKey + PrivvalKey crypto.PrivKey + NodeKey crypto.PrivKey IP net.IP ProxyPort uint32 StartAt int64 @@ -135,7 +136,8 @@ func LoadTestnet(file string) (*Testnet, error) { node := &Node{ Name: name, Testnet: testnet, - Key: keyGen.Generate(), + PrivvalKey: keyGen.Generate(manifest.KeyType), + NodeKey: keyGen.Generate("ed25519"), IP: ipGen.Next(), ProxyPort: proxyPortGen.Next(), Mode: ModeValidator, @@ -435,7 +437,7 @@ func (n Node) AddressP2P(withID bool) string { } addr := fmt.Sprintf("%v:26656", ip) if withID { - addr = fmt.Sprintf("%x@%v", n.Key.PubKey().Address().Bytes(), addr) + addr = fmt.Sprintf("%x@%v", n.NodeKey.PubKey().Address().Bytes(), addr) } return addr } diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 8c641d9f64..c1a7502dba 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -96,12 +96,12 @@ func Setup(testnet *e2e.Testnet) error { return err } - err = (&p2p.NodeKey{PrivKey: node.Key}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) + err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) if err != nil { return err } - (privval.NewFilePV(node.Key, + (privval.NewFilePV(node.PrivvalKey, filepath.Join(nodeDir, PrivvalKeyFile), filepath.Join(nodeDir, PrivvalStateFile), )).Save() @@ -194,8 +194,8 @@ func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) { for validator, power := range testnet.Validators { genesis.Validators = append(genesis.Validators, types.GenesisValidator{ Name: validator.Name, - Address: validator.Key.PubKey().Address(), - PubKey: validator.Key.PubKey(), + Address: validator.PrivvalKey.PubKey().Address(), + PubKey: validator.PrivvalKey.PubKey(), Power: power, }) } @@ -317,6 +317,7 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { "persist_interval": node.PersistInterval, "snapshot_interval": node.SnapshotInterval, "retain_blocks": node.RetainBlocks, + "key_type": node.PrivvalKey.Type(), } switch node.ABCIProtocol { case e2e.ProtocolUNIX: @@ -359,7 +360,7 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { for height, validators := range node.Testnet.ValidatorUpdates { updateVals := map[string]int64{} for node, power := range validators { - updateVals[base64.StdEncoding.EncodeToString(node.Key.PubKey().Bytes())] = power + updateVals[base64.StdEncoding.EncodeToString(node.PrivvalKey.PubKey().Bytes())] = power } validatorUpdates[fmt.Sprintf("%v", height)] = updateVals } diff --git a/test/e2e/tests/evidence_test.go b/test/e2e/tests/evidence_test.go index 8abb361c84..ea24b51e5c 100644 --- a/test/e2e/tests/evidence_test.go +++ b/test/e2e/tests/evidence_test.go @@ -22,7 +22,7 @@ func TestEvidence_Misbehavior(t *testing.T) { for _, evidence := range block.Evidence.Evidence { switch evidence := evidence.(type) { case *types.DuplicateVoteEvidence: - if bytes.Equal(evidence.VoteA.ValidatorAddress, node.Key.PubKey().Address()) { + if bytes.Equal(evidence.VoteA.ValidatorAddress, node.PrivvalKey.PubKey().Address()) { nodeEvidence = evidence } default: diff --git a/test/e2e/tests/validator_test.go b/test/e2e/tests/validator_test.go index 29f63bd926..8a36bb55d6 100644 --- a/test/e2e/tests/validator_test.go +++ b/test/e2e/tests/validator_test.go @@ -60,7 +60,7 @@ func TestValidator_Propose(t *testing.T) { if node.Mode != e2e.ModeValidator { return } - address := node.Key.PubKey().Address() + address := node.PrivvalKey.PubKey().Address() valSchedule := newValidatorSchedule(*node.Testnet) expectCount := 0 @@ -90,7 +90,7 @@ func TestValidator_Sign(t *testing.T) { if node.Mode != e2e.ModeValidator { return } - address := node.Key.PubKey().Address() + address := node.PrivvalKey.PubKey().Address() valSchedule := newValidatorSchedule(*node.Testnet) expectCount := 0 @@ -160,7 +160,7 @@ func (s *validatorSchedule) Increment(heights int64) { func makeVals(valMap map[*e2e.Node]int64) []*types.Validator { vals := make([]*types.Validator, 0, len(valMap)) for node, power := range valMap { - vals = append(vals, types.NewValidator(node.Key.PubKey(), power)) + vals = append(vals, types.NewValidator(node.PrivvalKey.PubKey(), power)) } return vals } From bea7673c1cd91596d131141191bc3454e080bfc4 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 17 Nov 2020 14:55:23 +0100 Subject: [PATCH 074/108] e2e: use ed25519 for secretConn (remote signer) (#5678) ## Description Hardcode ed25519 to dialTCPFn in e2e tests. I will backport `DefaultRequestHandler` fixes This will be replaced when grpc is implemented. --- privval/signer_requestHandler.go | 11 +++++++---- test/e2e/app/main.go | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/privval/signer_requestHandler.go b/privval/signer_requestHandler.go index ea020b6829..682863b19f 100644 --- a/privval/signer_requestHandler.go +++ b/privval/signer_requestHandler.go @@ -24,14 +24,17 @@ func DefaultValidationRequestHandler( switch r := req.Sum.(type) { case *privvalproto.Message_PubKeyRequest: if r.PubKeyRequest.GetChainId() != chainID { - res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{ + res = mustWrapMsg(&privvalproto.PubKeyResponse{ + PubKey: cryptoproto.PublicKey{}, Error: &privvalproto.RemoteSignerError{ Code: 0, Description: "unable to provide pubkey"}}) return res, fmt.Errorf("want chainID: %s, got chainID: %s", r.PubKeyRequest.GetChainId(), chainID) } var pubKey crypto.PubKey pubKey, err = privVal.GetPubKey() + if err != nil { + return res, err + } pk, err := cryptoenc.PubKeyToProto(pubKey) if err != nil { return res, err @@ -64,8 +67,8 @@ func DefaultValidationRequestHandler( case *privvalproto.Message_SignProposalRequest: if r.SignProposalRequest.GetChainId() != chainID { - res = mustWrapMsg(&privvalproto.SignedVoteResponse{ - Vote: tmproto.Vote{}, Error: &privvalproto.RemoteSignerError{ + res = mustWrapMsg(&privvalproto.SignedProposalResponse{ + Proposal: tmproto.Proposal{}, Error: &privvalproto.RemoteSignerError{ Code: 0, Description: "unable to sign proposal"}}) return res, fmt.Errorf("want chainID: %s, got chainID: %s", r.SignProposalRequest.GetChainId(), chainID) diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go index d37b09be4f..215c00d78f 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/app/main.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/abci/server" "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto/ed25519" tmflags "github.com/tendermint/tendermint/libs/cli/flags" "github.com/tendermint/tendermint/libs/log" tmnet "github.com/tendermint/tendermint/libs/net" @@ -174,7 +175,7 @@ func startSigner(cfg *Config) error { var dialFn privval.SocketDialer switch protocol { case "tcp": - dialFn = privval.DialTCPFn(address, 3*time.Second, filePV.Key.PrivKey) + dialFn = privval.DialTCPFn(address, 3*time.Second, ed25519.GenPrivKey()) case "unix": dialFn = privval.DialUnixFn(address) default: From 23bc2f690c6fe7c486381f4852efcca112a25b48 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 17 Nov 2020 11:49:57 +0100 Subject: [PATCH 075/108] ci: remove `add-path` (#5674) --- .github/workflows/tests.yml | 26 +++++++------------------- test/e2e/pkg/manifest.go | 4 ++++ test/e2e/pkg/testnet.go | 14 +++++++++++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1cae87b2ca..5a2eda03b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '^1.15.4' + go-version: "^1.15.4" - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -33,9 +33,6 @@ jobs: **/**.go go.mod go.sum - - name: Set GOBIN - run: | - echo "::add-path::$(go env GOPATH)/bin" - name: install run: make install install_abci if: "env.GIT_DIFF != ''" @@ -60,7 +57,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '^1.15.4' + go-version: "^1.15.4" - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -68,10 +65,7 @@ jobs: **/**.go go.mod go.sum - - name: Set GOBIN - run: | - echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.2 + - uses: actions/cache@v2.1.3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -95,7 +89,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '^1.15.4' + go-version: "^1.15.4" - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -103,10 +97,7 @@ jobs: **/**.go go.mod go.sum - - name: Set GOBIN - run: | - echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.2 + - uses: actions/cache@v2.1.3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -129,7 +120,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '^1.15.4' + go-version: "^1.15.4" - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: @@ -137,10 +128,7 @@ jobs: **/**.go go.mod go.sum - - name: Set GOBIN - run: | - echo "::add-path::$(go env GOPATH)/bin" - - uses: actions/cache@v2.1.2 + - uses: actions/cache@v2.1.3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 8316e57e6a..3fbf145580 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -46,6 +46,10 @@ type Manifest struct { // Nodes specifies the network nodes. At least one node must be given. Nodes map[string]*ManifestNode `toml:"node"` + + // KeyType sets the curve that will be used by validators. + // Options are ed25519 & secp256k1 + KeyType string `toml:"key_type"` } // ManifestNode represents a node in a testnet manifest. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index d4f788dbc0..90f2cff5e2 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" rpchttp "github.com/tendermint/tendermint/rpc/client/http" mcs "github.com/tendermint/tendermint/test/maverick/consensus" ) @@ -57,6 +58,7 @@ type Testnet struct { Validators map[*Node]int64 ValidatorUpdates map[int64]map[*Node]int64 Nodes []*Node + KeyType string } // Node represents a Tendermint node in a testnet. @@ -468,15 +470,21 @@ func newKeyGenerator(seed int64) *keyGenerator { } } -func (g *keyGenerator) Generate() crypto.PrivKey { +func (g *keyGenerator) Generate(keyType string) crypto.PrivKey { seed := make([]byte, ed25519.SeedSize) _, err := io.ReadFull(g.random, seed) if err != nil { panic(err) // this shouldn't happen } - - return ed25519.GenPrivKeyFromSecret(seed) + switch keyType { + case "secp256k1": + return secp256k1.GenPrivKeySecp256k1(seed) + case "", "ed25519": + return ed25519.GenPrivKeyFromSecret(seed) + default: + panic("KeyType not supported") // should not make it this far + } } // portGenerator generates local Docker proxy ports for each node. From 4ed0fddc37c662c7ed14921748e21c1e0f749bc5 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 17 Nov 2020 14:23:16 +0100 Subject: [PATCH 076/108] light: make fraction parts uint64, ensuring that it is always positive (#5655) --- CHANGELOG_PENDING.md | 2 ++ libs/math/fraction.go | 24 +++++++++++++++--------- libs/math/fraction_test.go | 26 ++++++++++++++++++++++---- light/verifier_test.go | 10 ++++------ types/validator_set.go | 4 ++-- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 78e78e56a3..73acf5426f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -20,6 +20,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES +- [libs/math] \#5665 Make fractions unsigned integers (uint64) (@cmwaters) + ### IMPROVEMENTS ### BUG FIXES diff --git a/libs/math/fraction.go b/libs/math/fraction.go index 399bc1c186..e38636485d 100644 --- a/libs/math/fraction.go +++ b/libs/math/fraction.go @@ -3,18 +3,18 @@ package math import ( "errors" "fmt" + "math" "strconv" "strings" ) -// Fraction defined in terms of a numerator divided by a denominator in int64 -// format. +// Fraction defined in terms of a numerator divided by a denominator in uint64 +// format. Fraction must be positive. type Fraction struct { // The portion of the denominator in the faction, e.g. 2 in 2/3. - Numerator int64 `json:"numerator"` - // The value by which the numerator is divided, e.g. 3 in 2/3. Must be - // positive. - Denominator int64 `json:"denominator"` + Numerator uint64 `json:"numerator"` + // The value by which the numerator is divided, e.g. 3 in 2/3. + Denominator uint64 `json:"denominator"` } func (fr Fraction) String() string { @@ -27,16 +27,22 @@ func (fr Fraction) String() string { func ParseFraction(f string) (Fraction, error) { o := strings.Split(f, "/") if len(o) != 2 { - return Fraction{}, errors.New("incorrect formating: should be like \"1/3\"") + return Fraction{}, errors.New("incorrect formating: should have a single slash i.e. \"1/3\"") } - numerator, err := strconv.ParseInt(o[0], 10, 64) + numerator, err := strconv.ParseUint(o[0], 10, 64) if err != nil { return Fraction{}, fmt.Errorf("incorrect formatting, err: %w", err) } - denominator, err := strconv.ParseInt(o[1], 10, 64) + denominator, err := strconv.ParseUint(o[1], 10, 64) if err != nil { return Fraction{}, fmt.Errorf("incorrect formatting, err: %w", err) } + if denominator == 0 { + return Fraction{}, errors.New("denominator can't be 0") + } + if numerator > math.MaxInt64 || denominator > math.MaxInt64 { + return Fraction{}, fmt.Errorf("value overflow, numerator and denominator must be less than %d", math.MaxInt64) + } return Fraction{Numerator: numerator, Denominator: denominator}, nil } diff --git a/libs/math/fraction_test.go b/libs/math/fraction_test.go index e4cabd32d5..73ca0f6c83 100644 --- a/libs/math/fraction_test.go +++ b/libs/math/fraction_test.go @@ -23,15 +23,33 @@ func TestParseFraction(t *testing.T) { exp: Fraction{15, 5}, err: false, }, + // test divide by zero error + { + f: "2/0", + exp: Fraction{}, + err: true, + }, + // test negative { f: "-1/2", - exp: Fraction{-1, 2}, - err: false, + exp: Fraction{}, + err: true, }, { f: "1/-2", - exp: Fraction{1, -2}, - err: false, + exp: Fraction{}, + err: true, + }, + // test overflow + { + f: "9223372036854775808/2", + exp: Fraction{}, + err: true, + }, + { + f: "2/9223372036854775808", + exp: Fraction{}, + err: true, }, { f: "2/3/4", diff --git a/light/verifier_test.go b/light/verifier_test.go index 712f3dac5a..9e10810b23 100644 --- a/light/verifier_test.go +++ b/light/verifier_test.go @@ -318,12 +318,10 @@ func TestValidateTrustLevel(t *testing.T) { 4: {tmmath.Fraction{Numerator: 4, Denominator: 5}, true}, // invalid - 5: {tmmath.Fraction{Numerator: 6, Denominator: 5}, false}, - 6: {tmmath.Fraction{Numerator: -1, Denominator: 3}, false}, - 7: {tmmath.Fraction{Numerator: 0, Denominator: 1}, false}, - 8: {tmmath.Fraction{Numerator: -1, Denominator: -3}, false}, - 9: {tmmath.Fraction{Numerator: 0, Denominator: 0}, false}, - 10: {tmmath.Fraction{Numerator: 1, Denominator: 0}, false}, + 5: {tmmath.Fraction{Numerator: 6, Denominator: 5}, false}, + 6: {tmmath.Fraction{Numerator: 0, Denominator: 1}, false}, + 7: {tmmath.Fraction{Numerator: 0, Denominator: 0}, false}, + 8: {tmmath.Fraction{Numerator: 1, Denominator: 0}, false}, } for _, tc := range testCases { diff --git a/types/validator_set.go b/types/validator_set.go index cc1a452a1d..af7b4034ef 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -779,11 +779,11 @@ func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *Comm ) // Safely calculate voting power needed. - totalVotingPowerMulByNumerator, overflow := safeMul(vals.TotalVotingPower(), trustLevel.Numerator) + totalVotingPowerMulByNumerator, overflow := safeMul(vals.TotalVotingPower(), int64(trustLevel.Numerator)) if overflow { return errors.New("int64 overflow while calculating voting power needed. please provide smaller trustLevel numerator") } - votingPowerNeeded := totalVotingPowerMulByNumerator / trustLevel.Denominator + votingPowerNeeded := totalVotingPowerMulByNumerator / int64(trustLevel.Denominator) for idx, commitSig := range commit.Signatures { // No need to verify absent or nil votes. From b5b53bfc0d652af534f7accf17a590738f273e03 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Wed, 18 Nov 2020 19:16:05 +0100 Subject: [PATCH 077/108] upgrading: update 0.34 instructions with updates since RC4 (#5686) --- UPGRADING.md | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index ff8309f9a0..b51c40824e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -8,8 +8,12 @@ This guide provides instructions for upgrading to specific versions of Tendermin This release is not compatible with previous blockchains due to changes to the encoding format (see "Protocol Buffers," below) and the block header (see "Blockchain Protocol"). +Note also that Tendermint 0.34 also requires Go 1.15 or higher. + ### ABCI Changes +* The `ABCIVersion` is now `0.17.0`. + * New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`) were added to support the new State Sync feature. Previously, syncing a new node to a preexisting network could take days; but with State Sync, @@ -62,7 +66,7 @@ directory. For more, see "Protobuf," below. Tendermint now relies on the application to tell it which transactions to index. This means that in the `config.toml`, generated by Tendermint, there is no longer a way to specify which -transactions to index. `tx.height` & `tx.hash` will always be indexed when using the `kv` indexer. +transactions to index. `tx.height` and `tx.hash` will always be indexed when using the `kv` indexer. Applications must now choose to either a) enable indexing for all transactions, or b) allow node operators to decide which transactions to index. @@ -108,7 +112,7 @@ Tendermint 0.34 includes new and updated consensus parameters. #### Evidence Parameters -* `MaxNum`, which caps the total amount of evidence by a absolute number. The default is 50. +* `MaxBytes`, which caps the total amount of evidence. The default is 1048576 (1 MB). ### Crypto @@ -168,16 +172,31 @@ Other user-relevant changes include: ### `privval` Package All requests are now accompanied by the chain ID from the network. -This is a optional field and can be ignored by key management systems. -It is recommended to check the chain ID if using the same key management system for multiple chains. +This is a optional field and can be ignored by key management systems; +however, if you are using the same key management system for multiple different +blockchains, we recommend that you check the chain ID. + ### RPC -`/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and -`/unsafe_write_heap_profile` were removed. -For profiling, please use the pprof server, which can -be enabled through `--rpc.pprof_laddr=X` flag or `pprof_laddr=X` config setting -in the rpc section. +* `/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and + `/unsafe_write_heap_profile` were removed. + For profiling, please use the pprof server, which can + be enabled through `--rpc.pprof_laddr=X` flag or `pprof_laddr=X` config setting + in the rpc section. +* The `Content-Type` header returned on RPC calls is now (correctly) set as `application/json`. + +### Version + +Version is now set through Go linker flags `ld_flags`. Applications that are using tendermint as a library should set this at compile time. + +Example: + +```sh +go install -mod=readonly -ldflags "-X github.com/tendermint/tendermint/version.TMCoreSemVer=$(go list -m github.com/tendermint/tendermint | sed 's/ /\@/g') -s -w " -trimpath ./cmd +``` + +Additionally, the exported constant `version.Version` is now `version.TMCoreSemVer`. ## v0.33.4 From b80d4d8ff0dadd60b16855a97dc58f15dfdb0f38 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 19 Nov 2020 17:41:41 +0100 Subject: [PATCH 078/108] relase_notes: add release notes for v0.34.0 --- release_notes.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index a537871c58..e60758562e 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1 +1,4 @@ - +Tendermint Core v0.34.0 is the Tendermint Core release which supports the Stargate upgrade. + +For more information on how to upgrade to Tendermint 0.34, please see [UPGRADING.md](https://github.com/tendermint/tendermint/blob/release/v0.34.0/UPGRADING.md). +For a full list of user-facing changes, please see [CHANGELOG.md](https://github.com/tendermint/tendermint/blob/release/v0.34.0/CHANGELOG.md). \ No newline at end of file From 0f29b1631e8f67357477111381addaa930531c18 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 11 Nov 2020 09:32:57 +0100 Subject: [PATCH 079/108] fix docker deployment (#5647) --- .github/workflows/docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 35d132b796..0a1dab4640 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,6 +2,7 @@ name: Build & Push # Build & Push rebuilds the tendermint docker image on every push to master and creation of tags # and pushes the image to https://hub.docker.com/r/interchainio/simapp/tags on: + pull_request: push: branches: - master @@ -13,6 +14,9 @@ jobs: build: runs-on: ubuntu-latest steps: + - uses: actions/setup-go@v2 + with: + go-version: "^1.15.4" - uses: actions/checkout@master - name: Prepare id: prep From 386a44cd029116311182035470db7c40d680dd3f Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 19 Nov 2020 18:32:32 +0100 Subject: [PATCH 080/108] .goreleaser: don't build linux/arm --- .goreleaser.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 9fb5933af4..c1790bb699 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,7 +16,6 @@ builds: - linux goarch: - amd64 - - arm - arm64 checksum: From fe9482598521a651d95fafd547be5fbba1458106 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 19 Nov 2020 17:32:02 +0100 Subject: [PATCH 081/108] changelog: squash changelog from 0.34 RCs into one (#5687) "Squashes" the changelog from RCs 2-6 into one changelog message for 0.34.0, and adds the changelog pending. --- CHANGELOG.md | 340 +++++++++++++++---------------------------- CHANGELOG_PENDING.md | 5 +- 2 files changed, 120 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8c1fc7ac..fe534485a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,288 +1,184 @@ # Changelog -## v0.34.0-rc6 +## v0.34.0 -*November 5, 2020* +*November 19, 2020* -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). - -### IMPROVEMENTS - -- [statesync] \#5516 Check that all heights necessary to rebuild state for a snapshot exist before adding the snapshot to the pool. (@erikgrinaker) - -### BUG FIXES - -- [blockchain/v2] \#5499 Fix "duplicate block enqueued by processor" panic (@melekes) -- [abci/grpc] \#5520 Return async responses in order, to avoid mempool panics. (@erikgrinaker) -- [blockchain/v2] \#5530 Fix "processed height 4541 but expected height 4540" panic (@melekes) -- [consensus/wal] Fix WAL autorepair by opening target WAL in read/write mode (@erikgrinaker) -- [block] \#5567 Fix MaxCommitSigBytes (@cmwaters) -- [blockchain/v2] \#5553 Make the removal of an already removed peer a noop (@melekes) -- [evidence] \#5574 Fix bug where node sends committed evidence to peer (@cmwaters) -- [privval] \#5583 Make `Vote`, `Proposal` & `PubKey` non-nullable in Responses (@marbar3778) -- [evidence] \#5610 Make it possible for abci evidence to be formed from tm evidence (@cmwaters) -- [privval] \#5638 Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) - -## v0.34.0-rc5 - -*October 13, 2020* - -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). - -### BREAKING CHANGES - -- Go API - - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) `MaxNum` evidence consensus parameter has been changed to `MaxBytes` (@cmwaters) - - [mempool] [\#5493](https://github.com/tendermint/tendermint/issues/5493) reduce `ComputeProtoSizeForTxs` to take one parameter (@marbar3778) - -### IMPROVEMENTS - -- [config] [\#5433](https://github.com/tendermint/tendermint/issues/5433) `statesync.rpc_servers` is now properly set when writing the configuration file (@erikgrinaker) - -- [privval] [\#5437](https://github.com/tendermint/tendermint/issues/5437) `NewSignerDialerEndpoint` can now be given `SignerServiceEndpointOption` (@erikgrinaker) -- [types] [\#5490](https://github.com/tendermint/tendermint/pull/5490) Use `Commit` and `CommitSig` max sizes instead of vote max size to calculate the maxiumum block size. (@cmwaters) - -### BUG FIXES - -- [privval] [\#5441](https://github.com/tendermint/tendermint/issues/5441) Fix faulty ping message encoding causing nil message errors in logs (@erikgrinaker) -- [rpc] [\#5459](https://github.com/tendermint/tendermint/issues/5459) Register the interface of public keys for json encoding (@marbar3778) -- [consensus] [\#5431](https://github.com/tendermint/tendermint/pull/5431) Check BlockParts do not exceed Maxium block size. (@cmwaters) -- [mempool] [\#5483](https://github.com/tendermint/tendermint/pull/5483) Check tx size does not exceed available free space in the block. (@marbar3778) - -## v0.34.0-rc4 - -*September 24, 2020* - -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). - -### BREAKING CHANGES - -- CLI/RPC/Config - - [config] [\#5315](https://github.com/tendermint/tendermint/issues/5315) Rename `prof_laddr` to `pprof_laddr` and move it to `rpc` section (@melekes) - - [rpc] [\#5315](https://github.com/tendermint/tendermint/issues/5315) Remove `/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and `/unsafe_write_heap_profile`. Please use pprof functionality instead (@melekes) - - [rpc/client, rpc/jsonrpc/client] [\#5347](https://github.com/tendermint/tendermint/issues/5347) All client methods now accept `context.Context` as 1st param (@melekes) - -- Apps - - [abci] [\#5324](https://github.com/tendermint/tendermint/pull/5324) abci evidence type is an enum with two types of possible evidence (@cmwaters) - -- P2P Protocol - - [mempool] [\#5321](https://github.com/tendermint/tendermint/issues/5321) Batch transactions when broadcasting them to peers (@melekes) `MaxBatchBytes` new config setting defines the max size of one batch. - -- Go API - - [evidence] [\#5317](https://github.com/tendermint/tendermint/issues/5317) Remove ConflictingHeaders evidence type & CompositeEvidence Interface. (@marbar3778) - - [evidence] [\#5318](https://github.com/tendermint/tendermint/issues/5318) Remove LunaticValidator evidence type. (@marbar3778) - - [evidence] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove Amnesia & potentialAmnesia evidence types and removed POLC. (@marbar3778) - - [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and change evidence interface (@cmwaters) - - [params] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove `ProofofTrialPeriod` from evidence params (@marbar3778) - - [light] [\#5347](https://github.com/tendermint/tendermint/issues/5347) `NewClient`, `NewHTTPClient`, `VerifyHeader` and `VerifyLightBlockAtHeight` now accept `context.Context` as 1st param (@melekes) - - [state] [\#5348](https://github.com/tendermint/tendermint/issues/5348) Define an Interface for the state store. (@marbar3778) - -### FEATURES - -- [privval] [\#5239](https://github.com/tendermint/tendermint/issues/5239) Add `chainID` to requests from client. (@marbar3778) -- [config] [\#5147](https://github.com/tendermint/tendermint/issues/5147) Add `--consensus.double_sign_check_height` flag and `DoubleSignCheckHeight` config variable. See [ADR-51](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-051-double-signing-risk-reduction.md) -- [light] [\#5298](https://github.com/tendermint/tendermint/pull/5298) Morph validator set and signed header into light block (@cmwaters) -- [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and refactor evidence lifecycle (@cmwaters) - -### IMPROVEMENTS - -- [blockchain] [\#5278](https://github.com/tendermint/tendermint/issues/5278) Verify only +2/3 of the signatures in a block when fast syncing. (@marbar3778) -- [rpc] [\#5293](https://github.com/tendermint/tendermint/issues/5293) `/dial_peers` has added `private` and `unconditional` as parameters. (@marbar3778) -- [types] [\#5340](https://github.com/tendermint/tendermint/issues/5340) Add check in `Header.ValidateBasic()` for block protocol version (@marbar3778) -- [statesync] [\#5399](https://github.com/tendermint/tendermint/issues/5399) Add `discovery_time` configuration setting, and reduce default to 15s. (@erikgrinaker) - -### BUG FIXES - -- [blockchain] [\#5249](https://github.com/tendermint/tendermint/issues/5249) Fix fast sync halt with initial height > 1 (@erikgrinaker) -- [statesync] [\#5302](https://github.com/tendermint/tendermint/issues/5302) Fix genesis state propagation to state sync routine (@erikgrinaker) -- [statesync] [\#5320](https://github.com/tendermint/tendermint/issues/5320) Broadcast snapshot request to all pre-connected peers on start (@erikgrinaker) -- [consensus] [\#5329](https://github.com/tendermint/tendermint/issues/5329) Fix wrong proposer schedule for validators returned by `InitChain` (@erikgrinaker) -- [store] [\#5382](https://github.com/tendermint/tendermint/issues/5382) Fix race conditions when loading/saving/pruning blocks (@erikgrinaker) -- [light] [\#5307](https://github.com/tendermint/tendermint/pull/5307) Persist correct proposer priority in light client validator sets (@cmwaters) -- [docker] [\#5385](https://github.com/tendermint/tendermint/issues/5385) Fix incorrect `time_iota_ms` default setting causing block timestamp drift (@erikgrinaker) -- [abci] [\#5395](https://github.com/tendermint/tendermint/issues/5395) Fix socket client error for state sync responses (@erikgrinaker) - - -## v0.34.0-rc3 - -*August 13, 2020* - -Special thanks to external contributors on this release: @SadPencil - -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). - -### BREAKING CHANGES: - -- Blockchain Protocol - - [\#5193](https://github.com/tendermint/tendermint/pull/5193) Header hashes are no longer empty for empty inputs, notably `DataHash`, `EvidenceHash`, and `LastResultsHash` (@erikgrinaker) - -- Go API - - [evidence] [\#5181](https://github.com/tendermint/tendermint/pull/5181) Phantom validator evidence was removed (@cmwaters) - - [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, following RFC6962 (@erikgrinaker) - - [crypto] [\#5214](https://github.com/tendermint/tendermint/issues/5214) Change `GenPrivKeySecp256k1` to `GenPrivKeyFromSecret` to be consistent with other keys (@marbar3778) - - [state] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `State.InitialHeight` field to record initial block height, must be `1` (not `0`) to start from 1 (@erikgrinaker) - - [state] [\#5231](https://github.com/tendermint/tendermint/issues/5231) `LoadStateFromDBOrGenesisFile()` and `LoadStateFromDBOrGenesisDoc()` no longer saves the state in the database if not found, the genesis state is simply returned (@erikgrinaker) - - [crypto] [\#5236](https://github.com/tendermint/tendermint/issues/5236) `VerifyBytes` is now `VerifySignature` on the `crypto.PubKey` interface (@marbar3778) - -### FEATURES: - -- [abci] [\#5174](https://github.com/tendermint/tendermint/pull/5174) Add amnesia evidence and remove mock and potential amnesia evidence from abci (@cmwaters) -- [abci] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `InitChain.InitialHeight` field giving the initial block height (@erikgrinaker) -- [abci] [\#5227](https://github.com/tendermint/tendermint/pull/5227) Add `ResponseInitChain.app_hash` which is recorded in genesis block (@erikgrinaker) -- [genesis] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `initial_height` field to specify the initial chain height (defaults to `1`) (@erikgrinaker) -- [db] [\#5233](https://github.com/tendermint/tendermint/issues/5233) Add support for `badgerdb` database backend (@erikgrinaker) - -### IMPROVEMENTS: - -- [evidence] [\#5219](https://github.com/tendermint/tendermint/pull/5219) Change the source of evidence time to block time (@cmwaters) - -### BUG FIXES: - -- [evidence] [\#5170](https://github.com/tendermint/tendermint/pull/5170) change abci evidence time to the time the infraction happened not the time the evidence was committed on the block (@cmwaters) -- [node] [\#5211](https://github.com/tendermint/tendermint/issues/5211) Don't attempt fast sync when the ABCI application specifies ourself as the only validator via `InitChain` (@erikgrinaker) -- [libs/rand] [\#5215](https://github.com/tendermint/tendermint/pull/5215) Fix out-of-memory error on unexpected argument of Str() (@SadPencil) - - -## v0.34.0-rc2 - -*July 30, 2020* +Holy smokes, this is a big one! For a more reader-friendly overview of the changes in 0.34.0 +(and of the changes you need to accommodate as a user), check out [UPGRADING.md](UPGRADING.md). Special thanks to external contributors on this release: @james-ray, @fedekunze, @favadi, @alessio, -@joe-bowman, @cuonglm +@joe-bowman, @cuonglm, @SadPencil and @dongsam. -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). +And as always, friendly reminder, that we have a [bug bounty program](https://hackerone.com/tendermint). -### BREAKING CHANGES: +### BREAKING CHANGES - CLI/RPC/Config - - [evidence] [\#4959](https://github.com/tendermint/tendermint/issues/4959) Add json tags to `DuplicateVoteEvidence` - - [light] [\#4946](https://github.com/tendermint/tendermint/issues/4946) `tendermint lite` cmd has been renamed to `tendermint light` - - [privval] [\#4582](https://github.com/tendermint/tendermint/issues/4582) `round` in private_validator_state.json is no longer a string in json it is now a number + - [config] [\#5315](https://github.com/tendermint/tendermint/pull/5315) Rename `prof_laddr` to `pprof_laddr` and move it to `rpc` section (@melekes) + - [evidence] [\#4959](https://github.com/tendermint/tendermint/pull/4959) Add JSON tags to `DuplicateVoteEvidence` (@marbar3778) + - [light] [\#4946](https://github.com/tendermint/tendermint/pull/4946) `tendermint lite` command has been renamed to `tendermint light` (@marbar3778) + - [privval] [\#4582](https://github.com/tendermint/tendermint/pull/4582) `round` in private_validator_state.json is no longer JSON string; instead it is a number (@marbar3778) - [rpc] [\#4792](https://github.com/tendermint/tendermint/pull/4792) `/validators` are now sorted by voting power (@melekes) - - [rpc] [\#4937](https://github.com/tendermint/tendermint/issues/4937) Return an error when `page` pagination param is 0 in `/validators`, `tx_search` (@melekes) - - [rpc] [\#5137](https://github.com/tendermint/tendermint/issues/5137) The json tags of `gasWanted` & `gasUsed` in `ResponseCheckTx` & `ResponseDeliverTx` have been made snake_case. (`gas_wanted` & `gas_used`) + - [rpc] [\#4947](https://github.com/tendermint/tendermint/pull/4947) Return an error when `page` pagination param is 0 in `/validators`, `tx_search` (@melekes) + - [rpc] [\#5137](https://github.com/tendermint/tendermint/pull/5137) JSON tags of `gasWanted` and `gasUsed` in `ResponseCheckTx` and `ResponseDeliverTx` have been made snake_case (`gas_wanted` and `gas_used`) (@marbar3778) + - [rpc] [\#5315](https://github.com/tendermint/tendermint/pull/5315) Remove `/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and `/unsafe_write_heap_profile`. Please use pprof functionality instead (@melekes) + - [rpc/client, rpc/jsonrpc/client] [\#5347](https://github.com/tendermint/tendermint/pull/5347) All client methods now accept `context.Context` as 1st param (@melekes) - Apps - - [abci] [\#4704](https://github.com/tendermint/tendermint/pull/4704) Add ABCI methods `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` for state sync snapshots. `ABCIVersion` bumped to 0.17.0. - - [abci] [\#4989](https://github.com/tendermint/tendermint/issues/4989) `Proof` within `ResponseQuery` has been renamed to `ProofOps` - - [abci] `CheckTxType` Protobuf enum names are now uppercase, to follow Protobuf style guide + - [abci] [\#4704](https://github.com/tendermint/tendermint/pull/4704) Add ABCI methods `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` for state sync snapshots. `ABCIVersion` bumped to 0.17.0. (@erikgrinaker) + - [abci] [\#4989](https://github.com/tendermint/tendermint/pull/4989) `Proof` within `ResponseQuery` has been renamed to `ProofOps` (@marbar3778) + - [abci] [\#5096](https://github.com/tendermint/tendermint/pull/5096) `CheckTxType` Protobuf enum names are now uppercase, to follow Protobuf style guide (@erikgrinaker) + - [abci] [\#5324](https://github.com/tendermint/tendermint/pull/5324) ABCI evidence type is now an enum with two types of possible evidence (@cmwaters) - P2P Protocol - - [blockchain] [\#4637](https://github.com/tendermint/tendermint/issues/4637) Migrate blockchain reactor(s) to Protobuf encoding - - [evidence] [\#4949](https://github.com/tendermint/tendermint/issues/4949) Migrate evidence reactor to Protobuf encoding - - [mempool] [\#4940](https://github.com/tendermint/tendermint/issues/4940) Migrate mempool from to Protobuf encoding - - [p2p/pex] [\#4973](https://github.com/tendermint/tendermint/issues/4973) Migrate `p2p/pex` reactor to Protobuf encoding - - [statesync] [\#4943](https://github.com/tendermint/tendermint/issues/4943) Migrate state sync reactor to Protobuf encoding + - [blockchain] [\#4637](https://github.com/tendermint/tendermint/pull/4637) Migrate blockchain reactor(s) to Protobuf encoding (@marbar3778) + - [evidence] [\#4949](https://github.com/tendermint/tendermint/pull/4949) Migrate evidence reactor to Protobuf encoding (@marbar3778) + - [mempool] [\#4940](https://github.com/tendermint/tendermint/pull/4940) Migrate mempool from to Protobuf encoding (@marbar3778) + - [mempool] [\#5321](https://github.com/tendermint/tendermint/pull/5321) Batch transactions when broadcasting them to peers (@melekes) + - `MaxBatchBytes` new config setting defines the max size of one batch. + - [p2p/pex] [\#4973](https://github.com/tendermint/tendermint/pull/4973) Migrate `p2p/pex` reactor to Protobuf encoding (@marbar3778) + - [statesync] [\#4943](https://github.com/tendermint/tendermint/pull/4943) Migrate state sync reactor to Protobuf encoding (@marbar3778) - Blockchain Protocol - - [evidence] [\#4780](https://github.com/tendermint/tendermint/pull/4780) Cap evidence to an absolute number (@cmwaters) - - Add `max_num` to consensus evidence parameters (default: 50 items). - - [evidence] [\#4725](https://github.com/tendermint/tendermint/issues/4725) Remove `Pubkey` from `DuplicateVoteEvidence` - - [state] [\#4845](https://github.com/tendermint/tendermint/issues/4845) Include `GasWanted` and `GasUsed` into `LastResultsHash` (@melekes) + - [evidence] [\#4725](https://github.com/tendermint/tendermint/pull/4725) Remove `Pubkey` from `DuplicateVoteEvidence` (@marbar3778) + - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) Cap evidence to a maximum number of bytes (supercedes [\#4780](https://github.com/tendermint/tendermint/pull/4780)) (@cmwaters) + - [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) Header hashes are no longer empty for empty inputs, notably `DataHash`, `EvidenceHash`, and `LastResultsHash` (@erikgrinaker) + - [state] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Include `GasWanted` and `GasUsed` into `LastResultsHash` (@melekes) - [types] [\#4792](https://github.com/tendermint/tendermint/pull/4792) Sort validators by voting power to enable faster commit verification (@melekes) - On-disk serialization - - [state] [\#4679](https://github.com/tendermint/tendermint/issues/4679) Migrate state module to Protobuf encoding + - [state] [\#4679](https://github.com/tendermint/tendermint/pull/4679) Migrate state module to Protobuf encoding (@marbar3778) - `BlockStoreStateJSON` is now `BlockStoreState` and is encoded as binary in the database - - [store] [\#4778](https://github.com/tendermint/tendermint/issues/4778) Migrate store module to Protobuf encoding + - [store] [\#4778](https://github.com/tendermint/tendermint/pull/4778) Migrate store module to Protobuf encoding (@marbar3778) - Light client, private validator - - [light] [\#4964](https://github.com/tendermint/tendermint/issues/4964) Migrate light module migration to Protobuf encoding - - [privval] [\#4985](https://github.com/tendermint/tendermint/issues/4985) Migrate `privval` module to Protobuf encoding + - [light] [\#4964](https://github.com/tendermint/tendermint/pull/4964) Migrate light module migration to Protobuf encoding (@marbar3778) + - [privval] [\#4985](https://github.com/tendermint/tendermint/pull/4985) Migrate `privval` module to Protobuf encoding (@marbar3778) - Go API - - [light] [\#4946](https://github.com/tendermint/tendermint/issues/4946) Rename `lite2` pkg to `light`. Remove `lite` implementation. + - [consensus] [\#4582](https://github.com/tendermint/tendermint/pull/4582) RoundState: `Round`, `LockedRound` & `CommitRound` are now `int32` (@marbar3778) + - [consensus] [\#4582](https://github.com/tendermint/tendermint/pull/4582) HeightVoteSet: `round` is now `int32` (@marbar3778) - [crypto] [\#4721](https://github.com/tendermint/tendermint/pull/4721) Remove `SimpleHashFromMap()` and `SimpleProofsFromMap()` (@erikgrinaker) - - [crypto] [\#4940](https://github.com/tendermint/tendermint/issues/4940) All keys have become `[]byte` instead of `[]byte`. The byte method no longer returns the marshaled value but just the `[]byte` form of the data. - - [crypto] \4988 Removal of key type multisig + - [crypto] [\#4940](https://github.com/tendermint/tendermint/pull/4940) All keys have become `[]byte` instead of `[]byte`. The byte method no longer returns the marshaled value but just the `[]byte` form of the data. (@marbar3778) + - [crypto] [\#4988](https://github.com/tendermint/tendermint/pull/4988) Removal of key type multisig (@marbar3778) - The key has been moved to the [Cosmos-SDK](https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go) - - [crypto] [\#4989](https://github.com/tendermint/tendermint/issues/4989) Remove `Simple` prefixes from `SimpleProof`, `SimpleValueOp` & `SimpleProofNode`. + - [crypto] [\#4989](https://github.com/tendermint/tendermint/pull/4989) Remove `Simple` prefixes from `SimpleProof`, `SimpleValueOp` & `SimpleProofNode`. (@marbar3778) - `merkle.Proof` has been renamed to `ProofOps`. - Protobuf messages `Proof` & `ProofOp` has been moved to `proto/crypto/merkle` - `SimpleHashFromByteSlices` has been renamed to `HashFromByteSlices` - `SimpleHashFromByteSlicesIterative` has been renamed to `HashFromByteSlicesIterative` - `SimpleProofsFromByteSlices` has been renamed to `ProofsFromByteSlices` - - [crypto] [\#4941](https://github.com/tendermint/tendermint/issues/4941) Remove suffixes from all keys. + - [crypto] [\#4941](https://github.com/tendermint/tendermint/pull/4941) Remove suffixes from all keys. (@marbar3778) - ed25519: type `PrivKeyEd25519` is now `PrivKey` - ed25519: type `PubKeyEd25519` is now `PubKey` - secp256k1: type`PrivKeySecp256k1` is now `PrivKey` - secp256k1: type`PubKeySecp256k1` is now `PubKey` - sr25519: type `PrivKeySr25519` is now `PrivKey` - sr25519: type `PubKeySr25519` is now `PubKey` - - multisig: type `PubKeyMultisigThreshold` is now `PubKey` - - [libs] [\#4831](https://github.com/tendermint/tendermint/issues/4831) Remove `Bech32` pkg from Tendermint. This pkg now lives in the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk/tree/4173ea5ebad906dd9b45325bed69b9c655504867/types/bech32) - - [rpc/client] [\#4947](https://github.com/tendermint/tendermint/issues/4947) `Validators`, `TxSearch` `page`/`per_page` params become pointers (@melekes) - - `UnconfirmedTxs` `limit` param is a pointer - - [proto] [\#5025](https://github.com/tendermint/tendermint/issues/5025) All proto files have been moved to `/proto` directory. + - [crypto] [\#5214](https://github.com/tendermint/tendermint/pull/5214) Change `GenPrivKeySecp256k1` to `GenPrivKeyFromSecret` to be consistent with other keys (@marbar3778) + - [crypto] [\#5236](https://github.com/tendermint/tendermint/pull/5236) `VerifyBytes` is now `VerifySignature` on the `crypto.PubKey` interface (@marbar3778) + - [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and change evidence interface (@cmwaters) + - [libs] [\#4831](https://github.com/tendermint/tendermint/pull/4831) Remove `Bech32` pkg from Tendermint. This pkg now lives in the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk/tree/4173ea5ebad906dd9b45325bed69b9c655504867/types/bech32) (@marbar3778) + - [light] [\#4946](https://github.com/tendermint/tendermint/pull/4946) Rename `lite2` pkg to `light`. Remove `lite` implementation. (@marbar3778) + - [light] [\#5347](https://github.com/tendermint/tendermint/pull/5347) `NewClient`, `NewHTTPClient`, `VerifyHeader` and `VerifyLightBlockAtHeight` now accept `context.Context` as 1st param (@melekes) + - [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, following RFC6962 (@erikgrinaker) + - [proto] [\#5025](https://github.com/tendermint/tendermint/pull/5025) All proto files have been moved to `/proto` directory. (@marbar3778) - Using the recommended the file layout from buf, [see here for more info](https://buf.build/docs/lint-checkers#file_layout) - - [state] [\#4679](https://github.com/tendermint/tendermint/issues/4679) `TxResult` is a Protobuf type defined in `abci` types directory - - [types] [\#4939](https://github.com/tendermint/tendermint/issues/4939) `SignedMsgType` has moved to a Protobuf enum types - - [types] [\#4962](https://github.com/tendermint/tendermint/issues/4962) `ConsensusParams`, `BlockParams`, `EvidenceParams`, `ValidatorParams` & `HashedParams` are now Protobuf types - - [types] [\#4852](https://github.com/tendermint/tendermint/issues/4852) Vote & Proposal `SignBytes` is now func `VoteSignBytes` & `ProposalSignBytes` - - [types] [\#4798](https://github.com/tendermint/tendermint/issues/4798) Simplify `VerifyCommitTrusting` func + remove extra validation (@melekes) - - [types] [\#4845](https://github.com/tendermint/tendermint/issues/4845) Remove `ABCIResult` - - [types] [\#5029](https://github.com/tendermint/tendermint/issues/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency - - [types] [\#4939](https://github.com/tendermint/tendermint/issues/4939) `Total` in `Parts` & `PartSetHeader` has been changed from a `int` to a `uint32` - - [types] [\#4939](https://github.com/tendermint/tendermint/issues/4939) Vote: `ValidatorIndex` & `Round` are now `int32` - - [types] [\#4939](https://github.com/tendermint/tendermint/issues/4939) Proposal: `POLRound` & `Round` are now `int32` - - [types] [\#4939](https://github.com/tendermint/tendermint/issues/4939) Block: `Round` is now `int32` - - [consensus] [\#4582](https://github.com/tendermint/tendermint/issues/4582) RoundState: `Round`, `LockedRound` & `CommitRound` are now `int32` - - [consensus] [\#4582](https://github.com/tendermint/tendermint/issues/4582) HeightVoteSet: `round` is now `int32` - - [rpc/jsonrpc/server] [\#5141](https://github.com/tendermint/tendermint/issues/5141) Remove `WriteRPCResponseArrayHTTP` (use `WriteRPCResponseHTTP` instead) (@melekes) + - [rpc/client] [\#4947](https://github.com/tendermint/tendermint/pull/4947) `Validators`, `TxSearch` `page`/`per_page` params become pointers (@melekes) + - `UnconfirmedTxs` `limit` param is a pointer + - [rpc/jsonrpc/server] [\#5141](https://github.com/tendermint/tendermint/pull/5141) Remove `WriteRPCResponseArrayHTTP` (use `WriteRPCResponseHTTP` instead) (@melekes) + - [state] [\#4679](https://github.com/tendermint/tendermint/pull/4679) `TxResult` is a Protobuf type defined in `abci` types directory (@marbar3778) + - [state] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `State.InitialHeight` field to record initial block height, must be `1` (not `0`) to start from 1 (@erikgrinaker) + - [state] [\#5231](https://github.com/tendermint/tendermint/pull/5231) `LoadStateFromDBOrGenesisFile()` and `LoadStateFromDBOrGenesisDoc()` no longer saves the state in the database if not found, the genesis state is simply returned (@erikgrinaker) + - [state] [\#5348](https://github.com/tendermint/tendermint/pull/5348) Define an Interface for the state store. (@marbar3778) + - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) `SignedMsgType` has moved to a Protobuf enum types (@marbar3778) + - [types] [\#4962](https://github.com/tendermint/tendermint/pull/4962) `ConsensusParams`, `BlockParams`, `EvidenceParams`, `ValidatorParams` & `HashedParams` are now Protobuf types (@marbar3778) + - [types] [\#4852](https://github.com/tendermint/tendermint/pull/4852) Vote & Proposal `SignBytes` is now func `VoteSignBytes` & `ProposalSignBytes` (@marbar3778) + - [types] [\#4798](https://github.com/tendermint/tendermint/pull/4798) Simplify `VerifyCommitTrusting` func + remove extra validation (@melekes) + - [types] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Remove `ABCIResult` (@melekes) + - [types] [\#5029](https://github.com/tendermint/tendermint/pull/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency (@marbar3778) + - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) `Total` in `Parts` & `PartSetHeader` has been changed from a `int` to a `uint32` (@marbar3778) + - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Vote: `ValidatorIndex` & `Round` are now `int32` (@marbar3778) + - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Proposal: `POLRound` & `Round` are now `int32` (@marbar3778) + - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Block: `Round` is now `int32` (@marbar3778) -### FEATURES: +### FEATURES -- [abci] [\#5031](https://github.com/tendermint/tendermint/issues/5031) Add `AppVersion` to consensus parameters (@james-ray) - - ... making it possible to update your ABCI application version via `EndBlock` response +- [abci] [\#5031](https://github.com/tendermint/tendermint/pull/5031) Add `AppVersion` to consensus parameters (@james-ray) + - This makes it possible to update your ABCI application version via `EndBlock` response +- [abci] [\#5174](https://github.com/tendermint/tendermint/pull/5174) Remove `MockEvidence` in favor of testing with actual evidence types (`DuplicateVoteEvidence` & `LightClientAttackEvidence`) (@cmwaters) +- [abci] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `InitChain.InitialHeight` field giving the initial block height (@erikgrinaker) +- [abci] [\#5227](https://github.com/tendermint/tendermint/pull/5227) Add `ResponseInitChain.app_hash` which is recorded in genesis block (@erikgrinaker) +- [config] [\#5147](https://github.com/tendermint/tendermint/pull/5147) Add `--consensus.double_sign_check_height` flag and `DoubleSignCheckHeight` config variable. See [ADR-51](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-051-double-signing-risk-reduction.md) (@dongsam) +- [db] [\#5233](https://github.com/tendermint/tendermint/pull/5233) Add support for `badgerdb` database backend (@erikgrinaker) - [evidence] [\#4532](https://github.com/tendermint/tendermint/pull/4532) Handle evidence from light clients (@melekes) -- [evidence] [#4821](https://github.com/tendermint/tendermint/pull/4821) Amnesia evidence can be detected, verified and committed (@cmwaters) -- [light] [\#4532](https://github.com/tendermint/tendermint/pull/4532) Submit conflicting headers, if any, to a full node & all witnesses (@melekes) -- [p2p] [\#4981](https://github.com/tendermint/tendermint/issues/4981) Expose `SaveAs` func on NodeKey (@melekes) +- [evidence] [#4821](https://github.com/tendermint/tendermint/pull/4821) Amnesia (light client attack) evidence can be detected, verified and committed (@cmwaters) +- [genesis] [\#5191](https://github.com/tendermint/tendermint/pull/5191) Add `initial_height` field to specify the initial chain height (defaults to `1`) (@erikgrinaker) +- [libs/math] [\#5665](https://github.com/tendermint/tendermint/pull/5665) Make fractions unsigned integers (uint64) (@cmwaters) +- [light] [\#5298](https://github.com/tendermint/tendermint/pull/5298) Morph validator set and signed header into light block (@cmwaters) +- [p2p] [\#4981](https://github.com/tendermint/tendermint/pull/4981) Expose `SaveAs` func on NodeKey (@melekes) +- [privval] [\#5239](https://github.com/tendermint/tendermint/pull/5239) Add `chainID` to requests from client. (@marbar3778) - [rpc] [\#4532](https://github.com/tendermint/tendermint/pull/4923) Support `BlockByHash` query (@fedekunze) -- [rpc] [\#4979](https://github.com/tendermint/tendermint/issues/4979) Support EXISTS operator in `/tx_search` query (@melekes) -- [rpc] [\#5017](https://github.com/tendermint/tendermint/issues/5017) Add `/check_tx` endpoint to check transactions without executing them or adding them to the mempool (@melekes) -- [statesync] Add state sync support, where a new node can be rapidly bootstrapped by fetching state snapshots from peers instead of replaying blocks. See the `[statesync]` config section. +- [rpc] [\#4979](https://github.com/tendermint/tendermint/pull/4979) Support EXISTS operator in `/tx_search` query (@melekes) +- [rpc] [\#5017](https://github.com/tendermint/tendermint/pull/5017) Add `/check_tx` endpoint to check transactions without executing them or adding them to the mempool (@melekes) - [rpc] [\#5108](https://github.com/tendermint/tendermint/pull/5108) Subscribe using the websocket for new evidence events (@cmwaters) +- [statesync] Add state sync support, where a new node can be rapidly bootstrapped by fetching state snapshots from peers instead of replaying blocks. See the `[statesync]` config section. +- [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and refactor evidence lifecycle - for more information see [ADR-059](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-059-evidence-composition-and-lifecycle.md) (@cmwaters) -### IMPROVEMENTS: +### IMPROVEMENTS -- [consensus] [\#4578](https://github.com/tendermint/tendermint/issues/4578) Attempt to repair the consensus WAL file (`data/cs.wal/wal`) automatically in case of corruption (@alessio) +- [blockchain] [\#5278](https://github.com/tendermint/tendermint/pull/5278) Verify only +2/3 of the signatures in a block when fast syncing. (@marbar3778) +- [consensus] [\#4578](https://github.com/tendermint/tendermint/pull/4578) Attempt to repair the consensus WAL file (`data/cs.wal/wal`) automatically in case of corruption (@alessio) - The original WAL file will be backed up to `data/cs.wal/wal.CORRUPTED`. -- [evidence] [\#4722](https://github.com/tendermint/tendermint/pull/4722) Improved evidence db (@cmwaters) +- [consensus] [\#5143](https://github.com/tendermint/tendermint/pull/5143) Only call `privValidator.GetPubKey` once per block (@melekes) +- [evidence] [\#4722](https://github.com/tendermint/tendermint/pull/4722) Consolidate evidence store and pool types to improve evidence DB (@cmwaters) - [evidence] [\#4839](https://github.com/tendermint/tendermint/pull/4839) Reject duplicate evidence from being proposed (@cmwaters) -- [evidence] [\#4892](https://github.com/tendermint/tendermint/pull/4892) Remove redundant header from phantom validator evidence (@cmwaters) +- [evidence] [\#5219](https://github.com/tendermint/tendermint/pull/5219) Change the source of evidence time to block time (@cmwaters) +- [libs] [\#5126](https://github.com/tendermint/tendermint/pull/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking (@marbar3778) - [light] [\#4935](https://github.com/tendermint/tendermint/pull/4935) Fetch and compare a new header with witnesses in parallel (@melekes) -- [light] [\#4929](https://github.com/tendermint/tendermint/pull/4929) compare header w/ witnesses only when doing bisection (@melekes) -- [light] [\#4916](https://github.com/tendermint/tendermint/pull/4916) validate basic for inbound validator sets and headers before further processing them (@cmwaters) -- [p2p/conn] [\#4795](https://github.com/tendermint/tendermint/issues/4795) Return err on `signChallenge()` instead of panic +- [light] [\#4929](https://github.com/tendermint/tendermint/pull/4929) Compare header with witnesses only when doing bisection (@melekes) +- [light] [\#4916](https://github.com/tendermint/tendermint/pull/4916) Validate basic for inbound validator sets and headers before further processing them (@cmwaters) +- [mempool] Add RemoveTxByKey() exported function for custom mempool cleaning (@p4u) +- [p2p/conn] [\#4795](https://github.com/tendermint/tendermint/pull/4795) Return err on `signChallenge()` instead of panic +- [privval] [\#5437](https://github.com/tendermint/tendermint/pull/5437) `NewSignerDialerEndpoint` can now be given `SignerServiceEndpointOption` (@erikgrinaker) +- [rpc] [\#4968](https://github.com/tendermint/tendermint/pull/4968) JSON encoding is now handled by `libs/json`, not Amino (@erikgrinaker) +- [rpc] [\#5293](https://github.com/tendermint/tendermint/pull/5293) `/dial_peers` has added `private` and `unconditional` as parameters. (@marbar3778) - [state] [\#4781](https://github.com/tendermint/tendermint/pull/4781) Export `InitStateVersion` for the initial state version (@erikgrinaker) - [txindex] [\#4466](https://github.com/tendermint/tendermint/pull/4466) Allow to index an event at runtime (@favadi) - `abci.EventAttribute` replaces `KV.Pair` -- [libs] [\#5126](https://github.com/tendermint/tendermint/issues/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking - [types] [\#4905](https://github.com/tendermint/tendermint/pull/4905) Add `ValidateBasic` to validator and validator set (@cmwaters) -- [rpc] [\#4968](https://github.com/tendermint/tendermint/issues/4968) JSON encoding is now handled by `libs/json`, not Amino -- [mempool] Add RemoveTxByKey() exported function for custom mempool cleaning (@p4u) -- [consensus] [\#5143](https://github.com/tendermint/tendermint/issues/5143) Only call `privValidator.GetPubKey` once per block (@melekes) - -### BUG FIXES: - -- [blockchain/v2] Correctly set block store base in status responses (@erikgrinaker) -- [consensus] [\#4895](https://github.com/tendermint/tendermint/pull/4895) Cache the address of the validator to reduce querying a remote KMS (@joe-bowman) -- [consensus] [\#4970](https://github.com/tendermint/tendermint/issues/4970) Stricter on `LastCommitRound` check (@cuonglm) -- [p2p][\#5136](https://github.com/tendermint/tendermint/pull/5136) Fix error for peer with the same ID but different IPs (@valardragon) -- [proxy] [\#5078](https://github.com/tendermint/tendermint/issues/5078) Fix a bug, where TM does not exit when ABCI app crashes (@melekes) +- [types] [\#5340](https://github.com/tendermint/tendermint/pull/5340) Add check in `Header.ValidateBasic()` for block protocol version (@marbar3778) +- [types] [\#5490](https://github.com/tendermint/tendermint/pull/5490) Use `Commit` and `CommitSig` max sizes instead of vote max size to calculate the maximum block size. (@cmwaters) -## v0.34.0-rc1 +### BUG FIXES -This release was removed, as a premature GitHub tag was recorded on sum.golang.org causing checksum errors. +- [abci/grpc] [\#5520](https://github.com/tendermint/tendermint/pull/5520) Return async responses in order, to avoid mempool panics. (@erikgrinaker) +- [blockchain/v2] [\#4971](https://github.com/tendermint/tendermint/pull/4971) Correctly set block store base in status responses (@erikgrinaker) +- [blockchain/v2] [\#5499](https://github.com/tendermint/tendermint/pull/5499) Fix "duplicate block enqueued by processor" panic (@melekes) +- [blockchain/v2] [\#5530](https://github.com/tendermint/tendermint/pull/5530) Fix out of order block processing panic (@melekes) +- [blockchain/v2] [\#5553](https://github.com/tendermint/tendermint/pull/5553) Make the removal of an already removed peer a noop (@melekes) +- [consensus] [\#4895](https://github.com/tendermint/tendermint/pull/4895) Cache the address of the validator to reduce querying a remote KMS (@joe-bowman) +- [consensus] [\#4970](https://github.com/tendermint/tendermint/pull/4970) Don't allow `LastCommitRound` to be negative (@cuonglm) +- [consensus] [\#5329](https://github.com/tendermint/tendermint/pull/5329) Fix wrong proposer schedule for validators returned by `InitChain` (@erikgrinaker) +- [docker] [\#5385](https://github.com/tendermint/tendermint/pull/5385) Fix incorrect `time_iota_ms` default setting causing block timestamp drift (@erikgrinaker) +- [evidence] [\#5170](https://github.com/tendermint/tendermint/pull/5170) Change ABCI evidence time to the time the infraction happened not the time the evidence was committed on the block (@cmwaters) +- [evidence] [\#5610](https://github.com/tendermint/tendermint/pull/5610) Make it possible for ABCI evidence to be formed from Tendermint evidence (@cmwaters) +- [libs/rand] [\#5215](https://github.com/tendermint/tendermint/pull/5215) Fix out-of-memory error on unexpected argument of Str() (@SadPencil) +- [light] [\#5307](https://github.com/tendermint/tendermint/pull/5307) Persist correct proposer priority in light client validator sets (@cmwaters) +- [p2p] [\#5136](https://github.com/tendermint/tendermint/pull/5136) Fix error for peer with the same ID but different IPs (@valardragon) +- [privval] [\#5638](https://github.com/tendermint/tendermint/pull/5638) Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) +- [proxy] [\#5078](https://github.com/tendermint/tendermint/pull/5078) Force Tendermint to exit when ABCI app crashes (@melekes) +- [rpc] [\#5660](https://github.com/tendermint/tendermint/pull/5660) Set `application/json` as the `Content-Type` header in RPC responses. (@alexanderbez) +- [store] [\#5382](https://github.com/tendermint/tendermint/pull/5382) Fix race conditions when loading/saving/pruning blocks (@erikgrinaker) ## v0.33.8 diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 73acf5426f..e814e4b925 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Unreleased Changes -## v0.34.0-rc7 +## v0.34.1 Special thanks to external contributors on this release: @@ -20,10 +20,9 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES -- [libs/math] \#5665 Make fractions unsigned integers (uint64) (@cmwaters) + ### IMPROVEMENTS ### BUG FIXES -- [rpc] \#5660 Set `application/json` as the `Content-Type` header in RPC responses. From 182fa3285128dfeb2b92690579be04f77ad5d0b1 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 19 Nov 2020 18:52:34 +0100 Subject: [PATCH 082/108] .goreleaser: build for windows --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index c1790bb699..494f7c2451 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -14,6 +14,7 @@ builds: goos: - darwin - linux + - windows goarch: - amd64 - arm64 From 15b70373cc1d3d18c16b0a27ade6668bdda7c9b2 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 24 Nov 2020 14:01:53 +0100 Subject: [PATCH 083/108] crypto: fix infinite recursion in Secp256k1 string formatting (#5707) (#5709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This caused stack overflow panics in E2E tests, e.g.: ``` 2020-11-24T02:37:17.6085640Z validator04 | runtime: goroutine stack exceeds 1000000000-byte limit 2020-11-24T02:37:17.6087818Z validator04 | runtime: sp=0xc0234b23c0 stack=[0xc0234b2000, 0xc0434b2000] 2020-11-24T02:37:17.6088920Z validator04 | fatal error: stack overflow 2020-11-24T02:37:17.6089776Z validator04 | 2020-11-24T02:37:17.6090569Z validator04 | runtime stack: 2020-11-24T02:37:17.6091677Z validator04 | runtime.throw(0x12dc476, 0xe) 2020-11-24T02:37:17.6093123Z validator04 | /usr/local/go/src/runtime/panic.go:1116 +0x72 2020-11-24T02:37:17.6094320Z validator04 | runtime.newstack() 2020-11-24T02:37:17.6095374Z validator04 | /usr/local/go/src/runtime/stack.go:1067 +0x78d 2020-11-24T02:37:17.6096381Z validator04 | runtime.morestack() 2020-11-24T02:37:17.6097657Z validator04 | /usr/local/go/src/runtime/asm_amd64.s:449 +0x8f 2020-11-24T02:37:17.6098505Z validator04 | 2020-11-24T02:37:17.6099328Z validator04 | goroutine 88 [running]: 2020-11-24T02:37:17.6100470Z validator04 | runtime.heapBitsSetType(0xc009565380, 0x20, 0x18, 0x1137e00) 2020-11-24T02:37:17.6101961Z validator04 | /usr/local/go/src/runtime/mbitmap.go:911 +0xaa5 fp=0xc0234b23d0 sp=0xc0234b23c8 pc=0x432625 2020-11-24T02:37:17.6103906Z validator04 | runtime.mallocgc(0x20, 0x1137e00, 0x117b601, 0x11e9240) 2020-11-24T02:37:17.6105179Z validator04 | /usr/local/go/src/runtime/malloc.go:1090 +0x5a5 fp=0xc0234b2470 sp=0xc0234b23d0 pc=0x428b25 2020-11-24T02:37:17.6106540Z validator04 | runtime.convTslice(0xc002743710, 0x21, 0x21, 0xc0234b24e8) 2020-11-24T02:37:17.6107861Z validator04 | /usr/local/go/src/runtime/iface.go:385 +0x59 fp=0xc0234b24a0 sp=0xc0234b2470 pc=0x426379 2020-11-24T02:37:17.6109315Z validator04 | github.com/tendermint/tendermint/crypto/secp256k1.PubKey.String(...) 2020-11-24T02:37:17.6151692Z validator04 | /src/tendermint/crypto/secp256k1/secp256k1.go:161 2020-11-24T02:37:17.6153872Z validator04 | github.com/tendermint/tendermint/crypto/secp256k1.(*PubKey).String(0xc009565360, 0x11e9240, 0xc009565360) 2020-11-24T02:37:17.6157421Z validator04 | :1 +0x65 fp=0xc0234b24f8 sp=0xc0234b24a0 pc=0x656965 2020-11-24T02:37:17.6159134Z validator04 | fmt.(*pp).handleMethods(0xc00956c680, 0x58, 0xc0234b2801) 2020-11-24T02:37:17.6161462Z validator04 | /usr/local/go/src/fmt/print.go:630 +0x30a fp=0xc0234b2768 sp=0xc0234b24f8 pc=0x518b8a [...] 2020-11-24T02:37:17.6649685Z validator04 | /usr/local/go/src/fmt/print.go:630 +0x30a fp=0xc0234b7f48 sp=0xc0234b7cd8 pc=0x518b8a 2020-11-24T02:37:17.6651177Z validator04 | created by github.com/tendermint/tendermint/node.startStateSync 2020-11-24T02:37:17.6652521Z validator04 | /src/tendermint/node/node.go:587 +0x150 ``` --- CHANGELOG_PENDING.md | 1 + crypto/secp256k1/secp256k1.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e814e4b925..1a8112718c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,3 +26,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES +- [crypto] \#5707 Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index fe5296900a..26926a9669 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -158,7 +158,7 @@ func (pubKey PubKey) Bytes() []byte { } func (pubKey PubKey) String() string { - return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:]) + return fmt.Sprintf("PubKeySecp256k1{%X}", []byte(pubKey)) } func (pubKey PubKey) Equals(other crypto.PubKey) bool { From 6c0d4070c246641d226a27f06a57f6c9786565c6 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 26 Nov 2020 16:12:25 +0100 Subject: [PATCH 084/108] ci: build for 32 bit, libs: fix overflow (#5700) --- .github/workflows/coverage.yml | 25 ++++++++++++++++++++++++- .github/workflows/tests.yml | 6 +++--- .goreleaser.yml | 3 ++- libs/math/fraction.go | 2 +- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index dbadb4d85f..ce8d47c509 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,6 +33,29 @@ jobs: name: "${{ github.sha }}-03" path: ./pkgs.txt.part.03 + build-linux: + name: Build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + goarch: ["arm", "amd64"] + timeout-minutes: 5 + steps: + - uses: actions/setup-go@v2 + with: + go-version: "^1.15.4" + - uses: actions/checkout@v2 + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - name: install + run: GOOS=linux GOARCH=${{ matrix.goarch }} make build + if: "env.GIT_DIFF != ''" + tests: runs-on: ubuntu-latest needs: split-test-files @@ -43,7 +66,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: '^1.15.4' + go-version: "^1.15.4" - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5a2eda03b4..0e8788b962 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,7 @@ jobs: test_abci_apps: runs-on: ubuntu-latest - needs: Build + needs: build timeout-minutes: 5 steps: - uses: actions/setup-go@v2 @@ -84,7 +84,7 @@ jobs: test_abci_cli: runs-on: ubuntu-latest - needs: Build + needs: build timeout-minutes: 5 steps: - uses: actions/setup-go@v2 @@ -115,7 +115,7 @@ jobs: test_apps: runs-on: ubuntu-latest - needs: Build + needs: build timeout-minutes: 5 steps: - uses: actions/setup-go@v2 diff --git a/.goreleaser.yml b/.goreleaser.yml index 494f7c2451..d1c61e4e31 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -8,7 +8,7 @@ builds: - id: "Tendermint" main: ./cmd/tendermint/main.go ldflags: - - -s -w -X github.com/tendermint/tendermint/version.TMCoreSemVer={{ .Version }} + - -s -w -X github.com/tendermint/tendermint/version.TMCoreSemVer={{ .Version }} env: - CGO_ENABLED=0 goos: @@ -17,6 +17,7 @@ builds: - windows goarch: - amd64 + - arm - arm64 checksum: diff --git a/libs/math/fraction.go b/libs/math/fraction.go index e38636485d..a8d2855924 100644 --- a/libs/math/fraction.go +++ b/libs/math/fraction.go @@ -42,7 +42,7 @@ func ParseFraction(f string) (Fraction, error) { return Fraction{}, errors.New("denominator can't be 0") } if numerator > math.MaxInt64 || denominator > math.MaxInt64 { - return Fraction{}, fmt.Errorf("value overflow, numerator and denominator must be less than %d", math.MaxInt64) + return Fraction{}, fmt.Errorf("value overflow, numerator and denominator must be less than %d", int64(math.MaxInt64)) } return Fraction{Numerator: numerator, Denominator: denominator}, nil } From ce144a1d712e9d69cfa3bde61fea40c5837c6bbd Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 24 Nov 2020 15:49:10 +0100 Subject: [PATCH 085/108] test: fix TestByzantinePrevoteEquivocation flake (#5710) This fixes spurious `TestByzantinePrevoteEquivocation` failures by extending the block range and time spent waiting for evidence. I've seen many runs where the evidence isn't committed until e.g. height 27. Haven't looked into _why_ this happens, but as long as the evidence is committed eventually and the test doesn't spuriously fail I'm (mostly) happy. WDYT @cmwaters? --- consensus/byzantine_test.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index fa945c310c..4e31df98dc 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -108,7 +108,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { eventBuses[i] = css[i].eventBus reactors[i].SetEventBus(eventBuses[i]) - blocksSub, err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock) + blocksSub, err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, 100) require.NoError(t, err) blocksSubs = append(blocksSubs, blocksSub) @@ -167,17 +167,17 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { wg := new(sync.WaitGroup) wg.Add(4) - for height := 1; height < 6; height++ { - for i := 0; i < nValidators; i++ { - go func(j int) { - msg := <-blocksSubs[j].Out() + for i := 0; i < nValidators; i++ { + go func(i int) { + for msg := range blocksSubs[i].Out() { block := msg.Data().(types.EventDataNewBlock).Block if len(block.Evidence.Evidence) != 0 { - evidenceFromEachValidator[j] = block.Evidence.Evidence[0] + evidenceFromEachValidator[i] = block.Evidence.Evidence[0] wg.Done() + return } - }(i) - } + } + }(i) } done := make(chan struct{}) @@ -186,7 +186,8 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { close(done) }() - pubkey, _ := bcs.privValidator.GetPubKey() + pubkey, err := bcs.privValidator.GetPubKey() + require.NoError(t, err) select { case <-done: @@ -198,11 +199,11 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { assert.Equal(t, prevoteHeight, ev.Height()) } } - case <-time.After(10 * time.Second): + case <-time.After(20 * time.Second): for i, reactor := range reactors { t.Logf("Consensus Reactor %d\n%v", i, reactor) } - t.Fatalf("Timed out waiting for all validators to commit first block") + t.Fatalf("Timed out waiting for validators to commit evidence") } } From 0d9606e1b43680468af6c999e4ce38aada552dd8 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 3 Dec 2020 23:12:08 +0100 Subject: [PATCH 086/108] reactors: omit incoming message bytes from reactor logs (#5743) After a reactor has failed to parse an incoming message, it shouldn't output the "bad" data into the logs, as that data is unfiltered and could have anything in it. (We also don't think this information is helpful to have in the logs anyways.) --- blockchain/v0/reactor.go | 2 +- blockchain/v2/reactor.go | 2 +- consensus/reactor.go | 2 +- mempool/reactor.go | 2 +- p2p/pex/pex_reactor.go | 2 +- statesync/reactor.go | 2 +- test/maverick/consensus/reactor.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/blockchain/v0/reactor.go b/blockchain/v0/reactor.go index 440a6090c2..01f32866a9 100644 --- a/blockchain/v0/reactor.go +++ b/blockchain/v0/reactor.go @@ -207,7 +207,7 @@ func (bcR *BlockchainReactor) respondToPeer(msg *bcproto.BlockRequest, func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := bc.DecodeMsg(msgBytes) if err != nil { - bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) bcR.Switch.StopPeerForError(src, err) return } diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index 05ed0b157f..410fb07c04 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -459,7 +459,7 @@ func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := bc.DecodeMsg(msgBytes) if err != nil { r.logger.Error("error decoding message", - "src", src.ID(), "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + "src", src.ID(), "chId", chID, "msg", msg, "err", err) _ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error())) return } diff --git a/consensus/reactor.go b/consensus/reactor.go index c4a2ef9fda..4ee0aae18a 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -228,7 +228,7 @@ func (conR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { - conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) conR.Switch.StopPeerForError(src, err) return } diff --git a/mempool/reactor.go b/mempool/reactor.go index b4f76b9e8d..d37aaaba4c 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -163,7 +163,7 @@ func (memR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := memR.decodeMsg(msgBytes) if err != nil { - memR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + memR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) memR.Switch.StopPeerForError(src, err) return } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 71b71b0544..ae18a32885 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -239,7 +239,7 @@ func (r *Reactor) logErrAddrBook(err error) { func (r *Reactor) Receive(chID byte, src Peer, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { - r.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + r.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) r.Switch.StopPeerForError(src, err) return } diff --git a/statesync/reactor.go b/statesync/reactor.go index 4f4310f840..5bbe7608a9 100644 --- a/statesync/reactor.go +++ b/statesync/reactor.go @@ -97,7 +97,7 @@ func (r *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { - r.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + r.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) r.Switch.StopPeerForError(src, err) return } diff --git a/test/maverick/consensus/reactor.go b/test/maverick/consensus/reactor.go index c82656115e..f4cf12c973 100644 --- a/test/maverick/consensus/reactor.go +++ b/test/maverick/consensus/reactor.go @@ -230,7 +230,7 @@ func (conR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { - conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) conR.Switch.StopPeerForError(src, err) return } From 2a4fd3804ca7624307b6bb3e41ed1e2979238201 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Thu, 3 Dec 2020 23:21:32 +0100 Subject: [PATCH 087/108] blockchain/v1: omit incoming message bytes from log --- blockchain/v1/reactor.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/blockchain/v1/reactor.go b/blockchain/v1/reactor.go index 780e43738c..c4c61ec516 100644 --- a/blockchain/v1/reactor.go +++ b/blockchain/v1/reactor.go @@ -253,8 +253,7 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { msg, err := bc.DecodeMsg(msgBytes) if err != nil { - bcR.Logger.Error("error decoding message", - "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) + bcR.Logger.Error("error decoding message", "src", src, "chId", chID, "err", err) _ = bcR.swReporter.Report(behaviour.BadMessage(src.ID(), err.Error())) return } From 7f06371915f1918182d55b7bb62f5dc1f758b8f8 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 4 Dec 2020 14:33:36 +0400 Subject: [PATCH 088/108] evidence: omit bytes field (#5745) Follow-up to https://github.com/tendermint/tendermint/pull/5743 --- evidence/reactor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evidence/reactor.go b/evidence/reactor.go index 421e4bc18a..a6586d33dd 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -69,7 +69,7 @@ func (evR *Reactor) AddPeer(peer p2p.Peer) { func (evR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { evis, err := decodeMsg(msgBytes) if err != nil { - evR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err, "bytes", msgBytes) + evR.Logger.Error("Error decoding message", "src", src, "chId", chID, "err", err) evR.Switch.StopPeerForError(src, err) return } From 9f0d71e81f68e420f1e7c09fcf31a8a0e66bcefa Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 11 Dec 2020 13:22:09 +0100 Subject: [PATCH 089/108] cmd: hyphen-case cli v0.34.1 (#5786) --- CHANGELOG_PENDING.md | 2 ++ cmd/tendermint/commands/gen_node_key.go | 8 +++++--- cmd/tendermint/commands/gen_validator.go | 8 +++++--- cmd/tendermint/commands/probe_upnp.go | 8 +++++--- cmd/tendermint/commands/replay.go | 6 ++++-- cmd/tendermint/commands/reset_priv_validator.go | 16 ++++++++++------ cmd/tendermint/commands/root.go | 14 +++++++++++--- cmd/tendermint/commands/run_node.go | 5 +++-- cmd/tendermint/commands/show_node_id.go | 8 +++++--- cmd/tendermint/commands/show_validator.go | 8 +++++--- 10 files changed, 55 insertions(+), 28 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 1a8112718c..2609637718 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -10,6 +10,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - CLI/RPC/Config + - [cli] \#5786 deprecate snake_case commands for hyphen-case (@cmwaters) + - Apps - P2P Protocol diff --git a/cmd/tendermint/commands/gen_node_key.go b/cmd/tendermint/commands/gen_node_key.go index e9f1f81032..dd4eee7403 100644 --- a/cmd/tendermint/commands/gen_node_key.go +++ b/cmd/tendermint/commands/gen_node_key.go @@ -12,9 +12,11 @@ import ( // GenNodeKeyCmd allows the generation of a node key. It prints node's ID to // the standard output. var GenNodeKeyCmd = &cobra.Command{ - Use: "gen_node_key", - Short: "Generate a node key for this node and print its ID", - RunE: genNodeKey, + Use: "gen-node-key", + Aliases: []string{"gen_node_key"}, + Short: "Generate a node key for this node and print its ID", + PreRun: deprecateSnakeCase, + RunE: genNodeKey, } func genNodeKey(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index 41020a5bee..3b3336fcdd 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -12,9 +12,11 @@ import ( // GenValidatorCmd allows the generation of a keypair for a // validator. var GenValidatorCmd = &cobra.Command{ - Use: "gen_validator", - Short: "Generate new validator keypair", - Run: genValidator, + Use: "gen-validator", + Aliases: []string{"gen_validator"}, + Short: "Generate new validator keypair", + PreRun: deprecateSnakeCase, + Run: genValidator, } func genValidator(cmd *cobra.Command, args []string) { diff --git a/cmd/tendermint/commands/probe_upnp.go b/cmd/tendermint/commands/probe_upnp.go index 9ac35fd508..7d0193c841 100644 --- a/cmd/tendermint/commands/probe_upnp.go +++ b/cmd/tendermint/commands/probe_upnp.go @@ -11,9 +11,11 @@ import ( // ProbeUpnpCmd adds capabilities to test the UPnP functionality. var ProbeUpnpCmd = &cobra.Command{ - Use: "probe_upnp", - Short: "Test UPnP functionality", - RunE: probeUpnp, + Use: "probe-upnp", + Aliases: []string{"probe_upnp"}, + Short: "Test UPnP functionality", + RunE: probeUpnp, + PreRun: deprecateSnakeCase, } func probeUpnp(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/replay.go b/cmd/tendermint/commands/replay.go index 303ccba6ba..5de8d0d3e8 100644 --- a/cmd/tendermint/commands/replay.go +++ b/cmd/tendermint/commands/replay.go @@ -18,9 +18,11 @@ var ReplayCmd = &cobra.Command{ // ReplayConsoleCmd allows replaying of messages from the WAL in a // console. var ReplayConsoleCmd = &cobra.Command{ - Use: "replay_console", - Short: "Replay messages from WAL in a console", + Use: "replay-console", + Aliases: []string{"replay_console"}, + Short: "Replay messages from WAL in a console", Run: func(cmd *cobra.Command, args []string) { consensus.RunReplayFile(config.BaseConfig, config.Consensus, true) }, + PreRun: deprecateSnakeCase, } diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index beefee5cbf..45b86d44c2 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -13,9 +13,11 @@ import ( // ResetAllCmd removes the database of this Tendermint core // instance. var ResetAllCmd = &cobra.Command{ - Use: "unsafe_reset_all", - Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", - Run: resetAll, + Use: "unsafe-reset-all", + Aliases: []string{"unsafe_reset_all"}, + Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", + Run: resetAll, + PreRun: deprecateSnakeCase, } var keepAddrBook bool @@ -26,9 +28,11 @@ func init() { // ResetPrivValidatorCmd resets the private validator files. var ResetPrivValidatorCmd = &cobra.Command{ - Use: "unsafe_reset_priv_validator", - Short: "(unsafe) Reset this node's validator to genesis state", - Run: resetPrivValidator, + Use: "unsafe-reset-priv-validator", + Aliases: []string{"unsafe_reset_priv_validator"}, + Short: "(unsafe) Reset this node's validator to genesis state", + Run: resetPrivValidator, + PreRun: deprecateSnakeCase, } // XXX: this is totally unsafe. diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 664f8ff149..b0e446c4c9 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "os" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -36,16 +37,16 @@ func ParseConfig() (*cfg.Config, error) { } conf.SetRoot(conf.RootDir) cfg.EnsureRoot(conf.RootDir) - if err = conf.ValidateBasic(); err != nil { + if err := conf.ValidateBasic(); err != nil { return nil, fmt.Errorf("error in config file: %v", err) } - return conf, err + return conf, nil } // RootCmd is the root command for Tendermint core. var RootCmd = &cobra.Command{ Use: "tendermint", - Short: "Tendermint Core (BFT Consensus) in Go", + Short: "BFT state machine replication for applications in any programming languages", PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { if cmd.Name() == VersionCmd.Name() { return nil @@ -68,3 +69,10 @@ var RootCmd = &cobra.Command{ return nil }, } + +// deprecateSnakeCase is a util function for 0.34.1. Should be removed in 0.35 +func deprecateSnakeCase(cmd *cobra.Command, args []string) { + if strings.Contains(cmd.CalledAs(), "_") { + fmt.Println("Deprecated: snake_case commands will be replaced by hyphen-case commands in the next major release") + } +} diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index af77553fa5..acc79920a0 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -99,8 +99,9 @@ func AddNodeFlags(cmd *cobra.Command) { // It can be used with a custom PrivValidator and in-process ABCI application. func NewRunNodeCmd(nodeProvider nm.Provider) *cobra.Command { cmd := &cobra.Command{ - Use: "node", - Short: "Run the tendermint node", + Use: "start", + Aliases: []string{"node", "run"}, + Short: "Run the tendermint node", RunE: func(cmd *cobra.Command, args []string) error { if err := checkGenesisHash(config); err != nil { return err diff --git a/cmd/tendermint/commands/show_node_id.go b/cmd/tendermint/commands/show_node_id.go index 6026e3e182..26c0974902 100644 --- a/cmd/tendermint/commands/show_node_id.go +++ b/cmd/tendermint/commands/show_node_id.go @@ -10,9 +10,11 @@ import ( // ShowNodeIDCmd dumps node's ID to the standard output. var ShowNodeIDCmd = &cobra.Command{ - Use: "show_node_id", - Short: "Show this node's ID", - RunE: showNodeID, + Use: "show-node-id", + Aliases: []string{"show_node_id"}, + Short: "Show this node's ID", + RunE: showNodeID, + PreRun: deprecateSnakeCase, } func showNodeID(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index e3980743a8..f29976bb3a 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -12,9 +12,11 @@ import ( // ShowValidatorCmd adds capabilities for showing the validator info. var ShowValidatorCmd = &cobra.Command{ - Use: "show_validator", - Short: "Show this node's validator info", - RunE: showValidator, + Use: "show-validator", + Aliases: []string{"show_validator"}, + Short: "Show this node's validator info", + RunE: showValidator, + PreRun: deprecateSnakeCase, } func showValidator(cmd *cobra.Command, args []string) error { From dc90cf60d5e751cb9f1ef4dc24903d235c942694 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 21 Dec 2020 15:25:14 +0400 Subject: [PATCH 090/108] mempool: introduce KeepInvalidTxsInCache config option (#5813) When set to true, an invalid transaction will be kept in the cache (this may help some applications to protect against spam). NOTE: this is a temporary config option. The more correct solution would be to add a TTL to each transaction (i.e. CheckTx may return a TTL in ResponseCheckTx). Closes: #5751 --- CHANGELOG_PENDING.md | 4 +- config/config.go | 4 ++ config/toml.go | 5 +++ docs/tendermint-core/configuration.md | 5 +++ mempool/clist_mempool.go | 10 +++-- mempool/clist_mempool_test.go | 57 +++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2609637718..395b13f479 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,10 +22,10 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES - - ### IMPROVEMENTS +- [mempool] \#5813 Add `keep-invalid-txs-in-cache` config option. When set to true, mempool will keep invalid transactions in the cache (@p4u) + ### BUG FIXES - [crypto] \#5707 Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) diff --git a/config/config.go b/config/config.go index 5e4eb9c776..882e59fb4f 100644 --- a/config/config.go +++ b/config/config.go @@ -656,6 +656,10 @@ type MempoolConfig struct { MaxTxsBytes int64 `mapstructure:"max_txs_bytes"` // Size of the cache (used to filter transactions we saw earlier) in transactions CacheSize int `mapstructure:"cache_size"` + // Do not remove invalid transactions from the cache (default: false) + // Set to true if it's not possible for any invalid transaction to become + // valid again in the future. + KeepInvalidTxsInCache bool `mapstructure:"keep-invalid-txs-in-cache"` // Maximum size of a single transaction // NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. MaxTxBytes int `mapstructure:"max_tx_bytes"` diff --git a/config/toml.go b/config/toml.go index 0dce3e6dce..62a17db9aa 100644 --- a/config/toml.go +++ b/config/toml.go @@ -329,6 +329,11 @@ max_txs_bytes = {{ .Mempool.MaxTxsBytes }} # Size of the cache (used to filter transactions we saw earlier) in transactions cache_size = {{ .Mempool.CacheSize }} +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = {{ .Mempool.KeepInvalidTxsInCache }} + # Maximum size of a single transaction. # NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. max_tx_bytes = {{ .Mempool.MaxTxBytes }} diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 1d79e9068b..16a600cc01 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -278,6 +278,11 @@ max_txs_bytes = 1073741824 # Size of the cache (used to filter transactions we saw earlier) in transactions cache_size = 10000 +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + # Maximum size of a single transaction. # NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. max_tx_bytes = 1048576 diff --git a/mempool/clist_mempool.go b/mempool/clist_mempool.go index 7b0c975222..e58a84df37 100644 --- a/mempool/clist_mempool.go +++ b/mempool/clist_mempool.go @@ -439,8 +439,10 @@ func (mem *CListMempool) resCbFirstTime( mem.logger.Info("Rejected bad transaction", "tx", txID(tx), "peerID", peerP2PID, "res", r, "err", postCheckErr) mem.metrics.FailedTxs.Add(1) - // remove from cache (it might be good later) - mem.cache.Remove(tx) + if !mem.config.KeepInvalidTxsInCache { + // remove from cache (it might be good later) + mem.cache.Remove(tx) + } } default: // ignore other messages @@ -472,7 +474,7 @@ func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) { // Tx became invalidated due to newly committed block. mem.logger.Info("Tx is no longer valid", "tx", txID(tx), "res", r, "err", postCheckErr) // NOTE: we remove tx from the cache because it might be good later - mem.removeTx(tx, mem.recheckCursor, true) + mem.removeTx(tx, mem.recheckCursor, !mem.config.KeepInvalidTxsInCache) } if mem.recheckCursor == mem.recheckEnd { mem.recheckCursor = nil @@ -586,7 +588,7 @@ func (mem *CListMempool) Update( if deliverTxResponses[i].Code == abci.CodeTypeOK { // Add valid committed tx to the cache (if missing). _ = mem.cache.Push(tx) - } else { + } else if !mem.config.KeepInvalidTxsInCache { // Allow invalid transactions to be resubmitted. mem.cache.Remove(tx) } diff --git a/mempool/clist_mempool_test.go b/mempool/clist_mempool_test.go index 46c161b657..a40ba69af8 100644 --- a/mempool/clist_mempool_test.go +++ b/mempool/clist_mempool_test.go @@ -216,6 +216,63 @@ func TestMempoolUpdate(t *testing.T) { } } +func TestMempool_KeepInvalidTxsInCache(t *testing.T) { + app := counter.NewApplication(true) + cc := proxy.NewLocalClientCreator(app) + wcfg := cfg.DefaultConfig() + wcfg.Mempool.KeepInvalidTxsInCache = true + mempool, cleanup := newMempoolWithAppAndConfig(cc, wcfg) + defer cleanup() + + // 1. An invalid transaction must remain in the cache after Update + { + a := make([]byte, 8) + binary.BigEndian.PutUint64(a, 0) + + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, 1) + + err := mempool.CheckTx(b, nil, TxInfo{}) + require.NoError(t, err) + + // simulate new block + _ = app.DeliverTx(abci.RequestDeliverTx{Tx: a}) + _ = app.DeliverTx(abci.RequestDeliverTx{Tx: b}) + err = mempool.Update(1, []types.Tx{a, b}, + []*abci.ResponseDeliverTx{{Code: abci.CodeTypeOK}, {Code: 2}}, nil, nil) + require.NoError(t, err) + + // a must be added to the cache + err = mempool.CheckTx(a, nil, TxInfo{}) + if assert.Error(t, err) { + assert.Equal(t, ErrTxInCache, err) + } + + // b must remain in the cache + err = mempool.CheckTx(b, nil, TxInfo{}) + if assert.Error(t, err) { + assert.Equal(t, ErrTxInCache, err) + } + } + + // 2. An invalid transaction must remain in the cache + { + a := make([]byte, 8) + binary.BigEndian.PutUint64(a, 0) + + // remove a from the cache to test (2) + mempool.cache.Remove(a) + + err := mempool.CheckTx(a, nil, TxInfo{}) + require.NoError(t, err) + + err = mempool.CheckTx(a, nil, TxInfo{}) + if assert.Error(t, err) { + assert.Equal(t, ErrTxInCache, err) + } + } +} + func TestTxsAvailable(t *testing.T) { app := kvstore.NewApplication() cc := proxy.NewLocalClientCreator(app) From dc101f2eff5640e90c62ad5eb3fdf013ac107db6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 21 Dec 2020 19:17:45 +0400 Subject: [PATCH 091/108] mempool: disable MaxBatchBytes (#5800) @p4u from vocdoni.io reported that the mempool might behave incorrectly under a high load. The consequences can range from pauses between blocks to the peers disconnecting from this node. My current theory is that the flowrate lib we're using to control flow (multiplex over a single TCP connection) was not designed w/ large blobs (1MB batch of txs) in mind. I've tried decreasing the Mempool reactor priority, but that did not have any visible effect. What actually worked is adding a time.Sleep into mempool.Reactor#broadcastTxRoutine after an each successful send == manual control flow of sort. As a temporary remedy (until the mempool package is refactored), the max-batch-bytes was disabled. Transactions will be sent one by one without batching Closes #5796 --- CHANGELOG_PENDING.md | 7 ++++ config/config.go | 16 +++------ config/toml.go | 1 + docs/tendermint-core/configuration.md | 1 + mempool/reactor.go | 50 +++++++-------------------- mempool/reactor_test.go | 15 +++----- 6 files changed, 31 insertions(+), 59 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 395b13f479..a5521561ee 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -4,6 +4,12 @@ Special thanks to external contributors on this release: +@p4u from vocdoni.io reported that the mempool might behave incorrectly under a +high load. The consequences can range from pauses between blocks to the peers +disconnecting from this node. As a temporary remedy (until the mempool package +is refactored), the `max-batch-bytes` was disabled. Transactions will be sent +one by one without batching. + Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). ### BREAKING CHANGES @@ -29,3 +35,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES - [crypto] \#5707 Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) +- [mempool] \#5800 Disable `max-batch-bytes` (@melekes) diff --git a/config/config.go b/config/config.go index 882e59fb4f..a356cf05ce 100644 --- a/config/config.go +++ b/config/config.go @@ -665,6 +665,7 @@ type MempoolConfig struct { MaxTxBytes int `mapstructure:"max_tx_bytes"` // Maximum size of a batch of transactions to send to a peer // Including space needed by encoding (one varint per transaction). + // XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 MaxBatchBytes int `mapstructure:"max_batch_bytes"` } @@ -676,11 +677,10 @@ func DefaultMempoolConfig() *MempoolConfig { WalPath: "", // Each signature verification takes .5ms, Size reduced until we implement // ABCI Recheck - Size: 5000, - MaxTxsBytes: 1024 * 1024 * 1024, // 1GB - CacheSize: 10000, - MaxTxBytes: 1024 * 1024, // 1MB - MaxBatchBytes: 10 * 1024 * 1024, // 10MB + Size: 5000, + MaxTxsBytes: 1024 * 1024 * 1024, // 1GB + CacheSize: 10000, + MaxTxBytes: 1024 * 1024, // 1MB } } @@ -716,12 +716,6 @@ func (cfg *MempoolConfig) ValidateBasic() error { if cfg.MaxTxBytes < 0 { return errors.New("max_tx_bytes can't be negative") } - if cfg.MaxBatchBytes < 0 { - return errors.New("max_batch_bytes can't be negative") - } - if cfg.MaxBatchBytes <= cfg.MaxTxBytes { - return errors.New("max_batch_bytes can't be less or equal to max_tx_bytes") - } return nil } diff --git a/config/toml.go b/config/toml.go index 62a17db9aa..f4bc4640b5 100644 --- a/config/toml.go +++ b/config/toml.go @@ -340,6 +340,7 @@ max_tx_bytes = {{ .Mempool.MaxTxBytes }} # Maximum size of a batch of transactions to send to a peer # Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 max_batch_bytes = {{ .Mempool.MaxBatchBytes }} ####################################################### diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 16a600cc01..17aabcaef8 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -289,6 +289,7 @@ max_tx_bytes = 1048576 # Maximum size of a batch of transactions to send to a peer # Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 max_batch_bytes = 10485760 ####################################################### diff --git a/mempool/reactor.go b/mempool/reactor.go index d37aaaba4c..0babb4b6c7 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -134,12 +134,18 @@ func (memR *Reactor) OnStart() error { // GetChannels implements Reactor by returning the list of channels for this // reactor. func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { - maxMsgSize := memR.config.MaxBatchBytes + largestTx := make([]byte, memR.config.MaxTxBytes) + batchMsg := protomem.Message{ + Sum: &protomem.Message_Txs{ + Txs: &protomem.Txs{Txs: [][]byte{largestTx}}, + }, + } + return []*p2p.ChannelDescriptor{ { ID: MempoolChannel, Priority: 5, - RecvMessageCapacity: maxMsgSize, + RecvMessageCapacity: batchMsg.Size(), }, } } @@ -232,20 +238,19 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { continue } - txs := memR.txs(next, peerID, peerState.GetHeight()) // WARNING: mutates next! + // NOTE: Transaction batching was disabled due to + // https://github.com/tendermint/tendermint/issues/5796 - // send txs - if len(txs) > 0 { + if _, ok := memTx.senders.Load(peerID); !ok { msg := protomem.Message{ Sum: &protomem.Message_Txs{ - Txs: &protomem.Txs{Txs: txs}, + Txs: &protomem.Txs{Txs: [][]byte{memTx.tx}}, }, } bz, err := msg.Marshal() if err != nil { panic(err) } - memR.Logger.Debug("Sending N txs to peer", "N", len(txs), "peer", peer) success := peer.Send(MempoolChannel, bz) if !success { time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) @@ -265,37 +270,6 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { } } -// txs iterates over the transaction list and builds a batch of txs. next is -// included. -// WARNING: mutates next! -func (memR *Reactor) txs(next *clist.CElement, peerID uint16, peerHeight int64) [][]byte { - batch := make([][]byte, 0) - - for { - memTx := next.Value.(*mempoolTx) - - if _, ok := memTx.senders.Load(peerID); !ok { - // If current batch + this tx size is greater than max => return. - batchMsg := protomem.Message{ - Sum: &protomem.Message_Txs{ - Txs: &protomem.Txs{Txs: append(batch, memTx.tx)}, - }, - } - if batchMsg.Size() > memR.config.MaxBatchBytes { - return batch - } - - batch = append(batch, memTx.tx) - } - - n := next.Next() - if n == nil { - return batch - } - next = n - } -} - //----------------------------------------------------------------------------- // Messages diff --git a/mempool/reactor_test.go b/mempool/reactor_test.go index d9e67d166c..bc51bfd9b3 100644 --- a/mempool/reactor_test.go +++ b/mempool/reactor_test.go @@ -149,9 +149,8 @@ func TestReactorNoBroadcastToSender(t *testing.T) { ensureNoTxs(t, reactors[peerID], 100*time.Millisecond) } -func TestReactor_MaxBatchBytes(t *testing.T) { +func TestReactor_MaxTxBytes(t *testing.T) { config := cfg.TestConfig() - config.Mempool.MaxBatchBytes = 1024 const N = 2 reactors := makeAndConnectReactors(config, N) @@ -168,9 +167,9 @@ func TestReactor_MaxBatchBytes(t *testing.T) { } } - // Broadcast a tx, which has the max size (minus proto overhead) + // Broadcast a tx, which has the max size // => ensure it's received by the second reactor. - tx1 := tmrand.Bytes(1018) + tx1 := tmrand.Bytes(config.Mempool.MaxTxBytes) err := reactors[0].mempool.CheckTx(tx1, nil, TxInfo{SenderID: UnknownPeerID}) require.NoError(t, err) waitForTxsOnReactors(t, []types.Tx{tx1}, reactors) @@ -180,13 +179,9 @@ func TestReactor_MaxBatchBytes(t *testing.T) { // Broadcast a tx, which is beyond the max size // => ensure it's not sent - tx2 := tmrand.Bytes(1020) + tx2 := tmrand.Bytes(config.Mempool.MaxTxBytes + 1) err = reactors[0].mempool.CheckTx(tx2, nil, TxInfo{SenderID: UnknownPeerID}) - require.NoError(t, err) - ensureNoTxs(t, reactors[1], 100*time.Millisecond) - // => ensure the second reactor did not disconnect from us - out, in, _ := reactors[1].Switch.NumPeers() - assert.Equal(t, 1, out+in) + require.Error(t, err) } func TestBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { From 829a9e1de7cc6bd5ec6a3d1b99bf66e578b74c1a Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 21 Dec 2020 09:39:07 -0800 Subject: [PATCH 092/108] docs/tutorials: specify 0.34 (#5823) # Description Specify 0.34 for tutorials. Closes: #5735 --- docs/tutorials/go-built-in.md | 21 +++++++++++++++++++-- docs/tutorials/go.md | 24 ++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/tutorials/go-built-in.md b/docs/tutorials/go-built-in.md index 3ca19329c5..b516cfb595 100644 --- a/docs/tutorials/go-built-in.md +++ b/docs/tutorials/go-built-in.md @@ -586,10 +586,27 @@ dependency management. ```bash go mod init github.com/me/example -go build +go get github.com/tendermint/tendermint/@v0.34.0 ``` -This should build the binary. +After running the above commands you will see two generated files, go.mod and go.sum. The go.mod file should look similar to: + +```go +module github.com/me/example + +go 1.15 + +require ( + github.com/dgraph-io/badger v1.6.2 + github.com/tendermint/tendermint v0.34.0 +) +``` + +Finally, we will build our binary: + +```sh +go build +``` To create a default configuration, nodeKey and private validator files, let's execute `tendermint init`. But before we do that, we will need to install diff --git a/docs/tutorials/go.md b/docs/tutorials/go.md index 70be81f073..28a015b995 100644 --- a/docs/tutorials/go.md +++ b/docs/tutorials/go.md @@ -43,7 +43,7 @@ Verify that you have the latest version of Go installed: ```bash $ go version -go version go1.14.x darwin/amd64 +go version go1.15.x darwin/amd64 ``` Make sure you have `$GOPATH` environment variable set: @@ -442,12 +442,28 @@ We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for dependency management. ```bash -export GO111MODULE=on go mod init github.com/me/example -go build +go get github.com/tendermint/tendermint/@v0.34.0 +``` + +After running the above commands you will see two generated files, go.mod and go.sum. The go.mod file should look similar to: + +```go +module github.com/me/example + +go 1.15 + +require ( + github.com/dgraph-io/badger v1.6.2 + github.com/tendermint/tendermint v0.34.0 +) ``` -This should build the binary. +Finally, we will build our binary: + +```sh +go build +``` To create a default configuration, nodeKey and private validator files, let's execute `tendermint init`. But before we do that, we will need to install From b1328db07f7f4743d64f08338db3078ebc0b1d8b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 23 Dec 2020 21:05:14 +0400 Subject: [PATCH 093/108] modify Reactor priorities (#5826) (#5830) blockchain/vX reactor priority was decreased because during the normal operation (i.e. when the node is not fast syncing) blockchain priority can't be the same as consensus reactor priority. Otherwise, it's theoretically possible to slow down consensus by constantly requesting blocks from the node. NOTE: ideally blockchain/vX reactor priority would be dynamic. e.g. when the node is fast syncing, the priority is 10 (max), but when it's done fast syncing - the priority gets decreased to 5 (only to serve blocks for other nodes). But it's not possible now, therefore I decided to focus on the normal operation (priority = 5). evidence and consensus critical messages are more important than the mempool ones, hence priorities are bumped by 1 (from 5 to 6). statesync reactor priority was changed from 1 to 5 to be the same as blockchain/vX priority. Refs https://github.com/tendermint/tendermint/issues/5816 --- blockchain/v0/reactor.go | 2 +- blockchain/v2/reactor.go | 2 +- consensus/reactor.go | 4 ++-- evidence/reactor.go | 2 +- statesync/reactor.go | 2 +- test/maverick/consensus/reactor.go | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/blockchain/v0/reactor.go b/blockchain/v0/reactor.go index 01f32866a9..ac2ef8ece6 100644 --- a/blockchain/v0/reactor.go +++ b/blockchain/v0/reactor.go @@ -140,7 +140,7 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: BlockchainChannel, - Priority: 10, + Priority: 5, SendQueueCapacity: 1000, RecvBufferCapacity: 50 * 4096, RecvMessageCapacity: bc.MaxMsgSize, diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index 410fb07c04..f7af248082 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -555,7 +555,7 @@ func (r *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: BlockchainChannel, - Priority: 10, + Priority: 5, SendQueueCapacity: 2000, RecvBufferCapacity: 50 * 4096, RecvMessageCapacity: bc.MaxMsgSize, diff --git a/consensus/reactor.go b/consensus/reactor.go index 4ee0aae18a..0303a2afd0 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -142,7 +142,7 @@ func (conR *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: StateChannel, - Priority: 5, + Priority: 6, SendQueueCapacity: 100, RecvMessageCapacity: maxMsgSize, }, @@ -156,7 +156,7 @@ func (conR *Reactor) GetChannels() []*p2p.ChannelDescriptor { }, { ID: VoteChannel, - Priority: 5, + Priority: 7, SendQueueCapacity: 100, RecvBufferCapacity: 100 * 100, RecvMessageCapacity: maxMsgSize, diff --git a/evidence/reactor.go b/evidence/reactor.go index a6586d33dd..d5b953e243 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -53,7 +53,7 @@ func (evR *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: EvidenceChannel, - Priority: 5, + Priority: 6, RecvMessageCapacity: maxMsgSize, }, } diff --git a/statesync/reactor.go b/statesync/reactor.go index 5bbe7608a9..62e1289d18 100644 --- a/statesync/reactor.go +++ b/statesync/reactor.go @@ -53,7 +53,7 @@ func (r *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: SnapshotChannel, - Priority: 3, + Priority: 5, SendQueueCapacity: 10, RecvMessageCapacity: snapshotMsgSize, }, diff --git a/test/maverick/consensus/reactor.go b/test/maverick/consensus/reactor.go index f4cf12c973..e623aebee1 100644 --- a/test/maverick/consensus/reactor.go +++ b/test/maverick/consensus/reactor.go @@ -144,7 +144,7 @@ func (conR *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { ID: StateChannel, - Priority: 5, + Priority: 6, SendQueueCapacity: 100, RecvMessageCapacity: maxMsgSize, }, @@ -158,7 +158,7 @@ func (conR *Reactor) GetChannels() []*p2p.ChannelDescriptor { }, { ID: VoteChannel, - Priority: 5, + Priority: 7, SendQueueCapacity: 100, RecvBufferCapacity: 100 * 100, RecvMessageCapacity: maxMsgSize, From 17ce2ccc92b459ac2e60aa52092028acadd237c7 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Wed, 23 Dec 2020 18:45:04 +0100 Subject: [PATCH 094/108] CHANGELOG: prepare 0.34.1-rc1 (#5832) --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++---- CHANGELOG_PENDING.md | 15 +-------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe534485a2..1171319ef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## v0.34.1-rc1 + +*December 23, 2020* + +Special thanks to external contributors on this release: + +@p4u from vocdoni.io reported that the mempool might behave incorrectly under a +high load. The consequences can range from pauses between blocks to the peers +disconnecting from this node. As a temporary remedy (until the mempool package +is refactored), the `max-batch-bytes` was disabled. Transactions will be sent +one by one without batching. + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### BREAKING CHANGES + +- CLI/RPC/Config + - [cli] [\#5786](https://github.com/tendermint/tendermint/issues/5786) deprecate snake_case commands for hyphen-case (@cmwaters) + +### IMPROVEMENTS + +- [mempool] [\#5813](https://github.com/tendermint/tendermint/issues/5813) Add `keep-invalid-txs-in-cache` config option. When set to true, mempool will keep invalid transactions in the cache (@p4u) + +### BUG FIXES + +- [crypto] [\#5707](https://github.com/tendermint/tendermint/issues/5707) Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) +- [mempool] [\#5800](https://github.com/tendermint/tendermint/issues/5800) Disable `max-batch-bytes` (@melekes) + ## v0.34.0 *November 19, 2020* @@ -38,14 +66,14 @@ And as always, friendly reminder, that we have a [bug bounty program](https://ha - [blockchain] [\#4637](https://github.com/tendermint/tendermint/pull/4637) Migrate blockchain reactor(s) to Protobuf encoding (@marbar3778) - [evidence] [\#4949](https://github.com/tendermint/tendermint/pull/4949) Migrate evidence reactor to Protobuf encoding (@marbar3778) - [mempool] [\#4940](https://github.com/tendermint/tendermint/pull/4940) Migrate mempool from to Protobuf encoding (@marbar3778) - - [mempool] [\#5321](https://github.com/tendermint/tendermint/pull/5321) Batch transactions when broadcasting them to peers (@melekes) + - [mempool] [\#5321](https://github.com/tendermint/tendermint/pull/5321) Batch transactions when broadcasting them to peers (@melekes) - `MaxBatchBytes` new config setting defines the max size of one batch. - [p2p/pex] [\#4973](https://github.com/tendermint/tendermint/pull/4973) Migrate `p2p/pex` reactor to Protobuf encoding (@marbar3778) - [statesync] [\#4943](https://github.com/tendermint/tendermint/pull/4943) Migrate state sync reactor to Protobuf encoding (@marbar3778) - Blockchain Protocol - - [evidence] [\#4725](https://github.com/tendermint/tendermint/pull/4725) Remove `Pubkey` from `DuplicateVoteEvidence` (@marbar3778) + - [evidence] [\#4725](https://github.com/tendermint/tendermint/pull/4725) Remove `Pubkey` from `DuplicateVoteEvidence` (@marbar3778) - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) Cap evidence to a maximum number of bytes (supercedes [\#4780](https://github.com/tendermint/tendermint/pull/4780)) (@cmwaters) - [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) Header hashes are no longer empty for empty inputs, notably `DataHash`, `EvidenceHash`, and `LastResultsHash` (@erikgrinaker) - [state] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Include `GasWanted` and `GasUsed` into `LastResultsHash` (@melekes) @@ -104,7 +132,7 @@ And as always, friendly reminder, that we have a [bug bounty program](https://ha - [types] [\#4852](https://github.com/tendermint/tendermint/pull/4852) Vote & Proposal `SignBytes` is now func `VoteSignBytes` & `ProposalSignBytes` (@marbar3778) - [types] [\#4798](https://github.com/tendermint/tendermint/pull/4798) Simplify `VerifyCommitTrusting` func + remove extra validation (@melekes) - [types] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Remove `ABCIResult` (@melekes) - - [types] [\#5029](https://github.com/tendermint/tendermint/pull/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency (@marbar3778) + - [types] [\#5029](https://github.com/tendermint/tendermint/pull/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) `Total` in `Parts` & `PartSetHeader` has been changed from a `int` to a `uint32` (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Vote: `ValidatorIndex` & `Round` are now `int32` (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Proposal: `POLRound` & `Round` are now `int32` (@marbar3778) @@ -142,7 +170,7 @@ And as always, friendly reminder, that we have a [bug bounty program](https://ha - [evidence] [\#4722](https://github.com/tendermint/tendermint/pull/4722) Consolidate evidence store and pool types to improve evidence DB (@cmwaters) - [evidence] [\#4839](https://github.com/tendermint/tendermint/pull/4839) Reject duplicate evidence from being proposed (@cmwaters) - [evidence] [\#5219](https://github.com/tendermint/tendermint/pull/5219) Change the source of evidence time to block time (@cmwaters) -- [libs] [\#5126](https://github.com/tendermint/tendermint/pull/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking (@marbar3778) +- [libs] [\#5126](https://github.com/tendermint/tendermint/pull/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking (@marbar3778) - [light] [\#4935](https://github.com/tendermint/tendermint/pull/4935) Fetch and compare a new header with witnesses in parallel (@melekes) - [light] [\#4929](https://github.com/tendermint/tendermint/pull/4929) Compare header with witnesses only when doing bisection (@melekes) - [light] [\#4916](https://github.com/tendermint/tendermint/pull/4916) Validate basic for inbound validator sets and headers before further processing them (@cmwaters) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a5521561ee..6c53ab6d2a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,23 +1,15 @@ # Unreleased Changes -## v0.34.1 +## v0.34.2 Special thanks to external contributors on this release: -@p4u from vocdoni.io reported that the mempool might behave incorrectly under a -high load. The consequences can range from pauses between blocks to the peers -disconnecting from this node. As a temporary remedy (until the mempool package -is refactored), the `max-batch-bytes` was disabled. Transactions will be sent -one by one without batching. - Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). ### BREAKING CHANGES - CLI/RPC/Config - - [cli] \#5786 deprecate snake_case commands for hyphen-case (@cmwaters) - - Apps - P2P Protocol @@ -30,9 +22,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS -- [mempool] \#5813 Add `keep-invalid-txs-in-cache` config option. When set to true, mempool will keep invalid transactions in the cache (@p4u) - ### BUG FIXES - -- [crypto] \#5707 Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) -- [mempool] \#5800 Disable `max-batch-bytes` (@melekes) From 13833cba9e3de4dc76c80a1ace3a51b000c0aab2 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 6 Jan 2021 16:10:28 +0100 Subject: [PATCH 095/108] p2p: fix MConnection inbound traffic statistics and rate limiting (#5868) (#5870) Fixes #5866. Inbound traffic monitoring (and by extension inbound rate limiting) was inadvertently removed in 660e72a. --- CHANGELOG_PENDING.md | 3 ++ libs/protoio/io.go | 25 ++++++++------ libs/protoio/io_test.go | 65 +++++++++++++++++++++++++---------- libs/protoio/reader.go | 48 ++++++++++++++++++-------- p2p/conn/connection.go | 3 +- p2p/conn/connection_test.go | 14 ++++---- p2p/conn/secret_connection.go | 4 +-- p2p/transport.go | 2 +- p2p/transport_test.go | 2 +- privval/signer_endpoint.go | 2 +- 10 files changed, 110 insertions(+), 58 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6c53ab6d2a..0bada29ab3 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -15,6 +15,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API + - [libs/protoio] Return number of bytes read in `Reader.ReadMsg()` (@erikgrinaker) - Blockchain Protocol @@ -23,3 +24,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS ### BUG FIXES + +- [p2p] \#5868 Fix inbound traffic statistics and rate limiting in `MConnection` (@erikgrinaker) diff --git a/libs/protoio/io.go b/libs/protoio/io.go index 91acbb71be..b12a1d4822 100644 --- a/libs/protoio/io.go +++ b/libs/protoio/io.go @@ -46,7 +46,7 @@ type WriteCloser interface { } type Reader interface { - ReadMsg(msg proto.Message) error + ReadMsg(msg proto.Message) (int, error) } type ReadCloser interface { @@ -72,25 +72,28 @@ func getSize(v interface{}) (int, bool) { } } -// byteReader wraps an io.Reader and implements io.ByteReader. Reading one byte at a -// time is extremely slow, but this is what Amino did already, and the caller can -// wrap the reader in bufio.Reader if appropriate. +// byteReader wraps an io.Reader and implements io.ByteReader, required by +// binary.ReadUvarint(). Reading one byte at a time is extremely slow, but this +// is what Amino did previously anyway, and the caller can wrap the underlying +// reader in a bufio.Reader if appropriate. type byteReader struct { - io.Reader - bytes []byte + reader io.Reader + buf []byte + bytesRead int // keeps track of bytes read via ReadByte() } func newByteReader(r io.Reader) *byteReader { return &byteReader{ - Reader: r, - bytes: make([]byte, 1), + reader: r, + buf: make([]byte, 1), } } func (r *byteReader) ReadByte() (byte, error) { - _, err := r.Read(r.bytes) + n, err := r.reader.Read(r.buf) + r.bytesRead += n if err != nil { - return 0, err + return 0x00, err } - return r.bytes[0], nil + return r.buf[0], nil } diff --git a/libs/protoio/io_test.go b/libs/protoio/io_test.go index f4556b31f0..e6e51d0512 100644 --- a/libs/protoio/io_test.go +++ b/libs/protoio/io_test.go @@ -39,6 +39,7 @@ import ( "github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/test" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/protoio" ) @@ -47,6 +48,7 @@ func iotest(writer protoio.WriteCloser, reader protoio.ReadCloser) error { varint := make([]byte, binary.MaxVarintLen64) size := 1000 msgs := make([]*test.NinOptNative, size) + lens := make([]int, size) r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := range msgs { msgs[i] = test.NewPopulatedNinOptNative(r, true) @@ -71,6 +73,7 @@ func iotest(writer protoio.WriteCloser, reader protoio.ReadCloser) error { if n != len(bz)+visize { return fmt.Errorf("WriteMsg() wrote %v bytes, expected %v", n, len(bz)+visize) // nolint } + lens[i] = n } if err := writer.Close(); err != nil { return err @@ -78,11 +81,13 @@ func iotest(writer protoio.WriteCloser, reader protoio.ReadCloser) error { i := 0 for { msg := &test.NinOptNative{} - if err := reader.ReadMsg(msg); err != nil { + if n, err := reader.ReadMsg(msg); err != nil { if err == io.EOF { break } return err + } else if n != lens[i] { + return fmt.Errorf("read %v bytes, expected %v", n, lens[i]) } if err := msg.VerboseEqual(msgs[i]); err != nil { return err @@ -116,21 +121,17 @@ func TestVarintNormal(t *testing.T) { buf := newBuffer() writer := protoio.NewDelimitedWriter(buf) reader := protoio.NewDelimitedReader(buf, 1024*1024) - if err := iotest(writer, reader); err != nil { - t.Error(err) - } - if !buf.closed { - t.Fatalf("did not close buffer") - } + err := iotest(writer, reader) + require.NoError(t, err) + require.True(t, buf.closed, "did not close buffer") } func TestVarintNoClose(t *testing.T) { buf := bytes.NewBuffer(nil) writer := protoio.NewDelimitedWriter(buf) reader := protoio.NewDelimitedReader(buf, 1024*1024) - if err := iotest(writer, reader); err != nil { - t.Error(err) - } + err := iotest(writer, reader) + require.NoError(t, err) } // issue 32 @@ -138,11 +139,8 @@ func TestVarintMaxSize(t *testing.T) { buf := newBuffer() writer := protoio.NewDelimitedWriter(buf) reader := protoio.NewDelimitedReader(buf, 20) - if err := iotest(writer, reader); err == nil { - t.Error(err) - } else { - t.Logf("%s", err) - } + err := iotest(writer, reader) + require.Error(t, err) } func TestVarintError(t *testing.T) { @@ -150,8 +148,37 @@ func TestVarintError(t *testing.T) { buf.Write([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}) reader := protoio.NewDelimitedReader(buf, 1024*1024) msg := &test.NinOptNative{} - err := reader.ReadMsg(msg) - if err == nil { - t.Fatalf("Expected error") - } + n, err := reader.ReadMsg(msg) + require.Error(t, err) + require.Equal(t, 10, n) +} + +func TestVarintTruncated(t *testing.T) { + buf := newBuffer() + buf.Write([]byte{0xff, 0xff}) + reader := protoio.NewDelimitedReader(buf, 1024*1024) + msg := &test.NinOptNative{} + n, err := reader.ReadMsg(msg) + require.Error(t, err) + require.Equal(t, 2, n) +} + +func TestShort(t *testing.T) { + buf := newBuffer() + + varintBuf := make([]byte, binary.MaxVarintLen64) + varintLen := binary.PutUvarint(varintBuf, 100) + _, err := buf.Write(varintBuf[:varintLen]) + require.NoError(t, err) + + bz, err := proto.Marshal(&test.NinOptNative{Field15: []byte{0x01, 0x02, 0x03}}) + require.NoError(t, err) + buf.Write(bz) + + reader := protoio.NewDelimitedReader(buf, 1024*1024) + require.NoError(t, err) + msg := &test.NinOptNative{} + n, err := reader.ReadMsg(msg) + require.Error(t, err) + require.Equal(t, varintLen+len(bz), n) } diff --git a/libs/protoio/reader.go b/libs/protoio/reader.go index 15a84899f0..66eed707cc 100644 --- a/libs/protoio/reader.go +++ b/libs/protoio/reader.go @@ -39,41 +39,58 @@ import ( "github.com/gogo/protobuf/proto" ) -// NewDelimitedReader reads varint-delimited Protobuf messages from a reader. Unlike the gogoproto -// NewDelimitedReader, this does not buffer the reader, which may cause poor performance but is -// necessary when only reading single messages (e.g. in the p2p package). +// NewDelimitedReader reads varint-delimited Protobuf messages from a reader. +// Unlike the gogoproto NewDelimitedReader, this does not buffer the reader, +// which may cause poor performance but is necessary when only reading single +// messages (e.g. in the p2p package). It also returns the number of bytes +// read, which is necessary for the p2p package. func NewDelimitedReader(r io.Reader, maxSize int) ReadCloser { var closer io.Closer if c, ok := r.(io.Closer); ok { closer = c } - return &varintReader{newByteReader(r), nil, maxSize, closer} + return &varintReader{r, nil, maxSize, closer} } type varintReader struct { - r *byteReader + r io.Reader buf []byte maxSize int closer io.Closer } -func (r *varintReader) ReadMsg(msg proto.Message) error { - length64, err := binary.ReadUvarint(newByteReader(r.r)) +func (r *varintReader) ReadMsg(msg proto.Message) (int, error) { + // ReadUvarint needs an io.ByteReader, and we also need to keep track of the + // number of bytes read, so we use our own byteReader. This can't be + // buffered, so the caller should pass a buffered io.Reader to avoid poor + // performance. + byteReader := newByteReader(r.r) + l, err := binary.ReadUvarint(byteReader) + n := byteReader.bytesRead if err != nil { - return err + return n, err } - length := int(length64) - if length < 0 || length > r.maxSize { - return fmt.Errorf("message exceeds max size (%v > %v)", length, r.maxSize) + + // Make sure length doesn't overflow the native int size (e.g. 32-bit), + // and that the returned sum of n+length doesn't overflow either. + length := int(l) + if l >= uint64(^uint(0)>>1) || length < 0 || n+length < 0 { + return n, fmt.Errorf("invalid out-of-range message length %v", l) + } + if length > r.maxSize { + return n, fmt.Errorf("message exceeds max size (%v > %v)", length, r.maxSize) } + if len(r.buf) < length { r.buf = make([]byte, length) } buf := r.buf[:length] - if _, err := io.ReadFull(r.r, buf); err != nil { - return err + nr, err := io.ReadFull(r.r, buf) + n += nr + if err != nil { + return n, err } - return proto.Unmarshal(buf, msg) + return n, proto.Unmarshal(buf, msg) } func (r *varintReader) Close() error { @@ -84,5 +101,6 @@ func (r *varintReader) Close() error { } func UnmarshalDelimited(data []byte, msg proto.Message) error { - return NewDelimitedReader(bytes.NewReader(data), len(data)).ReadMsg(msg) + _, err := NewDelimitedReader(bytes.NewReader(data), len(data)).ReadMsg(msg) + return err } diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index b5290116d3..65495074f5 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -583,7 +583,8 @@ FOR_LOOP: // Read packet type var packet tmp2p.Packet - err := protoReader.ReadMsg(&packet) + _n, err := protoReader.ReadMsg(&packet) + c.recvMonitor.Update(_n) if err != nil { // stopServices was invoked and we are shutting down // receiving is excpected to fail since we will close the connection diff --git a/p2p/conn/connection_test.go b/p2p/conn/connection_test.go index a189e8b890..6dadfb486a 100644 --- a/p2p/conn/connection_test.go +++ b/p2p/conn/connection_test.go @@ -185,7 +185,7 @@ func TestMConnectionPongTimeoutResultsInError(t *testing.T) { go func() { // read ping var pkt tmp2p.Packet - err := protoio.NewDelimitedReader(server, maxPingPongPacketSize).ReadMsg(&pkt) + _, err := protoio.NewDelimitedReader(server, maxPingPongPacketSize).ReadMsg(&pkt) require.NoError(t, err) serverGotPing <- struct{}{} }() @@ -236,7 +236,7 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { go func() { // read ping (one byte) var packet tmp2p.Packet - err := protoio.NewDelimitedReader(server, maxPingPongPacketSize).ReadMsg(&packet) + _, err := protoio.NewDelimitedReader(server, maxPingPongPacketSize).ReadMsg(&packet) require.NoError(t, err) serverGotPing <- struct{}{} @@ -284,19 +284,19 @@ func TestMConnectionMultiplePings(t *testing.T) { _, err = protoWriter.WriteMsg(mustWrapPacket(&tmp2p.PacketPing{})) require.NoError(t, err) - err = protoReader.ReadMsg(&pkt) + _, err = protoReader.ReadMsg(&pkt) require.NoError(t, err) _, err = protoWriter.WriteMsg(mustWrapPacket(&tmp2p.PacketPing{})) require.NoError(t, err) - err = protoReader.ReadMsg(&pkt) + _, err = protoReader.ReadMsg(&pkt) require.NoError(t, err) _, err = protoWriter.WriteMsg(mustWrapPacket(&tmp2p.PacketPing{})) require.NoError(t, err) - err = protoReader.ReadMsg(&pkt) + _, err = protoReader.ReadMsg(&pkt) require.NoError(t, err) assert.True(t, mconn.IsRunning()) @@ -331,7 +331,7 @@ func TestMConnectionPingPongs(t *testing.T) { var pkt tmp2p.PacketPing // read ping - err = protoReader.ReadMsg(&pkt) + _, err = protoReader.ReadMsg(&pkt) require.NoError(t, err) serverGotPing <- struct{}{} @@ -342,7 +342,7 @@ func TestMConnectionPingPongs(t *testing.T) { time.Sleep(mconn.config.PingInterval) // read ping - err = protoReader.ReadMsg(&pkt) + _, err = protoReader.ReadMsg(&pkt) require.NoError(t, err) serverGotPing <- struct{}{} diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index 0412247722..febb975f3e 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -313,7 +313,7 @@ func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byt }, func(_ int) (val interface{}, abort bool, err error) { var bytes gogotypes.BytesValue - err = protoio.NewDelimitedReader(conn, 1024*1024).ReadMsg(&bytes) + _, err = protoio.NewDelimitedReader(conn, 1024*1024).ReadMsg(&bytes) if err != nil { return nil, true, err // abort } @@ -419,7 +419,7 @@ func shareAuthSignature(sc io.ReadWriter, pubKey crypto.PubKey, signature []byte }, func(_ int) (val interface{}, abort bool, err error) { var pba tmp2p.AuthSigMessage - err = protoio.NewDelimitedReader(sc, 1024*1024).ReadMsg(&pba) + _, err = protoio.NewDelimitedReader(sc, 1024*1024).ReadMsg(&pba) if err != nil { return nil, true, err // abort } diff --git a/p2p/transport.go b/p2p/transport.go index e597ac0a14..8448888cb6 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -537,7 +537,7 @@ func handshake( }(errc, c) go func(errc chan<- error, c net.Conn) { protoReader := protoio.NewDelimitedReader(c, MaxNodeInfoSize()) - err := protoReader.ReadMsg(&pbpeerNodeInfo) + _, err := protoReader.ReadMsg(&pbpeerNodeInfo) errc <- err }(errc, c) diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 9b81dcd63c..77db5a3bd0 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -594,7 +594,7 @@ func TestTransportHandshake(t *testing.T) { ) protoReader := protoio.NewDelimitedReader(c, MaxNodeInfoSize()) - err := protoReader.ReadMsg(&pbni) + _, err := protoReader.ReadMsg(&pbni) if err != nil { t.Error(err) } diff --git a/privval/signer_endpoint.go b/privval/signer_endpoint.go index eb2ed442fb..e1594438cc 100644 --- a/privval/signer_endpoint.go +++ b/privval/signer_endpoint.go @@ -96,7 +96,7 @@ func (se *signerEndpoint) ReadMessage() (msg privvalproto.Message, err error) { } const maxRemoteSignerMsgSize = 1024 * 10 protoReader := protoio.NewDelimitedReader(se.conn, maxRemoteSignerMsgSize) - err = protoReader.ReadMsg(&msg) + _, err = protoReader.ReadMsg(&msg) if _, ok := err.(timeoutError); ok { if err != nil { err = fmt.Errorf("%v: %w", err, ErrReadTimeout) From 2924d41f8b51f1ae4d08338e5e0d039fb4c76a13 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Wed, 6 Jan 2021 16:32:43 +0100 Subject: [PATCH 096/108] changelog: update changelog for v0.34.1 (#5872) --- CHANGELOG.md | 8 ++++++-- CHANGELOG_PENDING.md | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1171319ef3..a2bb52b2cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## v0.34.1-rc1 +## v0.34.1 -*December 23, 2020* +*January 6, 2021* Special thanks to external contributors on this release: @@ -19,6 +19,9 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - CLI/RPC/Config - [cli] [\#5786](https://github.com/tendermint/tendermint/issues/5786) deprecate snake_case commands for hyphen-case (@cmwaters) +- Go API + - [libs/protoio] [\#5868](https://github.com/tendermint/tendermint/issues/5868) Return number of bytes read in `Reader.ReadMsg()` (@erikgrinaker) + ### IMPROVEMENTS - [mempool] [\#5813](https://github.com/tendermint/tendermint/issues/5813) Add `keep-invalid-txs-in-cache` config option. When set to true, mempool will keep invalid transactions in the cache (@p4u) @@ -27,6 +30,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [crypto] [\#5707](https://github.com/tendermint/tendermint/issues/5707) Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker) - [mempool] [\#5800](https://github.com/tendermint/tendermint/issues/5800) Disable `max-batch-bytes` (@melekes) +- [p2p] [\#5868](https://github.com/tendermint/tendermint/issues/5868) Fix inbound traffic statistics and rate limiting in `MConnection` (@erikgrinaker) ## v0.34.0 diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0bada29ab3..b2480c1cfd 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -15,7 +15,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API - - [libs/protoio] Return number of bytes read in `Reader.ReadMsg()` (@erikgrinaker) - Blockchain Protocol @@ -25,4 +24,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES -- [p2p] \#5868 Fix inbound traffic statistics and rate limiting in `MConnection` (@erikgrinaker) + From 4246000a8c152e3680aa033897cfa5133828ba0b Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 4 Jan 2021 14:38:42 +0100 Subject: [PATCH 097/108] tools/tm-signer-harness: fix listener leak in newTestHarnessListener() (#5850) Fixes #5837. --- tools/tm-signer-harness/internal/test_harness.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/tm-signer-harness/internal/test_harness.go b/tools/tm-signer-harness/internal/test_harness.go index 28a20caedd..33ae537fe0 100644 --- a/tools/tm-signer-harness/internal/test_harness.go +++ b/tools/tm-signer-harness/internal/test_harness.go @@ -374,6 +374,7 @@ func newTestHarnessListener(logger log.Logger, cfg TestHarnessConfig) (*privval. logger.Info("Resolved TCP address for listener", "addr", tcpLn.Addr()) svln = tcpLn default: + _ = ln.Close() logger.Error("Unsupported protocol (must be unix:// or tcp://)", "proto", proto) return nil, newTestHarnessError(ErrInvalidParameters, nil, fmt.Sprintf("Unsupported protocol: %s", proto)) } From c2b5f8bc4aa5c8ec53db40febca1b467669ad816 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 4 Jan 2021 14:56:01 +0100 Subject: [PATCH 098/108] abci/grpc: fix invalid mutex handling in StopForError() (#5849) Fixes #5840. --- abci/client/grpc_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 0f3aa75a50..b375c6cc9c 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -129,11 +129,11 @@ func (cli *grpcClient) OnStop() { } func (cli *grpcClient) StopForError(err error) { - cli.mtx.Lock() if !cli.IsRunning() { return } + cli.mtx.Lock() if cli.err == nil { cli.err = err } From fca7c6449abe6c2a1f693d826cba680ea15793dc Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 4 Jan 2021 15:30:38 +0100 Subject: [PATCH 099/108] libs/os: EnsureDir now returns IO errors and checks file type (#5852) Fixes #5839. --- CHANGELOG_PENDING.md | 1 + libs/os/os.go | 17 ++++++++++++----- libs/os/os_test.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b2480c1cfd..13b74e0e1e 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -15,6 +15,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API + - [libs/os] `EnsureDir` now propagates IO errors and checks the file type (@erikgrinaker) - Blockchain Protocol diff --git a/libs/os/os.go b/libs/os/os.go index ea24a42f60..8776e3a0ce 100644 --- a/libs/os/os.go +++ b/libs/os/os.go @@ -43,12 +43,19 @@ func Exit(s string) { os.Exit(1) } +// EnsureDir ensures the given directory exists, creating it if necessary. +// Errors if the path already exists as a non-directory. func EnsureDir(dir string, mode os.FileMode) error { - if _, err := os.Stat(dir); os.IsNotExist(err) { - err := os.MkdirAll(dir, mode) - if err != nil { - return fmt.Errorf("could not create directory %v: %w", dir, err) - } + info, err := os.Stat(dir) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to stat %q: %w", dir, err) + } + if info != nil && !info.IsDir() { + return fmt.Errorf("path %q already exists as a non-directory", dir) + } + err = os.MkdirAll(dir, mode) + if err != nil { + return fmt.Errorf("could not create directory %q: %w", dir, err) } return nil } diff --git a/libs/os/os_test.go b/libs/os/os_test.go index 9c80f1f5a3..c912465c5c 100644 --- a/libs/os/os_test.go +++ b/libs/os/os_test.go @@ -5,7 +5,10 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "testing" + + "github.com/stretchr/testify/require" ) func TestCopyFile(t *testing.T) { @@ -35,3 +38,36 @@ func TestCopyFile(t *testing.T) { } os.Remove(copyfile) } + +func TestEnsureDir(t *testing.T) { + tmp, err := ioutil.TempDir("", "ensure-dir") + require.NoError(t, err) + defer os.RemoveAll(tmp) + + // Should be possible to create a new directory. + err = EnsureDir(filepath.Join(tmp, "dir"), 0755) + require.NoError(t, err) + require.DirExists(t, filepath.Join(tmp, "dir")) + + // Should succeed on existing directory. + err = EnsureDir(filepath.Join(tmp, "dir"), 0755) + require.NoError(t, err) + + // Should fail on file. + err = ioutil.WriteFile(filepath.Join(tmp, "file"), []byte{}, 0644) + require.NoError(t, err) + err = EnsureDir(filepath.Join(tmp, "file"), 0755) + require.Error(t, err) + + // Should allow symlink to dir. + err = os.Symlink(filepath.Join(tmp, "dir"), filepath.Join(tmp, "linkdir")) + require.NoError(t, err) + err = EnsureDir(filepath.Join(tmp, "linkdir"), 0755) + require.NoError(t, err) + + // Should error on symlink to file. + err = os.Symlink(filepath.Join(tmp, "file"), filepath.Join(tmp, "linkfile")) + require.NoError(t, err) + err = EnsureDir(filepath.Join(tmp, "linkfile"), 0755) + require.Error(t, err) +} From 15eed81f12be1dcc3e7e2ba74097752ff40692c4 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 5 Jan 2021 11:44:03 +0100 Subject: [PATCH 100/108] test/consensus: improve WaitGroup handling in Byzantine tests (#5861) Fixes #5845. --- consensus/byzantine_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 4e31df98dc..a82242db29 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -166,14 +166,14 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { evidenceFromEachValidator := make([]types.Evidence, nValidators) wg := new(sync.WaitGroup) - wg.Add(4) for i := 0; i < nValidators; i++ { + wg.Add(1) go func(i int) { + defer wg.Done() for msg := range blocksSubs[i].Out() { block := msg.Data().(types.EventDataNewBlock).Block if len(block.Evidence.Evidence) != 0 { evidenceFromEachValidator[i] = block.Evidence.Evidence[0] - wg.Done() return } } @@ -340,8 +340,8 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { // wait till everyone makes the first new block // (one of them already has) wg := new(sync.WaitGroup) - wg.Add(2) for i := 1; i < N-1; i++ { + wg.Add(1) go func(j int) { <-blocksSubs[j].Out() wg.Done() From 2eba38051a83bad0a8fdd96736f42b95931025f7 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 5 Jan 2021 15:35:20 +0100 Subject: [PATCH 101/108] blockchain/v2: fix missing mutex unlock (#5862) Fixes #5843. --- blockchain/v2/reactor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index f7af248082..9dea749d70 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -500,12 +500,12 @@ func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { r.mtx.RUnlock() case *bcproto.BlockResponse: - r.mtx.RLock() bi, err := types.BlockFromProto(msg.Block) if err != nil { r.logger.Error("error transitioning block from protobuf", "err", err) return } + r.mtx.RLock() if r.events != nil { r.events <- bcBlockResponse{ peerID: src.ID(), From 3185bb8b2208090eee34e25e5fba9236d85f4dac Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 5 Jan 2021 15:45:24 +0100 Subject: [PATCH 102/108] blockchain/v0: stop tickers on poolRoutine exit (#5860) Fixes #5841. --- blockchain/v0/reactor.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/blockchain/v0/reactor.go b/blockchain/v0/reactor.go index ac2ef8ece6..81fad4932e 100644 --- a/blockchain/v0/reactor.go +++ b/blockchain/v0/reactor.go @@ -256,8 +256,13 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) func (bcR *BlockchainReactor) poolRoutine(stateSynced bool) { trySyncTicker := time.NewTicker(trySyncIntervalMS * time.Millisecond) + defer trySyncTicker.Stop() + statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second) + defer statusUpdateTicker.Stop() + switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second) + defer switchToConsensusTicker.Stop() blocksSynced := uint64(0) From 5d637659904c50f5f284fb0a42440bd46b195488 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 6 Jan 2021 16:27:35 +0100 Subject: [PATCH 103/108] os: simplify EnsureDir() (#5871) #5852 fixed an issue with error propagation in `os.EnsureDir()`. However, this function is basically identical to `os.MkdirAll()`, and can be replaced entirely with a call to it. We keep the function for backwards compatibility. --- libs/os/os.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libs/os/os.go b/libs/os/os.go index 8776e3a0ce..fb55e01715 100644 --- a/libs/os/os.go +++ b/libs/os/os.go @@ -46,14 +46,7 @@ func Exit(s string) { // EnsureDir ensures the given directory exists, creating it if necessary. // Errors if the path already exists as a non-directory. func EnsureDir(dir string, mode os.FileMode) error { - info, err := os.Stat(dir) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to stat %q: %w", dir, err) - } - if info != nil && !info.IsDir() { - return fmt.Errorf("path %q already exists as a non-directory", dir) - } - err = os.MkdirAll(dir, mode) + err := os.MkdirAll(dir, mode) if err != nil { return fmt.Errorf("could not create directory %q: %w", dir, err) } From ad552b2bb1174237ca8c927a859b0560ec06a1c1 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 12 Jan 2021 12:50:49 +0100 Subject: [PATCH 104/108] evidence: buffer evidence from consensus (#5890) --- CHANGELOG_PENDING.md | 3 ++- evidence/pool.go | 63 ++++++++++++++++++++++++++++++------------- evidence/pool_test.go | 31 ++++++++++++++++----- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 13b74e0e1e..171050ea7f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,4 +25,5 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES - +- [evidence] \#5890 Add a buffer to evidence from consensus to avoid broadcasting and proposing evidence before the +height of such an evidence has finished (@cmwaters) diff --git a/evidence/pool.go b/evidence/pool.go index 77dbf1a395..f574902902 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -41,6 +41,10 @@ type Pool struct { mtx sync.Mutex // latest state state sm.State + // evidence from consensus if buffered to this slice, awaiting until the next height + // before being flushed to the pool. This prevents broadcasting and proposing of + // evidence before the height with which the evidence happened is finished. + consensusBuffer []types.Evidence pruningHeight int64 pruningTime time.Time @@ -56,12 +60,13 @@ func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, } pool := &Pool{ - stateDB: stateDB, - blockStore: blockStore, - state: state, - logger: log.NewNopLogger(), - evidenceStore: evidenceDB, - evidenceList: clist.New(), + stateDB: stateDB, + blockStore: blockStore, + state: state, + logger: log.NewNopLogger(), + evidenceStore: evidenceDB, + evidenceList: clist.New(), + consensusBuffer: make([]types.Evidence, 0), } // if pending evidence already in db, in event of prior failure, then check for expiration, @@ -104,9 +109,20 @@ func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) { evpool.logger.Info("Updating evidence pool", "last_block_height", state.LastBlockHeight, "last_block_time", state.LastBlockTime) - // update the state - evpool.updateState(state) + evpool.logger.Info( + "updating evidence pool", + "last_block_height", state.LastBlockHeight, + "last_block_time", state.LastBlockTime, + ) + evpool.mtx.Lock() + // flush awaiting evidence from consensus into pool + evpool.flushConsensusBuffer() + // update state + evpool.state = state + evpool.mtx.Unlock() + + // move committed evidence out from the pending pool and into the committed pool evpool.markEvidenceAsCommitted(ev) // prune pending evidence when it has expired. This also updates when the next evidence will expire @@ -163,13 +179,13 @@ func (evpool *Pool) AddEvidenceFromConsensus(ev types.Evidence) error { return nil } - if err := evpool.addPendingEvidence(ev); err != nil { - return fmt.Errorf("can't add evidence to pending list: %w", err) - } - // add evidence to be gossiped with peers - evpool.evidenceList.PushBack(ev) - - evpool.logger.Info("Verified new evidence of byzantine behavior", "evidence", ev) + // add evidence to a buffer which will pass the evidence to the pool at the following height. + // This avoids the issue of some nodes verifying and proposing evidence at a height where the + // block hasn't been committed on cause others to potentially fail. + evpool.mtx.Lock() + defer evpool.mtx.Unlock() + evpool.consensusBuffer = append(evpool.consensusBuffer, ev) + evpool.logger.Info("received new evidence of byzantine behavior from consensus", "evidence", ev) return nil } @@ -491,10 +507,19 @@ func (evpool *Pool) removeEvidenceFromList( } } -func (evpool *Pool) updateState(state sm.State) { - evpool.mtx.Lock() - defer evpool.mtx.Unlock() - evpool.state = state +// flushConsensusBuffer moves the evidence produced from consensus into the evidence pool +// and list so that it can be broadcasted and proposed +func (evpool *Pool) flushConsensusBuffer() { + for _, ev := range evpool.consensusBuffer { + if err := evpool.addPendingEvidence(ev); err != nil { + evpool.logger.Error("failed to flush evidence from consensus buffer to pending list: %w", err) + continue + } + + evpool.evidenceList.PushBack(ev) + } + // reset consensus buffer + evpool.consensusBuffer = make([]types.Evidence, 0) } func bytesToEv(evBytes []byte) (types.Evidence, error) { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 046f4efc53..7d842003c5 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -148,16 +148,33 @@ func TestAddEvidenceFromConsensus(t *testing.T) { var height int64 = 10 pool, val := defaultTestPool(height) ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, val, evidenceChainID) - err := pool.AddEvidenceFromConsensus(ev) - assert.NoError(t, err) + + require.NoError(t, pool.AddEvidenceFromConsensus(ev)) + + // evidence from consensus should not be added immediately but reside in the consensus buffer + evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes) + require.Empty(t, evList) + require.Zero(t, evSize) + next := pool.EvidenceFront() - assert.Equal(t, ev, next.Value.(types.Evidence)) + require.Nil(t, next) + + // move to next height and update state and evidence pool + state := pool.State() + state.LastBlockHeight++ + pool.Update(state, []types.Evidence{}) + + // should be able to retrieve evidence from pool + evList, _ = pool.PendingEvidence(defaultEvidenceMaxBytes) + require.Equal(t, []types.Evidence{ev}, evList) // shouldn't be able to submit the same evidence twice - err = pool.AddEvidenceFromConsensus(ev) - assert.NoError(t, err) - evs, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) - assert.Equal(t, 1, len(evs)) + require.NoError(t, pool.AddEvidenceFromConsensus(ev)) + state = pool.State() + state.LastBlockHeight++ + pool.Update(state, []types.Evidence{}) + evList2, _ := pool.PendingEvidence(defaultEvidenceMaxBytes) + require.Equal(t, evList, evList2) } func TestEvidencePoolUpdate(t *testing.T) { From cf3a72098827c88350aec89687ef15f376affcef Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 12 Jan 2021 14:41:16 +0100 Subject: [PATCH 105/108] state sync: correctly set last consensus params height (#5889) --- CHANGELOG_PENDING.md | 1 + state/store.go | 3 ++- statesync/stateprovider.go | 11 ++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 171050ea7f..c3a1bc4f4c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -27,3 +27,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [evidence] \#5890 Add a buffer to evidence from consensus to avoid broadcasting and proposing evidence before the height of such an evidence has finished (@cmwaters) +- [statesync] \#5889 Set `LastHeightConsensusParamsChanged` when bootstrapping Tendermint state (@cmwaters) diff --git a/state/store.go b/state/store.go index 5bd2d9fc75..550c2cbffa 100644 --- a/state/store.go +++ b/state/store.go @@ -205,7 +205,8 @@ func (store dbStore) Bootstrap(state State) error { return err } - if err := store.saveConsensusParamsInfo(height, height, state.ConsensusParams); err != nil { + if err := store.saveConsensusParamsInfo(height, + state.LastHeightConsensusParamsChanged, state.ConsensusParams); err != nil { return err } diff --git a/statesync/stateprovider.go b/statesync/stateprovider.go index 4b1c75e328..c89052dbd4 100644 --- a/statesync/stateprovider.go +++ b/statesync/stateprovider.go @@ -150,7 +150,7 @@ func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm if err != nil { return sm.State{}, err } - curLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now()) + currentLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now()) if err != nil { return sm.State{}, err } @@ -162,10 +162,10 @@ func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm state.LastBlockHeight = lastLightBlock.Height state.LastBlockTime = lastLightBlock.Time state.LastBlockID = lastLightBlock.Commit.BlockID - state.AppHash = curLightBlock.AppHash - state.LastResultsHash = curLightBlock.LastResultsHash + state.AppHash = currentLightBlock.AppHash + state.LastResultsHash = currentLightBlock.LastResultsHash state.LastValidators = lastLightBlock.ValidatorSet - state.Validators = curLightBlock.ValidatorSet + state.Validators = currentLightBlock.ValidatorSet state.NextValidators = nextLightBlock.ValidatorSet state.LastHeightValidatorsChanged = nextLightBlock.Height @@ -179,12 +179,13 @@ func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err) } rpcclient := lightrpc.NewClient(primaryRPC, s.lc) - result, err := rpcclient.ConsensusParams(ctx, &nextLightBlock.Height) + result, err := rpcclient.ConsensusParams(ctx, ¤tLightBlock.Height) if err != nil { return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w", nextLightBlock.Height, err) } state.ConsensusParams = result.ConsensusParams + state.LastHeightConsensusParamsChanged = currentLightBlock.Height return state, nil } From 23c8a7a93d4349bf99d2534ab9a1ccd5aa62d26a Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 12 Jan 2021 17:42:26 +0100 Subject: [PATCH 106/108] changelog: prepare 0.34.2 release (#5894) --- CHANGELOG.md | 21 +++++++++++++++++++++ CHANGELOG_PENDING.md | 7 +------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2bb52b2cd..29bb4cf31a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## v0.34.2 + +*January 12, 2021* + +This release fixes a substantial bug in evidence handling where evidence could +sometimes be broadcast before the block containing that evidence was fully committed, +resulting in some nodes panicking when trying to verify said evidence. + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### BREAKING CHANGES + +- Go API + - [libs/os] [\#5871](https://github.com/tendermint/tendermint/issues/5871) `EnsureDir` now propagates IO errors and checks the file type (@erikgrinaker) + +### BUG FIXES + +- [evidence] [\#5890](https://github.com/tendermint/tendermint/pull/5890) Add a buffer to evidence from consensus to avoid broadcasting and proposing evidence before the + height of such an evidence has finished (@cmwaters) +- [statesync] [\#5889](https://github.com/tendermint/tendermint/issues/5889) Set `LastHeightConsensusParamsChanged` when bootstrapping Tendermint state (@cmwaters) + ## v0.34.1 *January 6, 2021* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index c3a1bc4f4c..c1c9320cca 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Unreleased Changes -## v0.34.2 +## v0.34.3 Special thanks to external contributors on this release: @@ -15,7 +15,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - P2P Protocol - Go API - - [libs/os] `EnsureDir` now propagates IO errors and checks the file type (@erikgrinaker) - Blockchain Protocol @@ -24,7 +23,3 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS ### BUG FIXES - -- [evidence] \#5890 Add a buffer to evidence from consensus to avoid broadcasting and proposing evidence before the -height of such an evidence has finished (@cmwaters) -- [statesync] \#5889 Set `LastHeightConsensusParamsChanged` when bootstrapping Tendermint state (@cmwaters) From 6a7a431ba54936309c75d3fb29bc5b63fe9975f6 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 6 Nov 2020 14:27:48 +0100 Subject: [PATCH 107/108] remove misbehaviors from e2e generator (#5629) --- test/e2e/generator/generate.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index f509ed1fd2..598d7ea15a 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -44,8 +44,11 @@ var ( "restart": 0.1, } nodeMisbehaviors = weightedChoice{ - misbehaviorOption{"double-prevote"}: 1, - misbehaviorOption{}: 9, + // FIXME evidence disabled due to node panicing when not + // having sufficient block history to process evidence. + // https://github.com/tendermint/tendermint/issues/5617 + // misbehaviorOption{"double-prevote"}: 1, + misbehaviorOption{}: 9, } ) From bdbe4a7cd7f5de46e69b95d66facad371f5c59fb Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 4 Jan 2021 16:19:07 +0100 Subject: [PATCH 108/108] test/e2e: disable abci/grpc and blockchain/v2 due to flake (#5854) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- test/e2e/generator/generate.go | 14 ++++++-------- test/e2e/networks/ci.toml | 13 ++++++------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 598d7ea15a..cba1c207d0 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -25,14 +25,12 @@ var ( } // The following specify randomly chosen values for testnet nodes. - nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} - nodeABCIProtocols = uniformChoice{"unix", "tcp", "grpc", "builtin"} + nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} + // FIXME: grpc disabled due to https://github.com/tendermint/tendermint/issues/5439 + nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin"} // "grpc" nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} - // FIXME v1 disabled due to https://github.com/tendermint/tendermint/issues/5444 - // FIXME v2 disabled due to: - // https://github.com/tendermint/tendermint/issues/5513 - // https://github.com/tendermint/tendermint/issues/5541 - nodeFastSyncs = uniformChoice{"", "v0"} // "v1", "v2" + // FIXME: v2 disabled due to flake + nodeFastSyncs = uniformChoice{"", "v0"} // "v2" nodeStateSyncs = uniformChoice{false, true} nodePersistIntervals = uniformChoice{0, 1, 5} nodeSnapshotIntervals = uniformChoice{0, 3} @@ -44,7 +42,7 @@ var ( "restart": 0.1, } nodeMisbehaviors = weightedChoice{ - // FIXME evidence disabled due to node panicing when not + // FIXME: evidence disabled due to node panicing when not // having sufficient block history to process evidence. // https://github.com/tendermint/tendermint/issues/5617 // misbehaviorOption{"double-prevote"}: 1, diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 67e06bea19..d0dc9db6db 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -49,7 +49,8 @@ perturb = ["restart"] [node.validator03] seeds = ["seed01"] database = "badgerdb" -abci_protocol = "grpc" +# FIXME: should be grpc, disabled due to https://github.com/tendermint/tendermint/issues/5439 +#abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 retain_blocks = 3 @@ -66,15 +67,15 @@ start_at = 1005 # Becomes part of the validator set at 1010 seeds = ["seed02"] database = "cleveldb" fast_sync = "v0" -abci_protocol = "grpc" +# FIXME: should be grpc, disabled due to https://github.com/tendermint/tendermint/issues/5439 +#abci_protocol = "grpc" privval_protocol = "tcp" perturb = ["kill", "pause", "disconnect", "restart"] [node.full01] start_at = 1010 mode = "full" -# FIXME Should use v1, but it won't catch up since some nodes don't have all blocks -# https://github.com/tendermint/tendermint/issues/5444 +# FIXME: should be v2, disabled due to flake fast_sync = "v0" persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] retain_blocks = 1 @@ -83,9 +84,7 @@ perturb = ["restart"] [node.full02] start_at = 1015 mode = "full" -# FIXME Should use v2, but it has concurrency bugs causing panics or halts -# https://github.com/tendermint/tendermint/issues/5513 -# https://github.com/tendermint/tendermint/issues/5541 +# FIXME: should be v2, disabled due to flake fast_sync = "v0" state_sync = true seeds = ["seed01"]