Browse Source

Merge remote-tracking branch 'origin/develop' into dev/adr_secp_signatures

pull/2091/head
ValarDragon 6 years ago
parent
commit
b3a3c8a192
37 changed files with 1544 additions and 678 deletions
  1. +1
    -1
      .circleci/config.yml
  2. +3
    -0
      CHANGELOG_PENDING.md
  3. +1
    -1
      README.md
  4. +1
    -1
      abci/README.md
  5. +600
    -347
      abci/types/types.pb.go
  6. +9
    -3
      abci/types/types.proto
  7. +124
    -0
      abci/types/typespb_test.go
  8. +0
    -4
      blockchain/reactor.go
  9. +9
    -6
      consensus/common_test.go
  10. +0
    -4
      consensus/reactor.go
  11. +96
    -21
      consensus/reactor_test.go
  12. +12
    -5
      docs/app-dev/abci-spec.md
  13. +8
    -8
      docs/app-dev/app-development.md
  14. +80
    -0
      docs/architecture/adr-015-crypto-encoding.md
  15. +22
    -16
      docs/spec/blockchain/blockchain.md
  16. +3
    -2
      docs/spec/blockchain/state.md
  17. +0
    -5
      evidence/reactor.go
  18. +7
    -2
      libs/autofile/autofile_test.go
  19. +7
    -8
      libs/autofile/group_test.go
  20. +0
    -55
      libs/common/os.go
  21. +0
    -42
      libs/common/os_test.go
  22. +11
    -0
      libs/common/random.go
  23. +128
    -0
      libs/common/tempfile.go
  24. +138
    -0
      libs/common/tempfile_test.go
  25. +3
    -2
      libs/db/backend_test.go
  26. +3
    -3
      libs/db/common_test.go
  27. +68
    -0
      p2p/conn/secret_connection_test.go
  28. +32
    -0
      p2p/conn/testdata/TestDeriveSecretsAndChallengeGolden.golden
  29. +19
    -13
      p2p/pex/pex_reactor.go
  30. +101
    -28
      p2p/pex/pex_reactor_test.go
  31. +19
    -13
      privval/priv_validator_test.go
  32. +2
    -43
      rpc/core/status.go
  33. +0
    -39
      rpc/core/status_test.go
  34. +7
    -4
      state/execution.go
  35. +1
    -1
      state/execution_test.go
  36. +8
    -1
      types/protobuf.go
  37. +21
    -0
      types/protobuf_test.go

+ 1
- 1
.circleci/config.yml View File

@ -147,7 +147,7 @@ jobs:
for pkg in $(go list github.com/tendermint/tendermint/... | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
GOCACHE=off go test -v -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
GOCACHE=off go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done
- persist_to_workspace:
root: /tmp/workspace


+ 3
- 0
CHANGELOG_PENDING.md View File

@ -7,6 +7,7 @@ BREAKING CHANGES:
- [tools] Removed `make ensure_deps` in favor of `make get_vendor_deps`
- [p2p] Remove salsa and ripemd primitives, in favor of using chacha as a stream cipher, and hkdf
- [abci] Changed time format from int64 to google.protobuf.Timestamp
- [abci] Changed Validators to LastCommitInfo in RequestBeginBlock
FEATURES:
- [tools] Added `make check_dep`
@ -19,7 +20,9 @@ IMPROVEMENTS:
- only process one block at a time to avoid starving
- [crypto] Switch hkdfchachapoly1305 to xchachapoly1305
- [common] bit array functions which take in another parameter are now thread safe
- [p2p] begin connecting to peers as soon a seed node provides them to you ([#2093](https://github.com/tendermint/tendermint/issues/2093))
BUG FIXES:
- [common] Safely handle cases where atomic write files already exist [#2109](https://github.com/tendermint/tendermint/issues/2109)
- [privval] fix a deadline for accepting new connections in socket private
validator.

+ 1
- 1
README.md View File

@ -22,7 +22,7 @@ develop | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/deve
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language -
and securely replicates it on many machines.
For protocol details, see [the specification](/docs/spec).
For protocol details, see [the specification](/docs/spec). For a consensus proof and detailed protocol analysis checkout our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
## A Note on Production Readiness


+ 1
- 1
abci/README.md View File

@ -29,7 +29,7 @@ The two guides to focus on are the `Application Development Guide` and `Using AB
To compile the protobuf file, run:
```
make protoc
cd $GOPATH/src/github.com/tendermint/tendermint/; make protoc_abci
```
See `protoc --help` and [the Protocol Buffers site](https://developers.google.com/protocol-buffers)


+ 600
- 347
abci/types/types.pb.go
File diff suppressed because it is too large
View File


+ 9
- 3
abci/types/types.proto View File

@ -71,10 +71,11 @@ message RequestQuery {
bool prove = 4;
}
// NOTE: validators here have empty pubkeys.
message RequestBeginBlock {
bytes hash = 1;
Header header = 2 [(gogoproto.nullable)=false];
repeated SigningValidator validators = 3 [(gogoproto.nullable)=false];
LastCommitInfo last_commit_info = 3 [(gogoproto.nullable)=false];
repeated Evidence byzantine_validators = 4 [(gogoproto.nullable)=false];
}
@ -203,14 +204,14 @@ message ConsensusParams {
BlockGossip block_gossip = 3;
}
// BlockSize contain limits on the block size.
// BlockSize contains limits on the block size.
message BlockSize {
int32 max_bytes = 1;
int32 max_txs = 2;
int64 max_gas = 3;
}
// TxSize contain limits on the tx size.
// TxSize contains limits on the tx size.
message TxSize {
int32 max_bytes = 1;
int64 max_gas = 2;
@ -223,6 +224,11 @@ message BlockGossip {
int32 block_part_size_bytes = 1;
}
message LastCommitInfo {
int32 commit_round = 1;
repeated SigningValidator validators = 2 [(gogoproto.nullable)=false];
}
//----------------------------------------
// Blockchain Types


+ 124
- 0
abci/types/typespb_test.go View File

@ -1646,6 +1646,62 @@ func TestBlockGossipMarshalTo(t *testing.T) {
}
}
func TestLastCommitInfoProto(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, false)
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
if err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
msg := &LastCommitInfo{}
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
littlefuzz := make([]byte, len(dAtA))
copy(littlefuzz, dAtA)
for i := range dAtA {
dAtA[i] = byte(popr.Intn(256))
}
if !p.Equal(msg) {
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
}
if len(littlefuzz) > 0 {
fuzzamount := 100
for i := 0; i < fuzzamount; i++ {
littlefuzz[popr.Intn(len(littlefuzz))] = byte(popr.Intn(256))
littlefuzz = append(littlefuzz, byte(popr.Intn(256)))
}
// shouldn't panic
_ = github_com_gogo_protobuf_proto.Unmarshal(littlefuzz, msg)
}
}
func TestLastCommitInfoMarshalTo(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, false)
size := p.Size()
dAtA := make([]byte, size)
for i := range dAtA {
dAtA[i] = byte(popr.Intn(256))
}
_, err := p.MarshalTo(dAtA)
if err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
msg := &LastCommitInfo{}
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
for i := range dAtA {
dAtA[i] = byte(popr.Intn(256))
}
if !p.Equal(msg) {
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
}
}
func TestHeaderProto(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
@ -2448,6 +2504,24 @@ func TestBlockGossipJSON(t *testing.T) {
t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p)
}
}
func TestLastCommitInfoJSON(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, true)
marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{}
jsondata, err := marshaler.MarshalToString(p)
if err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
msg := &LastCommitInfo{}
err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg)
if err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
if !p.Equal(msg) {
t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p)
}
}
func TestHeaderJSON(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
@ -3350,6 +3424,34 @@ func TestBlockGossipProtoCompactText(t *testing.T) {
}
}
func TestLastCommitInfoProtoText(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, true)
dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p)
msg := &LastCommitInfo{}
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
if !p.Equal(msg) {
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
}
}
func TestLastCommitInfoProtoCompactText(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, true)
dAtA := github_com_gogo_protobuf_proto.CompactTextString(p)
msg := &LastCommitInfo{}
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
if !p.Equal(msg) {
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
}
}
func TestHeaderProtoText(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
@ -4128,6 +4230,28 @@ func TestBlockGossipSize(t *testing.T) {
}
}
func TestLastCommitInfoSize(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))
p := NewPopulatedLastCommitInfo(popr, true)
size2 := github_com_gogo_protobuf_proto.Size(p)
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
if err != nil {
t.Fatalf("seed = %d, err = %v", seed, err)
}
size := p.Size()
if len(dAtA) != size {
t.Errorf("seed = %d, size %v != marshalled size %v", seed, size, len(dAtA))
}
if size2 != size {
t.Errorf("seed = %d, size %v != before marshal proto.Size %v", seed, size, size2)
}
size3 := github_com_gogo_protobuf_proto.Size(p)
if size3 != size {
t.Errorf("seed = %d, size %v != after marshal proto.Size %v", seed, size, size3)
}
}
func TestHeaderSize(t *testing.T) {
seed := time.Now().UnixNano()
popr := math_rand.New(math_rand.NewSource(seed))


+ 0
- 4
blockchain/reactor.go View File

@ -109,9 +109,6 @@ func (bcR *BlockchainReactor) SetLogger(l log.Logger) {
// OnStart implements cmn.Service.
func (bcR *BlockchainReactor) OnStart() error {
if err := bcR.BaseReactor.OnStart(); err != nil {
return err
}
if bcR.fastSync {
err := bcR.pool.Start()
if err != nil {
@ -124,7 +121,6 @@ func (bcR *BlockchainReactor) OnStart() error {
// OnStop implements cmn.Service.
func (bcR *BlockchainReactor) OnStop() {
bcR.BaseReactor.OnStop()
bcR.pool.Stop()
}


+ 9
- 6
consensus/common_test.go View File

@ -17,14 +17,14 @@ import (
bc "github.com/tendermint/tendermint/blockchain"
cfg "github.com/tendermint/tendermint/config"
cstypes "github.com/tendermint/tendermint/consensus/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
mempl "github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/privval"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/abci/example/counter"
"github.com/tendermint/tendermint/abci/example/kvstore"
@ -378,8 +378,11 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF
if i < nValidators {
privVal = privVals[i]
} else {
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal = privval.GenFilePV(tempFilePath)
tempFile, err := ioutil.TempFile("", "priv_validator_")
if err != nil {
panic(err)
}
privVal = privval.GenFilePV(tempFile.Name())
}
app := appFunc()
@ -429,7 +432,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []types.PrivValidator) {
genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower)
s0, _ := sm.MakeGenesisState(genDoc)
db := dbm.NewMemDB()
db := dbm.NewMemDB() // remove this ?
sm.SaveState(db, s0)
return s0, privValidators
}


+ 0
- 4
consensus/reactor.go View File

@ -58,9 +58,6 @@ func NewConsensusReactor(consensusState *ConsensusState, fastSync bool) *Consens
// broadcasted to other peers and starting state if we're not in fast sync.
func (conR *ConsensusReactor) OnStart() error {
conR.Logger.Info("ConsensusReactor ", "fastSync", conR.FastSync())
if err := conR.BaseReactor.OnStart(); err != nil {
return err
}
conR.subscribeToBroadcastEvents()
@ -77,7 +74,6 @@ func (conR *ConsensusReactor) OnStart() error {
// OnStop implements BaseService by unsubscribing from events and stopping
// state.
func (conR *ConsensusReactor) OnStop() {
conR.BaseReactor.OnStop()
conR.unsubscribeFromBroadcastEvents()
conR.conS.Stop()
if !conR.FastSync() {


+ 96
- 21
consensus/reactor_test.go View File

@ -4,15 +4,21 @@ import (
"context"
"fmt"
"os"
"path"
"runtime"
"runtime/pprof"
"sync"
"testing"
"time"
abcicli "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/kvstore"
abci "github.com/tendermint/tendermint/abci/types"
bc "github.com/tendermint/tendermint/blockchain"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
mempl "github.com/tendermint/tendermint/mempool"
sm "github.com/tendermint/tendermint/state"
cfg "github.com/tendermint/tendermint/config"
@ -94,49 +100,118 @@ func TestReactorBasic(t *testing.T) {
// Ensure we can process blocks with evidence
func TestReactorWithEvidence(t *testing.T) {
N := 4
css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
evpool := mockEvidencePool{
t: t,
ev: []types.Evidence{types.NewMockGoodEvidence(1, 1, []byte("somone"))},
}
for i := 0; i < N; i++ {
css[i].evpool = evpool
nValidators := 4
testName := "consensus_reactor_test"
tickerFunc := newMockTickerFunc(true)
appFunc := newCounter
// heed the advice from https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
// to unroll unwieldy abstractions. Here we duplicate the code from:
// css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
genDoc, privVals := randGenesisDoc(nValidators, false, 30)
css := make([]*ConsensusState, nValidators)
logger := consensusLogger()
for i := 0; i < nValidators; i++ {
stateDB := dbm.NewMemDB() // each state needs its own db
state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
thisConfig := ResetConfig(cmn.Fmt("%s_%d", testName, i))
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
app := appFunc()
vals := types.TM2PB.Validators(state.Validators)
app.InitChain(abci.RequestInitChain{Validators: vals})
pv := privVals[i]
// duplicate code from:
// css[i] = newConsensusStateWithConfig(thisConfig, state, privVals[i], app)
blockDB := dbm.NewMemDB()
blockStore := bc.NewBlockStore(blockDB)
// one for mempool, one for consensus
mtx := new(sync.Mutex)
proxyAppConnMem := abcicli.NewLocalClient(mtx, app)
proxyAppConnCon := abcicli.NewLocalClient(mtx, app)
// Make Mempool
mempool := mempl.NewMempool(thisConfig.Mempool, proxyAppConnMem, 0)
mempool.SetLogger(log.TestingLogger().With("module", "mempool"))
if thisConfig.Consensus.WaitForTxs() {
mempool.EnableTxsAvailable()
}
// mock the evidence pool
// everyone includes evidence of another double signing
vIdx := (i + 1) % nValidators
evpool := newMockEvidencePool(privVals[vIdx].GetAddress())
// Make ConsensusState
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool)
cs.SetLogger(log.TestingLogger().With("module", "consensus"))
cs.SetPrivValidator(pv)
eventBus := types.NewEventBus()
eventBus.SetLogger(log.TestingLogger().With("module", "events"))
eventBus.Start()
cs.SetEventBus(eventBus)
cs.SetTimeoutTicker(tickerFunc())
cs.SetLogger(logger.With("validator", i, "module", "consensus"))
css[i] = cs
}
reactors, eventChans, eventBuses := startConsensusNet(t, css, N)
reactors, eventChans, eventBuses := startConsensusNet(t, css, nValidators)
defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses)
// wait till everyone makes the first new block
timeoutWaitGroup(t, N, func(j int) {
<-eventChans[j]
// wait till everyone makes the first new block with no evidence
timeoutWaitGroup(t, nValidators, func(j int) {
blockI := <-eventChans[j]
block := blockI.(types.EventDataNewBlock).Block
assert.True(t, len(block.Evidence.Evidence) == 0)
}, css)
// second block should have evidence
timeoutWaitGroup(t, N, func(j int) {
<-eventChans[j]
timeoutWaitGroup(t, nValidators, func(j int) {
blockI := <-eventChans[j]
block := blockI.(types.EventDataNewBlock).Block
assert.True(t, len(block.Evidence.Evidence) > 0)
}, css)
}
// mock evidence pool returns no evidence for block 1,
// and returnes one piece for all higher blocks. The one piece
// is for a given validator at block 1.
type mockEvidencePool struct {
height int
ev []types.Evidence
t *testing.T
}
func (m mockEvidencePool) PendingEvidence() []types.Evidence {
func newMockEvidencePool(val []byte) *mockEvidencePool {
return &mockEvidencePool{
ev: []types.Evidence{types.NewMockGoodEvidence(1, 1, val)},
}
}
func (m *mockEvidencePool) PendingEvidence() []types.Evidence {
if m.height > 0 {
return m.ev
}
return nil
}
func (m mockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (m mockEvidencePool) Update(block *types.Block, state sm.State) {
m.height += 1
func (m *mockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (m *mockEvidencePool) Update(block *types.Block, state sm.State) {
if m.height > 0 {
require.True(m.t, len(block.Evidence.Evidence) > 0)
if len(block.Evidence.Evidence) == 0 {
panic("block has no evidence")
}
}
m.height += 1
}
//------------------------------------
// Ensure a testnet sends proposal heartbeats and makes blocks when there are txs
func TestReactorProposalHeartbeats(t *testing.T) {
N := 4


+ 12
- 5
docs/app-dev/abci-spec.md View File

@ -160,9 +160,8 @@ See below for more details on the message types and how they are used.
- **Request**:
- `Hash ([]byte)`: The block's hash. This can be derived from the
block header.
- `Header (struct{})`: The block header
- `Validators ([]SigningValidator)`: List of validators in the current validator
set and whether or not they signed a vote in the LastCommit
- `Header (struct{})`: The block header.
- `LastCommitInfo (LastCommitInfo)`: Info about the last commit.
- `ByzantineValidators ([]Evidence)`: List of evidence of
validators that acted maliciously
- **Response**:
@ -171,8 +170,9 @@ See below for more details on the message types and how they are used.
- Signals the beginning of a new block. Called prior to
any DeliverTxs.
- The header is expected to at least contain the Height.
- The `Validators` and `ByzantineValidators` can be used to
determine rewards and punishments for the validators.
- The `LastCommitInfo` and `ByzantineValidators` can be used to determine
rewards and punishments for the validators. NOTE validators here do not
include pubkeys.
### CheckTx
@ -326,3 +326,10 @@ See below for more details on the message types and how they are used.
It is the proposer's local time when block was created.
- `TotalVotingPower (int64)`: Total voting power of the validator set at
height `Height`
### LastCommitInfo
- **Fields**:
- `CommitRound (int32)`: Commit round.
- `Validators ([]SigningValidator)`: List of validators in the current
validator set and whether or not they signed a vote.

+ 8
- 8
docs/app-dev/app-development.md View File

@ -365,14 +365,14 @@ ResponseBeginBlock requestBeginBlock(RequestBeginBlock req) {
### EndBlock
The EndBlock request can be used to run some code at the end of every
block. Additionally, the response may contain a list of validators,
which can be used to update the validator set. To add a new validator or
update an existing one, simply include them in the list returned in the
EndBlock response. To remove one, include it in the list with a `power`
equal to `0`. Tendermint core will take care of updating the validator
set. Note the change in voting power must be strictly less than 1/3 per
block if you want a light client to be able to prove the transition
The EndBlock request can be used to run some code at the end of every block.
Additionally, the response may contain a list of validators, which can be used
to update the validator set. To add a new validator or update an existing one,
simply include them in the list returned in the EndBlock response. To remove
one, include it in the list with a `power` equal to `0`. Validator's `address`
field can be left empty. Tendermint core will take care of updating the
validator set. Note the change in voting power must be strictly less than 1/3
per block if you want a light client to be able to prove the transition
externally. See the [light client
docs](https://godoc.org/github.com/tendermint/tendermint/lite#hdr-How_We_Track_Validators)
for details on how it tracks validators.


+ 80
- 0
docs/architecture/adr-015-crypto-encoding.md View File

@ -0,0 +1,80 @@
# ADR 015: Crypto encoding
## Context
We must standardize our method for encoding public keys and signatures on chain.
Currently we amino encode the public keys and signatures.
The reason we are using amino here is primarily due to ease of support in
parsing for other languages.
We don't need its upgradability properties in cryptosystems, as a change in
the crypto that requires adapting the encoding, likely warrants being deemed
a new cryptosystem.
(I.e. using new public parameters)
## Decision
### Public keys
For public keys, we will continue to use amino encoding on the canonical
representation of the pubkey.
(Canonical as defined by the cryptosystem itself)
This has two significant drawbacks.
Amino encoding is less space-efficient, due to requiring support for upgradability.
Amino encoding support requires forking protobuf and adding this new interface support
option in the langauge of choice.
The reason for continuing to use amino however is that people can create code
more easily in languages that already have an up to date amino library.
It is possible that this will change in the future, if it is deemed that
requiring amino for interacting with tendermint cryptography is unneccessary.
The arguments for space efficiency here are refuted on the basis that there are
far more egregious wastages of space in the SDK.
The space requirement of the public keys doesn't cause many problems beyond
increasing the space attached to each validator / account.
The alternative to using amino here would be for us to create an enum type.
Switching to just an enum type is worthy of investigation post-launch.
For referrence, part of amino encoding interfaces is basically a 4 byte enum
type definition.
Enum types would just change that 4 bytes to be a varuint, and it would remove
the protobuf overhead, but it would be hard to integrate into the existing API.
### Signatures
Signatures should be switched to be `[]byte`.
Spatial efficiency in the signatures is quite important,
as it directly affects the gas cost of every transaction,
and the throughput of the chain.
Signatures don't need to encode what type they are for (unlike public keys)
since public keys must already be known.
Therefore we can validate the signature without needing to encode its type.
When placed in state, signatures will still be amino encoded, but it will be the
primitive type `[]byte` getting encoded.
#### Ed25519
Use the canonical representation for signatures.
#### Secp256k1
There isn't a clear canonical representation here.
Signatures have two elements `r,s`.
We should encode these bytes as `r || s`, where `r` and `s` are both exactly
32 bytes long.
This is basically Ethereum's encoding, but without the leading recovery bit.
## Status
Proposed. The signature section seems to be agreed upon for the most part.
Needs decision on Enum types.
## Consequences
### Positive
* More space efficient signatures
### Negative
* We have an amino dependency for cryptography.
### Neutral
* No change to public keys

+ 22
- 16
docs/spec/blockchain/blockchain.md View File

@ -52,7 +52,8 @@ type Header struct {
// application
ResultsHash []byte // SimpleMerkle of []abci.Result from prevBlock
AppHash []byte // Arbitrary state digest
ValidatorsHash []byte // SimpleMerkle of the ValidatorSet
ValidatorsHash []byte // SimpleMerkle of the current ValidatorSet
NextValidatorsHash []byte // SimpleMerkle of the next ValidatorSet
ConsensusParamsHash []byte // SimpleMerkle of the ConsensusParams
// consensus
@ -160,9 +161,12 @@ We refer to certain globally available objects:
`block` is the block under consideration,
`prevBlock` is the `block` at the previous height,
and `state` keeps track of the validator set, the consensus parameters
and other results from the application.
and other results from the application. At the point when `block` is the block under consideration,
the current version of the `state` corresponds to the state
after executing transactions from the `prevBlock`.
Elements of an object are accessed as expected,
ie. `block.Header`. See [here](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/state.md) for the definition of `state`.
ie. `block.Header`.
See [here](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/state.md) for the definition of `state`.
### Header
@ -278,7 +282,14 @@ block.ValidatorsHash == SimpleMerkleRoot(state.Validators)
Simple Merkle root of the current validator set that is committing the block.
This can be used to validate the `LastCommit` included in the next block.
May be updated by the application.
### NextValidatorsHash
```go
block.NextValidatorsHash == SimpleMerkleRoot(state.NextValidators)
```
Simple Merkle root of the next validator set that will be the validator set that commits the next block.
Modifications to the validator set are defined by the application.
### ConsensusParamsHash
@ -407,25 +418,20 @@ set (TODO). Execute is defined as:
```go
Execute(s State, app ABCIApp, block Block) State {
TODO: just spell out ApplyBlock here
and remove ABCIResponses struct.
abciResponses := app.ApplyBlock(block)
// Fuction ApplyBlock executes block of transactions against the app and returns the new root hash of the app state,
// modifications to the validator set and the changes of the consensus parameters.
AppHash, ValidatorChanges, ConsensusParamChanges := app.ApplyBlock(block)
return State{
LastResults: abciResponses.DeliverTxResults,
AppHash: abciResponses.AppHash,
Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges),
AppHash: AppHash,
LastValidators: state.Validators,
ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges),
Validators: state.NextValidators,
NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges),
ConsensusParams: UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges),
}
}
type ABCIResponses struct {
DeliverTxResults []Result
ValidatorChanges []Validator
ConsensusParamChanges ConsensusParams
AppHash []byte
}
```

+ 3
- 2
docs/spec/blockchain/state.md View File

@ -3,7 +3,7 @@
## State
The state contains information whose cryptographic digest is included in block headers, and thus is
necessary for validating new blocks. For instance, the set of validators and the results of
necessary for validating new blocks. For instance, the validators set and the results of
transactions are never included in blocks, but their Merkle roots are - the state keeps track of them.
Note that the `State` object itself is an implementation detail, since it is never
@ -18,8 +18,9 @@ type State struct {
LastResults []Result
AppHash []byte
Validators []Validator
LastValidators []Validator
Validators []Validator
NextValidators []Validator
ConsensusParams ConsensusParams
}


+ 0
- 5
evidence/reactor.go View File

@ -44,11 +44,6 @@ func (evR *EvidenceReactor) SetLogger(l log.Logger) {
evR.evpool.SetLogger(l)
}
// OnStart implements cmn.Service
func (evR *EvidenceReactor) OnStart() error {
return evR.BaseReactor.OnStart()
}
// GetChannels implements Reactor.
// It returns the list of channels for this reactor.
func (evR *EvidenceReactor) GetChannels() []*p2p.ChannelDescriptor {


+ 7
- 2
libs/autofile/autofile_test.go View File

@ -1,6 +1,7 @@
package autofile
import (
"io/ioutil"
"os"
"sync/atomic"
"syscall"
@ -13,10 +14,14 @@ import (
func TestSIGHUP(t *testing.T) {
// First, create an AutoFile writing to a tempfile dir
file, name := cmn.Tempfile("sighup_test")
if err := file.Close(); err != nil {
file, err := ioutil.TempFile("", "sighup_test")
if err != nil {
t.Fatalf("Error creating tempfile: %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("Error closing tempfile: %v", err)
}
name := file.Name()
// Here is the actual AutoFile
af, err := OpenAutoFile(name)
if err != nil {


+ 7
- 8
libs/autofile/group_test.go View File

@ -147,14 +147,13 @@ func TestSearch(t *testing.T) {
// Now search for each number
for i := 0; i < 100; i++ {
t.Log("Testing for i", i)
gr, match, err := g.Search("INFO", makeSearchFunc(i))
require.NoError(t, err, "Failed to search for line")
assert.True(t, match, "Expected Search to return exact match")
require.NoError(t, err, "Failed to search for line, tc #%d", i)
assert.True(t, match, "Expected Search to return exact match, tc #%d", i)
line, err := gr.ReadLine()
require.NoError(t, err, "Failed to read line after search")
require.NoError(t, err, "Failed to read line after search, tc #%d", i)
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", i)) {
t.Fatal("Failed to get correct line")
t.Fatalf("Failed to get correct line, tc #%d", i)
}
// Make sure we can continue to read from there.
cur := i + 1
@ -165,16 +164,16 @@ func TestSearch(t *testing.T) {
// OK!
break
} else {
t.Fatal("Got EOF after the wrong INFO #")
t.Fatalf("Got EOF after the wrong INFO #, tc #%d", i)
}
} else if err != nil {
t.Fatal("Error reading line", err)
t.Fatalf("Error reading line, tc #%d, err:\n%s", i, err)
}
if !strings.HasPrefix(line, "INFO ") {
continue
}
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", cur)) {
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v", cur, line)
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v, tc #%d", cur, line, i)
}
cur++
}


+ 0
- 55
libs/common/os.go View File

@ -8,7 +8,6 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
)
@ -124,60 +123,6 @@ func MustWriteFile(filePath string, contents []byte, mode os.FileMode) {
}
}
// WriteFileAtomic creates a temporary file with data and the perm given and
// swaps it atomically with filename if successful.
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
var (
dir = filepath.Dir(filename)
tempFile = filepath.Join(dir, "write-file-atomic-"+RandStr(32))
// Override in case it does exist, create in case it doesn't and force kernel
// flush, which still leaves the potential of lingering disk cache.
flag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC
)
f, err := os.OpenFile(tempFile, flag, perm)
if err != nil {
return err
}
// Clean up in any case. Defer stacking order is last-in-first-out.
defer os.Remove(f.Name())
defer f.Close()
if n, err := f.Write(data); err != nil {
return err
} else if n < len(data) {
return io.ErrShortWrite
}
// Close the file before renaming it, otherwise it will cause "The process
// cannot access the file because it is being used by another process." on windows.
f.Close()
return os.Rename(f.Name(), filename)
}
//--------------------------------------------------------------------------------
func Tempfile(prefix string) (*os.File, string) {
file, err := ioutil.TempFile("", prefix)
if err != nil {
PanicCrisis(err)
}
return file, file.Name()
}
func Tempdir(prefix string) (*os.File, string) {
tempDir := os.TempDir() + "/" + prefix + RandStr(12)
err := EnsureDir(tempDir, 0700)
if err != nil {
panic(Fmt("Error creating temp dir: %v", err))
}
dir, err := os.Open(tempDir)
if err != nil {
panic(Fmt("Error opening temp dir: %v", err))
}
return dir, tempDir
}
//--------------------------------------------------------------------------------
func Prompt(prompt string, defaultValue string) (string, error) {


+ 0
- 42
libs/common/os_test.go View File

@ -1,52 +1,10 @@
package common
import (
"bytes"
"io/ioutil"
"os"
"testing"
)
func TestWriteFileAtomic(t *testing.T) {
var (
data = []byte(RandStr(RandIntn(2048)))
old = RandBytes(RandIntn(2048))
perm os.FileMode = 0600
)
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
t.Fatal(err)
}
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
t.Fatal(err)
}
rData, err := ioutil.ReadFile(f.Name())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, rData) {
t.Fatalf("data mismatch: %v != %v", data, rData)
}
stat, err := os.Stat(f.Name())
if err != nil {
t.Fatal(err)
}
if have, want := stat.Mode().Perm(), perm; have != want {
t.Errorf("have %v, want %v", have, want)
}
}
func TestGoPath(t *testing.T) {
// restore original gopath upon exit
path := os.Getenv("GOPATH")


+ 11
- 0
libs/common/random.go View File

@ -109,6 +109,10 @@ func RandInt63n(n int64) int64 {
return grand.Int63n(n)
}
func RandBool() bool {
return grand.Bool()
}
func RandFloat32() float32 {
return grand.Float32()
}
@ -274,6 +278,13 @@ func (r *Rand) Intn(n int) int {
return i
}
// Bool returns a uniformly random boolean
func (r *Rand) Bool() bool {
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
// for reasoning behind computing like this
return r.Int63()%2 == 0
}
// Perm returns a pseudo-random permutation of n integers in [0, n).
func (r *Rand) Perm(n int) []int {
r.Lock()


+ 128
- 0
libs/common/tempfile.go View File

@ -0,0 +1,128 @@
package common
import (
fmt "fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
const (
atomicWriteFilePrefix = "write-file-atomic-"
// Maximum number of atomic write file conflicts before we start reseeding
// (reduced from golang's default 10 due to using an increased randomness space)
atomicWriteFileMaxNumConflicts = 5
// Maximum number of attempts to make at writing the write file before giving up
// (reduced from golang's default 10000 due to using an increased randomness space)
atomicWriteFileMaxNumWriteAttempts = 1000
// LCG constants from Donald Knuth MMIX
// This LCG's has a period equal to 2**64
lcgA = 6364136223846793005
lcgC = 1442695040888963407
// Create in case it doesn't exist and force kernel
// flush, which still leaves the potential of lingering disk cache.
// Never overwrites files
atomicWriteFileFlag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC | os.O_EXCL
)
var (
atomicWriteFileRand uint64
atomicWriteFileRandMu sync.Mutex
)
func writeFileRandReseed() uint64 {
// Scale the PID, to minimize the chance that two processes seeded at similar times
// don't get the same seed. Note that PID typically ranges in [0, 2**15), but can be
// up to 2**22 under certain configurations. We left bit-shift the PID by 20, so that
// a PID difference of one corresponds to a time difference of 2048 seconds.
// The important thing here is that now for a seed conflict, they would both have to be on
// the correct nanosecond offset, and second-based offset, which is much less likely than
// just a conflict with the correct nanosecond offset.
return uint64(time.Now().UnixNano() + int64(os.Getpid()<<20))
}
// Use a fast thread safe LCG for atomic write file names.
// Returns a string corresponding to a 64 bit int.
// If it was a negative int, the leading number is a 0.
func randWriteFileSuffix() string {
atomicWriteFileRandMu.Lock()
r := atomicWriteFileRand
if r == 0 {
r = writeFileRandReseed()
}
// Update randomness according to lcg
r = r*lcgA + lcgC
atomicWriteFileRand = r
atomicWriteFileRandMu.Unlock()
// Can have a negative name, replace this in the following
suffix := strconv.Itoa(int(r))
if string(suffix[0]) == "-" {
// Replace first "-" with "0". This is purely for UI clarity,
// as otherwhise there would be two `-` in a row.
suffix = strings.Replace(suffix, "-", "0", 1)
}
return suffix
}
// WriteFileAtomic creates a temporary file with data and provided perm and
// swaps it atomically with filename if successful.
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) {
// This implementation is inspired by the golang stdlibs method of creating
// tempfiles. Notable differences are that we use different flags, a 64 bit LCG
// and handle negatives differently.
// The core reason we can't use golang's TempFile is that we must write
// to the file synchronously, as we need this to persist to disk.
// We also open it in write-only mode, to avoid concerns that arise with read.
var (
dir = filepath.Dir(filename)
f *os.File
)
nconflict := 0
// Limit the number of attempts to create a file. Something is seriously
// wrong if it didn't get created after 1000 attempts, and we don't want
// an infinite loop
i := 0
for ; i < atomicWriteFileMaxNumWriteAttempts; i++ {
name := filepath.Join(dir, atomicWriteFilePrefix+randWriteFileSuffix())
f, err = os.OpenFile(name, atomicWriteFileFlag, perm)
// If the file already exists, try a new file
if os.IsExist(err) {
// If the files exists too many times, start reseeding as we've
// likely hit another instances seed.
if nconflict++; nconflict > atomicWriteFileMaxNumConflicts {
atomicWriteFileRandMu.Lock()
atomicWriteFileRand = writeFileRandReseed()
atomicWriteFileRandMu.Unlock()
}
continue
} else if err != nil {
return err
}
break
}
if i == atomicWriteFileMaxNumWriteAttempts {
return fmt.Errorf("Could not create atomic write file after %d attempts", i)
}
// Clean up in any case. Defer stacking order is last-in-first-out.
defer os.Remove(f.Name())
defer f.Close()
if n, err := f.Write(data); err != nil {
return err
} else if n < len(data) {
return io.ErrShortWrite
}
// Close the file before renaming it, otherwise it will cause "The process
// cannot access the file because it is being used by another process." on windows.
f.Close()
return os.Rename(f.Name(), filename)
}

+ 138
- 0
libs/common/tempfile_test.go View File

@ -0,0 +1,138 @@
package common
// Need access to internal variables, so can't use _test package
import (
"bytes"
fmt "fmt"
"io/ioutil"
"os"
testing "testing"
"github.com/stretchr/testify/require"
)
func TestWriteFileAtomic(t *testing.T) {
var (
data = []byte(RandStr(RandIntn(2048)))
old = RandBytes(RandIntn(2048))
perm os.FileMode = 0600
)
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
t.Fatal(err)
}
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
t.Fatal(err)
}
rData, err := ioutil.ReadFile(f.Name())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, rData) {
t.Fatalf("data mismatch: %v != %v", data, rData)
}
stat, err := os.Stat(f.Name())
if err != nil {
t.Fatal(err)
}
if have, want := stat.Mode().Perm(), perm; have != want {
t.Errorf("have %v, want %v", have, want)
}
}
// This tests atomic write file when there is a single duplicate file.
// Expected behavior is for a new file to be created, and the original write file to be unaltered.
func TestWriteFileAtomicDuplicateFile(t *testing.T) {
var (
defaultSeed uint64 = 1
testString = "This is a glorious test string"
expectedString = "Did the test file's string appear here?"
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
)
// Create a file at the seed, and reset the seed.
atomicWriteFileRand = defaultSeed
firstFileRand := randWriteFileSuffix()
atomicWriteFileRand = defaultSeed
fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
defer os.Remove(fname)
// Defer here, in case there is a panic in WriteFileAtomic.
defer os.Remove(fileToWrite)
require.Nil(t, err)
f.WriteString(testString)
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
// Check that the first atomic file was untouched
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
require.Nil(t, err, "Error reading first atomic file")
require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten")
// Check that the resultant file is correct
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
require.Nil(t, err, "Error reading resultant file")
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
// Check that the intermediate write file was deleted
// Get the second write files' randomness
atomicWriteFileRand = defaultSeed
_ = randWriteFileSuffix()
secondFileRand := randWriteFileSuffix()
_, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand)
require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted")
}
// This tests atomic write file when there are many duplicate files.
// Expected behavior is for a new file to be created under a completely new seed,
// and the original write files to be unaltered.
func TestWriteFileAtomicManyDuplicates(t *testing.T) {
var (
defaultSeed uint64 = 2
testString = "This is a glorious test string, from file %d"
expectedString = "Did any of the test file's string appear here?"
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
)
// Initialize all of the atomic write files
atomicWriteFileRand = defaultSeed
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
fileRand := randWriteFileSuffix()
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
require.Nil(t, err)
f.WriteString(fmt.Sprintf(testString, i))
defer os.Remove(fname)
}
atomicWriteFileRand = defaultSeed
// Defer here, in case there is a panic in WriteFileAtomic.
defer os.Remove(fileToWrite)
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
// Check that all intermittent atomic file were untouched
atomicWriteFileRand = defaultSeed
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
fileRand := randWriteFileSuffix()
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
require.Nil(t, err, "Error reading first atomic file")
require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes,
"atomic write file %d was overwritten", i)
}
// Check that the resultant file is correct
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
require.Nil(t, err, "Error reading resultant file")
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
}

+ 3
- 2
libs/db/backend_test.go View File

@ -2,6 +2,7 @@ package db
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -17,8 +18,8 @@ func cleanupDBDir(dir, name string) {
func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
// Default
dir, dirname := cmn.Tempdir(fmt.Sprintf("test_backend_%s_", backend))
defer dir.Close()
dirname, err := ioutil.TempDir("", fmt.Sprintf("test_backend_%s_", backend))
require.Nil(t, err)
db := NewDB("testdb", backend, dirname)
// A nonexistent key should return nil, even if the key is empty


+ 3
- 3
libs/db/common_test.go View File

@ -2,12 +2,12 @@ package db
import (
"fmt"
"io/ioutil"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tendermint/libs/common"
)
//----------------------------------------
@ -61,9 +61,9 @@ func checkValuePanics(t *testing.T, itr Iterator) {
}
func newTempDB(t *testing.T, backend DBBackendType) (db DB) {
dir, dirname := cmn.Tempdir("db_common_test")
dirname, err := ioutil.TempDir("", "db_common_test")
require.Nil(t, err)
db = NewDB("testdb", backend, dirname)
dir.Close()
return db
}


+ 68
- 0
p2p/conn/secret_connection_test.go View File

@ -1,8 +1,16 @@
package conn
import (
"bufio"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -208,6 +216,66 @@ func TestSecretConnectionReadWrite(t *testing.T) {
}
// Run go test -update from within this module
// to update the golden test vector file
var update = flag.Bool("update", false, "update .golden files")
func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
goldenFilepath := filepath.Join("testdata", t.Name()+".golden")
if *update {
t.Logf("Updating golden test vector file %s", goldenFilepath)
data := createGoldenTestVectors(t)
cmn.WriteFile(goldenFilepath, []byte(data), 0644)
}
f, err := os.Open(goldenFilepath)
if err != nil {
log.Fatal(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
params := strings.Split(line, ",")
randSecretVector, err := hex.DecodeString(params[0])
require.Nil(t, err)
randSecret := new([32]byte)
copy((*randSecret)[:], randSecretVector)
locIsLeast, err := strconv.ParseBool(params[1])
require.Nil(t, err)
expectedRecvSecret, err := hex.DecodeString(params[2])
require.Nil(t, err)
expectedSendSecret, err := hex.DecodeString(params[3])
require.Nil(t, err)
expectedChallenge, err := hex.DecodeString(params[4])
require.Nil(t, err)
recvSecret, sendSecret, challenge := deriveSecretAndChallenge(randSecret, locIsLeast)
require.Equal(t, expectedRecvSecret, (*recvSecret)[:], "Recv Secrets aren't equal")
require.Equal(t, expectedSendSecret, (*sendSecret)[:], "Send Secrets aren't equal")
require.Equal(t, expectedChallenge, (*challenge)[:], "challenges aren't equal")
}
}
// Creates the data for a test vector file.
// The file format is:
// Hex(diffie_hellman_secret), loc_is_least, Hex(recvSecret), Hex(sendSecret), Hex(challenge)
func createGoldenTestVectors(t *testing.T) string {
data := ""
for i := 0; i < 32; i++ {
randSecretVector := cmn.RandBytes(32)
randSecret := new([32]byte)
copy((*randSecret)[:], randSecretVector)
data += hex.EncodeToString((*randSecret)[:]) + ","
locIsLeast := cmn.RandBool()
data += strconv.FormatBool(locIsLeast) + ","
recvSecret, sendSecret, challenge := deriveSecretAndChallenge(randSecret, locIsLeast)
data += hex.EncodeToString((*recvSecret)[:]) + ","
data += hex.EncodeToString((*sendSecret)[:]) + ","
data += hex.EncodeToString((*challenge)[:]) + "\n"
}
return data
}
func BenchmarkSecretConnection(b *testing.B) {
b.StopTimer()
fooSecConn, barSecConn := makeSecretConnPair(b)


+ 32
- 0
p2p/conn/testdata/TestDeriveSecretsAndChallengeGolden.golden View File

@ -0,0 +1,32 @@
9fe4a5a73df12dbd8659b1d9280873fe993caefec6b0ebc2686dd65027148e03,true,80a83ad6afcb6f8175192e41973aed31dd75e3c106f813d986d9567a4865eb2f,96362a04f628a0666d9866147326898bb0847b8db8680263ad19e6336d4eed9e,2632c3fd20f456c5383ed16aa1d56dc7875a2b0fc0d5ff053c3ada8934098c69
0716764b370d543fee692af03832c16410f0a56e4ddb79604ea093b10bb6f654,false,84f2b1e8658456529a2c324f46c3406c3c6fecd5fbbf9169f60bed8956a8b03d,cba357ae33d7234520d5742102a2a6cdb39b7db59c14a58fa8aadd310127630f,576643a8fcc1a4cf866db900f4a150dbe35d44a1b3ff36e4911565c3fa22fc32
358dd73aae2c5b7b94b57f950408a3c681e748777ecab2063c8ca51a63588fa8,false,c2e2f664c8ee561af8e1e30553373be4ae23edecc8c6bd762d44b2afb7f2a037,d1563f428ac1c023c15d8082b2503157fe9ecbde4fb3493edd69ebc299b4970c,89fb6c6439b12fe11a4c604b8ad883f7dc76be33df590818fe5eb15ddb01face
0958308bdb583e639dd399a98cd21077d834b4b5e30771275a5a73a62efcc7e0,false,523c0ae97039173566f7ab4b8f271d8d78feef5a432d618e58ced4f80f7c1696,c1b743401c6e4508e62b8245ea7c3252bbad082e10af10e80608084d63877977,d7c52adf12ebc69677aec4bd387b0c5a35570fe61cb7b8ae55f3ab14b1b79be0
d93d134e72f58f177642ac30f36b2d3cd4720aa7e60feb1296411a9009cf4524,false,47a427bcc1ef6f0ce31dbf343bc8bbf49554b4dd1e2330fd97d0df23ecdbba10,73e23adb7801179349ecf9c8cdf64d71d64a9f1145ba6730e5d029f99eaf8840,a8fdcb77f591bfba7b8483aa15ae7b42054ba68625d51dec005896dfe910281f
6104474c791cda24d952b356fb41a5d273c0ce6cc87d270b1701d0523cd5aa13,true,1cb4397b9e478430321af4647da2ccbef62ff8888542d31cca3f626766c8080f,673b23318826bd31ad1a4995c6e5095c4b092f5598aa0a96381a3e977bc0eaf9,4a25a25c5f75d6cc512f2ba8c1546e6263e9ef8269f0c046c37838cc66aa83e6
8a6002503c15cab763e27c53fc449f6854a210c95cdd67e4466b0f2cb46b629c,false,f01ff06aef356c87f8d2646ff9ed8b855497c2ca00ea330661d84ef421a67e63,4f59bb23090010614877265a1597f1a142fa97b7208e1d554435763505f36f6a,1aadcb1c8b5993da102cebcb60c545b03197c98137064530840f45d917ad300e
31a57c6b1fe33beb1f7ebbbfc06d58c4f307cd355b6f9753e58f3edec16c7559,false,13e126c4cb240349dccf0dc843977671d34a1daffd0517d06ed66b703344db22,d491431906a306af45ecf9f1977e32d7f65a79f5139f931760416de27554b687,5ea7e8e3d5a30503423341609d360d246b61a9159fc07f253a46e357977cd745
71a3c79718b824627faeefdce887d9465b353bd962cc5e97c5b5dfedab457ef9,true,e2e8eea547dcee7eafa89ae41f48ab049beac24935fad75258924fd5273d23cb,45d2e839bf36a3616cbe8a9bdbd4e7b288bf5bf1e6e79c07995eb2b18eb2eaff,7ee50e0810bc9f98e56bc46de5da22d84b3efa52fe5d85db4b2344530ef17ed8
2e9dba2eb4f9019c2628ff5899744469c26caf793636f30ddb76601751aee968,false,8bfc3b314e4468d4e19c9d28b7bfd5b5532263105273b0fe80801f6146313993,b77d2b223e27038f978ab87a725859f6995f903056bdbd594ab04f0b2cbad517,9032be49a9cbcd1de6fee332f8f24ebf545c05e0175b98c564e7d1e69630ae20
81322b22c835efb26d78051f3a3840a9d01aa558c019ecfa26483b5c5535728c,true,61eacb7e9665e362ef492ef950cea58f8bc67434ab7ee5545139147adf395da4,0f600ef0c358cae938969f434c2ec0ce3be632fdf5246b7bb8ee3ff294036ecd,a7026b4c21fe225ecd775ae81249405c6f492882eb85f3f8e2232f11e515561e
826b86c5e8cb4173ff2d05c48e3537140c5e0f26f7866bbcd4e57616806e1be2,true,ae44dabd077d227c8d898930a7705a2b785c8849121282106c045bb58b66eb36,24b2c1b1e2a9ebe387df6dfb9fbde6c681e4eeb0a33bb1c3df3789087f56ffe3,b37a64ea97431b25cb271c4c8435f6dd97118b35da57168f3c3c269920f7bbc1
18b5a7b973d4b263072e69515c5b6ed22191c3d6e851aaba872904672f8344ec,true,ce402af2fb93b6ef18cd406f7c437d3cbfb09141b7a02116b1cfbabbf75ad84a,c86bdb1709ef0f4a31a818843660f83338b9db77e262bb7c6546138e51c6046b,11fcd8e59c4e7f6050d3cd332337db794ae31260c159e409af3ed8f4d6523bf4
26d10c56872b72bb76ae7c7b3f074afb3d4a364e5e3f8c661be9b4f5a522ea75,true,1c9782a8485c4ecb13904ec551a7f9300ecd687abfbe63c91c7fd583f84a7a4d,ae3f4ccd0dfee8b514f67db2e923714d324935b9ae9e488d088ebb79569d8cc4,8139a3ab728b0e765e4d90549ab8eed7e1048a83267eafa7442208a7f627558a
558838dfcfe94105c46a4ade4548e6c96271d33e6c752661356cc66024615bae,true,d5a38625be74177318072cf877f2427ce2327e9b58d2eb134d0ac52c9126572f,dead938f77007e3164b6eee4cd153433d03ca5d9ec64f41aa6b2d6a069edeeda,4a081a356361da429c564cf7ac8e217121bbe8c5ee5c9632bae0b7ddbe94f9d4
f4a3f6a93a4827a59682fd8bf1a8e4fd9aaff01a337a86e1966c8fff0e746014,true,39a0aea2a8ac7f0524d63e395a25b98fc3844ed039f20b11058019dca2b3840f,6ff53243426ded506d22501ae0f989d9946b86a8bb2550d7ed6e90fdf41d0e7c,8784e728bf12f465ed20dc6f0e1d949a68e5795d4799536427a6f859547b7fd6
1717020e1c4fca1b4926dba16671c0c04e4f19c621c646cb4525fa533b1c205c,false,b9a909767f3044608b4e314b149a729bef199f8311310e1ecd2072e5659b7194,7baf0ff4b980919cf545312f45234976f0b6c574aac5b772024f73248aad7538,99a18e1e4b039ef3777a8fdd0d9ffaccaf3b4523b6d26adacfe91cc5fcd9977e
de769062be27b2a4248dd5be315960c8d231738417ece670c2d6a1c52877b59e,true,cc6c2086718b21813513894546e85766d34c754e81fd6a19c12fc322ffb9b1c3,5a7da7500191c65a5f1fbb2a6122717edc70ca0469baf2bbbd6ca8255b93c077,8c0d32091dc687f1399c754a617d224742726bece848b50c35b4db5f0469ace7
7c5549f36767e02ebf49a4616467199459aa6932dcc091f182f822185659559a,true,d8335e606128b0c621ff6cda99dc62babf4a4436c574c5c478c20122712727d0,0a7c673cccd6f7fd4ed1673f7d0f2cb08961faced123ca901b74581d5bdc8b25,16ac1eb2a39384716c7d490272d87e76c10665fdb331e1883435de175ce4460e
ecf8261ebda248dc7796f98987efe1b7be363a59037c9e61044490d08a077610,true,53def80fcdba01367c0ea36459b57409f59a771f57a8259b54f24785e5656b7d,90140870b3b1e84c9dcf7836eac0581b16fe0a40307619d267c6f871e1efce6a,c6d1836b66c1a722a377c7eb058995a0ef8711839c6d6a0cdd6ad1ff70f935a5
21c0ef76ce0eae9391ceabfb08a861899db55ac4ccf010ed672599669c6938f2,false,8af5482cc015093f261d5b7ce87035dda41d8318b9960b52cca3e5f0d3f61808,f4d5338bcb57262e1034f01ed3858ca1e5d66a73f18588e72f3dc8c6a730be0c,7ba82c2820c95e3354d9a6ab4920ebcd7938ce19e25930fee58439246b0321b1
05f3b66d6b0fe906137e60b4719083a2465106badedcdae3a4c91c46c5367340,false,e5c9e074e95c2896fa4093830e96e9cf159b8dcba2ead21f37237cf6e9a9aaa2,b3a0a50309b4ca23cd34363fd8df30e73ec4a275973986c2e11a53752eff0a3b,358a62056ff05f27185b9952d291c6346171937f6811cafbacddd82e17010f39
fef0251cff7c5d1ba0514f1820a8265453365fd9f5bb8a92f955dc007a40e730,true,e35a0aff6e9060a39c15d276a1337f1948d0be0aef81fcd563a6783115b5283d,20a8efe83474253d70e5fd847df0cd26222cd39e9210687b68c0a23b73429108,2989fab4278b32f4f40dc02227ab30e10f62e15ab7aa7382da769b1d084e33df
1b7bb172baa2753ec9c3e81a7a9b4c6ef10f9ed7afcafa975395f095eca63a54,false,a98257203987d0c4d260d8feef841466977276612e268b69b5ce4191af161b29,ea177a20d6c1f73f9667090568f9197943037d6586f7e2d6b7b81756fc71df5f,844eff318ef4c6ee45f158c1946ff999e40ffac70883ab6d6b90995f246e69a2
5ee9b60a25753066d0ecc1155ca6afcc6b853ba558c9533c134a93b82e756856,true,9889460b95ca9545864a4a5194891b7d475362428d6d797532da10bf1fc92076,a7a96739abd8eceb6751afc98df68e29f7af16fbfda3d4710df9c35b6dcdb4d5,998326285c90a2ea2e1f6c6dac79530742645e3dd1b2b42a0733388a99cab81b
a102613781872f88a949d82cb5efcc2e0f437010a950d71b87929ecb480af3b3,false,e099080a55b9b29ccecbbb0d91dbe49defcc217efd1de0588e0836ce5970d327,319293b8660a3cea9879487645ddadda72a5c60079c9154bb0dbb8a0c9cda79e,4d567f1b1a1b304347cf7b129e4c7a05aa57e2bbb8ea335db9e33d05fab12e4d
1d4538180d06f37c43e8caa2d0d80aa7c5d701c8c3e31508704131427837f5cc,true,73afeeb46efc03d2b9f20fc271752528e52b8931287296a7e4367c96bccb32bd,59dc4b69d9ccf6f77715e47fb9bf454f1b90bbd05f1d2bbd07c7d6666f31c91f,ac59d735dfcdc3a0a4ce5a10f09dea8c6afd47de9c0308dc817e3789c8aee963
e4c480af1b0e3487a331761f64eb3f020a2b8ffa25ad17e00f57aa7ec2c5e84d,true,1145e9f001c70d364e97fcdbc88a2a3d6aecdd975212923820f90a0b215f11f6,b802ac7ef21c8abaeae024c76e3fa70a2a82f73e0bb7c7fe76752ad1742af2e6,0a95876e30617e32ae25acd3af97c37dc075825f800def3f2bf3f68a268744e9
3a7a83dd657dd6277bcfa957534f40d9b559039aad752066a8d7ed9a6d9c0ab5,false,f90a251ad2338b19cfee6a7965f6f5098136974abb99b3d24553fa6117384978,e422ed7567e5602731b3d980106d0546ef4a4da5eb7175d66a452df12d37bad2,b086bed71dfb6662cb10e2b4fb16a7c22394f488e822fc19697db6077f6caf6f
273e8560c2b1734e863a6542bded7a6fcbfb49a12770bd8866d4863dceea3ae9,false,3b7849a362e7b7ba8c8b8a0cd00df5180604987dbda6c03f37d9a09fdb27fb28,e6cdf4d767df0f411e970da8dda6acd3c2c34ce63908d8a6dbf3715daa0318e4,359a4a39fbdffc808161a48a3ffbe77fc6a03ff52324c22510a42e46c08a6f22
9b4f8702991be9569b6c0b07a2173104d41325017b27d68fa5af91cdab164c4d,true,598323677db11ece050289f31881ee8caacb59376c7182f9055708b2a4673f84,7675adc1264b6758beb097a991f766f62796f78c1cfa58a4de3d81c36434d3ae,d5d8d610ffd85b04cbe1c73ff5becd5917c513d9625b001f51d486d0dadcefe3
e1a686ba0169eb97379ebf9d22e073819450ee5ad5f049c8e93016e8d2ec1430,false,ffe461e6075865cde2704aa148fd29bcf0af245803f446cb6153244f25617993,46df6c25fa0344e662490c4da0bddca626644e67e66705840ef08aae35c343fa,e9a56d75acad4272ab0c49ee5919a4e86e6c5695ef065704c1e592d4e7b41a10

+ 19
- 13
p2p/pex/pex_reactor.go View File

@ -74,6 +74,8 @@ type PEXReactor struct {
requestsSent *cmn.CMap // ID->struct{}: unanswered send requests
lastReceivedRequests *cmn.CMap // ID->time.Time: last time peer requested from us
seedAddrs []*p2p.NetAddress
attemptsToDial sync.Map // address (string) -> {number of attempts (int), last time dialed (time.Time)}
}
@ -113,9 +115,6 @@ func NewPEXReactor(b AddrBook, config *PEXReactorConfig) *PEXReactor {
// OnStart implements BaseService
func (r *PEXReactor) OnStart() error {
if err := r.BaseReactor.OnStart(); err != nil {
return err
}
err := r.book.Start()
if err != nil && err != cmn.ErrAlreadyStarted {
return err
@ -123,10 +122,13 @@ func (r *PEXReactor) OnStart() error {
// return err if user provided a bad seed address
// or a host name that we cant resolve
if err := r.checkSeeds(); err != nil {
seedAddrs, err := r.checkSeeds()
if err != nil {
return err
}
r.seedAddrs = seedAddrs
// Check if this node should run
// in seed/crawler mode
if r.config.SeedMode {
@ -139,7 +141,6 @@ func (r *PEXReactor) OnStart() error {
// OnStop implements BaseService
func (r *PEXReactor) OnStop() {
r.BaseReactor.OnStop()
r.book.Stop()
}
@ -285,7 +286,6 @@ func (r *PEXReactor) RequestAddrs(p Peer) {
// request for this peer and deletes the open request.
// If there's no open request for the src peer, it returns an error.
func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error {
id := string(src.ID())
if !r.requestsSent.Has(id) {
return cmn.NewError("Received unsolicited pexAddrsMessage")
@ -301,6 +301,13 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error {
err := r.book.AddAddress(netAddr, srcAddr)
r.logErrAddrBook(err)
// If this address came from a seed node, try to connect to it without waiting.
for _, seedAddr := range r.seedAddrs {
if seedAddr.Equals(srcAddr) {
r.ensurePeers()
}
}
}
return nil
}
@ -472,18 +479,18 @@ func (r *PEXReactor) dialPeer(addr *p2p.NetAddress) {
}
// check seed addresses are well formed
func (r *PEXReactor) checkSeeds() error {
func (r *PEXReactor) checkSeeds() ([]*p2p.NetAddress, error) {
lSeeds := len(r.config.Seeds)
if lSeeds == 0 {
return nil
return nil, nil
}
_, errs := p2p.NewNetAddressStrings(r.config.Seeds)
netAddrs, errs := p2p.NewNetAddressStrings(r.config.Seeds)
for _, err := range errs {
if err != nil {
return err
return nil, err
}
}
return nil
return netAddrs, nil
}
// randomly dial seeds until we connect to one or exhaust them
@ -492,13 +499,12 @@ func (r *PEXReactor) dialSeeds() {
if lSeeds == 0 {
return
}
seedAddrs, _ := p2p.NewNetAddressStrings(r.config.Seeds)
perm := cmn.RandPerm(lSeeds)
// perm := r.Switch.rng.Perm(lSeeds)
for _, i := range perm {
// dial a random seed
seedAddr := seedAddrs[i]
seedAddr := r.seedAddrs[i]
err := r.Switch.DialPeerWithAddress(seedAddr, false)
if err == nil {
return


+ 101
- 28
p2p/pex/pex_reactor_test.go View File

@ -211,31 +211,26 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) {
defer os.RemoveAll(dir) // nolint: errcheck
// 1. create seed
seed := p2p.MakeSwitch(
cfg,
0,
"127.0.0.1",
"123.123.123",
func(i int, sw *p2p.Switch) *p2p.Switch {
book := NewAddrBook(filepath.Join(dir, "addrbook0.json"), false)
book.SetLogger(log.TestingLogger())
sw.SetAddrBook(book)
sw.SetLogger(log.TestingLogger())
r := NewPEXReactor(book, &PEXReactorConfig{})
r.SetLogger(log.TestingLogger())
sw.AddReactor("pex", r)
return sw
},
)
seed.AddListener(
p2p.NewDefaultListener("tcp://"+seed.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
)
seed := testCreateSeed(dir, 0, []*p2p.NetAddress{}, []*p2p.NetAddress{})
require.Nil(t, seed.Start())
defer seed.Stop()
// 2. create usual peer with only seed configured.
peer := testCreatePeerWithSeed(dir, 1, seed)
require.Nil(t, peer.Start())
defer peer.Stop()
// 3. check that the peer connects to seed immediately
assertPeersWithTimeout(t, []*p2p.Switch{peer}, 10*time.Millisecond, 3*time.Second, 1)
}
func TestConnectionSpeedForPeerReceivedFromSeed(t *testing.T) {
// directory to store address books
dir, err := ioutil.TempDir("", "pex_reactor")
require.Nil(t, err)
defer os.RemoveAll(dir) // nolint: errcheck
// 1. create peer
peer := p2p.MakeSwitch(
cfg,
1,
@ -250,20 +245,34 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) {
r := NewPEXReactor(
book,
&PEXReactorConfig{
Seeds: []string{seed.NodeInfo().NetAddress().String()},
},
&PEXReactorConfig{},
)
r.SetLogger(log.TestingLogger())
sw.AddReactor("pex", r)
return sw
},
)
peer.AddListener(
p2p.NewDefaultListener("tcp://"+peer.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
)
require.Nil(t, peer.Start())
defer peer.Stop()
// 3. check that the peer connects to seed immediately
assertPeersWithTimeout(t, []*p2p.Switch{peer}, 10*time.Millisecond, 3*time.Second, 1)
// 2. Create seed which knows about the peer
seed := testCreateSeed(dir, 2, []*p2p.NetAddress{peer.NodeInfo().NetAddress()}, []*p2p.NetAddress{peer.NodeInfo().NetAddress()})
require.Nil(t, seed.Start())
defer seed.Stop()
// 3. create another peer with only seed configured.
secondPeer := testCreatePeerWithSeed(dir, 3, seed)
require.Nil(t, secondPeer.Start())
defer secondPeer.Stop()
// 4. check that the second peer connects to seed immediately
assertPeersWithTimeout(t, []*p2p.Switch{secondPeer}, 10*time.Millisecond, 3*time.Second, 1)
// 5. check that the second peer connects to the first peer immediately
assertPeersWithTimeout(t, []*p2p.Switch{secondPeer}, 10*time.Millisecond, 1*time.Second, 2)
}
func TestPEXReactorCrawlStatus(t *testing.T) {
@ -401,6 +410,7 @@ func assertPeersWithTimeout(
outbound, inbound, _ := s.NumPeers()
if outbound+inbound < nPeers {
allGood = false
break
}
}
remaining -= checkPeriod
@ -417,14 +427,77 @@ func assertPeersWithTimeout(
numPeersStr += fmt.Sprintf("%d => {outbound: %d, inbound: %d}, ", i, outbound, inbound)
}
t.Errorf(
"expected all switches to be connected to at least one peer (switches: %s)",
numPeersStr,
"expected all switches to be connected to at least %d peer(s) (switches: %s)",
nPeers, numPeersStr,
)
return
}
}
}
// Creates a seed which knows about the provided addresses / source address pairs.
// Starting and stopping the seed is left to the caller
func testCreateSeed(dir string, id int, knownAddrs, srcAddrs []*p2p.NetAddress) *p2p.Switch {
seed := p2p.MakeSwitch(
cfg,
id,
"127.0.0.1",
"123.123.123",
func(i int, sw *p2p.Switch) *p2p.Switch {
book := NewAddrBook(filepath.Join(dir, "addrbookSeed.json"), false)
book.SetLogger(log.TestingLogger())
for j := 0; j < len(knownAddrs); j++ {
book.AddAddress(knownAddrs[j], srcAddrs[j])
book.MarkGood(knownAddrs[j])
}
sw.SetAddrBook(book)
sw.SetLogger(log.TestingLogger())
r := NewPEXReactor(book, &PEXReactorConfig{})
r.SetLogger(log.TestingLogger())
sw.AddReactor("pex", r)
return sw
},
)
seed.AddListener(
p2p.NewDefaultListener("tcp://"+seed.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
)
return seed
}
// Creates a peer which knows about the provided seed.
// Starting and stopping the peer is left to the caller
func testCreatePeerWithSeed(dir string, id int, seed *p2p.Switch) *p2p.Switch {
peer := p2p.MakeSwitch(
cfg,
id,
"127.0.0.1",
"123.123.123",
func(i int, sw *p2p.Switch) *p2p.Switch {
book := NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", id)), false)
book.SetLogger(log.TestingLogger())
sw.SetAddrBook(book)
sw.SetLogger(log.TestingLogger())
r := NewPEXReactor(
book,
&PEXReactorConfig{
Seeds: []string{seed.NodeInfo().NetAddress().String()},
},
)
r.SetLogger(log.TestingLogger())
sw.AddReactor("pex", r)
return sw
},
)
peer.AddListener(
p2p.NewDefaultListener("tcp://"+peer.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
)
return peer
}
func createReactor(conf *PEXReactorConfig) (r *PEXReactor, book *addrBook) {
// directory to store address book
dir, err := ioutil.TempDir("", "pex_reactor")


+ 19
- 13
privval/priv_validator_test.go View File

@ -3,6 +3,7 @@ package privval
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"testing"
"time"
@ -11,22 +12,22 @@ import (
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/types"
)
func TestGenLoadValidator(t *testing.T) {
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenFilePV(tempFilePath)
tempFile, err := ioutil.TempFile("", "priv_validator_")
require.Nil(t, err)
privVal := GenFilePV(tempFile.Name())
height := int64(100)
privVal.LastHeight = height
privVal.Save()
addr := privVal.GetAddress()
privVal = LoadFilePV(tempFilePath)
privVal = LoadFilePV(tempFile.Name())
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved")
}
@ -34,7 +35,9 @@ func TestGenLoadValidator(t *testing.T) {
func TestLoadOrGenValidator(t *testing.T) {
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
tempFile, err := ioutil.TempFile("", "priv_validator_")
require.Nil(t, err)
tempFilePath := tempFile.Name()
if err := os.Remove(tempFilePath); err != nil {
t.Error(err)
}
@ -91,8 +94,9 @@ func TestUnmarshalValidator(t *testing.T) {
func TestSignVote(t *testing.T) {
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenFilePV(tempFilePath)
tempFile, err := ioutil.TempFile("", "priv_validator_")
require.Nil(t, err)
privVal := GenFilePV(tempFile.Name())
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}}
@ -101,7 +105,7 @@ func TestSignVote(t *testing.T) {
// sign a vote for first time
vote := newVote(privVal.Address, 0, height, round, voteType, block1)
err := privVal.SignVote("mychainid", vote)
err = privVal.SignVote("mychainid", vote)
assert.NoError(err, "expected no error signing vote")
// try to sign the same vote again; should be fine
@ -132,8 +136,9 @@ func TestSignVote(t *testing.T) {
func TestSignProposal(t *testing.T) {
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenFilePV(tempFilePath)
tempFile, err := ioutil.TempFile("", "priv_validator_")
require.Nil(t, err)
privVal := GenFilePV(tempFile.Name())
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
block2 := types.PartSetHeader{10, []byte{3, 2, 1}}
@ -141,7 +146,7 @@ func TestSignProposal(t *testing.T) {
// sign a proposal for first time
proposal := newProposal(height, round, block1)
err := privVal.SignProposal("mychainid", proposal)
err = privVal.SignProposal("mychainid", proposal)
assert.NoError(err, "expected no error signing proposal")
// try to sign the same proposal again; should be fine
@ -170,8 +175,9 @@ func TestSignProposal(t *testing.T) {
}
func TestDifferByTimestamp(t *testing.T) {
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenFilePV(tempFilePath)
tempFile, err := ioutil.TempFile("", "priv_validator_")
require.Nil(t, err)
privVal := GenFilePV(tempFile.Name())
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
height, round := int64(10), 1


+ 2
- 43
rpc/core/status.go View File

@ -104,20 +104,11 @@ func Status() (*ctypes.ResultStatus, error) {
return result, nil
}
const consensusTimeout = time.Second
func validatorAtHeight(h int64) *types.Validator {
lastBlockHeight, vals := getValidatorsWithTimeout(
consensusState,
consensusTimeout,
)
if lastBlockHeight == -1 {
return nil
}
privValAddress := pubKey.Address()
lastBlockHeight, vals := consensusState.GetValidators()
// if we're still at height h, search in the current validator set
if lastBlockHeight == h {
for _, val := range vals {
@ -140,35 +131,3 @@ func validatorAtHeight(h int64) *types.Validator {
return nil
}
type validatorRetriever interface {
GetValidators() (int64, []*types.Validator)
}
// NOTE: Consensus might halt, but we still need to process RPC requests (at
// least for endpoints whole output does not depend on consensus state).
func getValidatorsWithTimeout(
vr validatorRetriever,
t time.Duration,
) (int64, []*types.Validator) {
resultCh := make(chan struct {
lastBlockHeight int64
vals []*types.Validator
})
go func() {
h, v := vr.GetValidators()
resultCh <- struct {
lastBlockHeight int64
vals []*types.Validator
}{h, v}
}()
select {
case res := <-resultCh:
return res.lastBlockHeight, res.vals
case <-time.After(t):
if logger != nil {
logger.Error("Timed out querying validators from consensus", "timeout", t)
}
return -1, []*types.Validator{}
}
}

+ 0
- 39
rpc/core/status_test.go View File

@ -1,39 +0,0 @@
package core
import (
"testing"
"time"
"github.com/tendermint/tendermint/types"
)
func TestGetValidatorsWithTimeout(t *testing.T) {
height, vs := getValidatorsWithTimeout(
testValidatorReceiver{},
time.Millisecond,
)
if height != -1 {
t.Errorf("expected negative height")
}
if len(vs) != 0 {
t.Errorf("expected no validators")
}
}
type testValidatorReceiver struct{}
func (tr testValidatorReceiver) GetValidators() (int64, []*types.Validator) {
vs := []*types.Validator{}
for i := 0; i < 3; i++ {
v, _ := types.RandValidator(true, 10)
vs = append(vs, v)
}
time.Sleep(time.Millisecond)
return 10, vs
}

+ 7
- 4
state/execution.go View File

@ -188,9 +188,12 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
// Begin block
_, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
Hash: block.Hash(),
Header: types.TM2PB.Header(&block.Header),
Validators: signVals,
Hash: block.Hash(),
Header: types.TM2PB.Header(&block.Header),
LastCommitInfo: abci.LastCommitInfo{
CommitRound: int32(block.LastCommit.Round()),
Validators: signVals,
},
ByzantineValidators: byzVals,
})
if err != nil {
@ -245,7 +248,7 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS
vote = block.LastCommit.Precommits[i]
}
val := abci.SigningValidator{
Validator: types.TM2PB.Validator(val),
Validator: types.TM2PB.ValidatorWithoutPubKey(val),
SignedLastBlock: vote != nil,
}
signVals[i] = val


+ 1
- 1
state/execution_test.go View File

@ -293,7 +293,7 @@ func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
}
func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
app.Validators = req.Validators
app.Validators = req.LastCommitInfo.Validators
app.ByzantineValidators = req.ByzantineValidators
return abci.ResponseBeginBlock{}
}


+ 8
- 1
types/protobuf.go View File

@ -50,6 +50,13 @@ func (tm2pb) Header(header *Header) abci.Header {
}
}
func (tm2pb) ValidatorWithoutPubKey(val *Validator) abci.Validator {
return abci.Validator{
Address: val.PubKey.Address(),
Power: val.VotingPower,
}
}
// XXX: panics on unknown pubkey type
func (tm2pb) Validator(val *Validator) abci.Validator {
return abci.Validator{
@ -129,7 +136,7 @@ func (tm2pb) Evidence(ev Evidence, valSet *ValidatorSet, evTime time.Time) abci.
return abci.Evidence{
Type: evType,
Validator: TM2PB.Validator(val),
Validator: TM2PB.ValidatorWithoutPubKey(val),
Height: ev.Height(),
Time: evTime,
TotalVotingPower: valSet.TotalVotingPower(),


+ 21
- 0
types/protobuf_test.go View File

@ -102,6 +102,9 @@ func TestABCIEvidence(t *testing.T) {
)
assert.Equal(t, "duplicate/vote", abciEv.Type)
// test we do not send pubkeys
assert.Empty(t, abciEv.Validator.PubKey)
}
type pubKeyEddie struct{}
@ -120,3 +123,21 @@ func TestABCIValidatorFromPubKeyAndPower(t *testing.T) {
assert.Panics(t, func() { TM2PB.ValidatorFromPubKeyAndPower(nil, 10) })
assert.Panics(t, func() { TM2PB.ValidatorFromPubKeyAndPower(pubKeyEddie{}, 10) })
}
func TestABCIValidatorWithoutPubKey(t *testing.T) {
pkEd := ed25519.GenPrivKey().PubKey()
abciVal := TM2PB.ValidatorWithoutPubKey(&Validator{
Address: pkEd.Address(),
PubKey: pkEd,
VotingPower: 10,
})
// pubkey must be nil
tmValExpected := abci.Validator{
Address: pkEd.Address(),
Power: 10,
}
assert.Equal(t, tmValExpected, abciVal)
}

Loading…
Cancel
Save