From e8b0458f16747a5c1cc5eea361da945241adb105 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 21 Dec 2017 14:04:05 -0600 Subject: [PATCH 001/124] check for overflow and underflow while choosing proposer Refs #919 --- types/validator_set.go | 45 +++++++++++++++++++++++++++++++++---- types/validator_set_test.go | 44 ++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/types/validator_set.go b/types/validator_set.go index 134e4e06..9aaa6830 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -48,12 +48,17 @@ func NewValidatorSet(vals []*Validator) *ValidatorSet { } // incrementAccum and update the proposer -// TODO: mind the overflow when times and votingPower shares too large. func (valSet *ValidatorSet) IncrementAccum(times int) { // Add VotingPower * times to each validator and order into heap. validatorsHeap := cmn.NewHeap() for _, val := range valSet.Validators { - val.Accum += val.VotingPower * int64(times) // TODO: mind overflow + res, overflow := signedMulWithOverflowCheck(val.VotingPower, int64(times)) + // check for overflow both multiplication and sum + if !overflow && val.Accum <= mostPositive-res { + val.Accum += res + } else { + val.Accum = mostPositive + } validatorsHeap.Push(val, accumComparable{val}) } @@ -63,7 +68,13 @@ func (valSet *ValidatorSet) IncrementAccum(times int) { if i == times-1 { valSet.Proposer = mostest } - mostest.Accum -= int64(valSet.TotalVotingPower()) + + // mind underflow + if mostest.Accum >= mostNegative+valSet.TotalVotingPower() { + mostest.Accum -= valSet.TotalVotingPower() + } else { + mostest.Accum = mostNegative + } validatorsHeap.Update(mostest, accumComparable{mostest}) } } @@ -117,7 +128,13 @@ func (valSet *ValidatorSet) Size() int { func (valSet *ValidatorSet) TotalVotingPower() int64 { if valSet.totalVotingPower == 0 { for _, val := range valSet.Validators { - valSet.totalVotingPower += val.VotingPower + // mind overflow + if valSet.totalVotingPower <= mostPositive-val.VotingPower { + valSet.totalVotingPower += val.VotingPower + } else { + valSet.totalVotingPower = mostPositive + return valSet.totalVotingPower + } } } return valSet.totalVotingPower @@ -425,3 +442,23 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*P sort.Sort(PrivValidatorsByAddress(privValidators)) return valSet, privValidators } + +const mostNegative int64 = -mostPositive - 1 +const mostPositive int64 = 1<<63 - 1 + +func signedMulWithOverflowCheck(a, b int64) (int64, bool) { + if a == 0 || b == 0 { + return 0, false + } + if a == 1 { + return b, false + } + if b == 1 { + return a, false + } + if a == mostNegative || b == mostNegative { + return -1, true + } + c := a * b + return c, c/b != a +} diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 572b7b00..c65f507f 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -5,8 +5,9 @@ import ( "strings" "testing" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" + "github.com/stretchr/testify/assert" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" ) @@ -190,6 +191,45 @@ func TestProposerSelection3(t *testing.T) { } } +func TestValidatorSetIncrementAccumOverflows(t *testing.T) { + // NewValidatorSet calls IncrementAccum(1) + vset := NewValidatorSet([]*Validator{ + // too much voting power + 0: {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, + // too big accum + 1: {Address: []byte("b"), VotingPower: 10, Accum: mostPositive}, + // almost too big accum + 2: {Address: []byte("c"), VotingPower: 10, Accum: mostPositive - 5}, + }) + + assert.Equal(t, int64(0), vset.Validators[0].Accum, "0") // because we decrement val with most voting power + assert.Equal(t, mostPositive, vset.Validators[1].Accum, "1") + assert.Equal(t, mostPositive, vset.Validators[2].Accum, "2") +} + +func TestValidatorSetIncrementAccumUnderflows(t *testing.T) { + // NewValidatorSet calls IncrementAccum(1) + vset := NewValidatorSet([]*Validator{ + 0: {Address: []byte("a"), VotingPower: mostPositive, Accum: mostNegative}, + 1: {Address: []byte("b"), VotingPower: 1, Accum: mostNegative}, + }) + + vset.IncrementAccum(5) + + assert.Equal(t, mostNegative, vset.Validators[0].Accum, "0") + assert.Equal(t, mostNegative, vset.Validators[1].Accum, "1") +} + +func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) { + vset := NewValidatorSet([]*Validator{ + {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, + {Address: []byte("b"), VotingPower: mostPositive, Accum: 0}, + {Address: []byte("c"), VotingPower: mostPositive, Accum: 0}, + }) + + assert.Equal(t, mostPositive, vset.TotalVotingPower()) +} + func BenchmarkValidatorSetCopy(b *testing.B) { b.StopTimer() vset := NewValidatorSet([]*Validator{}) From 69c3a7640bc48957ed3984596da276264b3f1038 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 25 Dec 2017 18:38:35 -0600 Subject: [PATCH 002/124] add safeAdd & safeSub plus quickcheck tests --- types/validator_set.go | 39 +++++++++++++++++++++++++++++-------- types/validator_set_test.go | 35 ++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/types/validator_set.go b/types/validator_set.go index 9aaa6830..a9b98659 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -52,10 +52,15 @@ func (valSet *ValidatorSet) IncrementAccum(times int) { // Add VotingPower * times to each validator and order into heap. validatorsHeap := cmn.NewHeap() for _, val := range valSet.Validators { - res, overflow := signedMulWithOverflowCheck(val.VotingPower, int64(times)) // check for overflow both multiplication and sum - if !overflow && val.Accum <= mostPositive-res { - val.Accum += res + res, overflow := safeMul(val.VotingPower, int64(times)) + if !overflow { + res2, overflow2 := safeAdd(val.Accum, res) + if !overflow2 { + val.Accum = res2 + } else { + val.Accum = mostPositive + } } else { val.Accum = mostPositive } @@ -70,8 +75,9 @@ func (valSet *ValidatorSet) IncrementAccum(times int) { } // mind underflow - if mostest.Accum >= mostNegative+valSet.TotalVotingPower() { - mostest.Accum -= valSet.TotalVotingPower() + res, underflow := safeSub(mostest.Accum, valSet.TotalVotingPower()) + if !underflow { + mostest.Accum = res } else { mostest.Accum = mostNegative } @@ -129,8 +135,9 @@ func (valSet *ValidatorSet) TotalVotingPower() int64 { if valSet.totalVotingPower == 0 { for _, val := range valSet.Validators { // mind overflow - if valSet.totalVotingPower <= mostPositive-val.VotingPower { - valSet.totalVotingPower += val.VotingPower + res, overflow := safeAdd(valSet.totalVotingPower, val.VotingPower) + if !overflow { + valSet.totalVotingPower = res } else { valSet.totalVotingPower = mostPositive return valSet.totalVotingPower @@ -443,10 +450,13 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*P return valSet, privValidators } +/////////////////////////////////////////////////////////////////////////////// +// Safe multiplication and addition/subtraction + const mostNegative int64 = -mostPositive - 1 const mostPositive int64 = 1<<63 - 1 -func signedMulWithOverflowCheck(a, b int64) (int64, bool) { +func safeMul(a, b int64) (int64, bool) { if a == 0 || b == 0 { return 0, false } @@ -462,3 +472,16 @@ func signedMulWithOverflowCheck(a, b int64) (int64, bool) { c := a * b return c, c/b != a } + +func safeAdd(a, b int64) (int64, bool) { + if b > 0 && a > mostPositive-b { + return -1, true + } else if b < 0 && a < mostNegative-b { + return -1, true + } + return a + b, false +} + +func safeSub(a, b int64) (int64, bool) { + return safeAdd(a, -b) +} diff --git a/types/validator_set_test.go b/types/validator_set_test.go index c65f507f..dd2a5999 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -4,6 +4,7 @@ import ( "bytes" "strings" "testing" + "testing/quick" "github.com/stretchr/testify/assert" crypto "github.com/tendermint/go-crypto" @@ -191,6 +192,16 @@ func TestProposerSelection3(t *testing.T) { } } +func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) { + vset := NewValidatorSet([]*Validator{ + {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, + {Address: []byte("b"), VotingPower: mostPositive, Accum: 0}, + {Address: []byte("c"), VotingPower: mostPositive, Accum: 0}, + }) + + assert.Equal(t, mostPositive, vset.TotalVotingPower()) +} + func TestValidatorSetIncrementAccumOverflows(t *testing.T) { // NewValidatorSet calls IncrementAccum(1) vset := NewValidatorSet([]*Validator{ @@ -220,14 +231,24 @@ func TestValidatorSetIncrementAccumUnderflows(t *testing.T) { assert.Equal(t, mostNegative, vset.Validators[1].Accum, "1") } -func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) { - vset := NewValidatorSet([]*Validator{ - {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, - {Address: []byte("b"), VotingPower: mostPositive, Accum: 0}, - {Address: []byte("c"), VotingPower: mostPositive, Accum: 0}, - }) +func TestSafeMul(t *testing.T) { + f := func(a, b int64) bool { + c, overflow := safeMul(a, b) + return overflow || (!overflow && c == a*b) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} - assert.Equal(t, mostPositive, vset.TotalVotingPower()) +func TestSafeAdd(t *testing.T) { + f := func(a, b int64) bool { + c, overflow := safeAdd(a, b) + return overflow || (!overflow && c == a+b) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } } func BenchmarkValidatorSetCopy(b *testing.B) { From 1339a44402b7e4f343b1051658c9b267a0cd970e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 26 Dec 2017 14:13:12 -0600 Subject: [PATCH 003/124] add safe*Clip funcs --- types/validator_set.go | 80 +++++++++++++++++++++++-------------- types/validator_set_test.go | 47 ++++++++++++++++------ 2 files changed, 83 insertions(+), 44 deletions(-) diff --git a/types/validator_set.go b/types/validator_set.go index a9b98659..3876c19d 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -3,6 +3,7 @@ package types import ( "bytes" "fmt" + "math" "sort" "strings" @@ -53,17 +54,7 @@ func (valSet *ValidatorSet) IncrementAccum(times int) { validatorsHeap := cmn.NewHeap() for _, val := range valSet.Validators { // check for overflow both multiplication and sum - res, overflow := safeMul(val.VotingPower, int64(times)) - if !overflow { - res2, overflow2 := safeAdd(val.Accum, res) - if !overflow2 { - val.Accum = res2 - } else { - val.Accum = mostPositive - } - } else { - val.Accum = mostPositive - } + val.Accum = safeAddClip(val.Accum, safeMulClip(val.VotingPower, int64(times))) validatorsHeap.Push(val, accumComparable{val}) } @@ -75,12 +66,7 @@ func (valSet *ValidatorSet) IncrementAccum(times int) { } // mind underflow - res, underflow := safeSub(mostest.Accum, valSet.TotalVotingPower()) - if !underflow { - mostest.Accum = res - } else { - mostest.Accum = mostNegative - } + mostest.Accum = safeSubClip(mostest.Accum, valSet.TotalVotingPower()) validatorsHeap.Update(mostest, accumComparable{mostest}) } } @@ -135,13 +121,7 @@ func (valSet *ValidatorSet) TotalVotingPower() int64 { if valSet.totalVotingPower == 0 { for _, val := range valSet.Validators { // mind overflow - res, overflow := safeAdd(valSet.totalVotingPower, val.VotingPower) - if !overflow { - valSet.totalVotingPower = res - } else { - valSet.totalVotingPower = mostPositive - return valSet.totalVotingPower - } + valSet.totalVotingPower = safeAddClip(valSet.totalVotingPower, val.VotingPower) } } return valSet.totalVotingPower @@ -453,9 +433,6 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*P /////////////////////////////////////////////////////////////////////////////// // Safe multiplication and addition/subtraction -const mostNegative int64 = -mostPositive - 1 -const mostPositive int64 = 1<<63 - 1 - func safeMul(a, b int64) (int64, bool) { if a == 0 || b == 0 { return 0, false @@ -466,7 +443,7 @@ func safeMul(a, b int64) (int64, bool) { if b == 1 { return a, false } - if a == mostNegative || b == mostNegative { + if a == math.MinInt64 || b == math.MinInt64 { return -1, true } c := a * b @@ -474,14 +451,55 @@ func safeMul(a, b int64) (int64, bool) { } func safeAdd(a, b int64) (int64, bool) { - if b > 0 && a > mostPositive-b { + if b > 0 && a > math.MaxInt64-b { return -1, true - } else if b < 0 && a < mostNegative-b { + } else if b < 0 && a < math.MinInt64-b { return -1, true } return a + b, false } func safeSub(a, b int64) (int64, bool) { - return safeAdd(a, -b) + if b > 0 && a < math.MinInt64+b { + return -1, true + } else if b < 0 && a > math.MaxInt64+b { + return -1, true + } + return a - b, false +} + +func safeMulClip(a, b int64) int64 { + c, overflow := safeMul(a, b) + if overflow { + if (a < 0 || b < 0) && !(a < 0 && b < 0) { + return math.MinInt64 + } else { + return math.MaxInt64 + } + } + return c +} + +func safeAddClip(a, b int64) int64 { + c, overflow := safeAdd(a, b) + if overflow { + if b < 0 { + return math.MinInt64 + } else { + return math.MaxInt64 + } + } + return c +} + +func safeSubClip(a, b int64) int64 { + c, overflow := safeSub(a, b) + if overflow { + if b > 0 { + return math.MinInt64 + } else { + return math.MaxInt64 + } + } + return c } diff --git a/types/validator_set_test.go b/types/validator_set_test.go index dd2a5999..9c751237 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "math" "strings" "testing" "testing/quick" @@ -194,41 +195,41 @@ func TestProposerSelection3(t *testing.T) { func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) { vset := NewValidatorSet([]*Validator{ - {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, - {Address: []byte("b"), VotingPower: mostPositive, Accum: 0}, - {Address: []byte("c"), VotingPower: mostPositive, Accum: 0}, + {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, + {Address: []byte("b"), VotingPower: math.MaxInt64, Accum: 0}, + {Address: []byte("c"), VotingPower: math.MaxInt64, Accum: 0}, }) - assert.Equal(t, mostPositive, vset.TotalVotingPower()) + assert.EqualValues(t, math.MaxInt64, vset.TotalVotingPower()) } func TestValidatorSetIncrementAccumOverflows(t *testing.T) { // NewValidatorSet calls IncrementAccum(1) vset := NewValidatorSet([]*Validator{ // too much voting power - 0: {Address: []byte("a"), VotingPower: mostPositive, Accum: 0}, + 0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, // too big accum - 1: {Address: []byte("b"), VotingPower: 10, Accum: mostPositive}, + 1: {Address: []byte("b"), VotingPower: 10, Accum: math.MaxInt64}, // almost too big accum - 2: {Address: []byte("c"), VotingPower: 10, Accum: mostPositive - 5}, + 2: {Address: []byte("c"), VotingPower: 10, Accum: math.MaxInt64 - 5}, }) assert.Equal(t, int64(0), vset.Validators[0].Accum, "0") // because we decrement val with most voting power - assert.Equal(t, mostPositive, vset.Validators[1].Accum, "1") - assert.Equal(t, mostPositive, vset.Validators[2].Accum, "2") + assert.EqualValues(t, math.MaxInt64, vset.Validators[1].Accum, "1") + assert.EqualValues(t, math.MaxInt64, vset.Validators[2].Accum, "2") } func TestValidatorSetIncrementAccumUnderflows(t *testing.T) { // NewValidatorSet calls IncrementAccum(1) vset := NewValidatorSet([]*Validator{ - 0: {Address: []byte("a"), VotingPower: mostPositive, Accum: mostNegative}, - 1: {Address: []byte("b"), VotingPower: 1, Accum: mostNegative}, + 0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: math.MinInt64}, + 1: {Address: []byte("b"), VotingPower: 1, Accum: math.MinInt64}, }) vset.IncrementAccum(5) - assert.Equal(t, mostNegative, vset.Validators[0].Accum, "0") - assert.Equal(t, mostNegative, vset.Validators[1].Accum, "1") + assert.EqualValues(t, math.MinInt64, vset.Validators[0].Accum, "0") + assert.EqualValues(t, math.MinInt64, vset.Validators[1].Accum, "1") } func TestSafeMul(t *testing.T) { @@ -251,6 +252,26 @@ func TestSafeAdd(t *testing.T) { } } +func TestSafeMulClip(t *testing.T) { + assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MinInt64, math.MinInt64)) + assert.EqualValues(t, math.MinInt64, safeMulClip(math.MaxInt64, math.MinInt64)) + assert.EqualValues(t, math.MinInt64, safeMulClip(math.MinInt64, math.MaxInt64)) + assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MaxInt64, 2)) +} + +func TestSafeAddClip(t *testing.T) { + assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, 10)) + assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, math.MaxInt64)) + assert.EqualValues(t, math.MinInt64, safeAddClip(math.MinInt64, -10)) +} + +func TestSafeSubClip(t *testing.T) { + assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, 10)) + assert.EqualValues(t, 0, safeSubClip(math.MinInt64, math.MinInt64)) + assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, math.MaxInt64)) + assert.EqualValues(t, math.MaxInt64, safeSubClip(math.MaxInt64, -10)) +} + func BenchmarkValidatorSetCopy(b *testing.B) { b.StopTimer() vset := NewValidatorSet([]*Validator{}) From 70ba60885057c9bf351b1a92db660d96448c6967 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Tue, 10 Oct 2017 12:56:18 -0400 Subject: [PATCH 004/124] config: write all default options to config file config: test the default file docs: spiff up config config: minor fixes & comments config: simplify test config; use a seperate config directory, #556 config: update docs & parameterize file paths config: PR comments config: use the default object fix a rebase error --- CHANGELOG.md | 1 + cmd/tendermint/commands/testnet.go | 13 +- cmd/tendermint/main.go | 4 +- config/config.go | 35 +++-- config/toml.go | 207 +++++++++++++++++++++------ config/toml_test.go | 39 ++++- consensus/test_data/build.sh | 148 +++++++++++++++++++ docs/deploy-testnets.rst | 2 +- docs/specification/configuration.rst | 195 ++++++++++++++++++------- docs/specification/genesis.rst | 2 +- docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 16 +-- rpc/core/doc.go | 2 +- scripts/debora/unsafe_reset_net.sh | 2 +- 14 files changed, 543 insertions(+), 125 deletions(-) create mode 100755 consensus/test_data/build.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f0809d..392b7e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ BREAKING CHANGES: - Better support for injecting randomness - Pass evidence/voteInfo through ABCI - Upgrade consensus for more real-time use of evidence +- the files usually found in `~/.tendermint` (`config.toml`, `genesis.json`, and `priv_validator.json`) are now in `~/.tendermint/config`. The `$TMHOME/data/` directory remains unchanged. FEATURES: - Peer reputation management diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 2c859df2..f5551a95 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -2,11 +2,12 @@ package commands import ( "fmt" - "path" + "path/filepath" "time" "github.com/spf13/cobra" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" ) @@ -35,6 +36,7 @@ var TestnetFilesCmd = &cobra.Command{ func testnetFiles(cmd *cobra.Command, args []string) { genVals := make([]types.GenesisValidator, nValidators) + defaultConfig := cfg.DefaultBaseConfig() // Initialize core dir and priv_validator.json's for i := 0; i < nValidators; i++ { @@ -44,7 +46,7 @@ func testnetFiles(cmd *cobra.Command, args []string) { cmn.Exit(err.Error()) } // Read priv_validator.json to populate vals - privValFile := path.Join(dataDir, mach, "priv_validator.json") + privValFile := filepath.Join(dataDir, mach, defaultConfig.PrivValidator) privVal := types.LoadPrivValidatorFS(privValFile) genVals[i] = types.GenesisValidator{ PubKey: privVal.GetPubKey(), @@ -63,7 +65,7 @@ func testnetFiles(cmd *cobra.Command, args []string) { // Write genesis file. for i := 0; i < nValidators; i++ { mach := cmn.Fmt("mach%d", i) - if err := genDoc.SaveAs(path.Join(dataDir, mach, "genesis.json")); err != nil { + if err := genDoc.SaveAs(filepath.Join(dataDir, mach, defaultConfig.Genesis)); err != nil { panic(err) } } @@ -73,14 +75,15 @@ func testnetFiles(cmd *cobra.Command, args []string) { // Initialize per-machine core directory func initMachCoreDirectory(base, mach string) error { - dir := path.Join(base, mach) + dir := filepath.Join(base, mach) err := cmn.EnsureDir(dir, 0777) if err != nil { return err } // Create priv_validator.json file if not present - ensurePrivValidator(path.Join(dir, "priv_validator.json")) + defaultConfig := cfg.DefaultBaseConfig() + ensurePrivValidator(filepath.Join(dir, defaultConfig.PrivValidator)) return nil } diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index c24cfe19..17b5a585 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -2,10 +2,12 @@ package main import ( "os" + "path/filepath" "github.com/tendermint/tmlibs/cli" cmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + cfg "github.com/tendermint/tendermint/config" nm "github.com/tendermint/tendermint/node" ) @@ -37,7 +39,7 @@ func main() { // Create & start node rootCmd.AddCommand(cmd.NewRunNodeCmd(nodeFunc)) - cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv("$HOME/.tendermint")) + cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv(filepath.Join("$HOME", cfg.DefaultTendermintDir))) if err := cmd.Execute(); err != nil { panic(err) } diff --git a/config/config.go b/config/config.go index 5d4a8ef6..1585fcd0 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,25 @@ import ( "time" ) +// Note: Most of the structs & relevant comments + the +// default configuration options were used to manually +// generate the config.toml. Please reflect any changes +// made here in the defaultConfigTemplate constant in +// config/toml.go +var ( + DefaultTendermintDir = ".tendermint" + defaultConfigDir = "config" + defaultDataDir = "data" + + defaultConfigFileName = "config.toml" + defaultGenesisJSONName = "genesis.json" + defaultPrivValName = "priv_validator.json" + + defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) + defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) + defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName) +) + // Config defines the top level configuration for a Tendermint node type Config struct { // Top level options use an anonymous struct @@ -66,10 +85,10 @@ type BaseConfig struct { // The ID of the chain to join (should be signed with every transaction and vote) ChainID string `mapstructure:"chain_id"` - // A JSON file containing the initial validator set and other meta data + // Path to the JSON file containing the initial validator set and other meta data Genesis string `mapstructure:"genesis_file"` - // A JSON file containing the private key to use as a validator in the consensus protocol + // Path to the JSON file containing the private key to use as a validator in the consensus protocol PrivValidator string `mapstructure:"priv_validator_file"` // A custom human readable name for this node @@ -107,8 +126,8 @@ type BaseConfig struct { // DefaultBaseConfig returns a default base configuration for a Tendermint node func DefaultBaseConfig() BaseConfig { return BaseConfig{ - Genesis: "genesis.json", - PrivValidator: "priv_validator.json", + Genesis: defaultGenesisJSONPath, + PrivValidator: defaultPrivValPath, Moniker: defaultMoniker, ProxyApp: "tcp://127.0.0.1:46658", ABCI: "socket", @@ -279,7 +298,7 @@ func DefaultMempoolConfig() *MempoolConfig { Recheck: true, RecheckEmpty: true, Broadcast: true, - WalPath: "data/mempool.wal", + WalPath: filepath.Join(defaultDataDir, "mempool.wal"), } } @@ -299,7 +318,7 @@ type ConsensusConfig struct { WalLight bool `mapstructure:"wal_light"` walFile string // overrides WalPath if set - // All timeouts are in ms + // All timeouts are in milliseconds TimeoutPropose int `mapstructure:"timeout_propose"` TimeoutProposeDelta int `mapstructure:"timeout_propose_delta"` TimeoutPrevote int `mapstructure:"timeout_prevote"` @@ -319,7 +338,7 @@ type ConsensusConfig struct { CreateEmptyBlocks bool `mapstructure:"create_empty_blocks"` CreateEmptyBlocksInterval int `mapstructure:"create_empty_blocks_interval"` - // Reactor sleep duration parameters are in ms + // Reactor sleep duration parameters are in milliseconds PeerGossipSleepDuration int `mapstructure:"peer_gossip_sleep_duration"` PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"` } @@ -367,7 +386,7 @@ func (cfg *ConsensusConfig) PeerQueryMaj23Sleep() time.Duration { // DefaultConsensusConfig returns a default configuration for the consensus service func DefaultConsensusConfig() *ConsensusConfig { return &ConsensusConfig{ - WalPath: "data/cs.wal/wal", + WalPath: filepath.Join(defaultDataDir, "cs.wal/wal"), WalLight: false, TimeoutPropose: 3000, TimeoutProposeDelta: 500, diff --git a/config/toml.go b/config/toml.go index 735f45c1..56a5a130 100644 --- a/config/toml.go +++ b/config/toml.go @@ -1,25 +1,37 @@ package config import ( + "bytes" "os" - "path" "path/filepath" - "strings" + "text/template" cmn "github.com/tendermint/tmlibs/common" ) +var configTemplate *template.Template + +func init() { + var err error + if configTemplate, err = template.New("configFileTemplate").Parse(defaultConfigTemplate); err != nil { + panic(err) + } +} + /****** these are for production settings ***********/ func EnsureRoot(rootDir string) { if err := cmn.EnsureDir(rootDir, 0700); err != nil { cmn.PanicSanity(err.Error()) } - if err := cmn.EnsureDir(rootDir+"/data", 0700); err != nil { + if err := cmn.EnsureDir(filepath.Join(rootDir, defaultConfigDir), 0700); err != nil { + cmn.PanicSanity(err.Error()) + } + if err := cmn.EnsureDir(filepath.Join(rootDir, defaultDataDir), 0700); err != nil { cmn.PanicSanity(err.Error()) } - configFilePath := path.Join(rootDir, "config.toml") + configFilePath := filepath.Join(rootDir, defaultConfigFilePath) // Write default config file if missing. if !cmn.FileExists(configFilePath) { @@ -27,26 +39,153 @@ func EnsureRoot(rootDir string) { } } -var defaultConfigTmpl = `# This is a TOML config file. +// XXX: this func should probably be called by cmd/tendermint/commands/init.go +// alongside the writing of the genesis.json and priv_validator.json +func writeConfigFile(configFilePath string) { + var buffer bytes.Buffer + + if err := configTemplate.Execute(&buffer, DefaultConfig()); err != nil { + panic(err) + } + + cmn.MustWriteFile(configFilePath, buffer.Bytes(), 0644) +} + +// Note: any changes to the comments/variables/mapstructure +// must be reflected in the appropriate struct in config/config.go +const defaultConfigTemplate = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml -proxy_app = "tcp://127.0.0.1:46658" -moniker = "__MONIKER__" -fast_sync = true -db_backend = "leveldb" -log_level = "state:info,*:error" +##### main base config options ##### +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "{{ .BaseConfig.ProxyApp }}" + +# A custom human readable name for this node +moniker = "{{ .BaseConfig.Moniker }}" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = {{ .BaseConfig.FastSync }} + +# Database backend: leveldb | memdb +db_backend = "{{ .BaseConfig.DBBackend }}" + +# Database directory +db_path = "{{ .BaseConfig.DBPath }}" + +# Output level for logging +log_level = "{{ .BaseConfig.LogLevel }}" + +##### additional base config options ##### + +# The ID of the chain to join (should be signed with every transaction and vote) +chain_id = "{{ .BaseConfig.ChainID }}" + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "{{ .BaseConfig.Genesis }}" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_file = "{{ .BaseConfig.PrivValidator }}" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "{{ .BaseConfig.ABCI }}" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "{{ .BaseConfig.ProfListenAddress }}" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = {{ .BaseConfig.FilterPeers }} + +# What indexer to use for transactions +tx_index = "{{ .BaseConfig.TxIndex }}" + +##### advanced configuration options ##### + +##### rpc server configuration options ##### [rpc] -laddr = "tcp://0.0.0.0:46657" +# TCP or UNIX socket address for the RPC server to listen on +laddr = "{{ .RPC.ListenAddress }}" + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "{{ .RPC.GRPCListenAddress }}" + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = {{ .RPC.Unsafe }} + +##### peer to peer configuration options ##### [p2p] -laddr = "tcp://0.0.0.0:46656" -seeds = "" -` -func defaultConfig(moniker string) string { - return strings.Replace(defaultConfigTmpl, "__MONIKER__", moniker, -1) -} +# Address to listen for incoming connections +laddr = "{{ .P2P.ListenAddress }}" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Path to address book +addr_book_file = "{{ .P2P.AddrBook }}" + +# Set true for strict address routability rules +addr_book_strict = {{ .P2P.AddrBookStrict }} + +# Time to wait before flushing messages out on the connection, in ms +flush_throttle_timeout = {{ .P2P.FlushThrottleTimeout }} + +# Maximum number of peers to connect to +max_num_peers = {{ .P2P.MaxNumPeers }} + +# Maximum size of a message packet payload, in bytes +max_msg_packet_payload_size = {{ .P2P.MaxMsgPacketPayloadSize }} + +# Rate at which packets can be sent, in bytes/second +send_rate = {{ .P2P.SendRate }} + +# Rate at which packets can be received, in bytes/second +recv_rate = {{ .P2P.RecvRate }} + +##### mempool configuration options ##### +[mempool] + +recheck = {{ .Mempool.Recheck }} +recheck_empty = {{ .Mempool.RecheckEmpty }} +broadcast = {{ .Mempool.Broadcast }} +wal_dir = "{{ .Mempool.WalPath }}" + +##### consensus configuration options ##### +[consensus] + +wal_file = "{{ .Consensus.WalPath }}" +wal_light = {{ .Consensus.WalLight }} + +# All timeouts are in milliseconds +timeout_propose = {{ .Consensus.TimeoutPropose }} +timeout_propose_delta = {{ .Consensus.TimeoutProposeDelta }} +timeout_prevote = {{ .Consensus.TimeoutPrevote }} +timeout_prevote_delta = {{ .Consensus.TimeoutPrevoteDelta }} +timeout_precommit = {{ .Consensus.TimeoutPrecommit }} +timeout_precommit_delta = {{ .Consensus.TimeoutPrecommitDelta }} +timeout_commit = {{ .Consensus.TimeoutCommit }} + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = {{ .Consensus.SkipTimeoutCommit }} + +# BlockSize +max_block_size_txs = {{ .Consensus.MaxBlockSizeTxs }} +max_block_size_bytes = {{ .Consensus.MaxBlockSizeBytes }} + +# EmptyBlocks mode and possible interval between empty blocks in seconds +create_empty_blocks = {{ .Consensus.CreateEmptyBlocks }} +create_empty_blocks_interval = {{ .Consensus.CreateEmptyBlocksInterval }} + +# Reactor sleep duration parameters are in milliseconds +peer_gossip_sleep_duration = {{ .Consensus.PeerGossipSleepDuration }} +peer_query_maj23_sleep_duration = {{ .Consensus.PeerQueryMaj23SleepDuration }} +` /****** these are for test settings ***********/ @@ -69,13 +208,17 @@ func ResetTestRoot(testName string) *Config { if err := cmn.EnsureDir(rootDir, 0700); err != nil { cmn.PanicSanity(err.Error()) } - if err := cmn.EnsureDir(rootDir+"/data", 0700); err != nil { + if err := cmn.EnsureDir(filepath.Join(rootDir, defaultConfigDir), 0700); err != nil { + cmn.PanicSanity(err.Error()) + } + if err := cmn.EnsureDir(filepath.Join(rootDir, defaultDataDir), 0700); err != nil { cmn.PanicSanity(err.Error()) } - configFilePath := path.Join(rootDir, "config.toml") - genesisFilePath := path.Join(rootDir, "genesis.json") - privFilePath := path.Join(rootDir, "priv_validator.json") + baseConfig := DefaultBaseConfig() + configFilePath := filepath.Join(rootDir, defaultConfigFilePath) + genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis) + privFilePath := filepath.Join(rootDir, baseConfig.PrivValidator) // Write default config file if missing. if !cmn.FileExists(configFilePath) { @@ -91,28 +234,6 @@ func ResetTestRoot(testName string) *Config { return config } -var testConfigTmpl = `# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -proxy_app = "dummy" -moniker = "__MONIKER__" -fast_sync = false -db_backend = "memdb" -log_level = "info" - -[rpc] -laddr = "tcp://0.0.0.0:36657" - -[p2p] -laddr = "tcp://0.0.0.0:36656" -seeds = "" -` - -func testConfig(moniker string) (testConfig string) { - testConfig = strings.Replace(testConfigTmpl, "__MONIKER__", moniker, -1) - return -} - var testGenesis = `{ "genesis_time": "0001-01-01T00:00:00.000Z", "chain_id": "tendermint_test", diff --git a/config/toml_test.go b/config/toml_test.go index f927a14c..e98719f6 100644 --- a/config/toml_test.go +++ b/config/toml_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -30,7 +31,7 @@ func TestEnsureRoot(t *testing.T) { EnsureRoot(tmpDir) // make sure config is set properly - data, err := ioutil.ReadFile(filepath.Join(tmpDir, "config.toml")) + data, err := ioutil.ReadFile(filepath.Join(tmpDir, defaultConfigFilePath)) require.Nil(err) assert.Equal([]byte(defaultConfig(defaultMoniker)), data) @@ -47,11 +48,41 @@ func TestEnsureTestRoot(t *testing.T) { rootDir := cfg.RootDir // make sure config is set properly - data, err := ioutil.ReadFile(filepath.Join(rootDir, "config.toml")) + data, err := ioutil.ReadFile(filepath.Join(rootDir, defaultConfigFilePath)) require.Nil(err) assert.Equal([]byte(testConfig(defaultMoniker)), data) // TODO: make sure the cfg returned and testconfig are the same! - - ensureFiles(t, rootDir, "data", "genesis.json", "priv_validator.json") + baseConfig := DefaultBaseConfig() + ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidator) +} + +func checkConfig(configFile string) bool { + var valid bool + + // list of words we expect in the config + var elems = []string{ + "moniker", + "seeds", + "proxy_app", + "fast_sync", + "create_empty_blocks", + "peer", + "timeout", + "broadcast", + "send", + "addr", + "wal", + "propose", + "max", + "genesis", + } + for _, e := range elems { + if !strings.Contains(configFile, e) { + valid = false + } else { + valid = true + } + } + return valid } diff --git a/consensus/test_data/build.sh b/consensus/test_data/build.sh new file mode 100755 index 00000000..b0e1a934 --- /dev/null +++ b/consensus/test_data/build.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash + +# Requires: killall command and jq JSON processor. + +# 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" || exit 1 + +# Make sure we have a tendermint command. +if ! hash tendermint 2>/dev/null; then + make install +fi + +# Make sure we have a cutWALUntil binary. +cutWALUntil=./scripts/cutWALUntil/cutWALUntil +cutWALUntilDir=$(dirname $cutWALUntil) +if ! hash $cutWALUntil 2>/dev/null; then + cd "$cutWALUntilDir" && go build && cd - || exit 1 +fi + +TMHOME=$(mktemp -d) +export TMHOME="$TMHOME" + +if [[ ! -d "$TMHOME" ]]; then + echo "Could not create temp directory" + exit 1 +else + echo "TMHOME: ${TMHOME}" +fi + +# TODO: eventually we should replace with `tendermint init --test` +DIR_TO_COPY=$HOME/.tendermint_test/consensus_state_test +if [ ! -d "$DIR_TO_COPY" ]; then + echo "$DIR_TO_COPY does not exist. Please run: go test ./consensus" + exit 1 +fi +echo "==> Copying ${DIR_TO_COPY} to ${TMHOME} directory..." +cp -r "$DIR_TO_COPY"/* "$TMHOME" + +# preserve original genesis file because later it will be modified (see small_block2) +cp "$TMHOME/config/genesis.json" "$TMHOME/config/genesis.json.bak" + +function reset(){ + echo "==> Resetting tendermint..." + tendermint unsafe_reset_all + cp "$TMHOME/config/genesis.json.bak" "$TMHOME/config/genesis.json" +} + +reset + +# function empty_block(){ +# echo "==> Starting tendermint..." +# tendermint node --proxy_app=persistent_dummy &> /dev/null & +# sleep 5 +# echo "==> Killing tendermint..." +# killall tendermint + +# echo "==> Copying WAL log..." +# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_empty_block.cswal +# mv consensus/test_data/new_empty_block.cswal consensus/test_data/empty_block.cswal + +# reset +# } + +function many_blocks(){ + bash scripts/txs/random.sh 1000 36657 &> /dev/null & + PID=$! + echo "==> Starting tendermint..." + tendermint node --proxy_app=persistent_dummy &> /dev/null & + sleep 10 + echo "==> Killing tendermint..." + kill -9 $PID + killall tendermint + + echo "==> Copying WAL log..." + $cutWALUntil "$TMHOME/data/cs.wal/wal" 6 consensus/test_data/new_many_blocks.cswal + mv consensus/test_data/new_many_blocks.cswal consensus/test_data/many_blocks.cswal + + reset +} + + +# function small_block1(){ +# bash scripts/txs/random.sh 1000 36657 &> /dev/null & +# PID=$! +# echo "==> Starting tendermint..." +# tendermint node --proxy_app=persistent_dummy &> /dev/null & +# sleep 10 +# echo "==> Killing tendermint..." +# kill -9 $PID +# killall tendermint + +# echo "==> Copying WAL log..." +# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_small_block1.cswal +# mv consensus/test_data/new_small_block1.cswal consensus/test_data/small_block1.cswal + +# reset +# } + + +# # block part size = 512 +# function small_block2(){ +# cat "$TMHOME/config/genesis.json" | jq '. + {consensus_params: {block_size_params: {max_bytes: 22020096}, block_gossip_params: {block_part_size_bytes: 512}}}' > "$TMHOME/config/new_genesis.json" +# mv "$TMHOME/config/new_genesis.json" "$TMHOME/config/genesis.json" +# bash scripts/txs/random.sh 1000 36657 &> /dev/null & +# PID=$! +# echo "==> Starting tendermint..." +# tendermint node --proxy_app=persistent_dummy &> /dev/null & +# sleep 5 +# echo "==> Killing tendermint..." +# kill -9 $PID +# killall tendermint + +# echo "==> Copying WAL log..." +# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_small_block2.cswal +# mv consensus/test_data/new_small_block2.cswal consensus/test_data/small_block2.cswal + +# reset +# } + + + +case "$1" in + # "small_block1") + # small_block1 + # ;; + # "small_block2") + # small_block2 + # ;; + # "empty_block") + # empty_block + # ;; + "many_blocks") + many_blocks + ;; + *) + # small_block1 + # small_block2 + # empty_block + many_blocks +esac + +echo "==> Cleaning up..." +rm -rf "$TMHOME" diff --git a/docs/deploy-testnets.rst b/docs/deploy-testnets.rst index 89fa4b79..658330fe 100644 --- a/docs/deploy-testnets.rst +++ b/docs/deploy-testnets.rst @@ -13,7 +13,7 @@ It's relatively easy to setup a Tendermint cluster manually. The only requirements for a particular Tendermint node are a private key for the validator, stored as ``priv_validator.json``, and a list of the public keys of all validators, stored as ``genesis.json``. These files should -be stored in ``~/.tendermint``, or wherever the ``$TMHOME`` variable +be stored in ``~/.tendermint/config``, or wherever the ``$TMHOME`` variable might be set to. Here are the steps to setting up a testnet manually: diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 74b41d09..62e35738 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -1,58 +1,151 @@ Configuration ============= -TendermintCore can be configured via a TOML file in -``$TMHOME/config.toml``. Some of these parameters can be overridden by -command-line flags. +Tendermint Core can be configured via a TOML file in +``$TMHOME/config/config.toml``. Some of these parameters can be overridden by +command-line flags. For most users, the options in the ``##### main +base configuration options #####`` are intended to be modified while +config options further below are intended for advance power users. -Config parameters -~~~~~~~~~~~~~~~~~ +Config options +~~~~~~~~~~~~~~ -The main config parameters are defined -`here `__. +The default configuration file create by ``tendermint init`` has all +the parameters set with their default values. It will look something +like the file below, however, double check by inspecting the +``config.toml`` created with your version of ``tendermint`` installed: -- ``abci``: ABCI transport (socket \| grpc). *Default*: ``socket`` -- ``db_backend``: Database backend for the blockchain and - TendermintCore state. ``leveldb`` or ``memdb``. *Default*: - ``"leveldb"`` -- ``db_dir``: Database dir. *Default*: ``"$TMHOME/data"`` -- ``fast_sync``: Whether to sync faster from the block pool. *Default*: - ``true`` -- ``genesis_file``: The location of the genesis file. *Default*: - ``"$TMHOME/genesis.json"`` -- ``log_level``: *Default*: ``"state:info,*:error"`` -- ``moniker``: Name of this node. *Default*: the host name or ``"anonymous"`` - if runtime fails to get the host name -- ``priv_validator_file``: Validator private key file. *Default*: - ``"$TMHOME/priv_validator.json"`` -- ``prof_laddr``: Profile listen address. *Default*: ``""`` -- ``proxy_app``: The ABCI app endpoint. *Default*: - ``"tcp://127.0.0.1:46658"`` +:: -- ``consensus.max_block_size_txs``: Maximum number of block txs. - *Default*: ``10000`` -- ``consensus.create_empty_blocks``: Create empty blocks w/o txs. - *Default*: ``true`` -- ``consensus.create_empty_blocks_interval``: Block creation interval, even if empty. -- ``consensus.timeout_*``: Various consensus timeout parameters -- ``consensus.wal_file``: Consensus state WAL. *Default*: - ``"$TMHOME/data/cs.wal/wal"`` -- ``consensus.wal_light``: Whether to use light-mode for Consensus - state WAL. *Default*: ``false`` - -- ``mempool.*``: Various mempool parameters - -- ``p2p.addr_book_file``: Peer address book. *Default*: - ``"$TMHOME/addrbook.json"``. **NOT USED** -- ``p2p.laddr``: Node listen address. (0.0.0.0:0 means any interface, - any port). *Default*: ``"0.0.0.0:46656"`` -- ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` -- ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: - ``""`` -- ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` - -- ``rpc.grpc_laddr``: GRPC listen address (BroadcastTx only). Port - required. *Default*: ``""`` -- ``rpc.laddr``: RPC listen address. Port required. *Default*: - ``"0.0.0.0:46657"`` -- ``rpc.unsafe``: Enabled unsafe rpc methods. *Default*: ``true`` + # This is a TOML config file. + # For more information, see https://github.com/toml-lang/toml + + ##### main base config options ##### + + # TCP or UNIX socket address of the ABCI application, + # or the name of an ABCI application compiled in with the Tendermint binary + proxy_app = "tcp://127.0.0.1:46658" + + # A custom human readable name for this node + moniker = "anonymous" + + # If this node is many blocks behind the tip of the chain, FastSync + # allows them to catchup quickly by downloading blocks in parallel + # and verifying their commits + fast_sync = true + + # Database backend: leveldb | memdb + db_backend = "leveldb" + + # Database directory + db_path = "data" + + # Output level for logging + log_level = "state:info,*:error" + + ##### additional base config options ##### + + # The ID of the chain to join (should be signed with every transaction and vote) + chain_id = "" + + # Path to the JSON file containing the initial validator set and other meta data + genesis_file = "genesis.json" + + # Path to the JSON file containing the private key to use as a validator in the consensus protocol + priv_validator_file = "priv_validator.json" + + # Mechanism to connect to the ABCI application: socket | grpc + abci = "socket" + + # TCP or UNIX socket address for the profiling server to listen on + prof_laddr = "" + + # If true, query the ABCI app on connecting to a new peer + # so the app can decide if we should keep the connection or not + filter_peers = false + + # What indexer to use for transactions + tx_index = "kv" + + ##### advanced configuration options ##### + + ##### rpc server configuration options ##### + [rpc] + + # TCP or UNIX socket address for the RPC server to listen on + laddr = "tcp://0.0.0.0:46657" + + # TCP or UNIX socket address for the gRPC server to listen on + # NOTE: This server only supports /broadcast_tx_commit + grpc_laddr = "" + + # Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool + unsafe = false + + ##### peer to peer configuration options ##### + [p2p] + + # Address to listen for incoming connections + laddr = "tcp://0.0.0.0:46656" + + # Comma separated list of seed nodes to connect to + seeds = "" + + # Path to address book + addr_book_file = "addrbook.json" + + # Set true for strict address routability rules + addr_book_strict = true + + # Time to wait before flushing messages out on the connection, in ms + flush_throttle_timeout = 100 + + # Maximum number of peers to connect to + max_num_peers = 50 + + # Maximum size of a message packet payload, in bytes + max_msg_packet_payload_size = 1024 + + # Rate at which packets can be sent, in bytes/second + send_rate = 512000 + + # Rate at which packets can be received, in bytes/second + recv_rate = 512000 + + ##### mempool configuration options ##### + [mempool] + + recheck = true + recheck_empty = true + broadcast = true + wal_dir = "data/mempool.wal" + + ##### consensus configuration options ##### + [consensus] + + wal_file = "data/cs.wal/wal" + wal_light = false + + # All timeouts are in milliseconds + timeout_propose = 3000 + timeout_propose_delta = 500 + timeout_prevote = 1000 + timeout_prevote_delta = 500 + timeout_precommit = 1000 + timeout_precommit_delta = 500 + timeout_commit = 1000 + + # Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) + skip_timeout_commit = false + + # BlockSize + max_block_size_txs = 10000 + max_block_size_bytes = 1 + + # EmptyBlocks mode and possible interval between empty blocks in seconds + create_empty_blocks = true + create_empty_blocks_interval = 0 + + # Reactor sleep duration parameters are in milliseconds + peer_gossip_sleep_duration = 100 + peer_query_maj23_sleep_duration = 2000 diff --git a/docs/specification/genesis.rst b/docs/specification/genesis.rst index a7ec7a26..7e36c131 100644 --- a/docs/specification/genesis.rst +++ b/docs/specification/genesis.rst @@ -1,7 +1,7 @@ Genesis ======= -The genesis.json file in ``$TMHOME`` defines the initial TendermintCore +The genesis.json file in ``$TMHOME/config`` defines the initial TendermintCore state upon genesis of the blockchain (`see definition `__). diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index 33173d19..f8e9bd3a 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -18,7 +18,7 @@ Configuration ~~~~~~~~~~~~~ Set the ``laddr`` config parameter under ``[rpc]`` table in the -$TMHOME/config.toml file or the ``--rpc.laddr`` command-line flag to the +$TMHOME/config/config.toml file or the ``--rpc.laddr`` command-line flag to the desired protocol://host:port setting. Default: ``tcp://0.0.0.0:46657``. Arguments diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 9076230e..75d49544 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -24,7 +24,8 @@ Initialize the root directory by running: tendermint init This will create a new private key (``priv_validator.json``), and a -genesis file (``genesis.json``) containing the associated public key. +genesis file (``genesis.json``) containing the associated public key, +in ``$TMHOME/config``. This is all that's necessary to run a local testnet with one validator. For more elaborate initialization, see our `testnet deployment @@ -153,8 +154,7 @@ The block interval setting allows for a delay (in seconds) between the creation create_empty_blocks_interval = 5 With this setting, empty blocks will be produced every 5s if no block has been produced otherwise, -regardless of the value of `create_empty_blocks`. - +regardless of the value of ``create_empty_blocks``. Broadcast API ------------- @@ -196,7 +196,7 @@ Tendermint Networks ------------------- When ``tendermint init`` is run, both a ``genesis.json`` and -``priv_validator.json`` are created in ``~/.tendermint``. The +``priv_validator.json`` are created in ``~/.tendermint/config``. The ``genesis.json`` might look like: :: @@ -263,7 +263,7 @@ with the consensus protocol. Peers ~~~~~ -To connect to peers on start-up, specify them in the ``config.toml`` or +To connect to peers on start-up, specify them in the ``$TMHOME/config/config.toml`` or on the command line. For instance, @@ -289,7 +289,7 @@ Adding a Non-Validator ~~~~~~~~~~~~~~~~~~~~~~ Adding a non-validator is simple. Just copy the original -``genesis.json`` to ``~/.tendermint`` on the new machine and start the +``genesis.json`` to ``~/.tendermint/config`` on the new machine and start the node, specifying seeds as necessary. If no seeds are specified, the node won't make any blocks, because it's not a validator, and it won't hear about any blocks, because it's not connected to the other peer. @@ -358,8 +358,8 @@ then the new ``genesis.json`` will be: ] } -Update the ``genesis.json`` in ``~/.tendermint``. Copy the genesis file -and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new +Update the ``genesis.json`` in ``~/.tendermint/config``. Copy the genesis file +and the new ``priv_validator.json`` to the ``~/.tendermint/config`` on a new machine. Now run ``tendermint node`` on both machines, and use either diff --git a/rpc/core/doc.go b/rpc/core/doc.go index a72cec02..d3ec5d3b 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -11,7 +11,7 @@ Tendermint RPC is built using [our own RPC library](https://github.com/tendermin ## Configuration -Set the `laddr` config parameter under `[rpc]` table in the `$TMHOME/config.toml` file or the `--rpc.laddr` command-line flag to the desired protocol://host:port setting. Default: `tcp://0.0.0.0:46657`. +Set the `laddr` config parameter under `[rpc]` table in the `$TMHOME/config/config.toml` file or the `--rpc.laddr` command-line flag to the desired protocol://host:port setting. Default: `tcp://0.0.0.0:46657`. ## Arguments diff --git a/scripts/debora/unsafe_reset_net.sh b/scripts/debora/unsafe_reset_net.sh index c6767427..3698e5ac 100755 --- a/scripts/debora/unsafe_reset_net.sh +++ b/scripts/debora/unsafe_reset_net.sh @@ -3,7 +3,7 @@ set -euo pipefail IFS=$'\n\t' debora run -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; killall tendermint; killall logjack" -debora run -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; tendermint unsafe_reset_priv_validator; rm -rf ~/.tendermint/data; rm ~/.tendermint/genesis.json; rm ~/.tendermint/logs/*" +debora run -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; tendermint unsafe_reset_priv_validator; rm -rf ~/.tendermint/data; rm ~/.tendermint/config/genesis.json; rm ~/.tendermint/logs/*" debora run -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; git pull origin develop; make" debora run -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; mkdir -p ~/.tendermint/logs" debora run --bg --label tendermint -- bash -c "cd \$GOPATH/src/github.com/tendermint/tendermint; tendermint node 2>&1 | stdinwriter -outpath ~/.tendermint/logs/tendermint.log" From 69d8c2e554df7577c2e1e02724fb9214287a9d19 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 1 Dec 2017 22:35:15 -0600 Subject: [PATCH 005/124] fixes after my own review --- config/config.go | 2 +- config/toml.go | 25 ++++++- docs/specification/configuration.rst | 103 ++++++++++++++++----------- 3 files changed, 84 insertions(+), 46 deletions(-) diff --git a/config/config.go b/config/config.go index 1585fcd0..25ccf0f5 100644 --- a/config/config.go +++ b/config/config.go @@ -386,7 +386,7 @@ func (cfg *ConsensusConfig) PeerQueryMaj23Sleep() time.Duration { // DefaultConsensusConfig returns a default configuration for the consensus service func DefaultConsensusConfig() *ConsensusConfig { return &ConsensusConfig{ - WalPath: filepath.Join(defaultDataDir, "cs.wal/wal"), + WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"), WalLight: false, TimeoutPropose: 3000, TimeoutProposeDelta: 500, diff --git a/config/toml.go b/config/toml.go index 56a5a130..e7b00dd6 100644 --- a/config/toml.go +++ b/config/toml.go @@ -100,9 +100,6 @@ prof_laddr = "{{ .BaseConfig.ProfListenAddress }}" # so the app can decide if we should keep the connection or not filter_peers = {{ .BaseConfig.FilterPeers }} -# What indexer to use for transactions -tx_index = "{{ .BaseConfig.TxIndex }}" - ##### advanced configuration options ##### ##### rpc server configuration options ##### @@ -185,6 +182,28 @@ create_empty_blocks_interval = {{ .Consensus.CreateEmptyBlocksInterval }} # Reactor sleep duration parameters are in milliseconds peer_gossip_sleep_duration = {{ .Consensus.PeerGossipSleepDuration }} peer_query_maj23_sleep_duration = {{ .Consensus.PeerQueryMaj23SleepDuration }} + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" (default) +# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "{{ .TxIndex.Indexer }}" + +# Comma-separated list of tags to index (by default the only tag is tx hash) +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "{{ .TxIndex.IndexTags }}" + +# When set to true, tells indexer to index all tags. Note this may be not +# desirable (see the comment above). IndexTags has a precedence over +# IndexAllTags (i.e. when given both, IndexTags will be indexed). +index_all_tags = {{ .TxIndex.IndexAllTags }} ` /****** these are for test settings ***********/ diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 62e35738..37359885 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -12,120 +12,117 @@ Config options The default configuration file create by ``tendermint init`` has all the parameters set with their default values. It will look something -like the file below, however, double check by inspecting the +like the file below, however, double check by inspecting the ``config.toml`` created with your version of ``tendermint`` installed: :: # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml - + ##### main base config options ##### - + # TCP or UNIX socket address of the ABCI application, # or the name of an ABCI application compiled in with the Tendermint binary proxy_app = "tcp://127.0.0.1:46658" - + # A custom human readable name for this node moniker = "anonymous" - + # If this node is many blocks behind the tip of the chain, FastSync # allows them to catchup quickly by downloading blocks in parallel # and verifying their commits fast_sync = true - + # Database backend: leveldb | memdb db_backend = "leveldb" - + # Database directory db_path = "data" - + # Output level for logging log_level = "state:info,*:error" - + ##### additional base config options ##### - + # The ID of the chain to join (should be signed with every transaction and vote) chain_id = "" - + # Path to the JSON file containing the initial validator set and other meta data genesis_file = "genesis.json" - + # Path to the JSON file containing the private key to use as a validator in the consensus protocol priv_validator_file = "priv_validator.json" - + # Mechanism to connect to the ABCI application: socket | grpc abci = "socket" - + # TCP or UNIX socket address for the profiling server to listen on prof_laddr = "" - + # If true, query the ABCI app on connecting to a new peer # so the app can decide if we should keep the connection or not filter_peers = false - - # What indexer to use for transactions - tx_index = "kv" - + ##### advanced configuration options ##### - + ##### rpc server configuration options ##### [rpc] - + # TCP or UNIX socket address for the RPC server to listen on laddr = "tcp://0.0.0.0:46657" - + # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit grpc_laddr = "" - + # Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool unsafe = false - + ##### peer to peer configuration options ##### [p2p] - + # Address to listen for incoming connections laddr = "tcp://0.0.0.0:46656" - + # Comma separated list of seed nodes to connect to seeds = "" - + # Path to address book addr_book_file = "addrbook.json" - + # Set true for strict address routability rules addr_book_strict = true - + # Time to wait before flushing messages out on the connection, in ms flush_throttle_timeout = 100 - + # Maximum number of peers to connect to max_num_peers = 50 - + # Maximum size of a message packet payload, in bytes max_msg_packet_payload_size = 1024 - + # Rate at which packets can be sent, in bytes/second send_rate = 512000 - + # Rate at which packets can be received, in bytes/second recv_rate = 512000 - + ##### mempool configuration options ##### [mempool] - + recheck = true recheck_empty = true broadcast = true wal_dir = "data/mempool.wal" - + ##### consensus configuration options ##### [consensus] - + wal_file = "data/cs.wal/wal" wal_light = false - + # All timeouts are in milliseconds timeout_propose = 3000 timeout_propose_delta = 500 @@ -134,18 +131,40 @@ like the file below, however, double check by inspecting the timeout_precommit = 1000 timeout_precommit_delta = 500 timeout_commit = 1000 - + # Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) skip_timeout_commit = false - + # BlockSize max_block_size_txs = 10000 max_block_size_bytes = 1 - + # EmptyBlocks mode and possible interval between empty blocks in seconds create_empty_blocks = true create_empty_blocks_interval = 0 - + # Reactor sleep duration parameters are in milliseconds peer_gossip_sleep_duration = 100 peer_query_maj23_sleep_duration = 2000 + + ##### transactions indexer configuration options ##### + [tx_index] + + # What indexer to use for transactions + # + # Options: + # 1) "null" (default) + # 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). + indexer = "{{ .TxIndex.Indexer }}" + + # Comma-separated list of tags to index (by default the only tag is tx hash) + # + # It's recommended to index only a subset of tags due to possible memory + # bloat. This is, of course, depends on the indexer's DB and the volume of + # transactions. + index_tags = "{{ .TxIndex.IndexTags }}" + + # When set to true, tells indexer to index all tags. Note this may be not + # desirable (see the comment above). IndexTags has a precedence over + # IndexAllTags (i.e. when given both, IndexTags will be indexed). + index_all_tags = {{ .TxIndex.IndexAllTags }} From a6f2e502e72f483ffc847230b64edbf58d8b4101 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 2 Dec 2017 11:26:51 -0600 Subject: [PATCH 006/124] move genesis.json file into config dir --- test/p2p/data/mach1/core/{ => config}/genesis.json | 0 test/p2p/data/mach2/core/{ => config}/genesis.json | 0 test/p2p/data/mach3/core/{ => config}/genesis.json | 0 test/p2p/data/mach4/core/{ => config}/genesis.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/p2p/data/mach1/core/{ => config}/genesis.json (100%) rename test/p2p/data/mach2/core/{ => config}/genesis.json (100%) rename test/p2p/data/mach3/core/{ => config}/genesis.json (100%) rename test/p2p/data/mach4/core/{ => config}/genesis.json (100%) diff --git a/test/p2p/data/mach1/core/genesis.json b/test/p2p/data/mach1/core/config/genesis.json similarity index 100% rename from test/p2p/data/mach1/core/genesis.json rename to test/p2p/data/mach1/core/config/genesis.json diff --git a/test/p2p/data/mach2/core/genesis.json b/test/p2p/data/mach2/core/config/genesis.json similarity index 100% rename from test/p2p/data/mach2/core/genesis.json rename to test/p2p/data/mach2/core/config/genesis.json diff --git a/test/p2p/data/mach3/core/genesis.json b/test/p2p/data/mach3/core/config/genesis.json similarity index 100% rename from test/p2p/data/mach3/core/genesis.json rename to test/p2p/data/mach3/core/config/genesis.json diff --git a/test/p2p/data/mach4/core/genesis.json b/test/p2p/data/mach4/core/config/genesis.json similarity index 100% rename from test/p2p/data/mach4/core/genesis.json rename to test/p2p/data/mach4/core/config/genesis.json From 9da5cd0180c44df64457ca8c1c2ea4f41902e985 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Sun, 10 Dec 2017 17:32:21 +0000 Subject: [PATCH 007/124] rebase fix --- consensus/test_data/build.sh | 148 ----------------------------------- 1 file changed, 148 deletions(-) delete mode 100755 consensus/test_data/build.sh diff --git a/consensus/test_data/build.sh b/consensus/test_data/build.sh deleted file mode 100755 index b0e1a934..00000000 --- a/consensus/test_data/build.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash - -# Requires: killall command and jq JSON processor. - -# 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" || exit 1 - -# Make sure we have a tendermint command. -if ! hash tendermint 2>/dev/null; then - make install -fi - -# Make sure we have a cutWALUntil binary. -cutWALUntil=./scripts/cutWALUntil/cutWALUntil -cutWALUntilDir=$(dirname $cutWALUntil) -if ! hash $cutWALUntil 2>/dev/null; then - cd "$cutWALUntilDir" && go build && cd - || exit 1 -fi - -TMHOME=$(mktemp -d) -export TMHOME="$TMHOME" - -if [[ ! -d "$TMHOME" ]]; then - echo "Could not create temp directory" - exit 1 -else - echo "TMHOME: ${TMHOME}" -fi - -# TODO: eventually we should replace with `tendermint init --test` -DIR_TO_COPY=$HOME/.tendermint_test/consensus_state_test -if [ ! -d "$DIR_TO_COPY" ]; then - echo "$DIR_TO_COPY does not exist. Please run: go test ./consensus" - exit 1 -fi -echo "==> Copying ${DIR_TO_COPY} to ${TMHOME} directory..." -cp -r "$DIR_TO_COPY"/* "$TMHOME" - -# preserve original genesis file because later it will be modified (see small_block2) -cp "$TMHOME/config/genesis.json" "$TMHOME/config/genesis.json.bak" - -function reset(){ - echo "==> Resetting tendermint..." - tendermint unsafe_reset_all - cp "$TMHOME/config/genesis.json.bak" "$TMHOME/config/genesis.json" -} - -reset - -# function empty_block(){ -# echo "==> Starting tendermint..." -# tendermint node --proxy_app=persistent_dummy &> /dev/null & -# sleep 5 -# echo "==> Killing tendermint..." -# killall tendermint - -# echo "==> Copying WAL log..." -# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_empty_block.cswal -# mv consensus/test_data/new_empty_block.cswal consensus/test_data/empty_block.cswal - -# reset -# } - -function many_blocks(){ - bash scripts/txs/random.sh 1000 36657 &> /dev/null & - PID=$! - echo "==> Starting tendermint..." - tendermint node --proxy_app=persistent_dummy &> /dev/null & - sleep 10 - echo "==> Killing tendermint..." - kill -9 $PID - killall tendermint - - echo "==> Copying WAL log..." - $cutWALUntil "$TMHOME/data/cs.wal/wal" 6 consensus/test_data/new_many_blocks.cswal - mv consensus/test_data/new_many_blocks.cswal consensus/test_data/many_blocks.cswal - - reset -} - - -# function small_block1(){ -# bash scripts/txs/random.sh 1000 36657 &> /dev/null & -# PID=$! -# echo "==> Starting tendermint..." -# tendermint node --proxy_app=persistent_dummy &> /dev/null & -# sleep 10 -# echo "==> Killing tendermint..." -# kill -9 $PID -# killall tendermint - -# echo "==> Copying WAL log..." -# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_small_block1.cswal -# mv consensus/test_data/new_small_block1.cswal consensus/test_data/small_block1.cswal - -# reset -# } - - -# # block part size = 512 -# function small_block2(){ -# cat "$TMHOME/config/genesis.json" | jq '. + {consensus_params: {block_size_params: {max_bytes: 22020096}, block_gossip_params: {block_part_size_bytes: 512}}}' > "$TMHOME/config/new_genesis.json" -# mv "$TMHOME/config/new_genesis.json" "$TMHOME/config/genesis.json" -# bash scripts/txs/random.sh 1000 36657 &> /dev/null & -# PID=$! -# echo "==> Starting tendermint..." -# tendermint node --proxy_app=persistent_dummy &> /dev/null & -# sleep 5 -# echo "==> Killing tendermint..." -# kill -9 $PID -# killall tendermint - -# echo "==> Copying WAL log..." -# $cutWALUntil "$TMHOME/data/cs.wal/wal" 1 consensus/test_data/new_small_block2.cswal -# mv consensus/test_data/new_small_block2.cswal consensus/test_data/small_block2.cswal - -# reset -# } - - - -case "$1" in - # "small_block1") - # small_block1 - # ;; - # "small_block2") - # small_block2 - # ;; - # "empty_block") - # empty_block - # ;; - "many_blocks") - many_blocks - ;; - *) - # small_block1 - # small_block2 - # empty_block - many_blocks -esac - -echo "==> Cleaning up..." -rm -rf "$TMHOME" From a92a32b862c7051253346e6402a5e8a77be46a9d Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Sun, 10 Dec 2017 18:01:19 +0000 Subject: [PATCH 008/124] config: lil fixes --- config/toml.go | 4 ++-- config/toml_test.go | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/config/toml.go b/config/toml.go index e7b00dd6..3522170d 100644 --- a/config/toml.go +++ b/config/toml.go @@ -35,7 +35,7 @@ func EnsureRoot(rootDir string) { // Write default config file if missing. if !cmn.FileExists(configFilePath) { - cmn.MustWriteFile(configFilePath, []byte(defaultConfig(defaultMoniker)), 0644) + writeConfigFile(configFilePath) } } @@ -241,7 +241,7 @@ func ResetTestRoot(testName string) *Config { // Write default config file if missing. if !cmn.FileExists(configFilePath) { - cmn.MustWriteFile(configFilePath, []byte(testConfig(defaultMoniker)), 0644) + writeConfigFile(configFilePath) } if !cmn.FileExists(genesisFilePath) { cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644) diff --git a/config/toml_test.go b/config/toml_test.go index e98719f6..a1637f67 100644 --- a/config/toml_test.go +++ b/config/toml_test.go @@ -20,7 +20,7 @@ func ensureFiles(t *testing.T, rootDir string, files ...string) { } func TestEnsureRoot(t *testing.T) { - assert, require := assert.New(t), require.New(t) + require := require.New(t) // setup temp dir for test tmpDir, err := ioutil.TempDir("", "config-test") @@ -33,13 +33,16 @@ func TestEnsureRoot(t *testing.T) { // make sure config is set properly data, err := ioutil.ReadFile(filepath.Join(tmpDir, defaultConfigFilePath)) require.Nil(err) - assert.Equal([]byte(defaultConfig(defaultMoniker)), data) + + if !checkConfig(string(data)) { + t.Fatalf("config file missing some information") + } ensureFiles(t, tmpDir, "data") } func TestEnsureTestRoot(t *testing.T) { - assert, require := assert.New(t), require.New(t) + require := require.New(t) testName := "ensureTestRoot" @@ -50,7 +53,10 @@ func TestEnsureTestRoot(t *testing.T) { // make sure config is set properly data, err := ioutil.ReadFile(filepath.Join(rootDir, defaultConfigFilePath)) require.Nil(err) - assert.Equal([]byte(testConfig(defaultMoniker)), data) + + if !checkConfig(string(data)) { + t.Fatalf("config file missing some information") + } // TODO: make sure the cfg returned and testconfig are the same! baseConfig := DefaultBaseConfig() From a8e625e99dcf19b8c3594665d8f3b70163ba3487 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 10 Dec 2017 20:43:58 -0500 Subject: [PATCH 009/124] config: unexpose chainID --- config/config.go | 13 +++++++++---- config/toml.go | 5 +---- consensus/common_test.go | 7 ++++--- consensus/state_test.go | 4 ++-- consensus/types/height_vote_set_test.go | 4 ++-- lite/client/provider_test.go | 4 +++- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/config/config.go b/config/config.go index 25ccf0f5..6689adfc 100644 --- a/config/config.go +++ b/config/config.go @@ -78,13 +78,14 @@ func (cfg *Config) SetRoot(root string) *Config { // BaseConfig defines the base configuration for a Tendermint node type BaseConfig struct { + + // chainID is unexposed and immutable but here for convenience + chainID string + // The root directory for all data. // This should be set in viper so it can unmarshal into this struct RootDir string `mapstructure:"home"` - // The ID of the chain to join (should be signed with every transaction and vote) - ChainID string `mapstructure:"chain_id"` - // Path to the JSON file containing the initial validator set and other meta data Genesis string `mapstructure:"genesis_file"` @@ -123,6 +124,10 @@ type BaseConfig struct { DBPath string `mapstructure:"db_dir"` } +func (c BaseConfig) ChainID() string { + return c.chainID +} + // DefaultBaseConfig returns a default base configuration for a Tendermint node func DefaultBaseConfig() BaseConfig { return BaseConfig{ @@ -143,7 +148,7 @@ func DefaultBaseConfig() BaseConfig { // TestBaseConfig returns a base configuration for testing a Tendermint node func TestBaseConfig() BaseConfig { conf := DefaultBaseConfig() - conf.ChainID = "tendermint_test" + conf.chainID = "tendermint_test" conf.ProxyApp = "dummy" conf.FastSync = false conf.DBBackend = "memdb" diff --git a/config/toml.go b/config/toml.go index 3522170d..92ceb7de 100644 --- a/config/toml.go +++ b/config/toml.go @@ -76,14 +76,11 @@ db_backend = "{{ .BaseConfig.DBBackend }}" # Database directory db_path = "{{ .BaseConfig.DBPath }}" -# Output level for logging +# Output level for logging, including package level options log_level = "{{ .BaseConfig.LogLevel }}" ##### additional base config options ##### -# The ID of the chain to join (should be signed with every transaction and vote) -chain_id = "{{ .BaseConfig.ChainID }}" - # Path to the JSON file containing the initial validator set and other meta data genesis_file = "{{ .BaseConfig.Genesis }}" diff --git a/consensus/common_test.go b/consensus/common_test.go index 6598c15e..d5514ee1 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -78,7 +78,7 @@ func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartS Type: voteType, BlockID: types.BlockID{hash, header}, } - err := vs.PrivValidator.SignVote(config.ChainID, vote) + err := vs.PrivValidator.SignVote(config.ChainID(), vote) return vote, err } @@ -129,7 +129,7 @@ func decideProposal(cs1 *ConsensusState, vs *validatorStub, height int64, round // Make proposal polRound, polBlockID := cs1.Votes.POLInfo() proposal = types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) - if err := vs.SignProposal(config.ChainID, proposal); err != nil { + if err := vs.SignProposal(cs1.state.ChainID, proposal); err != nil { panic(err) } return @@ -430,9 +430,10 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G privValidators[i] = privVal } sort.Sort(types.PrivValidatorsByAddress(privValidators)) + return &types.GenesisDoc{ GenesisTime: time.Now(), - ChainID: config.ChainID, + ChainID: config.ChainID(), Validators: validators, }, privValidators } diff --git a/consensus/state_test.go b/consensus/state_test.go index 6beb7da5..97562feb 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -204,7 +204,7 @@ func TestBadProposal(t *testing.T) { propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet(partSize) proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1, types.BlockID{}) - if err := vs2.SignProposal(config.ChainID, proposal); err != nil { + if err := vs2.SignProposal(config.ChainID(), proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } @@ -900,7 +900,7 @@ func TestLockPOLSafety2(t *testing.T) { // in round 2 we see the polkad block from round 0 newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0, propBlockID1) - if err := vs3.SignProposal(config.ChainID, newProp); err != nil { + if err := vs3.SignProposal(config.ChainID(), newProp); err != nil { t.Fatal(err) } if err := cs1.SetProposalAndBlock(newProp, propBlock0, propBlockParts0, "some peer"); err != nil { diff --git a/consensus/types/height_vote_set_test.go b/consensus/types/height_vote_set_test.go index 306592aa..5719d7ee 100644 --- a/consensus/types/height_vote_set_test.go +++ b/consensus/types/height_vote_set_test.go @@ -18,7 +18,7 @@ func init() { func TestPeerCatchupRounds(t *testing.T) { valSet, privVals := types.RandValidatorSet(10, 1) - hvs := NewHeightVoteSet(config.ChainID, 1, valSet) + hvs := NewHeightVoteSet(config.ChainID(), 1, valSet) vote999_0 := makeVoteHR(t, 1, 999, privVals, 0) added, err := hvs.AddVote(vote999_0, "peer1") @@ -59,7 +59,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []*types.PrivVal Type: types.VoteTypePrecommit, BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}}, } - chainID := config.ChainID + chainID := config.ChainID() err := privVal.SignVote(chainID, vote) if err != nil { panic(cmn.Fmt("Error signing vote: %v", err)) diff --git a/lite/client/provider_test.go b/lite/client/provider_test.go index 0bebfced..91d277fc 100644 --- a/lite/client/provider_test.go +++ b/lite/client/provider_test.go @@ -10,6 +10,7 @@ import ( liteErr "github.com/tendermint/tendermint/lite/errors" rpcclient "github.com/tendermint/tendermint/rpc/client" rpctest "github.com/tendermint/tendermint/rpc/test" + "github.com/tendermint/tendermint/types" ) func TestProvider(t *testing.T) { @@ -17,7 +18,8 @@ func TestProvider(t *testing.T) { cfg := rpctest.GetConfig() rpcAddr := cfg.RPC.ListenAddress - chainID := cfg.ChainID + genDoc, _ := types.GenesisDocFromFile(cfg.GenesisFile()) + chainID := genDoc.ChainID p := NewHTTPProvider(rpcAddr) require.NotNil(t, p) From 96e0e4ab5a7b74a3918cf51d001b0800309afc51 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 29 Dec 2017 22:12:04 +0100 Subject: [PATCH 010/124] Describe messages sent as part of consensus/gossip protocol --- docs/specification/new-spec/consensus.md | 197 +++++++++++++++++++++++ docs/specification/new-spec/encoding.md | 21 +++ 2 files changed, 218 insertions(+) create mode 100644 docs/specification/new-spec/consensus.md diff --git a/docs/specification/new-spec/consensus.md b/docs/specification/new-spec/consensus.md new file mode 100644 index 00000000..5c681056 --- /dev/null +++ b/docs/specification/new-spec/consensus.md @@ -0,0 +1,197 @@ +# Tendermint Consensus + +Tendermint consensus is a distributed protocol executed by validator processes to agree on +the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where +each round is a try to reach agreement on the next block. A round starts by having a dedicated +process (called proposer) suggesting to other processes what should be the next block with +the `ProposalMessage`. +The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote messages, prevote +and precommit votes). Note that a proposal message is just a suggestion what the next block should be; a +validator might vote with a `VoteMessage` for a different block. If in some round, enough number +of processes vote for the same block, then this block is committed and later added to the blockchain. +`ProposalMessage` and `VoteMessage` are signed by the private key of the validator. +The internals of the protocol and how it ensures safety and liveness properties are +explained [here](https://github.com/tendermint/spec). + +For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block +as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they +reach agreement on the `BlockID` (see `BlockID` definition in [Blockchain](blockchain.md) section) +that uniquely identifies each block. The block itself is disseminated to validator processes using +peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a +number of block parts, that are then gossiped between processes using `BlockPartMessage`. + +Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected +only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers +all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can +reach agreement on some block, and also obtain the content of the chosen block (block parts). As part of +the gossiping protocol, processes also send auxiliary messages that inform peers about the +executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and +also messages that inform peers what votes the process has seen (`HasVoteMessage`, +`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping protocol +to determine what messages a process should send to its peers. + +We now describe the content of each message exchanged during Tendermint consensus protocol. + +## ProposalMessage +ProposalMessage is sent when a new block is proposed. It is a suggestion of what the +next block in the blockchain should be. +``` +type ProposalMessage struct { + Proposal Proposal +} +``` +### Proposal +Proposal contains height and round for which this proposal is made, BlockID as a unique identifier of proposed +block, timestamp, and two fields (POLRound and POLBlockID) that are needed for termination of the consensus. +The message is signed by the validator private key. + +``` +type Proposal struct { + Height int64 + Round int + Timestamp Time + BlockID BlockID + POLRound int + POLBlockID BlockID + Signature Signature +} +``` + +NOTE: In the current version of the Tendermint, the consensus value in proposal is represented with +PartSetHeader, and with BlockID in vote message. It should be aligned as suggested in this spec as +BlockID contains PartSetHeader. + +## VoteMessage +VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the current round). +Vote is defined in [Blockchain](blockchain.md) section and contains validator's information (validator address +and index), height and round for which the vote is sent, vote type, blockID if process vote for some +block (`nil` otherwise) and a timestamp when the vote is sent. The message is signed by the validator private key. +``` +type VoteMessage struct { + Vote Vote +} +``` + +## BlockPartMessage +BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round +and the block part. + +``` +type BlockPartMessage struct { + Height int64 + Round int + Part Part +} +``` + +## ProposalHeartbeatMessage +ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions +to be able to create a next block proposal. + +``` +type ProposalHeartbeatMessage struct { + Heartbeat Heartbeat +} +``` + +### Heartbeat +Heartbeat contains validator information (address and index), +height, round and sequence number. It is signed by the private key of the validator. + +``` +type Heartbeat struct { + ValidatorAddress []byte + ValidatorIndex int + Height int64 + Round int + Sequence int + Signature Signature +} +``` + +## NewRoundStepMessage +NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. It is +used in the gossip part of the Tendermint protocol to inform peers about a current height/round/step +a process is in. + +``` +type NewRoundStepMessage struct { + Height int64 + Round int + Step RoundStepType + SecondsSinceStartTime int + LastCommitRound int +} +``` + +## CommitStepMessage +CommitStepMessage is sent when an agreement on some block is reached. It contains height for which agreement +is reached, block parts header that describes the decided block and is used to obtain all block parts, +and a bit array of the block parts a process currently has, so its peers can know what parts +it is missing so they can send them. + +``` +type CommitStepMessage struct { + Height int64 + BlockID BlockID + BlockParts BitArray +} +``` + +TODO: We use BlockID instead of BlockPartsHeader (in current implementation) for symmetry. + +## ProposalPOLMessage +ProposalPOLMessage is sent when a previous block is re-proposed. +It is used to inform peers in what round the process learned for this block (ProposalPOLRound), +and what prevotes for the re-proposed block the process has. + +``` +type ProposalPOLMessage struct { + Height int64 + ProposalPOLRound int + ProposalPOL BitArray +} +``` + + +## HasVoteMessage +HasVoteMessage is sent to indicate that a particular vote has been received. It contains height, +round, vote type and the index of the validator that is the originator of the corresponding vote. + +``` +type HasVoteMessage struct { + Height int64 + Round int + Type byte + Index int +} +``` + +## VoteSetMaj23Message +VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. +It contains height, round, vote type and the BlockID. + +``` +type VoteSetMaj23Message struct { + Height int64 + Round int + Type byte + BlockID BlockID +} +``` + +## VoteSetBitsMessage +VoteSetBitsMessage is sent to communicate the bit-array of votes a process has seen for a given +BlockID. It contains height, round, vote type, BlockID and a bit array of +the votes a process has. + +``` +type VoteSetBitsMessage struct { + Height int64 + Round int + Type byte + BlockID BlockID + Votes BitArray +} +``` + diff --git a/docs/specification/new-spec/encoding.md b/docs/specification/new-spec/encoding.md index a7482e6c..02456d84 100644 --- a/docs/specification/new-spec/encoding.md +++ b/docs/specification/new-spec/encoding.md @@ -93,6 +93,17 @@ encode([]int{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x01, 0x01, 0x02, 0x01, 0x encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x65, 0x66, 0x67] ``` +### BitArray +BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode +value of each array element. + +``` +type BitArray struct { + Bits int + Elems []uint64 +} +``` + ### Time Time is encoded as an `int64` of the number of nanoseconds since January 1, 1970, @@ -176,3 +187,13 @@ TMBIN encode an object and slice it into parts. ``` MakeParts(object, partSize) ``` + +### Part + +``` +type Part struct { + Index int + Bytes byte[] + Proof byte[] +} +``` From 4e834baa9a4cb7638539e6ce0bf30c51e3f6e3d3 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 31 Dec 2017 13:54:50 +0000 Subject: [PATCH 011/124] docs: update ecosystem.rst (#1037) * docs: update ecosystem.rst * typo [ci skip] --- docs/ecosystem.rst | 117 ++------------------------------------------- 1 file changed, 5 insertions(+), 112 deletions(-) diff --git a/docs/ecosystem.rst b/docs/ecosystem.rst index 30ab9a35..39e6785e 100644 --- a/docs/ecosystem.rst +++ b/docs/ecosystem.rst @@ -1,122 +1,15 @@ Tendermint Ecosystem ==================== -Below are the many applications built using various pieces of the Tendermint stack. We thank the community for their contributions thus far and welcome the addition of new projects. Feel free to submit a pull request to add your project! +The growing list of applications built using various pieces of the Tendermint stack can be found at: -ABCI Applications ------------------ +* https://tendermint.com/ecosystem -Burrow -^^^^^^ +We thank the community for their contributions thus far and welcome the addition of new projects. A pull request can be submitted to `this file `__ to include your project. -Ethereum Virtual Machine augmented with native permissioning scheme and global key-value store, written in Go, authored by Monax Industries, and incubated `by Hyperledger `__. - -cb-ledger -^^^^^^^^^ - -Custodian Bank Ledger, integrating central banking with the blockchains of tomorrow, written in C++, and `authored by Block Finance `__. - -Clearchain -^^^^^^^^^^ - -Application to manage a distributed ledger for money transfers that support multi-currency accounts, written in Go, and `authored by Allession Treglia `__. - -Comit -^^^^^ - -Public service reporting and tracking, written in Go, and `authored by Zach Balder `__. - -Cosmos SDK -^^^^^^^^^^ - -A prototypical account based crypto currency state machine supporting plugins, written in Go, and `authored by Cosmos `__. - -Ethermint -^^^^^^^^^ - -The go-ethereum state machine run as a ABCI app, written in Go, `authored by Tendermint `__. - -IAVL -^^^^ - -Immutable AVL+ tree with Merkle proofs, Written in Go, `authored by Tendermint `__. - -Lotion -^^^^^^ - -A Javascript microframework for building blockchain applications with Tendermint, written in Javascript, `authored by Judd Keppel of Tendermint `__. See also `lotion-chat `__ and `lotion-coin `__ apps written using Lotion. - -MerkleTree -^^^^^^^^^^ - -Immutable AVL+ tree with Merkle proofs, Written in Java, `authored by jTendermint `__. - -Passchain -^^^^^^^^^ - -Passchain is a tool to securely store and share passwords, tokens and other short secrets, `authored by trusch `__. - -Passwerk -^^^^^^^^ - -Encrypted storage web-utility backed by Tendermint, written in Go, `authored by Rigel Rozanski `__. - -Py-Tendermint -^^^^^^^^^^^^^ - -A Python microframework for building blockchain applications with Tendermint, written in Python, `authored by Dave Bryson `__. - -Stratumn -^^^^^^^^ - -SDK for "Proof-of-Process" networks, written in Go, `authored by the Stratumn team `__. - -TMChat -^^^^^^ - -P2P chat using Tendermint, written in Java, `authored by wolfposd `__. - - -ABCI Servers ------------- - -+------------------------------------------------------------------+--------------------+--------------+ -| **Name** | **Author** | **Language** | -| | | | -+------------------------------------------------------------------+--------------------+--------------+ -| `abci `__ | Tendermint | Go | -+------------------------------------------------------------------+--------------------+--------------+ -| `js abci `__ | Tendermint | Javascript | -+------------------------------------------------------------------+--------------------+--------------+ -| `cpp-tmsp `__ | Martin Dyring | C++ | -+------------------------------------------------------------------+--------------------+--------------+ -| `c-abci `__ | ChainX | C | -+------------------------------------------------------------------+--------------------+--------------+ -| `jabci `__ | jTendermint | Java | -+------------------------------------------------------------------+--------------------+--------------+ -| `ocaml-tmsp `__ | Zach Balder | Ocaml | -+------------------------------------------------------------------+--------------------+--------------+ -| `abci_server `__ | Krzysztof Jurewicz | Erlang | -+------------------------------------------------------------------+--------------------+--------------+ -| `rust-tsp `__   | Adrian Brink | Rust       | -+------------------------------------------------------------------+--------------------+--------------+ -| `hs-abci `__ | Alberto Gonzalez | Haskell | -+------------------------------------------------------------------+--------------------+--------------+ -| `haskell-abci `__ | Christoper Goes | Haskell | -+------------------------------------------------------------------+--------------------+--------------+ -| `Spearmint `__ | Dennis Mckinnon | Javascript | -+------------------------------------------------------------------+--------------------+--------------+ -| `py-abci `__ | Dave Bryson | Python | -+------------------------------------------------------------------+--------------------+--------------+ - -Deployment Tools ----------------- +Other Tools +----------- See `deploy testnets <./deploy-testnets.html>`__ for information about all the tools built by Tendermint. We have Kubernetes, Ansible, and Terraform integrations. -Cloudsoft built `brooklyn-tendermint `__ for deploying a tendermint testnet in docker continers. It uses Clocker for Apache Brooklyn. - -Dev Tools ---------- - For upgrading from older to newer versions of tendermint and to migrate your chain data, see `tm-migrator `__ written by @hxzqlh. From 1acb12edf5e8aae084c0f6e9b257d7a2f9dbfe13 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 31 Dec 2017 17:07:08 -0500 Subject: [PATCH 012/124] p2p docs --- p2p/README.md | 119 ++--------------------------------------- p2p/docs/connection.md | 116 +++++++++++++++++++++++++++++++++++++++ p2p/docs/node.md | 53 ++++++++++++++++++ p2p/docs/peer.md | 105 ++++++++++++++++++++++++++++++++++++ p2p/docs/reputation.md | 23 ++++++++ 5 files changed, 302 insertions(+), 114 deletions(-) create mode 100644 p2p/docs/connection.md create mode 100644 p2p/docs/node.md create mode 100644 p2p/docs/peer.md create mode 100644 p2p/docs/reputation.md diff --git a/p2p/README.md b/p2p/README.md index d653b2ca..5d1f984c 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -4,119 +4,10 @@ `tendermint/tendermint/p2p` provides an abstraction around peer-to-peer communication.
-## MConnection +See: -`MConnection` is a multiplex connection: +- [docs/connection] for details on how connections and multiplexing work +- [docs/peer] for details on peer ID, handshakes, and peer exchange +- [docs/node] for details about different types of nodes and how they should work +- [docs/reputation] for details on how peer reputation is managed -__multiplex__ *noun* a system or signal involving simultaneous transmission of -several messages along a single channel of communication. - -Each `MConnection` handles message transmission on multiple abstract communication -`Channel`s. Each channel has a globally unique byte id. -The byte id and the relative priorities of each `Channel` are configured upon -initialization of the connection. - -The `MConnection` supports three packet types: Ping, Pong, and Msg. - -### Ping and Pong - -The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively - -When we haven't received any messages on an `MConnection` in a time `pingTimeout`, we send a ping message. -When a ping is received on the `MConnection`, a pong is sent in response. - -If a pong is not received in sufficient time, the peer's score should be decremented (TODO). - -### Msg - -Messages in channels are chopped into smaller msgPackets for multiplexing. - -``` -type msgPacket struct { - ChannelID byte - EOF byte // 1 means message ends here. - Bytes []byte -} -``` - -The msgPacket is serialized using go-wire, and prefixed with a 0x3. -The received `Bytes` of a sequential set of packets are appended together -until a packet with `EOF=1` is received, at which point the complete serialized message -is returned for processing by the corresponding channels `onReceive` function. - -### Multiplexing - -Messages are sent from a single `sendRoutine`, which loops over a select statement that results in the sending -of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. -Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. -Messages are chosen for a batch one a time from the channel with the lowest ratio of recently sent bytes to channel priority. - -## Sending Messages - -There are two methods for sending messages: -```go -func (m MConnection) Send(chID byte, msg interface{}) bool {} -func (m MConnection) TrySend(chID byte, msg interface{}) bool {} -``` - -`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued -for the channel with the given id byte `chID`. The message `msg` is serialized -using the `tendermint/wire` submodule's `WriteBinary()` reflection routine. - -`TrySend(chID, msg)` is a nonblocking call that returns false if the channel's -queue is full. - -`Send()` and `TrySend()` are also exposed for each `Peer`. - -## Peer - -Each peer has one `MConnection` instance, and includes other information such as whether the connection -was outbound, whether the connection should be recreated if it closes, various identity information about the node, -and other higher level thread-safe data used by the reactors. - -## Switch/Reactor - -The `Switch` handles peer connections and exposes an API to receive incoming messages -on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one -or more `Channels`. So while sending outgoing messages is typically performed on the peer, -incoming messages are received on the reactor. - -```go -// Declare a MyReactor reactor that handles messages on MyChannelID. -type MyReactor struct{} - -func (reactor MyReactor) GetChannels() []*ChannelDescriptor { - return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}} -} - -func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) { - r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error) - msgString := ReadString(r, n, err) - fmt.Println(msgString) -} - -// Other Reactor methods omitted for brevity -... - -switch := NewSwitch([]Reactor{MyReactor{}}) - -... - -// Send a random message to all outbound connections -for _, peer := range switch.Peers().List() { - if peer.IsOutbound() { - peer.Send(MyChannelID, "Here's a random message") - } -} -``` - -### PexReactor/AddrBook - -A `PEXReactor` reactor implementation is provided to automate peer discovery. - -```go -book := p2p.NewAddrBook(addrBookFilePath) -pexReactor := p2p.NewPEXReactor(book) -... -switch := NewSwitch([]Reactor{pexReactor, myReactor, ...}) -``` diff --git a/p2p/docs/connection.md b/p2p/docs/connection.md new file mode 100644 index 00000000..72847fa1 --- /dev/null +++ b/p2p/docs/connection.md @@ -0,0 +1,116 @@ +## MConnection + +`MConnection` is a multiplex connection: + +__multiplex__ *noun* a system or signal involving simultaneous transmission of +several messages along a single channel of communication. + +Each `MConnection` handles message transmission on multiple abstract communication +`Channel`s. Each channel has a globally unique byte id. +The byte id and the relative priorities of each `Channel` are configured upon +initialization of the connection. + +The `MConnection` supports three packet types: Ping, Pong, and Msg. + +### Ping and Pong + +The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively + +When we haven't received any messages on an `MConnection` in a time `pingTimeout`, we send a ping message. +When a ping is received on the `MConnection`, a pong is sent in response. + +If a pong is not received in sufficient time, the peer's score should be decremented (TODO). + +### Msg + +Messages in channels are chopped into smaller msgPackets for multiplexing. + +``` +type msgPacket struct { + ChannelID byte + EOF byte // 1 means message ends here. + Bytes []byte +} +``` + +The msgPacket is serialized using go-wire, and prefixed with a 0x3. +The received `Bytes` of a sequential set of packets are appended together +until a packet with `EOF=1` is received, at which point the complete serialized message +is returned for processing by the corresponding channels `onReceive` function. + +### Multiplexing + +Messages are sent from a single `sendRoutine`, which loops over a select statement that results in the sending +of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. +Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. +Messages are chosen for a batch one a time from the channel with the lowest ratio of recently sent bytes to channel priority. + +## Sending Messages + +There are two methods for sending messages: +```go +func (m MConnection) Send(chID byte, msg interface{}) bool {} +func (m MConnection) TrySend(chID byte, msg interface{}) bool {} +``` + +`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued +for the channel with the given id byte `chID`. The message `msg` is serialized +using the `tendermint/wire` submodule's `WriteBinary()` reflection routine. + +`TrySend(chID, msg)` is a nonblocking call that returns false if the channel's +queue is full. + +`Send()` and `TrySend()` are also exposed for each `Peer`. + +## Peer + +Each peer has one `MConnection` instance, and includes other information such as whether the connection +was outbound, whether the connection should be recreated if it closes, various identity information about the node, +and other higher level thread-safe data used by the reactors. + +## Switch/Reactor + +The `Switch` handles peer connections and exposes an API to receive incoming messages +on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one +or more `Channels`. So while sending outgoing messages is typically performed on the peer, +incoming messages are received on the reactor. + +```go +// Declare a MyReactor reactor that handles messages on MyChannelID. +type MyReactor struct{} + +func (reactor MyReactor) GetChannels() []*ChannelDescriptor { + return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}} +} + +func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) { + r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error) + msgString := ReadString(r, n, err) + fmt.Println(msgString) +} + +// Other Reactor methods omitted for brevity +... + +switch := NewSwitch([]Reactor{MyReactor{}}) + +... + +// Send a random message to all outbound connections +for _, peer := range switch.Peers().List() { + if peer.IsOutbound() { + peer.Send(MyChannelID, "Here's a random message") + } +} +``` + +### PexReactor/AddrBook + +A `PEXReactor` reactor implementation is provided to automate peer discovery. + +```go +book := p2p.NewAddrBook(addrBookFilePath) +pexReactor := p2p.NewPEXReactor(book) +... +switch := NewSwitch([]Reactor{pexReactor, myReactor, ...}) +``` diff --git a/p2p/docs/node.md b/p2p/docs/node.md new file mode 100644 index 00000000..a8afc85c --- /dev/null +++ b/p2p/docs/node.md @@ -0,0 +1,53 @@ +# Tendermint Peer Discovery + +A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to others. +This document describes what kind of nodes Tendermint should enable and how they should work. + +## Node startup options +--p2p.seed_mode // If present, this node operates in seed mode. It will kick incoming peers after sharing some peers. +--p2p.seeds “1.2.3.4:466656,2.3.4.5:4444” // Dials these seeds to get peers and disconnects. +--p2p.persistent_peers “1.2.3.4:46656,2.3.4.5:466656” // These connections will be auto-redialed. If dial_seeds and persistent intersect, the user will be WARNED that seeds may auto-close connections and the node may not be able to keep the connection persistent + +## Seeds + +Seeds are the first point of contact for a new node. +They return a list of known active peers and disconnect. + +Seeds should operate full nodes, and with the PEX reactor in a "crawler" mode +that continuously explores to validate the availability of peers. + +Seeds should only respond with some top percentile of the best peers it knows about. + +## New Full Node + +A new node has seeds hardcoded into the software, but they can also be set manually (config file or flags). +The new node must also have access to a recent block height, H, and hash, HASH. + +The node then queries some seeds for peers for its chain, +dials those peers, and runs the Tendermint protocols with those it successfully connects to. + +When the peer catches up to height H, it ensures the block hash matches HASH. + +## Restarted Full Node + +A node checks its address book on startup and attempts to connect to peers from there. +If it can't connect to any peers after some time, it falls back to the seeds to find more. + +## Validator Node + +A validator node is a node that interfaces with a validator signing key. +These nodes require the highest security, and should not accept incoming connections. +They should maintain outgoing connections to a controlled set of "Sentry Nodes" that serve +as their proxy shield to the rest of the network. + +Validators that know and trust each other can accept incoming connections from one another and maintain direct private connectivity via VPN. + +## Sentry Node + +Sentry nodes are guardians of a validator node and provide it access to the rest of the network. +Sentry nodes may be dynamic, but should maintain persistent connections to some evolving random subset of each other. +They should always expect to have direct incoming connections from the validator node and its backup/s. +They do not report the validator node's address in the PEX. +They may be more strict about the quality of peers they keep. + +Sentry nodes belonging to validators that trust each other may wish to maintain persistent connections via VPN with one another, but only report each other sparingly in the PEX. diff --git a/p2p/docs/peer.md b/p2p/docs/peer.md new file mode 100644 index 00000000..15870ea7 --- /dev/null +++ b/p2p/docs/peer.md @@ -0,0 +1,105 @@ +# Tendermint Peers + +This document explains how Tendermint Peers are identified, how they connect to one another, +and how other peers are found. + +## Peer Identity + +Tendermint peers are expected to maintain long-term persistent identities in the form of a private key. +Each peer has an ID defined as `peer.ID == peer.PrivKey.Address()`, where `Address` uses the scheme defined in go-crypto. + +Peer ID's must come with some Proof-of-Work; that is, +they must satisfy `peer.PrivKey.Address() < target` for some difficulty target. +This ensures they are not too easy to generate. + +A single peer ID can have multiple IP addresses associated with - for simplicity, we only keep track +of the latest one. + +When attempting to connect to a peer, we use the PeerURL: `@:`. +We will attempt to connect to the peer at IP:PORT, and verify, +via authenticated encryption, that it is in possession of the private key +corresponding to ``. This prevents man-in-the-middle attacks on the peer layer. + +Peers can also be connected to without specifying an ID, ie. `:`. +In this case, the peer cannot be authenticated and other means, such as a VPN, +must be used. + +## Connections + +All p2p connections use TCP. +Upon establishing a successful TCP connection with a peer, +two handhsakes are performed: one for authenticated encryption, and one for Tendermint versioning. +Both handshakes have configurable timeouts (they should complete quickly). + +### Authenticated Encryption Handshake + +Tendermint implements the Station-to-Station protocol +using ED25519 keys for Diffie-Helman key-exchange and NACL SecretBox for encryption. +It goes as follows: +- generate an emphemeral ED25519 keypair +- send the ephemeral public key to the peer +- wait to receive the peer's ephemeral public key +- compute the Diffie-Hellman shared secret using the peers ephemeral public key and our ephemeral private key +- generate nonces to use for encryption + - TODO +- all communications from now on are encrypted using the shared secret +- generate a common challenge to sign +- sign the common challenge with our persistent private key +- send the signed challenge and persistent public key to the peer +- wait to receive the signed challenge and persistent public key from the peer +- verify the signature in the signed challenge using the peers persistent public key + + +If this is an outgoing connection (we dialed the peer) and we used a peer ID, +then finally verify that the `peer.PubKey` corresponds to the peer ID we dialed, +ie. `peer.PubKey.Address() == `. + +The connection has now been authenticated. All traffic is encrypted. + +Note that only the dialer can authenticate the identity of the peer, +but this is what we care about since when we join the network we wish to +ensure we have reached the intended peer (and are not being MITMd). + + +### Peer Filter + +Before continuing, we check if the new peer has the same ID has ourselves or +an existing peer. If so, we disconnect. + +We also check the peer's address and public key against +an optional whitelist which can be managed through the ABCI app - +if the whitelist is enabled and the peer is not on it, the connection is +terminated. + + +### Tendermint Version Handshake + +The Tendermint Version Handshake allows the peers to exchange their NodeInfo, which contains: + +``` +type NodeInfo struct { + PubKey crypto.PubKey `json:"pub_key"` + Moniker string `json:"moniker"` + Network string `json:"network"` + RemoteAddr string `json:"remote_addr"` + ListenAddr string `json:"listen_addr"` // accepting in + Version string `json:"version"` // major.minor.revision + Channels []int8 `json:"channels"` // active reactor channels + Other []string `json:"other"` // other application specific data +} +``` + +The connection is disconnected if: +- `peer.NodeInfo.PubKey != peer.PubKey` +- `peer.NodeInfo.Version` is not formatted as `X.X.X` where X are integers known as Major, Minor, and Revision +- `peer.NodeInfo.Version` Major is not the same as ours +- `peer.NodeInfo.Version` Minor is not the same as ours +- `peer.NodeInfo.Network` is not the same as ours + + +At this point, if we have not disconnected, the peer is valid and added to the switch, +so it is added to all reactors. + + +### Connection Activity + diff --git a/p2p/docs/reputation.md b/p2p/docs/reputation.md new file mode 100644 index 00000000..a2a995e5 --- /dev/null +++ b/p2p/docs/reputation.md @@ -0,0 +1,23 @@ + +# Peer Strategy + +Peers are managed using an address book and a trust metric. +The book keeps a record of vetted peers and unvetted peers. +When we need more peers, we pick them randomly from the addrbook with some +configurable bias for unvetted peers. When we’re asked for peers, we provide a random selection with no bias. + +The trust metric tracks the quality of the peers. +When a peer exceeds a certain quality for a certain amount of time, +it is marked as vetted in the addrbook. +If a vetted peer's quality degrades sufficiently, it is booted, and must prove itself from scratch. +If we need to make room for a new vetted peer, we move the lowest scoring vetted peer back to unvetted. +If we need to make room for a new unvetted peer, we remove the lowest scoring unvetted peer - +possibly only if its below some absolute minimum ? + +Peer quality is tracked in the connection and across the reactors. +Behaviours are defined as one of: + - fatal - something outright malicious. we should disconnect and remember them. + - bad - any kind of timeout, msgs that dont unmarshal, or fail other validity checks, or msgs we didn't ask for or arent expecting + - neutral - normal correct behaviour. unknown channels/msg types (version upgrades). + - good - some random majority of peers per reactor sending us useful messages + From cd15b677ec8339e0ac4992290aa103b524232b25 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Mon, 1 Jan 2018 15:35:28 +0000 Subject: [PATCH 013/124] docs: add abci spec --- docs/abci-cli.rst | 2 +- docs/conf.py | 45 +++++++++++++++++++++++++++------------------ docs/index.rst | 1 + 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/docs/abci-cli.rst b/docs/abci-cli.rst index 9a5ba833..ae410568 100644 --- a/docs/abci-cli.rst +++ b/docs/abci-cli.rst @@ -289,4 +289,4 @@ its own pattern of messages. For more information, see the `application developers guide <./app-development.html>`__. For examples of running an ABCI app with Tendermint, see the `getting started -guide <./getting-started.html>`__. +guide <./getting-started.html>`__. Next is the ABCI specification. diff --git a/docs/conf.py b/docs/conf.py index d5c49355..92c5e120 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -171,29 +171,38 @@ texinfo_documents = [ 'Database'), ] -repo = "https://raw.githubusercontent.com/tendermint/tools/" -branch = "master" +# ---- customization ------------------------- -tools = "./tools" -assets = tools + "/assets" +tools_repo = "https://raw.githubusercontent.com/tendermint/tools/" +tools_branch = "master" -if os.path.isdir(tools) != True: - os.mkdir(tools) -if os.path.isdir(assets) != True: - os.mkdir(assets) +tools_dir = "./tools" +assets_dir = tools_dir + "/assets" -urllib.urlretrieve(repo+branch+'/ansible/README.rst', filename=tools+'/ansible.rst') -urllib.urlretrieve(repo+branch+'/ansible/assets/a_plus_t.png', filename=assets+'/a_plus_t.png') +if os.path.isdir(tools_dir) != True: + os.mkdir(tools_dir) +if os.path.isdir(assets_dir) != True: + os.mkdir(assets_dir) -urllib.urlretrieve(repo+branch+'/docker/README.rst', filename=tools+'/docker.rst') +urllib.urlretrieve(tools_repo+tools_branch+'/ansible/README.rst', filename=tools_dir+'/ansible.rst') +urllib.urlretrieve(tools_repo+tools_branch+'/ansible/assets/a_plus_t.png', filename=assets_dir+'/a_plus_t.png') -urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/README.rst', filename=tools+'/mintnet-kubernetes.rst') -urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/gce1.png', filename=assets+'/gce1.png') -urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/gce2.png', filename=assets+'/gce2.png') -urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/statefulset.png', filename=assets+'/statefulset.png') -urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/t_plus_k.png', filename=assets+'/t_plus_k.png') +urllib.urlretrieve(tools_repo+tools_branch+'/docker/README.rst', filename=tools_dir+'/docker.rst') -urllib.urlretrieve(repo+branch+'/terraform-digitalocean/README.rst', filename=tools+'/terraform-digitalocean.rst') -urllib.urlretrieve(repo+branch+'/tm-bench/README.rst', filename=tools+'/benchmarking-and-monitoring.rst') +urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/README.rst', filename=tools_dir+'/mintnet-kubernetes.rst') +urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce1.png', filename=assets_dir+'/gce1.png') +urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce2.png', filename=assets_dir+'/gce2.png') +urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/statefulset.png', filename=assets_dir+'/statefulset.png') +urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/t_plus_k.png', filename=assets_dir+'/t_plus_k.png') + +urllib.urlretrieve(tools_repo+tools_branch+'/terraform-digitalocean/README.rst', filename=tools_dir+'/terraform-digitalocean.rst') +urllib.urlretrieve(tools_repo+tools_branch+'/tm-bench/README.rst', filename=tools_dir+'/benchmarking-and-monitoring.rst') # the readme for below is included in tm-bench # urllib.urlretrieve('https://raw.githubusercontent.com/tendermint/tools/master/tm-monitor/README.rst', filename='tools/tm-monitor.rst') + +#### abci spec ################################# + +abci_repo = "https://raw.githubusercontent.com/tendermint/abci/" +abci_branch = "spec-docs" + +urllib.urlretrieve(abci_repo+abci_branch+'/specification.rst', filename='abci-spec.rst') diff --git a/docs/index.rst b/docs/index.rst index 3ad3c4c5..b32ba484 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,7 @@ Tendermint 102 :maxdepth: 2 abci-cli.rst + abci-spec.rst app-architecture.rst app-development.rst how-to-read-logs.rst From bc71840f06af0aee7bc9ec17f2dc71e2bbd65f26 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 15:59:53 -0500 Subject: [PATCH 014/124] more p2p docs --- p2p/README.md | 3 +- p2p/docs/config.md | 39 +++++++++ p2p/docs/node.md | 25 ++++-- p2p/docs/peer.md | 55 ++++++++----- p2p/docs/pex.md | 94 ++++++++++++++++++++++ p2p/docs/{reputation.md => trustmetric.md} | 13 +-- 6 files changed, 189 insertions(+), 40 deletions(-) create mode 100644 p2p/docs/config.md create mode 100644 p2p/docs/pex.md rename p2p/docs/{reputation.md => trustmetric.md} (51%) diff --git a/p2p/README.md b/p2p/README.md index 5d1f984c..a30b83b7 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -9,5 +9,6 @@ See: - [docs/connection] for details on how connections and multiplexing work - [docs/peer] for details on peer ID, handshakes, and peer exchange - [docs/node] for details about different types of nodes and how they should work -- [docs/reputation] for details on how peer reputation is managed +- [docs/pex] for details on peer discovery and exchange +- [docs/config] for details on some config options diff --git a/p2p/docs/config.md b/p2p/docs/config.md new file mode 100644 index 00000000..bc3c343c --- /dev/null +++ b/p2p/docs/config.md @@ -0,0 +1,39 @@ +# P2P Config + +Here we describe configuration options around the Peer Exchange. + +## Seed Mode + +`--p2p.seed_mode` + +The node operates in seed mode. It will kick incoming peers after sharing some peers. +It will continually crawl the network for peers. + +## Seeds + +`--p2p.seeds “1.2.3.4:466656,2.3.4.5:4444”` + +Dials these seeds when we need more peers. They will return a list of peers and then disconnect. +If we already have enough peers in the address book, we may never need to dial them. + +## Persistent Peers + +`--p2p.persistent_peers “1.2.3.4:46656,2.3.4.5:466656”` + +Dial these peers and auto-redial them if the connection fails. +These are intended to be trusted persistent peers that can help +anchor us in the p2p network. + +Note that the auto-redial uses exponential backoff and will give up +after a day of trying to connect. + +NOTE: If `dial_seeds` and `persistent_peers` intersect, +the user will be WARNED that seeds may auto-close connections +and the node may not be able to keep the connection persistent. + +## Private Persistent Peers + +`--p2p.private_persistent_peers “1.2.3.4:46656,2.3.4.5:466656”` + +These are persistent peers that we do not add to the address book or +gossip to other peers. They stay private to us. diff --git a/p2p/docs/node.md b/p2p/docs/node.md index a8afc85c..9f9fc529 100644 --- a/p2p/docs/node.md +++ b/p2p/docs/node.md @@ -3,11 +3,6 @@ A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to others. This document describes what kind of nodes Tendermint should enable and how they should work. -## Node startup options ---p2p.seed_mode // If present, this node operates in seed mode. It will kick incoming peers after sharing some peers. ---p2p.seeds “1.2.3.4:466656,2.3.4.5:4444” // Dials these seeds to get peers and disconnects. ---p2p.persistent_peers “1.2.3.4:46656,2.3.4.5:466656” // These connections will be auto-redialed. If dial_seeds and persistent intersect, the user will be WARNED that seeds may auto-close connections and the node may not be able to keep the connection persistent - ## Seeds Seeds are the first point of contact for a new node. @@ -17,22 +12,36 @@ Seeds should operate full nodes, and with the PEX reactor in a "crawler" mode that continuously explores to validate the availability of peers. Seeds should only respond with some top percentile of the best peers it knows about. +See [reputation] for details on peer quality. ## New Full Node -A new node has seeds hardcoded into the software, but they can also be set manually (config file or flags). -The new node must also have access to a recent block height, H, and hash, HASH. +A new node needs a few things to connect to the network: +- a list of seeds, which can be provided to Tendermint via config file or flags, +or hardcoded into the software by in-process apps +- a `ChainID`, also called `Network` at the p2p layer +- a recent block height, H, and hash, HASH for the blockchain. -The node then queries some seeds for peers for its chain, +The values `H` and `HASH` must be received and corroborated by means external to Tendermint, and specific to the user - ie. via the user's trusted social consensus. +This requirement to validate `H` and `HASH` out-of-band and via social consensus +is the essential difference in security models between Proof-of-Work and Proof-of-Stake blockchains. + +With the above, the node then queries some seeds for peers for its chain, dials those peers, and runs the Tendermint protocols with those it successfully connects to. When the peer catches up to height H, it ensures the block hash matches HASH. +If not, Tendermint will exit, and the user must try again - either they are connected +to bad peers or their social consensus was invalidated. ## Restarted Full Node A node checks its address book on startup and attempts to connect to peers from there. If it can't connect to any peers after some time, it falls back to the seeds to find more. +Restarted full nodes can run the `blockchain` or `consensus` reactor protocols to sync up +to the latest state of the blockchain, assuming they aren't too far behind. +If they are too far behind, they may need to validate a recent `H` and `HASH` out-of-band again. + ## Validator Node A validator node is a node that interfaces with a validator signing key. diff --git a/p2p/docs/peer.md b/p2p/docs/peer.md index 15870ea7..5281a702 100644 --- a/p2p/docs/peer.md +++ b/p2p/docs/peer.md @@ -10,19 +10,19 @@ Each peer has an ID defined as `peer.ID == peer.PrivKey.Address()`, where `Addre Peer ID's must come with some Proof-of-Work; that is, they must satisfy `peer.PrivKey.Address() < target` for some difficulty target. -This ensures they are not too easy to generate. +This ensures they are not too easy to generate. To begin, let `target == 2^240`. -A single peer ID can have multiple IP addresses associated with - for simplicity, we only keep track -of the latest one. +A single peer ID can have multiple IP addresses associated with it. +For simplicity, we only keep track of the latest one. When attempting to connect to a peer, we use the PeerURL: `@:`. We will attempt to connect to the peer at IP:PORT, and verify, via authenticated encryption, that it is in possession of the private key corresponding to ``. This prevents man-in-the-middle attacks on the peer layer. -Peers can also be connected to without specifying an ID, ie. `:`. -In this case, the peer cannot be authenticated and other means, such as a VPN, -must be used. +Peers can also be connected to without specifying an ID, ie. just `:`. +In this case, the peer must be authenticated out-of-band of Tendermint, +for instance via VPN ## Connections @@ -40,18 +40,27 @@ It goes as follows: - send the ephemeral public key to the peer - wait to receive the peer's ephemeral public key - compute the Diffie-Hellman shared secret using the peers ephemeral public key and our ephemeral private key -- generate nonces to use for encryption - - TODO -- all communications from now on are encrypted using the shared secret -- generate a common challenge to sign +- generate two nonces to use for encryption (sending and receiving) as follows: + - sort the ephemeral public keys in ascending order and concatenate them + - RIPEMD160 the result + - append 4 empty bytes (extending the hash to 24-bytes) + - the result is nonce1 + - flip the last bit of nonce1 to get nonce2 + - if we had the smaller ephemeral pubkey, use nonce1 for receiving, nonce2 for sending; + else the opposite +- all communications from now on are encrypted using the shared secret and the nonces, where each nonce +- we now have an encrypted channel, but still need to authenticate +increments by 2 every time it is used +- generate a common challenge to sign: + - SHA256 of the sorted (lowest first) and concatenated ephemeral pub keys - sign the common challenge with our persistent private key -- send the signed challenge and persistent public key to the peer -- wait to receive the signed challenge and persistent public key from the peer -- verify the signature in the signed challenge using the peers persistent public key +- send the go-wire encoded persistent pubkey and signature to the peer +- wait to receive the persistent public key and signature from the peer +- verify the signature on the challenge using the peer's persistent public key If this is an outgoing connection (we dialed the peer) and we used a peer ID, -then finally verify that the `peer.PubKey` corresponds to the peer ID we dialed, +then finally verify that the peer's persistent public key corresponds to the peer ID we dialed, ie. `peer.PubKey.Address() == `. The connection has now been authenticated. All traffic is encrypted. @@ -60,21 +69,20 @@ Note that only the dialer can authenticate the identity of the peer, but this is what we care about since when we join the network we wish to ensure we have reached the intended peer (and are not being MITMd). - ### Peer Filter -Before continuing, we check if the new peer has the same ID has ourselves or +Before continuing, we check if the new peer has the same ID as ourselves or an existing peer. If so, we disconnect. We also check the peer's address and public key against an optional whitelist which can be managed through the ABCI app - -if the whitelist is enabled and the peer is not on it, the connection is +if the whitelist is enabled and the peer does not qualigy, the connection is terminated. ### Tendermint Version Handshake -The Tendermint Version Handshake allows the peers to exchange their NodeInfo, which contains: +The Tendermint Version Handshake allows the peers to exchange their NodeInfo: ``` type NodeInfo struct { @@ -95,11 +103,16 @@ The connection is disconnected if: - `peer.NodeInfo.Version` Major is not the same as ours - `peer.NodeInfo.Version` Minor is not the same as ours - `peer.NodeInfo.Network` is not the same as ours +- `peer.Channels` does not intersect with our known Channels. -At this point, if we have not disconnected, the peer is valid and added to the switch, -so it is added to all reactors. +At this point, if we have not disconnected, the peer is valid. +It is added to the switch and hence all reactors via the `AddPeer` method. +Note that each reactor may handle multiple channels. +## Connection Activity -### Connection Activity +Once a peer is added, incoming messages for a given reactor are handled through +that reactor's `Receive` method, and output messages are sent directly by the Reactors +on each peer. A typical reactor maintains per-peer go-routine/s that handle this. diff --git a/p2p/docs/pex.md b/p2p/docs/pex.md new file mode 100644 index 00000000..a71b9717 --- /dev/null +++ b/p2p/docs/pex.md @@ -0,0 +1,94 @@ +# Peer Strategy and Exchange + +Here we outline the design of the AddressBook +and how it used by the Peer Exchange Reactor (PEX) to ensure we are connected +to good peers and to gossip peers to others. + +## Peer Types + +Certain peers are special in that they are specified by the user as `persistent`, +which means we auto-redial them if the connection fails. +Some such peers can additional be marked as `private`, which means +we will not gossip them to others. + +All others peers are tracked using an address book. + +## Discovery + +Peer discovery begins with a list of seeds. +When we have no peers, or have been unable to find enough peers from existing ones, +we dial a randomly selected seed to get a list of peers to dial. + +So long as we have less than `MaxPeers`, we periodically request additional peers +from each of our own. If sufficient time goes by and we still can't find enough peers, +we try the seeds again. + +## Address Book + +Peers are tracked via their ID (their PubKey.Address()). +For each ID, the address book keeps the most recent IP:PORT. +Peers are added to the address book from the PEX when they first connect to us or +when we hear about them from other peers. + +The address book is arranged in sets of buckets, and distinguishes between +vetted and unvetted peers. It keeps different sets of buckets for vetted and +unvetted peers. Buckets provide randomization over peer selection. + +A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets. + +## Vetting + +When a peer is first added, it is unvetted. +Marking a peer as vetted is outside the scope of the `p2p` package. +For Tendermint, a Peer becomes vetted once it has contributed sufficiently +at the consensus layer; ie. once it has sent us valid and not-yet-known +votes and/or block parts for `NumBlocksForVetted` blocks. +Other users of the p2p package can determine their own conditions for when a peer is marked vetted. + +If a peer becomes vetted but there are already too many vetted peers, +a randomly selected one of the vetted peers becomes unvetted. + +If a peer becomes unvetted (either a new peer, or one that was previously vetted), +a randomly selected one of the unvetted peers is removed from the address book. + +More fine-grained tracking of peer behaviour can be done using +a Trust Metric, but it's best to start with something simple. + +## Select Peers to Dial + +When we need more peers, we pick them randomly from the addrbook with some +configurable bias for unvetted peers. The bias should be lower when we have fewer peers, +and can increase as we obtain more, ensuring that our first peers are more trustworthy, +but always giving us the chance to discover new good peers. + +## Select Peers to Exchange + +When we’re asked for peers, we select them as follows: +- select at most `maxGetSelection` peers +- try to select at least `minGetSelection` peers - if we have less than that, select them all. +- select a random, unbiased `getSelectionPercent` of the peers + +Send the selected peers. Note we select peers for sending without bias for vetted/unvetted. + +## Preventing Spam + +There are various cases where we decide a peer has misbehaved and we disconnect from them. +When this happens, the peer is removed from the address book and black listed for +some amount of time. We call this "Disconnect and Mark". +Note that the bad behaviour may be detected outside the PEX reactor itseld +(for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor +so it can remove and mark the peer. + +In the PEX, if a peer sends us unsolicited lists of peers, +or if the peer sends too many requests for more peers in a given amount of time, +we Disconnect and Mark. + +## Trust Metric + +The quality of peers can be tracked in more fine-grained detail using a +Proportional-Integral-Derrivative (PID) controller that incorporates +current, past, and rate-of-change data to inform peer quality. + +While a PID trust metric has been implemented, it remains for future work +to use it in the PEX. + diff --git a/p2p/docs/reputation.md b/p2p/docs/trustmetric.md similarity index 51% rename from p2p/docs/reputation.md rename to p2p/docs/trustmetric.md index a2a995e5..b0eaf96e 100644 --- a/p2p/docs/reputation.md +++ b/p2p/docs/trustmetric.md @@ -1,11 +1,4 @@ -# Peer Strategy - -Peers are managed using an address book and a trust metric. -The book keeps a record of vetted peers and unvetted peers. -When we need more peers, we pick them randomly from the addrbook with some -configurable bias for unvetted peers. When we’re asked for peers, we provide a random selection with no bias. - The trust metric tracks the quality of the peers. When a peer exceeds a certain quality for a certain amount of time, it is marked as vetted in the addrbook. @@ -17,7 +10,7 @@ possibly only if its below some absolute minimum ? Peer quality is tracked in the connection and across the reactors. Behaviours are defined as one of: - fatal - something outright malicious. we should disconnect and remember them. - - bad - any kind of timeout, msgs that dont unmarshal, or fail other validity checks, or msgs we didn't ask for or arent expecting - - neutral - normal correct behaviour. unknown channels/msg types (version upgrades). - - good - some random majority of peers per reactor sending us useful messages + - bad - any kind of timeout, msgs that dont unmarshal, or fail other validity checks, or msgs we didn't ask for or arent expecting + - neutral - normal correct behaviour. unknown channels/msg types (version upgrades). + - good - some random majority of peers per reactor sending us useful messages From 528154f1a218b9c884ea0de2402304f002c601a1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 29 Dec 2017 01:53:41 -0500 Subject: [PATCH 015/124] p2p: PrivKey need not be Ed25519 --- node/node.go | 19 +++++++++---------- p2p/README.md | 1 - p2p/peer.go | 8 ++++---- p2p/peer_test.go | 18 +++++++++--------- p2p/secret_connection.go | 16 ++++++++-------- p2p/secret_connection_test.go | 15 +++++++-------- p2p/switch.go | 18 +++++++++--------- p2p/switch_test.go | 6 +++--- p2p/types.go | 14 +++++++------- 9 files changed, 56 insertions(+), 59 deletions(-) diff --git a/node/node.go b/node/node.go index f922d832..7f845f90 100644 --- a/node/node.go +++ b/node/node.go @@ -96,7 +96,6 @@ type Node struct { privValidator types.PrivValidator // local node's validator key // network - privKey crypto.PrivKeyEd25519 // local node's p2p key sw *p2p.Switch // p2p connections addrBook *p2p.AddrBook // known peers trustMetricStore *trust.TrustMetricStore // trust metrics for all peers @@ -170,9 +169,6 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) - // Generate node PrivKey - privKey := crypto.GenPrivKeyEd25519() - // Decide whether to fast-sync or not // We don't fast-sync when the only validator is us. fastSync := config.FastSync @@ -275,7 +271,7 @@ func NewNode(config *cfg.Config, } return nil }) - sw.SetPubKeyFilter(func(pubkey crypto.PubKeyEd25519) error { + sw.SetPubKeyFilter(func(pubkey crypto.PubKey) error { resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: cmn.Fmt("/p2p/filter/pubkey/%X", pubkey.Bytes())}) if err != nil { return err @@ -328,7 +324,6 @@ func NewNode(config *cfg.Config, genesisDoc: genDoc, privValidator: privValidator, - privKey: privKey, sw: sw, addrBook: addrBook, trustMetricStore: trustMetricStore, @@ -371,9 +366,13 @@ func (n *Node) OnStart() error { l := p2p.NewDefaultListener(protocol, address, n.config.P2P.SkipUPNP, n.Logger.With("module", "p2p")) n.sw.AddListener(l) + // Generate node PrivKey + // TODO: Load + privKey := crypto.GenPrivKeyEd25519().Wrap() + // Start the switch - n.sw.SetNodeInfo(n.makeNodeInfo()) - n.sw.SetNodePrivKey(n.privKey) + n.sw.SetNodeInfo(n.makeNodeInfo(privKey.PubKey())) + n.sw.SetNodePrivKey(privKey) err = n.sw.Start() if err != nil { return err @@ -534,13 +533,13 @@ func (n *Node) ProxyApp() proxy.AppConns { return n.proxyApp } -func (n *Node) makeNodeInfo() *p2p.NodeInfo { +func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { txIndexerStatus := "on" if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } nodeInfo := &p2p.NodeInfo{ - PubKey: n.privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: pubKey, Moniker: n.config.Moniker, Network: n.genesisDoc.ChainID, Version: version.Version, diff --git a/p2p/README.md b/p2p/README.md index a30b83b7..3d0e9eeb 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -11,4 +11,3 @@ See: - [docs/node] for details about different types of nodes and how they should work - [docs/pex] for details on peer discovery and exchange - [docs/config] for details on some config options - diff --git a/p2p/peer.go b/p2p/peer.go index cc7f4927..91824dc8 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -77,7 +77,7 @@ func DefaultPeerConfig() *PeerConfig { } func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { conn, err := dial(addr, config) if err != nil { @@ -95,13 +95,13 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) } func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { conn := rawConn @@ -216,7 +216,7 @@ func (p *peer) Addr() net.Addr { } // PubKey returns peer's public key. -func (p *peer) PubKey() crypto.PubKeyEd25519 { +func (p *peer) PubKey() crypto.PubKey { if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index b53b0bb1..a2884b33 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -16,7 +16,7 @@ func TestPeerBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() @@ -43,7 +43,7 @@ func TestPeerWithoutAuthEnc(t *testing.T) { config.AuthEnc = false // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: config} rp.Start() defer rp.Stop() @@ -64,7 +64,7 @@ func TestPeerSend(t *testing.T) { config.AuthEnc = false // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: config} rp.Start() defer rp.Stop() @@ -85,13 +85,13 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) {ID: 0x01, Priority: 1}, } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} - pk := crypto.GenPrivKeyEd25519() + pk := crypto.GenPrivKeyEd25519().Wrap() p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config) if err != nil { return nil, err } err = p.HandshakeTimeout(&NodeInfo{ - PubKey: pk.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", Version: "123.123.123", @@ -103,7 +103,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) } type remotePeer struct { - PrivKey crypto.PrivKeyEd25519 + PrivKey crypto.PrivKey Config *PeerConfig addr *NetAddress quit chan struct{} @@ -113,8 +113,8 @@ func (p *remotePeer) Addr() *NetAddress { return p.addr } -func (p *remotePeer) PubKey() crypto.PubKeyEd25519 { - return p.PrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) +func (p *remotePeer) PubKey() crypto.PubKey { + return p.PrivKey.PubKey() } func (p *remotePeer) Start() { @@ -142,7 +142,7 @@ func (p *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } err = peer.HandshakeTimeout(&NodeInfo{ - PubKey: p.PrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", Version: "123.123.123", diff --git a/p2p/secret_connection.go b/p2p/secret_connection.go index aec0a751..f022d9c3 100644 --- a/p2p/secret_connection.go +++ b/p2p/secret_connection.go @@ -38,7 +38,7 @@ type SecretConnection struct { recvBuffer []byte recvNonce *[24]byte sendNonce *[24]byte - remPubKey crypto.PubKeyEd25519 + remPubKey crypto.PubKey shrSecret *[32]byte // shared secret } @@ -46,9 +46,9 @@ type SecretConnection struct { // Returns nil if error in handshake. // Caller should call conn.Close() // See docs/sts-final.pdf for more information. -func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25519) (*SecretConnection, error) { +func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) { - locPubKey := locPrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + locPubKey := locPrivKey.PubKey() // Generate ephemeral keys for perfect forward secrecy. locEphPub, locEphPriv := genEphKeys() @@ -100,12 +100,12 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25 } // We've authorized. - sc.remPubKey = remPubKey.Unwrap().(crypto.PubKeyEd25519) + sc.remPubKey = remPubKey return sc, nil } // Returns authenticated remote pubkey -func (sc *SecretConnection) RemotePubKey() crypto.PubKeyEd25519 { +func (sc *SecretConnection) RemotePubKey() crypto.PubKey { return sc.remPubKey } @@ -258,8 +258,8 @@ func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) { return hash32(append(loPubKey[:], hiPubKey[:]...)) } -func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKeyEd25519) (signature crypto.SignatureEd25519) { - signature = locPrivKey.Sign(challenge[:]).Unwrap().(crypto.SignatureEd25519) +func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature crypto.Signature) { + signature = locPrivKey.Sign(challenge[:]) return } @@ -268,7 +268,7 @@ type authSigMessage struct { Sig crypto.Signature } -func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKeyEd25519, signature crypto.SignatureEd25519) (*authSigMessage, error) { +func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKey, signature crypto.Signature) (*authSigMessage, error) { var recvMsg authSigMessage var err1, err2 error diff --git a/p2p/secret_connection_test.go b/p2p/secret_connection_test.go index 8b58fb41..5e0611a8 100644 --- a/p2p/secret_connection_test.go +++ b/p2p/secret_connection_test.go @@ -1,7 +1,6 @@ package p2p import ( - "bytes" "io" "testing" @@ -32,10 +31,10 @@ func makeDummyConnPair() (fooConn, barConn dummyConn) { func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) { fooConn, barConn := makeDummyConnPair() - fooPrvKey := crypto.GenPrivKeyEd25519() - fooPubKey := fooPrvKey.PubKey().Unwrap().(crypto.PubKeyEd25519) - barPrvKey := crypto.GenPrivKeyEd25519() - barPubKey := barPrvKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + fooPrvKey := crypto.GenPrivKeyEd25519().Wrap() + fooPubKey := fooPrvKey.PubKey() + barPrvKey := crypto.GenPrivKeyEd25519().Wrap() + barPubKey := barPrvKey.PubKey() cmn.Parallel( func() { @@ -46,7 +45,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection return } remotePubBytes := fooSecConn.RemotePubKey() - if !bytes.Equal(remotePubBytes[:], barPubKey[:]) { + if !remotePubBytes.Equals(barPubKey) { tb.Errorf("Unexpected fooSecConn.RemotePubKey. Expected %v, got %v", barPubKey, fooSecConn.RemotePubKey()) } @@ -59,7 +58,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection return } remotePubBytes := barSecConn.RemotePubKey() - if !bytes.Equal(remotePubBytes[:], fooPubKey[:]) { + if !remotePubBytes.Equals(fooPubKey) { tb.Errorf("Unexpected barSecConn.RemotePubKey. Expected %v, got %v", fooPubKey, barSecConn.RemotePubKey()) } @@ -93,7 +92,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() { return func() { // Node handskae - nodePrvKey := crypto.GenPrivKeyEd25519() + nodePrvKey := crypto.GenPrivKeyEd25519().Wrap() nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey) if err != nil { t.Errorf("Failed to establish SecretConnection for node: %v", err) diff --git a/p2p/switch.go b/p2p/switch.go index 76b01980..fde21642 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -81,11 +81,11 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodePrivKey crypto.PrivKeyEd25519 // our node privkey + nodeInfo *NodeInfo // our node info + nodePrivKey crypto.PrivKey // our node privkey filterConnByAddr func(net.Addr) error - filterConnByPubKey func(crypto.PubKeyEd25519) error + filterConnByPubKey func(crypto.PubKey) error rng *rand.Rand // seed for randomizing dial times and orders } @@ -184,10 +184,10 @@ func (sw *Switch) NodeInfo() *NodeInfo { // SetNodePrivKey sets the switch's private key for authenticated encryption. // NOTE: Overwrites sw.nodeInfo.PubKey. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKeyEd25519) { +func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKey) { sw.nodePrivKey = nodePrivKey if sw.nodeInfo != nil { - sw.nodeInfo.PubKey = nodePrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + sw.nodeInfo.PubKey = nodePrivKey.PubKey() } } @@ -285,7 +285,7 @@ func (sw *Switch) FilterConnByAddr(addr net.Addr) error { } // FilterConnByPubKey returns an error if connecting to the given public key is forbidden. -func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKeyEd25519) error { +func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { if sw.filterConnByPubKey != nil { return sw.filterConnByPubKey(pubkey) } @@ -299,7 +299,7 @@ func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { } // SetPubKeyFilter sets the function for filtering connections by public key. -func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKeyEd25519) error) { +func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { sw.filterConnByPubKey = f } @@ -603,14 +603,14 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f // TODO: let the config be passed in? s := initSwitch(i, NewSwitch(cfg)) s.SetNodeInfo(&NodeInfo{ - PubKey: privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: privKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) - s.SetNodePrivKey(privKey) + s.SetNodePrivKey(privKey.Wrap()) return s } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 72807d36..1d6d869a 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -200,7 +200,7 @@ func TestConnPubKeyFilter(t *testing.T) { c1, c2 := netPipe() // set pubkey filter - s1.SetPubKeyFilter(func(pubkey crypto.PubKeyEd25519) error { + s1.SetPubKeyFilter(func(pubkey crypto.PubKey) error { if bytes.Equal(pubkey.Bytes(), s2.nodeInfo.PubKey.Bytes()) { return fmt.Errorf("Error: pipe is blacklisted") } @@ -232,7 +232,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() @@ -259,7 +259,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() diff --git a/p2p/types.go b/p2p/types.go index 4e0994b7..63494d9c 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,13 +12,13 @@ import ( const maxNodeInfoSize = 10240 // 10Kb type NodeInfo struct { - PubKey crypto.PubKeyEd25519 `json:"pub_key"` - Moniker string `json:"moniker"` - Network string `json:"network"` - RemoteAddr string `json:"remote_addr"` - ListenAddr string `json:"listen_addr"` - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + PubKey crypto.PubKey `json:"pub_key"` + Moniker string `json:"moniker"` + Network string `json:"network"` + RemoteAddr string `json:"remote_addr"` + ListenAddr string `json:"listen_addr"` + Version string `json:"version"` // major.minor.revision + Other []string `json:"other"` // other application specific data } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From f2e0abf1dc23544c7ce5cfb0bdd0fd46dc96ce1e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 20:21:11 -0500 Subject: [PATCH 016/124] p2p: reorder some checks in addPeer; add comments to NodeInfo --- p2p/peer.go | 1 + p2p/switch.go | 20 ++++++++++---------- p2p/types.go | 14 +++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 91824dc8..e2e73f0f 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -304,6 +304,7 @@ func (p *peer) Set(key string, data interface{}) { } // Key returns the peer's id key. +// TODO: call this ID func (p *peer) Key() string { return p.nodeInfo.ListenAddr // XXX: should probably be PubKey.KeyString() } diff --git a/p2p/switch.go b/p2p/switch.go index fde21642..d4e3b348 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -232,10 +232,15 @@ func (sw *Switch) OnStop() { // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { + // Avoid self + if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { + return errors.New("Ignoring connection from self") + } + + // Filter peer against white list if err := sw.FilterConnByAddr(peer.Addr()); err != nil { return err } - if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { return err } @@ -244,9 +249,10 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Avoid self - if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { - return errors.New("Ignoring connection from self") + // Avoid duplicate + if sw.peers.Has(peer.Key()) { + return ErrSwitchDuplicatePeer + } // Check version, chain id @@ -254,12 +260,6 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Check for duplicate peer - if sw.peers.Has(peer.Key()) { - return ErrSwitchDuplicatePeer - - } - // Start peer if sw.IsRunning() { sw.startInitPeer(peer) diff --git a/p2p/types.go b/p2p/types.go index 63494d9c..86013290 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,13 +12,13 @@ import ( const maxNodeInfoSize = 10240 // 10Kb type NodeInfo struct { - PubKey crypto.PubKey `json:"pub_key"` - Moniker string `json:"moniker"` - Network string `json:"network"` - RemoteAddr string `json:"remote_addr"` - ListenAddr string `json:"listen_addr"` - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey + Moniker string `json:"moniker"` // arbitrary moniker + Network string `json:"network"` // network/chain ID + RemoteAddr string `json:"remote_addr"` // address for the connection + ListenAddr string `json:"listen_addr"` // accepting incoming + Version string `json:"version"` // major.minor.revision + Other []string `json:"other"` // other application specific data } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From b289d2baf44ae52ec9ee44b83d4f9ce2c56919b4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 20:21:42 -0500 Subject: [PATCH 017/124] persistent node key and ID --- config/config.go | 9 ++++ node/node.go | 16 +++++-- p2p/key.go | 111 +++++++++++++++++++++++++++++++++++++++++++++ p2p/key_test.go | 49 ++++++++++++++++++++ p2p/switch.go | 33 +++++++++----- p2p/switch_test.go | 4 +- 6 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 p2p/key.go create mode 100644 p2p/key_test.go diff --git a/config/config.go b/config/config.go index 5d4a8ef6..6018dc8d 100644 --- a/config/config.go +++ b/config/config.go @@ -72,6 +72,9 @@ type BaseConfig struct { // A JSON file containing the private key to use as a validator in the consensus protocol PrivValidator string `mapstructure:"priv_validator_file"` + // A JSON file containing the private key to use for p2p authenticated encryption + NodeKey string `mapstructure:"node_key"` + // A custom human readable name for this node Moniker string `mapstructure:"moniker"` @@ -109,6 +112,7 @@ func DefaultBaseConfig() BaseConfig { return BaseConfig{ Genesis: "genesis.json", PrivValidator: "priv_validator.json", + NodeKey: "node_key.json", Moniker: defaultMoniker, ProxyApp: "tcp://127.0.0.1:46658", ABCI: "socket", @@ -141,6 +145,11 @@ func (b BaseConfig) PrivValidatorFile() string { return rootify(b.PrivValidator, b.RootDir) } +// NodeKeyFile returns the full path to the node_key.json file +func (b BaseConfig) NodeKeyFile() string { + return rootify(b.NodeKey, b.RootDir) +} + // DBDir returns the full path to the database directory func (b BaseConfig) DBDir() string { return rootify(b.DBPath, b.RootDir) diff --git a/node/node.go b/node/node.go index 7f845f90..0027c680 100644 --- a/node/node.go +++ b/node/node.go @@ -367,12 +367,20 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: Load - privKey := crypto.GenPrivKeyEd25519().Wrap() + // TODO: both the loading function and the target + // will need to be configurable + difficulty := uint8(16) // number of leading 0s in bitstring + target := p2p.MakePoWTarget(difficulty) + nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile(), target) + if err != nil { + return err + } + n.Logger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", n.config.NodeKeyFile()) // Start the switch - n.sw.SetNodeInfo(n.makeNodeInfo(privKey.PubKey())) - n.sw.SetNodePrivKey(privKey) + n.sw.SetNodeInfo(n.makeNodeInfo(nodeKey.PubKey())) + n.sw.SetNodeKey(nodeKey) + n.sw.SetPeerIDTarget(target) err = n.sw.Start() if err != nil { return err diff --git a/p2p/key.go b/p2p/key.go new file mode 100644 index 00000000..aa2ac767 --- /dev/null +++ b/p2p/key.go @@ -0,0 +1,111 @@ +package p2p + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + + crypto "github.com/tendermint/go-crypto" + cmn "github.com/tendermint/tmlibs/common" +) + +//------------------------------------------------------------------------------ +// Persistent peer ID +// TODO: encrypt on disk + +// NodeKey is the persistent peer key. +// It contains the nodes private key for authentication. +type NodeKey struct { + PrivKey crypto.PrivKey `json:"priv_key"` // our priv key +} + +// ID returns the peer's canonical ID - the hash of its public key. +func (nodeKey *NodeKey) ID() []byte { + return nodeKey.PrivKey.PubKey().Address() +} + +// PubKey returns the peer's PubKey +func (nodeKey *NodeKey) PubKey() crypto.PubKey { + return nodeKey.PrivKey.PubKey() +} + +// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath, +// and checks that the corresponding ID is less than the target. +// If the file does not exist, it generates and saves a new NodeKey +// with ID less than target. +func LoadOrGenNodeKey(filePath string, target []byte) (*NodeKey, error) { + if cmn.FileExists(filePath) { + nodeKey, err := loadNodeKey(filePath) + if err != nil { + return nil, err + } + if bytes.Compare(nodeKey.ID(), target) >= 0 { + return nil, fmt.Errorf("Loaded ID (%X) does not satisfy target (%X)", nodeKey.ID(), target) + } + return nodeKey, nil + } else { + return genNodeKey(filePath, target) + } +} + +// MakePoWTarget returns a 20 byte target byte array. +func MakePoWTarget(difficulty uint8) []byte { + zeroPrefixLen := (int(difficulty) / 8) + prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) + mod := (difficulty % 8) + if mod > 0 { + nonZeroPrefix := byte(1 << (8 - mod)) + prefix = append(prefix, nonZeroPrefix) + } + return append(prefix, bytes.Repeat([]byte{255}, 20-len(prefix))...) +} + +func loadNodeKey(filePath string) (*NodeKey, error) { + jsonBytes, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + nodeKey := new(NodeKey) + err = json.Unmarshal(jsonBytes, nodeKey) + if err != nil { + return nil, fmt.Errorf("Error reading NodeKey from %v: %v\n", filePath, err) + } + return nodeKey, nil +} + +func genNodeKey(filePath string, target []byte) (*NodeKey, error) { + privKey := genPrivKeyEd25519PoW(target).Wrap() + nodeKey := &NodeKey{ + PrivKey: privKey, + } + + jsonBytes, err := json.Marshal(nodeKey) + if err != nil { + return nil, err + } + err = ioutil.WriteFile(filePath, jsonBytes, 0600) + if err != nil { + return nil, err + } + return nodeKey, nil +} + +// generate key with address satisfying the difficult target +func genPrivKeyEd25519PoW(target []byte) crypto.PrivKeyEd25519 { + secret := crypto.CRandBytes(32) + var privKey crypto.PrivKeyEd25519 + for i := 0; ; i++ { + privKey = crypto.GenPrivKeyEd25519FromSecret(secret) + if bytes.Compare(privKey.PubKey().Address(), target) < 0 { + break + } + z := new(big.Int) + z.SetBytes(secret) + z = z.Add(z, big.NewInt(1)) + secret = z.Bytes() + + } + return privKey +} diff --git a/p2p/key_test.go b/p2p/key_test.go new file mode 100644 index 00000000..ef885e55 --- /dev/null +++ b/p2p/key_test.go @@ -0,0 +1,49 @@ +package p2p + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + cmn "github.com/tendermint/tmlibs/common" +) + +func TestLoadOrGenNodeKey(t *testing.T) { + filePath := filepath.Join(os.TempDir(), cmn.RandStr(12)+"_peer_id.json") + + target := MakePoWTarget(2) + nodeKey, err := LoadOrGenNodeKey(filePath, target) + assert.Nil(t, err) + + nodeKey2, err := LoadOrGenNodeKey(filePath, target) + assert.Nil(t, err) + + assert.Equal(t, nodeKey, nodeKey2) +} + +func repeatBytes(val byte, n int) []byte { + return bytes.Repeat([]byte{val}, n) +} + +func TestPoWTarget(t *testing.T) { + + cases := []struct { + difficulty uint8 + target []byte + }{ + {0, bytes.Repeat([]byte{255}, 20)}, + {1, append([]byte{128}, repeatBytes(255, 19)...)}, + {8, append([]byte{0}, repeatBytes(255, 19)...)}, + {9, append([]byte{0, 128}, repeatBytes(255, 18)...)}, + {10, append([]byte{0, 64}, repeatBytes(255, 18)...)}, + {16, append([]byte{0, 0}, repeatBytes(255, 18)...)}, + {17, append([]byte{0, 0, 128}, repeatBytes(255, 17)...)}, + } + + for _, c := range cases { + assert.Equal(t, MakePoWTarget(c.difficulty), c.target) + } + +} diff --git a/p2p/switch.go b/p2p/switch.go index d4e3b348..344c3c1e 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -81,8 +81,9 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodePrivKey crypto.PrivKey // our node privkey + nodeInfo *NodeInfo // our node info + nodeKey *NodeKey // our node privkey + peerIDTarget []byte filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -181,16 +182,22 @@ func (sw *Switch) NodeInfo() *NodeInfo { return sw.nodeInfo } -// SetNodePrivKey sets the switch's private key for authenticated encryption. +// SetNodeKey sets the switch's private key for authenticated encryption. // NOTE: Overwrites sw.nodeInfo.PubKey. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKey) { - sw.nodePrivKey = nodePrivKey +func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { + sw.nodeKey = nodeKey if sw.nodeInfo != nil { - sw.nodeInfo.PubKey = nodePrivKey.PubKey() + sw.nodeInfo.PubKey = nodeKey.PubKey() } } +// SetPeerIDTarget sets the target for incoming peer ID's - +// the ID must be less than the target +func (sw *Switch) SetPeerIDTarget(target []byte) { + sw.peerIDTarget = target +} + // OnStart implements BaseService. It starts all the reactors, peers, and listeners. func (sw *Switch) OnStart() error { // Start reactors @@ -370,7 +377,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, defer sw.dialing.Delete(addr.IP.String()) sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) if err != nil { sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) return nil, err @@ -598,24 +605,26 @@ func StartSwitches(switches []*Switch) error { } func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { - privKey := crypto.GenPrivKeyEd25519() // new switch, add reactors // TODO: let the config be passed in? + nodeKey := &NodeKey{ + PrivKey: crypto.GenPrivKeyEd25519().Wrap(), + } s := initSwitch(i, NewSwitch(cfg)) s.SetNodeInfo(&NodeInfo{ - PubKey: privKey.PubKey(), + PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) - s.SetNodePrivKey(privKey.Wrap()) + s.SetNodeKey(nodeKey) return s } func (sw *Switch) addPeerWithConnection(conn net.Conn) error { - peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig) + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) if err != nil { if err := conn.Close(); err != nil { sw.Logger.Error("Error closing connection", "err", err) @@ -632,7 +641,7 @@ func (sw *Switch) addPeerWithConnection(conn net.Conn) error { } func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error { - peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config) + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) if err != nil { if err := conn.Close(); err != nil { sw.Logger.Error("Error closing connection", "err", err) diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 1d6d869a..6c606a67 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -236,7 +236,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, DefaultPeerConfig()) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) require.Nil(err) err = sw.addPeer(peer) require.Nil(err) @@ -263,7 +263,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, DefaultPeerConfig()) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) peer.makePersistent() require.Nil(err) err = sw.addPeer(peer) From a17105fd46ec6c6926ffe27487790f83446eecea Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 21:27:38 -0500 Subject: [PATCH 018/124] p2p: peer.Key -> peer.ID --- benchmarks/codec_test.go | 6 ++-- blockchain/pool.go | 33 +++++++++++---------- blockchain/pool_test.go | 15 +++++----- blockchain/reactor.go | 10 +++---- blockchain/reactor_test.go | 12 ++++---- consensus/reactor.go | 10 +++---- consensus/replay.go | 12 ++++---- consensus/state.go | 47 +++++++++++++++--------------- consensus/types/height_vote_set.go | 15 +++++----- consensus/wal.go | 2 +- p2p/key.go | 17 +++++++++-- p2p/peer.go | 16 +++++----- p2p/peer_set.go | 24 +++++++-------- p2p/peer_set_test.go | 12 ++++---- p2p/pex_reactor.go | 3 +- p2p/switch.go | 2 +- p2p/switch_test.go | 4 +-- rpc/core/consensus.go | 5 ++-- rpc/core/types/responses.go | 2 +- types/vote_set.go | 7 +++-- 20 files changed, 137 insertions(+), 117 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 631b303e..209fcd3b 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -17,7 +17,7 @@ func BenchmarkEncodeStatusWire(b *testing.B) { pubKey := crypto.GenPrivKeyEd25519().PubKey() status := &ctypes.ResultStatus{ NodeInfo: &p2p.NodeInfo{ - PubKey: pubKey.Unwrap().(crypto.PubKeyEd25519), + PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", RemoteAddr: "SOMEADDR", @@ -42,7 +42,7 @@ func BenchmarkEncodeStatusWire(b *testing.B) { func BenchmarkEncodeNodeInfoWire(b *testing.B) { b.StopTimer() - pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) + pubKey := crypto.GenPrivKeyEd25519().PubKey() nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", @@ -63,7 +63,7 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) { func BenchmarkEncodeNodeInfoBinary(b *testing.B) { b.StopTimer() - pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) + pubKey := crypto.GenPrivKeyEd25519().PubKey() nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", diff --git a/blockchain/pool.go b/blockchain/pool.go index e39749dc..f3148e6c 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" flow "github.com/tendermint/tmlibs/flowrate" @@ -56,16 +57,16 @@ type BlockPool struct { height int64 // the lowest key in requesters. numPending int32 // number of requests pending assignment or block response // peers - peers map[string]*bpPeer + peers map[p2p.ID]*bpPeer maxPeerHeight int64 requestsCh chan<- BlockRequest - timeoutsCh chan<- string + timeoutsCh chan<- p2p.ID } -func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { +func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- p2p.ID) *BlockPool { bp := &BlockPool{ - peers: make(map[string]*bpPeer), + peers: make(map[p2p.ID]*bpPeer), requesters: make(map[int64]*bpRequester), height: start, @@ -210,7 +211,7 @@ func (pool *BlockPool) RedoRequest(height int64) { } // TODO: ensure that blocks come in order for each peer. -func (pool *BlockPool) AddBlock(peerID string, block *types.Block, blockSize int) { +func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -240,7 +241,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 { } // Sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerID string, height int64) { +func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -258,14 +259,14 @@ func (pool *BlockPool) SetPeerHeight(peerID string, height int64) { } } -func (pool *BlockPool) RemovePeer(peerID string) { +func (pool *BlockPool) RemovePeer(peerID p2p.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() pool.removePeer(peerID) } -func (pool *BlockPool) removePeer(peerID string) { +func (pool *BlockPool) removePeer(peerID p2p.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { if requester.getBlock() != nil { @@ -321,14 +322,14 @@ func (pool *BlockPool) requestersLen() int64 { return int64(len(pool.requesters)) } -func (pool *BlockPool) sendRequest(height int64, peerID string) { +func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) { if !pool.IsRunning() { return } pool.requestsCh <- BlockRequest{height, peerID} } -func (pool *BlockPool) sendTimeout(peerID string) { +func (pool *BlockPool) sendTimeout(peerID p2p.ID) { if !pool.IsRunning() { return } @@ -357,7 +358,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { pool *BlockPool - id string + id p2p.ID recvMonitor *flow.Monitor height int64 @@ -368,7 +369,7 @@ type bpPeer struct { logger log.Logger } -func newBPPeer(pool *BlockPool, peerID string, height int64) *bpPeer { +func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer { peer := &bpPeer{ pool: pool, id: peerID, @@ -434,7 +435,7 @@ type bpRequester struct { redoCh chan struct{} mtx sync.Mutex - peerID string + peerID p2p.ID block *types.Block } @@ -458,7 +459,7 @@ func (bpr *bpRequester) OnStart() error { } // Returns true if the peer matches -func (bpr *bpRequester) setBlock(block *types.Block, peerID string) bool { +func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool { bpr.mtx.Lock() if bpr.block != nil || bpr.peerID != peerID { bpr.mtx.Unlock() @@ -477,7 +478,7 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() string { +func (bpr *bpRequester) getPeerID() p2p.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() return bpr.peerID @@ -551,5 +552,5 @@ OUTER_LOOP: type BlockRequest struct { Height int64 - PeerID string + PeerID p2p.ID } diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 3e347fd2..0cdbc3a9 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -15,14 +16,14 @@ func init() { } type testPeer struct { - id string + id p2p.ID height int64 } -func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer { - peers := make(map[string]testPeer, numPeers) +func makePeers(numPeers int, minHeight, maxHeight int64) map[p2p.ID]testPeer { + peers := make(map[p2p.ID]testPeer, numPeers) for i := 0; i < numPeers; i++ { - peerID := cmn.RandStr(12) + peerID := p2p.ID(cmn.RandStr(12)) height := minHeight + rand.Int63n(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height} } @@ -32,7 +33,7 @@ func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer { func TestBasic(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) - timeoutsCh := make(chan string, 100) + timeoutsCh := make(chan p2p.ID, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) pool.SetLogger(log.TestingLogger()) @@ -89,7 +90,7 @@ func TestBasic(t *testing.T) { func TestTimeout(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) - timeoutsCh := make(chan string, 100) + timeoutsCh := make(chan p2p.ID, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) pool.SetLogger(log.TestingLogger()) @@ -127,7 +128,7 @@ func TestTimeout(t *testing.T) { // Pull from channels counter := 0 - timedOut := map[string]struct{}{} + timedOut := map[p2p.ID]struct{}{} for { select { case peerID := <-timeoutsCh: diff --git a/blockchain/reactor.go b/blockchain/reactor.go index d4b803dd..701e04f6 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -52,7 +52,7 @@ type BlockchainReactor struct { pool *BlockPool fastSync bool requestsCh chan BlockRequest - timeoutsCh chan string + timeoutsCh chan p2p.ID } // NewBlockchainReactor returns new reactor instance. @@ -62,7 +62,7 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *Bl } requestsCh := make(chan BlockRequest, defaultChannelCapacity) - timeoutsCh := make(chan string, defaultChannelCapacity) + timeoutsCh := make(chan p2p.ID, defaultChannelCapacity) pool := NewBlockPool( store.Height()+1, requestsCh, @@ -131,7 +131,7 @@ func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { // RemovePeer implements Reactor by removing peer from the pool. func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { - bcR.pool.RemovePeer(peer.Key()) + bcR.pool.RemovePeer(peer.ID()) } // respondToPeer loads a block and sends it to the requesting peer, @@ -170,7 +170,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) } case *bcBlockResponseMessage: // Got a block. - bcR.pool.AddBlock(src.Key(), msg.Block, len(msgBytes)) + bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes)) case *bcStatusRequestMessage: // Send peer our state. queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) @@ -179,7 +179,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) } case *bcStatusResponseMessage: // Got a peer status. Unverified. - bcR.pool.SetPeerHeight(src.Key(), msg.Height) + bcR.pool.SetPeerHeight(src.ID(), msg.Height) default: bcR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) } diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index fcb8a6f8..5bdd2869 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -55,7 +55,7 @@ func TestNoBlockMessageResponse(t *testing.T) { defer bcr.Stop() // Add some peers in - peer := newbcrTestPeer(cmn.RandStr(12)) + peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12))) bcr.AddPeer(peer) chID := byte(0x01) @@ -113,16 +113,16 @@ func makeBlock(height int64, state sm.State) *types.Block { // The Test peer type bcrTestPeer struct { cmn.Service - key string - ch chan interface{} + id p2p.ID + ch chan interface{} } var _ p2p.Peer = (*bcrTestPeer)(nil) -func newbcrTestPeer(key string) *bcrTestPeer { +func newbcrTestPeer(id p2p.ID) *bcrTestPeer { return &bcrTestPeer{ Service: cmn.NewBaseService(nil, "bcrTestPeer", nil), - key: key, + id: id, ch: make(chan interface{}, 2), } } @@ -144,7 +144,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } -func (tp *bcrTestPeer) Key() string { return tp.key } +func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } func (tp *bcrTestPeer) IsOutbound() bool { return false } func (tp *bcrTestPeer) IsPersistent() bool { return true } func (tp *bcrTestPeer) Get(s string) interface{} { return s } diff --git a/consensus/reactor.go b/consensus/reactor.go index 9b3393e9..3f6ab506 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -205,7 +205,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) return } // Peer claims to have a maj23 for some BlockID at H,R,S, - votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.Key(), msg.BlockID) + votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.ID(), msg.BlockID) // Respond with a VoteSetBitsMessage showing which votes we have. // (and consequently shows which we don't have) var ourVotes *cmn.BitArray @@ -242,12 +242,12 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) switch msg := msg.(type) { case *ProposalMessage: ps.SetHasProposal(msg.Proposal) - conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} case *ProposalPOLMessage: ps.ApplyProposalPOLMessage(msg) case *BlockPartMessage: ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index) - conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} default: conR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) } @@ -267,7 +267,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) ps.EnsureVoteBitArrays(height-1, lastCommitSize) ps.SetHasVote(msg.Vote) - cs.peerMsgQueue <- msgInfo{msg, src.Key()} + cs.peerMsgQueue <- msgInfo{msg, src.ID()} default: // don't punish (leave room for soft upgrades) @@ -1200,7 +1200,7 @@ func (ps *PeerState) StringIndented(indent string) string { %s Key %v %s PRS %v %s}`, - indent, ps.Peer.Key(), + indent, ps.Peer.ID(), indent, ps.PeerRoundState.StringIndented(indent+" "), indent) } diff --git a/consensus/replay.go b/consensus/replay.go index 784e8bd6..88157107 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -61,21 +61,21 @@ func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan } } case msgInfo: - peerKey := m.PeerKey - if peerKey == "" { - peerKey = "local" + 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.BlockPartsHeader, "pol", p.POLRound, "peer", peerKey) + p.BlockPartsHeader, "pol", p.POLRound, "peer", peerID) case *BlockPartMessage: - cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerKey) + 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", peerKey) + "blockID", v.BlockID, "peer", peerID) } cs.handleMsg(m) diff --git a/consensus/state.go b/consensus/state.go index 518d81c5..56070b03 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -17,6 +17,7 @@ import ( cfg "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -46,8 +47,8 @@ var ( // msgs from the reactor which may update the state type msgInfo struct { - Msg ConsensusMessage `json:"msg"` - PeerKey string `json:"peer_key"` + Msg ConsensusMessage `json:"msg"` + PeerID p2p.ID `json:"peer_key"` } // internally generated messages which may update the state @@ -303,17 +304,17 @@ func (cs *ConsensusState) OpenWAL(walFile string) (WAL, error) { //------------------------------------------------------------ // Public interface for passing messages into the consensus state, possibly causing a state transition. -// If peerKey == "", the msg is considered internal. +// 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 *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { - if peerKey == "" { +func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} } else { - cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerKey} + cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID} } // TODO: wait for event?! @@ -321,12 +322,12 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool, } // SetProposal inputs a proposal. -func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string) error { +func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { - if peerKey == "" { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} } else { - cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerKey} + cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID} } // TODO: wait for event?! @@ -334,12 +335,12 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string) } // AddProposalBlockPart inputs a part of the proposal block. -func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerKey string) error { +func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error { - if peerKey == "" { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} } else { - cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerKey} + cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID} } // TODO: wait for event?! @@ -347,13 +348,13 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty } // SetProposalAndBlock inputs the proposal and all block parts. -func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerKey string) error { - if err := cs.SetProposal(proposal, peerKey); err != nil { +func (cs *ConsensusState) 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 < parts.Total(); i++ { part := parts.GetPart(i) - if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerKey); err != nil { + if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil { return err } } @@ -561,7 +562,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { defer cs.mtx.Unlock() var err error - msg, peerKey := mi.Msg, mi.PeerKey + msg, peerID := mi.Msg, mi.PeerID switch msg := msg.(type) { case *ProposalMessage: // will not cause transition. @@ -569,14 +570,14 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { err = cs.setProposal(msg.Proposal) case *BlockPartMessage: // if the proposal is complete, we'll enterPrevote or tryFinalizeCommit - _, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerKey != "") + _, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerID != "") if err != nil && msg.Round != cs.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 - err := cs.tryAddVote(msg.Vote, peerKey) + err := cs.tryAddVote(msg.Vote, peerID) if err == ErrAddingVote { // TODO: punish peer } @@ -591,7 +592,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg)) } if err != nil { - cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerKey, "err", err, "msg", msg) + cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerID, "err", err, "msg", msg) } } @@ -1308,8 +1309,8 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v } // Attempt to add the vote. if its a duplicate signature, dupeout the validator -func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { - _, err := cs.addVote(vote, peerKey) +func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) error { + _, 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. @@ -1335,7 +1336,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, err error) { +func (cs *ConsensusState) 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? @@ -1365,7 +1366,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, // A prevote/precommit for this height? if vote.Height == cs.Height { height := cs.Height - added, err = cs.Votes.AddVote(vote, peerKey) + added, err = cs.Votes.AddVote(vote, peerID) if added { cs.eventBus.PublishEventVote(types.EventDataVote{vote}) diff --git a/consensus/types/height_vote_set.go b/consensus/types/height_vote_set.go index 0a0a25fe..1435cf42 100644 --- a/consensus/types/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -4,6 +4,7 @@ import ( "strings" "sync" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" ) @@ -35,7 +36,7 @@ type HeightVoteSet struct { mtx sync.Mutex round int // max tracked round roundVoteSets map[int]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[string][]int // keys: peer.Key; values: at most 2 rounds + peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds } func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { @@ -53,7 +54,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { hvs.height = height hvs.valSet = valSet hvs.roundVoteSets = make(map[int]RoundVoteSet) - hvs.peerCatchupRounds = make(map[string][]int) + hvs.peerCatchupRounds = make(map[p2p.ID][]int) hvs.addRound(0) hvs.round = 0 @@ -101,8 +102,8 @@ func (hvs *HeightVoteSet) addRound(round int) { } // Duplicate votes return added=false, err=nil. -// By convention, peerKey is "" if origin is self. -func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { +// By convention, peerID is "" if origin is self. +func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(vote.Type) { @@ -110,10 +111,10 @@ func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool, } voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { - if rndz := hvs.peerCatchupRounds[peerKey]; len(rndz) < 2 { + if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 { hvs.addRound(vote.Round) voteSet = hvs.getVoteSet(vote.Round, vote.Type) - hvs.peerCatchupRounds[peerKey] = append(rndz, vote.Round) + hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round) } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! @@ -206,7 +207,7 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID string, blockID types.BlockID) { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/consensus/wal.go b/consensus/wal.go index dfbef879..88218940 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -121,7 +121,7 @@ func (wal *baseWAL) Save(msg WALMessage) { if wal.light { // in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts) if mi, ok := msg.(msgInfo); ok { - if mi.PeerKey != "" { + if mi.PeerID != "" { return } } diff --git a/p2p/key.go b/p2p/key.go index aa2ac767..eede7ce7 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -2,6 +2,7 @@ package p2p import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -21,8 +22,14 @@ type NodeKey struct { PrivKey crypto.PrivKey `json:"priv_key"` // our priv key } +type ID string + // ID returns the peer's canonical ID - the hash of its public key. -func (nodeKey *NodeKey) ID() []byte { +func (nodeKey *NodeKey) ID() ID { + return ID(hex.EncodeToString(nodeKey.id())) +} + +func (nodeKey *NodeKey) id() []byte { return nodeKey.PrivKey.PubKey().Address() } @@ -31,6 +38,10 @@ func (nodeKey *NodeKey) PubKey() crypto.PubKey { return nodeKey.PrivKey.PubKey() } +func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { + return bytes.Compare(nodeKey.id(), target) < 0 +} + // LoadOrGenNodeKey attempts to load the NodeKey from the given filePath, // and checks that the corresponding ID is less than the target. // If the file does not exist, it generates and saves a new NodeKey @@ -41,8 +52,8 @@ func LoadOrGenNodeKey(filePath string, target []byte) (*NodeKey, error) { if err != nil { return nil, err } - if bytes.Compare(nodeKey.ID(), target) >= 0 { - return nil, fmt.Errorf("Loaded ID (%X) does not satisfy target (%X)", nodeKey.ID(), target) + if !nodeKey.SatisfiesTarget(target) { + return nil, fmt.Errorf("Loaded ID (%s) does not satisfy target (%X)", nodeKey.ID(), target) } return nodeKey, nil } else { diff --git a/p2p/peer.go b/p2p/peer.go index e2e73f0f..35556a59 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -1,6 +1,7 @@ package p2p import ( + "encoding/hex" "fmt" "net" "time" @@ -17,7 +18,7 @@ import ( type Peer interface { cmn.Service - Key() string + ID() ID IsOutbound() bool IsPersistent() bool NodeInfo() *NodeInfo @@ -282,15 +283,15 @@ func (p *peer) CanSend(chID byte) bool { // String representation. func (p *peer) String() string { if p.outbound { - return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.Key()) + return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) } - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key()) + return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) } // Equals reports whenever 2 peers are actually represent the same node. func (p *peer) Equals(other Peer) bool { - return p.Key() == other.Key() + return p.ID() == other.ID() } // Get the data for a given key. @@ -303,10 +304,9 @@ func (p *peer) Set(key string, data interface{}) { p.Data.Set(key, data) } -// Key returns the peer's id key. -// TODO: call this ID -func (p *peer) Key() string { - return p.nodeInfo.ListenAddr // XXX: should probably be PubKey.KeyString() +// Key returns the peer's ID - the hex encoded hash of its pubkey. +func (p *peer) ID() ID { + return ID(hex.EncodeToString(p.nodeInfo.PubKey.Address())) } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/p2p/peer_set.go b/p2p/peer_set.go index c21748cf..dc53174a 100644 --- a/p2p/peer_set.go +++ b/p2p/peer_set.go @@ -6,8 +6,8 @@ import ( // IPeerSet has a (immutable) subset of the methods of PeerSet. type IPeerSet interface { - Has(key string) bool - Get(key string) Peer + Has(key ID) bool + Get(key ID) Peer List() []Peer Size() int } @@ -18,7 +18,7 @@ type IPeerSet interface { // Iteration over the peers is super fast and thread-safe. type PeerSet struct { mtx sync.Mutex - lookup map[string]*peerSetItem + lookup map[ID]*peerSetItem list []Peer } @@ -30,7 +30,7 @@ type peerSetItem struct { // NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. func NewPeerSet() *PeerSet { return &PeerSet{ - lookup: make(map[string]*peerSetItem), + lookup: make(map[ID]*peerSetItem), list: make([]Peer, 0, 256), } } @@ -40,7 +40,7 @@ func NewPeerSet() *PeerSet { func (ps *PeerSet) Add(peer Peer) error { ps.mtx.Lock() defer ps.mtx.Unlock() - if ps.lookup[peer.Key()] != nil { + if ps.lookup[peer.ID()] != nil { return ErrSwitchDuplicatePeer } @@ -48,13 +48,13 @@ func (ps *PeerSet) Add(peer Peer) error { // Appending is safe even with other goroutines // iterating over the ps.list slice. ps.list = append(ps.list, peer) - ps.lookup[peer.Key()] = &peerSetItem{peer, index} + ps.lookup[peer.ID()] = &peerSetItem{peer, index} return nil } // Has returns true iff the PeerSet contains // the peer referred to by this peerKey. -func (ps *PeerSet) Has(peerKey string) bool { +func (ps *PeerSet) Has(peerKey ID) bool { ps.mtx.Lock() _, ok := ps.lookup[peerKey] ps.mtx.Unlock() @@ -62,7 +62,7 @@ func (ps *PeerSet) Has(peerKey string) bool { } // Get looks up a peer by the provided peerKey. -func (ps *PeerSet) Get(peerKey string) Peer { +func (ps *PeerSet) Get(peerKey ID) Peer { ps.mtx.Lock() defer ps.mtx.Unlock() item, ok := ps.lookup[peerKey] @@ -77,7 +77,7 @@ func (ps *PeerSet) Get(peerKey string) Peer { func (ps *PeerSet) Remove(peer Peer) { ps.mtx.Lock() defer ps.mtx.Unlock() - item := ps.lookup[peer.Key()] + item := ps.lookup[peer.ID()] if item == nil { return } @@ -90,18 +90,18 @@ func (ps *PeerSet) Remove(peer Peer) { // If it's the last peer, that's an easy special case. if index == len(ps.list)-1 { ps.list = newList - delete(ps.lookup, peer.Key()) + delete(ps.lookup, peer.ID()) return } // Replace the popped item with the last item in the old list. lastPeer := ps.list[len(ps.list)-1] - lastPeerKey := lastPeer.Key() + lastPeerKey := lastPeer.ID() lastPeerItem := ps.lookup[lastPeerKey] newList[index] = lastPeer lastPeerItem.index = index ps.list = newList - delete(ps.lookup, peer.Key()) + delete(ps.lookup, peer.ID()) } // Size returns the number of unique items in the peerSet. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index a7f29315..609c4900 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" ) @@ -16,6 +17,7 @@ func randPeer() *peer { nodeInfo: &NodeInfo{ RemoteAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, } } @@ -39,7 +41,7 @@ func TestPeerSetAddRemoveOne(t *testing.T) { peerSet.Remove(peerAtFront) wantSize := n - i - 1 for j := 0; j < 2; j++ { - assert.Equal(t, false, peerSet.Has(peerAtFront.Key()), "#%d Run #%d: failed to remove peer", i, j) + assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j) assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j) // Test the route of removing the now non-existent element peerSet.Remove(peerAtFront) @@ -58,7 +60,7 @@ func TestPeerSetAddRemoveOne(t *testing.T) { for i := n - 1; i >= 0; i-- { peerAtEnd := peerList[i] peerSet.Remove(peerAtEnd) - assert.Equal(t, false, peerSet.Has(peerAtEnd.Key()), "#%d: failed to remove item at end", i) + assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i) assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i) } } @@ -82,7 +84,7 @@ func TestPeerSetAddRemoveMany(t *testing.T) { for i, peer := range peers { peerSet.Remove(peer) - if peerSet.Has(peer.Key()) { + if peerSet.Has(peer.ID()) { t.Errorf("Failed to remove peer") } if peerSet.Size() != len(peers)-i-1 { @@ -129,7 +131,7 @@ func TestPeerSetGet(t *testing.T) { t.Parallel() peerSet := NewPeerSet() peer := randPeer() - assert.Nil(t, peerSet.Get(peer.Key()), "expecting a nil lookup, before .Add") + assert.Nil(t, peerSet.Get(peer.ID()), "expecting a nil lookup, before .Add") if err := peerSet.Add(peer); err != nil { t.Fatalf("Failed to add new peer: %v", err) @@ -142,7 +144,7 @@ func TestPeerSetGet(t *testing.T) { wg.Add(1) go func(i int) { defer wg.Done() - got, want := peerSet.Get(peer.Key()), peer + got, want := peerSet.Get(peer.ID()), peer assert.Equal(t, got, want, "#%d: got=%v want=%v", i, got, want) }(i) } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 2bfe7dca..17b9d61a 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -265,7 +265,8 @@ func (r *PEXReactor) ensurePeers() { continue } // XXX: Should probably use pubkey as peer key ... - if connected := r.Switch.Peers().Has(try.String()); connected { + // TODO: use the ID correctly + if connected := r.Switch.Peers().Has(ID(try.String())); connected { continue } r.Logger.Info("Will dial address", "addr", try) diff --git a/p2p/switch.go b/p2p/switch.go index 344c3c1e..4da9355a 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -257,7 +257,7 @@ func (sw *Switch) addPeer(peer *peer) error { } // Avoid duplicate - if sw.peers.Has(peer.Key()) { + if sw.peers.Has(peer.ID()) { return ErrSwitchDuplicatePeer } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 6c606a67..7d61fa39 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -28,7 +28,7 @@ func init() { } type PeerMessage struct { - PeerKey string + PeerID ID Bytes []byte Counter int } @@ -77,7 +77,7 @@ func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { tr.mtx.Lock() defer tr.mtx.Unlock() //fmt.Printf("Received: %X, %X\n", chID, msgBytes) - tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.Key(), msgBytes, tr.msgsCounter}) + tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) tr.msgsCounter++ } } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 65c9fc36..25b67925 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -3,6 +3,7 @@ package core import ( cm "github.com/tendermint/tendermint/consensus" cstypes "github.com/tendermint/tendermint/consensus/types" + p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -82,11 +83,11 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // } // ``` func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - peerRoundStates := make(map[string]*cstypes.PeerRoundState) + peerRoundStates := make(map[p2p.ID]*cstypes.PeerRoundState) for _, peer := range p2pSwitch.Peers().List() { peerState := peer.Get(types.PeerStateKey).(*cm.PeerState) peerRoundState := peerState.GetRoundState() - peerRoundStates[peer.Key()] = peerRoundState + peerRoundStates[peer.ID()] = peerRoundState } return &ctypes.ResultDumpConsensusState{consensusState.GetRoundState(), peerRoundStates}, nil } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index dae7c004..97d227c1 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -99,7 +99,7 @@ type ResultValidators struct { type ResultDumpConsensusState struct { RoundState *cstypes.RoundState `json:"round_state"` - PeerRoundStates map[string]*cstypes.PeerRoundState `json:"peer_round_states"` + PeerRoundStates map[p2p.ID]*cstypes.PeerRoundState `json:"peer_round_states"` } type ResultBroadcastTx struct { diff --git a/types/vote_set.go b/types/vote_set.go index 34f98956..a97676f6 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "github.com/tendermint/tendermint/p2p" cmn "github.com/tendermint/tmlibs/common" ) @@ -58,7 +59,7 @@ type VoteSet struct { sum int64 // Sum of voting power for seen votes, discounting conflicts maj23 *BlockID // First 2/3 majority seen votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes - peerMaj23s map[string]BlockID // Maj23 for each peer + peerMaj23s map[p2p.ID]BlockID // Maj23 for each peer } // Constructs a new VoteSet struct used to accumulate votes for given height/round. @@ -77,7 +78,7 @@ func NewVoteSet(chainID string, height int64, round int, type_ byte, valSet *Val sum: 0, maj23: nil, votesByBlock: make(map[string]*blockVotes, valSet.Size()), - peerMaj23s: make(map[string]BlockID), + peerMaj23s: make(map[p2p.ID]BlockID), } } @@ -290,7 +291,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower // this can cause memory issues. // TODO: implement ability to remove peers too // NOTE: VoteSet must not be nil -func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) { +func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { if voteSet == nil { cmn.PanicSanity("SetPeerMaj23() on nil VoteSet") } From 7d35500e6b8f8d7110cd6c64d6204d84c87fee25 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 22:34:49 -0500 Subject: [PATCH 019/124] p2p: add ID to NetAddress and use for AddrBook --- p2p/addrbook.go | 34 +++++++++++++++++++--------------- p2p/addrbook_test.go | 5 ++++- p2p/key.go | 4 ++-- p2p/netaddress.go | 10 ++++++++-- p2p/pex_reactor.go | 19 ++++++++++--------- p2p/pex_reactor_test.go | 2 ++ p2p/switch.go | 18 ++++++++++-------- 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 8f924d12..6ccec61f 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -89,7 +89,7 @@ type AddrBook struct { mtx sync.Mutex rand *rand.Rand ourAddrs map[string]*NetAddress - addrLookup map[string]*knownAddress // new & old + addrLookup map[ID]*knownAddress // new & old bucketsOld []map[string]*knownAddress bucketsNew []map[string]*knownAddress nOld int @@ -104,7 +104,7 @@ func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook { am := &AddrBook{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), ourAddrs: make(map[string]*NetAddress), - addrLookup: make(map[string]*knownAddress), + addrLookup: make(map[ID]*knownAddress), filePath: filePath, routabilityStrict: routabilityStrict, } @@ -244,11 +244,11 @@ func (a *AddrBook) PickAddress(newBias int) *NetAddress { } // MarkGood marks the peer as good and moves it into an "old" bucket. -// XXX: we never call this! +// TODO: call this from somewhere func (a *AddrBook) MarkGood(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } @@ -262,7 +262,7 @@ func (a *AddrBook) MarkGood(addr *NetAddress) { func (a *AddrBook) MarkAttempt(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } @@ -279,11 +279,11 @@ func (a *AddrBook) MarkBad(addr *NetAddress) { func (a *AddrBook) RemoveAddress(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } - a.Logger.Info("Remove address from book", "addr", addr) + a.Logger.Info("Remove address from book", "addr", ka.Addr) a.removeFromAllBuckets(ka) } @@ -300,8 +300,8 @@ func (a *AddrBook) GetSelection() []*NetAddress { allAddr := make([]*NetAddress, a.size()) i := 0 - for _, v := range a.addrLookup { - allAddr[i] = v.Addr + for _, ka := range a.addrLookup { + allAddr[i] = ka.Addr i++ } @@ -388,7 +388,7 @@ func (a *AddrBook) loadFromFile(filePath string) bool { bucket := a.getBucket(ka.BucketType, bucketIndex) bucket[ka.Addr.String()] = ka } - a.addrLookup[ka.Addr.String()] = ka + a.addrLookup[ka.ID()] = ka if ka.BucketType == bucketTypeNew { a.nNew++ } else { @@ -466,7 +466,7 @@ func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { } // Ensure in addrLookup - a.addrLookup[addrStr] = ka + a.addrLookup[ka.ID()] = ka return true } @@ -503,7 +503,7 @@ func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool { } // Ensure in addrLookup - a.addrLookup[addrStr] = ka + a.addrLookup[ka.ID()] = ka return true } @@ -521,7 +521,7 @@ func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx } else { a.nOld-- } - delete(a.addrLookup, ka.Addr.String()) + delete(a.addrLookup, ka.ID()) } } @@ -536,7 +536,7 @@ func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) { } else { a.nOld-- } - delete(a.addrLookup, ka.Addr.String()) + delete(a.addrLookup, ka.ID()) } func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { @@ -559,7 +559,7 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) error { return fmt.Errorf("Cannot add ourselves with address %v", addr) } - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka != nil { // Already old. @@ -768,6 +768,10 @@ func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { } } +func (ka *knownAddress) ID() ID { + return ka.Addr.ID +} + func (ka *knownAddress) isOld() bool { return ka.BucketType == bucketTypeOld } diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index d84c008e..07ab3474 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -1,12 +1,14 @@ package p2p import ( + "encoding/hex" "fmt" "io/ioutil" "math/rand" "testing" "github.com/stretchr/testify/assert" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) @@ -102,7 +104,7 @@ func TestAddrBookLookup(t *testing.T) { src := addrSrc.src book.AddAddress(addr, src) - ka := book.addrLookup[addr.String()] + ka := book.addrLookup[addr.ID] assert.NotNil(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr) if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) { @@ -188,6 +190,7 @@ func randIPv4Address(t *testing.T) *NetAddress { ) port := rand.Intn(65535-1) + 1 addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) + addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) // TODO assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index eede7ce7..d0031458 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -12,6 +12,8 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) +type ID string + //------------------------------------------------------------------------------ // Persistent peer ID // TODO: encrypt on disk @@ -22,8 +24,6 @@ type NodeKey struct { PrivKey crypto.PrivKey `json:"priv_key"` // our priv key } -type ID string - // ID returns the peer's canonical ID - the hash of its public key. func (nodeKey *NodeKey) ID() ID { return ID(hex.EncodeToString(nodeKey.id())) diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 41c2cc97..8176cfde 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -16,8 +16,9 @@ import ( ) // NetAddress defines information about a peer on the network -// including its IP address, and port. +// including its ID, IP address, and port. type NetAddress struct { + ID ID IP net.IP Port uint16 str string @@ -122,10 +123,15 @@ func (na *NetAddress) Less(other interface{}) bool { // String representation. func (na *NetAddress) String() string { if na.str == "" { - na.str = net.JoinHostPort( + + addrStr := net.JoinHostPort( na.IP.String(), strconv.FormatUint(uint64(na.Port), 10), ) + if na.ID != "" { + addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) + } + na.str = addrStr } return na.str } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 17b9d61a..47169a1b 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -107,6 +107,7 @@ func (r *PEXReactor) AddPeer(p Peer) { } } else { // For inbound connections, the peer is its own source addr, err := NewNetAddressString(p.NodeInfo().ListenAddr) + addr.ID = p.ID() // TODO: handle in NewNetAddress func if err != nil { // peer gave us a bad ListenAddr. TODO: punish r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) @@ -154,9 +155,9 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { case *pexAddrsMessage: // We received some peer addresses from src. // TODO: (We don't want to get spammed with bad peers) - for _, addr := range msg.Addrs { - if addr != nil { - r.book.AddAddress(addr, srcAddr) + for _, netAddr := range msg.Addrs { + if netAddr != nil { + r.book.AddAddress(netAddr, srcAddr) } } default: @@ -170,8 +171,8 @@ func (r *PEXReactor) RequestPEX(p Peer) { } // SendAddrs sends addrs to the peer. -func (r *PEXReactor) SendAddrs(p Peer, addrs []*NetAddress) { - p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) +func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*NetAddress) { + p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) } // SetEnsurePeersPeriod sets period to ensure peers connected. @@ -258,19 +259,19 @@ func (r *PEXReactor) ensurePeers() { if try == nil { continue } - if _, selected := toDial[try.IP.String()]; selected { + if _, selected := toDial[string(try.ID)]; selected { continue } - if dialling := r.Switch.IsDialing(try); dialling { + if dialling := r.Switch.IsDialing(try.ID); dialling { continue } // XXX: Should probably use pubkey as peer key ... // TODO: use the ID correctly - if connected := r.Switch.Peers().Has(ID(try.String())); connected { + if connected := r.Switch.Peers().Has(try.ID); connected { continue } r.Logger.Info("Will dial address", "addr", try) - toDial[try.IP.String()] = try + toDial[string(try.ID)] = try } // Dial picked addresses diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a14f0eb2..a830b6c4 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -197,6 +198,7 @@ func createRandomPeer(outbound bool) *peer { nodeInfo: &NodeInfo{ ListenAddr: addr, RemoteAddr: netAddr.String(), + PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, mconn: &MConnection{RemoteAddress: netAddr}, diff --git a/p2p/switch.go b/p2p/switch.go index 4da9355a..8c89ee01 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -325,6 +325,7 @@ func (sw *Switch) startInitPeer(peer *peer) { // DialSeeds dials a list of seeds asynchronously in random order. func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { netAddrs, errs := NewNetAddressStrings(seeds) + // TODO: IDs for _, err := range errs { sw.Logger.Error("Error in seed's address", "err", err) } @@ -373,8 +374,8 @@ func (sw *Switch) dialSeed(addr *NetAddress) { // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { - sw.dialing.Set(addr.IP.String(), addr) - defer sw.dialing.Delete(addr.IP.String()) + sw.dialing.Set(string(addr.ID), addr) + defer sw.dialing.Delete(string(addr.ID)) sw.Logger.Info("Dialing peer", "address", addr) peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) @@ -396,9 +397,9 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, return peer, nil } -// IsDialing returns true if the switch is currently dialing the given address. -func (sw *Switch) IsDialing(addr *NetAddress) bool { - return sw.dialing.Has(addr.IP.String()) +// IsDialing returns true if the switch is currently dialing the given ID. +func (sw *Switch) IsDialing(id ID) bool { + return sw.dialing.Has(string(id)) } // Broadcast runs a go routine for each attempted send, which will block @@ -454,7 +455,8 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { // If no success after all that, it stops trying, and leaves it // to the PEX/Addrbook to find the peer again func (sw *Switch) reconnectToPeer(peer Peer) { - addr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) + netAddr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) + netAddr.ID = peer.ID() // TODO: handle above start := time.Now() sw.Logger.Info("Reconnecting to peer", "peer", peer) for i := 0; i < reconnectAttempts; i++ { @@ -462,7 +464,7 @@ func (sw *Switch) reconnectToPeer(peer Peer) { return } - peer, err := sw.DialPeerWithAddress(addr, true) + peer, err := sw.DialPeerWithAddress(netAddr, true) if err != nil { sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) // sleep a set amount @@ -484,7 +486,7 @@ func (sw *Switch) reconnectToPeer(peer Peer) { // sleep an exponentially increasing amount sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) - peer, err := sw.DialPeerWithAddress(addr, true) + peer, err := sw.DialPeerWithAddress(netAddr, true) if err != nil { sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) continue From 6e823c6e8735c5e351f054f7f608b0ddc81d81c4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 23:08:20 -0500 Subject: [PATCH 020/124] p2p: support addr format ID@IP:PORT --- p2p/connection.go | 6 ---- p2p/netaddress.go | 63 +++++++++++++++++++++++++---------------- p2p/netaddress_test.go | 14 +++++++-- p2p/peer_test.go | 2 +- p2p/pex_reactor_test.go | 2 +- p2p/switch.go | 2 ++ 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/p2p/connection.go b/p2p/connection.go index 626aeb10..306eaf7e 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -88,9 +88,6 @@ type MConnection struct { flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically chStatsTimer *cmn.RepeatTimer // update channel stats periodically - - LocalAddress *NetAddress - RemoteAddress *NetAddress } // MConnConfig is a MConnection configuration. @@ -140,9 +137,6 @@ func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onRec onReceive: onReceive, onError: onError, config: config, - - LocalAddress: NewNetAddress(conn.LocalAddr()), - RemoteAddress: NewNetAddress(conn.RemoteAddr()), } // Create channels diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 8176cfde..d804e348 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -5,6 +5,7 @@ package p2p import ( + "encoding/hex" "flag" "fmt" "net" @@ -12,6 +13,7 @@ import ( "strings" "time" + "github.com/pkg/errors" cmn "github.com/tendermint/tmlibs/common" ) @@ -29,25 +31,45 @@ type NetAddress struct { // using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will // panic. // TODO: socks proxies? -func NewNetAddress(addr net.Addr) *NetAddress { +func NewNetAddress(id ID, addr net.Addr) *NetAddress { tcpAddr, ok := addr.(*net.TCPAddr) if !ok { if flag.Lookup("test.v") == nil { // normal run cmn.PanicSanity(cmn.Fmt("Only TCPAddrs are supported. Got: %v", addr)) } else { // in testing - return NewNetAddressIPPort(net.IP("0.0.0.0"), 0) + netAddr := NewNetAddressIPPort(net.IP("0.0.0.0"), 0) + netAddr.ID = id + return netAddr } } ip := tcpAddr.IP port := uint16(tcpAddr.Port) - return NewNetAddressIPPort(ip, port) + netAddr := NewNetAddressIPPort(ip, port) + netAddr.ID = id + return netAddr } // NewNetAddressString returns a new NetAddress using the provided // address in the form of "IP:Port". Also resolves the host if host // is not an IP. func NewNetAddressString(addr string) (*NetAddress, error) { - host, portStr, err := net.SplitHostPort(removeProtocolIfDefined(addr)) + addr = removeProtocolIfDefined(addr) + + var id ID + spl := strings.Split(addr, "@") + if len(spl) == 2 { + idStr := spl[0] + idBytes, err := hex.DecodeString(idStr) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addr)) + } + if len(idBytes) != 20 { + return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be 20 hex-encoded bytes", len(idBytes)) + } + id, addr = ID(idStr), spl[1] + } + + host, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err } @@ -69,6 +91,7 @@ func NewNetAddressString(addr string) (*NetAddress, error) { } na := NewNetAddressIPPort(ip, uint16(port)) + na.ID = id return na, nil } @@ -94,10 +117,6 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { na := &NetAddress{ IP: ip, Port: port, - str: net.JoinHostPort( - ip.String(), - strconv.FormatUint(uint64(port), 10), - ), } return na } @@ -111,23 +130,10 @@ func (na *NetAddress) Equals(other interface{}) bool { return false } -func (na *NetAddress) Less(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - return na.String() < o.String() - } - - cmn.PanicSanity("Cannot compare unequal types") - return false -} - -// String representation. +// String representation: @: func (na *NetAddress) String() string { if na.str == "" { - - addrStr := net.JoinHostPort( - na.IP.String(), - strconv.FormatUint(uint64(na.Port), 10), - ) + addrStr := na.DialString() if na.ID != "" { addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) } @@ -136,9 +142,16 @@ func (na *NetAddress) String() string { return na.str } +func (na *NetAddress) DialString() string { + return net.JoinHostPort( + na.IP.String(), + strconv.FormatUint(uint64(na.Port), 10), + ) +} + // Dial calls net.Dial on the address. func (na *NetAddress) Dial() (net.Conn, error) { - conn, err := net.Dial("tcp", na.String()) + conn, err := net.Dial("tcp", na.DialString()) if err != nil { return nil, err } @@ -147,7 +160,7 @@ func (na *NetAddress) Dial() (net.Conn, error) { // DialTimeout calls net.DialTimeout on the address. func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { - conn, err := net.DialTimeout("tcp", na.String(), timeout) + conn, err := net.DialTimeout("tcp", na.DialString(), timeout) if err != nil { return nil, err } diff --git a/p2p/netaddress_test.go b/p2p/netaddress_test.go index 137be090..0aa45423 100644 --- a/p2p/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -13,12 +13,12 @@ func TestNewNetAddress(t *testing.T) { tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") require.Nil(err) - addr := NewNetAddress(tcpAddr) + addr := NewNetAddress("", tcpAddr) assert.Equal("127.0.0.1:8080", addr.String()) assert.NotPanics(func() { - NewNetAddress(&net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) + NewNetAddress("", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) }, "Calling NewNetAddress with UDPAddr should not panic in testing") } @@ -38,6 +38,16 @@ func TestNewNetAddressString(t *testing.T) { {"notahost:8080", "", false}, {"8082", "", false}, {"127.0.0:8080000", "", false}, + + {"deadbeef@127.0.0.1:8080", "", false}, + {"this-isnot-hex@127.0.0.1:8080", "", false}, + {"xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, + {"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, + + {"tcp://deadbeef@127.0.0.1:8080", "", false}, + {"tcp://this-isnot-hex@127.0.0.1:8080", "", false}, + {"tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, + {"tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, } for _, tc := range testCases { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index a2884b33..78a4e2f5 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -122,7 +122,7 @@ func (p *remotePeer) Start() { if e != nil { golog.Fatalf("net.Listen tcp :0: %+v", e) } - p.addr = NewNetAddress(l.Addr()) + p.addr = NewNetAddress("", l.Addr()) p.quit = make(chan struct{}) go p.accept(l) } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a830b6c4..22703746 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -201,7 +201,7 @@ func createRandomPeer(outbound bool) *peer { PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, - mconn: &MConnection{RemoteAddress: netAddr}, + mconn: &MConnection{}, } p.SetLogger(log.TestingLogger().With("peer", addr)) return p diff --git a/p2p/switch.go b/p2p/switch.go index 8c89ee01..22d3d5a5 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -12,6 +12,7 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" ) const ( @@ -622,6 +623,7 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodeKey(nodeKey) + s.SetLogger(log.TestingLogger()) return s } From 488ae529add8d90c7835991978450a55e7da9a28 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 23:23:11 -0500 Subject: [PATCH 021/124] p2p: authenticate peer ID --- p2p/peer.go | 12 ++++++------ p2p/switch.go | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 35556a59..ecf2efce 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -92,6 +92,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } return nil, err } + return peer, nil } @@ -218,13 +219,12 @@ func (p *peer) Addr() net.Addr { // PubKey returns peer's public key. func (p *peer) PubKey() crypto.PubKey { - if p.config.AuthEnc { + if p.NodeInfo() != nil { + return p.nodeInfo.PubKey + } else if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() } - if p.NodeInfo() == nil { - panic("Attempt to get peer's PubKey before calling Handshake") - } - return p.PubKey() + panic("Attempt to get peer's PubKey before calling Handshake") } // OnStart implements BaseService. @@ -306,7 +306,7 @@ func (p *peer) Set(key string, data interface{}) { // Key returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { - return ID(hex.EncodeToString(p.nodeInfo.PubKey.Address())) + return ID(hex.EncodeToString(p.PubKey().Address())) } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/p2p/switch.go b/p2p/switch.go index 22d3d5a5..e86acfad 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -239,9 +239,8 @@ func (sw *Switch) OnStop() { // NOTE: This performs a blocking handshake before the peer is added. // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { - // Avoid self - if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { + if sw.nodeKey.ID() == peer.ID() { return errors.New("Ignoring connection from self") } @@ -385,6 +384,14 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, return nil, err } peer.SetLogger(sw.Logger.With("peer", addr)) + + // authenticate peer + if addr.ID == "" { + peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) + } else if addr.ID != peer.ID() { + return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + } + if persistent { peer.makePersistent() } From a573b20888d0079dc771e9bcf1f206a247b1200a Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Wed, 3 Jan 2018 01:23:38 +0000 Subject: [PATCH 022/124] docs: add counter/dummy code snippets closes https://github.com/tendermint/abci/issues/134 --- docs/abci-cli.rst | 87 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/docs/abci-cli.rst b/docs/abci-cli.rst index ae410568..efbeb71b 100644 --- a/docs/abci-cli.rst +++ b/docs/abci-cli.rst @@ -53,7 +53,7 @@ Now run ``abci-cli`` to see the list of commands: -h, --help help for abci-cli -v, --verbose print the command and results as if it were a console session - Use "abci-cli [command] --help" for more information about a command. + Use "abci-cli [command] --help" for more information about a command. Dummy - First Example @@ -66,14 +66,56 @@ The most important messages are ``deliver_tx``, ``check_tx``, and ``commit``, but there are others for convenience, configuration, and information purposes. -Let's start a dummy application, which was installed at the same time as -``abci-cli`` above. The dummy just stores transactions in a merkle tree: +We'll start a dummy application, which was installed at the same time as +``abci-cli`` above. The dummy just stores transactions in a merkle tree. + +Its code can be found `here `__ and looks like: + +.. container:: toggle + + .. container:: header + + **Show/Hide Dummy Example** + + .. code-block:: go + + func cmdDummy(cmd *cobra.Command, args []string) error { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + + // Create the application - in memory or persisted to disk + var app types.Application + if flagPersist == "" { + app = dummy.NewDummyApplication() + } else { + app = dummy.NewPersistentDummyApplication(flagPersist) + app.(*dummy.PersistentDummyApplication).SetLogger(logger.With("module", "dummy")) + } + + // Start the listener + srv, err := server.NewServer(flagAddrD, flagAbci, app) + if err != nil { + return err + } + srv.SetLogger(logger.With("module", "abci-server")) + if err := srv.Start(); err != nil { + return err + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + srv.Stop() + }) + return nil + } + +Start by running: :: abci-cli dummy -In another terminal, run +And in another terminal, run :: @@ -187,6 +229,41 @@ Counter - Another Example Now that we've got the hang of it, let's try another application, the "counter" app. +Like the dummy app, its code can be found `here `__ and looks like: + +.. container:: toggle + + .. container:: header + + **Show/Hide Counter Example** + + .. code-block:: go + + func cmdCounter(cmd *cobra.Command, args []string) error { + + app := counter.NewCounterApplication(flagSerial) + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + + // Start the listener + srv, err := server.NewServer(flagAddrC, flagAbci, app) + if err != nil { + return err + } + srv.SetLogger(logger.With("module", "abci-server")) + if err := srv.Start(); err != nil { + return err + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + srv.Stop() + }) + return nil + } + + The counter app doesn't use a Merkle tree, it just counts how many times we've sent a transaction, asked for a hash, or committed the state. The result of ``commit`` is just the number of transactions sent. @@ -261,7 +338,7 @@ But the ultimate flexibility comes from being able to write the application easily in any language. We have implemented the counter in a number of languages (see the -example directory). +`example directory Date: Wed, 3 Jan 2018 10:49:47 +0100 Subject: [PATCH 023/124] Move P2P docs into docs folder --- docs/specification/new-spec/README.md | 1 + {p2p/docs => docs/specification/new-spec/p2p}/config.md | 0 {p2p/docs => docs/specification/new-spec/p2p}/connection.md | 0 {p2p/docs => docs/specification/new-spec/p2p}/node.md | 0 {p2p/docs => docs/specification/new-spec/p2p}/peer.md | 0 {p2p/docs => docs/specification/new-spec/p2p}/pex.md | 0 {p2p/docs => docs/specification/new-spec/p2p}/trustmetric.md | 0 7 files changed, 1 insertion(+) rename {p2p/docs => docs/specification/new-spec/p2p}/config.md (100%) rename {p2p/docs => docs/specification/new-spec/p2p}/connection.md (100%) rename {p2p/docs => docs/specification/new-spec/p2p}/node.md (100%) rename {p2p/docs => docs/specification/new-spec/p2p}/peer.md (100%) rename {p2p/docs => docs/specification/new-spec/p2p}/pex.md (100%) rename {p2p/docs => docs/specification/new-spec/p2p}/trustmetric.md (100%) diff --git a/docs/specification/new-spec/README.md b/docs/specification/new-spec/README.md index a5061e62..8a07d922 100644 --- a/docs/specification/new-spec/README.md +++ b/docs/specification/new-spec/README.md @@ -9,6 +9,7 @@ It contains the following components: - [Encoding and Digests](encoding.md) - [Blockchain](blockchain.md) - [State](state.md) +- [P2P](p2p/node.md) ## Overview diff --git a/p2p/docs/config.md b/docs/specification/new-spec/p2p/config.md similarity index 100% rename from p2p/docs/config.md rename to docs/specification/new-spec/p2p/config.md diff --git a/p2p/docs/connection.md b/docs/specification/new-spec/p2p/connection.md similarity index 100% rename from p2p/docs/connection.md rename to docs/specification/new-spec/p2p/connection.md diff --git a/p2p/docs/node.md b/docs/specification/new-spec/p2p/node.md similarity index 100% rename from p2p/docs/node.md rename to docs/specification/new-spec/p2p/node.md diff --git a/p2p/docs/peer.md b/docs/specification/new-spec/p2p/peer.md similarity index 100% rename from p2p/docs/peer.md rename to docs/specification/new-spec/p2p/peer.md diff --git a/p2p/docs/pex.md b/docs/specification/new-spec/p2p/pex.md similarity index 100% rename from p2p/docs/pex.md rename to docs/specification/new-spec/p2p/pex.md diff --git a/p2p/docs/trustmetric.md b/docs/specification/new-spec/p2p/trustmetric.md similarity index 100% rename from p2p/docs/trustmetric.md rename to docs/specification/new-spec/p2p/trustmetric.md From 0430ebf95cc0ae40d61778d60e92bf9c8401a3ce Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Wed, 3 Jan 2018 14:58:23 -0500 Subject: [PATCH 024/124] Makefile changes for cross-building and standardized builds using gox --- Makefile | 59 ++++++++++++++++++++++++++++++++++--------- scripts/dist_build.sh | 28 ++------------------ 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 2aed1acf..bb1d72d5 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,29 @@ -GOTOOLS = \ +GOTOOLS := \ github.com/mitchellh/gox \ github.com/Masterminds/glide \ github.com/tcnksm/ghr \ gopkg.in/alecthomas/gometalinter.v2 -GOTOOLS_CHECK = gox glide ghr gometalinter.v2 -PACKAGES=$(shell go list ./... | grep -v '/vendor/') -BUILD_TAGS?=tendermint -TMHOME = $${TMHOME:-$$HOME/.tendermint} -BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short HEAD`" +GO_MIN_VERSION := 1.9.2 +PACKAGES := $(shell go list ./... | grep -v '/vendor/') +BUILD_TAGS ?= tendermint +TMHOME ?= $(HOME)/.tendermint +GOPATH ?= $(shell go env GOPATH) +GOROOT ?= $(shell go env GOROOT) +GOGCCFLAGS ?= $(shell go env GOGCCFLAGS) +PROD_LDFLAGS ?= -w -s +XC_ARCH ?= 386 amd64 arm +XC_OS ?= solaris darwin freebsd linux windows +XC_OSARCH ?= !darwin/arm !solaris/amd64 !freebsd/amd64 +BUILD_OUTPUT ?= $(GOPATH)/bin/{{.OS}}_{{.Arch}}/tendermint + +GOX_FLAGS = -os="$(XC_OS)" -arch="$(XC_ARCH)" -osarch="$(XC_OSARCH)" -output="$(BUILD_OUTPUT)" +ifeq ($(BUILD_FLAGS_RACE),YES) +RACEFLAG=-race +else +RACEFLAG= +endif +BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "$(PROD_LDFLAGS) -X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=7 HEAD)" $(RACEFLAG) +GO_VERSION:=$(shell go version | grep -o '[[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+') all: check build test install metalinter @@ -17,27 +33,46 @@ check: check_tools get_vendor_deps ######################################## ### Build +build_cc: + $(shell which gox) $(BUILD_FLAGS) $(GOX_FLAGS) ./cmd/tendermint/ + build: - go build $(BUILD_FLAGS) -o build/tendermint ./cmd/tendermint/ + make build_cc PROD_LDFLAGS="" XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint build_race: - go build -race $(BUILD_FLAGS) -o build/tendermint ./cmd/tendermint + $(shell which go) build $(BUILD_FLAGS) -race -o "$(BUILD_OUTPUT)" ./cmd/tendermint/ +#For the future when this is merged: https://github.com/mitchellh/gox/pull/105 +# make build_cc PROD_LDFLAGS="" XC_ARCH=amd64 XC_OS=$(shell uname -s) BUILD_FLAGS_RACE=YES BUILD_OUTPUT=build/tendermint # dist builds binaries for all platforms and packages them for distribution dist: @BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/dist.sh'" install: - go install $(BUILD_FLAGS) ./cmd/tendermint + make build ######################################## ### Tools & dependencies check_tools: - @# https://stackoverflow.com/a/25668869 - @echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\ - $(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" +ifeq ($(GO_VERSION),) + $(error go not found) +endif +ifneq ($(GO_VERSION),$(GO_MIN_VERSION)) + $(warning WARNING: build will not be deterministic. go version should be $(GO_MIN_VERSION)) +endif +ifneq ($(findstring -fdebug-prefix-map,$(GOGCCFLAGS)),-fdebug-prefix-map) + $(warning WARNING: build will not be deterministic. The compiler does not support the '-fdebug-prefix-map' flag.) +endif +ifneq ($(GOROOT),/usr/local/go) + $(warning WARNING: build will not be deterministic. GOPATH should be set to /usr/local/go) +endif +ifneq ($(findstring $(GOPATH)/bin,$(PATH)),$(GOPATH)/bin) + $(warning WARNING: PATH does not contain GOPATH/bin. Some external dependencies might be unavailable.) +endif +# https://stackoverflow.com/a/25668869 + @echo "Found tools: $(foreach tool,$(notdir $(GOTOOLS)),$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH. Add GOPATH/bin to PATH and run 'make get_tools'")))" get_tools: @echo "--> Installing tools" diff --git a/scripts/dist_build.sh b/scripts/dist_build.sh index 587199e0..e7471c4d 100755 --- a/scripts/dist_build.sh +++ b/scripts/dist_build.sh @@ -9,32 +9,8 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that dir because we expect that. cd "$DIR" -# Get the git commit -GIT_COMMIT="$(git rev-parse --short HEAD)" -GIT_IMPORT="github.com/tendermint/tendermint/version" - -# Determine the arch/os combos we're building for -XC_ARCH=${XC_ARCH:-"386 amd64 arm"} -XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"} - -# Make sure build tools are available. -make tools - -# Get VENDORED dependencies -make get_vendor_deps - -# Build! -# ldflags: -s Omit the symbol table and debug information. -# -w Omit the DWARF symbol table. -echo "==> Building..." -"$(which gox)" \ - -os="${XC_OS}" \ - -arch="${XC_ARCH}" \ - -osarch="!darwin/arm !solaris/amd64 !freebsd/amd64" \ - -ldflags "-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}" \ - -output "build/pkg/{{.OS}}_{{.Arch}}/tendermint" \ - -tags="${BUILD_TAGS}" \ - github.com/tendermint/tendermint/cmd/tendermint +# Make sure build tools are available, get VENDORED dependencies and build +make get_tools get_vendor_deps build_cc # Zip all the files. echo "==> Packaging..." From f67f99c227e66c9bdbda2f3e34b5c7fcbc6f67c2 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Wed, 3 Jan 2018 17:24:11 -0500 Subject: [PATCH 025/124] Extended install document with docker option. Added extra checks to developer's build target. --- Makefile | 16 ++++++++++------ docs/install.rst | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index bb1d72d5..3aa5e051 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ TMHOME ?= $(HOME)/.tendermint GOPATH ?= $(shell go env GOPATH) GOROOT ?= $(shell go env GOROOT) GOGCCFLAGS ?= $(shell go env GOGCCFLAGS) -PROD_LDFLAGS ?= -w -s +#LDFLAGS_EXTRA ?= -w -s XC_ARCH ?= 386 amd64 arm XC_OS ?= solaris darwin freebsd linux windows XC_OSARCH ?= !darwin/arm !solaris/amd64 !freebsd/amd64 @@ -22,7 +22,7 @@ RACEFLAG=-race else RACEFLAG= endif -BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "$(PROD_LDFLAGS) -X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=7 HEAD)" $(RACEFLAG) +BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=7 HEAD) $(LDFLAGS_EXTRA)" $(RACEFLAG) GO_VERSION:=$(shell go version | grep -o '[[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+') all: check build test install metalinter @@ -33,16 +33,20 @@ check: check_tools get_vendor_deps ######################################## ### Build -build_cc: +build_cc: check_tools $(shell which gox) $(BUILD_FLAGS) $(GOX_FLAGS) ./cmd/tendermint/ build: - make build_cc PROD_LDFLAGS="" XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint +ifeq ($(OS),Windows_NT) + make build_cc XC_ARCH=amd64 XC_OS=windows BUILD_OUTPUT=$(GOPATH)/bin/tendermint +else + make build_cc XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint +endif build_race: +#TODO: Wait for this to be merged: https://github.com/mitchellh/gox/pull/105 Then switch over to make build and remove the go build line. +# make build BUILD_FLAGS_RACE=YES $(shell which go) build $(BUILD_FLAGS) -race -o "$(BUILD_OUTPUT)" ./cmd/tendermint/ -#For the future when this is merged: https://github.com/mitchellh/gox/pull/105 -# make build_cc PROD_LDFLAGS="" XC_ARCH=amd64 XC_OS=$(shell uname -s) BUILD_FLAGS_RACE=YES BUILD_OUTPUT=build/tendermint # dist builds binaries for all platforms and packages them for distribution dist: diff --git a/docs/install.rst b/docs/install.rst index 64fae4cd..9edc051a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -6,6 +6,19 @@ From Binary To download pre-built binaries, see the `Download page `__. +From Source using Docker +------------------------ + +If you have docker running, all you need is the ``golang`` image to build tendermint. +If you don't, you can get help setting it up `here `__. + +:: + mkdir $HOME/tendermintbin + docker run --rm -it -v $HOME/tendermintbin:/go/bin:Z golang:1.9.2 /bin/bash -c "go-wrapper download github.com/tendermint/tendermint/cmd/tendermint ; make -C /go/src/github.com/tendermint/tendermint get_tools get_vendor_deps build_cc" + +You will find the ``tendermint`` binaries for different architectures and operating systems in your ``$HOME/tendermintbin`` folder. + + From Source ----------- From 7b524994638fd8989859f96a5f1caa9d1f918542 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 4 Jan 2018 17:06:49 +0100 Subject: [PATCH 026/124] Start writing mempool specification Include overview and configuration options. --- mempool/docs/README.md | 11 ++++++++ mempool/docs/config.md | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 mempool/docs/README.md create mode 100644 mempool/docs/config.md diff --git a/mempool/docs/README.md b/mempool/docs/README.md new file mode 100644 index 00000000..075bafed --- /dev/null +++ b/mempool/docs/README.md @@ -0,0 +1,11 @@ +# Mempool Specification + +This package contains documents specifying the functionality +of the mempool module. + +Components: + +* [Config](./config.md) - how to configure it +* [Functionality](./functionality.md) - high-level description of the functionality it provides +* [External Messages](./messages.md) - The messages we accept over p2p and rpc interfaces +* [Local Services](./services.md) - Interfaces with consensus and abci services diff --git a/mempool/docs/config.md b/mempool/docs/config.md new file mode 100644 index 00000000..776149ba --- /dev/null +++ b/mempool/docs/config.md @@ -0,0 +1,59 @@ +# Mempool Configuration + +Here we describe configuration options around mempool. +For the purposes of this document, they are described +as command-line flags, but they can also be passed in as +environmental variables or in the config.toml file. The +following are all equivalent: + +Flag: `--mempool.recheck_empty=false` + +Environment: `TM_MEMPOOL_RECHECK_EMPTY=false` + +Config: +``` +[mempool] +recheck_empty = false +``` + + +## Recheck + +`--mempool.recheck=false` (default: true) + +`--mempool.recheck_empty=false` (default: true) + +Recheck determines if the mempool rechecks all pending +transactions after a block was committed. Once a block +is committed, the mempool removes all valid transactions +that were successfully included in the block. + +If `recheck` is true, then it will rerun CheckTx on +all remaining transactions with the new block state. + +If the block contained no transactions, it will skip the +recheck unless `recheck_empty` is true. + +## Broadcast + +`--mempool.broadcast=false` (default: true) + +Determines whether this node gossips any valid transactions +that arrive in mempool. Default is to gossip anything that +passes checktx. If this is disabled, transactions are not +gossiped, but instead stored locally and added to the next +block this node is the proposer. + +## WalDir + +`--mempool.wal_dir=/tmp/gaia/mempool.wal` (default: $TM_HOME/data/mempool.wal) + +This defines the directory where mempool writes the write-ahead +logs. These files can be used to reload unbroadcasted +transactions if the node crashes. + +If the directory passed in is an absolute path, the wal file is +created there. If the directory is a relative path, the path is +appended to home directory of the tendermint process to +generate an absolute path to the wal directory +(default `$HOME/.tendermint` or set via `TM_HOME` or `--home``) From 17b61db40a3e4f24a86103007a057af3f6ed2f2f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 4 Jan 2018 18:12:48 +0100 Subject: [PATCH 027/124] Document p2p and rpc messages --- mempool/docs/README.md | 2 +- mempool/docs/messages.md | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 mempool/docs/messages.md diff --git a/mempool/docs/README.md b/mempool/docs/README.md index 075bafed..3ad1213b 100644 --- a/mempool/docs/README.md +++ b/mempool/docs/README.md @@ -6,6 +6,6 @@ of the mempool module. Components: * [Config](./config.md) - how to configure it -* [Functionality](./functionality.md) - high-level description of the functionality it provides * [External Messages](./messages.md) - The messages we accept over p2p and rpc interfaces +* [Functionality](./functionality.md) - high-level description of the functionality it provides * [Local Services](./services.md) - Interfaces with consensus and abci services diff --git a/mempool/docs/messages.md b/mempool/docs/messages.md new file mode 100644 index 00000000..5bd1d1e5 --- /dev/null +++ b/mempool/docs/messages.md @@ -0,0 +1,60 @@ +# Mempool Messages + +## P2P Messages + +There is currently only one message that Mempool broadcasts +and receives over the p2p gossip network (via the reactor): +`TxMessage` + +```go +// TxMessage is a MempoolMessage containing a transaction. +type TxMessage struct { + Tx types.Tx +} +``` + +TxMessage is go-wire encoded and prepended with `0x1` as a +"type byte". This is followed by a go-wire encoded byte-slice. +Prefix of 40=0x28 byte tx is: `0x010128...` followed by +the actual 40-byte tx. Prefix of 350=0x015e byte tx is: +`0x0102015e...` followed by the actual 350 byte tx. + +(Please see the [go-wire repo](https://github.com/tendermint/go-wire#an-interface-example) for more information) + +## RPC Messages + +Mempool exposes `CheckTx([]byte)` over the RPC interface. + +It can be posted via `broadcast_commit`, `broadcast_sync` or +`broadcast_async`. They all parse a message with one argument, +`"tx": "HEX_ENCODED_BINARY"` and differ in only how long they +wait before returning (sync makes sure CheckTx passes, commit +makes sure it was included in a signed block). + +Request (`POST http://gaia.zone:46657/`): +```json +{ + "id": "", + "jsonrpc": "2.0", + "method": "broadcast_sync", + "params": { + "tx": "F012A4BC68..." + } +} +``` + + +Response: +```json +{ + "error": "", + "result": { + "hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52", + "log": "", + "data": "", + "code": 0 + }, + "id": "", + "jsonrpc": "2.0" +} +``` From 9cb45eb7df0fb0ad75cc9dbbceb77ae0c51956cf Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 4 Jan 2018 19:08:03 +0100 Subject: [PATCH 028/124] Add skeleton for functionality and concurrency --- mempool/docs/README.md | 2 +- mempool/docs/concurrency.md | 8 ++++++++ mempool/docs/functionality.md | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 mempool/docs/concurrency.md create mode 100644 mempool/docs/functionality.md diff --git a/mempool/docs/README.md b/mempool/docs/README.md index 3ad1213b..138b287a 100644 --- a/mempool/docs/README.md +++ b/mempool/docs/README.md @@ -8,4 +8,4 @@ Components: * [Config](./config.md) - how to configure it * [External Messages](./messages.md) - The messages we accept over p2p and rpc interfaces * [Functionality](./functionality.md) - high-level description of the functionality it provides -* [Local Services](./services.md) - Interfaces with consensus and abci services +* [Concurrency Model](./concurrency.md) - What guarantees we provide, what locks we require. diff --git a/mempool/docs/concurrency.md b/mempool/docs/concurrency.md new file mode 100644 index 00000000..991113e6 --- /dev/null +++ b/mempool/docs/concurrency.md @@ -0,0 +1,8 @@ +# Mempool Concurrency + +Look at the concurrency model this uses... + +* Receiving CheckTx +* Broadcasting new tx +* Interfaces with consensus engine, reap/update while checking +* Calling the ABCI app (ordering. callbacks. how proxy works alongside the blockchain proxy which actually writes blocks) diff --git a/mempool/docs/functionality.md b/mempool/docs/functionality.md new file mode 100644 index 00000000..85c3dc58 --- /dev/null +++ b/mempool/docs/functionality.md @@ -0,0 +1,37 @@ +# Mempool Functionality + +The mempool maintains a list of potentially valid transactions, +both to broadcast to other nodes, as well as to provide to the +consensus reactor when it is selected as the block proposer. + +There are two sides to the mempool state: + +* External: get, check, and broadcast new transactions +* Internal: return valid transaction, update list after block commit + + +## External functionality + +External functionality is exposed via network interfaces +to potentially untrusted actors. + +* CheckTx - triggered via RPC or P2P +* Broadcast - gossip messages after a successful check + +## Internal functionality + +Internal functionality is exposed via method calls to other +code compiled into the tendermint binary. + +* Reap - get tx to propose in next block +* Update - remove tx that were included in last block +* ABCI.CheckTx - call ABCI app to validate the tx + +What does it provide the consensus reactor? +What guarantees does it need from the ABCI app? +(talk about interleaving processes in concurrency) + +## Optimizations + +Talk about the LRU cache to make sure we don't process any +tx that we have seen before From ed81fb54ec15328be9bc21912face5ba5c55c5e2 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Fri, 5 Jan 2018 13:24:16 +0100 Subject: [PATCH 029/124] NewInquiring returns error instead of swallowing it --- lite/inquirer.go | 12 ++++++++---- lite/inquirer_test.go | 9 +++++---- lite/proxy/certifier.go | 7 ++++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lite/inquirer.go b/lite/inquirer.go index 5d6ce60c..9d0d77e5 100644 --- a/lite/inquirer.go +++ b/lite/inquirer.go @@ -23,16 +23,20 @@ type Inquiring struct { // // Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source // provider should be a client.HTTPProvider. -func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring { +func NewInquiring(chainID string, fc FullCommit, trusted Provider, + source Provider) (*Inquiring, error) { + // store the data in trusted - // TODO: StoredCommit() can return an error and we need to handle this. - trusted.StoreCommit(fc) + err := trusted.StoreCommit(fc) + if err != nil { + return nil, err + } return &Inquiring{ cert: NewDynamic(chainID, fc.Validators, fc.Height()), trusted: trusted, Source: source, - } + }, nil } // ChainID returns the chain id. diff --git a/lite/inquirer_test.go b/lite/inquirer_test.go index ce431754..97ad60e3 100644 --- a/lite/inquirer_test.go +++ b/lite/inquirer_test.go @@ -36,7 +36,7 @@ func TestInquirerValidPath(t *testing.T) { } // initialize a certifier with the initial state - cert := lite.NewInquiring(chainID, commits[0], trust, source) + cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) // this should fail validation.... commit := commits[count-1].Commit @@ -85,7 +85,7 @@ func TestInquirerMinimalPath(t *testing.T) { } // initialize a certifier with the initial state - cert := lite.NewInquiring(chainID, commits[0], trust, source) + cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) // this should fail validation.... commit := commits[count-1].Commit @@ -130,11 +130,12 @@ func TestInquirerVerifyHistorical(t *testing.T) { h := int64(20 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) resHash := []byte(fmt.Sprintf("res=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, + len(keys)) } // initialize a certifier with the initial state - cert := lite.NewInquiring(chainID, commits[0], trust, source) + cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) // store a few commits as trust for _, i := range []int{2, 5} { diff --git a/lite/proxy/certifier.go b/lite/proxy/certifier.go index 1d7284f2..3dda935e 100644 --- a/lite/proxy/certifier.go +++ b/lite/proxy/certifier.go @@ -25,6 +25,11 @@ func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { if err != nil { return nil, err } - cert := lite.NewInquiring(chainID, fc, trust, source) + + cert, err := lite.NewInquiring(chainID, fc, trust, source) + if err != nil { + return nil, err + } + return cert, nil } From ba475d312819078d35695467f22bbf45585e6e75 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Fri, 5 Jan 2018 13:25:58 +0100 Subject: [PATCH 030/124] Fix formatting --- lite/inquirer_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lite/inquirer_test.go b/lite/inquirer_test.go index 97ad60e3..51005967 100644 --- a/lite/inquirer_test.go +++ b/lite/inquirer_test.go @@ -32,7 +32,8 @@ func TestInquirerValidPath(t *testing.T) { vals := keys.ToValidators(vote, 0) h := int64(20 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, + len(keys)) } // initialize a certifier with the initial state @@ -81,7 +82,8 @@ func TestInquirerMinimalPath(t *testing.T) { h := int64(5 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) resHash := []byte(fmt.Sprintf("res=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, + len(keys)) } // initialize a certifier with the initial state From bb3dc10f24917601ce1a1b25882eecf6ef0a79b0 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Fri, 5 Jan 2018 14:22:13 -0500 Subject: [PATCH 031/124] Makefile improvements for deterministic builds based on Bucky's feedback --- Makefile | 28 ++++++++++++++++++++++------ scripts/dist_build.sh | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 3aa5e051..2a4b14e3 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ GOGCCFLAGS ?= $(shell go env GOGCCFLAGS) XC_ARCH ?= 386 amd64 arm XC_OS ?= solaris darwin freebsd linux windows XC_OSARCH ?= !darwin/arm !solaris/amd64 !freebsd/amd64 -BUILD_OUTPUT ?= $(GOPATH)/bin/{{.OS}}_{{.Arch}}/tendermint +BUILD_OUTPUT ?= ./build/{{.OS}}_{{.Arch}}/tendermint GOX_FLAGS = -os="$(XC_OS)" -arch="$(XC_ARCH)" -osarch="$(XC_OSARCH)" -output="$(BUILD_OUTPUT)" ifeq ($(BUILD_FLAGS_RACE),YES) @@ -22,8 +22,13 @@ RACEFLAG=-race else RACEFLAG= endif -BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=7 HEAD) $(LDFLAGS_EXTRA)" $(RACEFLAG) +BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=8 HEAD) $(LDFLAGS_EXTRA)" $(RACEFLAG) GO_VERSION:=$(shell go version | grep -o '[[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+') +#Check that that minor version of GO meets the minimum required +GO_MINOR_VERSION := $(shell grep -o \.[[:digit:]][[:digit:]]*\. <<< $(GO_VERSION) | grep -o [[:digit:]]* ) +GO_MIN_MINOR_VERSION := $(shell grep -o \.[[:digit:]][[:digit:]]*\. <<< $(GO_MIN_VERSION) | grep -o [[:digit:]]* ) +GO_MINOR_VERSION_CHECK := $(shell test $(GO_MINOR_VERSION) -ge $(GO_MIN_MINOR_VERSION) && echo YES) + all: check build test install metalinter @@ -33,14 +38,14 @@ check: check_tools get_vendor_deps ######################################## ### Build -build_cc: check_tools +build_xc: check_tools $(shell which gox) $(BUILD_FLAGS) $(GOX_FLAGS) ./cmd/tendermint/ build: ifeq ($(OS),Windows_NT) - make build_cc XC_ARCH=amd64 XC_OS=windows BUILD_OUTPUT=$(GOPATH)/bin/tendermint + make build_xc XC_ARCH=amd64 XC_OS=windows BUILD_OUTPUT=$(GOPATH)/bin/tendermint else - make build_cc XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint + make build_xc XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint endif build_race: @@ -63,15 +68,26 @@ check_tools: ifeq ($(GO_VERSION),) $(error go not found) endif +#Check minimum required go version ifneq ($(GO_VERSION),$(GO_MIN_VERSION)) $(warning WARNING: build will not be deterministic. go version should be $(GO_MIN_VERSION)) +ifneq ($(GO_MINOR_VERSION_CHECK),YES) + $(error ERROR: The minor version of Go ($(GO_VERSION)) is lower than the minimum required ($(GO_MIN_VERSION))) endif +endif +#-fdebug-prefix-map switches the temporary, randomized workdir name in the binary to a static text ifneq ($(findstring -fdebug-prefix-map,$(GOGCCFLAGS)),-fdebug-prefix-map) $(warning WARNING: build will not be deterministic. The compiler does not support the '-fdebug-prefix-map' flag.) endif +#GOROOT string is copied into the binary. For deterministic builds, we agree to keep it at /usr/local/go. (Default for golang:1.9.2 docker image, linux and osx.) ifneq ($(GOROOT),/usr/local/go) - $(warning WARNING: build will not be deterministic. GOPATH should be set to /usr/local/go) + $(warning WARNING: build will not be deterministic. GOROOT should be set to /usr/local/go) endif +#GOPATH string is copied into the binary. Although the -trimpath flag tries to eliminate it, it doesn't do it everywhere in Go 1.9.2. For deterministic builds we agree to keep it at /go. (Default for golang:1.9.2 docker image.) +ifneq ($(GOPATH),/go) + $(warning WARNING: build will not be deterministic. GOPATH should be set to /go) +endif +#External dependencies defined in GOTOOLS are built with get_tools. If they are already available on the system (for exmaple using a package manager), then get_tools might not be necessary. ifneq ($(findstring $(GOPATH)/bin,$(PATH)),$(GOPATH)/bin) $(warning WARNING: PATH does not contain GOPATH/bin. Some external dependencies might be unavailable.) endif diff --git a/scripts/dist_build.sh b/scripts/dist_build.sh index e7471c4d..f1d8779f 100755 --- a/scripts/dist_build.sh +++ b/scripts/dist_build.sh @@ -10,7 +10,7 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" cd "$DIR" # Make sure build tools are available, get VENDORED dependencies and build -make get_tools get_vendor_deps build_cc +make get_tools get_vendor_deps build_xc # Zip all the files. echo "==> Packaging..." From 92f5ae5a84591434311b92148b14edcf6551b7e5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 5 Jan 2018 22:19:12 -0500 Subject: [PATCH 032/124] fix vagrant [ci skip] --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 80d44f9c..12cfce47 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -44,6 +44,6 @@ EOF chown ubuntu:ubuntu /home/ubuntu/.bash_profile # get all deps and tools, ready to install/test - su - ubuntu -c 'cd /home/ubuntu/go/src/github.com/tendermint/tendermint && make get_vendor_deps && make tools' + su - ubuntu -c 'cd /home/ubuntu/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps' SHELL end From a0346000249541406dc9b84458af567ef8a58f2d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 5 Jan 2018 22:35:57 -0800 Subject: [PATCH 033/124] Revert "Changes to achieve a standardized build process and deterministic builds" --- Makefile | 79 +++++++------------------------------------ docs/install.rst | 13 ------- scripts/dist_build.sh | 28 +++++++++++++-- 3 files changed, 38 insertions(+), 82 deletions(-) diff --git a/Makefile b/Makefile index 2a4b14e3..2aed1acf 100644 --- a/Makefile +++ b/Makefile @@ -1,34 +1,13 @@ -GOTOOLS := \ +GOTOOLS = \ github.com/mitchellh/gox \ github.com/Masterminds/glide \ github.com/tcnksm/ghr \ gopkg.in/alecthomas/gometalinter.v2 -GO_MIN_VERSION := 1.9.2 -PACKAGES := $(shell go list ./... | grep -v '/vendor/') -BUILD_TAGS ?= tendermint -TMHOME ?= $(HOME)/.tendermint -GOPATH ?= $(shell go env GOPATH) -GOROOT ?= $(shell go env GOROOT) -GOGCCFLAGS ?= $(shell go env GOGCCFLAGS) -#LDFLAGS_EXTRA ?= -w -s -XC_ARCH ?= 386 amd64 arm -XC_OS ?= solaris darwin freebsd linux windows -XC_OSARCH ?= !darwin/arm !solaris/amd64 !freebsd/amd64 -BUILD_OUTPUT ?= ./build/{{.OS}}_{{.Arch}}/tendermint - -GOX_FLAGS = -os="$(XC_OS)" -arch="$(XC_ARCH)" -osarch="$(XC_OSARCH)" -output="$(BUILD_OUTPUT)" -ifeq ($(BUILD_FLAGS_RACE),YES) -RACEFLAG=-race -else -RACEFLAG= -endif -BUILD_FLAGS = -asmflags "-trimpath $(GOPATH)" -gcflags "-trimpath $(GOPATH)" -tags "$(BUILD_TAGS)" -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=$(shell git rev-parse --short=8 HEAD) $(LDFLAGS_EXTRA)" $(RACEFLAG) -GO_VERSION:=$(shell go version | grep -o '[[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+') -#Check that that minor version of GO meets the minimum required -GO_MINOR_VERSION := $(shell grep -o \.[[:digit:]][[:digit:]]*\. <<< $(GO_VERSION) | grep -o [[:digit:]]* ) -GO_MIN_MINOR_VERSION := $(shell grep -o \.[[:digit:]][[:digit:]]*\. <<< $(GO_MIN_VERSION) | grep -o [[:digit:]]* ) -GO_MINOR_VERSION_CHECK := $(shell test $(GO_MINOR_VERSION) -ge $(GO_MIN_MINOR_VERSION) && echo YES) - +GOTOOLS_CHECK = gox glide ghr gometalinter.v2 +PACKAGES=$(shell go list ./... | grep -v '/vendor/') +BUILD_TAGS?=tendermint +TMHOME = $${TMHOME:-$$HOME/.tendermint} +BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short HEAD`" all: check build test install metalinter @@ -38,61 +17,27 @@ check: check_tools get_vendor_deps ######################################## ### Build -build_xc: check_tools - $(shell which gox) $(BUILD_FLAGS) $(GOX_FLAGS) ./cmd/tendermint/ - build: -ifeq ($(OS),Windows_NT) - make build_xc XC_ARCH=amd64 XC_OS=windows BUILD_OUTPUT=$(GOPATH)/bin/tendermint -else - make build_xc XC_ARCH=amd64 XC_OS="$(shell uname -s)" BUILD_OUTPUT=$(GOPATH)/bin/tendermint -endif + go build $(BUILD_FLAGS) -o build/tendermint ./cmd/tendermint/ build_race: -#TODO: Wait for this to be merged: https://github.com/mitchellh/gox/pull/105 Then switch over to make build and remove the go build line. -# make build BUILD_FLAGS_RACE=YES - $(shell which go) build $(BUILD_FLAGS) -race -o "$(BUILD_OUTPUT)" ./cmd/tendermint/ + go build -race $(BUILD_FLAGS) -o build/tendermint ./cmd/tendermint # dist builds binaries for all platforms and packages them for distribution dist: @BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/dist.sh'" install: - make build + go install $(BUILD_FLAGS) ./cmd/tendermint ######################################## ### Tools & dependencies check_tools: -ifeq ($(GO_VERSION),) - $(error go not found) -endif -#Check minimum required go version -ifneq ($(GO_VERSION),$(GO_MIN_VERSION)) - $(warning WARNING: build will not be deterministic. go version should be $(GO_MIN_VERSION)) -ifneq ($(GO_MINOR_VERSION_CHECK),YES) - $(error ERROR: The minor version of Go ($(GO_VERSION)) is lower than the minimum required ($(GO_MIN_VERSION))) -endif -endif -#-fdebug-prefix-map switches the temporary, randomized workdir name in the binary to a static text -ifneq ($(findstring -fdebug-prefix-map,$(GOGCCFLAGS)),-fdebug-prefix-map) - $(warning WARNING: build will not be deterministic. The compiler does not support the '-fdebug-prefix-map' flag.) -endif -#GOROOT string is copied into the binary. For deterministic builds, we agree to keep it at /usr/local/go. (Default for golang:1.9.2 docker image, linux and osx.) -ifneq ($(GOROOT),/usr/local/go) - $(warning WARNING: build will not be deterministic. GOROOT should be set to /usr/local/go) -endif -#GOPATH string is copied into the binary. Although the -trimpath flag tries to eliminate it, it doesn't do it everywhere in Go 1.9.2. For deterministic builds we agree to keep it at /go. (Default for golang:1.9.2 docker image.) -ifneq ($(GOPATH),/go) - $(warning WARNING: build will not be deterministic. GOPATH should be set to /go) -endif -#External dependencies defined in GOTOOLS are built with get_tools. If they are already available on the system (for exmaple using a package manager), then get_tools might not be necessary. -ifneq ($(findstring $(GOPATH)/bin,$(PATH)),$(GOPATH)/bin) - $(warning WARNING: PATH does not contain GOPATH/bin. Some external dependencies might be unavailable.) -endif -# https://stackoverflow.com/a/25668869 - @echo "Found tools: $(foreach tool,$(notdir $(GOTOOLS)),$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH. Add GOPATH/bin to PATH and run 'make get_tools'")))" + @# https://stackoverflow.com/a/25668869 + @echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\ + $(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" get_tools: @echo "--> Installing tools" diff --git a/docs/install.rst b/docs/install.rst index 9edc051a..64fae4cd 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -6,19 +6,6 @@ From Binary To download pre-built binaries, see the `Download page `__. -From Source using Docker ------------------------- - -If you have docker running, all you need is the ``golang`` image to build tendermint. -If you don't, you can get help setting it up `here `__. - -:: - mkdir $HOME/tendermintbin - docker run --rm -it -v $HOME/tendermintbin:/go/bin:Z golang:1.9.2 /bin/bash -c "go-wrapper download github.com/tendermint/tendermint/cmd/tendermint ; make -C /go/src/github.com/tendermint/tendermint get_tools get_vendor_deps build_cc" - -You will find the ``tendermint`` binaries for different architectures and operating systems in your ``$HOME/tendermintbin`` folder. - - From Source ----------- diff --git a/scripts/dist_build.sh b/scripts/dist_build.sh index f1d8779f..587199e0 100755 --- a/scripts/dist_build.sh +++ b/scripts/dist_build.sh @@ -9,8 +9,32 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that dir because we expect that. cd "$DIR" -# Make sure build tools are available, get VENDORED dependencies and build -make get_tools get_vendor_deps build_xc +# Get the git commit +GIT_COMMIT="$(git rev-parse --short HEAD)" +GIT_IMPORT="github.com/tendermint/tendermint/version" + +# Determine the arch/os combos we're building for +XC_ARCH=${XC_ARCH:-"386 amd64 arm"} +XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"} + +# Make sure build tools are available. +make tools + +# Get VENDORED dependencies +make get_vendor_deps + +# Build! +# ldflags: -s Omit the symbol table and debug information. +# -w Omit the DWARF symbol table. +echo "==> Building..." +"$(which gox)" \ + -os="${XC_OS}" \ + -arch="${XC_ARCH}" \ + -osarch="!darwin/arm !solaris/amd64 !freebsd/amd64" \ + -ldflags "-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}" \ + -output "build/pkg/{{.OS}}_{{.Arch}}/tendermint" \ + -tags="${BUILD_TAGS}" \ + github.com/tendermint/tendermint/cmd/tendermint # Zip all the files. echo "==> Packaging..." From 13fa23c56854a855631b22a1149780fe64ec5760 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Sat, 6 Jan 2018 22:24:58 +0100 Subject: [PATCH 034/124] Add error checking --- lite/inquirer_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lite/inquirer_test.go b/lite/inquirer_test.go index 51005967..25bf51c4 100644 --- a/lite/inquirer_test.go +++ b/lite/inquirer_test.go @@ -37,14 +37,15 @@ func TestInquirerValidPath(t *testing.T) { } // initialize a certifier with the initial state - cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) + cert, err := lite.NewInquiring(chainID, commits[0], trust, source) + require.Nil(err) // this should fail validation.... commit := commits[count-1].Commit - err := cert.Certify(commit) + err = cert.Certify(commit) require.NotNil(err) - // add a few seed in the middle should be insufficient + // adding a few commits in the middle should be insufficient for i := 10; i < 13; i++ { err := source.StoreCommit(commits[i]) require.Nil(err) From dba48156165817c03682c54af42e63f1e46926c8 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 5 Jan 2018 11:52:58 +0100 Subject: [PATCH 035/124] Define requirements of the proposer selection procedure --- .../new-spec/proposer-selection.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/specification/new-spec/proposer-selection.md diff --git a/docs/specification/new-spec/proposer-selection.md b/docs/specification/new-spec/proposer-selection.md new file mode 100644 index 00000000..a83cb65e --- /dev/null +++ b/docs/specification/new-spec/proposer-selection.md @@ -0,0 +1,47 @@ +# Proposer selection procedure in Tendermint + +This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer. +As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning. +Let denote with `proposer_p(h,r)` a process returned by the Proposer Selection Procedure at the process p, at height h +and round r. Then the Proposer Selection procedure should fulfill the following properties: + +`Agreement`: Given a validator set V, and two honest validators, +p and q, for each height h, and each round r, +proposer_p(h,r) = proposer_q(h,r) + +`Liveness`: In every consecutive sequence of rounds of size K (K is system parameter), at least a +single round has an honest proposer. + +`Fairness`: The proposer selection is proportional to the validator voting power, i.e., a validator with more +voting power is selected more frequently, proportional to its power. More precisely, given a set of processes +with the total voting power N, during a sequence of rounds of size N, every process is proposer in a number of rounds +equal to its voting power. + +We now look at a few particular cases to understand better how fairness should be implemented. +If we have 4 processes with the following voting power distribution (p0,4), (p1, 2), (p2, 3), (p3, 4) at some round r, +we have the following sequence of proposer selections in the following rounds: + +`p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, etc` + +Let consider now the following scenario where a total voting power of faulty processes is aggregated in a single process +p0: (p0,3), (p1, 1), (p2, 1), (p3, 1), (p4, 1), (p5, 1), (p6, 1), (p7, 1). +In this case the sequence of proposer selections looks like this: + +`p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, etc` + +In this case, we see that a number of rounds coordinated by a faulty process is proportional to its voting power. +We consider also the case where we have voting power uniformly distributed among processes, i.e., we have 10 processes +each with voting power of 1. And let consider that there are 3 faulty processes with consecutive addresses, +for example the first 3 processes are faulty. Then the sequence looks like this: + +`p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, etc` + +In this case, we have 3 consecutive rounds with a faulty proposer. +One special case we consider is the case where a single honest process p0 has most of the voting power, for example: +(p0,100), (p1, 2), (p2, 3), (p3, 4). Then the sequence of proposer selection looks like this: + +p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p1, p0, p0, p0, p0, p0, etc + +This basically means that almost all rounds have the same proposer. But in this case, the process p0 has anyway enough +voting power to decide whatever he wants, so the fact that he coordinates almost all rounds seems correct. + From 555f560ecd7906d29112c5dbde4ef4952b15c63a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 8 Jan 2018 13:13:47 -0600 Subject: [PATCH 036/124] fix broken `make dist` target --- scripts/dist_build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/dist_build.sh b/scripts/dist_build.sh index 587199e0..337fbaca 100755 --- a/scripts/dist_build.sh +++ b/scripts/dist_build.sh @@ -18,7 +18,8 @@ XC_ARCH=${XC_ARCH:-"386 amd64 arm"} XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"} # Make sure build tools are available. -make tools +# TODO: Tools should be "vendored" too. +make get_tools # Get VENDORED dependencies make get_vendor_deps From b9cbaf8f10da417e4939f7e1e9b2d10aa1120181 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 4 Jan 2018 13:52:41 -0500 Subject: [PATCH 037/124] priv-val: fix timestamp for signing things that only differ by timestamp --- node/node.go | 4 +- types/priv_validator.go | 120 +++++++++++++++++++++++++---------- types/priv_validator_test.go | 60 ++++++++++++++++++ 3 files changed, 148 insertions(+), 36 deletions(-) diff --git a/node/node.go b/node/node.go index f922d832..fde8e1e0 100644 --- a/node/node.go +++ b/node/node.go @@ -185,9 +185,9 @@ func NewNode(config *cfg.Config, // Log whether this node is a validator or an observer if state.Validators.HasAddress(privValidator.GetAddress()) { - consensusLogger.Info("This node is a validator") + consensusLogger.Info("This node is a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey()) } else { - consensusLogger.Info("This node is not a validator") + consensusLogger.Info("This node is not a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey()) } // Make MempoolReactor diff --git a/types/priv_validator.go b/types/priv_validator.go index 31c65eeb..3577049e 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -17,10 +17,10 @@ import ( // TODO: type ? const ( - stepNone = 0 // Used to distinguish the initial state - stepPropose = 1 - stepPrevote = 2 - stepPrecommit = 3 + stepNone int8 = 0 // Used to distinguish the initial state + stepPropose int8 = 1 + stepPrevote int8 = 2 + stepPrecommit int8 = 3 ) func voteToStep(vote *Vote) int8 { @@ -199,12 +199,9 @@ func (privVal *PrivValidatorFS) Reset() { func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() - signature, err := privVal.signBytesHRS(vote.Height, vote.Round, voteToStep(vote), - SignBytes(chainID, vote), checkVotesOnlyDifferByTimestamp) - if err != nil { + if err := privVal.signVote(chainID, vote); err != nil { return errors.New(cmn.Fmt("Error signing vote: %v", err)) } - vote.Signature = signature return nil } @@ -213,12 +210,9 @@ func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() - signature, err := privVal.signBytesHRS(proposal.Height, proposal.Round, stepPropose, - SignBytes(chainID, proposal), checkProposalsOnlyDifferByTimestamp) - if err != nil { + if err := privVal.signProposal(chainID, proposal); err != nil { return fmt.Errorf("Error signing proposal: %v", err) } - proposal.Signature = signature return nil } @@ -250,36 +244,82 @@ func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bo return false, nil } -// signBytesHRS signs the given signBytes if the height/round/step (HRS) are -// greater than the latest state. If the HRS are equal and the only thing changed is the timestamp, -// it returns the privValidator.LastSignature. Else it returns an error. -func (privVal *PrivValidatorFS) signBytesHRS(height int64, round int, step int8, - signBytes []byte, checkFn checkOnlyDifferByTimestamp) (crypto.Signature, error) { - sig := crypto.Signature{} +// 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 (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error { + height, round, step := vote.Height, vote.Round, voteToStep(vote) + signBytes := SignBytes(chainID, vote) sameHRS, err := privVal.checkHRS(height, round, step) if err != nil { - return sig, err + return err } // We might crash before writing to the wal, - // causing us to try to re-sign for the same HRS + // causing us to try to re-sign for the same HRS. + // If signbytes are the same, use the last signature. + // If they only differ by timestamp, use last timestamp and signature + // Otherwise, return error if sameHRS { - // if they're the same or only differ by timestamp, - // return the LastSignature. Otherwise, error - if bytes.Equal(signBytes, privVal.LastSignBytes) || - checkFn(privVal.LastSignBytes, signBytes) { - return privVal.LastSignature, nil + if bytes.Equal(signBytes, privVal.LastSignBytes) { + vote.Signature = privVal.LastSignature + } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { + vote.Timestamp = timestamp + vote.Signature = privVal.LastSignature + } else { + err = fmt.Errorf("Conflicting data") } - return sig, fmt.Errorf("Conflicting data") + return err } - sig, err = privVal.Sign(signBytes) + // It passed the checks. Sign the vote + sig, err := privVal.Sign(signBytes) if err != nil { - return sig, err + return err } privVal.saveSigned(height, round, step, signBytes, sig) - return sig, nil + 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 (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error { + height, round, step := proposal.Height, proposal.Round, stepPropose + signBytes := SignBytes(chainID, proposal) + + sameHRS, err := privVal.checkHRS(height, round, step) + if err != nil { + return err + } + + // We might crash before writing to the wal, + // causing us to try to re-sign for the same HRS. + // If signbytes are the same, use the last signature. + // If they only differ by timestamp, use last timestamp and signature + // Otherwise, return error + if sameHRS { + if bytes.Equal(signBytes, privVal.LastSignBytes) { + proposal.Signature = privVal.LastSignature + } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { + proposal.Timestamp = timestamp + proposal.Signature = privVal.LastSignature + } else { + err = fmt.Errorf("Conflicting data") + } + return err + } + + // It passed the checks. Sign the proposal + sig, err := privVal.Sign(signBytes) + if err != nil { + return err + } + privVal.saveSigned(height, round, step, signBytes, sig) + proposal.Signature = sig + return nil } // Persist height/round/step and signature @@ -331,8 +371,9 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) { type checkOnlyDifferByTimestamp func([]byte, []byte) bool -// returns true if the only difference in the votes is their timestamp -func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) bool { +// returns the timestamp from the lastSignBytes. +// returns true if the only difference in the votes is their timestamp. +func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { var lastVote, newVote CanonicalJSONOnceVote if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil { panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err)) @@ -341,6 +382,11 @@ func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) bool { panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err)) } + lastTime, err := time.Parse(timeFormat, lastVote.Vote.Timestamp) + if err != nil { + panic(err) + } + // set the times to the same value and check equality now := CanonicalTime(time.Now()) lastVote.Vote.Timestamp = now @@ -348,11 +394,12 @@ func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) bool { lastVoteBytes, _ := json.Marshal(lastVote) newVoteBytes, _ := json.Marshal(newVote) - return bytes.Equal(newVoteBytes, lastVoteBytes) + return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes) } +// returns the timestamp from the lastSignBytes. // returns true if the only difference in the proposals is their timestamp -func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) bool { +func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { var lastProposal, newProposal CanonicalJSONOnceProposal if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil { panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err)) @@ -361,6 +408,11 @@ func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) boo panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err)) } + lastTime, err := time.Parse(timeFormat, lastProposal.Proposal.Timestamp) + if err != nil { + panic(err) + } + // set the times to the same value and check equality now := CanonicalTime(time.Now()) lastProposal.Proposal.Timestamp = now @@ -368,5 +420,5 @@ func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) boo lastProposalBytes, _ := json.Marshal(lastProposal) newProposalBytes, _ := json.Marshal(newProposal) - return bytes.Equal(newProposalBytes, lastProposalBytes) + return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes) } diff --git a/types/priv_validator_test.go b/types/priv_validator_test.go index 2fefee60..dd0ebff7 100644 --- a/types/priv_validator_test.go +++ b/types/priv_validator_test.go @@ -173,6 +173,58 @@ func TestSignProposal(t *testing.T) { assert.Equal(sig, proposal.Signature) } +func TestDifferByTimestamp(t *testing.T) { + _, tempFilePath := cmn.Tempfile("priv_validator_") + privVal := GenPrivValidatorFS(tempFilePath) + + block1 := PartSetHeader{5, []byte{1, 2, 3}} + height, round := int64(10), 1 + chainID := "mychainid" + + // test proposal + { + proposal := newProposal(height, round, block1) + err := privVal.SignProposal(chainID, proposal) + assert.NoError(t, err, "expected no error signing proposal") + signBytes := SignBytes(chainID, proposal) + sig := proposal.Signature + timeStamp := clipToMS(proposal.Timestamp) + + // manipulate the timestamp. should get changed back + proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond) + proposal.Signature = crypto.Signature{} + err = privVal.SignProposal("mychainid", proposal) + assert.NoError(t, err, "expected no error on signing same proposal") + + assert.Equal(t, timeStamp, proposal.Timestamp) + assert.Equal(t, signBytes, SignBytes(chainID, proposal)) + assert.Equal(t, sig, proposal.Signature) + } + + // test vote + { + voteType := VoteTypePrevote + blockID := BlockID{[]byte{1, 2, 3}, PartSetHeader{}} + vote := newVote(privVal.Address, 0, height, round, voteType, blockID) + err := privVal.SignVote("mychainid", vote) + assert.NoError(t, err, "expected no error signing vote") + + signBytes := SignBytes(chainID, vote) + sig := vote.Signature + timeStamp := clipToMS(vote.Timestamp) + + // manipulate the timestamp. should get changed back + vote.Timestamp = vote.Timestamp.Add(time.Millisecond) + vote.Signature = crypto.Signature{} + err = privVal.SignVote("mychainid", vote) + assert.NoError(t, err, "expected no error on signing same vote") + + assert.Equal(t, timeStamp, vote.Timestamp) + assert.Equal(t, signBytes, SignBytes(chainID, vote)) + assert.Equal(t, sig, vote.Signature) + } +} + func newVote(addr data.Bytes, idx int, height int64, round int, typ byte, blockID BlockID) *Vote { return &Vote{ ValidatorAddress: addr, @@ -190,5 +242,13 @@ func newProposal(height int64, round int, partsHeader PartSetHeader) *Proposal { Height: height, Round: round, BlockPartsHeader: partsHeader, + Timestamp: time.Now().UTC(), } } + +func clipToMS(t time.Time) time.Time { + nano := t.UnixNano() + million := int64(1000000) + nano = (nano / million) * million + return time.Unix(0, nano).UTC() +} From 32311acd01e447f42bedf3e44159e01945df643e Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Tue, 9 Jan 2018 17:36:11 +0100 Subject: [PATCH 038/124] Vulnerability in light client proxy (#1081) * Vulnerability in light client proxy When calling GetCertifiedCommit the light client proxy would call Certify and even on error return the Commit as if it had been correctly certified. Now it returns the error correctly and returns an empty Commit on error. * Improve names for clarity The lite package now contains StaticCertifier, DynamicCertifier and InqueringCertifier. This also changes the method receivers from one letter to two letter names, which will make future refactoring easier and follows the coding standards. * Fix test failures * Rename files * remove dead code --- lite/client/provider_test.go | 2 +- lite/{dynamic.go => dynamic_certifier.go} | 51 +++--- ...amic_test.go => dynamic_certifier_test.go} | 4 +- lite/inquirer.go | 159 ----------------- lite/inquiring_certifier.go | 163 ++++++++++++++++++ ...er_test.go => inquiring_certifier_test.go} | 6 +- lite/performance_test.go | 2 +- lite/proxy/certifier.go | 4 +- lite/proxy/proxy.go | 8 +- lite/proxy/query.go | 21 ++- lite/proxy/query_test.go | 5 +- lite/proxy/wrapper.go | 8 +- lite/{static.go => static_certifier.go} | 38 ++-- ...tatic_test.go => static_certifier_test.go} | 2 +- rpc/client/interface.go | 3 +- types/priv_validator.go | 2 - 16 files changed, 246 insertions(+), 232 deletions(-) rename lite/{dynamic.go => dynamic_certifier.go} (60%) rename lite/{dynamic_test.go => dynamic_certifier_test.go} (97%) delete mode 100644 lite/inquirer.go create mode 100644 lite/inquiring_certifier.go rename lite/{inquirer_test.go => inquiring_certifier_test.go} (95%) rename lite/{static.go => static_certifier.go} (54%) rename lite/{static_test.go => static_certifier_test.go} (97%) diff --git a/lite/client/provider_test.go b/lite/client/provider_test.go index 0bebfced..856769d9 100644 --- a/lite/client/provider_test.go +++ b/lite/client/provider_test.go @@ -35,7 +35,7 @@ func TestProvider(t *testing.T) { // let's check this is valid somehow assert.Nil(seed.ValidateBasic(chainID)) - cert := lite.NewStatic(chainID, seed.Validators) + cert := lite.NewStaticCertifier(chainID, seed.Validators) // historical queries now work :) lower := sh - 5 diff --git a/lite/dynamic.go b/lite/dynamic_certifier.go similarity index 60% rename from lite/dynamic.go rename to lite/dynamic_certifier.go index 231aed7a..0ddace8b 100644 --- a/lite/dynamic.go +++ b/lite/dynamic_certifier.go @@ -6,9 +6,9 @@ import ( liteErr "github.com/tendermint/tendermint/lite/errors" ) -var _ Certifier = &Dynamic{} +var _ Certifier = (*DynamicCertifier)(nil) -// Dynamic uses a Static for Certify, but adds an +// DynamicCertifier uses a StaticCertifier for Certify, but adds an // Update method to allow for a change of validators. // // You can pass in a FullCommit with another validator set, @@ -17,46 +17,48 @@ var _ Certifier = &Dynamic{} // validator set for the next Certify call. // For security, it will only follow validator set changes // going forward. -type Dynamic struct { - cert *Static +type DynamicCertifier struct { + cert *StaticCertifier lastHeight int64 } // NewDynamic returns a new dynamic certifier. -func NewDynamic(chainID string, vals *types.ValidatorSet, height int64) *Dynamic { - return &Dynamic{ - cert: NewStatic(chainID, vals), +func NewDynamicCertifier(chainID string, vals *types.ValidatorSet, height int64) *DynamicCertifier { + return &DynamicCertifier{ + cert: NewStaticCertifier(chainID, vals), lastHeight: height, } } // ChainID returns the chain id of this certifier. -func (c *Dynamic) ChainID() string { - return c.cert.ChainID() +// Implements Certifier. +func (dc *DynamicCertifier) ChainID() string { + return dc.cert.ChainID() } // Validators returns the validators of this certifier. -func (c *Dynamic) Validators() *types.ValidatorSet { - return c.cert.vSet +func (dc *DynamicCertifier) Validators() *types.ValidatorSet { + return dc.cert.vSet } // Hash returns the hash of this certifier. -func (c *Dynamic) Hash() []byte { - return c.cert.Hash() +func (dc *DynamicCertifier) Hash() []byte { + return dc.cert.Hash() } // LastHeight returns the last height of this certifier. -func (c *Dynamic) LastHeight() int64 { - return c.lastHeight +func (dc *DynamicCertifier) LastHeight() int64 { + return dc.lastHeight } // Certify will verify whether the commit is valid and will update the height if it is or return an // error if it is not. -func (c *Dynamic) Certify(check Commit) error { - err := c.cert.Certify(check) +// Implements Certifier. +func (dc *DynamicCertifier) Certify(check Commit) error { + err := dc.cert.Certify(check) if err == nil { // update last seen height if input is valid - c.lastHeight = check.Height() + dc.lastHeight = check.Height() } return err } @@ -65,15 +67,15 @@ func (c *Dynamic) Certify(check Commit) error { // the certifying validator set if safe to do so. // // Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr) -func (c *Dynamic) Update(fc FullCommit) error { +func (dc *DynamicCertifier) Update(fc FullCommit) error { // ignore all checkpoints in the past -> only to the future h := fc.Height() - if h <= c.lastHeight { + if h <= dc.lastHeight { return liteErr.ErrPastTime() } // first, verify if the input is self-consistent.... - err := fc.ValidateBasic(c.ChainID()) + err := fc.ValidateBasic(dc.ChainID()) if err != nil { return err } @@ -82,14 +84,13 @@ func (c *Dynamic) Update(fc FullCommit) error { // would be approved by the currently known validator set // as well as the new set commit := fc.Commit.Commit - err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(), - commit.BlockID, h, commit) + err = dc.Validators().VerifyCommitAny(fc.Validators, dc.ChainID(), commit.BlockID, h, commit) if err != nil { return liteErr.ErrTooMuchChange() } // looks good, we can update - c.cert = NewStatic(c.ChainID(), fc.Validators) - c.lastHeight = h + dc.cert = NewStaticCertifier(dc.ChainID(), fc.Validators) + dc.lastHeight = h return nil } diff --git a/lite/dynamic_test.go b/lite/dynamic_certifier_test.go similarity index 97% rename from lite/dynamic_test.go rename to lite/dynamic_certifier_test.go index c45371ac..88c145f9 100644 --- a/lite/dynamic_test.go +++ b/lite/dynamic_certifier_test.go @@ -23,7 +23,7 @@ func TestDynamicCert(t *testing.T) { vals := keys.ToValidators(20, 10) // and a certifier based on our known set chainID := "test-dyno" - cert := lite.NewDynamic(chainID, vals, 0) + cert := lite.NewDynamicCertifier(chainID, vals, 0) cases := []struct { keys lite.ValKeys @@ -67,7 +67,7 @@ func TestDynamicUpdate(t *testing.T) { chainID := "test-dyno-up" keys := lite.GenValKeys(5) vals := keys.ToValidators(20, 0) - cert := lite.NewDynamic(chainID, vals, 40) + cert := lite.NewDynamicCertifier(chainID, vals, 40) // one valid block to give us a sense of time h := int64(100) diff --git a/lite/inquirer.go b/lite/inquirer.go deleted file mode 100644 index 9d0d77e5..00000000 --- a/lite/inquirer.go +++ /dev/null @@ -1,159 +0,0 @@ -package lite - -import ( - "github.com/tendermint/tendermint/types" - - liteErr "github.com/tendermint/tendermint/lite/errors" -) - -// Inquiring wraps a dynamic certifier and implements an auto-update strategy. If a call to Certify -// fails due to a change it validator set, Inquiring will try and find a previous FullCommit which -// it can use to safely update the validator set. It uses a source provider to obtain the needed -// FullCommits. It stores properly validated data on the local system. -type Inquiring struct { - cert *Dynamic - // These are only properly validated data, from local system - trusted Provider - // This is a source of new info, like a node rpc, or other import method - Source Provider -} - -// NewInquiring returns a new Inquiring object. It uses the trusted provider to store validated -// data and the source provider to obtain missing FullCommits. -// -// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source -// provider should be a client.HTTPProvider. -func NewInquiring(chainID string, fc FullCommit, trusted Provider, - source Provider) (*Inquiring, error) { - - // store the data in trusted - err := trusted.StoreCommit(fc) - if err != nil { - return nil, err - } - - return &Inquiring{ - cert: NewDynamic(chainID, fc.Validators, fc.Height()), - trusted: trusted, - Source: source, - }, nil -} - -// ChainID returns the chain id. -func (c *Inquiring) ChainID() string { - return c.cert.ChainID() -} - -// Validators returns the validator set. -func (c *Inquiring) Validators() *types.ValidatorSet { - return c.cert.cert.vSet -} - -// LastHeight returns the last height. -func (c *Inquiring) LastHeight() int64 { - return c.cert.lastHeight -} - -// Certify makes sure this is checkpoint is valid. -// -// If the validators have changed since the last know time, it looks -// for a path to prove the new validators. -// -// On success, it will store the checkpoint in the store for later viewing -func (c *Inquiring) Certify(commit Commit) error { - err := c.useClosestTrust(commit.Height()) - if err != nil { - return err - } - - err = c.cert.Certify(commit) - if !liteErr.IsValidatorsChangedErr(err) { - return err - } - err = c.updateToHash(commit.Header.ValidatorsHash) - if err != nil { - return err - } - - err = c.cert.Certify(commit) - if err != nil { - return err - } - - // store the new checkpoint - return c.trusted.StoreCommit(NewFullCommit(commit, c.Validators())) -} - -// Update will verify if this is a valid change and update -// the certifying validator set if safe to do so. -func (c *Inquiring) Update(fc FullCommit) error { - err := c.useClosestTrust(fc.Height()) - if err != nil { - return err - } - - err = c.cert.Update(fc) - if err == nil { - err = c.trusted.StoreCommit(fc) - } - return err -} - -func (c *Inquiring) useClosestTrust(h int64) error { - closest, err := c.trusted.GetByHeight(h) - if err != nil { - return err - } - - // if the best seed is not the one we currently use, - // let's just reset the dynamic validator - if closest.Height() != c.LastHeight() { - c.cert = NewDynamic(c.ChainID(), closest.Validators, closest.Height()) - } - return nil -} - -// updateToHash gets the validator hash we want to update to -// if IsTooMuchChangeErr, we try to find a path by binary search over height -func (c *Inquiring) updateToHash(vhash []byte) error { - // try to get the match, and update - fc, err := c.Source.GetByHash(vhash) - if err != nil { - return err - } - err = c.cert.Update(fc) - // handle IsTooMuchChangeErr by using divide and conquer - if liteErr.IsTooMuchChangeErr(err) { - err = c.updateToHeight(fc.Height()) - } - return err -} - -// updateToHeight will use divide-and-conquer to find a path to h -func (c *Inquiring) updateToHeight(h int64) error { - // try to update to this height (with checks) - fc, err := c.Source.GetByHeight(h) - if err != nil { - return err - } - start, end := c.LastHeight(), fc.Height() - if end <= start { - return liteErr.ErrNoPathFound() - } - err = c.Update(fc) - - // we can handle IsTooMuchChangeErr specially - if !liteErr.IsTooMuchChangeErr(err) { - return err - } - - // try to update to mid - mid := (start + end) / 2 - err = c.updateToHeight(mid) - if err != nil { - return err - } - - // if we made it to mid, we recurse - return c.updateToHeight(h) -} diff --git a/lite/inquiring_certifier.go b/lite/inquiring_certifier.go new file mode 100644 index 00000000..042bd08e --- /dev/null +++ b/lite/inquiring_certifier.go @@ -0,0 +1,163 @@ +package lite + +import ( + "github.com/tendermint/tendermint/types" + + liteErr "github.com/tendermint/tendermint/lite/errors" +) + +var _ Certifier = (*InquiringCertifier)(nil) + +// InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call +// to Certify fails due to a change it validator set, InquiringCertifier will try and find a +// previous FullCommit which it can use to safely update the validator set. It uses a source +// provider to obtain the needed FullCommits. It stores properly validated data on the local system. +type InquiringCertifier struct { + cert *DynamicCertifier + // These are only properly validated data, from local system + trusted Provider + // This is a source of new info, like a node rpc, or other import method + Source Provider +} + +// NewInquiringCertifier returns a new Inquiring object. It uses the trusted provider to store +// validated data and the source provider to obtain missing FullCommits. +// +// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source +// provider should be a client.HTTPProvider. +func NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider, + source Provider) (*InquiringCertifier, error) { + + // store the data in trusted + err := trusted.StoreCommit(fc) + if err != nil { + return nil, err + } + + return &InquiringCertifier{ + cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()), + trusted: trusted, + Source: source, + }, nil +} + +// ChainID returns the chain id. +// Implements Certifier. +func (ic *InquiringCertifier) ChainID() string { + return ic.cert.ChainID() +} + +// Validators returns the validator set. +func (ic *InquiringCertifier) Validators() *types.ValidatorSet { + return ic.cert.cert.vSet +} + +// LastHeight returns the last height. +func (ic *InquiringCertifier) LastHeight() int64 { + return ic.cert.lastHeight +} + +// Certify makes sure this is checkpoint is valid. +// +// If the validators have changed since the last know time, it looks +// for a path to prove the new validators. +// +// On success, it will store the checkpoint in the store for later viewing +// Implements Certifier. +func (ic *InquiringCertifier) Certify(commit Commit) error { + err := ic.useClosestTrust(commit.Height()) + if err != nil { + return err + } + + err = ic.cert.Certify(commit) + if !liteErr.IsValidatorsChangedErr(err) { + return err + } + err = ic.updateToHash(commit.Header.ValidatorsHash) + if err != nil { + return err + } + + err = ic.cert.Certify(commit) + if err != nil { + return err + } + + // store the new checkpoint + return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators())) +} + +// Update will verify if this is a valid change and update +// the certifying validator set if safe to do so. +func (ic *InquiringCertifier) Update(fc FullCommit) error { + err := ic.useClosestTrust(fc.Height()) + if err != nil { + return err + } + + err = ic.cert.Update(fc) + if err == nil { + err = ic.trusted.StoreCommit(fc) + } + return err +} + +func (ic *InquiringCertifier) useClosestTrust(h int64) error { + closest, err := ic.trusted.GetByHeight(h) + if err != nil { + return err + } + + // if the best seed is not the one we currently use, + // let's just reset the dynamic validator + if closest.Height() != ic.LastHeight() { + ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height()) + } + return nil +} + +// updateToHash gets the validator hash we want to update to +// if IsTooMuchChangeErr, we try to find a path by binary search over height +func (ic *InquiringCertifier) updateToHash(vhash []byte) error { + // try to get the match, and update + fc, err := ic.Source.GetByHash(vhash) + if err != nil { + return err + } + err = ic.cert.Update(fc) + // handle IsTooMuchChangeErr by using divide and conquer + if liteErr.IsTooMuchChangeErr(err) { + err = ic.updateToHeight(fc.Height()) + } + return err +} + +// updateToHeight will use divide-and-conquer to find a path to h +func (ic *InquiringCertifier) updateToHeight(h int64) error { + // try to update to this height (with checks) + fc, err := ic.Source.GetByHeight(h) + if err != nil { + return err + } + start, end := ic.LastHeight(), fc.Height() + if end <= start { + return liteErr.ErrNoPathFound() + } + err = ic.Update(fc) + + // we can handle IsTooMuchChangeErr specially + if !liteErr.IsTooMuchChangeErr(err) { + return err + } + + // try to update to mid + mid := (start + end) / 2 + err = ic.updateToHeight(mid) + if err != nil { + return err + } + + // if we made it to mid, we recurse + return ic.updateToHeight(h) +} diff --git a/lite/inquirer_test.go b/lite/inquiring_certifier_test.go similarity index 95% rename from lite/inquirer_test.go rename to lite/inquiring_certifier_test.go index 25bf51c4..db8160bd 100644 --- a/lite/inquirer_test.go +++ b/lite/inquiring_certifier_test.go @@ -37,7 +37,7 @@ func TestInquirerValidPath(t *testing.T) { } // initialize a certifier with the initial state - cert, err := lite.NewInquiring(chainID, commits[0], trust, source) + cert, err := lite.NewInquiringCertifier(chainID, commits[0], trust, source) require.Nil(err) // this should fail validation.... @@ -88,7 +88,7 @@ func TestInquirerMinimalPath(t *testing.T) { } // initialize a certifier with the initial state - cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) + cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source) // this should fail validation.... commit := commits[count-1].Commit @@ -138,7 +138,7 @@ func TestInquirerVerifyHistorical(t *testing.T) { } // initialize a certifier with the initial state - cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) + cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source) // store a few commits as trust for _, i := range []int{2, 5} { diff --git a/lite/performance_test.go b/lite/performance_test.go index 835e52f9..28c73bb0 100644 --- a/lite/performance_test.go +++ b/lite/performance_test.go @@ -105,7 +105,7 @@ func BenchmarkCertifyCommitSec100(b *testing.B) { func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) { chainID := "bench-certify" vals := keys.ToValidators(20, 10) - cert := lite.NewStatic(chainID, vals) + cert := lite.NewStaticCertifier(chainID, vals) check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys)) for i := 0; i < b.N; i++ { err := cert.Certify(check) diff --git a/lite/proxy/certifier.go b/lite/proxy/certifier.go index 3dda935e..6e319dc0 100644 --- a/lite/proxy/certifier.go +++ b/lite/proxy/certifier.go @@ -6,7 +6,7 @@ import ( "github.com/tendermint/tendermint/lite/files" ) -func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { +func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.InquiringCertifier, error) { trust := lite.NewCacheProvider( lite.NewMemStoreProvider(), files.NewProvider(rootDir), @@ -26,7 +26,7 @@ func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { return nil, err } - cert, err := lite.NewInquiring(chainID, fc, trust, source) + cert, err := lite.NewInquiringCertifier(chainID, fc, trust, source) if err != nil { return nil, err } diff --git a/lite/proxy/proxy.go b/lite/proxy/proxy.go index 21db13ed..34aa99fa 100644 --- a/lite/proxy/proxy.go +++ b/lite/proxy/proxy.go @@ -18,7 +18,11 @@ const ( // set up the rpc routes to proxy via the given client, // and start up an http/rpc server on the location given by bind (eg. :1234) func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error { - c.Start() + err := c.Start() + if err != nil { + return err + } + r := RPCRoutes(c) // build the handler... @@ -30,7 +34,7 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error core.SetLogger(logger) mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) - _, err := rpc.StartHTTPServer(listenAddr, mux, logger) + _, err = rpc.StartHTTPServer(listenAddr, mux, logger) return err } diff --git a/lite/proxy/query.go b/lite/proxy/query.go index 0a9d86a0..72c3ed29 100644 --- a/lite/proxy/query.go +++ b/lite/proxy/query.go @@ -51,7 +51,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption // make sure the proof is the proper height if resp.IsErr() { - err = errors.Errorf("Query error %d: %d", resp.Code) + err = errors.Errorf("Query error for key %d: %d", key, resp.Code) return nil, nil, err } if len(resp.Key) == 0 || len(resp.Proof) == 0 { @@ -79,7 +79,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption if err != nil { return nil, nil, errors.Wrap(err, "Couldn't verify proof") } - return &ctypes.ResultABCIQuery{resp}, eproof, nil + return &ctypes.ResultABCIQuery{Response: resp}, eproof, nil } // The key wasn't found, construct a proof of non-existence. @@ -93,13 +93,12 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption if err != nil { return nil, nil, errors.Wrap(err, "Couldn't verify proof") } - return &ctypes.ResultABCIQuery{resp}, aproof, ErrNoData() + return &ctypes.ResultABCIQuery{Response: resp}, aproof, ErrNoData() } // GetCertifiedCommit gets the signed header for a given height // and certifies it. Returns error if unable to get a proven header. -func GetCertifiedCommit(h int64, node rpcclient.Client, - cert lite.Certifier) (empty lite.Commit, err error) { +func GetCertifiedCommit(h int64, node rpcclient.Client, cert lite.Certifier) (lite.Commit, error) { // FIXME: cannot use cert.GetByHeight for now, as it also requires // Validators and will fail on querying tendermint for non-current height. @@ -107,14 +106,18 @@ func GetCertifiedCommit(h int64, node rpcclient.Client, rpcclient.WaitForHeight(node, h, nil) cresp, err := node.Commit(&h) if err != nil { - return + return lite.Commit{}, err } - commit := client.CommitFromResult(cresp) + commit := client.CommitFromResult(cresp) // validate downloaded checkpoint with our request and trust store. if commit.Height() != h { - return empty, certerr.ErrHeightMismatch(h, commit.Height()) + return lite.Commit{}, certerr.ErrHeightMismatch(h, commit.Height()) } - err = cert.Certify(commit) + + if err = cert.Certify(commit); err != nil { + return lite.Commit{}, err + } + return commit, nil } diff --git a/lite/proxy/query_test.go b/lite/proxy/query_test.go index 234f65e5..6fc4b973 100644 --- a/lite/proxy/query_test.go +++ b/lite/proxy/query_test.go @@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) { source := certclient.NewProvider(cl) seed, err := source.GetByHeight(brh - 2) require.NoError(err, "%+v", err) - cert := lite.NewStatic("my-chain", seed.Validators) + cert := lite.NewStaticCertifier("my-chain", seed.Validators) client.WaitForHeight(cl, 3, nil) latest, err := source.LatestCommit() @@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) { source := certclient.NewProvider(cl) seed, err := source.GetByHeight(brh - 2) require.NoError(err, "%+v", err) - cert := lite.NewStatic("my-chain", seed.Validators) + cert := lite.NewStaticCertifier("my-chain", seed.Validators) // First let's make sure a bogus transaction hash returns a valid non-existence proof. key := types.Tx([]byte("bogus")).Hash() @@ -136,5 +136,4 @@ func _TestTxProofs(t *testing.T) { commit, err := GetCertifiedCommit(br.Height, cl, cert) require.Nil(err, "%+v", err) require.Equal(res.Proof.RootHash, commit.Header.DataHash) - } diff --git a/lite/proxy/wrapper.go b/lite/proxy/wrapper.go index a76c2942..7d504217 100644 --- a/lite/proxy/wrapper.go +++ b/lite/proxy/wrapper.go @@ -15,14 +15,14 @@ var _ rpcclient.Client = Wrapper{} // provable before passing it along. Allows you to make any rpcclient fully secure. type Wrapper struct { rpcclient.Client - cert *lite.Inquiring + cert *lite.InquiringCertifier } // SecureClient uses a given certifier to wrap an connection to an untrusted // host and return a cryptographically secure rpc client. // // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface -func SecureClient(c rpcclient.Client, cert *lite.Inquiring) Wrapper { +func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) Wrapper { wrap := Wrapper{c, cert} // TODO: no longer possible as no more such interface exposed.... // if we wrap http client, then we can swap out the event switch to filter @@ -34,7 +34,9 @@ func SecureClient(c rpcclient.Client, cert *lite.Inquiring) Wrapper { } // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof -func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { +func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, + opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert) return res, err } diff --git a/lite/static.go b/lite/static_certifier.go similarity index 54% rename from lite/static.go rename to lite/static_certifier.go index abbef578..1ec3b809 100644 --- a/lite/static.go +++ b/lite/static_certifier.go @@ -10,62 +10,64 @@ import ( liteErr "github.com/tendermint/tendermint/lite/errors" ) -var _ Certifier = &Static{} +var _ Certifier = (*StaticCertifier)(nil) -// Static assumes a static set of validators, set on +// StaticCertifier assumes a static set of validators, set on // initilization and checks against them. // The signatures on every header is checked for > 2/3 votes // against the known validator set upon Certify // // Good for testing or really simple chains. Building block // to support real-world functionality. -type Static struct { +type StaticCertifier struct { chainID string vSet *types.ValidatorSet vhash []byte } -// NewStatic returns a new certifier with a static validator set. -func NewStatic(chainID string, vals *types.ValidatorSet) *Static { - return &Static{ +// NewStaticCertifier returns a new certifier with a static validator set. +func NewStaticCertifier(chainID string, vals *types.ValidatorSet) *StaticCertifier { + return &StaticCertifier{ chainID: chainID, vSet: vals, } } // ChainID returns the chain id. -func (c *Static) ChainID() string { - return c.chainID +// Implements Certifier. +func (sc *StaticCertifier) ChainID() string { + return sc.chainID } // Validators returns the validator set. -func (c *Static) Validators() *types.ValidatorSet { - return c.vSet +func (sc *StaticCertifier) Validators() *types.ValidatorSet { + return sc.vSet } // Hash returns the hash of the validator set. -func (c *Static) Hash() []byte { - if len(c.vhash) == 0 { - c.vhash = c.vSet.Hash() +func (sc *StaticCertifier) Hash() []byte { + if len(sc.vhash) == 0 { + sc.vhash = sc.vSet.Hash() } - return c.vhash + return sc.vhash } // Certify makes sure that the commit is valid. -func (c *Static) Certify(commit Commit) error { +// Implements Certifier. +func (sc *StaticCertifier) Certify(commit Commit) error { // do basic sanity checks - err := commit.ValidateBasic(c.chainID) + err := commit.ValidateBasic(sc.chainID) if err != nil { return err } // make sure it has the same validator set we have (static means static) - if !bytes.Equal(c.Hash(), commit.Header.ValidatorsHash) { + if !bytes.Equal(sc.Hash(), commit.Header.ValidatorsHash) { return liteErr.ErrValidatorsChanged() } // then make sure we have the proper signatures for this - err = c.vSet.VerifyCommit(c.chainID, commit.Commit.BlockID, + err = sc.vSet.VerifyCommit(sc.chainID, commit.Commit.BlockID, commit.Header.Height, commit.Commit) return errors.WithStack(err) } diff --git a/lite/static_test.go b/lite/static_certifier_test.go similarity index 97% rename from lite/static_test.go rename to lite/static_certifier_test.go index 3e4d5927..03567daa 100644 --- a/lite/static_test.go +++ b/lite/static_certifier_test.go @@ -21,7 +21,7 @@ func TestStaticCert(t *testing.T) { vals := keys.ToValidators(20, 10) // and a certifier based on our known set chainID := "test-static" - cert := lite.NewStatic(chainID, vals) + cert := lite.NewStaticCertifier(chainID, vals) cases := []struct { keys lite.ValKeys diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 70cb4d95..6e798c37 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -33,7 +33,8 @@ type ABCIClient interface { // reading from abci app ABCIInfo() (*ctypes.ResultABCIInfo, error) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) - ABCIQueryWithOptions(path string, data data.Bytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) + ABCIQueryWithOptions(path string, data data.Bytes, + opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) // writing to abci app BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) diff --git a/types/priv_validator.go b/types/priv_validator.go index 3577049e..628f58cf 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -369,8 +369,6 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) { //------------------------------------- -type checkOnlyDifferByTimestamp func([]byte, []byte) bool - // returns the timestamp from the lastSignBytes. // returns true if the only difference in the votes is their timestamp. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { From 170777300ea92dc21a8aec1abc16cb51812513a4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 11:04:26 -0600 Subject: [PATCH 039/124] update docker Closes #1087 --- DOCKER/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index c0d09d95..c00318fb 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,8 +1,8 @@ FROM alpine:3.6 # This is the release of tendermint to pull in. -ENV TM_VERSION 0.13.0 -ENV TM_SHA256SUM 36d773d4c2890addc61cc87a72c1e9c21c89516921b0defb0edfebde719b4b85 +ENV TM_VERSION 0.15.0 +ENV TM_SHA256SUM 71cc271c67eca506ca492c8b90b090132f104bf5dbfe0af2702a50886e88de17 # Tendermint will be looking for genesis file in /tendermint (unless you change # `genesis_file` in config.toml). You can put your config.toml and private From 48638eaa20dc62ef7ce66138d0ad87f3b8ffa4ff Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 11:05:15 -0600 Subject: [PATCH 040/124] update docker readme --- DOCKER/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DOCKER/README.md b/DOCKER/README.md index fceab5fe..06f400ea 100644 --- a/DOCKER/README.md +++ b/DOCKER/README.md @@ -1,6 +1,7 @@ # Supported tags and respective `Dockerfile` links -- `0.13.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile) +- `0.15.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/170777300ea92dc21a8aec1abc16cb51812513a4/DOCKER/Dockerfile) +- `0.13.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile) - `0.12.1` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/457c688346b565e90735431619ca3ca597ef9007/DOCKER/Dockerfile) - `0.12.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/70d8afa6e952e24c573ece345560a5971bf2cc0e/DOCKER/Dockerfile) - `0.11.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/9177cc1f64ca88a4a0243c5d1773d10fba67e201/DOCKER/Dockerfile) From 03a14d834284900af5184b9c7f2e261ecdbb2ae2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 9 Jan 2018 12:44:49 -0500 Subject: [PATCH 041/124] docs/p2p: updates from review (#1076) --- docs/specification/new-spec/p2p/config.md | 8 ++-- docs/specification/new-spec/p2p/connection.md | 38 ++++++++----------- docs/specification/new-spec/p2p/node.md | 7 +++- docs/specification/new-spec/p2p/peer.md | 32 +++++++--------- 4 files changed, 38 insertions(+), 47 deletions(-) diff --git a/docs/specification/new-spec/p2p/config.md b/docs/specification/new-spec/p2p/config.md index bc3c343c..565f7800 100644 --- a/docs/specification/new-spec/p2p/config.md +++ b/docs/specification/new-spec/p2p/config.md @@ -6,14 +6,14 @@ Here we describe configuration options around the Peer Exchange. `--p2p.seed_mode` -The node operates in seed mode. It will kick incoming peers after sharing some peers. -It will continually crawl the network for peers. +The node operates in seed mode. In seed mode, a node continuously crawls the network for peers, +and upon incoming connection shares some peers and disconnects. ## Seeds `--p2p.seeds “1.2.3.4:466656,2.3.4.5:4444”` -Dials these seeds when we need more peers. They will return a list of peers and then disconnect. +Dials these seeds when we need more peers. They should return a list of peers and then disconnect. If we already have enough peers in the address book, we may never need to dial them. ## Persistent Peers @@ -27,7 +27,7 @@ anchor us in the p2p network. Note that the auto-redial uses exponential backoff and will give up after a day of trying to connect. -NOTE: If `dial_seeds` and `persistent_peers` intersect, +NOTE: If `seeds` and `persistent_peers` intersect, the user will be WARNED that seeds may auto-close connections and the node may not be able to keep the connection persistent. diff --git a/docs/specification/new-spec/p2p/connection.md b/docs/specification/new-spec/p2p/connection.md index 72847fa1..dfe0d090 100644 --- a/docs/specification/new-spec/p2p/connection.md +++ b/docs/specification/new-spec/p2p/connection.md @@ -1,12 +1,14 @@ +## P2P Multiplex Connection + +... + ## MConnection -`MConnection` is a multiplex connection: - -__multiplex__ *noun* a system or signal involving simultaneous transmission of -several messages along a single channel of communication. - -Each `MConnection` handles message transmission on multiple abstract communication -`Channel`s. Each channel has a globally unique byte id. +`MConnection` is a multiplex connection that supports multiple independent streams +with distinct quality of service guarantees atop a single TCP connection. +Each stream is known as a `Channel` and each `Channel` has a globally unique byte id. +Each `Channel` also has a relative priority that determines the quality of service +of the `Channel` in comparison to the others. The byte id and the relative priorities of each `Channel` are configured upon initialization of the connection. @@ -14,12 +16,13 @@ The `MConnection` supports three packet types: Ping, Pong, and Msg. ### Ping and Pong -The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively +The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively. When we haven't received any messages on an `MConnection` in a time `pingTimeout`, we send a ping message. -When a ping is received on the `MConnection`, a pong is sent in response. +When a ping is received on the `MConnection`, a pong is sent in response only if there are no other messages +to send and the peer has not sent us too many pings. -If a pong is not received in sufficient time, the peer's score should be decremented (TODO). +If a pong or message is not received in sufficient time after a ping, disconnect from the peer. ### Msg @@ -57,8 +60,8 @@ func (m MConnection) TrySend(chID byte, msg interface{}) bool {} for the channel with the given id byte `chID`. The message `msg` is serialized using the `tendermint/wire` submodule's `WriteBinary()` reflection routine. -`TrySend(chID, msg)` is a nonblocking call that returns false if the channel's -queue is full. +`TrySend(chID, msg)` is a nonblocking call that queues the message msg in the channel +with the given id byte chID if the queue is not full; otherwise it returns false immediately. `Send()` and `TrySend()` are also exposed for each `Peer`. @@ -103,14 +106,3 @@ for _, peer := range switch.Peers().List() { } } ``` - -### PexReactor/AddrBook - -A `PEXReactor` reactor implementation is provided to automate peer discovery. - -```go -book := p2p.NewAddrBook(addrBookFilePath) -pexReactor := p2p.NewPEXReactor(book) -... -switch := NewSwitch([]Reactor{pexReactor, myReactor, ...}) -``` diff --git a/docs/specification/new-spec/p2p/node.md b/docs/specification/new-spec/p2p/node.md index 9f9fc529..0ab8e508 100644 --- a/docs/specification/new-spec/p2p/node.md +++ b/docs/specification/new-spec/p2p/node.md @@ -39,8 +39,10 @@ A node checks its address book on startup and attempts to connect to peers from If it can't connect to any peers after some time, it falls back to the seeds to find more. Restarted full nodes can run the `blockchain` or `consensus` reactor protocols to sync up -to the latest state of the blockchain, assuming they aren't too far behind. -If they are too far behind, they may need to validate a recent `H` and `HASH` out-of-band again. +to the latest state of the blockchain from wherever they were last. +In a Proof-of-Stake context, if they are sufficiently far behind (greater than the length +of the unbonding period), they will need to validate a recent `H` and `HASH` out-of-band again +so they know they have synced the correct chain. ## Validator Node @@ -54,6 +56,7 @@ Validators that know and trust each other can accept incoming connections from o ## Sentry Node Sentry nodes are guardians of a validator node and provide it access to the rest of the network. +They should be well connected to other full nodes on the network. Sentry nodes may be dynamic, but should maintain persistent connections to some evolving random subset of each other. They should always expect to have direct incoming connections from the validator node and its backup/s. They do not report the validator node's address in the PEX. diff --git a/docs/specification/new-spec/p2p/peer.md b/docs/specification/new-spec/p2p/peer.md index 5281a702..a172764c 100644 --- a/docs/specification/new-spec/p2p/peer.md +++ b/docs/specification/new-spec/p2p/peer.md @@ -5,15 +5,11 @@ and how other peers are found. ## Peer Identity -Tendermint peers are expected to maintain long-term persistent identities in the form of a private key. -Each peer has an ID defined as `peer.ID == peer.PrivKey.Address()`, where `Address` uses the scheme defined in go-crypto. - -Peer ID's must come with some Proof-of-Work; that is, -they must satisfy `peer.PrivKey.Address() < target` for some difficulty target. -This ensures they are not too easy to generate. To begin, let `target == 2^240`. +Tendermint peers are expected to maintain long-term persistent identities in the form of a public key. +Each peer has an ID defined as `peer.ID == peer.PubKey.Address()`, where `Address` uses the scheme defined in go-crypto. A single peer ID can have multiple IP addresses associated with it. -For simplicity, we only keep track of the latest one. +TODO: define how to deal with this. When attempting to connect to a peer, we use the PeerURL: `@:`. We will attempt to connect to the peer at IP:PORT, and verify, @@ -22,7 +18,7 @@ corresponding to ``. This prevents man-in-the-middle attacks on the peer lay Peers can also be connected to without specifying an ID, ie. just `:`. In this case, the peer must be authenticated out-of-band of Tendermint, -for instance via VPN +for instance via VPN. ## Connections @@ -49,8 +45,8 @@ It goes as follows: - if we had the smaller ephemeral pubkey, use nonce1 for receiving, nonce2 for sending; else the opposite - all communications from now on are encrypted using the shared secret and the nonces, where each nonce -- we now have an encrypted channel, but still need to authenticate increments by 2 every time it is used +- we now have an encrypted channel, but still need to authenticate - generate a common challenge to sign: - SHA256 of the sorted (lowest first) and concatenated ephemeral pub keys - sign the common challenge with our persistent private key @@ -76,7 +72,7 @@ an existing peer. If so, we disconnect. We also check the peer's address and public key against an optional whitelist which can be managed through the ABCI app - -if the whitelist is enabled and the peer does not qualigy, the connection is +if the whitelist is enabled and the peer does not qualify, the connection is terminated. @@ -86,14 +82,14 @@ The Tendermint Version Handshake allows the peers to exchange their NodeInfo: ``` type NodeInfo struct { - PubKey crypto.PubKey `json:"pub_key"` - Moniker string `json:"moniker"` - Network string `json:"network"` - RemoteAddr string `json:"remote_addr"` - ListenAddr string `json:"listen_addr"` // accepting in - Version string `json:"version"` // major.minor.revision - Channels []int8 `json:"channels"` // active reactor channels - Other []string `json:"other"` // other application specific data + PubKey crypto.PubKey + Moniker string + Network string + RemoteAddr string + ListenAddr string + Version string + Channels []int8 + Other []string } ``` From c521f385a6fa7b4147e1edb55ad3ab7ca0df26cb Mon Sep 17 00:00:00 2001 From: Zach Date: Tue, 9 Jan 2018 20:35:47 +0000 Subject: [PATCH 042/124] add quick start guide (#1069) --- docs/examples/getting-started.md | 139 ++++++++++++++++++++++++ docs/examples/install_tendermint.sh | 32 ++++++ docs/examples/node1/config.toml | 15 +++ docs/examples/node1/genesis.json | 42 +++++++ docs/examples/node1/priv_validator.json | 15 +++ docs/examples/node2/config.toml | 15 +++ docs/examples/node2/genesis.json | 42 +++++++ docs/examples/node2/priv_validator.json | 15 +++ docs/examples/node3/config.toml | 15 +++ docs/examples/node3/genesis.json | 42 +++++++ docs/examples/node3/priv_validator.json | 15 +++ docs/examples/node4/config.toml | 15 +++ docs/examples/node4/genesis.json | 42 +++++++ docs/examples/node4/priv_validator.json | 15 +++ 14 files changed, 459 insertions(+) create mode 100644 docs/examples/getting-started.md create mode 100644 docs/examples/install_tendermint.sh create mode 100644 docs/examples/node1/config.toml create mode 100644 docs/examples/node1/genesis.json create mode 100644 docs/examples/node1/priv_validator.json create mode 100644 docs/examples/node2/config.toml create mode 100644 docs/examples/node2/genesis.json create mode 100644 docs/examples/node2/priv_validator.json create mode 100644 docs/examples/node3/config.toml create mode 100644 docs/examples/node3/genesis.json create mode 100644 docs/examples/node3/priv_validator.json create mode 100644 docs/examples/node4/config.toml create mode 100644 docs/examples/node4/genesis.json create mode 100644 docs/examples/node4/priv_validator.json diff --git a/docs/examples/getting-started.md b/docs/examples/getting-started.md new file mode 100644 index 00000000..d7447428 --- /dev/null +++ b/docs/examples/getting-started.md @@ -0,0 +1,139 @@ +# Tendermint + +## Overview + +This is a quick start guide. If you have a vague idea about how Tendermint works +and want to get started right away, continue. Otherwise, [review the documentation](http://tendermint.readthedocs.io/en/master/) + +## Install + +### Quick Install + +On a fresh Ubuntu 16.04 machine can be done with [this script](https://git.io/vNLfY), like so: + +``` +curl -L https://git.io/vNLfY | bash +source ~/.profile +``` + +WARNING: do not run the above on your local machine. + +The script is also used to facilitate cluster deployment below. + +### Manual Install + +Requires: +- `go` minimum version 1.9.2 +- `$GOPATH` set and `$GOPATH/bin` on your $PATH (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) + +To install Tendermint, run: + +``` +go get github.com/tendermint/tendermint +cd $GOPATH/src/github.com/tendermint/tendermint +make get_vendor_deps +make install +``` + +Confirm installation: + +``` +$ tendermint version +0.15.0-381fe19 +``` + +## Initialization + +Running: + +``` +tendermint init +``` + +will create the required files for a single, local node. + +These files are found in `$HOME/.tendermint`: + +``` +$ ls $HOME/.tendermint + +config.toml data genesis.json priv_validator.json +``` + +For a single, local node, no further configuration is required. +Configuring a cluster is covered further below. + +## Local Node + +Start tendermint with a simple in-process application: + +``` +tendermint node --proxy_app=dummy +``` + +and blocks will start to stream in: + +``` +I[01-06|01:45:15.592] Executed block module=state height=1 validTxs=0 invalidTxs=0 +I[01-06|01:45:15.624] Committed state module=state height=1 txs=0 appHash= +``` + +Check the status with: + +``` +curl -s localhost:46657/status +``` + +### Sending Transactions + +With the dummy app running, we can send transactions: + +``` +curl -s 'localhost:46657/broadcast_tx_commit?tx="abcd"' +``` + +and check that it worked with: + +``` +curl -s 'localhost:46657/abci_query?data="abcd"' +``` + +We can send transactions with a key:value store: + +``` +curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"' +``` + +and query the key: + +``` +curl -s 'localhost:46657/abci_query?data="name"' +``` + +where the value is returned in hex. + +## Cluster of Nodes + +First create four Ubuntu cloud machines. The following was testing on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. + +Then, `ssh` into each machine, and `curl` then execute [this script](https://git.io/vNLfY): + +``` +curl -L https://git.io/vNLfY | bash +source ~/.profile +``` + +This will install `go` and other dependencies, get the Tendermint source code, then compile the `tendermint` binary. + +Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence: + +``` +tendermint node --home ./node1 --proxy_app=dummy +tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656 +tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656 +tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656 +``` + +Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json` have come online). Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. + +Transactions can then be sent as covered in the single, local node example above. diff --git a/docs/examples/install_tendermint.sh b/docs/examples/install_tendermint.sh new file mode 100644 index 00000000..ca328dad --- /dev/null +++ b/docs/examples/install_tendermint.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# XXX: this script is meant to be used only on a fresh Ubuntu 16.04 instance +# and has only been tested on Digital Ocean + +# get and unpack golang +curl -O https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz +tar -xvf go1.9.2.linux-amd64.tar.gz + +apt install make + +## move go and add binary to path +mv go /usr/local +echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile + +## create the GOPATH directory, set GOPATH and put on PATH +mkdir goApps +echo "export GOPATH=/root/goApps" >> ~/.profile +echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile + +source ~/.profile + +## get the code and move into it +REPO=github.com/tendermint/tendermint +go get $REPO +cd $GOPATH/src/$REPO + +## build +git checkout v0.15.0 +make get_tools +make get_vendor_deps +make install \ No newline at end of file diff --git a/docs/examples/node1/config.toml b/docs/examples/node1/config.toml new file mode 100644 index 00000000..10bbf710 --- /dev/null +++ b/docs/examples/node1/config.toml @@ -0,0 +1,15 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "penguin" +fast_sync = true +db_backend = "leveldb" +log_level = "state:info,*:error" + +[rpc] +laddr = "tcp://0.0.0.0:46657" + +[p2p] +laddr = "tcp://0.0.0.0:46656" +seeds = "" diff --git a/docs/examples/node1/genesis.json b/docs/examples/node1/genesis.json new file mode 100644 index 00000000..78ff6ab3 --- /dev/null +++ b/docs/examples/node1/genesis.json @@ -0,0 +1,42 @@ +{ + "genesis_time":"0001-01-01T00:00:00Z", + "chain_id":"test-chain-wt7apy", + "validators":[ + { + "pub_key":{ + "type":"ed25519", + "data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + }, + "power":10, + "name":"node1" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + }, + "power":10, + "name":"node2" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + }, + "power":10, + "name":"node3" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + }, + "power":10, + "name":"node4" + } + ], + "app_hash":"" +} diff --git a/docs/examples/node1/priv_validator.json b/docs/examples/node1/priv_validator.json new file mode 100644 index 00000000..f6c5634a --- /dev/null +++ b/docs/examples/node1/priv_validator.json @@ -0,0 +1,15 @@ +{ + "address":"4DC2756029CE0D8F8C6C3E4C3CE6EE8C30AF352F", + "pub_key":{ + "type":"ed25519", + "data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + }, + "last_height":0, + "last_round":0, + "last_step":0, + "last_signature":null, + "priv_key":{ + "type":"ed25519", + "data":"4D3648E1D93C8703E436BFF814728B6BD270CFDFD686DF5385E8ACBEB7BE2D7DF08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + } +} diff --git a/docs/examples/node2/config.toml b/docs/examples/node2/config.toml new file mode 100644 index 00000000..10bbf710 --- /dev/null +++ b/docs/examples/node2/config.toml @@ -0,0 +1,15 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "penguin" +fast_sync = true +db_backend = "leveldb" +log_level = "state:info,*:error" + +[rpc] +laddr = "tcp://0.0.0.0:46657" + +[p2p] +laddr = "tcp://0.0.0.0:46656" +seeds = "" diff --git a/docs/examples/node2/genesis.json b/docs/examples/node2/genesis.json new file mode 100644 index 00000000..78ff6ab3 --- /dev/null +++ b/docs/examples/node2/genesis.json @@ -0,0 +1,42 @@ +{ + "genesis_time":"0001-01-01T00:00:00Z", + "chain_id":"test-chain-wt7apy", + "validators":[ + { + "pub_key":{ + "type":"ed25519", + "data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + }, + "power":10, + "name":"node1" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + }, + "power":10, + "name":"node2" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + }, + "power":10, + "name":"node3" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + }, + "power":10, + "name":"node4" + } + ], + "app_hash":"" +} diff --git a/docs/examples/node2/priv_validator.json b/docs/examples/node2/priv_validator.json new file mode 100644 index 00000000..7733196e --- /dev/null +++ b/docs/examples/node2/priv_validator.json @@ -0,0 +1,15 @@ +{ + "address": "DD6C63A762608A9DDD4A845657743777F63121D6", + "pub_key": { + "type": "ed25519", + "data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + }, + "last_height": 0, + "last_round": 0, + "last_step": 0, + "last_signature": null, + "priv_key": { + "type": "ed25519", + "data": "7B0DE666FF5E9B437D284BCE767F612381890C018B93B0A105D2E829A568DA6FA8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + } +} diff --git a/docs/examples/node3/config.toml b/docs/examples/node3/config.toml new file mode 100644 index 00000000..10bbf710 --- /dev/null +++ b/docs/examples/node3/config.toml @@ -0,0 +1,15 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "penguin" +fast_sync = true +db_backend = "leveldb" +log_level = "state:info,*:error" + +[rpc] +laddr = "tcp://0.0.0.0:46657" + +[p2p] +laddr = "tcp://0.0.0.0:46656" +seeds = "" diff --git a/docs/examples/node3/genesis.json b/docs/examples/node3/genesis.json new file mode 100644 index 00000000..78ff6ab3 --- /dev/null +++ b/docs/examples/node3/genesis.json @@ -0,0 +1,42 @@ +{ + "genesis_time":"0001-01-01T00:00:00Z", + "chain_id":"test-chain-wt7apy", + "validators":[ + { + "pub_key":{ + "type":"ed25519", + "data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + }, + "power":10, + "name":"node1" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + }, + "power":10, + "name":"node2" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + }, + "power":10, + "name":"node3" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + }, + "power":10, + "name":"node4" + } + ], + "app_hash":"" +} diff --git a/docs/examples/node3/priv_validator.json b/docs/examples/node3/priv_validator.json new file mode 100644 index 00000000..d570b127 --- /dev/null +++ b/docs/examples/node3/priv_validator.json @@ -0,0 +1,15 @@ +{ + "address": "6D6A1E313B407B5474106CA8759C976B777AB659", + "pub_key": { + "type": "ed25519", + "data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + }, + "last_height": 0, + "last_round": 0, + "last_step": 0, + "last_signature": null, + "priv_key": { + "type": "ed25519", + "data": "622432A370111A5C25CFE121E163FE709C9D5C95F551EDBD7A2C69A8545C9B76E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + } +} diff --git a/docs/examples/node4/config.toml b/docs/examples/node4/config.toml new file mode 100644 index 00000000..10bbf710 --- /dev/null +++ b/docs/examples/node4/config.toml @@ -0,0 +1,15 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "penguin" +fast_sync = true +db_backend = "leveldb" +log_level = "state:info,*:error" + +[rpc] +laddr = "tcp://0.0.0.0:46657" + +[p2p] +laddr = "tcp://0.0.0.0:46656" +seeds = "" diff --git a/docs/examples/node4/genesis.json b/docs/examples/node4/genesis.json new file mode 100644 index 00000000..78ff6ab3 --- /dev/null +++ b/docs/examples/node4/genesis.json @@ -0,0 +1,42 @@ +{ + "genesis_time":"0001-01-01T00:00:00Z", + "chain_id":"test-chain-wt7apy", + "validators":[ + { + "pub_key":{ + "type":"ed25519", + "data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" + }, + "power":10, + "name":"node1" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" + }, + "power":10, + "name":"node2" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" + }, + "power":10, + "name":"node3" + } + , + { + "pub_key":{ + "type":"ed25519", + "data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + }, + "power":10, + "name":"node4" + } + ], + "app_hash":"" +} diff --git a/docs/examples/node4/priv_validator.json b/docs/examples/node4/priv_validator.json new file mode 100644 index 00000000..1ea7831b --- /dev/null +++ b/docs/examples/node4/priv_validator.json @@ -0,0 +1,15 @@ +{ + "address": "829A9663611D3DD88A3D84EA0249679D650A0755", + "pub_key": { + "type": "ed25519", + "data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + }, + "last_height": 0, + "last_round": 0, + "last_step": 0, + "last_signature": null, + "priv_key": { + "type": "ed25519", + "data": "0A604D1C9AE94A50150BF39E603239092F9392E4773F4D8F4AC1D86E6438E89E2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" + } +} From 179d6062e4c385b67af414129d6c3cd9b09cb79a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 27 Dec 2017 14:16:49 -0600 Subject: [PATCH 043/124] try to connect through addrbook before requesting peers from seeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we only use seeds if we can’t connect to peers in the addrbook. Refs #864 --- node/node.go | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/node/node.go b/node/node.go index fde8e1e0..f8164955 100644 --- a/node/node.go +++ b/node/node.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "strings" + "time" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -379,19 +380,42 @@ func (n *Node) OnStart() error { return err } - // If seeds exist, add them to the address book and dial out - if n.config.P2P.Seeds != "" { - // dial out - seeds := strings.Split(n.config.P2P.Seeds, ",") - if err := n.DialSeeds(seeds); err != nil { - return err - } + err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() + if err != nil { + return err } // start tx indexer return n.indexerService.Start() } +func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() error { + if n.config.P2P.Seeds == "" { + return nil + } + + seeds := strings.Split(n.config.P2P.Seeds, ",") + + // prefer peers from address book + if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { + // give some time to PexReactor to connect us to other peers + const fallbackToSeedsAfterSec = 30 * time.Second + go func() { + time.Sleep(fallbackToSeedsAfterSec) + // fallback to dialing seeds if for some reason we can't connect to any + // peers + outbound, inbound, _ := n.sw.NumPeers() + if n.IsRunning() && outbound+inbound == 0 { + n.DialSeeds(seeds) + } + }() + return nil + } + + // add seeds to the address book and dial out + return n.DialSeeds(seeds) +} + // OnStop stops the Node. It implements cmn.Service. func (n *Node) OnStop() { n.BaseService.OnStop() From 28fc15028a16ed866354ca8f4be853effda14ca6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 28 Dec 2017 12:54:39 -0600 Subject: [PATCH 044/124] distinguish between seeds and manual peers in the config/flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - we only use seeds if we can’t connect to peers in the addrbook. - we always connect to nodes given in config/flags Refs #864 --- benchmarks/blockchain/localsync.sh | 2 +- cmd/tendermint/commands/run_node.go | 1 + config/config.go | 7 +++- config/toml.go | 2 + docs/deploy-testnets.rst | 6 +-- docs/specification/configuration.rst | 2 + docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 10 ++--- node/node.go | 41 +++++++++---------- p2p/pex_reactor.go | 2 +- p2p/switch.go | 28 ++++++------- rpc/client/localclient.go | 4 +- rpc/client/mock/client.go | 4 +- rpc/core/doc.go | 2 +- rpc/core/net.go | 16 ++++---- rpc/core/pipe.go | 2 +- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- test/p2p/README.md | 2 +- test/p2p/fast_sync/test_peer.sh | 6 +-- test/p2p/local_testnet_start.sh | 10 ++--- test/p2p/manual_peers.sh | 12 ++++++ .../{dial_seeds.sh => dial_manual_peers.sh} | 12 +++--- test/p2p/pex/test.sh | 4 +- test/p2p/pex/test_addrbook.sh | 8 ++-- ...ial_seeds.sh => test_dial_manual_peers.sh} | 14 +++---- test/p2p/seeds.sh | 12 ------ test/p2p/test.sh | 4 +- 28 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 test/p2p/manual_peers.sh rename test/p2p/pex/{dial_seeds.sh => dial_manual_peers.sh} (60%) rename test/p2p/pex/{test_dial_seeds.sh => test_dial_manual_peers.sh} (75%) delete mode 100644 test/p2p/seeds.sh diff --git a/benchmarks/blockchain/localsync.sh b/benchmarks/blockchain/localsync.sh index e181c565..18afaee8 100755 --- a/benchmarks/blockchain/localsync.sh +++ b/benchmarks/blockchain/localsync.sh @@ -51,7 +51,7 @@ tendermint node \ --proxy_app dummy \ --p2p.laddr tcp://127.0.0.1:56666 \ --rpc.laddr tcp://127.0.0.1:56667 \ - --p2p.seeds 127.0.0.1:56656 \ + --p2p.manual_peers 127.0.0.1:56656 \ --log_level error & # wait for node to start up so we only count time where we are actually syncing diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 0f37bb31..5b87a4b8 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -29,6 +29,7 @@ func AddNodeFlags(cmd *cobra.Command) { // 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 host:port seed nodes") + cmd.Flags().String("p2p.manual_peers", config.P2P.ManualPeers, "Comma delimited host:port manual peers") cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") diff --git a/config/config.go b/config/config.go index 5d4a8ef6..2d38ed91 100644 --- a/config/config.go +++ b/config/config.go @@ -170,7 +170,7 @@ type RPCConfig struct { // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` - // Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool + // Activate unsafe RPC commands like /dial_manual_peers and /unsafe_flush_mempool Unsafe bool `mapstructure:"unsafe"` } @@ -203,8 +203,13 @@ type P2PConfig struct { ListenAddress string `mapstructure:"laddr"` // Comma separated list of seed nodes to connect to + // We only use these if we can’t connect to peers in the addrbook Seeds string `mapstructure:"seeds"` + // Comma separated list of manual peers to connect to + // We always connect to these + ManualPeers string `mapstructure:"manual_peers"` + // Skip UPNP port forwarding SkipUPNP bool `mapstructure:"skip_upnp"` diff --git a/config/toml.go b/config/toml.go index 735f45c1..59fc46dc 100644 --- a/config/toml.go +++ b/config/toml.go @@ -42,6 +42,7 @@ laddr = "tcp://0.0.0.0:46657" [p2p] laddr = "tcp://0.0.0.0:46656" seeds = "" +manual_peers = "" ` func defaultConfig(moniker string) string { @@ -106,6 +107,7 @@ laddr = "tcp://0.0.0.0:36657" [p2p] laddr = "tcp://0.0.0.0:36656" seeds = "" +manual_peers = "" ` func testConfig(moniker string) (testConfig string) { diff --git a/docs/deploy-testnets.rst b/docs/deploy-testnets.rst index 89fa4b79..8c66c4b3 100644 --- a/docs/deploy-testnets.rst +++ b/docs/deploy-testnets.rst @@ -24,13 +24,13 @@ Here are the steps to setting up a testnet manually: ``tendermint gen_validator`` 4) Compile a list of public keys for each validator into a ``genesis.json`` file. -5) Run ``tendermint node --p2p.seeds=< seed addresses >`` on each node, - where ``< seed addresses >`` is a comma separated list of the IP:PORT +5) Run ``tendermint node --p2p.manual_peers=< peer addresses >`` on each node, + where ``< peer addresses >`` is a comma separated list of the IP:PORT combination for each node. The default port for Tendermint is ``46656``. Thus, if the IP addresses of your nodes were ``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command would look like: - ``tendermint node --p2p.seeds=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. + ``tendermint node --p2p.manual_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. After a few seconds, all the nodes should connect to eachother and start making blocks! For more information, see the Tendermint Networks section diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 74b41d09..3271cd59 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -49,6 +49,8 @@ The main config parameters are defined - ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` - ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: ``""`` +- ``p2p.manual_peers``: Comma delimited host:port manual peers. *Default*: + ``""`` - ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` - ``rpc.grpc_laddr``: GRPC listen address (BroadcastTx only). Port diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index 33173d19..f637c171 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,7 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ - http://localhost:46657/dial_seeds?seeds=_ + http://localhost:46657/dial_manual_peers?manual_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 9076230e..2ec5624e 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -270,14 +270,14 @@ For instance, :: - tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.manual_peers "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to +Alternatively, you can use the ``/dial_manual_peers`` endpoint of the RPC to specify peers for a running node to connect to: :: - curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds + curl --data-urlencode "manual_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_manual_peers Additionally, the peer-exchange protocol can be enabled using the ``--pex`` flag, though this feature is `still under @@ -290,7 +290,7 @@ Adding a Non-Validator Adding a non-validator is simple. Just copy the original ``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying seeds as necessary. If no seeds are specified, the node +node, specifying manual_peers as necessary. If no manual_peers are specified, the node won't make any blocks, because it's not a validator, and it won't hear about any blocks, because it's not connected to the other peer. @@ -363,7 +363,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.seeds`` or the ``/dial_seeds`` to get them to peer up. They +``--p2p.manual_peers`` or the ``/dial_manual_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/node/node.go b/node/node.go index f8164955..1970bb88 100644 --- a/node/node.go +++ b/node/node.go @@ -380,40 +380,44 @@ func (n *Node) OnStart() error { return err } - err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() - if err != nil { - return err + // Always connect to manual peers + if n.config.P2P.ManualPeers != "" { + err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.ManualPeers, ","), true) + if err != nil { + return err + } + } + + if n.config.P2P.Seeds != "" { + err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(strings.Split(n.config.P2P.Seeds, ",")) + if err != nil { + return err + } } // start tx indexer return n.indexerService.Start() } -func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() error { - if n.config.P2P.Seeds == "" { - return nil - } - - seeds := strings.Split(n.config.P2P.Seeds, ",") - +func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(seeds []string) error { // prefer peers from address book if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { - // give some time to PexReactor to connect us to other peers - const fallbackToSeedsAfterSec = 30 * time.Second + // give some time for PexReactor to connect us to other peers + const fallbackToSeedsAfter = 30 * time.Second go func() { - time.Sleep(fallbackToSeedsAfterSec) + time.Sleep(fallbackToSeedsAfter) // fallback to dialing seeds if for some reason we can't connect to any // peers outbound, inbound, _ := n.sw.NumPeers() if n.IsRunning() && outbound+inbound == 0 { - n.DialSeeds(seeds) + // TODO: ignore error? + n.sw.DialPeersAsync(n.addrBook, seeds, false) } }() return nil } - // add seeds to the address book and dial out - return n.DialSeeds(seeds) + return n.sw.DialPeersAsync(n.addrBook, seeds, false) } // OnStop stops the Node. It implements cmn.Service. @@ -599,11 +603,6 @@ func (n *Node) NodeInfo() *p2p.NodeInfo { return n.sw.NodeInfo() } -// DialSeeds dials the given seeds on the Switch. -func (n *Node) DialSeeds(seeds []string) error { - return n.sw.DialSeeds(n.addrBook, seeds) -} - //------------------------------------------------------------------------------ var ( diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 2bfe7dca..d4a8cbf1 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -100,7 +100,7 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialSeeds or when we + // Either it was added in DialManualPeers or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) diff --git a/p2p/switch.go b/p2p/switch.go index 76b01980..4deee63d 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -16,7 +16,7 @@ import ( const ( // wait a random amount of time from this interval - // before dialing seeds or reconnecting to help prevent DoS + // before dialing peers or reconnecting to help prevent DoS dialRandomizerIntervalMilliseconds = 3000 // repeatedly try to reconnect for a few minutes @@ -315,15 +315,15 @@ func (sw *Switch) startInitPeer(peer *peer) { } } -// DialSeeds dials a list of seeds asynchronously in random order. -func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { - netAddrs, errs := NewNetAddressStrings(seeds) +// DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). +func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { + netAddrs, errs := NewNetAddressStrings(peers) for _, err := range errs { - sw.Logger.Error("Error in seed's address", "err", err) + sw.Logger.Error("Error in peer's address", "err", err) } if addrBook != nil { - // add seeds to `addrBook` + // add manual peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { @@ -342,7 +342,12 @@ func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { go func(i int) { sw.randomSleep(0) j := perm[i] - sw.dialSeed(netAddrs[j]) + peer, err := sw.DialPeerWithAddress(netAddrs[j], persistent) + if err != nil { + sw.Logger.Error("Error dialing peer", "err", err) + } else { + sw.Logger.Info("Connected to peer", "peer", peer) + } }(i) } return nil @@ -354,15 +359,6 @@ func (sw *Switch) randomSleep(interval time.Duration) { time.Sleep(r + interval) } -func (sw *Switch) dialSeed(addr *NetAddress) { - peer, err := sw.DialPeerWithAddress(addr, true) - if err != nil { - sw.Logger.Error("Error dialing seed", "err", err) - } else { - sw.Logger.Info("Connected to seed", "peer", peer) - } -} - // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 71f25ef2..d30f7543 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,8 +84,8 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } -func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) +func (Local) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { + return core.UnsafeDialManualPeers(manual_peers) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index dc75e04c..84f6aa2d 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,8 +107,8 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } -func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) +func (c Client) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { + return core.UnsafeDialManualPeers(manual_peers) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index a72cec02..1a59459d 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,7 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ -/dial_seeds?seeds=_ +/dial_manual_peers?manual_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ /unsafe_start_cpu_profiler?filename=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index b3f1c7ce..845cda64 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -54,18 +54,18 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { +func UnsafeDialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - if len(seeds) == 0 { - return &ctypes.ResultDialSeeds{}, fmt.Errorf("No seeds provided") + if len(manual_peers) == 0 { + return &ctypes.ResultDialManualPeers{}, fmt.Errorf("No manual peers provided") } - // starts go routines to dial each seed after random delays - logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds) - err := p2pSwitch.DialSeeds(addrBook, seeds) + // starts go routines to dial each peer after random delays + logger.Info("DialManualPeers", "addrBook", addrBook, "manual_peers", manual_peers) + err := p2pSwitch.DialPeersAsync(addrBook, manual_peers, true) if err != nil { - return &ctypes.ResultDialSeeds{}, err + return &ctypes.ResultDialManualPeers{}, err } - return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil + return &ctypes.ResultDialManualPeers{"Dialing manual peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 927d7cca..6d67fd89 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -32,7 +32,7 @@ type P2P interface { NumPeers() (outbound, inbound, dialig int) NodeInfo() *p2p.NodeInfo IsListening() bool - DialSeeds(*p2p.AddrBook, []string) error + DialPeersAsync(*p2p.AddrBook, []string, bool) error } //---------------------------------------------- diff --git a/rpc/core/routes.go b/rpc/core/routes.go index fb5a1fd3..84d405ac 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,7 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") + Routes["dial_manual_peers"] = rpc.NewRPCFunc(UnsafeDialManualPeers, "manual_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index dae7c004..4d8e7946 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,7 +82,7 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } -type ResultDialSeeds struct { +type ResultDialManualPeers struct { Log string `json:"log"` } diff --git a/test/p2p/README.md b/test/p2p/README.md index e2a577cf..692b730b 100644 --- a/test/p2p/README.md +++ b/test/p2p/README.md @@ -38,7 +38,7 @@ for i in $(seq 1 4); do --name local_testnet_$i \ --entrypoint tendermint \ -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$i/core \ - tendermint_tester node --p2p.seeds 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy + tendermint_tester node --p2p.manual_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy done ``` diff --git a/test/p2p/fast_sync/test_peer.sh b/test/p2p/fast_sync/test_peer.sh index 8174be0e..ab5d517d 100644 --- a/test/p2p/fast_sync/test_peer.sh +++ b/test/p2p/fast_sync/test_peer.sh @@ -23,11 +23,11 @@ docker rm -vf local_testnet_$ID set -e # restart peer - should have an empty blockchain -SEEDS="$(test/p2p/ip.sh 1):46656" +MANUAL_PEERS="$(test/p2p/ip.sh 1):46656" for j in `seq 2 $N`; do - SEEDS="$SEEDS,$(test/p2p/ip.sh $j):46656" + MANUAL_PEERS="$MANUAL_PEERS,$(test/p2p/ip.sh $j):46656" done -bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.seeds $SEEDS --p2p.pex --rpc.unsafe" +bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.manual_peers $MANUAL_PEERS --p2p.pex --rpc.unsafe" # wait for peer to sync and check the app hash bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME fs_$ID "test/p2p/fast_sync/check_peer.sh $ID" diff --git a/test/p2p/local_testnet_start.sh b/test/p2p/local_testnet_start.sh index f70bdf04..c808c613 100644 --- a/test/p2p/local_testnet_start.sh +++ b/test/p2p/local_testnet_start.sh @@ -7,10 +7,10 @@ N=$3 APP_PROXY=$4 set +u -SEEDS=$5 -if [[ "$SEEDS" != "" ]]; then - echo "Seeds: $SEEDS" - SEEDS="--p2p.seeds $SEEDS" +MANUAL_PEERS=$5 +if [[ "$MANUAL_PEERS" != "" ]]; then + echo "ManualPeers: $MANUAL_PEERS" + MANUAL_PEERS="--p2p.manual_peers $MANUAL_PEERS" fi set -u @@ -20,5 +20,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" docker network create --driver bridge --subnet 172.57.0.0/16 "$NETWORK_NAME" for i in $(seq 1 "$N"); do - bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$SEEDS --p2p.pex --rpc.unsafe" + bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$MANUAL_PEERS --p2p.pex --rpc.unsafe" done diff --git a/test/p2p/manual_peers.sh b/test/p2p/manual_peers.sh new file mode 100644 index 00000000..90051b15 --- /dev/null +++ b/test/p2p/manual_peers.sh @@ -0,0 +1,12 @@ +#! /bin/bash +set -eu + +N=$1 + +cd "$GOPATH/src/github.com/tendermint/tendermint" + +manual_peers="$(test/p2p/ip.sh 1):46656" +for i in $(seq 2 $N); do + manual_peers="$manual_peers,$(test/p2p/ip.sh $i):46656" +done +echo "$manual_peers" diff --git a/test/p2p/pex/dial_seeds.sh b/test/p2p/pex/dial_manual_peers.sh similarity index 60% rename from test/p2p/pex/dial_seeds.sh rename to test/p2p/pex/dial_manual_peers.sh index 15c22af6..4ee79b86 100644 --- a/test/p2p/pex/dial_seeds.sh +++ b/test/p2p/pex/dial_manual_peers.sh @@ -19,13 +19,13 @@ for i in `seq 1 $N`; do done set -e -# seeds need quotes -seeds="\"$(test/p2p/ip.sh 1):46656\"" +# manual_peers need quotes +manual_peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - seeds="$seeds,\"$(test/p2p/ip.sh $i):46656\"" + manual_peers="$manual_peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $seeds +echo $manual_peers -echo $seeds +echo $manual_peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "seeds=[$seeds]" "$IP:46657/dial_seeds" +curl --data-urlencode "manual_peers=[$manual_peers]" "$IP:46657/dial_manual_peers" diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index d54d8135..2c42781d 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd $GOPATH/src/github.com/tendermint/tendermint echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP -echo "Test connecting via /dial_seeds" -bash test/p2p/pex/test_dial_seeds.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +echo "Test connecting via /dial_manual_peers" +bash test/p2p/pex/test_dial_manual_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index 35dcb89d..b215606d 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -9,7 +9,7 @@ PROXY_APP=$4 ID=1 echo "----------------------------------------------------------------------" -echo "Testing pex creates the addrbook and uses it if seeds are not provided" +echo "Testing pex creates the addrbook and uses it if manual_peers are not provided" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_addrbook_$ID" @@ -22,7 +22,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide seeds +# NOTE that we do not provide manual_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" echo "with the following addrbook:" @@ -35,7 +35,7 @@ echo "" bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N" echo "----------------------------------------------------------------------" -echo "Testing other peers connect to us if we have neither seeds nor the addrbook" +echo "Testing other peers connect to us if we have neither manual_peers nor the addrbook" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_no_addrbook_$ID" @@ -46,7 +46,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide seeds +# NOTE that we do not provide manual_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" # if the client runs forever, it means other peers have removed us from their books (which should not happen) diff --git a/test/p2p/pex/test_dial_seeds.sh b/test/p2p/pex/test_dial_manual_peers.sh similarity index 75% rename from test/p2p/pex/test_dial_seeds.sh rename to test/p2p/pex/test_dial_manual_peers.sh index ea72004d..ba3e1c4d 100644 --- a/test/p2p/pex/test_dial_seeds.sh +++ b/test/p2p/pex/test_dial_manual_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_seeds call" +echo "Testing full network connection using one /dial_manual_peers call" echo "(assuming peers are started with pex enabled)" # stop the existing testnet and remove local network @@ -21,16 +21,16 @@ set -e # start the testnet on a local network # NOTE we re-use the same network for all tests -SEEDS="" -bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $SEEDS +MANUAL_PEERS="" +bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $MANUAL_PEERS -# dial seeds from one node -CLIENT_NAME="dial_seeds" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_seeds.sh $N" +# dial manual_peers from one node +CLIENT_NAME="dial_manual_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_manual_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_seeds_basic" +CLIENT_NAME="dial_manual_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N" diff --git a/test/p2p/seeds.sh b/test/p2p/seeds.sh deleted file mode 100644 index 4bf866cb..00000000 --- a/test/p2p/seeds.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash -set -eu - -N=$1 - -cd "$GOPATH/src/github.com/tendermint/tendermint" - -seeds="$(test/p2p/ip.sh 1):46656" -for i in $(seq 2 $N); do - seeds="$seeds,$(test/p2p/ip.sh $i):46656" -done -echo "$seeds" diff --git a/test/p2p/test.sh b/test/p2p/test.sh index 6a5537b9..f348efe4 100644 --- a/test/p2p/test.sh +++ b/test/p2p/test.sh @@ -13,11 +13,11 @@ set +e bash test/p2p/local_testnet_stop.sh "$NETWORK_NAME" "$N" set -e -SEEDS=$(bash test/p2p/seeds.sh $N) +MANUAL_PEERS=$(bash test/p2p/manual_peers.sh $N) # 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" "$N" "$PROXY_APP" "$SEEDS" +bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$MANUAL_PEERS" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes From 37f86f9518cc80b1d2f759ce09f381ea96730e54 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 28 Dec 2017 18:27:36 -0600 Subject: [PATCH 045/124] update changelog [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda15071..1ceddd2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ BUG FIXES: - Graceful handling/recovery for apps that have non-determinism or fail to halt - Graceful handling/recovery for violations of safety, or liveness +## 0.16.0 (TBD) + +BREAKING CHANGES: +- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_manual_peers` +- [p2p] old `seeds` is now `manual_peers` (persistent peers to which TM will always connect to) +- [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) + ## 0.15.0 (December 29, 2017) BREAKING CHANGES: From e4897b7bdd033067d7d0fbbbd25c3f917d35d25a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 16:18:05 -0600 Subject: [PATCH 046/124] rename manual peers to persistent peers --- CHANGELOG.md | 4 ++-- benchmarks/blockchain/localsync.sh | 2 +- cmd/tendermint/commands/run_node.go | 2 +- config/config.go | 6 +++--- config/toml.go | 4 ++-- docs/deploy-testnets.rst | 4 ++-- docs/specification/configuration.rst | 2 +- docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 10 +++++----- node/node.go | 6 +++--- p2p/pex_reactor.go | 2 +- p2p/switch.go | 2 +- rpc/client/localclient.go | 4 ++-- rpc/client/mock/client.go | 4 ++-- rpc/core/doc.go | 2 +- rpc/core/net.go | 14 +++++++------- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- test/p2p/README.md | 2 +- test/p2p/fast_sync/test_peer.sh | 6 +++--- test/p2p/local_testnet_start.sh | 10 +++++----- test/p2p/manual_peers.sh | 6 +++--- test/p2p/pex/dial_manual_peers.sh | 16 ++++++++-------- test/p2p/pex/test.sh | 4 ++-- test/p2p/pex/test_addrbook.sh | 8 ++++---- test/p2p/pex/test_dial_manual_peers.sh | 14 +++++++------- test/p2p/test.sh | 4 ++-- 27 files changed, 72 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ceddd2e..b935a381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ BUG FIXES: ## 0.16.0 (TBD) BREAKING CHANGES: -- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_manual_peers` -- [p2p] old `seeds` is now `manual_peers` (persistent peers to which TM will always connect to) +- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_persistent_peers` +- [p2p] old `seeds` is now `persistent_peers` (persistent peers to which TM will always connect to) - [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) ## 0.15.0 (December 29, 2017) diff --git a/benchmarks/blockchain/localsync.sh b/benchmarks/blockchain/localsync.sh index 18afaee8..389464b6 100755 --- a/benchmarks/blockchain/localsync.sh +++ b/benchmarks/blockchain/localsync.sh @@ -51,7 +51,7 @@ tendermint node \ --proxy_app dummy \ --p2p.laddr tcp://127.0.0.1:56666 \ --rpc.laddr tcp://127.0.0.1:56667 \ - --p2p.manual_peers 127.0.0.1:56656 \ + --p2p.persistent_peers 127.0.0.1:56656 \ --log_level error & # wait for node to start up so we only count time where we are actually syncing diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 5b87a4b8..0eb7a425 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -29,7 +29,7 @@ func AddNodeFlags(cmd *cobra.Command) { // 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 host:port seed nodes") - cmd.Flags().String("p2p.manual_peers", config.P2P.ManualPeers, "Comma delimited host:port manual peers") + cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma delimited host:port persistent peers") cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") diff --git a/config/config.go b/config/config.go index 2d38ed91..04a37c68 100644 --- a/config/config.go +++ b/config/config.go @@ -170,7 +170,7 @@ type RPCConfig struct { // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` - // Activate unsafe RPC commands like /dial_manual_peers and /unsafe_flush_mempool + // Activate unsafe RPC commands like /dial_persistent_peers and /unsafe_flush_mempool Unsafe bool `mapstructure:"unsafe"` } @@ -206,9 +206,9 @@ type P2PConfig struct { // We only use these if we can’t connect to peers in the addrbook Seeds string `mapstructure:"seeds"` - // Comma separated list of manual peers to connect to + // Comma separated list of persistent peers to connect to // We always connect to these - ManualPeers string `mapstructure:"manual_peers"` + PersistentPeers string `mapstructure:"persistent_peers"` // Skip UPNP port forwarding SkipUPNP bool `mapstructure:"skip_upnp"` diff --git a/config/toml.go b/config/toml.go index 59fc46dc..e644445f 100644 --- a/config/toml.go +++ b/config/toml.go @@ -42,7 +42,7 @@ laddr = "tcp://0.0.0.0:46657" [p2p] laddr = "tcp://0.0.0.0:46656" seeds = "" -manual_peers = "" +persistent_peers = "" ` func defaultConfig(moniker string) string { @@ -107,7 +107,7 @@ laddr = "tcp://0.0.0.0:36657" [p2p] laddr = "tcp://0.0.0.0:36656" seeds = "" -manual_peers = "" +persistent_peers = "" ` func testConfig(moniker string) (testConfig string) { diff --git a/docs/deploy-testnets.rst b/docs/deploy-testnets.rst index 8c66c4b3..33c21457 100644 --- a/docs/deploy-testnets.rst +++ b/docs/deploy-testnets.rst @@ -24,13 +24,13 @@ Here are the steps to setting up a testnet manually: ``tendermint gen_validator`` 4) Compile a list of public keys for each validator into a ``genesis.json`` file. -5) Run ``tendermint node --p2p.manual_peers=< peer addresses >`` on each node, +5) Run ``tendermint node --p2p.persistent_peers=< peer addresses >`` on each node, where ``< peer addresses >`` is a comma separated list of the IP:PORT combination for each node. The default port for Tendermint is ``46656``. Thus, if the IP addresses of your nodes were ``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command would look like: - ``tendermint node --p2p.manual_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. + ``tendermint node --p2p.persistent_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. After a few seconds, all the nodes should connect to eachother and start making blocks! For more information, see the Tendermint Networks section diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 3271cd59..46e4059e 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -49,7 +49,7 @@ The main config parameters are defined - ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` - ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: ``""`` -- ``p2p.manual_peers``: Comma delimited host:port manual peers. *Default*: +- ``p2p.persistent_peers``: Comma delimited host:port persistent peers. *Default*: ``""`` - ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index f637c171..daafce11 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,7 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ - http://localhost:46657/dial_manual_peers?manual_peers=_ + http://localhost:46657/dial_persistent_peers?persistent_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 2ec5624e..2a04ac54 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -270,14 +270,14 @@ For instance, :: - tendermint node --p2p.manual_peers "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.persistent_peers "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_manual_peers`` endpoint of the RPC to +Alternatively, you can use the ``/dial_persistent_peers`` endpoint of the RPC to specify peers for a running node to connect to: :: - curl --data-urlencode "manual_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_manual_peers + curl --data-urlencode "persistent_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_persistent_peers Additionally, the peer-exchange protocol can be enabled using the ``--pex`` flag, though this feature is `still under @@ -290,7 +290,7 @@ Adding a Non-Validator Adding a non-validator is simple. Just copy the original ``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying manual_peers as necessary. If no manual_peers are specified, the node +node, specifying persistent_peers as necessary. If no persistent_peers are specified, the node won't make any blocks, because it's not a validator, and it won't hear about any blocks, because it's not connected to the other peer. @@ -363,7 +363,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.manual_peers`` or the ``/dial_manual_peers`` to get them to peer up. They +``--p2p.persistent_peers`` or the ``/dial_persistent_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/node/node.go b/node/node.go index 1970bb88..a4ea193b 100644 --- a/node/node.go +++ b/node/node.go @@ -380,9 +380,9 @@ func (n *Node) OnStart() error { return err } - // Always connect to manual peers - if n.config.P2P.ManualPeers != "" { - err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.ManualPeers, ","), true) + // Always connect to persistent peers + if n.config.P2P.PersistentPeers != "" { + err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.PersistentPeers, ","), true) if err != nil { return err } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index d4a8cbf1..64e7dafb 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -100,7 +100,7 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialManualPeers or when we + // Either it was added in DialPersistentPeers or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) diff --git a/p2p/switch.go b/p2p/switch.go index 4deee63d..1b449d7e 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -323,7 +323,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent } if addrBook != nil { - // add manual peers to `addrBook` + // add persistent peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index d30f7543..af91ac79 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,8 +84,8 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } -func (Local) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - return core.UnsafeDialManualPeers(manual_peers) +func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { + return core.UnsafeDialPersistentPeers(persistent_peers) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 84f6aa2d..469e80a6 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,8 +107,8 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } -func (c Client) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - return core.UnsafeDialManualPeers(manual_peers) +func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { + return core.UnsafeDialPersistentPeers(persistent_peers) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 1a59459d..030e5d61 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,7 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ -/dial_manual_peers?manual_peers=_ +/dial_persistent_peers?persistent_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ /unsafe_start_cpu_profiler?filename=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index 845cda64..c79c55de 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -54,18 +54,18 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { +func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - if len(manual_peers) == 0 { - return &ctypes.ResultDialManualPeers{}, fmt.Errorf("No manual peers provided") + if len(persistent_peers) == 0 { + return &ctypes.ResultDialPersistentPeers{}, fmt.Errorf("No persistent peers provided") } // starts go routines to dial each peer after random delays - logger.Info("DialManualPeers", "addrBook", addrBook, "manual_peers", manual_peers) - err := p2pSwitch.DialPeersAsync(addrBook, manual_peers, true) + logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) + err := p2pSwitch.DialPeersAsync(addrBook, persistent_peers, true) if err != nil { - return &ctypes.ResultDialManualPeers{}, err + return &ctypes.ResultDialPersistentPeers{}, err } - return &ctypes.ResultDialManualPeers{"Dialing manual peers in progress. See /net_info for details"}, nil + return &ctypes.ResultDialPersistentPeers{"Dialing persistent peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 84d405ac..0bf7af62 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,7 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_manual_peers"] = rpc.NewRPCFunc(UnsafeDialManualPeers, "manual_peers") + Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 4d8e7946..59c2aeea 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,7 +82,7 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } -type ResultDialManualPeers struct { +type ResultDialPersistentPeers struct { Log string `json:"log"` } diff --git a/test/p2p/README.md b/test/p2p/README.md index 692b730b..b68f7a81 100644 --- a/test/p2p/README.md +++ b/test/p2p/README.md @@ -38,7 +38,7 @@ for i in $(seq 1 4); do --name local_testnet_$i \ --entrypoint tendermint \ -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$i/core \ - tendermint_tester node --p2p.manual_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy + tendermint_tester node --p2p.persistent_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy done ``` diff --git a/test/p2p/fast_sync/test_peer.sh b/test/p2p/fast_sync/test_peer.sh index ab5d517d..1f341bf5 100644 --- a/test/p2p/fast_sync/test_peer.sh +++ b/test/p2p/fast_sync/test_peer.sh @@ -23,11 +23,11 @@ docker rm -vf local_testnet_$ID set -e # restart peer - should have an empty blockchain -MANUAL_PEERS="$(test/p2p/ip.sh 1):46656" +PERSISTENT_PEERS="$(test/p2p/ip.sh 1):46656" for j in `seq 2 $N`; do - MANUAL_PEERS="$MANUAL_PEERS,$(test/p2p/ip.sh $j):46656" + PERSISTENT_PEERS="$PERSISTENT_PEERS,$(test/p2p/ip.sh $j):46656" done -bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.manual_peers $MANUAL_PEERS --p2p.pex --rpc.unsafe" +bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $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 fs_$ID "test/p2p/fast_sync/check_peer.sh $ID" diff --git a/test/p2p/local_testnet_start.sh b/test/p2p/local_testnet_start.sh index c808c613..25b3c6d3 100644 --- a/test/p2p/local_testnet_start.sh +++ b/test/p2p/local_testnet_start.sh @@ -7,10 +7,10 @@ N=$3 APP_PROXY=$4 set +u -MANUAL_PEERS=$5 -if [[ "$MANUAL_PEERS" != "" ]]; then - echo "ManualPeers: $MANUAL_PEERS" - MANUAL_PEERS="--p2p.manual_peers $MANUAL_PEERS" +PERSISTENT_PEERS=$5 +if [[ "$PERSISTENT_PEERS" != "" ]]; then + echo "PersistentPeers: $PERSISTENT_PEERS" + PERSISTENT_PEERS="--p2p.persistent_peers $PERSISTENT_PEERS" fi set -u @@ -20,5 +20,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" docker network create --driver bridge --subnet 172.57.0.0/16 "$NETWORK_NAME" for i in $(seq 1 "$N"); do - bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$MANUAL_PEERS --p2p.pex --rpc.unsafe" + bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$PERSISTENT_PEERS --p2p.pex --rpc.unsafe" done diff --git a/test/p2p/manual_peers.sh b/test/p2p/manual_peers.sh index 90051b15..4ad55bc0 100644 --- a/test/p2p/manual_peers.sh +++ b/test/p2p/manual_peers.sh @@ -5,8 +5,8 @@ N=$1 cd "$GOPATH/src/github.com/tendermint/tendermint" -manual_peers="$(test/p2p/ip.sh 1):46656" +persistent_peers="$(test/p2p/ip.sh 1):46656" for i in $(seq 2 $N); do - manual_peers="$manual_peers,$(test/p2p/ip.sh $i):46656" + persistent_peers="$persistent_peers,$(test/p2p/ip.sh $i):46656" done -echo "$manual_peers" +echo "$persistent_peers" diff --git a/test/p2p/pex/dial_manual_peers.sh b/test/p2p/pex/dial_manual_peers.sh index 4ee79b86..95c1d6e9 100644 --- a/test/p2p/pex/dial_manual_peers.sh +++ b/test/p2p/pex/dial_manual_peers.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/bash set -u N=$1 @@ -11,7 +11,7 @@ for i in `seq 1 $N`; do curl -s $addr/status > /dev/null ERR=$? while [ "$ERR" != 0 ]; do - sleep 1 + sleep 1 curl -s $addr/status > /dev/null ERR=$? done @@ -19,13 +19,13 @@ for i in `seq 1 $N`; do done set -e -# manual_peers need quotes -manual_peers="\"$(test/p2p/ip.sh 1):46656\"" +# persistent_peers need quotes +persistent_peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - manual_peers="$manual_peers,\"$(test/p2p/ip.sh $i):46656\"" + persistent_peers="$persistent_peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $manual_peers +echo $persistent_peers -echo $manual_peers +echo $persistent_peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "manual_peers=[$manual_peers]" "$IP:46657/dial_manual_peers" +curl --data-urlencode "persistent_peers=[$persistent_peers]" "$IP:46657/dial_persistent_peers" diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 2c42781d..7cf6151d 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd $GOPATH/src/github.com/tendermint/tendermint echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP -echo "Test connecting via /dial_manual_peers" -bash test/p2p/pex/test_dial_manual_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +echo "Test connecting via /dial_persistent_peers" +bash test/p2p/pex/test_dial_persistent_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index b215606d..1dd26b17 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -9,7 +9,7 @@ PROXY_APP=$4 ID=1 echo "----------------------------------------------------------------------" -echo "Testing pex creates the addrbook and uses it if manual_peers are not provided" +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" @@ -22,7 +22,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide manual_peers +# NOTE that we do not provide persistent_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" echo "with the following addrbook:" @@ -35,7 +35,7 @@ echo "" bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N" echo "----------------------------------------------------------------------" -echo "Testing other peers connect to us if we have neither manual_peers nor the addrbook" +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" @@ -46,7 +46,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide manual_peers +# NOTE that we do not provide persistent_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" # if the client runs forever, it means other peers have removed us from their books (which should not happen) diff --git a/test/p2p/pex/test_dial_manual_peers.sh b/test/p2p/pex/test_dial_manual_peers.sh index ba3e1c4d..7dda62b1 100644 --- a/test/p2p/pex/test_dial_manual_peers.sh +++ b/test/p2p/pex/test_dial_manual_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_manual_peers call" +echo "Testing full network connection using one /dial_persistent_peers call" echo "(assuming peers are started with pex enabled)" # stop the existing testnet and remove local network @@ -21,16 +21,16 @@ set -e # start the testnet on a local network # NOTE we re-use the same network for all tests -MANUAL_PEERS="" -bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $MANUAL_PEERS +PERSISTENT_PEERS="" +bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $PERSISTENT_PEERS -# dial manual_peers from one node -CLIENT_NAME="dial_manual_peers" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_manual_peers.sh $N" +# dial persistent_peers from one node +CLIENT_NAME="dial_persistent_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_persistent_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_manual_peers_basic" +CLIENT_NAME="dial_persistent_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N" diff --git a/test/p2p/test.sh b/test/p2p/test.sh index f348efe4..c95f6973 100644 --- a/test/p2p/test.sh +++ b/test/p2p/test.sh @@ -13,11 +13,11 @@ set +e bash test/p2p/local_testnet_stop.sh "$NETWORK_NAME" "$N" set -e -MANUAL_PEERS=$(bash test/p2p/manual_peers.sh $N) +PERSISTENT_PEERS=$(bash test/p2p/persistent_peers.sh $N) # 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" "$N" "$PROXY_APP" "$MANUAL_PEERS" +bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$PERSISTENT_PEERS" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes From 1b455883d2a5acfd0705c2dddf5c31eff2b7a01b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 16:36:15 -0600 Subject: [PATCH 047/124] readd /dial_seeds --- CHANGELOG.md | 4 +++- docs/specification/rpc.rst | 1 + rpc/client/localclient.go | 4 ++++ rpc/client/mock/client.go | 4 ++++ rpc/core/doc.go | 1 + rpc/core/net.go | 19 +++++++++++++++---- rpc/core/routes.go | 1 + rpc/core/types/responses.go | 4 ++++ ...nual_peers.sh => dial_persistent_peers.sh} | 0 test/p2p/pex/test.sh | 6 +++--- ...peers.sh => test_dial_persistent_peers.sh} | 0 11 files changed, 36 insertions(+), 8 deletions(-) rename test/p2p/pex/{dial_manual_peers.sh => dial_persistent_peers.sh} (100%) rename test/p2p/pex/{test_dial_manual_peers.sh => test_dial_persistent_peers.sh} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b935a381..fdf3aea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,10 +28,12 @@ BUG FIXES: ## 0.16.0 (TBD) BREAKING CHANGES: -- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_persistent_peers` - [p2p] old `seeds` is now `persistent_peers` (persistent peers to which TM will always connect to) - [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) +FEATURES: +- [p2p] added new `/dial_persistent_peers` **unsafe** endpoint + ## 0.15.0 (December 29, 2017) BREAKING CHANGES: diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index daafce11..e273ce84 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,6 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ + http://localhost:46657/dial_seeds?seeds=_ http://localhost:46657/dial_persistent_peers?persistent_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index af91ac79..cc23b944 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,6 +84,10 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } +func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(seeds) +} + func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { return core.UnsafeDialPersistentPeers(persistent_peers) } diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 469e80a6..913812d6 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,6 +107,10 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } +func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(seeds) +} + func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { return core.UnsafeDialPersistentPeers(persistent_peers) } diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 030e5d61..a801cd0d 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,6 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ +/dial_seeds?seeds=_ /dial_persistent_peers?persistent_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index c79c55de..b528e1e3 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -1,8 +1,7 @@ package core import ( - "fmt" - + "github.com/pkg/errors" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -54,10 +53,22 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { +func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + if len(seeds) == 0 { + return &ctypes.ResultDialSeeds{}, errors.New("No seeds provided") + } + // starts go routines to dial each peer after random delays + logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds) + err := p2pSwitch.DialPeersAsync(addrBook, seeds, false) + if err != nil { + return &ctypes.ResultDialSeeds{}, err + } + return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil +} +func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { if len(persistent_peers) == 0 { - return &ctypes.ResultDialPersistentPeers{}, fmt.Errorf("No persistent peers provided") + return &ctypes.ResultDialPersistentPeers{}, errors.New("No persistent peers provided") } // starts go routines to dial each peer after random delays logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 0bf7af62..d00165e6 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,6 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API + Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 59c2aeea..c26defb7 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,6 +82,10 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } +type ResultDialSeeds struct { + Log string `json:"log"` +} + type ResultDialPersistentPeers struct { Log string `json:"log"` } diff --git a/test/p2p/pex/dial_manual_peers.sh b/test/p2p/pex/dial_persistent_peers.sh similarity index 100% rename from test/p2p/pex/dial_manual_peers.sh rename to test/p2p/pex/dial_persistent_peers.sh diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 7cf6151d..06d40c3e 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -6,10 +6,10 @@ NETWORK_NAME=$2 N=$3 PROXY_APP=$4 -cd $GOPATH/src/github.com/tendermint/tendermint +cd "$GOPATH/src/github.com/tendermint/tendermint" echo "Test reconnecting from the address book" -bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +bash test/p2p/pex/test_addrbook.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" echo "Test connecting via /dial_persistent_peers" -bash test/p2p/pex/test_dial_persistent_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +bash test/p2p/pex/test_dial_persistent_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" diff --git a/test/p2p/pex/test_dial_manual_peers.sh b/test/p2p/pex/test_dial_persistent_peers.sh similarity index 100% rename from test/p2p/pex/test_dial_manual_peers.sh rename to test/p2p/pex/test_dial_persistent_peers.sh From ef0493ddf3744249a47f20ab49f27fa974b3cf84 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 17:41:49 -0600 Subject: [PATCH 048/124] rewrite Peers section of Using Tendermint guide [ci skip] --- docs/using-tendermint.rst | 45 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 2a04ac54..20c5fa06 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -129,7 +129,7 @@ No Empty Blocks This much requested feature was implemented in version 0.10.3. While the default behaviour of ``tendermint`` is still to create blocks approximately once per second, it is possible to disable empty blocks or set a block creation interval. In the former case, blocks will be created when there are new transactions or when the AppHash changes. -To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, +To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, run tendermint with this additional flag: :: @@ -263,36 +263,47 @@ with the consensus protocol. Peers ~~~~~ -To connect to peers on start-up, specify them in the ``config.toml`` or -on the command line. +If you are starting Tendermint core for the first time, it will need some peers. + +You can provide a list of seeds (nodes, whole purpose is providing you with +peers) in the ``config.toml`` or on the command line. For instance, :: - tendermint node --p2p.persistent_peers "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_persistent_peers`` endpoint of the RPC to -specify peers for a running node to connect to: +Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to +specify seeds for a running node to connect to: :: - curl --data-urlencode "persistent_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_persistent_peers + curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds -Additionally, the peer-exchange protocol can be enabled using the -``--pex`` flag, though this feature is `still under -development `__. If -``--pex`` is enabled, peers will gossip about known peers and form a -more resilient network. +Note, if the peer-exchange protocol (PEX) is enabled (default), you should not +normally need seeds after the first start. Peers will be gossipping about known +peers and forming a network, storing peer addresses in the addrbook. + +If you want Tendermint to connect to specific set of addresses and maintain a +persistent connection with each, you can use the ``--p2p.persistent_peers`` +flag or the corresponding setting in the ``config.toml`` or the +``/dial_persistent_peers`` RPC endpoint to do it without stopping Tendermint +core instance. + +:: + + tendermint node --p2p.persistent_peers "10.11.12.13:46656,10.11.12.14:46656" + curl --data-urlencode "persistent_peers=[\"10.11.12.13:46656\",\"10.11.12.14:46656\"]" localhost:46657/dial_persistent_peers Adding a Non-Validator ~~~~~~~~~~~~~~~~~~~~~~ -Adding a non-validator is simple. Just copy the original -``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying persistent_peers as necessary. If no persistent_peers are specified, the node -won't make any blocks, because it's not a validator, and it won't hear -about any blocks, because it's not connected to the other peer. +Adding a non-validator is simple. Just copy the original ``genesis.json`` to +``~/.tendermint`` on the new machine and start the node, specifying seeds or +persistent peers. If no seeds or persistent peers are specified, the node won't +make any blocks, because it's not a validator, and it won't hear about any +blocks, because it's not connected to the other peers. Adding a Validator ~~~~~~~~~~~~~~~~~~ From 705d51aa423b617d293501b3888eb57367cbf16f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 17:09:09 -0600 Subject: [PATCH 049/124] move dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect into PEX reactor --- node/node.go | 32 ++----------------- p2p/pex_reactor.go | 19 +++++++++-- p2p/pex_reactor_test.go | 10 +++--- .../{manual_peers.sh => persistent_peers.sh} | 0 4 files changed, 24 insertions(+), 37 deletions(-) rename test/p2p/{manual_peers.sh => persistent_peers.sh} (100%) diff --git a/node/node.go b/node/node.go index a4ea193b..cf78a8aa 100644 --- a/node/node.go +++ b/node/node.go @@ -8,7 +8,6 @@ import ( "net" "net/http" "strings" - "time" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -256,7 +255,8 @@ func NewNode(config *cfg.Config, trustMetricStore = trust.NewTrustMetricStore(trustHistoryDB, trust.DefaultConfig()) trustMetricStore.SetLogger(p2pLogger) - pexReactor := p2p.NewPEXReactor(addrBook) + pexReactor := p2p.NewPEXReactor(addrBook, + &p2p.PEXReactorConfig{Seeds: strings.Split(config.P2P.Seeds, ",")}) pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } @@ -388,38 +388,10 @@ func (n *Node) OnStart() error { } } - if n.config.P2P.Seeds != "" { - err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(strings.Split(n.config.P2P.Seeds, ",")) - if err != nil { - return err - } - } - // start tx indexer return n.indexerService.Start() } -func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(seeds []string) error { - // prefer peers from address book - if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { - // give some time for PexReactor to connect us to other peers - const fallbackToSeedsAfter = 30 * time.Second - go func() { - time.Sleep(fallbackToSeedsAfter) - // fallback to dialing seeds if for some reason we can't connect to any - // peers - outbound, inbound, _ := n.sw.NumPeers() - if n.IsRunning() && outbound+inbound == 0 { - // TODO: ignore error? - n.sw.DialPeersAsync(n.addrBook, seeds, false) - } - }() - return nil - } - - return n.sw.DialPeersAsync(n.addrBook, seeds, false) -} - // OnStop stops the Node. It implements cmn.Service. func (n *Node) OnStop() { n.BaseService.OnStop() diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 64e7dafb..9e67a6c2 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -45,6 +45,7 @@ type PEXReactor struct { BaseReactor book *AddrBook + config *PEXReactorConfig ensurePeersPeriod time.Duration // tracks message count by peer, so we can prevent abuse @@ -52,10 +53,18 @@ type PEXReactor struct { maxMsgCountByPeer uint16 } +// PEXReactorConfig holds reactor specific configuration data. +type PEXReactorConfig struct { + // Seeds is a list of addresses reactor may use if it can't connect to peers + // in the addrbook. + Seeds []string +} + // NewPEXReactor creates new PEX reactor. -func NewPEXReactor(b *AddrBook) *PEXReactor { +func NewPEXReactor(b *AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ book: b, + config: config, ensurePeersPeriod: defaultEnsurePeersPeriod, msgCountByPeer: cmn.NewCMap(), maxMsgCountByPeer: defaultMaxMsgCountByPeer, @@ -238,7 +247,7 @@ func (r *PEXReactor) ensurePeersRoutine() { // placeholder. It should not be the case that an address becomes old/vetted // upon a single successful connection. func (r *PEXReactor) ensurePeers() { - numOutPeers, _, numDialing := r.Switch.NumPeers() + numOutPeers, numInPeers, numDialing := r.Switch.NumPeers() numToDial := minNumOutboundPeers - (numOutPeers + numDialing) r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial) if numToDial <= 0 { @@ -291,6 +300,12 @@ func (r *PEXReactor) ensurePeers() { r.RequestPEX(peer) } } + + // If we can't connect to any known address, fallback to dialing seeds + if numOutPeers+numInPeers+numDialing == 0 { + r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) + r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) + } } func (r *PEXReactor) flushMsgCountByPeer() { diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a14f0eb2..b37f6641 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -24,7 +24,7 @@ func TestPEXReactorBasic(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) assert.NotNil(r) @@ -40,7 +40,7 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) size := book.Size() @@ -76,7 +76,7 @@ func TestPEXReactorRunning(t *testing.T) { switches[i] = makeSwitch(config, i, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { sw.SetLogger(log.TestingLogger().With("switch", i)) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) r.SetEnsurePeersPeriod(250 * time.Millisecond) sw.AddReactor("pex", r) @@ -141,7 +141,7 @@ func TestPEXReactorReceive(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", false) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) peer := createRandomPeer(false) @@ -166,7 +166,7 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) r.SetMaxMsgCountByPeer(5) diff --git a/test/p2p/manual_peers.sh b/test/p2p/persistent_peers.sh similarity index 100% rename from test/p2p/manual_peers.sh rename to test/p2p/persistent_peers.sh From 075ae1e301c5957dd1954a769e3e09eb270852cc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 18:29:29 -0600 Subject: [PATCH 050/124] minimal test for dialing seeds in pex reactor --- p2p/pex_reactor.go | 13 ++++++----- p2p/pex_reactor_test.go | 49 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 9e67a6c2..ce2bba8b 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -293,16 +293,17 @@ func (r *PEXReactor) ensurePeers() { // If we need more addresses, pick a random peer and ask for more. if r.book.NeedMoreAddrs() { - if peers := r.Switch.Peers().List(); len(peers) > 0 { - i := rand.Int() % len(peers) // nolint: gas - peer := peers[i] - r.Logger.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer) + peers := r.Switch.Peers().List() + peersCount := len(peers) + if peersCount > 0 { + peer := peers[rand.Int()%peersCount] // nolint: gas + r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer) r.RequestPEX(peer) } } - // If we can't connect to any known address, fallback to dialing seeds - if numOutPeers+numInPeers+numDialing == 0 { + // If we are not connected to nor dialing anybody, fallback to dialing seeds. + if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index b37f6641..fc2fe687 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -107,6 +107,7 @@ func TestPEXReactorRunning(t *testing.T) { func assertSomePeersWithTimeout(t *testing.T, switches []*Switch, checkPeriod, timeout time.Duration) { ticker := time.NewTicker(checkPeriod) + remaining := timeout for { select { case <-ticker.C: @@ -118,16 +119,21 @@ func assertSomePeersWithTimeout(t *testing.T, switches []*Switch, checkPeriod, t allGood = false } } + remaining -= checkPeriod + if remaining < 0 { + remaining = 0 + } if allGood { return } - case <-time.After(timeout): + case <-time.After(remaining): numPeersStr := "" for i, s := range switches { outbound, inbound, _ := s.NumPeers() 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) + return } } } @@ -180,6 +186,47 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ListenAddr)) } +func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) // nolint: errcheck + + book := NewAddrBook(dir+"addrbook.json", false) + book.SetLogger(log.TestingLogger()) + + // 1. create seed + seed := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + sw.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{}) + r.SetLogger(log.TestingLogger()) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + return sw + }) + seed.AddListener(NewDefaultListener("tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger())) + err = seed.Start() + require.Nil(t, err) + defer seed.Stop() + + // 2. create usual peer + sw := makeSwitch(config, 1, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + sw.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{Seeds: []string{seed.NodeInfo().ListenAddr}}) + r.SetLogger(log.TestingLogger()) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + return sw + }) + err = sw.Start() + require.Nil(t, err) + defer sw.Stop() + + // 3. check that peer at least connects to seed + assertSomePeersWithTimeout(t, []*Switch{sw}, 10*time.Millisecond, 10*time.Second) +} + func createRoutableAddr() (addr string, netAddr *NetAddress) { for { addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) From 657ad214cb95489207246755fc65b8fc44b5f384 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Wed, 10 Jan 2018 15:57:39 +0000 Subject: [PATCH 051/124] p2p tests: put priv_val in right place --- test/p2p/data/mach1/core/{ => config}/priv_validator.json | 0 test/p2p/data/mach2/core/{ => config}/priv_validator.json | 0 test/p2p/data/mach3/core/{ => config}/priv_validator.json | 0 test/p2p/data/mach4/core/{ => config}/priv_validator.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/p2p/data/mach1/core/{ => config}/priv_validator.json (100%) rename test/p2p/data/mach2/core/{ => config}/priv_validator.json (100%) rename test/p2p/data/mach3/core/{ => config}/priv_validator.json (100%) rename test/p2p/data/mach4/core/{ => config}/priv_validator.json (100%) diff --git a/test/p2p/data/mach1/core/priv_validator.json b/test/p2p/data/mach1/core/config/priv_validator.json similarity index 100% rename from test/p2p/data/mach1/core/priv_validator.json rename to test/p2p/data/mach1/core/config/priv_validator.json diff --git a/test/p2p/data/mach2/core/priv_validator.json b/test/p2p/data/mach2/core/config/priv_validator.json similarity index 100% rename from test/p2p/data/mach2/core/priv_validator.json rename to test/p2p/data/mach2/core/config/priv_validator.json diff --git a/test/p2p/data/mach3/core/priv_validator.json b/test/p2p/data/mach3/core/config/priv_validator.json similarity index 100% rename from test/p2p/data/mach3/core/priv_validator.json rename to test/p2p/data/mach3/core/config/priv_validator.json diff --git a/test/p2p/data/mach4/core/priv_validator.json b/test/p2p/data/mach4/core/config/priv_validator.json similarity index 100% rename from test/p2p/data/mach4/core/priv_validator.json rename to test/p2p/data/mach4/core/config/priv_validator.json From c79ba3c3494cb9b07bf30275af48b35b9b4acd8b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 12:18:50 -0600 Subject: [PATCH 052/124] document the maximum supported voting power due to overflow [ci skip] Refs #1075 --- docs/using-tendermint.rst | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 9076230e..086a2570 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -127,10 +127,14 @@ Some fields from the config file can be overwritten with flags. No Empty Blocks --------------- -This much requested feature was implemented in version 0.10.3. While the default behaviour of ``tendermint`` is still to create blocks approximately once per second, it is possible to disable empty blocks or set a block creation interval. In the former case, blocks will be created when there are new transactions or when the AppHash changes. +This much requested feature was implemented in version 0.10.3. While the +default behaviour of ``tendermint`` is still to create blocks approximately +once per second, it is possible to disable empty blocks or set a block creation +interval. In the former case, blocks will be created when there are new +transactions or when the AppHash changes. -To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, -run tendermint with this additional flag: +To configure tendermint to not produce empty blocks unless there are +transactions or the app hash changes, run tendermint with this additional flag: :: @@ -246,13 +250,17 @@ conflicting messages. Note also that the ``pub_key`` (the public key) in the ``priv_validator.json`` is also present in the ``genesis.json``. -The genesis file contains the list of public keys which may participate -in the consensus, and their corresponding voting power. Greater than 2/3 -of the voting power must be active (ie. the corresponding private keys -must be producing signatures) for the consensus to make progress. In our -case, the genesis file contains the public key of our -``priv_validator.json``, so a tendermint node started with the default -root directory will be able to make new blocks, as we've already seen. +The genesis file contains the list of public keys which may participate in the +consensus, and their corresponding voting power. Greater than 2/3 of the voting +power must be active (ie. the corresponding private keys must be producing +signatures) for the consensus to make progress. In our case, the genesis file +contains the public key of our ``priv_validator.json``, so a tendermint node +started with the default root directory will be able to make progress. Voting +power uses an `int64` but must be positive, thus the range is: 0 through +9223372036854775807. Because of how the current proposer selection algorithm works, +we do not recommend having voting powers greater than 10^12 (ie. 1 trillion) +(see `Proposals section of Byzantine Consensus Algorithm +<./specification/byzantine-consensus-algorithm.html#proposals>`__ for details). If we want to add more nodes to the network, we have two choices: we can add a new validator node, who will also participate in the consensus by From b40aa91b4101622455ef22cf1eaf31d0ef835cb6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 12 Jan 2018 12:47:54 -0600 Subject: [PATCH 053/124] document event subscriptions Refs #1100 --- rpc/core/events.go | 75 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/rpc/core/events.go b/rpc/core/events.go index 538134b0..9353ace6 100644 --- a/rpc/core/events.go +++ b/rpc/core/events.go @@ -13,11 +13,57 @@ import ( // Subscribe for events via WebSocket. // +// To tell which events you want, you need to provide a query. query is a +// string, which has a form: "condition AND condition ..." (no OR at the +// moment). condition has a form: "key operation operand". key is a string with +// a restricted set of possible symbols ( \t\n\r\\()"'=>< are not allowed). +// operation can be "=", "<", "<=", ">", ">=", "CONTAINS". operand can be a +// string (escaped with single quotes), number, date or time. +// +// Examples: +// tm.event = 'NewBlock' # new blocks +// tm.event = 'CompleteProposal' # node got a complete proposal +// tm.event = 'Tx' AND tx.hash = 'XYZ' # single transaction +// tm.event = 'Tx' AND tx.height = 5 # all txs of the fifth block +// tx.height = 5 # all txs of the fifth block +// +// Tendermint provides a few predefined keys: tm.event, tx.hash and tx.height. +// Note for transactions, you can define additional keys by providing tags with +// DeliverTx response. +// +// DeliverTx{ +// Tags: []*KVPair{ +// "agent.name": "K", +// } +// } +// +// tm.event = 'Tx' AND agent.name = 'K' +// tm.event = 'Tx' AND account.created_at >= TIME 2013-05-03T14:45:00Z +// tm.event = 'Tx' AND contract.sign_date = DATE 2017-01-01 +// tm.event = 'Tx' AND account.owner CONTAINS 'Igor' +// +// See list of all possible events here +// https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants +// +// For complete query syntax, check out +// https://godoc.org/github.com/tendermint/tmlibs/pubsub/query. +// // ```go +// import "github.com/tendermint/tmlibs/pubsub/query" // import "github.com/tendermint/tendermint/types" // // client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket") -// result, err := client.AddListenerForEvent(types.EventStringNewBlock()) +// ctx, cancel := context.WithTimeout(context.Background(), timeout) +// defer cancel() +// query := query.MustParse("tm.event = 'Tx' AND tx.height = 3") +// txs := make(chan interface{}) +// err := client.Subscribe(ctx, "test-client", query, txs) +// +// go func() { +// for e := range txs { +// fmt.Println("got ", e.(types.TMEventData).Unwrap().(types.EventDataTx)) +// } +// }() // ``` // // > The above command returns JSON structured like this: @@ -35,7 +81,7 @@ import ( // // | Parameter | Type | Default | Required | Description | // |-----------+--------+---------+----------+-------------| -// | event | string | "" | true | Event name | +// | query | string | "" | true | Query | // // func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscribe, error) { @@ -68,10 +114,8 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri // Unsubscribe from events via WebSocket. // // ```go -// import 'github.com/tendermint/tendermint/types' -// // client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket") -// result, err := client.RemoveListenerForEvent(types.EventStringNewBlock()) +// err := client.Unsubscribe("test-client", query) // ``` // // > The above command returns JSON structured like this: @@ -89,7 +133,7 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri // // | Parameter | Type | Default | Required | Description | // |-----------+--------+---------+----------+-------------| -// | event | string | "" | true | Event name | +// | query | string | "" | true | Query | // // func Unsubscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultUnsubscribe, error) { @@ -106,6 +150,25 @@ func Unsubscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultUnsub return &ctypes.ResultUnsubscribe{}, nil } +// Unsubscribe from all events via WebSocket. +// +// ```go +// client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket") +// err := client.UnsubscribeAll("test-client") +// ``` +// +// > The above command returns JSON structured like this: +// +// ```json +// { +// "error": "", +// "result": {}, +// "id": "", +// "jsonrpc": "2.0" +// } +// ``` +// +// func UnsubscribeAll(wsCtx rpctypes.WSRPCContext) (*ctypes.ResultUnsubscribe, error) { addr := wsCtx.GetRemoteAddr() logger.Info("Unsubscribe from all", "remote", addr) From e2b3b5b58ccec6efff6385ddd64806d71e1fb3f6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 14:50:32 -0500 Subject: [PATCH 054/124] dial_persistent_peers -> dial_peers with persistent option --- docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 8 ++++---- p2p/switch.go | 2 +- rpc/client/localclient.go | 4 ++-- rpc/client/mock/client.go | 4 ++-- rpc/core/net.go | 14 +++++++------- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index e714cb35..7df394d7 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -112,7 +112,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ http://localhost:46657/dial_seeds?seeds=_ - http://localhost:46657/dial_persistent_peers?persistent_peers=_ + http://localhost:46657/dial_peers?peers=_&persistent=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 1b60ea5f..735fce65 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -287,7 +287,7 @@ specify seeds for a running node to connect to: :: - curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds + curl 'localhost:46657/dial_seeds?seeds=\["1.2.3.4:46656","5.6.7.8:46656"\]' Note, if the peer-exchange protocol (PEX) is enabled (default), you should not normally need seeds after the first start. Peers will be gossipping about known @@ -296,13 +296,13 @@ peers and forming a network, storing peer addresses in the addrbook. If you want Tendermint to connect to specific set of addresses and maintain a persistent connection with each, you can use the ``--p2p.persistent_peers`` flag or the corresponding setting in the ``config.toml`` or the -``/dial_persistent_peers`` RPC endpoint to do it without stopping Tendermint +``/dial_peers`` RPC endpoint to do it without stopping Tendermint core instance. :: tendermint node --p2p.persistent_peers "10.11.12.13:46656,10.11.12.14:46656" - curl --data-urlencode "persistent_peers=[\"10.11.12.13:46656\",\"10.11.12.14:46656\"]" localhost:46657/dial_persistent_peers + curl 'localhost:46657/dial_peers?persistent=true&peers=\["1.2.3.4:46656","5.6.7.8:46656"\]' Adding a Non-Validator ~~~~~~~~~~~~~~~~~~~~~~ @@ -382,7 +382,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint/config`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.persistent_peers`` or the ``/dial_persistent_peers`` to get them to peer up. They +``--p2p.persistent_peers`` or the ``/dial_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/p2p/switch.go b/p2p/switch.go index 1b449d7e..95c632aa 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -323,7 +323,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent } if addrBook != nil { - // add persistent peers to `addrBook` + // add peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index cc23b944..5e0573a1 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -88,8 +88,8 @@ func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return core.UnsafeDialSeeds(seeds) } -func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - return core.UnsafeDialPersistentPeers(persistent_peers) +func (Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(peers, persistent) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 913812d6..6c472898 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -111,8 +111,8 @@ func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return core.UnsafeDialSeeds(seeds) } -func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - return core.UnsafeDialPersistentPeers(persistent_peers) +func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(peers, persistent) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/net.go b/rpc/core/net.go index b528e1e3..af52c81c 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -66,17 +66,17 @@ func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil } -func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - if len(persistent_peers) == 0 { - return &ctypes.ResultDialPersistentPeers{}, errors.New("No persistent peers provided") +func UnsafeDialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + if len(peers) == 0 { + return &ctypes.ResultDialPeers{}, errors.New("No peers provided") } // starts go routines to dial each peer after random delays - logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) - err := p2pSwitch.DialPeersAsync(addrBook, persistent_peers, true) + logger.Info("DialPeers", "addrBook", addrBook, "peers", peers, "persistent", persistent) + err := p2pSwitch.DialPeersAsync(addrBook, peers, persistent) if err != nil { - return &ctypes.ResultDialPersistentPeers{}, err + return &ctypes.ResultDialPeers{}, err } - return &ctypes.ResultDialPersistentPeers{"Dialing persistent peers in progress. See /net_info for details"}, nil + return &ctypes.ResultDialPeers{"Dialing peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index d00165e6..3ea7aa08 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -39,7 +39,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") - Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") + Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index c26defb7..bffdd028 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -86,7 +86,7 @@ type ResultDialSeeds struct { Log string `json:"log"` } -type ResultDialPersistentPeers struct { +type ResultDialPeers struct { Log string `json:"log"` } From c1e167e330907f5639c32b4099d393ae3492f868 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 15:11:13 -0500 Subject: [PATCH 055/124] note in trust metric test --- p2p/trust/metric_test.go | 2 ++ p2p/trust/ticker.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index 00219a19..69c9f8f2 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -68,7 +68,9 @@ func TestTrustMetricStopPause(t *testing.T) { tt.NextTick() tm.Pause() + // could be 1 or 2 because Pause and NextTick race first := tm.Copy().numIntervals + // Allow more time to pass and check the intervals are unchanged tt.NextTick() tt.NextTick() diff --git a/p2p/trust/ticker.go b/p2p/trust/ticker.go index bce9fcc2..3f0f3091 100644 --- a/p2p/trust/ticker.go +++ b/p2p/trust/ticker.go @@ -24,7 +24,7 @@ type TestTicker struct { // NewTestTicker returns our ticker used within test routines func NewTestTicker() *TestTicker { - c := make(chan time.Time, 1) + c := make(chan time.Time) return &TestTicker{ C: c, } From 9670519a211f81474dd3a1b42a0d210e0aab54e4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 15:38:40 -0500 Subject: [PATCH 056/124] remove PoW from ID --- node/node.go | 8 ++----- p2p/key.go | 64 ++++++++++++++++++++----------------------------- p2p/key_test.go | 31 ++++++++++++------------ 3 files changed, 44 insertions(+), 59 deletions(-) diff --git a/node/node.go b/node/node.go index 7fecf710..bd3bd1ca 100644 --- a/node/node.go +++ b/node/node.go @@ -368,11 +368,8 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: both the loading function and the target - // will need to be configurable - difficulty := uint8(16) // number of leading 0s in bitstring - target := p2p.MakePoWTarget(difficulty) - nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile(), target) + // TODO: the loading function will need to be configurable + nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err } @@ -381,7 +378,6 @@ func (n *Node) OnStart() error { // Start the switch n.sw.SetNodeInfo(n.makeNodeInfo(nodeKey.PubKey())) n.sw.SetNodeKey(nodeKey) - n.sw.SetPeerIDTarget(target) err = n.sw.Start() if err != nil { return err diff --git a/p2p/key.go b/p2p/key.go index d0031458..6883c86e 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/big" crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" @@ -42,37 +41,20 @@ func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { return bytes.Compare(nodeKey.id(), target) < 0 } -// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath, -// and checks that the corresponding ID is less than the target. -// If the file does not exist, it generates and saves a new NodeKey -// with ID less than target. -func LoadOrGenNodeKey(filePath string, target []byte) (*NodeKey, error) { +// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. +// If the file does not exist, it generates and saves a new NodeKey. +func LoadOrGenNodeKey(filePath string) (*NodeKey, error) { if cmn.FileExists(filePath) { nodeKey, err := loadNodeKey(filePath) if err != nil { return nil, err } - if !nodeKey.SatisfiesTarget(target) { - return nil, fmt.Errorf("Loaded ID (%s) does not satisfy target (%X)", nodeKey.ID(), target) - } return nodeKey, nil } else { - return genNodeKey(filePath, target) + return genNodeKey(filePath) } } -// MakePoWTarget returns a 20 byte target byte array. -func MakePoWTarget(difficulty uint8) []byte { - zeroPrefixLen := (int(difficulty) / 8) - prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) - mod := (difficulty % 8) - if mod > 0 { - nonZeroPrefix := byte(1 << (8 - mod)) - prefix = append(prefix, nonZeroPrefix) - } - return append(prefix, bytes.Repeat([]byte{255}, 20-len(prefix))...) -} - func loadNodeKey(filePath string) (*NodeKey, error) { jsonBytes, err := ioutil.ReadFile(filePath) if err != nil { @@ -86,8 +68,8 @@ func loadNodeKey(filePath string) (*NodeKey, error) { return nodeKey, nil } -func genNodeKey(filePath string, target []byte) (*NodeKey, error) { - privKey := genPrivKeyEd25519PoW(target).Wrap() +func genNodeKey(filePath string) (*NodeKey, error) { + privKey := crypto.GenPrivKeyEd25519().Wrap() nodeKey := &NodeKey{ PrivKey: privKey, } @@ -103,20 +85,26 @@ func genNodeKey(filePath string, target []byte) (*NodeKey, error) { return nodeKey, nil } -// generate key with address satisfying the difficult target -func genPrivKeyEd25519PoW(target []byte) crypto.PrivKeyEd25519 { - secret := crypto.CRandBytes(32) - var privKey crypto.PrivKeyEd25519 - for i := 0; ; i++ { - privKey = crypto.GenPrivKeyEd25519FromSecret(secret) - if bytes.Compare(privKey.PubKey().Address(), target) < 0 { - break - } - z := new(big.Int) - z.SetBytes(secret) - z = z.Add(z, big.NewInt(1)) - secret = z.Bytes() +//------------------------------------------------------------------------------ +// MakePoWTarget returns the big-endian encoding of 2^(targetBits - difficulty) - 1. +// It can be used as a Proof of Work target. +// NOTE: targetBits must be a multiple of 8 and difficulty must be less than targetBits. +func MakePoWTarget(difficulty, targetBits uint) []byte { + if targetBits%8 != 0 { + panic(fmt.Sprintf("targetBits (%d) not a multiple of 8", targetBits)) } - return privKey + if difficulty >= targetBits { + panic(fmt.Sprintf("difficulty (%d) >= targetBits (%d)", difficulty, targetBits)) + } + targetBytes := targetBits / 8 + zeroPrefixLen := (int(difficulty) / 8) + prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) + mod := (difficulty % 8) + if mod > 0 { + nonZeroPrefix := byte(1<<(8-mod) - 1) + prefix = append(prefix, nonZeroPrefix) + } + tailLen := int(targetBytes) - len(prefix) + return append(prefix, bytes.Repeat([]byte{0xFF}, tailLen)...) } diff --git a/p2p/key_test.go b/p2p/key_test.go index ef885e55..c2e1f3e0 100644 --- a/p2p/key_test.go +++ b/p2p/key_test.go @@ -13,37 +13,38 @@ import ( func TestLoadOrGenNodeKey(t *testing.T) { filePath := filepath.Join(os.TempDir(), cmn.RandStr(12)+"_peer_id.json") - target := MakePoWTarget(2) - nodeKey, err := LoadOrGenNodeKey(filePath, target) + nodeKey, err := LoadOrGenNodeKey(filePath) assert.Nil(t, err) - nodeKey2, err := LoadOrGenNodeKey(filePath, target) + nodeKey2, err := LoadOrGenNodeKey(filePath) assert.Nil(t, err) assert.Equal(t, nodeKey, nodeKey2) } -func repeatBytes(val byte, n int) []byte { - return bytes.Repeat([]byte{val}, n) +//---------------------------------------------------------- + +func padBytes(bz []byte, targetBytes int) []byte { + return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...) } func TestPoWTarget(t *testing.T) { + targetBytes := 20 cases := []struct { - difficulty uint8 + difficulty uint target []byte }{ - {0, bytes.Repeat([]byte{255}, 20)}, - {1, append([]byte{128}, repeatBytes(255, 19)...)}, - {8, append([]byte{0}, repeatBytes(255, 19)...)}, - {9, append([]byte{0, 128}, repeatBytes(255, 18)...)}, - {10, append([]byte{0, 64}, repeatBytes(255, 18)...)}, - {16, append([]byte{0, 0}, repeatBytes(255, 18)...)}, - {17, append([]byte{0, 0, 128}, repeatBytes(255, 17)...)}, + {0, padBytes([]byte{}, targetBytes)}, + {1, padBytes([]byte{127}, targetBytes)}, + {8, padBytes([]byte{0}, targetBytes)}, + {9, padBytes([]byte{0, 127}, targetBytes)}, + {10, padBytes([]byte{0, 63}, targetBytes)}, + {16, padBytes([]byte{0, 0}, targetBytes)}, + {17, padBytes([]byte{0, 0, 127}, targetBytes)}, } for _, c := range cases { - assert.Equal(t, MakePoWTarget(c.difficulty), c.target) + assert.Equal(t, MakePoWTarget(c.difficulty, 20*8), c.target) } - } From e4d52401cfa08355bfbe9abf422d4d795d363dcf Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:06:31 -0500 Subject: [PATCH 057/124] some fixes from review --- p2p/addrbook_test.go | 2 +- p2p/key.go | 5 +++++ p2p/netaddress.go | 9 +++++---- p2p/netaddress_test.go | 7 +++++++ p2p/pex_reactor.go | 9 +++++---- p2p/switch.go | 3 ++- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index 07ab3474..bcef569d 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -190,7 +190,7 @@ func randIPv4Address(t *testing.T) *NetAddress { ) port := rand.Intn(65535-1) + 1 addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) - addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) // TODO + addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index 6883c86e..5a4ab48f 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -11,8 +11,13 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) +// ID is a hex-encoded crypto.Address type ID string +// IDByteLength is the length of a crypto.Address. Currently only 20. +// TODO: support other length addresses ? +const IDByteLength = 20 + //------------------------------------------------------------------------------ // Persistent peer ID // TODO: encrypt on disk diff --git a/p2p/netaddress.go b/p2p/netaddress.go index d804e348..c11d1442 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -50,8 +50,8 @@ func NewNetAddress(id ID, addr net.Addr) *NetAddress { } // NewNetAddressString returns a new NetAddress using the provided -// address in the form of "IP:Port". Also resolves the host if host -// is not an IP. +// address in the form of "ID@IP:Port", where the ID is optional. +// Also resolves the host if host is not an IP. func NewNetAddressString(addr string) (*NetAddress, error) { addr = removeProtocolIfDefined(addr) @@ -63,8 +63,9 @@ func NewNetAddressString(addr string) (*NetAddress, error) { if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addr)) } - if len(idBytes) != 20 { - return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be 20 hex-encoded bytes", len(idBytes)) + if len(idBytes) != IDByteLength { + return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be %d hex-encoded bytes", + addr, len(idBytes), IDByteLength) } id, addr = ID(idStr), spl[1] } diff --git a/p2p/netaddress_test.go b/p2p/netaddress_test.go index 0aa45423..6c1930a2 100644 --- a/p2p/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -48,6 +48,13 @@ func TestNewNetAddressString(t *testing.T) { {"tcp://this-isnot-hex@127.0.0.1:8080", "", false}, {"tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, {"tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, + + {"tcp://@127.0.0.1:8080", "", false}, + {"tcp://@", "", false}, + {"", "", false}, + {"@", "", false}, + {" @", "", false}, + {" @ ", "", false}, } for _, tc := range testCases { diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 6fd0285d..fc1cbdd9 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -109,19 +109,20 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialPersistentPeers or when we + // Either it was added in DialPeersAsync or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) } - } else { // For inbound connections, the peer is its own source - addr, err := NewNetAddressString(p.NodeInfo().ListenAddr) - addr.ID = p.ID() // TODO: handle in NewNetAddress func + } else { + addrStr := fmt.Sprintf("%s@%s", p.ID(), p.NodeInfo().ListenAddr) + addr, err := NewNetAddressString(addrStr) if err != nil { // peer gave us a bad ListenAddr. TODO: punish r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) return } + // For inbound connections, the peer is its own source r.book.AddAddress(addr, addr) } } diff --git a/p2p/switch.go b/p2p/switch.go index cd353ca8..eca52e98 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -94,6 +94,7 @@ type Switch struct { var ( ErrSwitchDuplicatePeer = errors.New("Duplicate peer") + ErrSwitchConnectToSelf = errors.New("Connect to self") ) func NewSwitch(config *cfg.P2PConfig) *Switch { @@ -241,7 +242,7 @@ func (sw *Switch) OnStop() { func (sw *Switch) addPeer(peer *peer) error { // Avoid self if sw.nodeKey.ID() == peer.ID() { - return errors.New("Ignoring connection from self") + return ErrSwitchConnectToSelf } // Filter peer against white list From 53a5498fc5022a72b27a38e9583160356d584cd1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:14:28 -0500 Subject: [PATCH 058/124] more fixes from review --- config/config.go | 2 +- config/toml.go | 3 +++ node/node.go | 2 +- p2p/addrbook.go | 2 +- p2p/addrbook_test.go | 5 +++-- p2p/key.go | 12 +++++------- p2p/netaddress.go | 7 ++++++- p2p/peer.go | 2 +- p2p/pex_reactor.go | 16 ++++------------ p2p/switch.go | 10 +--------- p2p/types.go | 12 ++++++++++++ 11 files changed, 38 insertions(+), 35 deletions(-) diff --git a/config/config.go b/config/config.go index 645b9e10..f93b7924 100644 --- a/config/config.go +++ b/config/config.go @@ -95,7 +95,7 @@ type BaseConfig struct { PrivValidator string `mapstructure:"priv_validator_file"` // A JSON file containing the private key to use for p2p authenticated encryption - NodeKey string `mapstructure:"node_key"` + NodeKey string `mapstructure:"node_key_file"` // A custom human readable name for this node Moniker string `mapstructure:"moniker"` diff --git a/config/toml.go b/config/toml.go index f979af95..e7bd2610 100644 --- a/config/toml.go +++ b/config/toml.go @@ -87,6 +87,9 @@ genesis_file = "{{ .BaseConfig.Genesis }}" # Path to the JSON file containing the private key to use as a validator in the consensus protocol priv_validator_file = "{{ .BaseConfig.PrivValidator }}" +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "{{ .BaseConfig.NodeKey}}" + # Mechanism to connect to the ABCI application: socket | grpc abci = "{{ .BaseConfig.ABCI }}" diff --git a/node/node.go b/node/node.go index bd3bd1ca..5535b1e1 100644 --- a/node/node.go +++ b/node/node.go @@ -368,7 +368,7 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: the loading function will need to be configurable + // TODO: pass in like priv_val nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 6ccec61f..8826ff1e 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -283,7 +283,7 @@ func (a *AddrBook) RemoveAddress(addr *NetAddress) { if ka == nil { return } - a.Logger.Info("Remove address from book", "addr", ka.Addr) + a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID) a.removeFromAllBuckets(ka) } diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index bcef569d..00051ae1 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -189,8 +189,9 @@ func randIPv4Address(t *testing.T) *NetAddress { rand.Intn(255), ) port := rand.Intn(65535-1) + 1 - addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) - addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) + id := ID(hex.EncodeToString(cmn.RandBytes(IDByteLength))) + idAddr := IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) + addr, err := NewNetAddressString(idAddr) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index 5a4ab48f..ea0f0b07 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -30,11 +30,7 @@ type NodeKey struct { // ID returns the peer's canonical ID - the hash of its public key. func (nodeKey *NodeKey) ID() ID { - return ID(hex.EncodeToString(nodeKey.id())) -} - -func (nodeKey *NodeKey) id() []byte { - return nodeKey.PrivKey.PubKey().Address() + return PubKeyToID(nodeKey.PubKey()) } // PubKey returns the peer's PubKey @@ -42,8 +38,10 @@ func (nodeKey *NodeKey) PubKey() crypto.PubKey { return nodeKey.PrivKey.PubKey() } -func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { - return bytes.Compare(nodeKey.id(), target) < 0 +// PubKeyToID returns the ID corresponding to the given PubKey. +// It's the hex-encoding of the pubKey.Address(). +func PubKeyToID(pubKey crypto.PubKey) ID { + return ID(hex.EncodeToString(pubKey.Address())) } // LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. diff --git a/p2p/netaddress.go b/p2p/netaddress.go index c11d1442..fed5e59d 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -26,6 +26,11 @@ type NetAddress struct { str string } +// IDAddressString returns id@hostPort. +func IDAddressString(id ID, hostPort string) string { + return fmt.Sprintf("%s@%s", id, hostPort) +} + // NewNetAddress returns a new NetAddress using the provided TCP // address. When testing, other net.Addr (except TCP) will result in // using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will @@ -136,7 +141,7 @@ func (na *NetAddress) String() string { if na.str == "" { addrStr := na.DialString() if na.ID != "" { - addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) + addrStr = IDAddressString(na.ID, addrStr) } na.str = addrStr } diff --git a/p2p/peer.go b/p2p/peer.go index ecf2efce..ff8d8d0e 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -304,7 +304,7 @@ func (p *peer) Set(key string, data interface{}) { p.Data.Set(key, data) } -// Key returns the peer's ID - the hex encoded hash of its pubkey. +// ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { return ID(hex.EncodeToString(p.PubKey().Address())) } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index fc1cbdd9..0816a6e9 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -115,14 +115,8 @@ func (r *PEXReactor) AddPeer(p Peer) { r.RequestPEX(p) } } else { - addrStr := fmt.Sprintf("%s@%s", p.ID(), p.NodeInfo().ListenAddr) - addr, err := NewNetAddressString(addrStr) - if err != nil { - // peer gave us a bad ListenAddr. TODO: punish - r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) - return - } // For inbound connections, the peer is its own source + addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } } @@ -261,7 +255,7 @@ func (r *PEXReactor) ensurePeers() { // NOTE: range here is [10, 90]. Too high ? newBias := cmn.MinInt(numOutPeers, 8)*10 + 10 - toDial := make(map[string]*NetAddress) + toDial := make(map[ID]*NetAddress) // Try maxAttempts times to pick numToDial addresses to dial maxAttempts := numToDial * 3 for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ { @@ -269,19 +263,17 @@ func (r *PEXReactor) ensurePeers() { if try == nil { continue } - if _, selected := toDial[string(try.ID)]; selected { + if _, selected := toDial[try.ID]; selected { continue } if dialling := r.Switch.IsDialing(try.ID); dialling { continue } - // XXX: Should probably use pubkey as peer key ... - // TODO: use the ID correctly if connected := r.Switch.Peers().Has(try.ID); connected { continue } r.Logger.Info("Will dial address", "addr", try) - toDial[string(try.ID)] = try + toDial[try.ID] = try } // Dial picked addresses diff --git a/p2p/switch.go b/p2p/switch.go index eca52e98..da1aa552 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -84,7 +84,6 @@ type Switch struct { dialing *cmn.CMap nodeInfo *NodeInfo // our node info nodeKey *NodeKey // our node privkey - peerIDTarget []byte filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -194,12 +193,6 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { } } -// SetPeerIDTarget sets the target for incoming peer ID's - -// the ID must be less than the target -func (sw *Switch) SetPeerIDTarget(target []byte) { - sw.peerIDTarget = target -} - // OnStart implements BaseService. It starts all the reactors, peers, and listeners. func (sw *Switch) OnStart() error { // Start reactors @@ -460,8 +453,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { // If no success after all that, it stops trying, and leaves it // to the PEX/Addrbook to find the peer again func (sw *Switch) reconnectToPeer(peer Peer) { - netAddr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) - netAddr.ID = peer.ID() // TODO: handle above + netAddr := peer.NodeInfo().NetAddress() start := time.Now() sw.Logger.Info("Reconnecting to peer", "peer", peer) for i := 0; i < reconnectAttempts; i++ { diff --git a/p2p/types.go b/p2p/types.go index 86013290..287c88b0 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -11,6 +11,8 @@ import ( const maxNodeInfoSize = 10240 // 10Kb +// NodeInfo is the basic node information exchanged +// between two peers during the Tendermint P2P handshake type NodeInfo struct { PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey Moniker string `json:"moniker"` // arbitrary moniker @@ -54,6 +56,16 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } +func (info *NodeInfo) NetAddress() *NetAddress { + id := PubKeyToID(info.PubKey) + addr := info.ListenAddr + netAddr, err := NewNetAddressString(IDAddressString(id, addr)) + if err != nil { + panic(err) // everything should be well formed by now + } + return netAddr +} + func (info *NodeInfo) ListenHost() string { host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas return host From 7667e119731271d745814b34379844a5a6fe7a29 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:50:03 -0500 Subject: [PATCH 059/124] remove RemoteAddr from NodeInfo --- p2p/peer.go | 2 -- p2p/peer_set_test.go | 1 - p2p/peer_test.go | 9 +++++---- p2p/pex_reactor.go | 24 +++++++++--------------- p2p/pex_reactor_test.go | 5 ++--- p2p/switch.go | 1 - p2p/types.go | 7 +++++-- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index ff8d8d0e..2f5dff78 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -206,8 +206,6 @@ func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return errors.Wrap(err, "Error removing deadline") } - peerNodeInfo.RemoteAddr = p.Addr().String() - p.nodeInfo = peerNodeInfo return nil } diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 609c4900..e50bb384 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -15,7 +15,6 @@ import ( func randPeer() *peer { return &peer{ nodeInfo: &NodeInfo{ - RemoteAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 78a4e2f5..aafe8d7a 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -142,10 +142,11 @@ func (p *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } err = peer.HandshakeTimeout(&NodeInfo{ - PubKey: p.PrivKey.PubKey(), - Moniker: "remote_peer", - Network: "testing", - Version: "123.123.123", + PubKey: p.PrivKey.PubKey(), + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + ListenAddr: l.Addr().String(), }, 1*time.Second) if err != nil { golog.Fatalf("Failed to perform handshake: %+v", err) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 0816a6e9..5aa63db7 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -129,17 +129,11 @@ func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { - srcAddrStr := src.NodeInfo().RemoteAddr - srcAddr, err := NewNetAddressString(srcAddrStr) - if err != nil { - // this should never happen. TODO: cancel conn - r.Logger.Error("Error in Receive: invalid peer address", "addr", srcAddrStr, "err", err) - return - } + srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddrStr) - if r.ReachedMaxMsgCountForPeer(srcAddrStr) { - r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddrStr) + r.IncrementMsgCountForPeer(srcAddr.ID) + if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { + r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) // TODO remove src from peers? return } @@ -192,19 +186,19 @@ func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { // ReachedMaxMsgCountForPeer returns true if we received too many // messages from peer with address `addr`. // NOTE: assumes the value in the CMap is non-nil -func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool { - return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer +func (r *PEXReactor) ReachedMaxMsgCountForPeer(peerID ID) bool { + return r.msgCountByPeer.Get(string(peerID)).(uint16) >= r.maxMsgCountByPeer } // Increment or initialize the msg count for the peer in the CMap -func (r *PEXReactor) IncrementMsgCountForPeer(addr string) { +func (r *PEXReactor) IncrementMsgCountForPeer(peerID ID) { var count uint16 - countI := r.msgCountByPeer.Get(addr) + countI := r.msgCountByPeer.Get(string(peerID)) if countI != nil { count = countI.(uint16) } count++ - r.msgCountByPeer.Set(addr, count) + r.msgCountByPeer.Set(string(peerID), count) } // Ensures that sufficient peers are connected. (continuous) diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 2fb616fb..296b1886 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -184,7 +184,7 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { r.Receive(PexChannel, peer, msg) } - assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ListenAddr)) + assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ID())) } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { @@ -243,8 +243,7 @@ func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ nodeInfo: &NodeInfo{ - ListenAddr: addr, - RemoteAddr: netAddr.String(), + ListenAddr: netAddr.String(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, diff --git a/p2p/switch.go b/p2p/switch.go index da1aa552..48464914 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -615,7 +615,6 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, - RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodeKey(nodeKey) diff --git a/p2p/types.go b/p2p/types.go index 287c88b0..2aec521b 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -17,7 +17,6 @@ type NodeInfo struct { PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey Moniker string `json:"moniker"` // arbitrary moniker Network string `json:"network"` // network/chain ID - RemoteAddr string `json:"remote_addr"` // address for the connection ListenAddr string `json:"listen_addr"` // accepting incoming Version string `json:"version"` // major.minor.revision Other []string `json:"other"` // other application specific data @@ -56,6 +55,10 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } +func (info *NodeInfo) ID() ID { + return PubKeyToID(info.PubKey) +} + func (info *NodeInfo) NetAddress() *NetAddress { id := PubKeyToID(info.PubKey) addr := info.ListenAddr @@ -81,7 +84,7 @@ func (info *NodeInfo) ListenPort() int { } func (info NodeInfo) String() string { - return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [remote %v, listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.RemoteAddr, info.ListenAddr, info.Version, info.Other) + return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.ListenAddr, info.Version, info.Other) } func splitVersion(version string) (string, string, string, error) { From 452d10f368a626a47bf4f5e9736a6b1166c252e0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 17:25:51 -0500 Subject: [PATCH 060/124] cleanup switch --- p2p/addrbook.go | 13 + p2p/base_reactor.go | 35 +++ p2p/peer.go | 2 + p2p/switch.go | 562 ++++++++++++++++++-------------------------- p2p/test_util.go | 111 +++++++++ p2p/util.go | 15 -- 6 files changed, 384 insertions(+), 354 deletions(-) create mode 100644 p2p/base_reactor.go create mode 100644 p2p/test_util.go delete mode 100644 p2p/util.go diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 8826ff1e..7591c307 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -5,6 +5,7 @@ package p2p import ( + "crypto/sha256" "encoding/binary" "encoding/json" "fmt" @@ -867,3 +868,15 @@ func (ka *knownAddress) isBad() bool { return false } + +//----------------------------------------------------------------------------- + +// doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. +func doubleSha256(b []byte) []byte { + hasher := sha256.New() + hasher.Write(b) // nolint: errcheck, gas + sum := hasher.Sum(nil) + hasher.Reset() + hasher.Write(sum) // nolint: errcheck, gas + return hasher.Sum(nil) +} diff --git a/p2p/base_reactor.go b/p2p/base_reactor.go new file mode 100644 index 00000000..e8107d73 --- /dev/null +++ b/p2p/base_reactor.go @@ -0,0 +1,35 @@ +package p2p + +import cmn "github.com/tendermint/tmlibs/common" + +type Reactor interface { + cmn.Service // Start, Stop + + SetSwitch(*Switch) + GetChannels() []*ChannelDescriptor + AddPeer(peer Peer) + RemovePeer(peer Peer, reason interface{}) + Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil +} + +//-------------------------------------- + +type BaseReactor struct { + cmn.BaseService // Provides Start, Stop, .Quit + Switch *Switch +} + +func NewBaseReactor(name string, impl Reactor) *BaseReactor { + return &BaseReactor{ + BaseService: *cmn.NewBaseService(nil, name, impl), + Switch: nil, + } +} + +func (br *BaseReactor) SetSwitch(sw *Switch) { + br.Switch = sw +} +func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil } +func (_ *BaseReactor) AddPeer(peer Peer) {} +func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} +func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} diff --git a/p2p/peer.go b/p2p/peer.go index 2f5dff78..1f5192d5 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -99,6 +99,8 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { + // TODO: issue PoW challenge + return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) } diff --git a/p2p/switch.go b/p2p/switch.go index 48464914..4ece1a58 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -12,7 +12,6 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" ) const ( @@ -31,46 +30,17 @@ const ( reconnectBackOffBaseSeconds = 3 ) -type Reactor interface { - cmn.Service // Start, Stop - - SetSwitch(*Switch) - GetChannels() []*ChannelDescriptor - AddPeer(peer Peer) - RemovePeer(peer Peer, reason interface{}) - Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil -} - -//-------------------------------------- - -type BaseReactor struct { - cmn.BaseService // Provides Start, Stop, .Quit - Switch *Switch -} - -func NewBaseReactor(name string, impl Reactor) *BaseReactor { - return &BaseReactor{ - BaseService: *cmn.NewBaseService(nil, name, impl), - Switch: nil, - } -} - -func (br *BaseReactor) SetSwitch(sw *Switch) { - br.Switch = sw -} -func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil } -func (_ *BaseReactor) AddPeer(peer Peer) {} -func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} -func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} +var ( + ErrSwitchDuplicatePeer = errors.New("Duplicate peer") + ErrSwitchConnectToSelf = errors.New("Connect to self") +) //----------------------------------------------------------------------------- -/* -The `Switch` handles peer connections and exposes an API to receive incoming messages -on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one -or more `Channels`. So while sending outgoing messages is typically performed on the peer, -incoming messages are received on the reactor. -*/ +// `Switch` handles peer connections and exposes an API to receive incoming messages +// on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one +// or more `Channels`. So while sending outgoing messages is typically performed on the peer, +// incoming messages are received on the reactor. type Switch struct { cmn.BaseService @@ -91,11 +61,6 @@ type Switch struct { rng *rand.Rand // seed for randomizing dial times and orders } -var ( - ErrSwitchDuplicatePeer = errors.New("Duplicate peer") - ErrSwitchConnectToSelf = errors.New("Connect to self") -) - func NewSwitch(config *cfg.P2PConfig) *Switch { sw := &Switch{ config: config, @@ -122,6 +87,9 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { return sw } +//--------------------------------------------------------------------- +// Switch setup + // AddReactor adds the given reactor to the switch. // NOTE: Not goroutine safe. func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { @@ -193,6 +161,9 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { } } +//--------------------------------------------------------------------- +// Service start/stop + // OnStart implements BaseService. It starts all the reactors, peers, and listeners. func (sw *Switch) OnStart() error { // Start reactors @@ -228,176 +199,26 @@ func (sw *Switch) OnStop() { } } -// addPeer checks the given peer's validity, performs a handshake, and adds the -// peer to the switch and to all registered reactors. -// NOTE: This performs a blocking handshake before the peer is added. -// NOTE: If error is returned, caller is responsible for calling peer.CloseConn() -func (sw *Switch) addPeer(peer *peer) error { - // Avoid self - if sw.nodeKey.ID() == peer.ID() { - return ErrSwitchConnectToSelf - } +//--------------------------------------------------------------------- +// Peers - // Filter peer against white list - if err := sw.FilterConnByAddr(peer.Addr()); err != nil { - return err - } - if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { - return err - } - - if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second)); err != nil { - return err - } - - // Avoid duplicate - if sw.peers.Has(peer.ID()) { - return ErrSwitchDuplicatePeer - - } - - // Check version, chain id - if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo()); err != nil { - return err - } - - // Start peer - if sw.IsRunning() { - sw.startInitPeer(peer) - } - - // Add the peer to .peers. - // We start it first so that a peer in the list is safe to Stop. - // It should not err since we already checked peers.Has(). - if err := sw.peers.Add(peer); err != nil { - return err - } - - sw.Logger.Info("Added peer", "peer", peer) - return nil +// Peers returns the set of peers that are connected to the switch. +func (sw *Switch) Peers() IPeerSet { + return sw.peers } -// FilterConnByAddr returns an error if connecting to the given address is forbidden. -func (sw *Switch) FilterConnByAddr(addr net.Addr) error { - if sw.filterConnByAddr != nil { - return sw.filterConnByAddr(addr) - } - return nil -} - -// FilterConnByPubKey returns an error if connecting to the given public key is forbidden. -func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { - if sw.filterConnByPubKey != nil { - return sw.filterConnByPubKey(pubkey) - } - return nil - -} - -// SetAddrFilter sets the function for filtering connections by address. -func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { - sw.filterConnByAddr = f -} - -// SetPubKeyFilter sets the function for filtering connections by public key. -func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { - sw.filterConnByPubKey = f -} - -func (sw *Switch) startInitPeer(peer *peer) { - err := peer.Start() // spawn send/recv routines - if err != nil { - // Should never happen - sw.Logger.Error("Error starting peer", "peer", peer, "err", err) - } - - for _, reactor := range sw.reactors { - reactor.AddPeer(peer) - } -} - -// DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). -func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { - netAddrs, errs := NewNetAddressStrings(peers) - // TODO: IDs - for _, err := range errs { - sw.Logger.Error("Error in peer's address", "err", err) - } - - if addrBook != nil { - // add peers to `addrBook` - ourAddrS := sw.nodeInfo.ListenAddr - ourAddr, _ := NewNetAddressString(ourAddrS) - for _, netAddr := range netAddrs { - // do not add ourselves - if netAddr.Equals(ourAddr) { - continue - } - addrBook.AddAddress(netAddr, ourAddr) +// NumPeers returns the count of outbound/inbound and outbound-dialing peers. +func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { + peers := sw.peers.List() + for _, peer := range peers { + if peer.IsOutbound() { + outbound++ + } else { + inbound++ } - addrBook.Save() } - - // permute the list, dial them in random order. - perm := sw.rng.Perm(len(netAddrs)) - for i := 0; i < len(perm); i++ { - go func(i int) { - sw.randomSleep(0) - j := perm[i] - peer, err := sw.DialPeerWithAddress(netAddrs[j], persistent) - if err != nil { - sw.Logger.Error("Error dialing peer", "err", err) - } else { - sw.Logger.Info("Connected to peer", "peer", peer) - } - }(i) - } - return nil -} - -// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] -func (sw *Switch) randomSleep(interval time.Duration) { - r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond - time.Sleep(r + interval) -} - -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. -// If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. -func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { - sw.dialing.Set(string(addr.ID), addr) - defer sw.dialing.Delete(string(addr.ID)) - - sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) - if err != nil { - sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) - return nil, err - } - peer.SetLogger(sw.Logger.With("peer", addr)) - - // authenticate peer - if addr.ID == "" { - peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) - } else if addr.ID != peer.ID() { - return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) - } - - if persistent { - peer.makePersistent() - } - err = sw.addPeer(peer) - if err != nil { - sw.Logger.Error("Failed to add peer", "address", addr, "err", err) - peer.CloseConn() - return nil, err - } - sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) - return peer, nil -} - -// IsDialing returns true if the switch is currently dialing the given ID. -func (sw *Switch) IsDialing(id ID) bool { - return sw.dialing.Has(string(id)) + dialing = sw.dialing.Size() + return } // Broadcast runs a go routine for each attempted send, which will block @@ -417,25 +238,6 @@ func (sw *Switch) Broadcast(chID byte, msg interface{}) chan bool { return successChan } -// NumPeers returns the count of outbound/inbound and outbound-dialing peers. -func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { - peers := sw.peers.List() - for _, peer := range peers { - if peer.IsOutbound() { - outbound++ - } else { - inbound++ - } - } - dialing = sw.dialing.Size() - return -} - -// Peers returns the set of peers that are connected to the switch. -func (sw *Switch) Peers() IPeerSet { - return sw.peers -} - // StopPeerForError disconnects from a peer due to external error. // If the peer is persistent, it will attempt to reconnect. // TODO: make record depending on reason. @@ -448,6 +250,21 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { } } +// StopPeerGracefully disconnects from a peer gracefully. +// TODO: handle graceful disconnects. +func (sw *Switch) StopPeerGracefully(peer Peer) { + sw.Logger.Info("Stopping peer gracefully") + sw.stopAndRemovePeer(peer, nil) +} + +func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { + sw.peers.Remove(peer) + peer.Stop() + for _, reactor := range sw.reactors { + reactor.RemovePeer(peer, reason) + } +} + // reconnectToPeer tries to reconnect to the peer, first repeatedly // with a fixed interval, then with exponential backoff. // If no success after all that, it stops trying, and leaves it @@ -495,21 +312,125 @@ func (sw *Switch) reconnectToPeer(peer Peer) { sw.Logger.Error("Failed to reconnect to peer. Giving up", "peer", peer, "elapsed", time.Since(start)) } -// StopPeerGracefully disconnects from a peer gracefully. -// TODO: handle graceful disconnects. -func (sw *Switch) StopPeerGracefully(peer Peer) { - sw.Logger.Info("Stopping peer gracefully") - sw.stopAndRemovePeer(peer, nil) +//--------------------------------------------------------------------- +// Dialing + +// IsDialing returns true if the switch is currently dialing the given ID. +func (sw *Switch) IsDialing(id ID) bool { + return sw.dialing.Has(string(id)) } -func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { - sw.peers.Remove(peer) - peer.Stop() - for _, reactor := range sw.reactors { - reactor.RemovePeer(peer, reason) +// DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). +func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { + netAddrs, errs := NewNetAddressStrings(peers) + // TODO: IDs + for _, err := range errs { + sw.Logger.Error("Error in peer's address", "err", err) } + + if addrBook != nil { + // add peers to `addrBook` + ourAddrS := sw.nodeInfo.ListenAddr + ourAddr, _ := NewNetAddressString(ourAddrS) + for _, netAddr := range netAddrs { + // do not add ourselves + if netAddr.Equals(ourAddr) { + continue + } + addrBook.AddAddress(netAddr, ourAddr) + } + addrBook.Save() + } + + // permute the list, dial them in random order. + perm := sw.rng.Perm(len(netAddrs)) + for i := 0; i < len(perm); i++ { + go func(i int) { + sw.randomSleep(0) + j := perm[i] + peer, err := sw.DialPeerWithAddress(netAddrs[j], persistent) + if err != nil { + sw.Logger.Error("Error dialing peer", "err", err) + } else { + sw.Logger.Info("Connected to peer", "peer", peer) + } + }(i) + } + return nil } +// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. +// If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. +func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { + sw.dialing.Set(string(addr.ID), addr) + defer sw.dialing.Delete(string(addr.ID)) + + sw.Logger.Info("Dialing peer", "address", addr) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) + if err != nil { + sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) + return nil, err + } + peer.SetLogger(sw.Logger.With("peer", addr)) + + // authenticate peer + if addr.ID == "" { + peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) + } else if addr.ID != peer.ID() { + return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + } + + if persistent { + peer.makePersistent() + } + err = sw.addPeer(peer) + if err != nil { + sw.Logger.Error("Failed to add peer", "address", addr, "err", err) + peer.CloseConn() + return nil, err + } + sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) + return peer, nil +} + +// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] +func (sw *Switch) randomSleep(interval time.Duration) { + r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond + time.Sleep(r + interval) +} + +//------------------------------------------------------------------------------------ +// Connection filtering + +// FilterConnByAddr returns an error if connecting to the given address is forbidden. +func (sw *Switch) FilterConnByAddr(addr net.Addr) error { + if sw.filterConnByAddr != nil { + return sw.filterConnByAddr(addr) + } + return nil +} + +// FilterConnByPubKey returns an error if connecting to the given public key is forbidden. +func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { + if sw.filterConnByPubKey != nil { + return sw.filterConnByPubKey(pubkey) + } + return nil + +} + +// SetAddrFilter sets the function for filtering connections by address. +func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { + sw.filterConnByAddr = f +} + +// SetPubKeyFilter sets the function for filtering connections by public key. +func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { + sw.filterConnByPubKey = f +} + +//------------------------------------------------------------------------------------ + func (sw *Switch) listenerRoutine(l Listener) { for { inConn, ok := <-l.Connections() @@ -525,7 +446,7 @@ func (sw *Switch) listenerRoutine(l Listener) { } // New inbound connection! - err := sw.addPeerWithConnectionAndConfig(inConn, sw.peerConfig) + err := sw.addInboundPeerWithConfig(inConn, sw.peerConfig) if err != nil { sw.Logger.Info("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "err", err) continue @@ -539,107 +460,7 @@ func (sw *Switch) listenerRoutine(l Listener) { // cleanup } -//------------------------------------------------------------------ -// Connects switches via arbitrary net.Conn. Used for testing. - -// MakeConnectedSwitches returns n switches, connected according to the connect func. -// If connect==Connect2Switches, the switches will be fully connected. -// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). -// NOTE: panics if any switch fails to start. -func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { - switches := make([]*Switch, n) - for i := 0; i < n; i++ { - switches[i] = makeSwitch(cfg, i, "testing", "123.123.123", initSwitch) - } - - if err := StartSwitches(switches); err != nil { - panic(err) - } - - for i := 0; i < n; i++ { - for j := i + 1; j < n; j++ { - connect(switches, i, j) - } - } - - return switches -} - -// Connect2Switches will connect switches i and j via net.Pipe(). -// Blocks until a connection is established. -// NOTE: caller ensures i and j are within bounds. -func Connect2Switches(switches []*Switch, i, j int) { - switchI := switches[i] - switchJ := switches[j] - c1, c2 := netPipe() - doneCh := make(chan struct{}) - go func() { - err := switchI.addPeerWithConnection(c1) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - go func() { - err := switchJ.addPeerWithConnection(c2) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - <-doneCh - <-doneCh -} - -// StartSwitches calls sw.Start() for each given switch. -// It returns the first encountered error. -func StartSwitches(switches []*Switch) error { - for _, s := range switches { - err := s.Start() // start switch and reactors - if err != nil { - return err - } - } - return nil -} - -func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { - // new switch, add reactors - // TODO: let the config be passed in? - nodeKey := &NodeKey{ - PrivKey: crypto.GenPrivKeyEd25519().Wrap(), - } - s := initSwitch(i, NewSwitch(cfg)) - s.SetNodeInfo(&NodeInfo{ - PubKey: nodeKey.PubKey(), - Moniker: cmn.Fmt("switch%d", i), - Network: network, - Version: version, - ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), - }) - s.SetNodeKey(nodeKey) - s.SetLogger(log.TestingLogger()) - return s -} - -func (sw *Switch) addPeerWithConnection(conn net.Conn) error { - peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) - if err = sw.addPeer(peer); err != nil { - peer.CloseConn() - return err - } - - return nil -} - -func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error { +func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error { peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) if err != nil { if err := conn.Close(); err != nil { @@ -655,3 +476,66 @@ func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConf return nil } + +// addPeer checks the given peer's validity, performs a handshake, and adds the +// peer to the switch and to all registered reactors. +// We already have an authenticated SecretConnection with the peer. +// NOTE: This performs a blocking handshake before the peer is added. +// NOTE: If error is returned, caller is responsible for calling peer.CloseConn() +func (sw *Switch) addPeer(peer *peer) error { + // Avoid self + if sw.nodeKey.ID() == peer.ID() { + return ErrSwitchConnectToSelf + } + + // Filter peer against white list + if err := sw.FilterConnByAddr(peer.Addr()); err != nil { + return err + } + if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { + return err + } + + // Exchange NodeInfo with the peer + if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second)); err != nil { + return err + } + + // Avoid duplicate + if sw.peers.Has(peer.ID()) { + return ErrSwitchDuplicatePeer + + } + + // Check version, chain id + if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo()); err != nil { + return err + } + + // Start peer + if sw.IsRunning() { + sw.startInitPeer(peer) + } + + // Add the peer to .peers. + // We start it first so that a peer in the list is safe to Stop. + // It should not err since we already checked peers.Has(). + if err := sw.peers.Add(peer); err != nil { + return err + } + + sw.Logger.Info("Added peer", "peer", peer) + return nil +} + +func (sw *Switch) startInitPeer(peer *peer) { + err := peer.Start() // spawn send/recv routines + if err != nil { + // Should never happen + sw.Logger.Error("Error starting peer", "peer", peer, "err", err) + } + + for _, reactor := range sw.reactors { + reactor.AddPeer(peer) + } +} diff --git a/p2p/test_util.go b/p2p/test_util.go new file mode 100644 index 00000000..9c6892f1 --- /dev/null +++ b/p2p/test_util.go @@ -0,0 +1,111 @@ +package p2p + +import ( + "math/rand" + "net" + + crypto "github.com/tendermint/go-crypto" + cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" +) + +//------------------------------------------------------------------ +// Connects switches via arbitrary net.Conn. Used for testing. + +// MakeConnectedSwitches returns n switches, connected according to the connect func. +// If connect==Connect2Switches, the switches will be fully connected. +// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). +// NOTE: panics if any switch fails to start. +func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { + switches := make([]*Switch, n) + for i := 0; i < n; i++ { + switches[i] = makeSwitch(cfg, i, "testing", "123.123.123", initSwitch) + } + + if err := StartSwitches(switches); err != nil { + panic(err) + } + + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + connect(switches, i, j) + } + } + + return switches +} + +// Connect2Switches will connect switches i and j via net.Pipe(). +// Blocks until a connection is established. +// NOTE: caller ensures i and j are within bounds. +func Connect2Switches(switches []*Switch, i, j int) { + switchI := switches[i] + switchJ := switches[j] + c1, c2 := netPipe() + doneCh := make(chan struct{}) + go func() { + err := switchI.addPeerWithConnection(c1) + if err != nil { + panic(err) + } + doneCh <- struct{}{} + }() + go func() { + err := switchJ.addPeerWithConnection(c2) + if err != nil { + panic(err) + } + doneCh <- struct{}{} + }() + <-doneCh + <-doneCh +} + +func (sw *Switch) addPeerWithConnection(conn net.Conn) error { + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) + if err != nil { + if err := conn.Close(); err != nil { + sw.Logger.Error("Error closing connection", "err", err) + } + return err + } + peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) + if err = sw.addPeer(peer); err != nil { + peer.CloseConn() + return err + } + + return nil +} + +// StartSwitches calls sw.Start() for each given switch. +// It returns the first encountered error. +func StartSwitches(switches []*Switch) error { + for _, s := range switches { + err := s.Start() // start switch and reactors + if err != nil { + return err + } + } + return nil +} + +func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { + // new switch, add reactors + // TODO: let the config be passed in? + nodeKey := &NodeKey{ + PrivKey: crypto.GenPrivKeyEd25519().Wrap(), + } + s := initSwitch(i, NewSwitch(cfg)) + s.SetNodeInfo(&NodeInfo{ + PubKey: nodeKey.PubKey(), + Moniker: cmn.Fmt("switch%d", i), + Network: network, + Version: version, + ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), + }) + s.SetNodeKey(nodeKey) + s.SetLogger(log.TestingLogger()) + return s +} diff --git a/p2p/util.go b/p2p/util.go deleted file mode 100644 index a4c3ad58..00000000 --- a/p2p/util.go +++ /dev/null @@ -1,15 +0,0 @@ -package p2p - -import ( - "crypto/sha256" -) - -// doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. -func doubleSha256(b []byte) []byte { - hasher := sha256.New() - hasher.Write(b) // nolint: errcheck, gas - sum := hasher.Sum(nil) - hasher.Reset() - hasher.Write(sum) // nolint: errcheck, gas - return hasher.Sum(nil) -} From 08f84cd712e02fb4c81094e50d2c88112d571a10 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 23:48:41 -0500 Subject: [PATCH 061/124] a little more moving around --- node/node.go | 2 +- p2p/peer.go | 10 ++---- p2p/pex_reactor.go | 4 +-- p2p/switch.go | 86 +++++++++++++++++++++++++--------------------- p2p/types.go | 26 +++++++++++--- 5 files changed, 72 insertions(+), 56 deletions(-) diff --git a/node/node.go b/node/node.go index 5535b1e1..2990ba9d 100644 --- a/node/node.go +++ b/node/node.go @@ -544,9 +544,9 @@ func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { } nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, - Moniker: n.config.Moniker, Network: n.genesisDoc.ChainID, Version: version.Version, + Moniker: n.config.Moniker, Other: []string{ cmn.Fmt("wire_version=%v", wire.Version), cmn.Fmt("p2p_version=%v", p2p.Version), diff --git a/p2p/peer.go b/p2p/peer.go index 1f5192d5..9e434b97 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -195,19 +195,13 @@ func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return errors.Wrap(err2, "Error during handshake/read") } - if p.config.AuthEnc { - // Check that the professed PubKey matches the sconn's. - if !peerNodeInfo.PubKey.Equals(p.PubKey().Wrap()) { - return fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v", - peerNodeInfo.PubKey, p.PubKey()) - } - } - // Remove deadline if err := p.conn.SetDeadline(time.Time{}); err != nil { return errors.Wrap(err, "Error removing deadline") } + // TODO: fix the peerNodeInfo.ListenAddr + p.nodeInfo = peerNodeInfo return nil } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 5aa63db7..1903a460 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -115,7 +115,8 @@ func (r *PEXReactor) AddPeer(p Peer) { r.RequestPEX(p) } } else { - // For inbound connections, the peer is its own source + // For inbound connections, the peer is its own source, + // and its NodeInfo has already been validated addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } @@ -130,7 +131,6 @@ func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddr.ID) if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) diff --git a/p2p/switch.go b/p2p/switch.go index 4ece1a58..05edb8c1 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -359,38 +359,12 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent return nil } -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. +// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { sw.dialing.Set(string(addr.ID), addr) defer sw.dialing.Delete(string(addr.ID)) - - sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) - if err != nil { - sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) - return nil, err - } - peer.SetLogger(sw.Logger.With("peer", addr)) - - // authenticate peer - if addr.ID == "" { - peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) - } else if addr.ID != peer.ID() { - return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) - } - - if persistent { - peer.makePersistent() - } - err = sw.addPeer(peer) - if err != nil { - sw.Logger.Error("Failed to add peer", "address", addr, "err", err) - peer.CloseConn() - return nil, err - } - sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) - return peer, nil + return sw.addOutboundPeerWithConfig(addr, sw.peerConfig, persistent) } // sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] @@ -451,10 +425,6 @@ func (sw *Switch) listenerRoutine(l Listener) { sw.Logger.Info("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "err", err) continue } - - // NOTE: We don't yet have the listening port of the - // remote (if they have a listener at all). - // The peerHandshake will handle that. } // cleanup @@ -477,9 +447,40 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er return nil } -// addPeer checks the given peer's validity, performs a handshake, and adds the -// peer to the switch and to all registered reactors. -// We already have an authenticated SecretConnection with the peer. +// dial the peer; make secret connection; authenticate against the dialed ID; +// add the peer. +func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { + sw.Logger.Info("Dialing peer", "address", addr) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) + if err != nil { + sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) + return nil, err + } + peer.SetLogger(sw.Logger.With("peer", addr)) + + // authenticate peer + if addr.ID == "" { + peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) + } else if addr.ID != peer.ID() { + return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + } + + if persistent { + peer.makePersistent() + } + err = sw.addPeer(peer) + if err != nil { + sw.Logger.Error("Failed to add peer", "address", addr, "err", err) + peer.CloseConn() + return nil, err + } + sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) + return peer, nil +} + +// addPeer performs the Tendermint P2P handshake with a peer +// that already has a SecretConnection. If all goes well, +// it starts the peer and adds it to the switch. // NOTE: This performs a blocking handshake before the peer is added. // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { @@ -488,6 +489,12 @@ func (sw *Switch) addPeer(peer *peer) error { return ErrSwitchConnectToSelf } + // Avoid duplicate + if sw.peers.Has(peer.ID()) { + return ErrSwitchDuplicatePeer + + } + // Filter peer against white list if err := sw.FilterConnByAddr(peer.Addr()); err != nil { return err @@ -501,10 +508,9 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Avoid duplicate - if sw.peers.Has(peer.ID()) { - return ErrSwitchDuplicatePeer - + // Validate the peers nodeInfo against the pubkey + if err := peer.NodeInfo().Validate(peer.PubKey()); err != nil { + return err } // Check version, chain id @@ -512,7 +518,7 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Start peer + // All good. Start peer if sw.IsRunning() { sw.startInitPeer(peer) } diff --git a/p2p/types.go b/p2p/types.go index 2aec521b..d4adb9ee 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,14 +12,30 @@ import ( const maxNodeInfoSize = 10240 // 10Kb // NodeInfo is the basic node information exchanged -// between two peers during the Tendermint P2P handshake +// between two peers during the Tendermint P2P handshake. type NodeInfo struct { + // Authenticate PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey - Moniker string `json:"moniker"` // arbitrary moniker - Network string `json:"network"` // network/chain ID ListenAddr string `json:"listen_addr"` // accepting incoming - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + + // Check compatibility + Network string `json:"network"` // network/chain ID + Version string `json:"version"` // major.minor.revision + + // Sanitize + Moniker string `json:"moniker"` // arbitrary moniker + Other []string `json:"other"` // other application specific data +} + +// Validate checks the self-reported NodeInfo is safe. +// It returns an error if the info.PubKey doesn't match the given pubKey. +// TODO: constraints for Moniker/Other? Or is that for the UI ? +func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { + if !info.PubKey.Equals(pubKey) { + return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)", + info.PubKey, pubKey) + } + return nil } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From 8b74a8d6ac945c86a520bb98d415f0e7bec87561 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 00:10:29 -0500 Subject: [PATCH 062/124] NodeInfo not a pointer --- node/node.go | 6 +++--- p2p/peer.go | 27 +++++++++++---------------- p2p/peer_set_test.go | 2 +- p2p/peer_test.go | 4 ++-- p2p/pex_reactor_test.go | 2 +- p2p/switch.go | 13 ++++--------- p2p/test_util.go | 2 +- p2p/types.go | 12 ++++++------ rpc/core/net.go | 2 +- rpc/core/pipe.go | 2 +- rpc/core/types/responses.go | 4 ++-- 11 files changed, 33 insertions(+), 43 deletions(-) diff --git a/node/node.go b/node/node.go index 2990ba9d..4db7f44d 100644 --- a/node/node.go +++ b/node/node.go @@ -537,12 +537,12 @@ func (n *Node) ProxyApp() proxy.AppConns { return n.proxyApp } -func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { +func (n *Node) makeNodeInfo(pubKey crypto.PubKey) p2p.NodeInfo { txIndexerStatus := "on" if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Network: n.genesisDoc.ChainID, Version: version.Version, @@ -574,7 +574,7 @@ func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { //------------------------------------------------------------------------------ // NodeInfo returns the Node's Info from the Switch. -func (n *Node) NodeInfo() *p2p.NodeInfo { +func (n *Node) NodeInfo() p2p.NodeInfo { return n.sw.NodeInfo() } diff --git a/p2p/peer.go b/p2p/peer.go index 9e434b97..57718b6b 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -1,7 +1,6 @@ package p2p import ( - "encoding/hex" "fmt" "net" "time" @@ -21,7 +20,7 @@ type Peer interface { ID() ID IsOutbound() bool IsPersistent() bool - NodeInfo() *NodeInfo + NodeInfo() NodeInfo Status() ConnectionStatus Send(byte, interface{}) bool @@ -47,7 +46,7 @@ type peer struct { persistent bool config *PeerConfig - nodeInfo *NodeInfo + nodeInfo NodeInfo Data *cmn.CMap // User data. } @@ -128,7 +127,7 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ } } - // Key and NodeInfo are set after Handshake + // NodeInfo is set after Handshake p := &peer{ outbound: outbound, conn: conn, @@ -169,23 +168,23 @@ func (p *peer) IsPersistent() bool { // HandshakeTimeout performs a handshake between a given node and the peer. // NOTE: blocking -func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) error { +func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull if err := p.conn.SetDeadline(time.Now().Add(timeout)); err != nil { return errors.Wrap(err, "Error setting deadline") } - var peerNodeInfo = new(NodeInfo) + var peerNodeInfo NodeInfo var err1 error var err2 error cmn.Parallel( func() { var n int - wire.WriteBinary(ourNodeInfo, p.conn, &n, &err1) + wire.WriteBinary(&ourNodeInfo, p.conn, &n, &err1) }, func() { var n int - wire.ReadBinary(peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) + wire.ReadBinary(&peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) p.Logger.Info("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { @@ -213,7 +212,7 @@ func (p *peer) Addr() net.Addr { // PubKey returns peer's public key. func (p *peer) PubKey() crypto.PubKey { - if p.NodeInfo() != nil { + if !p.nodeInfo.PubKey.Empty() { return p.nodeInfo.PubKey } else if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() @@ -300,16 +299,12 @@ func (p *peer) Set(key string, data interface{}) { // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { - return ID(hex.EncodeToString(p.PubKey().Address())) + return PubKeyToID(p.PubKey()) } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() *NodeInfo { - if p.nodeInfo == nil { - return nil - } - n := *p.nodeInfo // copy - return &n +func (p *peer) NodeInfo() NodeInfo { + return p.nodeInfo } // Status returns the peer's ConnectionStatus. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index e50bb384..e906eb8e 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -14,7 +14,7 @@ import ( // Returns an empty dummy peer func randPeer() *peer { return &peer{ - nodeInfo: &NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/peer_test.go b/p2p/peer_test.go index aafe8d7a..f4a5363b 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -90,7 +90,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) if err != nil { return nil, err } - err = p.HandshakeTimeout(&NodeInfo{ + err = p.HandshakeTimeout(NodeInfo{ PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", @@ -141,7 +141,7 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } - err = peer.HandshakeTimeout(&NodeInfo{ + err = peer.HandshakeTimeout(NodeInfo{ PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 296b1886..20c8b823 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -242,7 +242,7 @@ func createRoutableAddr() (addr string, netAddr *NetAddress) { func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ - nodeInfo: &NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: netAddr.String(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/switch.go b/p2p/switch.go index 05edb8c1..0ff64dbf 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -52,8 +52,8 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodeKey *NodeKey // our node privkey + nodeInfo NodeInfo // our node info + nodeKey *NodeKey // our node privkey filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -70,7 +70,6 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { reactorsByCh: make(map[byte]Reactor), peers: NewPeerSet(), dialing: cmn.NewCMap(), - nodeInfo: nil, } // Ensure we have a completely undeterministic PRNG. cmd.RandInt64() draws @@ -141,24 +140,20 @@ func (sw *Switch) IsListening() bool { // SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo *NodeInfo) { +func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { sw.nodeInfo = nodeInfo } // NodeInfo returns the switch's NodeInfo. // NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() *NodeInfo { +func (sw *Switch) NodeInfo() NodeInfo { return sw.nodeInfo } // SetNodeKey sets the switch's private key for authenticated encryption. -// NOTE: Overwrites sw.nodeInfo.PubKey. // NOTE: Not goroutine safe. func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { sw.nodeKey = nodeKey - if sw.nodeInfo != nil { - sw.nodeInfo.PubKey = nodeKey.PubKey() - } } //--------------------------------------------------------------------- diff --git a/p2p/test_util.go b/p2p/test_util.go index 9c6892f1..18167e20 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -98,7 +98,7 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } s := initSwitch(i, NewSwitch(cfg)) - s.SetNodeInfo(&NodeInfo{ + s.SetNodeInfo(NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, diff --git a/p2p/types.go b/p2p/types.go index d4adb9ee..d93adc9b 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -30,7 +30,7 @@ type NodeInfo struct { // Validate checks the self-reported NodeInfo is safe. // It returns an error if the info.PubKey doesn't match the given pubKey. // TODO: constraints for Moniker/Other? Or is that for the UI ? -func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { +func (info NodeInfo) Validate(pubKey crypto.PubKey) error { if !info.PubKey.Equals(pubKey) { return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)", info.PubKey, pubKey) @@ -39,7 +39,7 @@ func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { } // CONTRACT: two nodes are compatible if the major/minor versions match and network match -func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { +func (info NodeInfo) CompatibleWith(other NodeInfo) error { iMajor, iMinor, _, iErr := splitVersion(info.Version) oMajor, oMinor, _, oErr := splitVersion(other.Version) @@ -71,11 +71,11 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } -func (info *NodeInfo) ID() ID { +func (info NodeInfo) ID() ID { return PubKeyToID(info.PubKey) } -func (info *NodeInfo) NetAddress() *NetAddress { +func (info NodeInfo) NetAddress() *NetAddress { id := PubKeyToID(info.PubKey) addr := info.ListenAddr netAddr, err := NewNetAddressString(IDAddressString(id, addr)) @@ -85,12 +85,12 @@ func (info *NodeInfo) NetAddress() *NetAddress { return netAddr } -func (info *NodeInfo) ListenHost() string { +func (info NodeInfo) ListenHost() string { host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas return host } -func (info *NodeInfo) ListenPort() int { +func (info NodeInfo) ListenPort() int { _, port, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas port_i, err := strconv.Atoi(port) if err != nil { diff --git a/rpc/core/net.go b/rpc/core/net.go index af52c81c..14e7389d 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -41,7 +41,7 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { peers := []ctypes.Peer{} for _, peer := range p2pSwitch.Peers().List() { peers = append(peers, ctypes.Peer{ - NodeInfo: *peer.NodeInfo(), + NodeInfo: peer.NodeInfo(), IsOutbound: peer.IsOutbound(), ConnectionStatus: peer.Status(), }) diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 6d67fd89..301977ac 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -30,7 +30,7 @@ type P2P interface { Listeners() []p2p.Listener Peers() p2p.IPeerSet NumPeers() (outbound, inbound, dialig int) - NodeInfo() *p2p.NodeInfo + NodeInfo() p2p.NodeInfo IsListening() bool DialPeersAsync(*p2p.AddrBook, []string, bool) error } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index a6bb3068..233b44e9 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -54,7 +54,7 @@ func NewResultCommit(header *types.Header, commit *types.Commit, } type ResultStatus struct { - NodeInfo *p2p.NodeInfo `json:"node_info"` + NodeInfo p2p.NodeInfo `json:"node_info"` PubKey crypto.PubKey `json:"pub_key"` LatestBlockHash data.Bytes `json:"latest_block_hash"` LatestAppHash data.Bytes `json:"latest_app_hash"` @@ -64,7 +64,7 @@ type ResultStatus struct { } func (s *ResultStatus) TxIndexEnabled() bool { - if s == nil || s.NodeInfo == nil { + if s == nil { return false } for _, s := range s.NodeInfo.Other { From f9e4f6eb6ba51d0cac2186bccdd7a9f320fe3c0e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 00:44:16 -0500 Subject: [PATCH 063/124] reorder peer.go methods --- p2p/peer.go | 180 ++++++++++++++++++++++----------------------- p2p/peer_test.go | 4 +- p2p/switch.go | 10 +-- p2p/switch_test.go | 5 +- 4 files changed, 95 insertions(+), 104 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 57718b6b..596b9216 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -17,10 +17,10 @@ import ( type Peer interface { cmn.Service - ID() ID - IsOutbound() bool - IsPersistent() bool - NodeInfo() NodeInfo + ID() ID // peer's cryptographic ID + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + NodeInfo() NodeInfo // peer's info Status() ConnectionStatus Send(byte, interface{}) bool @@ -30,9 +30,9 @@ type Peer interface { Get(string) interface{} } -// Peer could be marked as persistent, in which case you can use -// Redial function to reconnect. Note that inbound peers can't be -// made persistent. They should be made persistent on the other end. +//---------------------------------------------------------- + +// peer implements Peer. // // Before using a peer, you will need to perform a handshake on connection. type peer struct { @@ -77,7 +77,7 @@ func DefaultPeerConfig() *PeerConfig { } func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig, persistent bool) (*peer, error) { conn, err := dial(addr, config) if err != nil { @@ -91,6 +91,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } return nil, err } + peer.persistent = persistent return peer, nil } @@ -142,23 +143,41 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ return p, nil } +//--------------------------------------------------- +// Implements cmn.Service + +// SetLogger implements BaseService. func (p *peer) SetLogger(l log.Logger) { p.Logger = l p.mconn.SetLogger(l) } -// CloseConn should be used when the peer was created, but never started. -func (p *peer) CloseConn() { - p.conn.Close() // nolint: errcheck +// OnStart implements BaseService. +func (p *peer) OnStart() error { + if err := p.BaseService.OnStart(); err != nil { + return err + } + err := p.mconn.Start() + return err } -// makePersistent marks the peer as persistent. -func (p *peer) makePersistent() { - if !p.outbound { - panic("inbound peers can't be made persistent") - } +// OnStop implements BaseService. +func (p *peer) OnStop() { + p.BaseService.OnStop() + p.mconn.Stop() // stop everything and close the conn +} - p.persistent = true +//--------------------------------------------------- +// Implements Peer + +// ID returns the peer's ID - the hex encoded hash of its pubkey. +func (p *peer) ID() ID { + return PubKeyToID(p.PubKey()) +} + +// IsOutbound returns true if the connection is outbound, false otherwise. +func (p *peer) IsOutbound() bool { + return p.outbound } // IsPersistent returns true if the peer is persitent, false otherwise. @@ -166,7 +185,56 @@ func (p *peer) IsPersistent() bool { return p.persistent } -// HandshakeTimeout performs a handshake between a given node and the peer. +// NodeInfo returns a copy of the peer's NodeInfo. +func (p *peer) NodeInfo() NodeInfo { + return p.nodeInfo +} + +// Status returns the peer's ConnectionStatus. +func (p *peer) Status() ConnectionStatus { + return p.mconn.Status() +} + +// Send msg to the channel identified by chID byte. Returns false if the send +// queue is full after timeout, specified by MConnection. +func (p *peer) Send(chID byte, msg interface{}) bool { + if !p.IsRunning() { + // see Switch#Broadcast, where we fetch the list of peers and loop over + // them - while we're looping, one peer may be removed and stopped. + return false + } + return p.mconn.Send(chID, msg) +} + +// TrySend msg to the channel identified by chID byte. Immediately returns +// false if the send queue is full. +func (p *peer) TrySend(chID byte, msg interface{}) bool { + if !p.IsRunning() { + return false + } + return p.mconn.TrySend(chID, msg) +} + +// Get the data for a given key. +func (p *peer) Get(key string) interface{} { + return p.Data.Get(key) +} + +// Set sets the data for the given key. +func (p *peer) Set(key string, data interface{}) { + p.Data.Set(key, data) +} + +//--------------------------------------------------- +// methods used by the Switch + +// CloseConn should be called by the Switch if the peer was created but never started. +func (p *peer) CloseConn() { + p.conn.Close() // nolint: errcheck +} + +// HandshakeTimeout performs the Tendermint P2P handshake between a given node and the peer +// by exchanging their NodeInfo. It sets the received nodeInfo on the peer. // NOTE: blocking func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull @@ -220,51 +288,6 @@ func (p *peer) PubKey() crypto.PubKey { panic("Attempt to get peer's PubKey before calling Handshake") } -// OnStart implements BaseService. -func (p *peer) OnStart() error { - if err := p.BaseService.OnStart(); err != nil { - return err - } - err := p.mconn.Start() - return err -} - -// OnStop implements BaseService. -func (p *peer) OnStop() { - p.BaseService.OnStop() - p.mconn.Stop() -} - -// Connection returns underlying MConnection. -func (p *peer) Connection() *MConnection { - return p.mconn -} - -// IsOutbound returns true if the connection is outbound, false otherwise. -func (p *peer) IsOutbound() bool { - return p.outbound -} - -// Send msg to the channel identified by chID byte. Returns false if the send -// queue is full after timeout, specified by MConnection. -func (p *peer) Send(chID byte, msg interface{}) bool { - if !p.IsRunning() { - // see Switch#Broadcast, where we fetch the list of peers and loop over - // them - while we're looping, one peer may be removed and stopped. - return false - } - return p.mconn.Send(chID, msg) -} - -// TrySend msg to the channel identified by chID byte. Immediately returns -// false if the send queue is full. -func (p *peer) TrySend(chID byte, msg interface{}) bool { - if !p.IsRunning() { - return false - } - return p.mconn.TrySend(chID, msg) -} - // CanSend returns true if the send queue is not full, false otherwise. func (p *peer) CanSend(chID byte) bool { if !p.IsRunning() { @@ -282,35 +305,8 @@ func (p *peer) String() string { return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) } -// Equals reports whenever 2 peers are actually represent the same node. -func (p *peer) Equals(other Peer) bool { - return p.ID() == other.ID() -} - -// Get the data for a given key. -func (p *peer) Get(key string) interface{} { - return p.Data.Get(key) -} - -// Set sets the data for the given key. -func (p *peer) Set(key string, data interface{}) { - p.Data.Set(key, data) -} - -// ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() ID { - return PubKeyToID(p.PubKey()) -} - -// NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() NodeInfo { - return p.nodeInfo -} - -// Status returns the peer's ConnectionStatus. -func (p *peer) Status() ConnectionStatus { - return p.mconn.Status() -} +//------------------------------------------------------------------ +// helper funcs func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { conn, err := addr.DialTimeout(config.DialTimeout * time.Second) diff --git a/p2p/peer_test.go b/p2p/peer_test.go index f4a5363b..d99fff5e 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -30,7 +30,7 @@ func TestPeerBasic(t *testing.T) { assert.True(p.IsRunning()) assert.True(p.IsOutbound()) assert.False(p.IsPersistent()) - p.makePersistent() + p.persistent = true assert.True(p.IsPersistent()) assert.Equal(rp.Addr().String(), p.Addr().String()) assert.Equal(rp.PubKey(), p.PubKey()) @@ -86,7 +86,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} pk := crypto.GenPrivKeyEd25519().Wrap() - p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config) + p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config, false) if err != nil { return nil, err } diff --git a/p2p/switch.go b/p2p/switch.go index 0ff64dbf..eaf3c22e 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -428,9 +428,7 @@ func (sw *Switch) listenerRoutine(l Listener) { func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error { peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } + peer.CloseConn() return err } peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) @@ -446,7 +444,7 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er // add the peer. func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config, persistent) if err != nil { sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) return nil, err @@ -457,12 +455,10 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig if addr.ID == "" { peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) } else if addr.ID != peer.ID() { + peer.CloseConn() return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) } - if persistent { - peer.makePersistent() - } err = sw.addPeer(peer) if err != nil { sw.Logger.Error("Failed to add peer", "address", addr, "err", err) diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 7d61fa39..a729698e 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -236,7 +236,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), false) require.Nil(err) err = sw.addPeer(peer) require.Nil(err) @@ -263,8 +263,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) - peer.makePersistent() + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), true) require.Nil(err) err = sw.addPeer(peer) require.Nil(err) From 68237911ba03cf16d9d20db2dd95c549311f6b33 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:11:50 -0500 Subject: [PATCH 064/124] NetAddress.Same checks ID or DialString --- p2p/netaddress.go | 15 ++++++++++++++- p2p/switch.go | 8 +++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/p2p/netaddress.go b/p2p/netaddress.go index fed5e59d..333d16e5 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -127,12 +127,25 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { return na } -// Equals reports whether na and other are the same addresses. +// Equals reports whether na and other are the same addresses, +// including their ID, IP, and Port. func (na *NetAddress) Equals(other interface{}) bool { if o, ok := other.(*NetAddress); ok { return na.String() == o.String() } + return false +} +// Same returns true is na has the same non-empty ID or DialString as other. +func (na *NetAddress) Same(other interface{}) bool { + if o, ok := other.(*NetAddress); ok { + if na.DialString() == o.DialString() { + return true + } + if na.ID != "" && na.ID == o.ID { + return true + } + } return false } diff --git a/p2p/switch.go b/p2p/switch.go index eaf3c22e..3f026556 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -318,18 +318,16 @@ func (sw *Switch) IsDialing(id ID) bool { // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { netAddrs, errs := NewNetAddressStrings(peers) - // TODO: IDs for _, err := range errs { sw.Logger.Error("Error in peer's address", "err", err) } if addrBook != nil { // add peers to `addrBook` - ourAddrS := sw.nodeInfo.ListenAddr - ourAddr, _ := NewNetAddressString(ourAddrS) + ourAddr := sw.nodeInfo.NetAddress() for _, netAddr := range netAddrs { - // do not add ourselves - if netAddr.Equals(ourAddr) { + // do not add our address or ID + if netAddr.Same(ourAddr) { continue } addrBook.AddAddress(netAddr, ourAddr) From 3368eeb03ec82de933c9be33ad906ab83823e351 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:17:43 -0500 Subject: [PATCH 065/124] fix tests --- benchmarks/codec_test.go | 12 ++++-------- blockchain/reactor_test.go | 2 +- rpc/core/types/responses_test.go | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 209fcd3b..8ac62a24 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -16,11 +16,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() status := &ctypes.ResultStatus{ - NodeInfo: &p2p.NodeInfo{ + NodeInfo: p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -43,11 +42,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) { func BenchmarkEncodeNodeInfoWire(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -64,11 +62,10 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) { func BenchmarkEncodeNodeInfoBinary(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -87,11 +84,10 @@ func BenchmarkEncodeNodeInfoProto(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) pubKey2 := &proto.PubKey{Ed25519: &proto.PubKeyEd25519{Bytes: pubKey[:]}} - nodeInfo := &proto.NodeInfo{ + nodeInfo := proto.NodeInfo{ PubKey: pubKey2, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 5bdd2869..06f6c36c 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -142,7 +142,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { } func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } -func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } +func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } func (tp *bcrTestPeer) IsOutbound() bool { return false } diff --git a/rpc/core/types/responses_test.go b/rpc/core/types/responses_test.go index fa0da3fd..e410d47a 100644 --- a/rpc/core/types/responses_test.go +++ b/rpc/core/types/responses_test.go @@ -17,7 +17,7 @@ func TestStatusIndexer(t *testing.T) { status = &ResultStatus{} assert.False(status.TxIndexEnabled()) - status.NodeInfo = &p2p.NodeInfo{} + status.NodeInfo = p2p.NodeInfo{} assert.False(status.TxIndexEnabled()) cases := []struct { From 3df5fd21cd58db0c8a29c88320410059e26b80e9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 03:22:01 -0500 Subject: [PATCH 066/124] better abuse handling in pex --- p2p/pex_reactor.go | 170 ++++++++++++++++++++-------------------- p2p/pex_reactor_test.go | 102 +++++++++++++++++++++--- 2 files changed, 176 insertions(+), 96 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 1903a460..63862d1a 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -7,6 +7,7 @@ import ( "reflect" "time" + "github.com/pkg/errors" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" ) @@ -19,10 +20,6 @@ const ( defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB - - // maximum pex messages one peer can send to us during `msgCountByPeerFlushInterval` - defaultMaxMsgCountByPeer = 1000 - msgCountByPeerFlushInterval = 1 * time.Hour ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -32,15 +29,8 @@ const ( // // ## Preventing abuse // -// For now, it just limits the number of messages from one peer to -// `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000 -// msg/hour). -// -// NOTE [2017-01-17]: -// Limiting is fine for now. Maybe down the road we want to keep track of the -// quality of peer messages so if peerA keeps telling us about peers we can't -// connect to then maybe we should care less about peerA. But I don't think -// that kind of complexity is priority right now. +// Only accept pexAddrsMsg from peers we sent a corresponding pexRequestMsg too. +// Only accept one pexRequestMsg every ~defaultEnsurePeersPeriod. type PEXReactor struct { BaseReactor @@ -48,9 +38,9 @@ type PEXReactor struct { config *PEXReactorConfig ensurePeersPeriod time.Duration - // tracks message count by peer, so we can prevent abuse - msgCountByPeer *cmn.CMap - maxMsgCountByPeer uint16 + // maps to prevent abuse + requestsSent *cmn.CMap // unanswered send requests + lastReceivedRequests *cmn.CMap // last time peer requested from us } // PEXReactorConfig holds reactor specific configuration data. @@ -63,11 +53,11 @@ type PEXReactorConfig struct { // NewPEXReactor creates new PEX reactor. func NewPEXReactor(b *AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ - book: b, - config: config, - ensurePeersPeriod: defaultEnsurePeersPeriod, - msgCountByPeer: cmn.NewCMap(), - maxMsgCountByPeer: defaultMaxMsgCountByPeer, + book: b, + config: config, + ensurePeersPeriod: defaultEnsurePeersPeriod, + requestsSent: cmn.NewCMap(), + lastReceivedRequests: cmn.NewCMap(), } r.BaseReactor = *NewBaseReactor("PEXReactor", r) return r @@ -83,7 +73,6 @@ func (r *PEXReactor) OnStart() error { return err } go r.ensurePeersRoutine() - go r.flushMsgCountByPeer() return nil } @@ -108,15 +97,17 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { // or by requesting more addresses (if outbound). func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { - // For outbound peers, the address is already in the books. - // Either it was added in DialPeersAsync or when we - // received the peer's address in r.Receive + // For outbound peers, the address is already in the books - + // either via DialPeersAsync or r.Receive. + // Ask it for more peers if we need. if r.book.NeedMoreAddrs() { r.RequestPEX(p) } } else { - // For inbound connections, the peer is its own source, - // and its NodeInfo has already been validated + // For inbound peers, the peer is its own source, + // and its NodeInfo has already been validated. + // Let the ensurePeersRoutine handle asking for more + // peers when we need - we don't trust inbound peers as much. addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } @@ -124,20 +115,13 @@ func (r *PEXReactor) AddPeer(p Peer) { // RemovePeer implements Reactor. func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { - // If we aren't keeping track of local temp data for each peer here, then we - // don't have to do anything. + id := string(p.ID()) + r.requestsSent.Delete(id) + r.lastReceivedRequests.Delete(id) } // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { - srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddr.ID) - if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { - r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) - // TODO remove src from peers? - return - } - _, msg, err := DecodeMessage(msgBytes) if err != nil { r.Logger.Error("Error decoding message", "err", err) @@ -147,27 +131,81 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { switch msg := msg.(type) { case *pexRequestMessage: - // src requested some peers. - // NOTE: we might send an empty selection + // We received a request for peers from src. + if err := r.receiveRequest(src); err != nil { + r.Switch.StopPeerForError(src, err) + return + } r.SendAddrs(src, r.book.GetSelection()) case *pexAddrsMessage: // We received some peer addresses from src. - // TODO: (We don't want to get spammed with bad peers) - for _, netAddr := range msg.Addrs { - if netAddr != nil { - r.book.AddAddress(netAddr, srcAddr) - } + if err := r.ReceivePEX(msg.Addrs, src); err != nil { + r.Switch.StopPeerForError(src, err) + return } default: r.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } } -// RequestPEX asks peer for more addresses. +func (r *PEXReactor) receiveRequest(src Peer) error { + id := string(src.ID()) + v := r.lastReceivedRequests.Get(id) + if v == nil { + // initialize with empty time + lastReceived := time.Time{} + r.lastReceivedRequests.Set(id, lastReceived) + return nil + } + + lastReceived := v.(time.Time) + if lastReceived.Equal(time.Time{}) { + // first time gets a free pass. then we start tracking the time + lastReceived := time.Now() + r.lastReceivedRequests.Set(id, lastReceived) + return nil + } + + now := time.Now() + if now.Sub(lastReceived) < r.ensurePeersPeriod/3 { + return fmt.Errorf("Peer (%v) is sending too many PEX requests. Disconnecting", src.ID()) + } + r.lastReceivedRequests.Set(id, now) + return nil +} + +// RequestPEX asks peer for more addresses if we do not already +// have a request out for this peer. func (r *PEXReactor) RequestPEX(p Peer) { + id := string(p.ID()) + if r.requestsSent.Has(id) { + return + } + r.requestsSent.Set(id, struct{}{}) p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) } +// ReceivePEX adds the given addrs to the addrbook if theres an open +// 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) ReceivePEX(addrs []*NetAddress, src Peer) error { + id := string(src.ID()) + + if !r.requestsSent.Has(id) { + return errors.New("Received unsolicited pexAddrsMessage") + } + + r.requestsSent.Delete(id) + + srcAddr := src.NodeInfo().NetAddress() + for _, netAddr := range addrs { + if netAddr != nil { + r.book.AddAddress(netAddr, srcAddr) + } + } + return nil +} + // SendAddrs sends addrs to the peer. func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*NetAddress) { p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) @@ -178,41 +216,13 @@ func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) { r.ensurePeersPeriod = d } -// SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'. -func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { - r.maxMsgCountByPeer = v -} - -// ReachedMaxMsgCountForPeer returns true if we received too many -// messages from peer with address `addr`. -// NOTE: assumes the value in the CMap is non-nil -func (r *PEXReactor) ReachedMaxMsgCountForPeer(peerID ID) bool { - return r.msgCountByPeer.Get(string(peerID)).(uint16) >= r.maxMsgCountByPeer -} - -// Increment or initialize the msg count for the peer in the CMap -func (r *PEXReactor) IncrementMsgCountForPeer(peerID ID) { - var count uint16 - countI := r.msgCountByPeer.Get(string(peerID)) - if countI != nil { - count = countI.(uint16) - } - count++ - r.msgCountByPeer.Set(string(peerID), count) -} - // Ensures that sufficient peers are connected. (continuous) func (r *PEXReactor) ensurePeersRoutine() { // Randomize when routine starts ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6 time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond) - // fire once immediately. - r.ensurePeers() - - // fire periodically ticker := time.NewTicker(r.ensurePeersPeriod) - for { select { case <-ticker.C: @@ -298,20 +308,6 @@ func (r *PEXReactor) ensurePeers() { } } -func (r *PEXReactor) flushMsgCountByPeer() { - ticker := time.NewTicker(msgCountByPeerFlushInterval) - - for { - select { - case <-ticker.C: - r.msgCountByPeer.Clear() - case <-r.Quit: - ticker.Stop() - return - } - } -} - //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 20c8b823..0c1c1733 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -153,9 +153,11 @@ func TestPEXReactorReceive(t *testing.T) { peer := createRandomPeer(false) + // we have to send a request to receive responses + r.RequestPEX(peer) + size := book.Size() - netAddr, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) - addrs := []*NetAddress{netAddr} + addrs := []*NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) assert.Equal(size+1, book.Size()) @@ -164,7 +166,7 @@ func TestPEXReactorReceive(t *testing.T) { r.Receive(PexChannel, peer, msg) } -func TestPEXReactorAbuseFromPeer(t *testing.T) { +func TestPEXReactorRequestMessageAbuse(t *testing.T) { assert, require := assert.New(t), require.New(t) dir, err := ioutil.TempDir("", "pex_reactor") @@ -174,17 +176,66 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { book.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) + sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw.SetLogger(log.TestingLogger()) + sw.AddReactor("PEX", r) + r.SetSwitch(sw) r.SetLogger(log.TestingLogger()) - r.SetMaxMsgCountByPeer(5) - peer := createRandomPeer(false) + peer := newMockPeer() + id := string(peer.ID()) msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) - for i := 0; i < 10; i++ { - r.Receive(PexChannel, peer, msg) - } - assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ID())) + // first time creates the entry + r.Receive(PexChannel, peer, msg) + assert.True(r.lastReceivedRequests.Has(id)) + + // next time sets the last time value + r.Receive(PexChannel, peer, msg) + assert.True(r.lastReceivedRequests.Has(id)) + + // third time is too many too soon - peer is removed + r.Receive(PexChannel, peer, msg) + assert.False(r.lastReceivedRequests.Has(id)) + assert.False(sw.Peers().Has(peer.ID())) + +} + +func TestPEXReactorAddrsMessageAbuse(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(err) + defer os.RemoveAll(dir) // nolint: errcheck + book := NewAddrBook(dir+"addrbook.json", true) + book.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{}) + sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw.SetLogger(log.TestingLogger()) + sw.AddReactor("PEX", r) + r.SetSwitch(sw) + r.SetLogger(log.TestingLogger()) + + peer := newMockPeer() + + id := string(peer.ID()) + + // request addrs from the peer + r.RequestPEX(peer) + assert.True(r.requestsSent.Has(id)) + + addrs := []*NetAddress{peer.NodeInfo().NetAddress()} + msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) + + // receive some addrs. should clear the request + r.Receive(PexChannel, peer, msg) + assert.False(r.requestsSent.Has(id)) + + // receiving more addrs causes a disconnect + r.Receive(PexChannel, peer, msg) + assert.False(sw.Peers().Has(peer.ID())) } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { @@ -252,3 +303,36 @@ func createRandomPeer(outbound bool) *peer { p.SetLogger(log.TestingLogger().With("peer", addr)) return p } + +type mockPeer struct { + *cmn.BaseService + pubKey crypto.PubKey + addr *NetAddress + outbound, persistent bool +} + +func newMockPeer() mockPeer { + _, netAddr := createRoutableAddr() + mp := mockPeer{ + addr: netAddr, + pubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), + } + mp.BaseService = cmn.NewBaseService(nil, "MockPeer", mp) + mp.Start() + return mp +} + +func (mp mockPeer) ID() ID { return PubKeyToID(mp.pubKey) } +func (mp mockPeer) IsOutbound() bool { return mp.outbound } +func (mp mockPeer) IsPersistent() bool { return mp.persistent } +func (mp mockPeer) NodeInfo() NodeInfo { + return NodeInfo{ + PubKey: mp.pubKey, + ListenAddr: mp.addr.DialString(), + } +} +func (mp mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } +func (mp mockPeer) Send(byte, interface{}) bool { return false } +func (mp mockPeer) TrySend(byte, interface{}) bool { return false } +func (mp mockPeer) Set(string, interface{}) {} +func (mp mockPeer) Get(string) interface{} { return nil } From 17f7a9b510ae575d343d11c4e8dc1991cceb074e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 03:56:15 -0500 Subject: [PATCH 067/124] improve seed dialing logic --- p2p/pex_reactor.go | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 63862d1a..fd4e35e0 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -72,6 +72,11 @@ func (r *PEXReactor) OnStart() error { if err != nil && err != cmn.ErrAlreadyStarted { return err } + + if err := r.checkSeeds(); err != nil { + return err + } + go r.ensurePeersRoutine() return nil } @@ -222,6 +227,11 @@ func (r *PEXReactor) ensurePeersRoutine() { ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6 time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond) + // fire once immediately. + // ensures we dial the seeds right away if the book is empty + r.ensurePeers() + + // fire periodically ticker := time.NewTicker(r.ensurePeersPeriod) for { select { @@ -301,10 +311,43 @@ func (r *PEXReactor) ensurePeers() { } } - // If we are not connected to nor dialing anybody, fallback to dialing seeds. + // If we are not connected to nor dialing anybody, fallback to dialing a seed. if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { - r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) - r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) + r.Logger.Info("No addresses to dial nor connected peers. Falling back to seeds") + r.dialSeed() + } +} + +func (r *PEXReactor) checkSeeds() error { + lSeeds := len(r.config.Seeds) + if lSeeds > 0 { + seedAddrs, errs := NewNetAddressStrings(r.config.Seeds) + for _, err := range errs { + if err != nil { + return err + } + } + } +} + +func (r *PEXReactor) dialSeed() error { + lSeeds := len(r.config.Seeds) + if lSeeds > 0 { + seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) + + perm := r.Switch.rng.Perm(lSeeds) + for _, i := range perm { + // dial a random seed + seedAddr := seedAddrs[i] + peer, err := sw.DialPeerWithAddress(seedAddr, false) + if err != nil { + sw.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) + } else { + sw.Logger.Info("Connected to seed", "peer", peer) + return + } + } + sw.Logger.Error("Couldn't connect to any seeds") } } From a29c67563c2b63cd1dabd746a80264742d716669 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 14 Jan 2018 13:43:26 +0000 Subject: [PATCH 068/124] Update p2p README, closes #1102 --- p2p/README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/p2p/README.md b/p2p/README.md index a30b83b7..f386c511 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -1,14 +1,11 @@ -# `tendermint/tendermint/p2p` +# p2p -[![CircleCI](https://circleci.com/gh/tendermint/tendermint/p2p.svg?style=svg)](https://circleci.com/gh/tendermint/tendermint/p2p) +The p2p package provides an abstraction around peer-to-peer communication. -`tendermint/tendermint/p2p` provides an abstraction around peer-to-peer communication.
- -See: - -- [docs/connection] for details on how connections and multiplexing work -- [docs/peer] for details on peer ID, handshakes, and peer exchange -- [docs/node] for details about different types of nodes and how they should work -- [docs/pex] for details on peer discovery and exchange -- [docs/config] for details on some config options +Docs: +- [Connection](../docs/specification/new-spec/p2p/connection.md) for details on how connections and multiplexing work +- [Peer](../docs/specification/new-spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange +- [Node](../docs/specification/new-spec/p2p/node.md) for details about different types of nodes and how they should work +- [Pex](../docs/specification/new-spec/p2p/pex.md) for details on peer discovery and exchange +- [Config](../docs/specification/new-spec/p2p/config.md) for details on some config option From 26aaa283a9266fcbdf18294cb5b450d1b2cf348a Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Sun, 14 Jan 2018 13:49:27 +0000 Subject: [PATCH 069/124] p2p: remove deprecated Dockerfile --- p2p/Dockerfile | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 p2p/Dockerfile diff --git a/p2p/Dockerfile b/p2p/Dockerfile deleted file mode 100644 index 6c71b2f8..00000000 --- a/p2p/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM golang:latest - -RUN curl https://glide.sh/get | sh - -RUN mkdir -p /go/src/github.com/tendermint/tendermint/p2p -WORKDIR /go/src/github.com/tendermint/tendermint/p2p - -COPY glide.yaml /go/src/github.com/tendermint/tendermint/p2p/ -COPY glide.lock /go/src/github.com/tendermint/tendermint/p2p/ - -RUN glide install - -COPY . /go/src/github.com/tendermint/tendermint/p2p From fc7915ab4c1e4780d07dc5380bcc0e638ac87b6f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 13:03:57 -0500 Subject: [PATCH 070/124] fixes from review --- p2p/pex_reactor.go | 58 +++++++++++++++++++++++------------------ p2p/pex_reactor_test.go | 9 ++++++- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index fd4e35e0..93fddc11 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -39,8 +39,8 @@ type PEXReactor struct { ensurePeersPeriod time.Duration // maps to prevent abuse - requestsSent *cmn.CMap // unanswered send requests - lastReceivedRequests *cmn.CMap // last time peer requested from us + requestsSent *cmn.CMap // ID->struct{}: unanswered send requests + lastReceivedRequests *cmn.CMap // ID->time.Time: last time peer requested from us } // PEXReactorConfig holds reactor specific configuration data. @@ -73,6 +73,7 @@ func (r *PEXReactor) OnStart() error { return err } + // return err if user provided a bad seed address if err := r.checkSeeds(); err != nil { return err } @@ -166,7 +167,7 @@ func (r *PEXReactor) receiveRequest(src Peer) error { lastReceived := v.(time.Time) if lastReceived.Equal(time.Time{}) { // first time gets a free pass. then we start tracking the time - lastReceived := time.Now() + lastReceived = time.Now() r.lastReceivedRequests.Set(id, lastReceived) return nil } @@ -318,37 +319,42 @@ func (r *PEXReactor) ensurePeers() { } } +// check seed addresses are well formed func (r *PEXReactor) checkSeeds() error { lSeeds := len(r.config.Seeds) - if lSeeds > 0 { - seedAddrs, errs := NewNetAddressStrings(r.config.Seeds) - for _, err := range errs { - if err != nil { - return err - } + if lSeeds == 0 { + return nil + } + _, errs := NewNetAddressStrings(r.config.Seeds) + for _, err := range errs { + if err != nil { + return err } } + return nil } -func (r *PEXReactor) dialSeed() error { +// randomly dial seeds until we connect to one or exhaust them +func (r *PEXReactor) dialSeed() { lSeeds := len(r.config.Seeds) - if lSeeds > 0 { - seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) - - perm := r.Switch.rng.Perm(lSeeds) - for _, i := range perm { - // dial a random seed - seedAddr := seedAddrs[i] - peer, err := sw.DialPeerWithAddress(seedAddr, false) - if err != nil { - sw.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) - } else { - sw.Logger.Info("Connected to seed", "peer", peer) - return - } - } - sw.Logger.Error("Couldn't connect to any seeds") + if lSeeds == 0 { + return } + seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) + + perm := r.Switch.rng.Perm(lSeeds) + for _, i := range perm { + // dial a random seed + seedAddr := seedAddrs[i] + peer, err := r.Switch.DialPeerWithAddress(seedAddr, false) + if err != nil { + r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) + } else { + r.Switch.Logger.Info("Connected to seed", "peer", peer) + return + } + } + r.Switch.Logger.Error("Couldn't connect to any seeds") } //----------------------------------------------------------------------------- diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 0c1c1733..c0681586 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -183,6 +183,8 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { r.SetLogger(log.TestingLogger()) peer := newMockPeer() + sw.peers.Add(peer) + assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) @@ -190,16 +192,17 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { // first time creates the entry r.Receive(PexChannel, peer, msg) assert.True(r.lastReceivedRequests.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // next time sets the last time value r.Receive(PexChannel, peer, msg) assert.True(r.lastReceivedRequests.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // third time is too many too soon - peer is removed r.Receive(PexChannel, peer, msg) assert.False(r.lastReceivedRequests.Has(id)) assert.False(sw.Peers().Has(peer.ID())) - } func TestPEXReactorAddrsMessageAbuse(t *testing.T) { @@ -219,12 +222,15 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { r.SetLogger(log.TestingLogger()) peer := newMockPeer() + sw.peers.Add(peer) + assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) // request addrs from the peer r.RequestPEX(peer) assert.True(r.requestsSent.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) addrs := []*NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) @@ -232,6 +238,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { // receive some addrs. should clear the request r.Receive(PexChannel, peer, msg) assert.False(r.requestsSent.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // receiving more addrs causes a disconnect r.Receive(PexChannel, peer, msg) From 620c957a444e51bcb6124d985f059b43b0273a37 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 13:24:43 -0500 Subject: [PATCH 071/124] fix test --- node/node.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 4db7f44d..6c816a9e 100644 --- a/node/node.go +++ b/node/node.go @@ -251,8 +251,12 @@ func NewNode(config *cfg.Config, trustMetricStore = trust.NewTrustMetricStore(trustHistoryDB, trust.DefaultConfig()) trustMetricStore.SetLogger(p2pLogger) + var seeds []string + if config.P2P.Seeds != "" { + seeds = strings.Split(config.P2P.Seeds, ",") + } pexReactor := p2p.NewPEXReactor(addrBook, - &p2p.PEXReactorConfig{Seeds: strings.Split(config.P2P.Seeds, ",")}) + &p2p.PEXReactorConfig{Seeds: seeds}) pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } From 9d4d939b89cbe6c589ba16926242abcc8f081d9e Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Thu, 18 Jan 2018 15:35:00 +0000 Subject: [PATCH 072/124] docs: tx formats: closes #1083, #536 --- docs/using-tendermint.rst | 53 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index a7749d47..7956a972 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -68,7 +68,7 @@ Transactions ------------ To send a transaction, use ``curl`` to make requests to the Tendermint -RPC server: +RPC server, for example: :: @@ -93,6 +93,54 @@ Visit http://localhost:46657 in your browser to see the list of other endpoints. Some take no arguments (like ``/status``), while others specify the argument name and use ``_`` as a placeholder. +Formatting +~~~~~~~~~~ + +The following nuances when sending/formatting transactions should +be taken into account: + +With ``GET``: + +To send a UTF8 string byte array, quote the value of the tx pramater: + +:: + + curl http://localhost:46657/broadcast_tx_commit?tx="hello" + +which sends a 5 byte transaction: "h e l l o" [68 65 6c 6c 6f]. + +Using a special character: + +:: + + curl http://localhost:46657/broadcast_tx_commit?tx="€5" + +sends a 4 byte transaction: "€5" (UTF8) [e2 82 ac 35]. + +To send as raw hex, omit quotes AND prefix the hex string with ``0x``: + +:: + + curl http://localhost:46657/broadcast_tx_commit?tx=0x01020304 + +which sends a 4 byte transaction: [01 02 03 04]. + +With ``POST`` (using ``json``), the raw hex must be ``base64`` encoded: + +:: + + curl -X POST http://localhots:46657 + { + "method": broadcast_tx_commit", + "jsonrpc": "2.0", + "params": {"tx": "AQIDBA=="}, + "id": "" + } + +which sends the same 4 byte transaction: [01 02 03 04]. + +Note that raw hex cannot be used in ``POST`` transactions. + Reset ----- @@ -196,6 +244,9 @@ can take on the order of a second. For a quick result, use ``broadcast_tx_sync``, but the transaction will not be committed until later, and by that point its effect on the state may change. +Note: see the Transactions => Formatting section for details about +transaction formating. + Tendermint Networks ------------------- From 0a20e8f2682ce4854afaaa698c6bef592a187567 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Thu, 18 Jan 2018 19:39:07 +0100 Subject: [PATCH 073/124] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..902469b4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ + + +* [ ] Updated all relevant documentation in docs +* [ ] Updated all code comments where relevant +* [ ] Wrote tests +* [ ] Updated CHANGELOG.md From f57f97c4bd30a35946a0c59cb1157c36e126ceb5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Jan 2018 17:40:12 -0500 Subject: [PATCH 074/124] fix bash tests --- .../pex/{dial_persistent_peers.sh => dial_peers.sh} | 13 +++++++------ test/p2p/pex/test.sh | 4 ++-- ..._dial_persistent_peers.sh => test_dial_peers.sh} | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) rename test/p2p/pex/{dial_persistent_peers.sh => dial_peers.sh} (57%) rename test/p2p/pex/{test_dial_persistent_peers.sh => test_dial_peers.sh} (78%) diff --git a/test/p2p/pex/dial_persistent_peers.sh b/test/p2p/pex/dial_peers.sh similarity index 57% rename from test/p2p/pex/dial_persistent_peers.sh rename to test/p2p/pex/dial_peers.sh index 95c1d6e9..ddda7dbe 100644 --- a/test/p2p/pex/dial_persistent_peers.sh +++ b/test/p2p/pex/dial_peers.sh @@ -19,13 +19,14 @@ for i in `seq 1 $N`; do done set -e -# persistent_peers need quotes -persistent_peers="\"$(test/p2p/ip.sh 1):46656\"" +# peers need quotes +peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - persistent_peers="$persistent_peers,\"$(test/p2p/ip.sh $i):46656\"" + peers="$peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $persistent_peers +echo $peers -echo $persistent_peers +echo $peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "persistent_peers=[$persistent_peers]" "$IP:46657/dial_persistent_peers" +curl "$IP:46657/dial_peers?persistent=true&peers=\[$peers\]" + diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 06d40c3e..ffecd651 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" -echo "Test connecting via /dial_persistent_peers" -bash test/p2p/pex/test_dial_persistent_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" +echo "Test connecting via /dial_peers" +bash test/p2p/pex/test_dial_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" diff --git a/test/p2p/pex/test_dial_persistent_peers.sh b/test/p2p/pex/test_dial_peers.sh similarity index 78% rename from test/p2p/pex/test_dial_persistent_peers.sh rename to test/p2p/pex/test_dial_peers.sh index 7dda62b1..d0b04234 100644 --- a/test/p2p/pex/test_dial_persistent_peers.sh +++ b/test/p2p/pex/test_dial_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_persistent_peers call" +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 @@ -26,11 +26,11 @@ bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $ -# dial persistent_peers from one node -CLIENT_NAME="dial_persistent_peers" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_persistent_peers.sh $N" +# dial peers from one node +CLIENT_NAME="dial_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_persistent_peers_basic" +CLIENT_NAME="dial_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N" From e764a180d8e60f76554238a225ecde3e759b50df Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Jan 2018 18:58:21 -0500 Subject: [PATCH 075/124] docs: fix tx formats [ci skip] --- docs/using-tendermint.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 7956a972..f4b0cb08 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -105,15 +105,24 @@ To send a UTF8 string byte array, quote the value of the tx pramater: :: - curl http://localhost:46657/broadcast_tx_commit?tx="hello" + curl 'http://localhost:46657/broadcast_tx_commit?tx="hello"' which sends a 5 byte transaction: "h e l l o" [68 65 6c 6c 6f]. +Note the URL must be wrapped with single quoes, else bash will ignore the double quotes. +To avoid the single quotes, escape the double quotes: + +:: + + curl http://localhost:46657/broadcast_tx_commit?tx=\"hello\" + + + Using a special character: :: - curl http://localhost:46657/broadcast_tx_commit?tx="€5" + curl 'http://localhost:46657/broadcast_tx_commit?tx="€5"' sends a 4 byte transaction: "€5" (UTF8) [e2 82 ac 35]. @@ -129,13 +138,7 @@ With ``POST`` (using ``json``), the raw hex must be ``base64`` encoded: :: - curl -X POST http://localhots:46657 - { - "method": broadcast_tx_commit", - "jsonrpc": "2.0", - "params": {"tx": "AQIDBA=="}, - "id": "" - } + curl --data-binary '{"jsonrpc":"2.0","id":"anything","method":"broadcast_tx_commit","params": {"tx": "AQIDBA=="}}' -H 'content-type:text/plain;' http://localhost:46657 which sends the same 4 byte transaction: [01 02 03 04]. From cca597a9c05987ff3fbbb0696da65ed6904f3b9d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Jan 2018 23:12:20 -0500 Subject: [PATCH 076/124] fix and test config file --- cmd/tendermint/commands/root.go | 6 +- cmd/tendermint/commands/root_test.go | 188 ++++++++++++++++++--------- config/config.go | 3 +- config/toml.go | 2 + glide.lock | 22 ++-- glide.yaml | 2 +- 6 files changed, 148 insertions(+), 75 deletions(-) diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index a54b5006..e6a17566 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -18,7 +18,11 @@ var ( ) func init() { - RootCmd.PersistentFlags().String("log_level", config.LogLevel, "Log level") + registerFlagsRootCmd(RootCmd) +} + +func registerFlagsRootCmd(cmd *cobra.Command) { + cmd.PersistentFlags().String("log_level", config.LogLevel, "Log level") } // ParseConfig retrieves the default environment configuration, diff --git a/cmd/tendermint/commands/root_test.go b/cmd/tendermint/commands/root_test.go index 8217ee16..59d258af 100644 --- a/cmd/tendermint/commands/root_test.go +++ b/cmd/tendermint/commands/root_test.go @@ -1,7 +1,10 @@ package commands import ( + "fmt" + "io/ioutil" "os" + "path/filepath" "strconv" "testing" @@ -12,6 +15,7 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tmlibs/cli" + cmn "github.com/tendermint/tmlibs/common" ) var ( @@ -22,89 +26,151 @@ const ( rootName = "root" ) -// isolate provides a clean setup and returns a copy of RootCmd you can -// modify in the test cases. -// NOTE: it unsets all TM* env variables. -func isolate(cmds ...*cobra.Command) cli.Executable { +// clearConfig clears env vars, the given root dir, and resets viper. +func clearConfig(dir string) { if err := os.Unsetenv("TMHOME"); err != nil { panic(err) } if err := os.Unsetenv("TM_HOME"); err != nil { panic(err) } - if err := os.RemoveAll(defaultRoot); err != nil { + + if err := os.RemoveAll(dir); err != nil { panic(err) } - viper.Reset() config = cfg.DefaultConfig() - r := &cobra.Command{ - Use: rootName, - PersistentPreRunE: RootCmd.PersistentPreRunE, - } - r.AddCommand(cmds...) - wr := cli.PrepareBaseCmd(r, "TM", defaultRoot) - return wr } -func TestRootConfig(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - // we pre-create a config file we can refer to in the rest of - // the test cases. - cvals := map[string]string{ - "moniker": "monkey", - "fast_sync": "false", +// prepare new rootCmd +func testRootCmd() *cobra.Command { + rootCmd := &cobra.Command{ + Use: RootCmd.Use, + PersistentPreRunE: RootCmd.PersistentPreRunE, + Run: func(cmd *cobra.Command, args []string) {}, } - // proper types of the above settings - cfast := false - conf, err := cli.WriteDemoConfig(cvals) - require.Nil(err) + registerFlagsRootCmd(rootCmd) + var l string + rootCmd.PersistentFlags().String("log", l, "Log") + return rootCmd +} +func testSetup(rootDir string, args []string, env map[string]string) error { + clearConfig(defaultRoot) + + rootCmd := testRootCmd() + cmd := cli.PrepareBaseCmd(rootCmd, "TM", defaultRoot) + + // run with the args and env + args = append([]string{rootCmd.Use}, args...) + return cli.RunWithArgs(cmd, args, env) +} + +func TestRootHome(t *testing.T) { + newRoot := filepath.Join(defaultRoot, "something-else") + cases := []struct { + args []string + env map[string]string + root string + }{ + {nil, nil, defaultRoot}, + {[]string{"--home", newRoot}, nil, newRoot}, + {nil, map[string]string{"TMHOME": newRoot}, newRoot}, + } + + for i, tc := range cases { + idxString := strconv.Itoa(i) + + err := testSetup(defaultRoot, tc.args, tc.env) + require.Nil(t, err, idxString) + + assert.Equal(t, tc.root, config.RootDir, idxString) + assert.Equal(t, tc.root, config.P2P.RootDir, idxString) + assert.Equal(t, tc.root, config.Consensus.RootDir, idxString) + assert.Equal(t, tc.root, config.Mempool.RootDir, idxString) + } +} + +func TestRootFlagsEnv(t *testing.T) { + + // defaults defaults := cfg.DefaultConfig() - dmax := defaults.P2P.MaxNumPeers + defaultLogLvl := defaults.LogLevel cases := []struct { args []string env map[string]string - root string - moniker string - fastSync bool - maxPeer int + logLevel string }{ - {nil, nil, defaultRoot, defaults.Moniker, defaults.FastSync, dmax}, - // try multiple ways of setting root (two flags, cli vs. env) - {[]string{"--home", conf}, nil, conf, cvals["moniker"], cfast, dmax}, - {nil, map[string]string{"TMHOME": conf}, conf, cvals["moniker"], cfast, dmax}, - // check setting p2p subflags two different ways - {[]string{"--p2p.max_num_peers", "420"}, nil, defaultRoot, defaults.Moniker, defaults.FastSync, 420}, - {nil, map[string]string{"TM_P2P_MAX_NUM_PEERS": "17"}, defaultRoot, defaults.Moniker, defaults.FastSync, 17}, - // try to set env that have no flags attached... - {[]string{"--home", conf}, map[string]string{"TM_MONIKER": "funny"}, conf, "funny", cfast, dmax}, + {[]string{"--log", "debug"}, nil, defaultLogLvl}, // wrong flag + {[]string{"--log_level", "debug"}, nil, "debug"}, // right flag + {nil, map[string]string{"TM_LOW": "debug"}, defaultLogLvl}, // wrong env flag + {nil, map[string]string{"MT_LOG_LEVEL": "debug"}, defaultLogLvl}, // wrong env prefix + {nil, map[string]string{"TM_LOG_LEVEL": "debug"}, "debug"}, // right env } - for idx, tc := range cases { - i := strconv.Itoa(idx) - // test command that does nothing, except trigger unmarshalling in root - noop := &cobra.Command{ - Use: "noop", - RunE: func(cmd *cobra.Command, args []string) error { - return nil - }, - } - noop.Flags().Int("p2p.max_num_peers", defaults.P2P.MaxNumPeers, "") - cmd := isolate(noop) + for i, tc := range cases { + idxString := strconv.Itoa(i) - args := append([]string{rootName, noop.Use}, tc.args...) - err := cli.RunWithArgs(cmd, args, tc.env) - require.Nil(err, i) - assert.Equal(tc.root, config.RootDir, i) - assert.Equal(tc.root, config.P2P.RootDir, i) - assert.Equal(tc.root, config.Consensus.RootDir, i) - assert.Equal(tc.root, config.Mempool.RootDir, i) - assert.Equal(tc.moniker, config.Moniker, i) - assert.Equal(tc.fastSync, config.FastSync, i) - assert.Equal(tc.maxPeer, config.P2P.MaxNumPeers, i) + err := testSetup(defaultRoot, tc.args, tc.env) + require.Nil(t, err, idxString) + + assert.Equal(t, tc.logLevel, config.LogLevel, idxString) } - +} + +func TestRootConfig(t *testing.T) { + + // write non-default config + nonDefaultLogLvl := "abc:debug" + cvals := map[string]string{ + "log_level": nonDefaultLogLvl, + } + + cases := []struct { + args []string + env map[string]string + + logLvl string + }{ + {nil, nil, nonDefaultLogLvl}, // should load config + {[]string{"--log_level=abc:info"}, nil, "abc:info"}, // flag over rides + {nil, map[string]string{"TM_LOG_LEVEL": "abc:info"}, "abc:info"}, // env over rides + } + + for i, tc := range cases { + idxString := strconv.Itoa(i) + clearConfig(defaultRoot) + + // XXX: path must match cfg.defaultConfigPath + configFilePath := filepath.Join(defaultRoot, "config") + err := cmn.EnsureDir(configFilePath, 0700) + require.Nil(t, err) + + // write the non-defaults to a different path + // TODO: support writing sub configs so we can test that too + err = WriteConfigVals(configFilePath, cvals) + require.Nil(t, err) + + rootCmd := testRootCmd() + cmd := cli.PrepareBaseCmd(rootCmd, "TM", defaultRoot) + + // run with the args and env + tc.args = append([]string{rootCmd.Use}, tc.args...) + err = cli.RunWithArgs(cmd, tc.args, tc.env) + require.Nil(t, err, idxString) + + assert.Equal(t, tc.logLvl, config.LogLevel, idxString) + } +} + +// WriteConfigVals writes a toml file with the given values. +// It returns an error if writing was impossible. +func WriteConfigVals(dir string, vals map[string]string) error { + data := "" + for k, v := range vals { + data = data + fmt.Sprintf("%s = \"%s\"\n", k, v) + } + cfile := filepath.Join(dir, "config.toml") + return ioutil.WriteFile(cfile, []byte(data), 0666) } diff --git a/config/config.go b/config/config.go index f93b7924..9103de10 100644 --- a/config/config.go +++ b/config/config.go @@ -7,11 +7,12 @@ import ( "time" ) -// Note: Most of the structs & relevant comments + the +// NOTE: Most of the structs & relevant comments + the // default configuration options were used to manually // generate the config.toml. Please reflect any changes // made here in the defaultConfigTemplate constant in // config/toml.go +// NOTE: tmlibs/cli must know to look in the config dir! var ( DefaultTendermintDir = ".tendermint" defaultConfigDir = "config" diff --git a/config/toml.go b/config/toml.go index e7bd2610..bdc9f5a6 100644 --- a/config/toml.go +++ b/config/toml.go @@ -20,6 +20,8 @@ func init() { /****** these are for production settings ***********/ +// EnsureRoot creates the root, config, and data directories if they don't exist, +// and panics if it fails. func EnsureRoot(rootDir string) { if err := cmn.EnsureDir(rootDir, 0700); err != nil { cmn.PanicSanity(err.Error()) diff --git a/glide.lock b/glide.lock index b8335886..383772f0 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 072c8e685dd519c1f509da67379b70451a681bf3ef6cbd82900a1f68c55bbe16 -updated: 2017-12-29T11:08:17.355999228-05:00 +hash: 9399a10e80d255104f8ec07b5d495c41d8a3f7a421f9da97ebd78c65189f381d +updated: 2018-01-18T23:11:10.703734578-05:00 imports: - name: github.com/btcsuite/btcd version: 2e60448ffcc6bf78332d1fe590260095f554dd78 @@ -10,7 +10,7 @@ imports: - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 - name: github.com/go-kit/kit - version: 953e747656a7bbb5e1f998608b460458958b70cc + version: 53f10af5d5c7375d4655a3d6852457ed17ab5cc7 subpackages: - log - log/level @@ -68,13 +68,13 @@ imports: - name: github.com/mitchellh/mapstructure version: 06020f85339e21b2478f756a78e295255ffa4d6a - name: github.com/pelletier/go-toml - version: 0131db6d737cfbbfb678f8b7d92e55e27ce46224 + version: 4e9e0ee19b60b13eb79915933f44d8ed5f268bdd - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/rcrowley/go-metrics version: e181e095bae94582363434144c61a9653aff6e50 - name: github.com/spf13/afero - version: 57afd63c68602b63ed976de00dd066ccb3c319db + version: 8d919cbe7e2627e417f3e45c3c0e489a5b7e2536 subpackages: - mem - name: github.com/spf13/cast @@ -88,7 +88,7 @@ imports: - name: github.com/spf13/viper version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 - name: github.com/syndtr/goleveldb - version: 34011bf325bce385408353a30b101fe5e923eb6e + version: adf24ef3f94bd13ec4163060b21a5678f22b429b subpackages: - leveldb - leveldb/cache @@ -129,7 +129,7 @@ imports: subpackages: - iavl - name: github.com/tendermint/tmlibs - version: 91b4b534ad78e442192c8175db92a06a51064064 + version: 15e51fa76086a3c505f227679c2478043ae7261b subpackages: - autofile - cli @@ -144,7 +144,7 @@ imports: - pubsub/query - test - name: golang.org/x/crypto - version: 95a4943f35d008beabde8c11e5075a1b714e6419 + version: 94eea52f7b742c7cbe0b03b22f0c4c8631ece122 subpackages: - curve25519 - nacl/box @@ -165,11 +165,11 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 83801418e1b59fb1880e363299581ee543af32ca + version: 8b4580aae2a0dd0c231a45d3ccb8434ff533b840 subpackages: - unix - name: golang.org/x/text - version: e19ae1496984b1c655b8044a65c0300a3c878dd3 + version: 57961680700a5336d15015c8c50686ca5ba362a4 subpackages: - secure/bidirule - transform @@ -199,7 +199,7 @@ imports: - tap - transport - name: gopkg.in/go-playground/validator.v9 - version: b1f51f36f1c98cc97f777d6fc9d4b05eaa0cabb5 + version: 61caf9d3038e1af346dbf5c2e16f6678e1548364 - name: gopkg.in/yaml.v2 version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5 testImports: diff --git a/glide.yaml b/glide.yaml index b7846d64..2302a6dc 100644 --- a/glide.yaml +++ b/glide.yaml @@ -34,7 +34,7 @@ import: subpackages: - iavl - package: github.com/tendermint/tmlibs - version: v0.6.0 + version: v0.6.1 subpackages: - autofile - cli From ebeadfc57e51f3614131ac873fbd9530a452a666 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 00:11:23 -0500 Subject: [PATCH 077/124] dont run metalinter --- Makefile | 6 +++--- test/run_test.sh | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2aed1acf..5fd599cc 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,14 @@ GOTOOLS = \ github.com/mitchellh/gox \ github.com/Masterminds/glide \ github.com/tcnksm/ghr \ - gopkg.in/alecthomas/gometalinter.v2 + # gopkg.in/alecthomas/gometalinter.v2 GOTOOLS_CHECK = gox glide ghr gometalinter.v2 PACKAGES=$(shell go list ./... | grep -v '/vendor/') BUILD_TAGS?=tendermint TMHOME = $${TMHOME:-$$HOME/.tendermint} BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short HEAD`" -all: check build test install metalinter +all: check build test install check: check_tools get_vendor_deps @@ -42,7 +42,7 @@ check_tools: get_tools: @echo "--> Installing tools" go get -u -v $(GOTOOLS) - @gometalinter.v2 --install + # @gometalinter.v2 --install update_tools: @echo "--> Updating tools" diff --git a/test/run_test.sh b/test/run_test.sh index ae2ff6b4..b505126e 100644 --- a/test/run_test.sh +++ b/test/run_test.sh @@ -6,9 +6,6 @@ pwd BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "Current branch: $BRANCH" -# run the linter -make metalinter - # run the go unit tests with coverage bash test/test_cover.sh From 1cb76625d3be9dd25a630b164100436d5665024a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Jan 2018 20:38:19 -0500 Subject: [PATCH 078/124] consensus: rename test funcs --- consensus/mempool_test.go | 10 +++++----- consensus/reactor_test.go | 21 +++++++-------------- consensus/state_test.go | 32 ++++++++++++++++---------------- consensus/wal_test.go | 2 +- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 91acce65..97bc050f 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -19,7 +19,7 @@ func init() { config = ResetConfig("consensus_mempool_test") } -func TestNoProgressUntilTxsAvailable(t *testing.T) { +func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) @@ -37,7 +37,7 @@ func TestNoProgressUntilTxsAvailable(t *testing.T) { ensureNoNewStep(newBlockCh) } -func TestProgressAfterCreateEmptyBlocksInterval(t *testing.T) { +func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") config.Consensus.CreateEmptyBlocksInterval = int(ensureTimeout.Seconds()) state, privVals := randGenesisState(1, false, 10) @@ -52,7 +52,7 @@ func TestProgressAfterCreateEmptyBlocksInterval(t *testing.T) { ensureNewStep(newBlockCh) // until the CreateEmptyBlocksInterval has passed } -func TestProgressInHigherRound(t *testing.T) { +func TestMempoolProgressInHigherRound(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) @@ -94,7 +94,7 @@ func deliverTxsRange(cs *ConsensusState, start, end int) { } } -func TestTxConcurrentWithCommit(t *testing.T) { +func TestMempoolTxConcurrentWithCommit(t *testing.T) { state, privVals := randGenesisState(1, false, 10) cs := newConsensusState(state, privVals[0], NewCounterApplication()) height, round := cs.Height, cs.Round @@ -116,7 +116,7 @@ func TestTxConcurrentWithCommit(t *testing.T) { } } -func TestRmBadTx(t *testing.T) { +func TestMempoolRmBadTx(t *testing.T) { state, privVals := randGenesisState(1, false, 10) app := NewCounterApplication() cs := newConsensusState(state, privVals[0], app) diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index bcf97f94..0fb348bd 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -31,31 +31,24 @@ func startConsensusNet(t *testing.T, css []*ConsensusState, N int) ([]*Consensus reactors := make([]*ConsensusReactor, N) eventChans := make([]chan interface{}, N) eventBuses := make([]*types.EventBus, N) - logger := consensusLogger() for i := 0; i < N; i++ { - /*thisLogger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") + /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ - thisLogger := logger - reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states - reactors[i].conS.SetLogger(thisLogger.With("validator", i)) - reactors[i].SetLogger(thisLogger.With("validator", i)) - - eventBuses[i] = types.NewEventBus() - eventBuses[i].SetLogger(thisLogger.With("module", "events", "validator", i)) - err := eventBuses[i].Start() - require.NoError(t, err) + reactors[i].SetLogger(css[i].Logger.With("validator", "i")) + // eventBus is already started with the cs + eventBuses[i] = css[i].eventBus reactors[i].SetEventBus(eventBuses[i]) eventChans[i] = make(chan interface{}, 1) - err = eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i]) + err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i]) require.NoError(t, err) } // make connected switches and start all reactors p2p.MakeConnectedSwitches(config.P2P, N, func(i int, s *p2p.Switch) *p2p.Switch { s.AddReactor("CONSENSUS", reactors[i]) - s.SetLogger(reactors[i].Logger.With("module", "p2p", "validator", i)) + s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) return s }, p2p.Connect2Switches) @@ -84,7 +77,7 @@ func stopConsensusNet(logger log.Logger, reactors []*ConsensusReactor, eventBuse } // Ensure a testnet makes blocks -func TestReactor(t *testing.T) { +func TestReactorBasic(t *testing.T) { N := 4 css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) reactors, eventChans, eventBuses := startConsensusNet(t, css, N) diff --git a/consensus/state_test.go b/consensus/state_test.go index 97562feb..e6b2a135 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -55,7 +55,7 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh //---------------------------------------------------------------------------------------------------- // ProposeSuite -func TestProposerSelection0(t *testing.T) { +func TestStateProposerSelection0(t *testing.T) { cs1, vss := randConsensusState(4) height, round := cs1.Height, cs1.Round @@ -89,7 +89,7 @@ func TestProposerSelection0(t *testing.T) { } // Now let's do it all again, but starting from round 2 instead of 0 -func TestProposerSelection2(t *testing.T) { +func TestStateProposerSelection2(t *testing.T) { cs1, vss := randConsensusState(4) // test needs more work for more than 3 validators newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -118,7 +118,7 @@ func TestProposerSelection2(t *testing.T) { } // a non-validator should timeout into the prevote round -func TestEnterProposeNoPrivValidator(t *testing.T) { +func TestStateEnterProposeNoPrivValidator(t *testing.T) { cs, _ := randConsensusState(1) cs.SetPrivValidator(nil) height, round := cs.Height, cs.Round @@ -143,7 +143,7 @@ func TestEnterProposeNoPrivValidator(t *testing.T) { } // a validator should not timeout of the prevote round (TODO: unless the block is really big!) -func TestEnterProposeYesPrivValidator(t *testing.T) { +func TestStateEnterProposeYesPrivValidator(t *testing.T) { cs, _ := randConsensusState(1) height, round := cs.Height, cs.Round @@ -179,7 +179,7 @@ func TestEnterProposeYesPrivValidator(t *testing.T) { } } -func TestBadProposal(t *testing.T) { +func TestStateBadProposal(t *testing.T) { cs1, vss := randConsensusState(2) height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -239,7 +239,7 @@ func TestBadProposal(t *testing.T) { // FullRoundSuite // propose, prevote, and precommit a block -func TestFullRound1(t *testing.T) { +func TestStateFullRound1(t *testing.T) { cs, vss := randConsensusState(1) height, round := cs.Height, cs.Round @@ -275,7 +275,7 @@ func TestFullRound1(t *testing.T) { } // nil is proposed, so prevote and precommit nil -func TestFullRoundNil(t *testing.T) { +func TestStateFullRoundNil(t *testing.T) { cs, vss := randConsensusState(1) height, round := cs.Height, cs.Round @@ -293,7 +293,7 @@ func TestFullRoundNil(t *testing.T) { // run through propose, prevote, precommit commit with two validators // where the first validator has to wait for votes from the second -func TestFullRound2(t *testing.T) { +func TestStateFullRound2(t *testing.T) { cs1, vss := randConsensusState(2) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -334,7 +334,7 @@ func TestFullRound2(t *testing.T) { // two validators, 4 rounds. // two vals take turns proposing. val1 locks on first one, precommits nil on everything else -func TestLockNoPOL(t *testing.T) { +func TestStateLockNoPOL(t *testing.T) { cs1, vss := randConsensusState(2) vs2 := vss[1] height := cs1.Height @@ -503,7 +503,7 @@ func TestLockNoPOL(t *testing.T) { } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka -func TestLockPOLRelock(t *testing.T) { +func TestStateLockPOLRelock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -618,7 +618,7 @@ func TestLockPOLRelock(t *testing.T) { } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka -func TestLockPOLUnlock(t *testing.T) { +func TestStateLockPOLUnlock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -715,7 +715,7 @@ func TestLockPOLUnlock(t *testing.T) { // a polka at round 1 but we miss it // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock -func TestLockPOLSafety1(t *testing.T) { +func TestStateLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -838,7 +838,7 @@ func TestLockPOLSafety1(t *testing.T) { // What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 -func TestLockPOLSafety2(t *testing.T) { +func TestStateLockPOLSafety2(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -937,7 +937,7 @@ func TestLockPOLSafety2(t *testing.T) { // TODO: Slashing /* -func TestSlashingPrevotes(t *testing.T) { +func TestStateSlashingPrevotes(t *testing.T) { cs1, vss := randConsensusState(2) vs2 := vss[1] @@ -972,7 +972,7 @@ func TestSlashingPrevotes(t *testing.T) { // XXX: Check for existence of Dupeout info } -func TestSlashingPrecommits(t *testing.T) { +func TestStateSlashingPrecommits(t *testing.T) { cs1, vss := randConsensusState(2) vs2 := vss[1] @@ -1017,7 +1017,7 @@ func TestSlashingPrecommits(t *testing.T) { // 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! -func TestHalt1(t *testing.T) { +func TestStateHalt1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] diff --git a/consensus/wal_test.go b/consensus/wal_test.go index c7f08739..3553591c 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -41,7 +41,7 @@ func TestWALEncoderDecoder(t *testing.T) { } } -func TestSearchForEndHeight(t *testing.T) { +func TestWALSearchForEndHeight(t *testing.T) { walBody, err := WALWithNBlocks(6) if err != nil { t.Fatal(err) From 8171628ee56221e73e341c8a8867f0b2049fe7b7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 00:14:35 -0500 Subject: [PATCH 079/124] make tests run faster --- config/config.go | 3 +++ consensus/common_test.go | 4 ++-- consensus/reactor_test.go | 37 +++++++++++++++---------------------- p2p/test_util.go | 5 +++-- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/config/config.go b/config/config.go index 9103de10..77bd54d3 100644 --- a/config/config.go +++ b/config/config.go @@ -294,6 +294,7 @@ func TestP2PConfig() *P2PConfig { conf := DefaultP2PConfig() conf.ListenAddress = "tcp://0.0.0.0:36656" conf.SkipUPNP = true + conf.FlushThrottleTimeout = 10 return conf } @@ -438,6 +439,8 @@ func TestConsensusConfig() *ConsensusConfig { config.TimeoutPrecommitDelta = 1 config.TimeoutCommit = 10 config.SkipTimeoutCommit = true + config.PeerGossipSleepDuration = 5 + config.PeerQueryMaj23SleepDuration = 50 return config } diff --git a/consensus/common_test.go b/consensus/common_test.go index 95775994..40a320e4 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -36,8 +36,8 @@ const ( ) // genesis, chain_id, priv_val -var config *cfg.Config // NOTE: must be reset for each _test.go file -var ensureTimeout = time.Second * 2 +var config *cfg.Config // NOTE: must be reset for each _test.go file +var ensureTimeout = time.Second * 1 // must be in seconds because CreateEmptyBlocksInterval is func ensureDir(dir string, mode os.FileMode) { if err := cmn.EnsureDir(dir, mode); err != nil { diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 0fb348bd..c1e2a462 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -35,7 +35,7 @@ func startConsensusNet(t *testing.T, css []*ConsensusState, N int) ([]*Consensus /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states - reactors[i].SetLogger(css[i].Logger.With("validator", "i")) + reactors[i].SetLogger(css[i].Logger.With("validator", "i", "module", "consensus")) // eventBus is already started with the cs eventBuses[i] = css[i].eventBus @@ -83,9 +83,8 @@ func TestReactorBasic(t *testing.T) { reactors, eventChans, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) // wait till everyone makes the first new block - timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, N, func(j int) { <-eventChans[j] - wg.Done() }, css) } @@ -106,9 +105,8 @@ func TestReactorProposalHeartbeats(t *testing.T) { require.NoError(t, err) } // wait till everyone sends a proposal heartbeat - timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, N, func(j int) { <-heartbeatChans[j] - wg.Done() }, css) // send a tx @@ -117,9 +115,8 @@ func TestReactorProposalHeartbeats(t *testing.T) { } // wait till everyone makes the first new block - timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, N, func(j int) { <-eventChans[j] - wg.Done() }, css) } @@ -140,9 +137,8 @@ func TestReactorVotingPowerChange(t *testing.T) { } // wait till everyone makes block 1 - timeoutWaitGroup(t, nVals, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, nVals, func(j int) { <-eventChans[j] - wg.Done() }, css) //--------------------------------------------------------------------------- @@ -203,9 +199,8 @@ func TestReactorValidatorSetChanges(t *testing.T) { } // wait till everyone makes block 1 - timeoutWaitGroup(t, nPeers, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, nPeers, func(j int) { <-eventChans[j] - wg.Done() }, css) //--------------------------------------------------------------------------- @@ -293,16 +288,13 @@ func TestReactorWithTimeoutCommit(t *testing.T) { defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) // wait till everyone makes the first new block - timeoutWaitGroup(t, N-1, func(wg *sync.WaitGroup, j int) { + timeoutWaitGroup(t, N-1, func(j int) { <-eventChans[j] - wg.Done() }, css) } func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) { - timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { - defer wg.Done() - + timeoutWaitGroup(t, n, func(j int) { css[j].Logger.Debug("waitForAndValidateBlock") newBlockI, ok := <-eventChans[j] if !ok { @@ -320,8 +312,7 @@ func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{} } func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) { - timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { - defer wg.Done() + timeoutWaitGroup(t, n, func(j int) { ntxs := 0 BLOCK_TX_LOOP: for { @@ -352,8 +343,7 @@ func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]st } func waitForBlockWithUpdatedValsAndValidateIt(t *testing.T, n int, updatedVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState) { - timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { - defer wg.Done() + timeoutWaitGroup(t, n, func(j int) { var newBlock *types.Block LOOP: @@ -391,11 +381,14 @@ func validateBlock(block *types.Block, activeVals map[string]struct{}) error { return nil } -func timeoutWaitGroup(t *testing.T, n int, f func(*sync.WaitGroup, int), css []*ConsensusState) { +func timeoutWaitGroup(t *testing.T, n int, f func(int), css []*ConsensusState) { wg := new(sync.WaitGroup) wg.Add(n) for i := 0; i < n; i++ { - go f(wg, i) + go func(j int) { + f(j) + wg.Done() + }(i) } done := make(chan struct{}) diff --git a/p2p/test_util.go b/p2p/test_util.go index 18167e20..dca23a0e 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -97,7 +97,9 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f nodeKey := &NodeKey{ PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } - s := initSwitch(i, NewSwitch(cfg)) + s := NewSwitch(cfg) + s.SetLogger(log.TestingLogger()) + s = initSwitch(i, s) s.SetNodeInfo(NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), @@ -106,6 +108,5 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodeKey(nodeKey) - s.SetLogger(log.TestingLogger()) return s } From f06cc6630b5fbc915ee02fafebe9e1a1ad12c061 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 00:57:00 -0500 Subject: [PATCH 080/124] mempool: cfg.CacheSize and expose InitWAL --- config/config.go | 18 ++++++++++++++++-- consensus/replay_test.go | 6 ++++-- mempool/mempool.go | 12 +++++------- mempool/mempool_test.go | 5 +++-- node/node.go | 1 + 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/config/config.go b/config/config.go index 77bd54d3..072606b4 100644 --- a/config/config.go +++ b/config/config.go @@ -60,9 +60,9 @@ func TestConfig() *Config { BaseConfig: TestBaseConfig(), RPC: TestRPCConfig(), P2P: TestP2PConfig(), - Mempool: DefaultMempoolConfig(), + Mempool: TestMempoolConfig(), Consensus: TestConsensusConfig(), - TxIndex: DefaultTxIndexConfig(), + TxIndex: TestTxIndexConfig(), } } @@ -313,6 +313,7 @@ type MempoolConfig struct { RecheckEmpty bool `mapstructure:"recheck_empty"` Broadcast bool `mapstructure:"broadcast"` WalPath string `mapstructure:"wal_dir"` + CacheSize int `mapstructure:"cache_size"` } // DefaultMempoolConfig returns a default configuration for the Tendermint mempool @@ -322,9 +323,17 @@ func DefaultMempoolConfig() *MempoolConfig { RecheckEmpty: true, Broadcast: true, WalPath: filepath.Join(defaultDataDir, "mempool.wal"), + CacheSize: 100000, } } +// TestMempoolConfig returns a configuration for testing the Tendermint mempool +func TestMempoolConfig() *MempoolConfig { + config := DefaultMempoolConfig() + config.CacheSize = 1000 + return config +} + // WalDir returns the full path to the mempool's write-ahead log func (m *MempoolConfig) WalDir() string { return rootify(m.WalPath, m.RootDir) @@ -492,6 +501,11 @@ func DefaultTxIndexConfig() *TxIndexConfig { } } +// TestTxIndexConfig returns a default configuration for the transaction indexer. +func TestTxIndexConfig() *TxIndexConfig { + return DefaultTxIndexConfig() +} + //----------------------------------------------------------------------------- // Utils diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 24255262..a4786dde 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -82,12 +82,14 @@ func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64, func sendTxs(cs *ConsensusState, ctx context.Context) { i := 0 - for { + tx := []byte{byte(i)} + for i := 0; i < 256; i++ { select { case <-ctx.Done(): return default: - cs.mempool.CheckTx([]byte{byte(i)}, nil) + tx[0] = byte(i) + cs.mempool.CheckTx(tx, nil) i++ } } diff --git a/mempool/mempool.go b/mempool/mempool.go index ccd615ac..04dbe50a 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -3,7 +3,6 @@ package mempool import ( "bytes" "container/list" - "fmt" "sync" "sync/atomic" "time" @@ -49,7 +48,7 @@ TODO: Better handle abci client errors. (make it automatically handle connection */ -const cacheSize = 100000 +var ErrTxInCache = errors.New("Tx already exists in cache") // Mempool is an ordered in-memory pool for transactions before they are proposed in a consensus // round. Transaction validity is checked using the CheckTx abci message before the transaction is @@ -92,9 +91,8 @@ func NewMempool(config *cfg.MempoolConfig, proxyAppConn proxy.AppConnMempool, he recheckCursor: nil, recheckEnd: nil, logger: log.NewNopLogger(), - cache: newTxCache(cacheSize), + cache: newTxCache(config.CacheSize), } - mempool.initWAL() proxyAppConn.SetResponseCallback(mempool.resCb) return mempool } @@ -131,7 +129,7 @@ func (mem *Mempool) CloseWAL() bool { return true } -func (mem *Mempool) initWAL() { +func (mem *Mempool) InitWAL() { walDir := mem.config.WalDir() if walDir != "" { err := cmn.EnsureDir(walDir, 0700) @@ -192,7 +190,7 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { // CACHE if mem.cache.Exists(tx) { - return fmt.Errorf("Tx already exists in cache") + return ErrTxInCache } mem.cache.Push(tx) // END CACHE @@ -449,7 +447,7 @@ func newTxCache(cacheSize int) *txCache { // Reset resets the txCache to empty. func (cache *txCache) Reset() { cache.mtx.Lock() - cache.map_ = make(map[string]struct{}, cacheSize) + cache.map_ = make(map[string]struct{}, cache.size) cache.list.Init() cache.mtx.Unlock() } diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 4d75cc58..6dfb5984 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -236,12 +236,13 @@ func TestMempoolCloseWAL(t *testing.T) { require.Equal(t, 0, len(m1), "no matches yet") // 3. Create the mempool - wcfg := *(cfg.DefaultMempoolConfig()) + wcfg := cfg.DefaultMempoolConfig() wcfg.RootDir = rootDir app := dummy.NewDummyApplication() cc := proxy.NewLocalClientCreator(app) appConnMem, _ := cc.NewABCIClient() - mempool := NewMempool(&wcfg, appConnMem, 10) + mempool := NewMempool(wcfg, appConnMem, 10) + mempool.InitWAL() // 4. Ensure that the directory contains the WAL file m2, err := filepath.Glob(filepath.Join(rootDir, "*")) diff --git a/node/node.go b/node/node.go index 6c816a9e..3012ed05 100644 --- a/node/node.go +++ b/node/node.go @@ -189,6 +189,7 @@ func NewNode(config *cfg.Config, // Make MempoolReactor mempoolLogger := logger.With("module", "mempool") mempool := mempl.NewMempool(config.Mempool, proxyApp.Mempool(), state.LastBlockHeight) + mempool.InitWAL() // no need to have the mempool wal during tests mempool.SetLogger(mempoolLogger) mempoolReactor := mempl.NewMempoolReactor(config.Mempool, mempool) mempoolReactor.SetLogger(mempoolLogger) From 6f3d9b4be3d281feac4d2495889d1584c9ed0df8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 01:36:52 -0500 Subject: [PATCH 081/124] fix race --- consensus/replay_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index a4786dde..483bd3a7 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -81,14 +81,12 @@ func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64, } func sendTxs(cs *ConsensusState, ctx context.Context) { - i := 0 - tx := []byte{byte(i)} for i := 0; i < 256; i++ { select { case <-ctx.Done(): return default: - tx[0] = byte(i) + tx := []byte{byte(i)} cs.mempool.CheckTx(tx, nil) i++ } From d36e118bf60da9a952e967daa4638dba15627341 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 8 Jan 2018 13:12:09 +0100 Subject: [PATCH 082/124] Add Consensus reactor spec --- .../new-spec/consensus-reactor.md | 355 ++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 docs/specification/new-spec/consensus-reactor.md diff --git a/docs/specification/new-spec/consensus-reactor.md b/docs/specification/new-spec/consensus-reactor.md new file mode 100644 index 00000000..f0d3e750 --- /dev/null +++ b/docs/specification/new-spec/consensus-reactor.md @@ -0,0 +1,355 @@ +# Consensus Reactor + +Consensus Reactor defines a reactor for the consensus service. It contains ConsensusState service that +manages the state of the Tendermint consensus internal state machine. +When Consensus Reactor is started, it starts Broadcast Routine and it starts ConsensusState service. +Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manage) known peer state +(that is used extensively in gossip routines) and starts the following three routines for the peer p: +Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible +for decoding messages received from a peer and for adequate processing of the message depending on its type and content. +The processing normally consists of updating the known peer state and for some messages +(`ProposalMessage`, `BlockPartMessage` and `VoteMessage`) also forwarding message to ConsensusState module +for further processing. In the following text we specify the core functionality of those separate unit of executions +that are part of the Consensus Reactor. + +## ConsensusState service + +Consensus State handles execution of the Tendermint BFT 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. + +Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine. +Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed +by the Receive Routine. + + +### Receive Routine of the ConsensusState service + +Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions. +It is the only routine that updates RoundState that contains internal consensus state. +Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. +It receives messages from peers, internal validators and from Timeout Ticker +and invokes the corresponding handlers, potentially updating the RoundState. +The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are +discussed in separate document (see [spec](https://github.com/tendermint/spec)). For understanding of this document +it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is +then extensively used by the gossip routines to determine what information should be sent to peer processes. + +## Round State + +RoundState defines the internal consensus state. It contains height, round, round step, a current validator set, +a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of +received votes and last commit and last validators set. + +``` +type RoundState struct { + Height int64 + Round int + Step RoundStepType + Validators ValidatorSet + Proposal Proposal + ProposalBlock Block + ProposalBlockParts PartSet + LockedRound int + LockedBlock Block + LockedBlockParts PartSet + Votes HeightVoteSet + LastCommit VoteSet + LastValidators ValidatorSet +} +``` + +Internally, consensus will run as a state machine with the following states: +RoundStepNewHeight, RoundStepNewRound, RoundStepPropose, RoundStepProposeWait, RoundStepPrevote, +RoundStepPrevoteWait, RoundStepPrecommit, RoundStepPrecommitWait and RoundStepCommit. + +## Peer Round State + +Peer round state contains the known state of a peer. It is being updated by the Receive routine of +Consensus Reactor and by the gossip routines upon sending a message to the peer. + +``` +type PeerRoundState struct { + Height int64 // Height peer is at + Round int // Round peer is at, -1 if unknown. + Step RoundStepType // Step peer is at + Proposal bool // True if peer has proposal for this round + ProposalBlockPartsHeader PartSetHeader + ProposalBlockParts BitArray + ProposalPOLRound int // Proposal's POL round. -1 if none. + ProposalPOL BitArray // nil until ProposalPOLMessage received. + Prevotes BitArray // All votes peer has for this round + Precommits BitArray // All precommits peer has for this round + LastCommitRound int // Round of commit for last height. -1 if none. + LastCommit BitArray // All commit precommits of commit for last height. + CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. + CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound +} +``` + +## Receive method of Consensus reactor + +The entry point of the Consensus reactor is a receive method. When a message is received from a peer p, +normally the peer round state is updated correspondingly, and some messages +are passed for further processing, for example to ConsensusState service. We now specify the processing of messages +in the receive method of Consensus reactor for each message type. In the following message handler, rs denotes +RoundState and prs PeerRoundState. + +### NewRoundStepMessage handler + +``` +handleMessage(msg): + if msg is from smaller height/round/step then return + // Just remember these values. + prsHeight = prs.Height + prsRound = prs.Round + prsCatchupCommitRound = prs.CatchupCommitRound + prsCatchupCommit = prs.CatchupCommit + + Update prs with values from msg + if prs.Height or prs.Round has been updated then + reset Proposal related fields of the peer state + if prs.Round has been updated and msg.Round == prsCatchupCommitRound then + prs.Precommits = psCatchupCommit + if prs.Height has been updated then + if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then + prs.LastCommitRound = msg.LastCommitRound + prs.LastCommit = prs.Precommits + } else { + prs.LastCommitRound = msg.LastCommitRound + prs.LastCommit = nil + } + Reset prs.CatchupCommitRound and prs.CatchupCommit +``` + +### CommitStepMessage handler + +``` +handleMessage(msg): + if prs.Height == msg.Height then + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader + prs.ProposalBlockParts = msg.BlockParts +``` + +### HasVoteMessage handler + +``` +handleMessage(msg): + if prs.Height == msg.Height then + prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) +``` + +### VoteSetMaj23Message handler + +``` +handleMessage(msg): + if prs.Height == msg.Height then + Record in rs that a peer claim to have ⅔ majority for msg.BlockID + Send VoteSetBitsMessage showing votes node has for that BlockId +``` + +### ProposalMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return + prs.Proposal = true + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader + prs.ProposalBlockParts = empty set + prs.ProposalPOLRound = msg.POLRound + prs.ProposalPOL = nil + Send msg through internal peerMsgQueue to ConsensusState service +``` + +### ProposalPOLMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return + prs.ProposalPOL = msg.ProposalPOL +``` + +### BlockPartMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height || prs.Round != msg.Round then return + Record in prs that peer has block part msg.Part.Index + Send msg trough internal peerMsgQueue to ConsensusState service +``` + +### VoteMessage handler + +``` +handleMessage(msg): + Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round + Send msg trough internal peerMsgQueue to ConsensusState service +``` + +### VoteSetBitsMessage handler + +``` +handleMessage(msg): + Update prs for the bit-array of votes peer claims to have for the msg.BlockID +``` + +## Gossip Data Routine + +It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and +`ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (denoted rs) +and the known PeerRoundState (denotes prs). The routine repeats forever the logic shown below: + +``` +1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then + Part = pick a random proposal block part the peer does not have + Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel + if send returns true, record that the peer knows the corresponding block Part + Continue + +1b) if (0 < prs.Height) and (prs.Height < rs.Height) then + help peer catch up using gossipDataForCatchup function + Continue + +1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then + Sleep PeerGossipSleepDuration + Continue + +// at this point rs.Height == prs.Height and rs.Round == prs.Round +1d) if (rs.Proposal != nil and !prs.Proposal) then + Send ProposalMessage(rs.Proposal) to the peer + if send returns true, record that the peer knows Proposal + if 0 <= rs.Proposal.POLRound then + polRound = rs.Proposal.POLRound + prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray() + Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray) + Continue + +2) Sleep PeerGossipSleepDuration +``` + +### Gossip Data For Catchup + +This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). +The function executes the following logic: + + if peer does not have all block parts for prs.ProposalBlockPart then + blockMeta = Load Block Metadata for height prs.Height from blockStore + if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then + Sleep PeerGossipSleepDuration + return + Part = pick a random proposal block part the peer does not have + Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel + if send returns true, record that the peer knows the corresponding block Part + return + else Sleep PeerGossipSleepDuration + +## Gossip Votes Routine + +It is used to send the following message: `VoteMessage` on the VoteChannel. +The gossip votes routine is based on the local RoundState (denoted rs) +and the known PeerRoundState (denotes prs). The routine repeats forever the logic shown below: + +``` +1a) if rs.Height == prs.Height then + if prs.Step == RoundStepNewHeight then + vote = random vote from rs.LastCommit the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then + Prevotes = rs.Votes.Prevotes(prs.Round) + vote = random vote from Prevotes the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then + Precommits = rs.Votes.Precommits(prs.Round) + vote = random vote from Precommits the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.ProposalPOLRound != -1 then + PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) + vote = random vote from PolPrevotes the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +1b) if prs.Height != 0 and rs.Height == prs.Height+1 then + vote = random vote from rs.LastCommit peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then + Commit = get commit from BlockStore for prs.Height + vote = random vote from Commit the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +2) Sleep PeerGossipSleepDuration +``` + +## QueryMaj23Routine + +It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given +BlockID has seen +2/3 votes. This routine is based on the local RoundState (denoted rs) and the known PeerRoundState +(denotes prs). The routine repeats forever the logic shown below. + +``` +1a) if rs.Height == prs.Height then + Prevotes = rs.Votes.Prevotes(prs.Round) + if there is a ⅔ majority for some blockId in Prevotes then + m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1b) if rs.Height == prs.Height then + Precommits = rs.Votes.Precommits(prs.Round) + if there is a ⅔ majority for some blockId in Precommits then + m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then + Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) + if there is a ⅔ majority for some blockId in Prevotes then + m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and + prs.Height <= blockStore.Height() then + Commit = LoadCommit(prs.Height) + m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +2) Sleep PeerQueryMaj23SleepDuration +``` + +## Broadcast routine + +The Broadcast routine subscribes to internal event bus to receive new round steps, votes messages and proposal +heartbeat messages, and broadcasts messages to peers upon receiving those events. +It brodcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that +broadcasting these messages does not depend on the PeerRoundState. It is sent on the StateChannel. +Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel. +`ProposalHeartbeatMessage` is sent the same way on the StateChannel. + + + + + + + + + + + + + + + + + + + From f2d19162d2a0704a5834f10c71b33ef22c8af832 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 17:10:08 -0500 Subject: [PATCH 083/124] fixes from caffix review --- docs/specification/new-spec/p2p/connection.md | 2 +- docs/specification/new-spec/p2p/peer.md | 5 +++-- docs/specification/new-spec/p2p/pex.md | 18 +++++++++++------- docs/specification/new-spec/p2p/trustmetric.md | 16 ---------------- 4 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 docs/specification/new-spec/p2p/trustmetric.md diff --git a/docs/specification/new-spec/p2p/connection.md b/docs/specification/new-spec/p2p/connection.md index dfe0d090..400111f4 100644 --- a/docs/specification/new-spec/p2p/connection.md +++ b/docs/specification/new-spec/p2p/connection.md @@ -46,7 +46,7 @@ is returned for processing by the corresponding channels `onReceive` function. Messages are sent from a single `sendRoutine`, which loops over a select statement that results in the sending of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. -Messages are chosen for a batch one a time from the channel with the lowest ratio of recently sent bytes to channel priority. +Messages are chosen for a batch one at a time from the channel with the lowest ratio of recently sent bytes to channel priority. ## Sending Messages diff --git a/docs/specification/new-spec/p2p/peer.md b/docs/specification/new-spec/p2p/peer.md index a172764c..39be966b 100644 --- a/docs/specification/new-spec/p2p/peer.md +++ b/docs/specification/new-spec/p2p/peer.md @@ -1,7 +1,8 @@ # Tendermint Peers -This document explains how Tendermint Peers are identified, how they connect to one another, -and how other peers are found. +This document explains how Tendermint Peers are identified and how they connect to one another. + +For details on peer discovery, see the [peer exchange (PEX) reactor doc](pex.md). ## Peer Identity diff --git a/docs/specification/new-spec/p2p/pex.md b/docs/specification/new-spec/p2p/pex.md index a71b9717..43d6f80d 100644 --- a/docs/specification/new-spec/p2p/pex.md +++ b/docs/specification/new-spec/p2p/pex.md @@ -8,10 +8,10 @@ to good peers and to gossip peers to others. Certain peers are special in that they are specified by the user as `persistent`, which means we auto-redial them if the connection fails. -Some such peers can additional be marked as `private`, which means -we will not gossip them to others. +Some peers can be marked as `private`, which means +we will not put them in the address book or gossip them to others. -All others peers are tracked using an address book. +All peers except private peers are tracked using the address book. ## Discovery @@ -31,7 +31,7 @@ Peers are added to the address book from the PEX when they first connect to us o when we hear about them from other peers. The address book is arranged in sets of buckets, and distinguishes between -vetted and unvetted peers. It keeps different sets of buckets for vetted and +vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and unvetted peers. Buckets provide randomization over peer selection. A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets. @@ -52,7 +52,7 @@ If a peer becomes unvetted (either a new peer, or one that was previously vetted a randomly selected one of the unvetted peers is removed from the address book. More fine-grained tracking of peer behaviour can be done using -a Trust Metric, but it's best to start with something simple. +a trust metric (see below), but it's best to start with something simple. ## Select Peers to Dial @@ -75,7 +75,7 @@ Send the selected peers. Note we select peers for sending without bias for vette There are various cases where we decide a peer has misbehaved and we disconnect from them. When this happens, the peer is removed from the address book and black listed for some amount of time. We call this "Disconnect and Mark". -Note that the bad behaviour may be detected outside the PEX reactor itseld +Note that the bad behaviour may be detected outside the PEX reactor itself (for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor so it can remove and mark the peer. @@ -86,9 +86,13 @@ we Disconnect and Mark. ## Trust Metric The quality of peers can be tracked in more fine-grained detail using a -Proportional-Integral-Derrivative (PID) controller that incorporates +Proportional-Integral-Derivative (PID) controller that incorporates current, past, and rate-of-change data to inform peer quality. While a PID trust metric has been implemented, it remains for future work to use it in the PEX. +See the [trustmetric](../../../architecture/adr-006-trust-metric.md ) +and [trustmetric useage](../../../architecture/adr-007-trust-metric-usage.md ) +architecture docs for more details. + diff --git a/docs/specification/new-spec/p2p/trustmetric.md b/docs/specification/new-spec/p2p/trustmetric.md deleted file mode 100644 index b0eaf96e..00000000 --- a/docs/specification/new-spec/p2p/trustmetric.md +++ /dev/null @@ -1,16 +0,0 @@ - -The trust metric tracks the quality of the peers. -When a peer exceeds a certain quality for a certain amount of time, -it is marked as vetted in the addrbook. -If a vetted peer's quality degrades sufficiently, it is booted, and must prove itself from scratch. -If we need to make room for a new vetted peer, we move the lowest scoring vetted peer back to unvetted. -If we need to make room for a new unvetted peer, we remove the lowest scoring unvetted peer - -possibly only if its below some absolute minimum ? - -Peer quality is tracked in the connection and across the reactors. -Behaviours are defined as one of: - - fatal - something outright malicious. we should disconnect and remember them. - - bad - any kind of timeout, msgs that dont unmarshal, or fail other validity checks, or msgs we didn't ask for or arent expecting - - neutral - normal correct behaviour. unknown channels/msg types (version upgrades). - - good - some random majority of peers per reactor sending us useful messages - From ae27e85bf7dc29323cfc73505b040b83293e9990 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Jan 2018 17:51:09 -0500 Subject: [PATCH 084/124] add warnings about new spec --- docs/specification/new-spec/README.md | 15 +++++++++++++-- docs/specification/new-spec/encoding.md | 15 +++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/specification/new-spec/README.md b/docs/specification/new-spec/README.md index 8a07d922..179048dd 100644 --- a/docs/specification/new-spec/README.md +++ b/docs/specification/new-spec/README.md @@ -1,14 +1,24 @@ # Tendermint Specification This is a markdown specification of the Tendermint blockchain. - It defines the base data structures used in the blockchain and how they are validated. -It contains the following components: +XXX: this spec is a work in progress and not yet complete - see github +[isses](https://github.com/tendermint/tendermint/issues) and +[pull requests](https://github.com/tendermint/tendermint/pulls) +for more details. +If you find discrepancies between the spec and the code that +do not have an associated issue or pull request on github, +please submit them to our [bug bounty](https://tendermint.com/security)! + +## Contents + +- [Overview](#overview) - [Encoding and Digests](encoding.md) - [Blockchain](blockchain.md) - [State](state.md) +- [Consensus](consensus.md) - [P2P](p2p/node.md) ## Overview @@ -56,3 +66,4 @@ We call this the `State`. Block verification also requires access to the previou - Light Client - P2P - Reactor protocols (consensus, mempool, blockchain, pex) + diff --git a/docs/specification/new-spec/encoding.md b/docs/specification/new-spec/encoding.md index 02456d84..f401dde7 100644 --- a/docs/specification/new-spec/encoding.md +++ b/docs/specification/new-spec/encoding.md @@ -6,6 +6,9 @@ Tendermint aims to encode data structures in a manner similar to how the corresp Variable length items are length-prefixed. While the encoding was inspired by Go, it is easily implemented in other languages as well given its intuitive design. +XXX: This is changing to use real varints and 4-byte-prefixes. +See https://github.com/tendermint/go-wire/tree/sdk2. + ### Fixed Length Integers Fixed length integers are encoded in Big-Endian using the specified number of bytes. @@ -94,13 +97,13 @@ encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x ``` ### BitArray -BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode +BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode value of each array element. ``` type BitArray struct { - Bits int - Elems []uint64 + Bits int + Elems []uint64 } ``` @@ -192,8 +195,8 @@ MakeParts(object, partSize) ``` type Part struct { - Index int - Bytes byte[] - Proof byte[] + Index int + Bytes byte[] + Proof byte[] } ``` From 39d8da35364f740aec4dd4009bc3c0ef95cbaa26 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 14:54:59 -0500 Subject: [PATCH 085/124] docs: update getting started [ci skip] --- docs/examples/getting-started.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/examples/getting-started.md b/docs/examples/getting-started.md index d7447428..3ae42e27 100644 --- a/docs/examples/getting-started.md +++ b/docs/examples/getting-started.md @@ -22,19 +22,22 @@ The script is also used to facilitate cluster deployment below. ### Manual Install -Requires: -- `go` minimum version 1.9.2 -- `$GOPATH` set and `$GOPATH/bin` on your $PATH (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) +Requires: +- `go` minimum version 1.9 +- `$GOPATH` environment variable must be set +- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) To install Tendermint, run: ``` go get github.com/tendermint/tendermint cd $GOPATH/src/github.com/tendermint/tendermint -make get_vendor_deps +make get_tools && make get_vendor_deps make install ``` +Note that `go get` may return an error but it can be ignored. + Confirm installation: ``` @@ -98,7 +101,7 @@ and check that it worked with: curl -s 'localhost:46657/abci_query?data="abcd"' ``` -We can send transactions with a key:value store: +We can send transactions with a key and value too: ``` curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"' @@ -114,9 +117,9 @@ where the value is returned in hex. ## Cluster of Nodes -First create four Ubuntu cloud machines. The following was testing on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. +First create four Ubuntu cloud machines. The following was tested on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. -Then, `ssh` into each machine, and `curl` then execute [this script](https://git.io/vNLfY): +Then, `ssh` into each machine, and execute [this script](https://git.io/vNLfY): ``` curl -L https://git.io/vNLfY | bash @@ -128,12 +131,12 @@ This will install `go` and other dependencies, get the Tendermint source code, t Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence: ``` -tendermint node --home ./node1 --proxy_app=dummy -tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656 -tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656 -tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656 +tendermint node --home ./node1 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 +tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 ``` -Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json` have come online). Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. +Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json`) have come online. Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. Transactions can then be sent as covered in the single, local node example above. From 949211a137287b209783dfeb2eb03a0978f5e0a2 Mon Sep 17 00:00:00 2001 From: caffix Date: Tue, 9 Jan 2018 20:12:41 -0500 Subject: [PATCH 086/124] added a test for PEX reactor seed mode --- p2p/pex_reactor.go | 268 ++++++++++++++++++++++++++++++++++++++++ p2p/pex_reactor_test.go | 45 +++++++ 2 files changed, 313 insertions(+) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 93fddc11..7313f7d5 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "reflect" + "sort" "time" "github.com/pkg/errors" @@ -334,6 +335,273 @@ func (r *PEXReactor) checkSeeds() error { return nil } +// Explores the network searching for more peers. (continuous) +// Seed/Crawler Mode causes this node to quickly disconnect +// from peers, except other seed nodes. +func (r *PEXReactor) seedCrawlerMode() { + // Do an initial crawl + r.crawlPeers() + + // Fire periodically + ticker := time.NewTicker(defaultSeedModePeriod) + + for { + select { + case <-ticker.C: + r.attemptDisconnects() + r.crawlPeers() + case <-r.Quit: + return + } + } +} + +// crawlStatus handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlStatus struct { + // The remote address of a potential peer we learned about + Addr *NetAddress + + // Not empty if we are connected to the address + PeerID string + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestFirst implements sort.Interface for []crawlStatus +// based on the LastAttempt field. +type oldestFirst []crawlStatus + +func (of oldestFirst) Len() int { return len(of) } +func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } +func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } + +// getCrawlStatus returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getCrawlStatus() []crawlStatus { + var of oldestFirst + + addrs := r.book.ListOfKnownAddresses() + // Go through all the addresses in the AddressBook + for _, addr := range addrs { + var peerID string + + // Check if a peer is already connected from this addr + if p := r.Switch.peers.GetByRemoteAddr(addr.Addr); p != nil { + peerID = p.Key() + } + + of = append(of, crawlStatus{ + Addr: addr.Addr, + PeerID: peerID, + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(of) + return of +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +// +// TODO Basically, we need to work harder on our good-peer/bad-peer marking. +// What we're currently doing in terms of marking good/bad peers is just a +// placeholder. It should not be the case that an address becomes old/vetted +// upon a single successful connection. +func (r *PEXReactor) crawlPeers() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, cs := range crawlerStatus { + // Do not dial peers that are already connected + if cs.PeerID != "" { + continue + } + // Do not attempt to connect with peers we recently dialed + if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) + if err != nil { + r.book.MarkAttempt(cs.Addr) + continue + } + // Enter the peer ID into our crawl status information + cs.PeerID = p.Key() + r.book.MarkGood(cs.Addr) + } + // Crawl the connected peers asking for more addresses + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // We will wait a minimum period of time before crawling peers again + if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { + p := r.Switch.Peers().Get(cs.PeerID) + if p != nil { + r.RequestPEX(p) + r.book.MarkAttempt(cs.Addr) + } + } + } +} + +// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) +func (r *PEXReactor) attemptDisconnects() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Go through each peer we have connected with + // looking for opportunities to disconnect + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // Remain connected to each peer for a minimum period of time + if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { + continue + } + // Fetch the Peer using the saved ID + p := r.Switch.Peers().Get(cs.PeerID) + if p == nil { + continue + } + // Do not disconnect from persistent peers. + // Specifically, we need to remain connected to other seeds + if p.IsPersistent() { + continue + } + // Otherwise, disconnect from the peer + r.Switch.StopPeerGracefully(p) + } +} + +// crawlStatus handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlStatus struct { + // The remote address of a potential peer we learned about + Addr *NetAddress + + // Not empty if we are connected to the address + PeerID string + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestAttempt implements sort.Interface for []crawlStatus +// based on the LastAttempt field. +type oldestAttempt []crawlStatus + +func (oa oldestAttempt) Len() int { return len(oa) } +func (oa oldestAttempt) Swap(i, j int) { oa[i], oa[j] = oa[j], oa[i] } +func (oa oldestAttempt) Less(i, j int) bool { return oa[i].LastAttempt.Before(oa[j].LastAttempt) } + +// getCrawlStatus returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getCrawlStatus() []crawlStatus { + var oa oldestAttempt + + addrs := r.book.ListOfKnownAddresses() + // Go through all the addresses in the AddressBook + for _, addr := range addrs { + p := r.Switch.peers.GetByRemoteAddr(addr.Addr) + + oa = append(oa, crawlStatus{ + Addr: addr.Addr, + PeerID: p.Key(), + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(oa) + return oa +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +// +// TODO Basically, we need to work harder on our good-peer/bad-peer marking. +// What we're currently doing in terms of marking good/bad peers is just a +// placeholder. It should not be the case that an address becomes old/vetted +// upon a single successful connection. +func (r *PEXReactor) crawlPeers() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, cs := range crawlerStatus { + // Do not dial peers that are already connected + if cs.PeerID != "" { + continue + } + // Do not attempt to connect with peers we recently dialed + if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) + if err != nil { + r.book.MarkAttempt(cs.Addr) + continue + } + // Enter the peer ID into our crawl status information + cs.PeerID = p.Key() + r.book.MarkGood(cs.Addr) + } + // Crawl the connected peers asking for more addresses + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // We will wait a minimum period of time before crawling peers again + if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { + p := r.Switch.peers.Get(cs.PeerID) + if p != nil { + r.RequestPEX(p) + } + } + } +} + +// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) +func (r *PEXReactor) attemptDisconnects() { + crawlerStatus := r.getCrawlStatus() + + now := time.Now() + // Go through each peer we have connected with + // looking for opportunities to disconnect + for _, cs := range crawlerStatus { + if cs.PeerID == "" { + continue + } + // Remain connected to each peer for a minimum period of time + if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { + continue + } + // Fetch the Peer using the saved ID + p := r.Switch.peers.Get(cs.PeerID) + if p == nil { + continue + } + // Do not disconnect from persistent peers. + // Specifically, we need to remain connected to other seeds + if p.IsPersistent() { + continue + } + // Otherwise, disconnect from the peer + r.Switch.StopPeerGracefully(p) + } +} + // randomly dial seeds until we connect to one or exhaust them func (r *PEXReactor) dialSeed() { lSeeds := len(r.config.Seeds) diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index c0681586..b8ee89b3 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -286,6 +286,51 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { assertSomePeersWithTimeout(t, []*Switch{sw}, 10*time.Millisecond, 10*time.Second) } +func TestPEXReactorCrawlStatus(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(err) + defer os.RemoveAll(dir) // nolint: errcheck + book := NewAddrBook(dir+"addrbook.json", false) + book.SetLogger(log.TestingLogger()) + + var r *PEXReactor + // Seed/Crawler mode uses data from the Switch + makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + r = NewPEXReactor(book, true) + r.SetLogger(log.TestingLogger()) + sw.SetLogger(log.TestingLogger().With("switch", i)) + sw.AddReactor("pex", r) + return sw + }) + + // Create a peer, and add it to the peer set + peer := createRandomPeer(false) + r.Switch.peers.Add(peer) + // Add the peer address to the address book + addr1, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) + r.book.AddAddress(addr1, addr1) + // Add an address to the book that does not have a peer + _, addr2 := createRoutableAddr() + r.book.AddAddress(addr2, addr1) + + // Get the crawl status data + status := r.getCrawlStatus() + + // Make sure it has the proper number of elements + assert.Equal(2, len(status)) + + var num int + for _, cs := range status { + if cs.PeerID != "" { + num++ + } + } + // Check that only one has been identified as a connected peer + assert.Equal(1, num) +} + func createRoutableAddr() (addr string, netAddr *NetAddress) { for { addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) From 88eb3e7af0b63b3bc059453d95f501789e399730 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:40:29 -0500 Subject: [PATCH 087/124] some minor renames --- p2p/pex_reactor.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 7313f7d5..0c3567a3 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -21,6 +21,11 @@ const ( defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB + + // Seed/Crawler constants + defaultSeedDisconnectWaitPeriod = 2 * time.Minute + defaultCrawlPeerInterval = 2 * time.Minute + defaultCrawlPeersPeriod = 30 * time.Second ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -79,7 +84,13 @@ func (r *PEXReactor) OnStart() error { return err } - go r.ensurePeersRoutine() + // Check if this node should run + // in seed/crawler mode + if r.config.SeedMode { + go r.crawlPeersRoutine() + } else { + go r.ensurePeersRoutine() + } return nil } @@ -338,7 +349,7 @@ func (r *PEXReactor) checkSeeds() error { // Explores the network searching for more peers. (continuous) // Seed/Crawler Mode causes this node to quickly disconnect // from peers, except other seed nodes. -func (r *PEXReactor) seedCrawlerMode() { +func (r *PEXReactor) crawlPeersRoutine() { // Do an initial crawl r.crawlPeers() From c2f97e64545920b98aa4ea48f9669b643a80b001 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 18:28:40 -0500 Subject: [PATCH 088/124] p2p: seed mode fixes from rebase and review --- p2p/connection.go | 4 + p2p/pex_reactor.go | 418 +++++++++++++--------------------------- p2p/pex_reactor_test.go | 48 +++-- 3 files changed, 158 insertions(+), 312 deletions(-) diff --git a/p2p/connection.go b/p2p/connection.go index 306eaf7e..dcb66096 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -88,6 +88,8 @@ type MConnection struct { flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically chStatsTimer *cmn.RepeatTimer // update channel stats periodically + + created time.Time // time of creation } // MConnConfig is a MConnection configuration. @@ -502,6 +504,7 @@ FOR_LOOP: } type ConnectionStatus struct { + Duration time.Duration SendMonitor flow.Status RecvMonitor flow.Status Channels []ChannelStatus @@ -517,6 +520,7 @@ type ChannelStatus struct { func (c *MConnection) Status() ConnectionStatus { var status ConnectionStatus + status.Duration = time.Since(c.created) status.SendMonitor = c.sendMonitor.Status() status.RecvMonitor = c.recvMonitor.Status() status.Channels = make([]ChannelStatus, len(c.channels)) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 0c3567a3..5d919421 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -17,15 +17,22 @@ const ( // PexChannel is a channel for PEX messages PexChannel = byte(0x00) - // period to ensure peers connected - defaultEnsurePeersPeriod = 30 * time.Second - minNumOutboundPeers = 10 - maxPexMessageSize = 1048576 // 1MB + maxPexMessageSize = 1048576 // 1MB + + // ensure we have enough peers + defaultEnsurePeersPeriod = 30 * time.Second + defaultMinNumOutboundPeers = 10 // Seed/Crawler constants - defaultSeedDisconnectWaitPeriod = 2 * time.Minute - defaultCrawlPeerInterval = 2 * time.Minute - defaultCrawlPeersPeriod = 30 * time.Second + // TODO: + // We want seeds to only advertise good peers. + // Peers are marked by external mechanisms. + // We need a config value that can be set to be + // on the order of how long it would take before a good + // peer is marked good. + defaultSeedDisconnectWaitPeriod = 2 * time.Minute // disconnect after this + defaultCrawlPeerInterval = 2 * time.Minute // dont redial for this. TODO: back-off + defaultCrawlPeersPeriod = 30 * time.Second // check some peers every this ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -51,8 +58,11 @@ type PEXReactor struct { // PEXReactorConfig holds reactor specific configuration data. type PEXReactorConfig struct { - // Seeds is a list of addresses reactor may use if it can't connect to peers - // in the addrbook. + // Seed/Crawler mode + SeedMode bool + + // Seeds is a list of addresses reactor may use + // if it can't connect to peers in the addrbook. Seeds []string } @@ -259,19 +269,12 @@ func (r *PEXReactor) ensurePeersRoutine() { // ensurePeers ensures that sufficient peers are connected. (once) // -// Old bucket / New bucket are arbitrary categories to denote whether an -// address is vetted or not, and this needs to be determined over time via a // heuristic that we haven't perfected yet, or, perhaps is manually edited by // the node operator. It should not be used to compute what addresses are // already connected or not. -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. func (r *PEXReactor) ensurePeers() { numOutPeers, numInPeers, numDialing := r.Switch.NumPeers() - numToDial := minNumOutboundPeers - (numOutPeers + numDialing) + numToDial := defaultMinNumOutboundPeers - (numOutPeers + numDialing) r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial) if numToDial <= 0 { return @@ -327,7 +330,7 @@ func (r *PEXReactor) ensurePeers() { // If we are not connected to nor dialing anybody, fallback to dialing a seed. if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { r.Logger.Info("No addresses to dial nor connected peers. Falling back to seeds") - r.dialSeed() + r.dialSeeds() } } @@ -346,275 +349,8 @@ func (r *PEXReactor) checkSeeds() error { return nil } -// Explores the network searching for more peers. (continuous) -// Seed/Crawler Mode causes this node to quickly disconnect -// from peers, except other seed nodes. -func (r *PEXReactor) crawlPeersRoutine() { - // Do an initial crawl - r.crawlPeers() - - // Fire periodically - ticker := time.NewTicker(defaultSeedModePeriod) - - for { - select { - case <-ticker.C: - r.attemptDisconnects() - r.crawlPeers() - case <-r.Quit: - return - } - } -} - -// crawlStatus handles temporary data needed for the -// network crawling performed during seed/crawler mode. -type crawlStatus struct { - // The remote address of a potential peer we learned about - Addr *NetAddress - - // Not empty if we are connected to the address - PeerID string - - // The last time we attempt to reach this address - LastAttempt time.Time - - // The last time we successfully reached this address - LastSuccess time.Time -} - -// oldestFirst implements sort.Interface for []crawlStatus -// based on the LastAttempt field. -type oldestFirst []crawlStatus - -func (of oldestFirst) Len() int { return len(of) } -func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } -func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } - -// getCrawlStatus returns addresses of potential peers that we wish to validate. -// NOTE: The status information is ordered as described above. -func (r *PEXReactor) getCrawlStatus() []crawlStatus { - var of oldestFirst - - addrs := r.book.ListOfKnownAddresses() - // Go through all the addresses in the AddressBook - for _, addr := range addrs { - var peerID string - - // Check if a peer is already connected from this addr - if p := r.Switch.peers.GetByRemoteAddr(addr.Addr); p != nil { - peerID = p.Key() - } - - of = append(of, crawlStatus{ - Addr: addr.Addr, - PeerID: peerID, - LastAttempt: addr.LastAttempt, - LastSuccess: addr.LastSuccess, - }) - } - sort.Sort(of) - return of -} - -// crawlPeers will crawl the network looking for new peer addresses. (once) -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. -func (r *PEXReactor) crawlPeers() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Use addresses we know of to reach additional peers - for _, cs := range crawlerStatus { - // Do not dial peers that are already connected - if cs.PeerID != "" { - continue - } - // Do not attempt to connect with peers we recently dialed - if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { - continue - } - // Otherwise, attempt to connect with the known address - p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) - if err != nil { - r.book.MarkAttempt(cs.Addr) - continue - } - // Enter the peer ID into our crawl status information - cs.PeerID = p.Key() - r.book.MarkGood(cs.Addr) - } - // Crawl the connected peers asking for more addresses - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // We will wait a minimum period of time before crawling peers again - if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { - p := r.Switch.Peers().Get(cs.PeerID) - if p != nil { - r.RequestPEX(p) - r.book.MarkAttempt(cs.Addr) - } - } - } -} - -// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) -func (r *PEXReactor) attemptDisconnects() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Go through each peer we have connected with - // looking for opportunities to disconnect - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // Remain connected to each peer for a minimum period of time - if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { - continue - } - // Fetch the Peer using the saved ID - p := r.Switch.Peers().Get(cs.PeerID) - if p == nil { - continue - } - // Do not disconnect from persistent peers. - // Specifically, we need to remain connected to other seeds - if p.IsPersistent() { - continue - } - // Otherwise, disconnect from the peer - r.Switch.StopPeerGracefully(p) - } -} - -// crawlStatus handles temporary data needed for the -// network crawling performed during seed/crawler mode. -type crawlStatus struct { - // The remote address of a potential peer we learned about - Addr *NetAddress - - // Not empty if we are connected to the address - PeerID string - - // The last time we attempt to reach this address - LastAttempt time.Time - - // The last time we successfully reached this address - LastSuccess time.Time -} - -// oldestAttempt implements sort.Interface for []crawlStatus -// based on the LastAttempt field. -type oldestAttempt []crawlStatus - -func (oa oldestAttempt) Len() int { return len(oa) } -func (oa oldestAttempt) Swap(i, j int) { oa[i], oa[j] = oa[j], oa[i] } -func (oa oldestAttempt) Less(i, j int) bool { return oa[i].LastAttempt.Before(oa[j].LastAttempt) } - -// getCrawlStatus returns addresses of potential peers that we wish to validate. -// NOTE: The status information is ordered as described above. -func (r *PEXReactor) getCrawlStatus() []crawlStatus { - var oa oldestAttempt - - addrs := r.book.ListOfKnownAddresses() - // Go through all the addresses in the AddressBook - for _, addr := range addrs { - p := r.Switch.peers.GetByRemoteAddr(addr.Addr) - - oa = append(oa, crawlStatus{ - Addr: addr.Addr, - PeerID: p.Key(), - LastAttempt: addr.LastAttempt, - LastSuccess: addr.LastSuccess, - }) - } - sort.Sort(oa) - return oa -} - -// crawlPeers will crawl the network looking for new peer addresses. (once) -// -// TODO Basically, we need to work harder on our good-peer/bad-peer marking. -// What we're currently doing in terms of marking good/bad peers is just a -// placeholder. It should not be the case that an address becomes old/vetted -// upon a single successful connection. -func (r *PEXReactor) crawlPeers() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Use addresses we know of to reach additional peers - for _, cs := range crawlerStatus { - // Do not dial peers that are already connected - if cs.PeerID != "" { - continue - } - // Do not attempt to connect with peers we recently dialed - if now.Sub(cs.LastAttempt) < defaultCrawlPeerInterval { - continue - } - // Otherwise, attempt to connect with the known address - p, err := r.Switch.DialPeerWithAddress(cs.Addr, false) - if err != nil { - r.book.MarkAttempt(cs.Addr) - continue - } - // Enter the peer ID into our crawl status information - cs.PeerID = p.Key() - r.book.MarkGood(cs.Addr) - } - // Crawl the connected peers asking for more addresses - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // We will wait a minimum period of time before crawling peers again - if now.Sub(cs.LastAttempt) >= defaultCrawlPeerInterval { - p := r.Switch.peers.Get(cs.PeerID) - if p != nil { - r.RequestPEX(p) - } - } - } -} - -// attemptDisconnects checks the crawlStatus info for Peers to disconnect from. (once) -func (r *PEXReactor) attemptDisconnects() { - crawlerStatus := r.getCrawlStatus() - - now := time.Now() - // Go through each peer we have connected with - // looking for opportunities to disconnect - for _, cs := range crawlerStatus { - if cs.PeerID == "" { - continue - } - // Remain connected to each peer for a minimum period of time - if now.Sub(cs.LastSuccess) < defaultSeedDisconnectWaitPeriod { - continue - } - // Fetch the Peer using the saved ID - p := r.Switch.peers.Get(cs.PeerID) - if p == nil { - continue - } - // Do not disconnect from persistent peers. - // Specifically, we need to remain connected to other seeds - if p.IsPersistent() { - continue - } - // Otherwise, disconnect from the peer - r.Switch.StopPeerGracefully(p) - } -} - // randomly dial seeds until we connect to one or exhaust them -func (r *PEXReactor) dialSeed() { +func (r *PEXReactor) dialSeeds() { lSeeds := len(r.config.Seeds) if lSeeds == 0 { return @@ -636,6 +372,116 @@ func (r *PEXReactor) dialSeed() { r.Switch.Logger.Error("Couldn't connect to any seeds") } +//---------------------------------------------------------- + +// Explores the network searching for more peers. (continuous) +// Seed/Crawler Mode causes this node to quickly disconnect +// from peers, except other seed nodes. +func (r *PEXReactor) crawlPeersRoutine() { + // Do an initial crawl + r.crawlPeers() + + // Fire periodically + ticker := time.NewTicker(defaultCrawlPeersPeriod) + + for { + select { + case <-ticker.C: + r.attemptDisconnects() + r.crawlPeers() + case <-r.Quit: + return + } + } +} + +// crawlPeerInfo handles temporary data needed for the +// network crawling performed during seed/crawler mode. +type crawlPeerInfo struct { + // The listening address of a potential peer we learned about + Addr *NetAddress + + // The last time we attempt to reach this address + LastAttempt time.Time + + // The last time we successfully reached this address + LastSuccess time.Time +} + +// oldestFirst implements sort.Interface for []crawlPeerInfo +// based on the LastAttempt field. +type oldestFirst []crawlPeerInfo + +func (of oldestFirst) Len() int { return len(of) } +func (of oldestFirst) Swap(i, j int) { of[i], of[j] = of[j], of[i] } +func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j].LastAttempt) } + +// getPeersToCrawl returns addresses of potential peers that we wish to validate. +// NOTE: The status information is ordered as described above. +func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { + var of oldestFirst + + // TODO: not this. be more selective + addrs := r.book.ListOfKnownAddresses() + for _, addr := range addrs { + if len(addr.ID()) == 0 { + continue // dont use peers without id + } + + of = append(of, crawlPeerInfo{ + Addr: addr.Addr, + LastAttempt: addr.LastAttempt, + LastSuccess: addr.LastSuccess, + }) + } + sort.Sort(of) + return of +} + +// crawlPeers will crawl the network looking for new peer addresses. (once) +func (r *PEXReactor) crawlPeers() { + peerInfos := r.getPeersToCrawl() + + now := time.Now() + // Use addresses we know of to reach additional peers + for _, pi := range peerInfos { + // Do not attempt to connect with peers we recently dialed + if now.Sub(pi.LastAttempt) < defaultCrawlPeerInterval { + continue + } + // Otherwise, attempt to connect with the known address + _, err := r.Switch.DialPeerWithAddress(pi.Addr, false) + if err != nil { + r.book.MarkAttempt(pi.Addr) + continue + } + } + // Crawl the connected peers asking for more addresses + for _, pi := range peerInfos { + // We will wait a minimum period of time before crawling peers again + if now.Sub(pi.LastAttempt) >= defaultCrawlPeerInterval { + peer := r.Switch.Peers().Get(pi.Addr.ID) + if peer != nil { + r.RequestPEX(peer) + } + } + } +} + +// attemptDisconnects checks if we've been with each peer long enough to disconnect +func (r *PEXReactor) attemptDisconnects() { + for _, peer := range r.Switch.Peers().List() { + status := peer.Status() + if status.Duration < defaultSeedDisconnectWaitPeriod { + continue + } + if peer.IsPersistent() { + continue + } + r.Switch.StopPeerGracefully(peer) + } +} + //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index b8ee89b3..91e30fea 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -295,46 +295,42 @@ func TestPEXReactorCrawlStatus(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", false) book.SetLogger(log.TestingLogger()) - var r *PEXReactor + pexR := NewPEXReactor(book, &PEXReactorConfig{SeedMode: true}) // Seed/Crawler mode uses data from the Switch makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { - r = NewPEXReactor(book, true) - r.SetLogger(log.TestingLogger()) + pexR.SetLogger(log.TestingLogger()) sw.SetLogger(log.TestingLogger().With("switch", i)) - sw.AddReactor("pex", r) + sw.AddReactor("pex", pexR) return sw }) - // Create a peer, and add it to the peer set + // Create a peer, add it to the peer set and the addrbook. peer := createRandomPeer(false) - r.Switch.peers.Add(peer) - // Add the peer address to the address book - addr1, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) - r.book.AddAddress(addr1, addr1) - // Add an address to the book that does not have a peer - _, addr2 := createRoutableAddr() - r.book.AddAddress(addr2, addr1) + pexR.Switch.peers.Add(peer) + addr1 := peer.NodeInfo().NetAddress() + pexR.book.AddAddress(addr1, addr1) - // Get the crawl status data - status := r.getCrawlStatus() + // Add a non-connected address to the book. + _, addr2 := createRoutableAddr() + pexR.book.AddAddress(addr2, addr1) + + // Get some peerInfos to crawl + peerInfos := pexR.getPeersToCrawl() // Make sure it has the proper number of elements - assert.Equal(2, len(status)) + assert.Equal(2, len(peerInfos)) - var num int - for _, cs := range status { - if cs.PeerID != "" { - num++ - } - } - // Check that only one has been identified as a connected peer - assert.Equal(1, num) + // TODO: test } func createRoutableAddr() (addr string, netAddr *NetAddress) { for { - addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) - netAddr, _ = NewNetAddressString(addr) + var err error + addr = cmn.Fmt("%X@%v.%v.%v.%v:46656", cmn.RandBytes(20), rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) + netAddr, err = NewNetAddressString(addr) + if err != nil { + panic(err) + } if netAddr.Routable() { break } @@ -346,7 +342,7 @@ func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ nodeInfo: NodeInfo{ - ListenAddr: netAddr.String(), + ListenAddr: netAddr.DialString(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, From 7b87cdaed8b9b7e4d37a15ee5a64fb1715071265 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 19:28:43 -0500 Subject: [PATCH 089/124] p2p: seed disconnects after sending addrs --- p2p/pex_reactor.go | 33 +++++++++++++++++++++------------ p2p/pex_reactor_test.go | 4 ++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 5d919421..bd19ab3b 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -129,7 +129,7 @@ func (r *PEXReactor) AddPeer(p Peer) { // either via DialPeersAsync or r.Receive. // Ask it for more peers if we need. if r.book.NeedMoreAddrs() { - r.RequestPEX(p) + r.RequestAddrs(p) } } else { // For inbound peers, the peer is its own source, @@ -159,15 +159,24 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { switch msg := msg.(type) { case *pexRequestMessage: - // We received a request for peers from src. + // Check we're not receiving too many requests if err := r.receiveRequest(src); err != nil { r.Switch.StopPeerForError(src, err) return } - r.SendAddrs(src, r.book.GetSelection()) + + // Seeds disconnect after sending a batch of addrs + if r.config.SeedMode { + // TODO: should we be more selective ? + r.SendAddrs(src, r.book.GetSelection()) + r.Switch.StopPeerGracefully(src) + } else { + r.SendAddrs(src, r.book.GetSelection()) + } + case *pexAddrsMessage: - // We received some peer addresses from src. - if err := r.ReceivePEX(msg.Addrs, src); err != nil { + // If we asked for addresses, add them to the book + if err := r.ReceiveAddrs(msg.Addrs, src); err != nil { r.Switch.StopPeerForError(src, err) return } @@ -202,9 +211,9 @@ func (r *PEXReactor) receiveRequest(src Peer) error { return nil } -// RequestPEX asks peer for more addresses if we do not already +// RequestAddrs asks peer for more addresses if we do not already // have a request out for this peer. -func (r *PEXReactor) RequestPEX(p Peer) { +func (r *PEXReactor) RequestAddrs(p Peer) { id := string(p.ID()) if r.requestsSent.Has(id) { return @@ -213,10 +222,10 @@ func (r *PEXReactor) RequestPEX(p Peer) { p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) } -// ReceivePEX adds the given addrs to the addrbook if theres an open +// ReceiveAddrs adds the given addrs to the addrbook if theres an open // 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) ReceivePEX(addrs []*NetAddress, src Peer) error { +func (r *PEXReactor) ReceiveAddrs(addrs []*NetAddress, src Peer) error { id := string(src.ID()) if !r.requestsSent.Has(id) { @@ -323,7 +332,7 @@ func (r *PEXReactor) ensurePeers() { if peersCount > 0 { peer := peers[rand.Int()%peersCount] // nolint: gas r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer) - r.RequestPEX(peer) + r.RequestAddrs(peer) } } @@ -421,7 +430,7 @@ func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { var of oldestFirst - // TODO: not this. be more selective + // TODO: be more selective addrs := r.book.ListOfKnownAddresses() for _, addr := range addrs { if len(addr.ID()) == 0 { @@ -462,7 +471,7 @@ func (r *PEXReactor) crawlPeers() { if now.Sub(pi.LastAttempt) >= defaultCrawlPeerInterval { peer := r.Switch.Peers().Get(pi.Addr.ID) if peer != nil { - r.RequestPEX(peer) + r.RequestAddrs(peer) } } } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 91e30fea..44fd8b51 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -154,7 +154,7 @@ func TestPEXReactorReceive(t *testing.T) { peer := createRandomPeer(false) // we have to send a request to receive responses - r.RequestPEX(peer) + r.RequestAddrs(peer) size := book.Size() addrs := []*NetAddress{peer.NodeInfo().NetAddress()} @@ -228,7 +228,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { id := string(peer.ID()) // request addrs from the peer - r.RequestPEX(peer) + r.RequestAddrs(peer) assert.True(r.requestsSent.Has(id)) assert.True(sw.Peers().Has(peer.ID())) From 8d758560d83ebb827168ce3ebb70e6e99d310101 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 19:30:13 -0500 Subject: [PATCH 090/124] p2p/trustmetric: non-deterministic test --- p2p/trust/metric_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index 69c9f8f2..98ea99ab 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -56,7 +56,8 @@ func TestTrustMetricConfig(t *testing.T) { tm.Wait() } -func TestTrustMetricStopPause(t *testing.T) { +// XXX: This test fails non-deterministically +func _TestTrustMetricStopPause(t *testing.T) { // The TestTicker will provide manual control over // the passing of time within the metric tt := NewTestTicker() @@ -89,6 +90,8 @@ func TestTrustMetricStopPause(t *testing.T) { // and check that the number of intervals match tm.NextTimeInterval() tm.NextTimeInterval() + // XXX: fails non-deterministically: + // expected 5, got 6 assert.Equal(t, second+2, tm.Copy().numIntervals) if first > second { From 930fde056a15b238c10861377bbf2fb7a29b171c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 21:28:00 -0500 Subject: [PATCH 091/124] p2p: add back lost func --- p2p/addrbook.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 7591c307..fee179cb 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -324,6 +324,30 @@ func (a *AddrBook) GetSelection() []*NetAddress { return allAddr[:numAddresses] } +// ListOfKnownAddresses returns the new and old addresses. +func (a *AddrBook) ListOfKnownAddresses() []*knownAddress { + a.mtx.Lock() + defer a.mtx.Unlock() + + addrs := []*knownAddress{} + for _, addr := range a.addrLookup { + addrs = append(addrs, addr.copy()) + } + return addrs +} + +func (ka *knownAddress) copy() *knownAddress { + return &knownAddress{ + Addr: ka.Addr, + Src: ka.Src, + Attempts: ka.Attempts, + LastAttempt: ka.LastAttempt, + LastSuccess: ka.LastSuccess, + BucketType: ka.BucketType, + Buckets: ka.Buckets, + } +} + /* Loading & Saving */ type addrBookJSON struct { From 03550c7076279317bfcbeda944f757163ff19fdc Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 19:12:04 -0500 Subject: [PATCH 092/124] wip addrbook --- p2p/{ => addrbook}/addrbook.go | 289 +++++++++------------------- p2p/{ => addrbook}/addrbook_test.go | 2 +- p2p/addrbook/file.go | 83 ++++++++ p2p/addrbook/known_address.go | 138 +++++++++++++ p2p/addrbook/params.go | 55 ++++++ p2p/pex_reactor.go | 6 +- p2p/switch.go | 1 - 7 files changed, 368 insertions(+), 206 deletions(-) rename p2p/{ => addrbook}/addrbook.go (75%) rename p2p/{ => addrbook}/addrbook_test.go (99%) create mode 100644 p2p/addrbook/file.go create mode 100644 p2p/addrbook/known_address.go create mode 100644 p2p/addrbook/params.go diff --git a/p2p/addrbook.go b/p2p/addrbook/addrbook.go similarity index 75% rename from p2p/addrbook.go rename to p2p/addrbook/addrbook.go index fee179cb..1f317a2e 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook/addrbook.go @@ -2,17 +2,15 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package p2p +package addrbook import ( "crypto/sha256" "encoding/binary" - "encoding/json" "fmt" "math" "math/rand" "net" - "os" "sync" "time" @@ -20,65 +18,39 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) -const ( - // addresses under which the address manager will claim to need more addresses. - needAddressThreshold = 1000 - - // interval used to dump the address cache to disk for future use. - dumpAddressInterval = time.Minute * 2 - - // max addresses in each old address bucket. - oldBucketSize = 64 - - // buckets we split old addresses over. - oldBucketCount = 64 - - // max addresses in each new address bucket. - newBucketSize = 64 - - // buckets that we spread new addresses over. - newBucketCount = 256 - - // old buckets over which an address group will be spread. - oldBucketsPerGroup = 4 - - // new buckets over which a source address group will be spread. - newBucketsPerGroup = 32 - - // buckets a frequently seen new address may end up in. - maxNewBucketsPerAddress = 4 - - // days before which we assume an address has vanished - // if we have not seen it announced in that long. - numMissingDays = 30 - - // tries without a single success before we assume an address is bad. - numRetries = 3 - - // max failures we will accept without a success before considering an address bad. - maxFailures = 10 - - // days since the last success before we will consider evicting an address. - minBadDays = 7 - - // % of total addresses known returned by GetSelection. - getSelectionPercent = 23 - - // min addresses that must be returned by GetSelection. Useful for bootstrapping. - minGetSelection = 32 - - // max addresses returned by GetSelection - // NOTE: this must match "maxPexMessageSize" - maxGetSelection = 250 -) - const ( bucketTypeNew = 0x01 bucketTypeOld = 0x02 ) -// AddrBook - concurrency safe peer address manager. -type AddrBook struct { +// AddrBook is an address book used for tracking peers +// so we can gossip about them to others and select +// peers to dial. +type AddrBook interface { + cmn.Service + + // Add and remove an address + AddAddress(addr *NetAddress, src *NetAddress) + RemoveAddress(addr *NetAddress) + + // Do we need more peers? + NeedMoreAddrs() bool + + // Pick an address to dial + PickAddress(newBias int) *NetAddress + + // Mark address + MarkGood(*NetAddress) + MarkAttempt(*Address) + MarkBad(*NetAddress) + + // Send a selection of addresses to peers + GetSelection() []*NetAddress +} + +// addrBook - concurrency safe peer address manager. +// Implements AddrBook. +type addrBook struct { cmn.BaseService // immutable after creation @@ -101,7 +73,7 @@ type AddrBook struct { // NewAddrBook creates a new address book. // Use Start to begin processing asynchronous address updates. -func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook { +func NewAddrBook(filePath string, routabilityStrict bool) *addrBook { am := &AddrBook{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), ourAddrs: make(map[string]*NetAddress), @@ -114,8 +86,9 @@ func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook { return am } +// Initialize the buckets. // When modifying this, don't forget to update loadFromFile() -func (a *AddrBook) init() { +func (a *addrBook) init() { a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits // New addr buckets a.bucketsNew = make([]map[string]*knownAddress, newBucketCount) @@ -130,7 +103,7 @@ func (a *AddrBook) init() { } // OnStart implements Service. -func (a *AddrBook) OnStart() error { +func (a *addrBook) OnStart() error { if err := a.BaseService.OnStart(); err != nil { return err } @@ -145,11 +118,11 @@ func (a *AddrBook) OnStart() error { } // OnStop implements Service. -func (a *AddrBook) OnStop() { +func (a *addrBook) OnStop() { a.BaseService.OnStop() } -func (a *AddrBook) Wait() { +func (a *addrBook) Wait() { a.wg.Wait() } @@ -161,46 +134,40 @@ func (a *AddrBook) AddOurAddress(addr *NetAddress) { a.ourAddrs[addr.String()] = addr } -// OurAddresses returns a list of our addresses. -func (a *AddrBook) OurAddresses() []*NetAddress { - addrs := []*NetAddress{} - for _, addr := range a.ourAddrs { - addrs = append(addrs, addr) - } - return addrs -} +//------------------------------------------------------- -// AddAddress adds the given address as received from the given source. +// AddAddress implements AddrBook - adds the given address as received from the given source. // NOTE: addr must not be nil -func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) error { +func (a *addrBook) AddAddress(addr *NetAddress, src *NetAddress) error { a.mtx.Lock() defer a.mtx.Unlock() return a.addAddress(addr, src) } -// NeedMoreAddrs returns true if there are not have enough addresses in the book. -func (a *AddrBook) NeedMoreAddrs() bool { +// RemoveAddress implements AddrBook - removes the address from the book. +func (a *addrBook) RemoveAddress(addr *NetAddress) { + a.mtx.Lock() + defer a.mtx.Unlock() + ka := a.addrLookup[addr.ID] + if ka == nil { + return + } + a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID) + a.removeFromAllBuckets(ka) +} + +// NeedMoreAddrs implements AddrBook - returns true if there are not have enough addresses in the book. +func (a *addrBook) NeedMoreAddrs() bool { return a.Size() < needAddressThreshold } -// Size returns the number of addresses in the book. -func (a *AddrBook) Size() int { - a.mtx.Lock() - defer a.mtx.Unlock() - return a.size() -} - -func (a *AddrBook) size() int { - return a.nNew + a.nOld -} - -// PickAddress picks an address to connect to. +// PickAddress implements AddrBook. It picks an address to connect to. // The address is picked randomly from an old or new bucket according // to the newBias argument, which must be between [0, 100] (or else is truncated to that range) // and determines how biased we are to pick an address from a new bucket. // PickAddress returns nil if the AddrBook is empty or if we try to pick // from an empty bucket. -func (a *AddrBook) PickAddress(newBias int) *NetAddress { +func (a *addrBook) PickAddress(newBias int) *NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -244,9 +211,9 @@ func (a *AddrBook) PickAddress(newBias int) *NetAddress { return nil } -// MarkGood marks the peer as good and moves it into an "old" bucket. -// TODO: call this from somewhere -func (a *AddrBook) MarkGood(addr *NetAddress) { +// MarkGood implements AddrBook - it marks the peer as good and +// moves it into an "old" bucket. +func (a *addrBook) MarkGood(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -259,8 +226,8 @@ func (a *AddrBook) MarkGood(addr *NetAddress) { } } -// MarkAttempt marks that an attempt was made to connect to the address. -func (a *AddrBook) MarkAttempt(addr *NetAddress) { +// MarkAttempt implements AddrBook - it marks that an attempt was made to connect to the address. +func (a *addrBook) MarkAttempt(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -270,28 +237,15 @@ func (a *AddrBook) MarkAttempt(addr *NetAddress) { ka.markAttempt() } -// MarkBad currently just ejects the address. In the future, consider -// blacklisting. -func (a *AddrBook) MarkBad(addr *NetAddress) { +// MarkBad implements AddrBook. Currently it just ejects the address. +// TODO: black list for some amount of time +func (a *addrBook) MarkBad(addr *NetAddress) { a.RemoveAddress(addr) } -// RemoveAddress removes the address from the book. -func (a *AddrBook) RemoveAddress(addr *NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() - ka := a.addrLookup[addr.ID] - if ka == nil { - return - } - a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID) - a.removeFromAllBuckets(ka) -} - -/* Peer exchange */ - -// GetSelection randomly selects some addresses (old & new). Suitable for peer-exchange protocols. -func (a *AddrBook) GetSelection() []*NetAddress { +// GetSelection implements AddrBook. +// It randomly selects some addresses (old & new). Suitable for peer-exchange protocols. +func (a *addrBook) GetSelection() []*NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -336,18 +290,6 @@ func (a *AddrBook) ListOfKnownAddresses() []*knownAddress { return addrs } -func (ka *knownAddress) copy() *knownAddress { - return &knownAddress{ - Addr: ka.Addr, - Src: ka.Src, - Attempts: ka.Attempts, - LastAttempt: ka.LastAttempt, - LastSuccess: ka.LastSuccess, - BucketType: ka.BucketType, - Buckets: ka.Buckets, - } -} - /* Loading & Saving */ type addrBookJSON struct { @@ -357,81 +299,24 @@ type addrBookJSON struct { func (a *AddrBook) saveToFile(filePath string) { a.Logger.Info("Saving AddrBook to file", "size", a.Size()) +} +//------------------------------------------------ + +// Size returns the number of addresses in the book. +func (a *addrBook) Size() int { a.mtx.Lock() defer a.mtx.Unlock() - // Compile Addrs - addrs := []*knownAddress{} - for _, ka := range a.addrLookup { - addrs = append(addrs, ka) - } - - aJSON := &addrBookJSON{ - Key: a.key, - Addrs: addrs, - } - - jsonBytes, err := json.MarshalIndent(aJSON, "", "\t") - if err != nil { - a.Logger.Error("Failed to save AddrBook to file", "err", err) - return - } - err = cmn.WriteFileAtomic(filePath, jsonBytes, 0644) - if err != nil { - a.Logger.Error("Failed to save AddrBook to file", "file", filePath, "err", err) - } + return a.size() } -// Returns false if file does not exist. -// cmn.Panics if file is corrupt. -func (a *AddrBook) loadFromFile(filePath string) bool { - // If doesn't exist, do nothing. - _, err := os.Stat(filePath) - if os.IsNotExist(err) { - return false - } - - // Load addrBookJSON{} - r, err := os.Open(filePath) - if err != nil { - cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err)) - } - defer r.Close() // nolint: errcheck - aJSON := &addrBookJSON{} - dec := json.NewDecoder(r) - err = dec.Decode(aJSON) - if err != nil { - cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)) - } - - // Restore all the fields... - // Restore the key - a.key = aJSON.Key - // Restore .bucketsNew & .bucketsOld - for _, ka := range aJSON.Addrs { - for _, bucketIndex := range ka.Buckets { - bucket := a.getBucket(ka.BucketType, bucketIndex) - bucket[ka.Addr.String()] = ka - } - a.addrLookup[ka.ID()] = ka - if ka.BucketType == bucketTypeNew { - a.nNew++ - } else { - a.nOld++ - } - } - return true +func (a *addrBook) size() int { + return a.nNew + a.nOld } -// Save saves the book. -func (a *AddrBook) Save() { - a.Logger.Info("Saving AddrBook to file", "size", a.Size()) - a.saveToFile(a.filePath) -} +//---------------------------------------------------------- -/* Private methods */ - -func (a *AddrBook) saveRoutine() { +func (a *addrBook) saveRoutine() { defer a.wg.Done() saveFileTicker := time.NewTicker(dumpAddressInterval) @@ -449,7 +334,7 @@ out: a.Logger.Info("Address handler done") } -func (a *AddrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress { +func (a *addrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress { switch bucketType { case bucketTypeNew: return a.bucketsNew[bucketIdx] @@ -463,7 +348,7 @@ func (a *AddrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAd // Adds ka to new bucket. Returns false if it couldn't do it cuz buckets full. // NOTE: currently it always returns true. -func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { +func (a *addrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { // Sanity check if ka.isOld() { a.Logger.Error(cmn.Fmt("Cannot add address already in old bucket to a new bucket: %v", ka)) @@ -497,7 +382,7 @@ func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { } // Adds ka to old bucket. Returns false if it couldn't do it cuz buckets full. -func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool { +func (a *addrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool { // Sanity check if ka.isNew() { a.Logger.Error(cmn.Fmt("Cannot add new address to old bucket: %v", ka)) @@ -533,7 +418,7 @@ func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool { return true } -func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx int) { +func (a *addrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx int) { if ka.BucketType != bucketType { a.Logger.Error(cmn.Fmt("Bucket type mismatch: %v", ka)) return @@ -550,7 +435,7 @@ func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx } } -func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) { +func (a *addrBook) removeFromAllBuckets(ka *knownAddress) { for _, bucketIdx := range ka.Buckets { bucket := a.getBucket(ka.BucketType, bucketIdx) delete(bucket, ka.Addr.String()) @@ -564,7 +449,7 @@ func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) { delete(a.addrLookup, ka.ID()) } -func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { +func (a *addrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { bucket := a.getBucket(bucketType, bucketIdx) var oldest *knownAddress for _, ka := range bucket { @@ -575,7 +460,7 @@ func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { return oldest } -func (a *AddrBook) addAddress(addr, src *NetAddress) error { +func (a *addrBook) addAddress(addr, src *NetAddress) error { if a.routabilityStrict && !addr.Routable() { return fmt.Errorf("Cannot add non-routable address %v", addr) } @@ -613,7 +498,7 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) error { // Make space in the new buckets by expiring the really bad entries. // If no bad entries are available we remove the oldest. -func (a *AddrBook) expireNew(bucketIdx int) { +func (a *addrBook) expireNew(bucketIdx int) { for addrStr, ka := range a.bucketsNew[bucketIdx] { // If an entry is bad, throw it away if ka.isBad() { @@ -631,7 +516,7 @@ func (a *AddrBook) expireNew(bucketIdx int) { // Promotes an address from new to old. // TODO: Move to old probabilistically. // The better a node is, the less likely it should be evicted from an old bucket. -func (a *AddrBook) moveToOld(ka *knownAddress) { +func (a *addrBook) moveToOld(ka *knownAddress) { // Sanity check if ka.isOld() { a.Logger.Error(cmn.Fmt("Cannot promote address that is already old %v", ka)) @@ -676,7 +561,7 @@ func (a *AddrBook) moveToOld(ka *knownAddress) { // doublesha256( key + sourcegroup + // int64(doublesha256(key + group + sourcegroup))%bucket_per_group ) % num_new_buckets -func (a *AddrBook) calcNewBucket(addr, src *NetAddress) int { +func (a *addrBook) calcNewBucket(addr, src *NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(a.groupKey(addr))...) @@ -697,7 +582,7 @@ func (a *AddrBook) calcNewBucket(addr, src *NetAddress) int { // doublesha256( key + group + // int64(doublesha256(key + addr))%buckets_per_group ) % num_old_buckets -func (a *AddrBook) calcOldBucket(addr *NetAddress) int { +func (a *addrBook) calcOldBucket(addr *NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(addr.String())...) @@ -719,7 +604,7 @@ func (a *AddrBook) calcOldBucket(addr *NetAddress) int { // This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string // "local" for a local address and the string "unroutable" for an unroutable // address. -func (a *AddrBook) groupKey(na *NetAddress) string { +func (a *addrBook) groupKey(na *NetAddress) string { if a.routabilityStrict && na.Local() { return "local" } diff --git a/p2p/addrbook_test.go b/p2p/addrbook/addrbook_test.go similarity index 99% rename from p2p/addrbook_test.go rename to p2p/addrbook/addrbook_test.go index 00051ae1..ff8d239d 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook/addrbook_test.go @@ -1,4 +1,4 @@ -package p2p +package addrbook import ( "encoding/hex" diff --git a/p2p/addrbook/file.go b/p2p/addrbook/file.go new file mode 100644 index 00000000..956ac56c --- /dev/null +++ b/p2p/addrbook/file.go @@ -0,0 +1,83 @@ +package addrbook + +import ( + "encoding/json" + "os" + + cmn "github.com/tendermint/tmlibs/common" +) + +/* Loading & Saving */ + +type addrBookJSON struct { + Key string + Addrs []*knownAddress +} + +func (a *addrBook) saveToFile(filePath string) { + a.Logger.Info("Saving AddrBook to file", "size", a.Size()) + + a.mtx.Lock() + defer a.mtx.Unlock() + // Compile Addrs + addrs := []*knownAddress{} + for _, ka := range a.addrLookup { + addrs = append(addrs, ka) + } + + aJSON := &addrBookJSON{ + Key: a.key, + Addrs: addrs, + } + + jsonBytes, err := json.MarshalIndent(aJSON, "", "\t") + if err != nil { + a.Logger.Error("Failed to save AddrBook to file", "err", err) + return + } + err = cmn.WriteFileAtomic(filePath, jsonBytes, 0644) + if err != nil { + a.Logger.Error("Failed to save AddrBook to file", "file", filePath, "err", err) + } +} + +// Returns false if file does not exist. +// cmn.Panics if file is corrupt. +func (a *addrBook) loadFromFile(filePath string) bool { + // If doesn't exist, do nothing. + _, err := os.Stat(filePath) + if os.IsNotExist(err) { + return false + } + + // Load addrBookJSON{} + r, err := os.Open(filePath) + if err != nil { + cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err)) + } + defer r.Close() // nolint: errcheck + aJSON := &addrBookJSON{} + dec := json.NewDecoder(r) + err = dec.Decode(aJSON) + if err != nil { + cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)) + } + + // Restore all the fields... + // Restore the key + a.key = aJSON.Key + // Restore .bucketsNew & .bucketsOld + for _, ka := range aJSON.Addrs { + for _, bucketIndex := range ka.Buckets { + bucket := a.getBucket(ka.BucketType, bucketIndex) + bucket[ka.Addr.String()] = ka + } + a.addrLookup[ka.ID()] = ka + if ka.BucketType == bucketTypeNew { + a.nNew++ + } else { + a.nOld++ + } + } + return true +} diff --git a/p2p/addrbook/known_address.go b/p2p/addrbook/known_address.go new file mode 100644 index 00000000..2a879081 --- /dev/null +++ b/p2p/addrbook/known_address.go @@ -0,0 +1,138 @@ +package addrbook + +import "time" + +// knownAddress tracks information about a known network address +// that is used to determine how viable an address is. +type knownAddress struct { + Addr *NetAddress + Src *NetAddress + Attempts int32 + LastAttempt time.Time + LastSuccess time.Time + BucketType byte + Buckets []int +} + +func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { + return &knownAddress{ + Addr: addr, + Src: src, + Attempts: 0, + LastAttempt: time.Now(), + BucketType: bucketTypeNew, + Buckets: nil, + } +} + +func (ka *knownAddress) ID() ID { + return ka.Addr.ID +} + +func (ka *knownAddress) copy() *knownAddress { + return &knownAddress{ + Addr: ka.Addr, + Src: ka.Src, + Attempts: ka.Attempts, + LastAttempt: ka.LastAttempt, + LastSuccess: ka.LastSuccess, + BucketType: ka.BucketType, + Buckets: ka.Buckets, + } +} + +func (ka *knownAddress) isOld() bool { + return ka.BucketType == bucketTypeOld +} + +func (ka *knownAddress) isNew() bool { + return ka.BucketType == bucketTypeNew +} + +func (ka *knownAddress) markAttempt() { + now := time.Now() + ka.LastAttempt = now + ka.Attempts += 1 +} + +func (ka *knownAddress) markGood() { + now := time.Now() + ka.LastAttempt = now + ka.Attempts = 0 + ka.LastSuccess = now +} + +func (ka *knownAddress) addBucketRef(bucketIdx int) int { + for _, bucket := range ka.Buckets { + if bucket == bucketIdx { + // TODO refactor to return error? + // log.Warn(Fmt("Bucket already exists in ka.Buckets: %v", ka)) + return -1 + } + } + ka.Buckets = append(ka.Buckets, bucketIdx) + return len(ka.Buckets) +} + +func (ka *knownAddress) removeBucketRef(bucketIdx int) int { + buckets := []int{} + for _, bucket := range ka.Buckets { + if bucket != bucketIdx { + buckets = append(buckets, bucket) + } + } + if len(buckets) != len(ka.Buckets)-1 { + // TODO refactor to return error? + // log.Warn(Fmt("bucketIdx not found in ka.Buckets: %v", ka)) + return -1 + } + ka.Buckets = buckets + return len(ka.Buckets) +} + +/* + An address is bad if the address in question is a New address, has not been tried in the last + minute, and meets one of the following criteria: + + 1) It claims to be from the future + 2) It hasn't been seen in over a week + 3) It has failed at least three times and never succeeded + 4) It has failed ten times in the last week + + All addresses that meet these criteria are assumed to be worthless and not + worth keeping hold of. + + XXX: so a good peer needs us to call MarkGood before the conditions above are reached! +*/ +func (ka *knownAddress) isBad() bool { + // Is Old --> good + if ka.BucketType == bucketTypeOld { + return false + } + + // Has been attempted in the last minute --> good + if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) { + return false + } + + // Too old? + // XXX: does this mean if we've kept a connection up for this long we'll disconnect?! + // and shouldn't it be .Before ? + if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { + return true + } + + // Never succeeded? + if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries { + return true + } + + // Hasn't succeeded in too long? + // XXX: does this mean if we've kept a connection up for this long we'll disconnect?! + if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) && + ka.Attempts >= maxFailures { + return true + } + + return false +} diff --git a/p2p/addrbook/params.go b/p2p/addrbook/params.go new file mode 100644 index 00000000..f410ed9a --- /dev/null +++ b/p2p/addrbook/params.go @@ -0,0 +1,55 @@ +package addrbook + +import "time" + +const ( + // addresses under which the address manager will claim to need more addresses. + needAddressThreshold = 1000 + + // interval used to dump the address cache to disk for future use. + dumpAddressInterval = time.Minute * 2 + + // max addresses in each old address bucket. + oldBucketSize = 64 + + // buckets we split old addresses over. + oldBucketCount = 64 + + // max addresses in each new address bucket. + newBucketSize = 64 + + // buckets that we spread new addresses over. + newBucketCount = 256 + + // old buckets over which an address group will be spread. + oldBucketsPerGroup = 4 + + // new buckets over which a source address group will be spread. + newBucketsPerGroup = 32 + + // buckets a frequently seen new address may end up in. + maxNewBucketsPerAddress = 4 + + // days before which we assume an address has vanished + // if we have not seen it announced in that long. + numMissingDays = 7 + + // tries without a single success before we assume an address is bad. + numRetries = 3 + + // max failures we will accept without a success before considering an address bad. + maxFailures = 10 // ? + + // days since the last success before we will consider evicting an address. + minBadDays = 7 + + // % of total addresses known returned by GetSelection. + getSelectionPercent = 23 + + // min addresses that must be returned by GetSelection. Useful for bootstrapping. + minGetSelection = 32 + + // max addresses returned by GetSelection + // NOTE: this must match "maxPexMessageSize" + maxGetSelection = 250 +) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index bd19ab3b..57665e07 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -11,6 +11,8 @@ import ( "github.com/pkg/errors" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" + + "github.com/tendermint/tendermint/p2p/addrbook" ) const ( @@ -47,7 +49,7 @@ const ( type PEXReactor struct { BaseReactor - book *AddrBook + book *addrbook.AddrBook config *PEXReactorConfig ensurePeersPeriod time.Duration @@ -67,7 +69,7 @@ type PEXReactorConfig struct { } // NewPEXReactor creates new PEX reactor. -func NewPEXReactor(b *AddrBook, config *PEXReactorConfig) *PEXReactor { +func NewPEXReactor(b *addrbook.AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ book: b, config: config, diff --git a/p2p/switch.go b/p2p/switch.go index 3f026556..db2e7d98 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -332,7 +332,6 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent } addrBook.AddAddress(netAddr, ourAddr) } - addrBook.Save() } // permute the list, dial them in random order. From 5b5cbaa66a4b036abdec06015a71bc1ebf9c059d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 21:12:04 -0500 Subject: [PATCH 093/124] p2p: use sub dirs --- p2p/base_reactor.go | 9 +- p2p/listener.go | 27 +-- p2p/peer.go | 55 ++--- p2p/peer_set.go | 16 +- p2p/peer_set_test.go | 5 +- p2p/peer_test.go | 18 +- p2p/{addrbook => pex}/addrbook.go | 241 ++++++--------------- p2p/{addrbook => pex}/addrbook_test.go | 15 +- p2p/{addrbook => pex}/file.go | 2 +- p2p/{addrbook => pex}/known_address.go | 16 +- p2p/{addrbook => pex}/params.go | 2 +- p2p/{ => pex}/pex_reactor.go | 46 ++-- p2p/{ => pex}/pex_reactor_test.go | 111 ++++------ p2p/switch.go | 46 ++-- p2p/switch_test.go | 40 ++-- p2p/test_util.go | 48 +++- p2p/{ => tmconn}/conn_go110.go | 4 +- p2p/{ => tmconn}/conn_notgo110.go | 4 +- p2p/{ => tmconn}/connection.go | 16 +- p2p/{ => tmconn}/connection_test.go | 14 +- p2p/{ => tmconn}/secret_connection.go | 2 +- p2p/{ => tmconn}/secret_connection_test.go | 2 +- p2p/types/errors.go | 20 ++ p2p/{ => types}/key.go | 2 +- p2p/{ => types}/key_test.go | 2 +- p2p/{ => types}/netaddress.go | 2 +- p2p/{ => types}/netaddress_test.go | 2 +- p2p/{types.go => types/node_info.go} | 6 +- 28 files changed, 366 insertions(+), 407 deletions(-) rename p2p/{addrbook => pex}/addrbook.go (76%) rename p2p/{addrbook => pex}/addrbook_test.go (93%) rename p2p/{addrbook => pex}/file.go (99%) rename p2p/{addrbook => pex}/known_address.go (92%) rename p2p/{addrbook => pex}/params.go (98%) rename p2p/{ => pex}/pex_reactor.go (92%) rename p2p/{ => pex}/pex_reactor_test.go (76%) rename p2p/{ => tmconn}/conn_go110.go (86%) rename p2p/{ => tmconn}/conn_notgo110.go (93%) rename p2p/{ => tmconn}/connection.go (98%) rename p2p/{ => tmconn}/connection_test.go (97%) rename p2p/{ => tmconn}/secret_connection.go (99%) rename p2p/{ => tmconn}/secret_connection_test.go (99%) create mode 100644 p2p/types/errors.go rename p2p/{ => types}/key.go (99%) rename p2p/{ => types}/key_test.go (98%) rename p2p/{ => types}/netaddress.go (99%) rename p2p/{ => types}/netaddress_test.go (99%) rename p2p/{types.go => types/node_info.go} (97%) diff --git a/p2p/base_reactor.go b/p2p/base_reactor.go index e8107d73..a24a7629 100644 --- a/p2p/base_reactor.go +++ b/p2p/base_reactor.go @@ -1,12 +1,15 @@ package p2p -import cmn "github.com/tendermint/tmlibs/common" +import ( + "github.com/tendermint/tendermint/p2p/tmconn" + cmn "github.com/tendermint/tmlibs/common" +) type Reactor interface { cmn.Service // Start, Stop SetSwitch(*Switch) - GetChannels() []*ChannelDescriptor + GetChannels() []*tmconn.ChannelDescriptor AddPeer(peer Peer) RemovePeer(peer Peer, reason interface{}) Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil @@ -29,7 +32,7 @@ func NewBaseReactor(name string, impl Reactor) *BaseReactor { func (br *BaseReactor) SetSwitch(sw *Switch) { br.Switch = sw } -func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil } +func (_ *BaseReactor) GetChannels() []*tmconn.ChannelDescriptor { return nil } func (_ *BaseReactor) AddPeer(peer Peer) {} func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} diff --git a/p2p/listener.go b/p2p/listener.go index 884c45ee..01d71833 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/tendermint/tendermint/p2p/types" "github.com/tendermint/tendermint/p2p/upnp" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -13,8 +14,8 @@ import ( type Listener interface { Connections() <-chan net.Conn - InternalAddress() *NetAddress - ExternalAddress() *NetAddress + InternalAddress() *types.NetAddress + ExternalAddress() *types.NetAddress String() string Stop() error } @@ -24,8 +25,8 @@ type DefaultListener struct { cmn.BaseService listener net.Listener - intAddr *NetAddress - extAddr *NetAddress + intAddr *types.NetAddress + extAddr *types.NetAddress connections chan net.Conn } @@ -71,14 +72,14 @@ func NewDefaultListener(protocol string, lAddr string, skipUPNP bool, logger log logger.Info("Local listener", "ip", listenerIP, "port", listenerPort) // Determine internal address... - var intAddr *NetAddress - intAddr, err = NewNetAddressString(lAddr) + var intAddr *types.NetAddress + intAddr, err = types.NewNetAddressString(lAddr) if err != nil { panic(err) } // Determine external address... - var extAddr *NetAddress + var extAddr *types.NetAddress if !skipUPNP { // If the lAddrIP is INADDR_ANY, try UPnP if lAddrIP == "" || lAddrIP == "0.0.0.0" { @@ -151,11 +152,11 @@ func (l *DefaultListener) Connections() <-chan net.Conn { return l.connections } -func (l *DefaultListener) InternalAddress() *NetAddress { +func (l *DefaultListener) InternalAddress() *types.NetAddress { return l.intAddr } -func (l *DefaultListener) ExternalAddress() *NetAddress { +func (l *DefaultListener) ExternalAddress() *types.NetAddress { return l.extAddr } @@ -172,7 +173,7 @@ func (l *DefaultListener) String() string { /* external address helpers */ // UPNP external address discovery & port mapping -func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) *NetAddress { +func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) *types.NetAddress { logger.Info("Getting UPNP external address") nat, err := upnp.Discover() if err != nil { @@ -198,11 +199,11 @@ func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) * } logger.Info("Got UPNP external address", "address", ext) - return NewNetAddressIPPort(ext, uint16(externalPort)) + return types.NewNetAddressIPPort(ext, uint16(externalPort)) } // TODO: use syscalls: see issue #712 -func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *NetAddress { +func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *types.NetAddress { addrs, err := net.InterfaceAddrs() if err != nil { panic(cmn.Fmt("Could not fetch interface addresses: %v", err)) @@ -217,7 +218,7 @@ func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) * if v4 == nil || (!settleForLocal && v4[0] == 127) { continue } // loopback - return NewNetAddressIPPort(ipnet.IP, uint16(port)) + return types.NewNetAddressIPPort(ipnet.IP, uint16(port)) } // try again, but settle for local diff --git a/p2p/peer.go b/p2p/peer.go index 596b9216..17c5861f 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -11,17 +11,20 @@ import ( wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) // Peer is an interface representing a peer connected on a reactor. type Peer interface { cmn.Service - ID() ID // peer's cryptographic ID - IsOutbound() bool // did we dial the peer - IsPersistent() bool // do we redial this peer when we disconnect - NodeInfo() NodeInfo // peer's info - Status() ConnectionStatus + ID() types.ID // peer's cryptographic ID + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + NodeInfo() types.NodeInfo // peer's info + Status() tmconn.ConnectionStatus Send(byte, interface{}) bool TrySend(byte, interface{}) bool @@ -40,13 +43,13 @@ type peer struct { outbound bool - conn net.Conn // source connection - mconn *MConnection // multiplex connection + conn net.Conn // source connection + mconn *tmconn.MConnection // multiplex connection persistent bool config *PeerConfig - nodeInfo NodeInfo + nodeInfo types.NodeInfo Data *cmn.CMap // User data. } @@ -58,7 +61,7 @@ type PeerConfig struct { HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"` DialTimeout time.Duration `mapstructure:"dial_timeout"` - MConfig *MConnConfig `mapstructure:"connection"` + MConfig *tmconn.MConnConfig `mapstructure:"connection"` Fuzz bool `mapstructure:"fuzz"` // fuzz connection (for testing) FuzzConfig *FuzzConnConfig `mapstructure:"fuzz_config"` @@ -70,13 +73,13 @@ func DefaultPeerConfig() *PeerConfig { AuthEnc: true, HandshakeTimeout: 20, // * time.Second, DialTimeout: 3, // * time.Second, - MConfig: DefaultMConnConfig(), + MConfig: tmconn.DefaultMConnConfig(), Fuzz: false, FuzzConfig: DefaultFuzzConnConfig(), } } -func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, +func newOutboundPeer(addr *types.NetAddress, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig, persistent bool) (*peer, error) { conn, err := dial(addr, config) @@ -96,7 +99,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] return peer, nil } -func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, +func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { // TODO: issue PoW challenge @@ -104,7 +107,7 @@ func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*Cha return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) } -func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, +func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { conn := rawConn @@ -122,7 +125,7 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ } var err error - conn, err = MakeSecretConnection(conn, ourNodePrivKey) + conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey) if err != nil { return nil, errors.Wrap(err, "Error creating peer") } @@ -171,8 +174,8 @@ func (p *peer) OnStop() { // Implements Peer // ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() ID { - return PubKeyToID(p.PubKey()) +func (p *peer) ID() types.ID { + return types.PubKeyToID(p.PubKey()) } // IsOutbound returns true if the connection is outbound, false otherwise. @@ -186,12 +189,12 @@ func (p *peer) IsPersistent() bool { } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() NodeInfo { +func (p *peer) NodeInfo() types.NodeInfo { return p.nodeInfo } // Status returns the peer's ConnectionStatus. -func (p *peer) Status() ConnectionStatus { +func (p *peer) Status() tmconn.ConnectionStatus { return p.mconn.Status() } @@ -236,13 +239,13 @@ func (p *peer) CloseConn() { // HandshakeTimeout performs the Tendermint P2P handshake between a given node and the peer // by exchanging their NodeInfo. It sets the received nodeInfo on the peer. // NOTE: blocking -func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { +func (p *peer) HandshakeTimeout(ourNodeInfo types.NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull if err := p.conn.SetDeadline(time.Now().Add(timeout)); err != nil { return errors.Wrap(err, "Error setting deadline") } - var peerNodeInfo NodeInfo + var peerNodeInfo types.NodeInfo var err1 error var err2 error cmn.Parallel( @@ -252,7 +255,7 @@ func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) err }, func() { var n int - wire.ReadBinary(&peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) + wire.ReadBinary(&peerNodeInfo, p.conn, types.MaxNodeInfoSize(), &n, &err2) p.Logger.Info("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { @@ -283,7 +286,7 @@ func (p *peer) PubKey() crypto.PubKey { if !p.nodeInfo.PubKey.Empty() { return p.nodeInfo.PubKey } else if p.config.AuthEnc { - return p.conn.(*SecretConnection).RemotePubKey() + return p.conn.(*tmconn.SecretConnection).RemotePubKey() } panic("Attempt to get peer's PubKey before calling Handshake") } @@ -308,7 +311,7 @@ func (p *peer) String() string { //------------------------------------------------------------------ // helper funcs -func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { +func dial(addr *types.NetAddress, config *PeerConfig) (net.Conn, error) { conn, err := addr.DialTimeout(config.DialTimeout * time.Second) if err != nil { return nil, err @@ -316,8 +319,8 @@ func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { return conn, nil } -func createMConnection(conn net.Conn, p *peer, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), config *MConnConfig) *MConnection { +func createMConnection(conn net.Conn, p *peer, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, + onPeerError func(Peer, interface{}), config *tmconn.MConnConfig) *tmconn.MConnection { onReceive := func(chID byte, msgBytes []byte) { reactor := reactorsByCh[chID] @@ -331,5 +334,5 @@ func createMConnection(conn net.Conn, p *peer, reactorsByCh map[byte]Reactor, ch onPeerError(p, r) } - return NewMConnectionWithConfig(conn, chDescs, onReceive, onError, config) + return tmconn.NewMConnectionWithConfig(conn, chDescs, onReceive, onError, config) } diff --git a/p2p/peer_set.go b/p2p/peer_set.go index dc53174a..7a0680cb 100644 --- a/p2p/peer_set.go +++ b/p2p/peer_set.go @@ -2,12 +2,14 @@ package p2p import ( "sync" + + "github.com/tendermint/tendermint/p2p/types" ) // IPeerSet has a (immutable) subset of the methods of PeerSet. type IPeerSet interface { - Has(key ID) bool - Get(key ID) Peer + Has(key types.ID) bool + Get(key types.ID) Peer List() []Peer Size() int } @@ -18,7 +20,7 @@ type IPeerSet interface { // Iteration over the peers is super fast and thread-safe. type PeerSet struct { mtx sync.Mutex - lookup map[ID]*peerSetItem + lookup map[types.ID]*peerSetItem list []Peer } @@ -30,7 +32,7 @@ type peerSetItem struct { // NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. func NewPeerSet() *PeerSet { return &PeerSet{ - lookup: make(map[ID]*peerSetItem), + lookup: make(map[types.ID]*peerSetItem), list: make([]Peer, 0, 256), } } @@ -41,7 +43,7 @@ func (ps *PeerSet) Add(peer Peer) error { ps.mtx.Lock() defer ps.mtx.Unlock() if ps.lookup[peer.ID()] != nil { - return ErrSwitchDuplicatePeer + return types.ErrSwitchDuplicatePeer } index := len(ps.list) @@ -54,7 +56,7 @@ func (ps *PeerSet) Add(peer Peer) error { // Has returns true iff the PeerSet contains // the peer referred to by this peerKey. -func (ps *PeerSet) Has(peerKey ID) bool { +func (ps *PeerSet) Has(peerKey types.ID) bool { ps.mtx.Lock() _, ok := ps.lookup[peerKey] ps.mtx.Unlock() @@ -62,7 +64,7 @@ func (ps *PeerSet) Has(peerKey ID) bool { } // Get looks up a peer by the provided peerKey. -func (ps *PeerSet) Get(peerKey ID) Peer { +func (ps *PeerSet) Get(peerKey types.ID) Peer { ps.mtx.Lock() defer ps.mtx.Unlock() item, ok := ps.lookup[peerKey] diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index e906eb8e..7d7ed106 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -8,13 +8,14 @@ import ( "github.com/stretchr/testify/assert" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/p2p/types" cmn "github.com/tendermint/tmlibs/common" ) // Returns an empty dummy peer func randPeer() *peer { return &peer{ - nodeInfo: NodeInfo{ + nodeInfo: types.NodeInfo{ ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, @@ -119,7 +120,7 @@ func TestPeerSetAddDuplicate(t *testing.T) { // Our next procedure is to ensure that only one addition // succeeded and that the rest are each ErrSwitchDuplicatePeer. - wantErrCount, gotErrCount := n-1, errsTally[ErrSwitchDuplicatePeer] + wantErrCount, gotErrCount := n-1, errsTally[types.ErrSwitchDuplicatePeer] assert.Equal(t, wantErrCount, gotErrCount, "invalid ErrSwitchDuplicatePeer count") wantNilErrCount, gotNilErrCount := 1, errsTally[nil] diff --git a/p2p/peer_test.go b/p2p/peer_test.go index d99fff5e..dc13cf9d 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) func TestPeerBasic(t *testing.T) { @@ -80,8 +82,8 @@ func TestPeerSend(t *testing.T) { assert.True(p.Send(0x01, "Asylum")) } -func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*peer, error) { - chDescs := []*ChannelDescriptor{ +func createOutboundPeerAndPerformHandshake(addr *types.NetAddress, config *PeerConfig) (*peer, error) { + chDescs := []*tmconn.ChannelDescriptor{ {ID: 0x01, Priority: 1}, } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} @@ -90,7 +92,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) if err != nil { return nil, err } - err = p.HandshakeTimeout(NodeInfo{ + err = p.HandshakeTimeout(types.NodeInfo{ PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", @@ -105,11 +107,11 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) type remotePeer struct { PrivKey crypto.PrivKey Config *PeerConfig - addr *NetAddress + addr *types.NetAddress quit chan struct{} } -func (p *remotePeer) Addr() *NetAddress { +func (p *remotePeer) Addr() *types.NetAddress { return p.addr } @@ -122,7 +124,7 @@ func (p *remotePeer) Start() { if e != nil { golog.Fatalf("net.Listen tcp :0: %+v", e) } - p.addr = NewNetAddress("", l.Addr()) + p.addr = types.NewNetAddress("", l.Addr()) p.quit = make(chan struct{}) go p.accept(l) } @@ -137,11 +139,11 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to accept conn: %+v", err) } - peer, err := newInboundPeer(conn, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p Peer, r interface{}) {}, p.PrivKey, p.Config) + peer, err := newInboundPeer(conn, make(map[byte]Reactor), make([]*tmconn.ChannelDescriptor, 0), func(p Peer, r interface{}) {}, p.PrivKey, p.Config) if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } - err = peer.HandshakeTimeout(NodeInfo{ + err = peer.HandshakeTimeout(types.NodeInfo{ PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", diff --git a/p2p/addrbook/addrbook.go b/p2p/pex/addrbook.go similarity index 76% rename from p2p/addrbook/addrbook.go rename to p2p/pex/addrbook.go index 1f317a2e..93f35211 100644 --- a/p2p/addrbook/addrbook.go +++ b/p2p/pex/addrbook.go @@ -2,7 +2,7 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package addrbook +package pex import ( "crypto/sha256" @@ -16,6 +16,8 @@ import ( crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" + + "github.com/tendermint/tendermint/p2p/types" ) const ( @@ -29,25 +31,33 @@ const ( type AddrBook interface { cmn.Service + // Add our own addresses so we don't later add ourselves + AddOurAddress(*types.NetAddress) + // Add and remove an address - AddAddress(addr *NetAddress, src *NetAddress) - RemoveAddress(addr *NetAddress) + AddAddress(addr *types.NetAddress, src *types.NetAddress) error + RemoveAddress(addr *types.NetAddress) // Do we need more peers? NeedMoreAddrs() bool // Pick an address to dial - PickAddress(newBias int) *NetAddress + PickAddress(newBias int) *types.NetAddress // Mark address - MarkGood(*NetAddress) - MarkAttempt(*Address) - MarkBad(*NetAddress) + MarkGood(*types.NetAddress) + MarkAttempt(*types.NetAddress) + MarkBad(*types.NetAddress) // Send a selection of addresses to peers - GetSelection() []*NetAddress + GetSelection() []*types.NetAddress + + // TODO: remove + ListOfKnownAddresses() []*knownAddress } +var _ AddrBook = (*addrBook)(nil) + // addrBook - concurrency safe peer address manager. // Implements AddrBook. type addrBook struct { @@ -56,13 +66,13 @@ type addrBook struct { // immutable after creation filePath string routabilityStrict bool - key string + key string // random prefix for bucket placement // accessed concurrently mtx sync.Mutex rand *rand.Rand - ourAddrs map[string]*NetAddress - addrLookup map[ID]*knownAddress // new & old + ourAddrs map[string]*types.NetAddress + addrLookup map[types.ID]*knownAddress // new & old bucketsOld []map[string]*knownAddress bucketsNew []map[string]*knownAddress nOld int @@ -74,10 +84,10 @@ type addrBook struct { // NewAddrBook creates a new address book. // Use Start to begin processing asynchronous address updates. func NewAddrBook(filePath string, routabilityStrict bool) *addrBook { - am := &AddrBook{ - rand: rand.New(rand.NewSource(time.Now().UnixNano())), - ourAddrs: make(map[string]*NetAddress), - addrLookup: make(map[ID]*knownAddress), + am := &addrBook{ + rand: rand.New(rand.NewSource(time.Now().UnixNano())), // TODO: seed from outside + ourAddrs: make(map[string]*types.NetAddress), + addrLookup: make(map[types.ID]*knownAddress), filePath: filePath, routabilityStrict: routabilityStrict, } @@ -126,26 +136,26 @@ func (a *addrBook) Wait() { a.wg.Wait() } -// AddOurAddress adds another one of our addresses. -func (a *AddrBook) AddOurAddress(addr *NetAddress) { +//------------------------------------------------------- + +// AddOurAddress one of our addresses. +func (a *addrBook) AddOurAddress(addr *types.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() a.Logger.Info("Add our address to book", "addr", addr) a.ourAddrs[addr.String()] = addr } -//------------------------------------------------------- - // AddAddress implements AddrBook - adds the given address as received from the given source. // NOTE: addr must not be nil -func (a *addrBook) AddAddress(addr *NetAddress, src *NetAddress) error { +func (a *addrBook) AddAddress(addr *types.NetAddress, src *types.NetAddress) error { a.mtx.Lock() defer a.mtx.Unlock() return a.addAddress(addr, src) } // RemoveAddress implements AddrBook - removes the address from the book. -func (a *addrBook) RemoveAddress(addr *NetAddress) { +func (a *addrBook) RemoveAddress(addr *types.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -167,7 +177,7 @@ func (a *addrBook) NeedMoreAddrs() bool { // and determines how biased we are to pick an address from a new bucket. // PickAddress returns nil if the AddrBook is empty or if we try to pick // from an empty bucket. -func (a *addrBook) PickAddress(newBias int) *NetAddress { +func (a *addrBook) PickAddress(newBias int) *types.NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -213,7 +223,7 @@ func (a *addrBook) PickAddress(newBias int) *NetAddress { // MarkGood implements AddrBook - it marks the peer as good and // moves it into an "old" bucket. -func (a *addrBook) MarkGood(addr *NetAddress) { +func (a *addrBook) MarkGood(addr *types.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -227,7 +237,7 @@ func (a *addrBook) MarkGood(addr *NetAddress) { } // MarkAttempt implements AddrBook - it marks that an attempt was made to connect to the address. -func (a *addrBook) MarkAttempt(addr *NetAddress) { +func (a *addrBook) MarkAttempt(addr *types.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -239,13 +249,13 @@ func (a *addrBook) MarkAttempt(addr *NetAddress) { // MarkBad implements AddrBook. Currently it just ejects the address. // TODO: black list for some amount of time -func (a *addrBook) MarkBad(addr *NetAddress) { +func (a *addrBook) MarkBad(addr *types.NetAddress) { a.RemoveAddress(addr) } // GetSelection implements AddrBook. // It randomly selects some addresses (old & new). Suitable for peer-exchange protocols. -func (a *addrBook) GetSelection() []*NetAddress { +func (a *addrBook) GetSelection() []*types.NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -253,7 +263,7 @@ func (a *addrBook) GetSelection() []*NetAddress { return nil } - allAddr := make([]*NetAddress, a.size()) + allAddr := make([]*types.NetAddress, a.size()) i := 0 for _, ka := range a.addrLookup { allAddr[i] = ka.Addr @@ -279,7 +289,7 @@ func (a *addrBook) GetSelection() []*NetAddress { } // ListOfKnownAddresses returns the new and old addresses. -func (a *AddrBook) ListOfKnownAddresses() []*knownAddress { +func (a *addrBook) ListOfKnownAddresses() []*knownAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -290,17 +300,6 @@ func (a *AddrBook) ListOfKnownAddresses() []*knownAddress { return addrs } -/* Loading & Saving */ - -type addrBookJSON struct { - Key string - Addrs []*knownAddress -} - -func (a *AddrBook) saveToFile(filePath string) { - a.Logger.Info("Saving AddrBook to file", "size", a.Size()) -} - //------------------------------------------------ // Size returns the number of addresses in the book. @@ -334,6 +333,8 @@ out: a.Logger.Info("Address handler done") } +//---------------------------------------------------------- + func (a *addrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress { switch bucketType { case bucketTypeNew: @@ -365,17 +366,18 @@ func (a *addrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { // Enforce max addresses. if len(bucket) > newBucketSize { - a.Logger.Info("new bucket is full, expiring old ") + a.Logger.Info("new bucket is full, expiring new") a.expireNew(bucketIdx) } // Add to bucket. bucket[addrStr] = ka + // increment nNew if the peer doesnt already exist in a bucket if ka.addBucketRef(bucketIdx) == 1 { a.nNew++ } - // Ensure in addrLookup + // Add it to addrLookup a.addrLookup[ka.ID()] = ka return true @@ -449,6 +451,8 @@ func (a *addrBook) removeFromAllBuckets(ka *knownAddress) { delete(a.addrLookup, ka.ID()) } +//---------------------------------------------------------- + func (a *addrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { bucket := a.getBucket(bucketType, bucketIdx) var oldest *knownAddress @@ -460,7 +464,9 @@ func (a *addrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { return oldest } -func (a *addrBook) addAddress(addr, src *NetAddress) error { +// adds the address to a "new" bucket. if its already in one, +// it only adds it probabilistically +func (a *addrBook) addAddress(addr, src *types.NetAddress) error { if a.routabilityStrict && !addr.Routable() { return fmt.Errorf("Cannot add non-routable address %v", addr) } @@ -490,7 +496,10 @@ func (a *addrBook) addAddress(addr, src *NetAddress) error { } bucket := a.calcNewBucket(addr, src) - a.addToNewBucket(ka, bucket) + added := a.addToNewBucket(ka, bucket) + if !added { + a.Logger.Info("Can't add new address, addr book is full", "address", addr, "total", a.size()) + } a.Logger.Info("Added new address", "address", addr, "total", a.size()) return nil @@ -513,9 +522,9 @@ func (a *addrBook) expireNew(bucketIdx int) { a.removeFromBucket(oldest, bucketTypeNew, bucketIdx) } -// Promotes an address from new to old. -// TODO: Move to old probabilistically. -// The better a node is, the less likely it should be evicted from an old bucket. +// Promotes an address from new to old. If the destination bucket is full, +// demote the oldest one to a "new" bucket. +// TODO: Demote more probabilistically? func (a *addrBook) moveToOld(ka *knownAddress) { // Sanity check if ka.isOld() { @@ -559,9 +568,12 @@ func (a *addrBook) moveToOld(ka *knownAddress) { } } +//--------------------------------------------------------------------- +// calculate bucket placements + // doublesha256( key + sourcegroup + // int64(doublesha256(key + group + sourcegroup))%bucket_per_group ) % num_new_buckets -func (a *addrBook) calcNewBucket(addr, src *NetAddress) int { +func (a *addrBook) calcNewBucket(addr, src *types.NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(a.groupKey(addr))...) @@ -582,7 +594,7 @@ func (a *addrBook) calcNewBucket(addr, src *NetAddress) int { // doublesha256( key + group + // int64(doublesha256(key + addr))%buckets_per_group ) % num_old_buckets -func (a *addrBook) calcOldBucket(addr *NetAddress) int { +func (a *addrBook) calcOldBucket(addr *types.NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(addr.String())...) @@ -604,7 +616,7 @@ func (a *addrBook) calcOldBucket(addr *NetAddress) int { // This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string // "local" for a local address and the string "unroutable" for an unroutable // address. -func (a *addrBook) groupKey(na *NetAddress) string { +func (a *addrBook) groupKey(na *types.NetAddress) string { if a.routabilityStrict && na.Local() { return "local" } @@ -649,137 +661,6 @@ func (a *addrBook) groupKey(na *NetAddress) string { return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String() } -//----------------------------------------------------------------------------- - -/* - knownAddress - - tracks information about a known network address that is used - to determine how viable an address is. -*/ -type knownAddress struct { - Addr *NetAddress - Src *NetAddress - Attempts int32 - LastAttempt time.Time - LastSuccess time.Time - BucketType byte - Buckets []int -} - -func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { - return &knownAddress{ - Addr: addr, - Src: src, - Attempts: 0, - LastAttempt: time.Now(), - BucketType: bucketTypeNew, - Buckets: nil, - } -} - -func (ka *knownAddress) ID() ID { - return ka.Addr.ID -} - -func (ka *knownAddress) isOld() bool { - return ka.BucketType == bucketTypeOld -} - -func (ka *knownAddress) isNew() bool { - return ka.BucketType == bucketTypeNew -} - -func (ka *knownAddress) markAttempt() { - now := time.Now() - ka.LastAttempt = now - ka.Attempts += 1 -} - -func (ka *knownAddress) markGood() { - now := time.Now() - ka.LastAttempt = now - ka.Attempts = 0 - ka.LastSuccess = now -} - -func (ka *knownAddress) addBucketRef(bucketIdx int) int { - for _, bucket := range ka.Buckets { - if bucket == bucketIdx { - // TODO refactor to return error? - // log.Warn(Fmt("Bucket already exists in ka.Buckets: %v", ka)) - return -1 - } - } - ka.Buckets = append(ka.Buckets, bucketIdx) - return len(ka.Buckets) -} - -func (ka *knownAddress) removeBucketRef(bucketIdx int) int { - buckets := []int{} - for _, bucket := range ka.Buckets { - if bucket != bucketIdx { - buckets = append(buckets, bucket) - } - } - if len(buckets) != len(ka.Buckets)-1 { - // TODO refactor to return error? - // log.Warn(Fmt("bucketIdx not found in ka.Buckets: %v", ka)) - return -1 - } - ka.Buckets = buckets - return len(ka.Buckets) -} - -/* - An address is bad if the address in question is a New address, has not been tried in the last - minute, and meets one of the following criteria: - - 1) It claims to be from the future - 2) It hasn't been seen in over a month - 3) It has failed at least three times and never succeeded - 4) It has failed ten times in the last week - - All addresses that meet these criteria are assumed to be worthless and not - worth keeping hold of. - - XXX: so a good peer needs us to call MarkGood before the conditions above are reached! -*/ -func (ka *knownAddress) isBad() bool { - // Is Old --> good - if ka.BucketType == bucketTypeOld { - return false - } - - // Has been attempted in the last minute --> good - if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) { - return false - } - - // Too old? - // XXX: does this mean if we've kept a connection up for this long we'll disconnect?! - // and shouldn't it be .Before ? - if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { - return true - } - - // Never succeeded? - if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries { - return true - } - - // Hasn't succeeded in too long? - // XXX: does this mean if we've kept a connection up for this long we'll disconnect?! - if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) && - ka.Attempts >= maxFailures { - return true - } - - return false -} - -//----------------------------------------------------------------------------- - // doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. func doubleSha256(b []byte) []byte { hasher := sha256.New() diff --git a/p2p/addrbook/addrbook_test.go b/p2p/pex/addrbook_test.go similarity index 93% rename from p2p/addrbook/addrbook_test.go rename to p2p/pex/addrbook_test.go index ff8d239d..206e3401 100644 --- a/p2p/addrbook/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -1,4 +1,4 @@ -package addrbook +package pex import ( "encoding/hex" @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/p2p/types" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) @@ -168,8 +169,8 @@ func TestAddrBookHandlesDuplicates(t *testing.T) { } type netAddressPair struct { - addr *NetAddress - src *NetAddress + addr *types.NetAddress + src *types.NetAddress } func randNetAddressPairs(t *testing.T, n int) []netAddressPair { @@ -180,7 +181,7 @@ func randNetAddressPairs(t *testing.T, n int) []netAddressPair { return randAddrs } -func randIPv4Address(t *testing.T) *NetAddress { +func randIPv4Address(t *testing.T) *types.NetAddress { for { ip := fmt.Sprintf("%v.%v.%v.%v", rand.Intn(254)+1, @@ -189,9 +190,9 @@ func randIPv4Address(t *testing.T) *NetAddress { rand.Intn(255), ) port := rand.Intn(65535-1) + 1 - id := ID(hex.EncodeToString(cmn.RandBytes(IDByteLength))) - idAddr := IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) - addr, err := NewNetAddressString(idAddr) + id := types.ID(hex.EncodeToString(cmn.RandBytes(types.IDByteLength))) + idAddr := types.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) + addr, err := types.NewNetAddressString(idAddr) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/addrbook/file.go b/p2p/pex/file.go similarity index 99% rename from p2p/addrbook/file.go rename to p2p/pex/file.go index 956ac56c..521fcfcf 100644 --- a/p2p/addrbook/file.go +++ b/p2p/pex/file.go @@ -1,4 +1,4 @@ -package addrbook +package pex import ( "encoding/json" diff --git a/p2p/addrbook/known_address.go b/p2p/pex/known_address.go similarity index 92% rename from p2p/addrbook/known_address.go rename to p2p/pex/known_address.go index 2a879081..db6d021f 100644 --- a/p2p/addrbook/known_address.go +++ b/p2p/pex/known_address.go @@ -1,12 +1,16 @@ -package addrbook +package pex -import "time" +import ( + "time" + + "github.com/tendermint/tendermint/p2p/types" +) // knownAddress tracks information about a known network address // that is used to determine how viable an address is. type knownAddress struct { - Addr *NetAddress - Src *NetAddress + Addr *types.NetAddress + Src *types.NetAddress Attempts int32 LastAttempt time.Time LastSuccess time.Time @@ -14,7 +18,7 @@ type knownAddress struct { Buckets []int } -func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { +func newKnownAddress(addr *types.NetAddress, src *types.NetAddress) *knownAddress { return &knownAddress{ Addr: addr, Src: src, @@ -25,7 +29,7 @@ func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { } } -func (ka *knownAddress) ID() ID { +func (ka *knownAddress) ID() types.ID { return ka.Addr.ID } diff --git a/p2p/addrbook/params.go b/p2p/pex/params.go similarity index 98% rename from p2p/addrbook/params.go rename to p2p/pex/params.go index f410ed9a..f94e1021 100644 --- a/p2p/addrbook/params.go +++ b/p2p/pex/params.go @@ -1,4 +1,4 @@ -package addrbook +package pex import "time" diff --git a/p2p/pex_reactor.go b/p2p/pex/pex_reactor.go similarity index 92% rename from p2p/pex_reactor.go rename to p2p/pex/pex_reactor.go index 57665e07..24c9417f 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -1,4 +1,4 @@ -package p2p +package pex import ( "bytes" @@ -12,9 +12,13 @@ import ( wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tendermint/p2p/addrbook" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) +type Peer = p2p.Peer + const ( // PexChannel is a channel for PEX messages PexChannel = byte(0x00) @@ -47,9 +51,9 @@ const ( // Only accept pexAddrsMsg from peers we sent a corresponding pexRequestMsg too. // Only accept one pexRequestMsg every ~defaultEnsurePeersPeriod. type PEXReactor struct { - BaseReactor + p2p.BaseReactor - book *addrbook.AddrBook + book AddrBook config *PEXReactorConfig ensurePeersPeriod time.Duration @@ -69,7 +73,7 @@ type PEXReactorConfig struct { } // NewPEXReactor creates new PEX reactor. -func NewPEXReactor(b *addrbook.AddrBook, config *PEXReactorConfig) *PEXReactor { +func NewPEXReactor(b AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ book: b, config: config, @@ -77,7 +81,7 @@ func NewPEXReactor(b *addrbook.AddrBook, config *PEXReactorConfig) *PEXReactor { requestsSent: cmn.NewCMap(), lastReceivedRequests: cmn.NewCMap(), } - r.BaseReactor = *NewBaseReactor("PEXReactor", r) + r.BaseReactor = *p2p.NewBaseReactor("PEXReactor", r) return r } @@ -113,8 +117,8 @@ func (r *PEXReactor) OnStop() { } // GetChannels implements Reactor -func (r *PEXReactor) GetChannels() []*ChannelDescriptor { - return []*ChannelDescriptor{ +func (r *PEXReactor) GetChannels() []*tmconn.ChannelDescriptor { + return []*tmconn.ChannelDescriptor{ { ID: PexChannel, Priority: 1, @@ -227,7 +231,7 @@ func (r *PEXReactor) RequestAddrs(p Peer) { // ReceiveAddrs adds the given addrs to the addrbook if theres an open // 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 []*NetAddress, src Peer) error { +func (r *PEXReactor) ReceiveAddrs(addrs []*types.NetAddress, src Peer) error { id := string(src.ID()) if !r.requestsSent.Has(id) { @@ -246,7 +250,7 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*NetAddress, src Peer) error { } // SendAddrs sends addrs to the peer. -func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*NetAddress) { +func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*types.NetAddress) { p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) } @@ -296,7 +300,7 @@ func (r *PEXReactor) ensurePeers() { // NOTE: range here is [10, 90]. Too high ? newBias := cmn.MinInt(numOutPeers, 8)*10 + 10 - toDial := make(map[ID]*NetAddress) + toDial := make(map[types.ID]*types.NetAddress) // Try maxAttempts times to pick numToDial addresses to dial maxAttempts := numToDial * 3 for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ { @@ -319,10 +323,15 @@ func (r *PEXReactor) ensurePeers() { // Dial picked addresses for _, item := range toDial { - go func(picked *NetAddress) { + go func(picked *types.NetAddress) { _, err := r.Switch.DialPeerWithAddress(picked, false) if err != nil { - r.book.MarkAttempt(picked) + // TODO: detect more "bad peer" scenarios + if _, ok := err.(types.ErrSwitchAuthenticationFailure); ok { + r.book.MarkBad(picked) + } else { + r.book.MarkAttempt(picked) + } } }(item) } @@ -351,7 +360,7 @@ func (r *PEXReactor) checkSeeds() error { if lSeeds == 0 { return nil } - _, errs := NewNetAddressStrings(r.config.Seeds) + _, errs := types.NewNetAddressStrings(r.config.Seeds) for _, err := range errs { if err != nil { return err @@ -366,9 +375,10 @@ func (r *PEXReactor) dialSeeds() { if lSeeds == 0 { return } - seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) + seedAddrs, _ := types.NewNetAddressStrings(r.config.Seeds) - perm := r.Switch.rng.Perm(lSeeds) + perm := rand.Perm(lSeeds) + // perm := r.Switch.rng.Perm(lSeeds) for _, i := range perm { // dial a random seed seedAddr := seedAddrs[i] @@ -410,7 +420,7 @@ func (r *PEXReactor) crawlPeersRoutine() { // network crawling performed during seed/crawler mode. type crawlPeerInfo struct { // The listening address of a potential peer we learned about - Addr *NetAddress + Addr *types.NetAddress // The last time we attempt to reach this address LastAttempt time.Time @@ -534,7 +544,7 @@ func (m *pexRequestMessage) String() string { A message with announced peer addresses. */ type pexAddrsMessage struct { - Addrs []*NetAddress + Addrs []*types.NetAddress } func (m *pexAddrsMessage) String() string { diff --git a/p2p/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go similarity index 76% rename from p2p/pex_reactor_test.go rename to p2p/pex/pex_reactor_test.go index 44fd8b51..439914ac 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -1,21 +1,35 @@ -package p2p +package pex import ( "fmt" "io/ioutil" - "math/rand" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) +var ( + config *cfg.P2PConfig +) + +func init() { + config = cfg.DefaultP2PConfig() + config.PexReactor = true +} + func TestPEXReactorBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -45,7 +59,7 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { r.SetLogger(log.TestingLogger()) size := book.Size() - peer := createRandomPeer(false) + peer := p2p.CreateRandomPeer(false) r.AddPeer(peer) assert.Equal(size+1, book.Size()) @@ -53,7 +67,7 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { r.RemovePeer(peer, "peer not available") assert.Equal(size+1, book.Size()) - outboundPeer := createRandomPeer(true) + outboundPeer := p2p.CreateRandomPeer(true) r.AddPeer(outboundPeer) assert.Equal(size+1, book.Size(), "outbound peers should not be added to the address book") @@ -64,7 +78,7 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { func TestPEXReactorRunning(t *testing.T) { N := 3 - switches := make([]*Switch, N) + switches := make([]*p2p.Switch, N) dir, err := ioutil.TempDir("", "pex_reactor") require.Nil(t, err) @@ -74,7 +88,7 @@ func TestPEXReactorRunning(t *testing.T) { // create switches for i := 0; i < N; i++ { - switches[i] = makeSwitch(config, i, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + switches[i] = p2p.MakeSwitch(config, i, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { sw.SetLogger(log.TestingLogger().With("switch", i)) r := NewPEXReactor(book, &PEXReactorConfig{}) @@ -87,9 +101,9 @@ func TestPEXReactorRunning(t *testing.T) { // fill the address book and add listeners for _, s := range switches { - addr, _ := NewNetAddressString(s.NodeInfo().ListenAddr) + addr, _ := types.NewNetAddressString(s.NodeInfo().ListenAddr) book.AddAddress(addr, addr) - s.AddListener(NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true, log.TestingLogger())) + s.AddListener(p2p.NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true, log.TestingLogger())) } // start switches @@ -106,7 +120,7 @@ func TestPEXReactorRunning(t *testing.T) { } } -func assertSomePeersWithTimeout(t *testing.T, switches []*Switch, checkPeriod, timeout time.Duration) { +func assertSomePeersWithTimeout(t *testing.T, switches []*p2p.Switch, checkPeriod, timeout time.Duration) { ticker := time.NewTicker(checkPeriod) remaining := timeout for { @@ -151,13 +165,13 @@ func TestPEXReactorReceive(t *testing.T) { r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) - peer := createRandomPeer(false) + peer := p2p.CreateRandomPeer(false) // we have to send a request to receive responses r.RequestAddrs(peer) size := book.Size() - addrs := []*NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*types.NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) assert.Equal(size+1, book.Size()) @@ -176,14 +190,14 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { book.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) - sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw }) sw.SetLogger(log.TestingLogger()) sw.AddReactor("PEX", r) r.SetSwitch(sw) r.SetLogger(log.TestingLogger()) peer := newMockPeer() - sw.peers.Add(peer) + p2p.AddPeerToSwitch(sw, peer) assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) @@ -215,14 +229,14 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { book.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) - sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw }) sw.SetLogger(log.TestingLogger()) sw.AddReactor("PEX", r) r.SetSwitch(sw) r.SetLogger(log.TestingLogger()) peer := newMockPeer() - sw.peers.Add(peer) + p2p.AddPeerToSwitch(sw, peer) assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) @@ -232,7 +246,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { assert.True(r.requestsSent.Has(id)) assert.True(sw.Peers().Has(peer.ID())) - addrs := []*NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*types.NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) // receive some addrs. should clear the request @@ -254,7 +268,7 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { book.SetLogger(log.TestingLogger()) // 1. create seed - seed := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + seed := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { sw.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) @@ -263,13 +277,13 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { sw.AddReactor("pex", r) return sw }) - seed.AddListener(NewDefaultListener("tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger())) + seed.AddListener(p2p.NewDefaultListener("tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger())) err = seed.Start() require.Nil(t, err) defer seed.Stop() // 2. create usual peer - sw := makeSwitch(config, 1, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + sw := p2p.MakeSwitch(config, 1, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { sw.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{Seeds: []string{seed.NodeInfo().ListenAddr}}) @@ -283,7 +297,7 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { defer sw.Stop() // 3. check that peer at least connects to seed - assertSomePeersWithTimeout(t, []*Switch{sw}, 10*time.Millisecond, 10*time.Second) + assertSomePeersWithTimeout(t, []*p2p.Switch{sw}, 10*time.Millisecond, 10*time.Second) } func TestPEXReactorCrawlStatus(t *testing.T) { @@ -297,7 +311,7 @@ func TestPEXReactorCrawlStatus(t *testing.T) { pexR := NewPEXReactor(book, &PEXReactorConfig{SeedMode: true}) // Seed/Crawler mode uses data from the Switch - makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { pexR.SetLogger(log.TestingLogger()) sw.SetLogger(log.TestingLogger().With("switch", i)) sw.AddReactor("pex", pexR) @@ -305,13 +319,13 @@ func TestPEXReactorCrawlStatus(t *testing.T) { }) // Create a peer, add it to the peer set and the addrbook. - peer := createRandomPeer(false) - pexR.Switch.peers.Add(peer) + peer := p2p.CreateRandomPeer(false) + p2p.AddPeerToSwitch(pexR.Switch, peer) addr1 := peer.NodeInfo().NetAddress() pexR.book.AddAddress(addr1, addr1) // Add a non-connected address to the book. - _, addr2 := createRoutableAddr() + _, addr2 := p2p.CreateRoutableAddr() pexR.book.AddAddress(addr2, addr1) // Get some peerInfos to crawl @@ -323,44 +337,15 @@ func TestPEXReactorCrawlStatus(t *testing.T) { // TODO: test } -func createRoutableAddr() (addr string, netAddr *NetAddress) { - for { - var err error - addr = cmn.Fmt("%X@%v.%v.%v.%v:46656", cmn.RandBytes(20), rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) - netAddr, err = NewNetAddressString(addr) - if err != nil { - panic(err) - } - if netAddr.Routable() { - break - } - } - return -} - -func createRandomPeer(outbound bool) *peer { - addr, netAddr := createRoutableAddr() - p := &peer{ - nodeInfo: NodeInfo{ - ListenAddr: netAddr.DialString(), - PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), - }, - outbound: outbound, - mconn: &MConnection{}, - } - p.SetLogger(log.TestingLogger().With("peer", addr)) - return p -} - type mockPeer struct { *cmn.BaseService pubKey crypto.PubKey - addr *NetAddress + addr *types.NetAddress outbound, persistent bool } func newMockPeer() mockPeer { - _, netAddr := createRoutableAddr() + _, netAddr := p2p.CreateRoutableAddr() mp := mockPeer{ addr: netAddr, pubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), @@ -370,17 +355,17 @@ func newMockPeer() mockPeer { return mp } -func (mp mockPeer) ID() ID { return PubKeyToID(mp.pubKey) } +func (mp mockPeer) ID() types.ID { return types.PubKeyToID(mp.pubKey) } func (mp mockPeer) IsOutbound() bool { return mp.outbound } func (mp mockPeer) IsPersistent() bool { return mp.persistent } -func (mp mockPeer) NodeInfo() NodeInfo { - return NodeInfo{ +func (mp mockPeer) NodeInfo() types.NodeInfo { + return types.NodeInfo{ PubKey: mp.pubKey, ListenAddr: mp.addr.DialString(), } } -func (mp mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } -func (mp mockPeer) Send(byte, interface{}) bool { return false } -func (mp mockPeer) TrySend(byte, interface{}) bool { return false } -func (mp mockPeer) Set(string, interface{}) {} -func (mp mockPeer) Get(string) interface{} { return nil } +func (mp mockPeer) Status() tmconn.ConnectionStatus { return tmconn.ConnectionStatus{} } +func (mp mockPeer) Send(byte, interface{}) bool { return false } +func (mp mockPeer) TrySend(byte, interface{}) bool { return false } +func (mp mockPeer) Set(string, interface{}) {} +func (mp mockPeer) Get(string) interface{} { return nil } diff --git a/p2p/switch.go b/p2p/switch.go index db2e7d98..ec54478c 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -11,6 +11,8 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" cmn "github.com/tendermint/tmlibs/common" ) @@ -30,10 +32,11 @@ const ( reconnectBackOffBaseSeconds = 3 ) -var ( - ErrSwitchDuplicatePeer = errors.New("Duplicate peer") - ErrSwitchConnectToSelf = errors.New("Connect to self") -) +//----------------------------------------------------------------------------- + +type AddrBook interface { + AddAddress(addr *types.NetAddress, src *types.NetAddress) +} //----------------------------------------------------------------------------- @@ -48,12 +51,12 @@ type Switch struct { peerConfig *PeerConfig listeners []Listener reactors map[string]Reactor - chDescs []*ChannelDescriptor + chDescs []*tmconn.ChannelDescriptor reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo NodeInfo // our node info - nodeKey *NodeKey // our node privkey + nodeInfo types.NodeInfo // our node info + nodeKey *types.NodeKey // our node privkey filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -66,7 +69,7 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { config: config, peerConfig: DefaultPeerConfig(), reactors: make(map[string]Reactor), - chDescs: make([]*ChannelDescriptor, 0), + chDescs: make([]*tmconn.ChannelDescriptor, 0), reactorsByCh: make(map[byte]Reactor), peers: NewPeerSet(), dialing: cmn.NewCMap(), @@ -77,10 +80,10 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { sw.rng = rand.New(rand.NewSource(cmn.RandInt64())) // TODO: collapse the peerConfig into the config ? - sw.peerConfig.MConfig.flushThrottle = time.Duration(config.FlushThrottleTimeout) * time.Millisecond + sw.peerConfig.MConfig.FlushThrottle = time.Duration(config.FlushThrottleTimeout) * time.Millisecond sw.peerConfig.MConfig.SendRate = config.SendRate sw.peerConfig.MConfig.RecvRate = config.RecvRate - sw.peerConfig.MConfig.maxMsgPacketPayloadSize = config.MaxMsgPacketPayloadSize + sw.peerConfig.MConfig.MaxMsgPacketPayloadSize = config.MaxMsgPacketPayloadSize sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw) return sw @@ -140,19 +143,19 @@ func (sw *Switch) IsListening() bool { // SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { +func (sw *Switch) SetNodeInfo(nodeInfo types.NodeInfo) { sw.nodeInfo = nodeInfo } // NodeInfo returns the switch's NodeInfo. // NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() NodeInfo { +func (sw *Switch) NodeInfo() types.NodeInfo { return sw.nodeInfo } // SetNodeKey sets the switch's private key for authenticated encryption. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { +func (sw *Switch) SetNodeKey(nodeKey *types.NodeKey) { sw.nodeKey = nodeKey } @@ -311,13 +314,13 @@ func (sw *Switch) reconnectToPeer(peer Peer) { // Dialing // IsDialing returns true if the switch is currently dialing the given ID. -func (sw *Switch) IsDialing(id ID) bool { +func (sw *Switch) IsDialing(id types.ID) bool { return sw.dialing.Has(string(id)) } // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). -func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { - netAddrs, errs := NewNetAddressStrings(peers) +func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent bool) error { + netAddrs, errs := types.NewNetAddressStrings(peers) for _, err := range errs { sw.Logger.Error("Error in peer's address", "err", err) } @@ -330,6 +333,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent if netAddr.Same(ourAddr) { continue } + // TODO: move this out of here ? addrBook.AddAddress(netAddr, ourAddr) } } @@ -353,7 +357,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. -func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { +func (sw *Switch) DialPeerWithAddress(addr *types.NetAddress, persistent bool) (Peer, error) { sw.dialing.Set(string(addr.ID), addr) defer sw.dialing.Delete(string(addr.ID)) return sw.addOutboundPeerWithConfig(addr, sw.peerConfig, persistent) @@ -439,7 +443,7 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er // dial the peer; make secret connection; authenticate against the dialed ID; // add the peer. -func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { +func (sw *Switch) addOutboundPeerWithConfig(addr *types.NetAddress, config *PeerConfig, persistent bool) (Peer, error) { sw.Logger.Info("Dialing peer", "address", addr) peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config, persistent) if err != nil { @@ -453,7 +457,7 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) } else if addr.ID != peer.ID() { peer.CloseConn() - return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + return nil, types.ErrSwitchAuthenticationFailure{addr, peer.ID()} } err = sw.addPeer(peer) @@ -474,12 +478,12 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig func (sw *Switch) addPeer(peer *peer) error { // Avoid self if sw.nodeKey.ID() == peer.ID() { - return ErrSwitchConnectToSelf + return types.ErrSwitchConnectToSelf } // Avoid duplicate if sw.peers.Has(peer.ID()) { - return ErrSwitchDuplicatePeer + return types.ErrSwitchDuplicatePeer } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index a729698e..ae7e89e7 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -16,6 +16,8 @@ import ( "github.com/tendermint/tmlibs/log" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) var ( @@ -28,7 +30,7 @@ func init() { } type PeerMessage struct { - PeerID ID + PeerID types.ID Bytes []byte Counter int } @@ -37,7 +39,7 @@ type TestReactor struct { BaseReactor mtx sync.Mutex - channels []*ChannelDescriptor + channels []*tmconn.ChannelDescriptor peersAdded []Peer peersRemoved []Peer logMessages bool @@ -45,7 +47,7 @@ type TestReactor struct { msgsReceived map[byte][]PeerMessage } -func NewTestReactor(channels []*ChannelDescriptor, logMessages bool) *TestReactor { +func NewTestReactor(channels []*tmconn.ChannelDescriptor, logMessages bool) *TestReactor { tr := &TestReactor{ channels: channels, logMessages: logMessages, @@ -56,7 +58,7 @@ func NewTestReactor(channels []*ChannelDescriptor, logMessages bool) *TestReacto return tr } -func (tr *TestReactor) GetChannels() []*ChannelDescriptor { +func (tr *TestReactor) GetChannels() []*tmconn.ChannelDescriptor { return tr.channels } @@ -92,7 +94,7 @@ func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { // convenience method for creating two switches connected to each other. // XXX: note this uses net.Pipe and not a proper TCP conn -func makeSwitchPair(t testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { +func MakeSwitchPair(t testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { // Create two switches that will be interconnected. switches := MakeConnectedSwitches(config, 2, initSwitch, Connect2Switches) return switches[0], switches[1] @@ -100,11 +102,11 @@ func makeSwitchPair(t testing.TB, initSwitch func(int, *Switch) *Switch) (*Switc func initSwitchFunc(i int, sw *Switch) *Switch { // Make two reactors of two channels each - sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{ + sw.AddReactor("foo", NewTestReactor([]*tmconn.ChannelDescriptor{ {ID: byte(0x00), Priority: 10}, {ID: byte(0x01), Priority: 10}, }, true)) - sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{ + sw.AddReactor("bar", NewTestReactor([]*tmconn.ChannelDescriptor{ {ID: byte(0x02), Priority: 10}, {ID: byte(0x03), Priority: 10}, }, true)) @@ -112,7 +114,7 @@ func initSwitchFunc(i int, sw *Switch) *Switch { } func TestSwitches(t *testing.T) { - s1, s2 := makeSwitchPair(t, initSwitchFunc) + s1, s2 := MakeSwitchPair(t, initSwitchFunc) defer s1.Stop() defer s2.Stop() @@ -156,12 +158,12 @@ func assertMsgReceivedWithTimeout(t *testing.T, msg string, channel byte, reacto } func TestConnAddrFilter(t *testing.T) { - s1 := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) - s2 := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + s1 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + s2 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) defer s1.Stop() defer s2.Stop() - c1, c2 := netPipe() + c1, c2 := tmconn.NetPipe() s1.SetAddrFilter(func(addr net.Addr) error { if addr.String() == c1.RemoteAddr().String() { @@ -192,12 +194,12 @@ func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) } func TestConnPubKeyFilter(t *testing.T) { - s1 := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) - s2 := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + s1 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + s2 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) defer s1.Stop() defer s2.Stop() - c1, c2 := netPipe() + c1, c2 := tmconn.NetPipe() // set pubkey filter s1.SetPubKeyFilter(func(pubkey crypto.PubKey) error { @@ -224,7 +226,7 @@ func TestConnPubKeyFilter(t *testing.T) { func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { assert, require := assert.New(t), require.New(t) - sw := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + sw := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) err := sw.Start() if err != nil { t.Error(err) @@ -251,7 +253,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { func TestSwitchReconnectsToPersistentPeer(t *testing.T) { assert, require := assert.New(t), require.New(t) - sw := makeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) + sw := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc) err := sw.Start() if err != nil { t.Error(err) @@ -302,13 +304,13 @@ func TestSwitchFullConnectivity(t *testing.T) { func BenchmarkSwitches(b *testing.B) { b.StopTimer() - s1, s2 := makeSwitchPair(b, func(i int, sw *Switch) *Switch { + s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { // Make bar reactors of bar channels each - sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{ + sw.AddReactor("foo", NewTestReactor([]*tmconn.ChannelDescriptor{ {ID: byte(0x00), Priority: 10}, {ID: byte(0x01), Priority: 10}, }, false)) - sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{ + sw.AddReactor("bar", NewTestReactor([]*tmconn.ChannelDescriptor{ {ID: byte(0x02), Priority: 10}, {ID: byte(0x03), Priority: 10}, }, false)) diff --git a/p2p/test_util.go b/p2p/test_util.go index dca23a0e..aad6fb23 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -5,11 +5,47 @@ import ( "net" crypto "github.com/tendermint/go-crypto" - cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" ) +func AddPeerToSwitch(sw *Switch, peer Peer) { + sw.peers.Add(peer) +} + +func CreateRandomPeer(outbound bool) *peer { + addr, netAddr := CreateRoutableAddr() + p := &peer{ + nodeInfo: types.NodeInfo{ + ListenAddr: netAddr.DialString(), + PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), + }, + outbound: outbound, + mconn: &tmconn.MConnection{}, + } + p.SetLogger(log.TestingLogger().With("peer", addr)) + return p +} + +func CreateRoutableAddr() (addr string, netAddr *types.NetAddress) { + for { + var err error + addr = cmn.Fmt("%X@%v.%v.%v.%v:46656", cmn.RandBytes(20), rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) + netAddr, err = types.NewNetAddressString(addr) + if err != nil { + panic(err) + } + if netAddr.Routable() { + break + } + } + return +} + //------------------------------------------------------------------ // Connects switches via arbitrary net.Conn. Used for testing. @@ -20,7 +56,7 @@ import ( func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { switches := make([]*Switch, n) for i := 0; i < n; i++ { - switches[i] = makeSwitch(cfg, i, "testing", "123.123.123", initSwitch) + switches[i] = MakeSwitch(cfg, i, "testing", "123.123.123", initSwitch) } if err := StartSwitches(switches); err != nil { @@ -42,7 +78,7 @@ func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Swit func Connect2Switches(switches []*Switch, i, j int) { switchI := switches[i] switchJ := switches[j] - c1, c2 := netPipe() + c1, c2 := tmconn.NetPipe() doneCh := make(chan struct{}) go func() { err := switchI.addPeerWithConnection(c1) @@ -91,16 +127,16 @@ func StartSwitches(switches []*Switch) error { return nil } -func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { +func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { // new switch, add reactors // TODO: let the config be passed in? - nodeKey := &NodeKey{ + nodeKey := &types.NodeKey{ PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } s := NewSwitch(cfg) s.SetLogger(log.TestingLogger()) s = initSwitch(i, s) - s.SetNodeInfo(NodeInfo{ + s.SetNodeInfo(types.NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, diff --git a/p2p/conn_go110.go b/p2p/tmconn/conn_go110.go similarity index 86% rename from p2p/conn_go110.go rename to p2p/tmconn/conn_go110.go index 2fca7c3d..75e55d85 100644 --- a/p2p/conn_go110.go +++ b/p2p/tmconn/conn_go110.go @@ -1,6 +1,6 @@ // +build go1.10 -package p2p +package tmconn // Go1.10 has a proper net.Conn implementation that // has the SetDeadline method implemented as per @@ -10,6 +10,6 @@ package p2p import "net" -func netPipe() (net.Conn, net.Conn) { +func NetPipe() (net.Conn, net.Conn) { return net.Pipe() } diff --git a/p2p/conn_notgo110.go b/p2p/tmconn/conn_notgo110.go similarity index 93% rename from p2p/conn_notgo110.go rename to p2p/tmconn/conn_notgo110.go index a5c2f741..bb72d64a 100644 --- a/p2p/conn_notgo110.go +++ b/p2p/tmconn/conn_notgo110.go @@ -1,6 +1,6 @@ // +build !go1.10 -package p2p +package tmconn import ( "net" @@ -24,7 +24,7 @@ func (p *pipe) SetDeadline(t time.Time) error { return nil } -func netPipe() (net.Conn, net.Conn) { +func NetPipe() (net.Conn, net.Conn) { p1, p2 := net.Pipe() return &pipe{p1}, &pipe{p2} } diff --git a/p2p/connection.go b/p2p/tmconn/connection.go similarity index 98% rename from p2p/connection.go rename to p2p/tmconn/connection.go index dcb66096..92c48c36 100644 --- a/p2p/connection.go +++ b/p2p/tmconn/connection.go @@ -1,4 +1,4 @@ -package p2p +package tmconn import ( "bufio" @@ -97,13 +97,13 @@ type MConnConfig struct { SendRate int64 `mapstructure:"send_rate"` RecvRate int64 `mapstructure:"recv_rate"` - maxMsgPacketPayloadSize int + MaxMsgPacketPayloadSize int - flushThrottle time.Duration + FlushThrottle time.Duration } func (cfg *MConnConfig) maxMsgPacketTotalSize() int { - return cfg.maxMsgPacketPayloadSize + maxMsgPacketOverheadSize + return cfg.MaxMsgPacketPayloadSize + maxMsgPacketOverheadSize } // DefaultMConnConfig returns the default config. @@ -111,8 +111,8 @@ func DefaultMConnConfig() *MConnConfig { return &MConnConfig{ SendRate: defaultSendRate, RecvRate: defaultRecvRate, - maxMsgPacketPayloadSize: defaultMaxMsgPacketPayloadSize, - flushThrottle: defaultFlushThrottle, + MaxMsgPacketPayloadSize: defaultMaxMsgPacketPayloadSize, + FlushThrottle: defaultFlushThrottle, } } @@ -171,7 +171,7 @@ func (c *MConnection) OnStart() error { return err } c.quit = make(chan struct{}) - c.flushTimer = cmn.NewThrottleTimer("flush", c.config.flushThrottle) + c.flushTimer = cmn.NewThrottleTimer("flush", c.config.FlushThrottle) c.pingTimer = cmn.NewRepeatTimer("ping", pingTimeout) c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateStats) go c.sendRoutine() @@ -586,7 +586,7 @@ func newChannel(conn *MConnection, desc ChannelDescriptor) *Channel { desc: desc, sendQueue: make(chan []byte, desc.SendQueueCapacity), recving: make([]byte, 0, desc.RecvBufferCapacity), - maxMsgPacketPayloadSize: conn.config.maxMsgPacketPayloadSize, + maxMsgPacketPayloadSize: conn.config.MaxMsgPacketPayloadSize, } } diff --git a/p2p/connection_test.go b/p2p/tmconn/connection_test.go similarity index 97% rename from p2p/connection_test.go rename to p2p/tmconn/connection_test.go index 2a64764e..65ae017c 100644 --- a/p2p/connection_test.go +++ b/p2p/tmconn/connection_test.go @@ -1,4 +1,4 @@ -package p2p +package tmconn import ( "net" @@ -31,7 +31,7 @@ func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msg func TestMConnectionSend(t *testing.T) { assert, require := assert.New(t), require.New(t) - server, client := netPipe() + server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck @@ -64,7 +64,7 @@ func TestMConnectionSend(t *testing.T) { func TestMConnectionReceive(t *testing.T) { assert, require := assert.New(t), require.New(t) - server, client := netPipe() + server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck @@ -102,7 +102,7 @@ func TestMConnectionReceive(t *testing.T) { func TestMConnectionStatus(t *testing.T) { assert, require := assert.New(t), require.New(t) - server, client := netPipe() + server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck @@ -119,7 +119,7 @@ func TestMConnectionStatus(t *testing.T) { func TestMConnectionStopsAndReturnsError(t *testing.T) { assert, require := assert.New(t), require.New(t) - server, client := netPipe() + server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck @@ -152,7 +152,7 @@ func TestMConnectionStopsAndReturnsError(t *testing.T) { } func newClientAndServerConnsForReadErrors(require *require.Assertions, chOnErr chan struct{}) (*MConnection, *MConnection) { - server, client := netPipe() + server, client := NetPipe() onReceive := func(chID byte, msgBytes []byte) {} onError := func(r interface{}) {} @@ -283,7 +283,7 @@ func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { func TestMConnectionTrySend(t *testing.T) { assert, require := assert.New(t), require.New(t) - server, client := netPipe() + server, client := NetPipe() defer server.Close() defer client.Close() diff --git a/p2p/secret_connection.go b/p2p/tmconn/secret_connection.go similarity index 99% rename from p2p/secret_connection.go rename to p2p/tmconn/secret_connection.go index f022d9c3..e1a3e050 100644 --- a/p2p/secret_connection.go +++ b/p2p/tmconn/secret_connection.go @@ -4,7 +4,7 @@ // is known ahead of time, and thus we are technically // still vulnerable to MITM. (TODO!) // See docs/sts-final.pdf for more info -package p2p +package tmconn import ( "bytes" diff --git a/p2p/secret_connection_test.go b/p2p/tmconn/secret_connection_test.go similarity index 99% rename from p2p/secret_connection_test.go rename to p2p/tmconn/secret_connection_test.go index 5e0611a8..5ef2c410 100644 --- a/p2p/secret_connection_test.go +++ b/p2p/tmconn/secret_connection_test.go @@ -1,4 +1,4 @@ -package p2p +package tmconn import ( "io" diff --git a/p2p/types/errors.go b/p2p/types/errors.go new file mode 100644 index 00000000..ead2a833 --- /dev/null +++ b/p2p/types/errors.go @@ -0,0 +1,20 @@ +package types + +import ( + "errors" + "fmt" +) + +var ( + ErrSwitchDuplicatePeer = errors.New("Duplicate peer") + ErrSwitchConnectToSelf = errors.New("Connect to self") +) + +type ErrSwitchAuthenticationFailure struct { + Dialed *NetAddress + Got ID +} + +func (e ErrSwitchAuthenticationFailure) Error() string { + return fmt.Sprintf("Failed to authenticate peer. Dialed %v, but got peer with ID %s", e.Dialed, e.Got) +} diff --git a/p2p/key.go b/p2p/types/key.go similarity index 99% rename from p2p/key.go rename to p2p/types/key.go index ea0f0b07..4ce5ee50 100644 --- a/p2p/key.go +++ b/p2p/types/key.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "bytes" diff --git a/p2p/key_test.go b/p2p/types/key_test.go similarity index 98% rename from p2p/key_test.go rename to p2p/types/key_test.go index c2e1f3e0..f18fb3b9 100644 --- a/p2p/key_test.go +++ b/p2p/types/key_test.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "bytes" diff --git a/p2p/netaddress.go b/p2p/types/netaddress.go similarity index 99% rename from p2p/netaddress.go rename to p2p/types/netaddress.go index 333d16e5..f0b397e8 100644 --- a/p2p/netaddress.go +++ b/p2p/types/netaddress.go @@ -2,7 +2,7 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package p2p +package types import ( "encoding/hex" diff --git a/p2p/netaddress_test.go b/p2p/types/netaddress_test.go similarity index 99% rename from p2p/netaddress_test.go rename to p2p/types/netaddress_test.go index 6c1930a2..0119cc3b 100644 --- a/p2p/netaddress_test.go +++ b/p2p/types/netaddress_test.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "net" diff --git a/p2p/types.go b/p2p/types/node_info.go similarity index 97% rename from p2p/types.go rename to p2p/types/node_info.go index d93adc9b..10c48685 100644 --- a/p2p/types.go +++ b/p2p/types/node_info.go @@ -1,4 +1,4 @@ -package p2p +package types import ( "fmt" @@ -11,6 +11,10 @@ import ( const maxNodeInfoSize = 10240 // 10Kb +func MaxNodeInfoSize() int { + return maxNodeInfoSize +} + // NodeInfo is the basic node information exchanged // between two peers during the Tendermint P2P handshake. type NodeInfo struct { From 0d7d16005a256e9080fd8df45e0483bfe90d18ed Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 20 Jan 2018 21:44:09 -0500 Subject: [PATCH 094/124] fixes --- node/node.go | 14 ++++++++------ p2p/switch.go | 2 +- p2p/types.go | 12 ++++++++++++ rpc/core/pipe.go | 6 +++--- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 p2p/types.go diff --git a/node/node.go b/node/node.go index 3012ed05..cab8aa80 100644 --- a/node/node.go +++ b/node/node.go @@ -22,7 +22,9 @@ import ( "github.com/tendermint/tendermint/evidence" mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/pex" "github.com/tendermint/tendermint/p2p/trust" + p2ptypes "github.com/tendermint/tendermint/p2p/types" "github.com/tendermint/tendermint/proxy" rpccore "github.com/tendermint/tendermint/rpc/core" grpccore "github.com/tendermint/tendermint/rpc/grpc" @@ -97,7 +99,7 @@ type Node struct { // network sw *p2p.Switch // p2p connections - addrBook *p2p.AddrBook // known peers + addrBook pex.AddrBook // known peers trustMetricStore *trust.TrustMetricStore // trust metrics for all peers // services @@ -238,10 +240,10 @@ func NewNode(config *cfg.Config, sw.AddReactor("EVIDENCE", evidenceReactor) // Optionally, start the pex reactor - var addrBook *p2p.AddrBook + var addrBook pex.AddrBook var trustMetricStore *trust.TrustMetricStore if config.P2P.PexReactor { - addrBook = p2p.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) + addrBook = pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile())) // Get the trust metric history data @@ -256,8 +258,8 @@ func NewNode(config *cfg.Config, if config.P2P.Seeds != "" { seeds = strings.Split(config.P2P.Seeds, ",") } - pexReactor := p2p.NewPEXReactor(addrBook, - &p2p.PEXReactorConfig{Seeds: seeds}) + pexReactor := pex.NewPEXReactor(addrBook, + &pex.PEXReactorConfig{Seeds: seeds}) pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } @@ -374,7 +376,7 @@ func (n *Node) OnStart() error { // Generate node PrivKey // TODO: pass in like priv_val - nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) + nodeKey, err := p2ptypes.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err } diff --git a/p2p/switch.go b/p2p/switch.go index ec54478c..c9938374 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -35,7 +35,7 @@ const ( //----------------------------------------------------------------------------- type AddrBook interface { - AddAddress(addr *types.NetAddress, src *types.NetAddress) + AddAddress(addr *types.NetAddress, src *types.NetAddress) error } //----------------------------------------------------------------------------- diff --git a/p2p/types.go b/p2p/types.go new file mode 100644 index 00000000..db7469ec --- /dev/null +++ b/p2p/types.go @@ -0,0 +1,12 @@ +package p2p + +import ( + "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/types" +) + +type ID = types.ID +type NodeInfo = types.NodeInfo + +type ChannelDescriptor = tmconn.ChannelDescriptor +type ConnectionStatus = tmconn.ConnectionStatus diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 301977ac..2edb3f3d 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -32,7 +32,7 @@ type P2P interface { NumPeers() (outbound, inbound, dialig int) NodeInfo() p2p.NodeInfo IsListening() bool - DialPeersAsync(*p2p.AddrBook, []string, bool) error + DialPeersAsync(p2p.AddrBook, []string, bool) error } //---------------------------------------------- @@ -54,7 +54,7 @@ var ( // objects pubKey crypto.PubKey genDoc *types.GenesisDoc // cache the genesis structure - addrBook *p2p.AddrBook + addrBook p2p.AddrBook txIndexer txindex.TxIndexer consensusReactor *consensus.ConsensusReactor eventBus *types.EventBus // thread safe @@ -94,7 +94,7 @@ func SetGenesisDoc(doc *types.GenesisDoc) { genDoc = doc } -func SetAddrBook(book *p2p.AddrBook) { +func SetAddrBook(book p2p.AddrBook) { addrBook = book } From 44e967184a6f44f9d788015f265e9bb1a926688c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 21 Jan 2018 00:33:53 -0500 Subject: [PATCH 095/124] p2p: tmconn->conn and types->p2p --- node/node.go | 3 +- p2p/base_reactor.go | 6 +-- p2p/{tmconn => conn}/conn_go110.go | 2 +- p2p/{tmconn => conn}/conn_notgo110.go | 2 +- p2p/{tmconn => conn}/connection.go | 2 +- p2p/{tmconn => conn}/connection_test.go | 2 +- p2p/{tmconn => conn}/secret_connection.go | 2 +- .../secret_connection_test.go | 2 +- p2p/{types => }/errors.go | 2 +- p2p/{types => }/key.go | 2 +- p2p/{types => }/key_test.go | 2 +- p2p/listener.go | 27 +++++----- p2p/{types => }/netaddress.go | 2 +- p2p/{types => }/netaddress_test.go | 2 +- p2p/{types => }/node_info.go | 2 +- p2p/peer.go | 29 +++++----- p2p/peer_set.go | 16 +++--- p2p/peer_set_test.go | 5 +- p2p/peer_test.go | 15 +++--- p2p/pex/addrbook.go | 53 +++++++++---------- p2p/pex/addrbook_test.go | 14 ++--- p2p/pex/known_address.go | 10 ++-- p2p/pex/pex_reactor.go | 25 +++++---- p2p/pex/pex_reactor_test.go | 27 +++++----- p2p/switch.go | 33 ++++++------ p2p/switch_test.go | 23 ++++---- p2p/test_util.go | 17 +++--- p2p/types.go | 10 ++-- 28 files changed, 160 insertions(+), 177 deletions(-) rename p2p/{tmconn => conn}/conn_go110.go (96%) rename p2p/{tmconn => conn}/conn_notgo110.go (98%) rename p2p/{tmconn => conn}/connection.go (99%) rename p2p/{tmconn => conn}/connection_test.go (99%) rename p2p/{tmconn => conn}/secret_connection.go (99%) rename p2p/{tmconn => conn}/secret_connection_test.go (99%) rename p2p/{types => }/errors.go (96%) rename p2p/{types => }/key.go (99%) rename p2p/{types => }/key_test.go (98%) rename p2p/{types => }/netaddress.go (99%) rename p2p/{types => }/netaddress_test.go (99%) rename p2p/{types => }/node_info.go (99%) diff --git a/node/node.go b/node/node.go index cab8aa80..bdbf12f8 100644 --- a/node/node.go +++ b/node/node.go @@ -24,7 +24,6 @@ import ( "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p/pex" "github.com/tendermint/tendermint/p2p/trust" - p2ptypes "github.com/tendermint/tendermint/p2p/types" "github.com/tendermint/tendermint/proxy" rpccore "github.com/tendermint/tendermint/rpc/core" grpccore "github.com/tendermint/tendermint/rpc/grpc" @@ -376,7 +375,7 @@ func (n *Node) OnStart() error { // Generate node PrivKey // TODO: pass in like priv_val - nodeKey, err := p2ptypes.LoadOrGenNodeKey(n.config.NodeKeyFile()) + nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err } diff --git a/p2p/base_reactor.go b/p2p/base_reactor.go index a24a7629..20525e67 100644 --- a/p2p/base_reactor.go +++ b/p2p/base_reactor.go @@ -1,7 +1,7 @@ package p2p import ( - "github.com/tendermint/tendermint/p2p/tmconn" + "github.com/tendermint/tendermint/p2p/conn" cmn "github.com/tendermint/tmlibs/common" ) @@ -9,7 +9,7 @@ type Reactor interface { cmn.Service // Start, Stop SetSwitch(*Switch) - GetChannels() []*tmconn.ChannelDescriptor + GetChannels() []*conn.ChannelDescriptor AddPeer(peer Peer) RemovePeer(peer Peer, reason interface{}) Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil @@ -32,7 +32,7 @@ func NewBaseReactor(name string, impl Reactor) *BaseReactor { func (br *BaseReactor) SetSwitch(sw *Switch) { br.Switch = sw } -func (_ *BaseReactor) GetChannels() []*tmconn.ChannelDescriptor { return nil } +func (_ *BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } func (_ *BaseReactor) AddPeer(peer Peer) {} func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} diff --git a/p2p/tmconn/conn_go110.go b/p2p/conn/conn_go110.go similarity index 96% rename from p2p/tmconn/conn_go110.go rename to p2p/conn/conn_go110.go index 75e55d85..68218810 100644 --- a/p2p/tmconn/conn_go110.go +++ b/p2p/conn/conn_go110.go @@ -1,6 +1,6 @@ // +build go1.10 -package tmconn +package conn // Go1.10 has a proper net.Conn implementation that // has the SetDeadline method implemented as per diff --git a/p2p/tmconn/conn_notgo110.go b/p2p/conn/conn_notgo110.go similarity index 98% rename from p2p/tmconn/conn_notgo110.go rename to p2p/conn/conn_notgo110.go index bb72d64a..ed642eb5 100644 --- a/p2p/tmconn/conn_notgo110.go +++ b/p2p/conn/conn_notgo110.go @@ -1,6 +1,6 @@ // +build !go1.10 -package tmconn +package conn import ( "net" diff --git a/p2p/tmconn/connection.go b/p2p/conn/connection.go similarity index 99% rename from p2p/tmconn/connection.go rename to p2p/conn/connection.go index 92c48c36..71b2a13d 100644 --- a/p2p/tmconn/connection.go +++ b/p2p/conn/connection.go @@ -1,4 +1,4 @@ -package tmconn +package conn import ( "bufio" diff --git a/p2p/tmconn/connection_test.go b/p2p/conn/connection_test.go similarity index 99% rename from p2p/tmconn/connection_test.go rename to p2p/conn/connection_test.go index 65ae017c..9c8eccbe 100644 --- a/p2p/tmconn/connection_test.go +++ b/p2p/conn/connection_test.go @@ -1,4 +1,4 @@ -package tmconn +package conn import ( "net" diff --git a/p2p/tmconn/secret_connection.go b/p2p/conn/secret_connection.go similarity index 99% rename from p2p/tmconn/secret_connection.go rename to p2p/conn/secret_connection.go index e1a3e050..aa6db05b 100644 --- a/p2p/tmconn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -4,7 +4,7 @@ // is known ahead of time, and thus we are technically // still vulnerable to MITM. (TODO!) // See docs/sts-final.pdf for more info -package tmconn +package conn import ( "bytes" diff --git a/p2p/tmconn/secret_connection_test.go b/p2p/conn/secret_connection_test.go similarity index 99% rename from p2p/tmconn/secret_connection_test.go rename to p2p/conn/secret_connection_test.go index 5ef2c410..8af9cdeb 100644 --- a/p2p/tmconn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -1,4 +1,4 @@ -package tmconn +package conn import ( "io" diff --git a/p2p/types/errors.go b/p2p/errors.go similarity index 96% rename from p2p/types/errors.go rename to p2p/errors.go index ead2a833..cb6a7051 100644 --- a/p2p/types/errors.go +++ b/p2p/errors.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "errors" diff --git a/p2p/types/key.go b/p2p/key.go similarity index 99% rename from p2p/types/key.go rename to p2p/key.go index 4ce5ee50..ea0f0b07 100644 --- a/p2p/types/key.go +++ b/p2p/key.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "bytes" diff --git a/p2p/types/key_test.go b/p2p/key_test.go similarity index 98% rename from p2p/types/key_test.go rename to p2p/key_test.go index f18fb3b9..c2e1f3e0 100644 --- a/p2p/types/key_test.go +++ b/p2p/key_test.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "bytes" diff --git a/p2p/listener.go b/p2p/listener.go index 01d71833..884c45ee 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/tendermint/tendermint/p2p/types" "github.com/tendermint/tendermint/p2p/upnp" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -14,8 +13,8 @@ import ( type Listener interface { Connections() <-chan net.Conn - InternalAddress() *types.NetAddress - ExternalAddress() *types.NetAddress + InternalAddress() *NetAddress + ExternalAddress() *NetAddress String() string Stop() error } @@ -25,8 +24,8 @@ type DefaultListener struct { cmn.BaseService listener net.Listener - intAddr *types.NetAddress - extAddr *types.NetAddress + intAddr *NetAddress + extAddr *NetAddress connections chan net.Conn } @@ -72,14 +71,14 @@ func NewDefaultListener(protocol string, lAddr string, skipUPNP bool, logger log logger.Info("Local listener", "ip", listenerIP, "port", listenerPort) // Determine internal address... - var intAddr *types.NetAddress - intAddr, err = types.NewNetAddressString(lAddr) + var intAddr *NetAddress + intAddr, err = NewNetAddressString(lAddr) if err != nil { panic(err) } // Determine external address... - var extAddr *types.NetAddress + var extAddr *NetAddress if !skipUPNP { // If the lAddrIP is INADDR_ANY, try UPnP if lAddrIP == "" || lAddrIP == "0.0.0.0" { @@ -152,11 +151,11 @@ func (l *DefaultListener) Connections() <-chan net.Conn { return l.connections } -func (l *DefaultListener) InternalAddress() *types.NetAddress { +func (l *DefaultListener) InternalAddress() *NetAddress { return l.intAddr } -func (l *DefaultListener) ExternalAddress() *types.NetAddress { +func (l *DefaultListener) ExternalAddress() *NetAddress { return l.extAddr } @@ -173,7 +172,7 @@ func (l *DefaultListener) String() string { /* external address helpers */ // UPNP external address discovery & port mapping -func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) *types.NetAddress { +func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) *NetAddress { logger.Info("Getting UPNP external address") nat, err := upnp.Discover() if err != nil { @@ -199,11 +198,11 @@ func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) * } logger.Info("Got UPNP external address", "address", ext) - return types.NewNetAddressIPPort(ext, uint16(externalPort)) + return NewNetAddressIPPort(ext, uint16(externalPort)) } // TODO: use syscalls: see issue #712 -func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *types.NetAddress { +func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *NetAddress { addrs, err := net.InterfaceAddrs() if err != nil { panic(cmn.Fmt("Could not fetch interface addresses: %v", err)) @@ -218,7 +217,7 @@ func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) * if v4 == nil || (!settleForLocal && v4[0] == 127) { continue } // loopback - return types.NewNetAddressIPPort(ipnet.IP, uint16(port)) + return NewNetAddressIPPort(ipnet.IP, uint16(port)) } // try again, but settle for local diff --git a/p2p/types/netaddress.go b/p2p/netaddress.go similarity index 99% rename from p2p/types/netaddress.go rename to p2p/netaddress.go index f0b397e8..333d16e5 100644 --- a/p2p/types/netaddress.go +++ b/p2p/netaddress.go @@ -2,7 +2,7 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package types +package p2p import ( "encoding/hex" diff --git a/p2p/types/netaddress_test.go b/p2p/netaddress_test.go similarity index 99% rename from p2p/types/netaddress_test.go rename to p2p/netaddress_test.go index 0119cc3b..6c1930a2 100644 --- a/p2p/types/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "net" diff --git a/p2p/types/node_info.go b/p2p/node_info.go similarity index 99% rename from p2p/types/node_info.go rename to p2p/node_info.go index 10c48685..552c464d 100644 --- a/p2p/types/node_info.go +++ b/p2p/node_info.go @@ -1,4 +1,4 @@ -package types +package p2p import ( "fmt" diff --git a/p2p/peer.go b/p2p/peer.go index 17c5861f..60f9dceb 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -12,18 +12,17 @@ import ( cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + tmconn "github.com/tendermint/tendermint/p2p/conn" ) // Peer is an interface representing a peer connected on a reactor. type Peer interface { cmn.Service - ID() types.ID // peer's cryptographic ID - IsOutbound() bool // did we dial the peer - IsPersistent() bool // do we redial this peer when we disconnect - NodeInfo() types.NodeInfo // peer's info + ID() ID // peer's cryptographic ID + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + NodeInfo() NodeInfo // peer's info Status() tmconn.ConnectionStatus Send(byte, interface{}) bool @@ -49,7 +48,7 @@ type peer struct { persistent bool config *PeerConfig - nodeInfo types.NodeInfo + nodeInfo NodeInfo Data *cmn.CMap // User data. } @@ -79,7 +78,7 @@ func DefaultPeerConfig() *PeerConfig { } } -func newOutboundPeer(addr *types.NetAddress, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, +func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig, persistent bool) (*peer, error) { conn, err := dial(addr, config) @@ -174,8 +173,8 @@ func (p *peer) OnStop() { // Implements Peer // ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() types.ID { - return types.PubKeyToID(p.PubKey()) +func (p *peer) ID() ID { + return PubKeyToID(p.PubKey()) } // IsOutbound returns true if the connection is outbound, false otherwise. @@ -189,7 +188,7 @@ func (p *peer) IsPersistent() bool { } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() types.NodeInfo { +func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo } @@ -239,13 +238,13 @@ func (p *peer) CloseConn() { // HandshakeTimeout performs the Tendermint P2P handshake between a given node and the peer // by exchanging their NodeInfo. It sets the received nodeInfo on the peer. // NOTE: blocking -func (p *peer) HandshakeTimeout(ourNodeInfo types.NodeInfo, timeout time.Duration) error { +func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull if err := p.conn.SetDeadline(time.Now().Add(timeout)); err != nil { return errors.Wrap(err, "Error setting deadline") } - var peerNodeInfo types.NodeInfo + var peerNodeInfo NodeInfo var err1 error var err2 error cmn.Parallel( @@ -255,7 +254,7 @@ func (p *peer) HandshakeTimeout(ourNodeInfo types.NodeInfo, timeout time.Duratio }, func() { var n int - wire.ReadBinary(&peerNodeInfo, p.conn, types.MaxNodeInfoSize(), &n, &err2) + wire.ReadBinary(&peerNodeInfo, p.conn, MaxNodeInfoSize(), &n, &err2) p.Logger.Info("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { @@ -311,7 +310,7 @@ func (p *peer) String() string { //------------------------------------------------------------------ // helper funcs -func dial(addr *types.NetAddress, config *PeerConfig) (net.Conn, error) { +func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { conn, err := addr.DialTimeout(config.DialTimeout * time.Second) if err != nil { return nil, err diff --git a/p2p/peer_set.go b/p2p/peer_set.go index 7a0680cb..dc53174a 100644 --- a/p2p/peer_set.go +++ b/p2p/peer_set.go @@ -2,14 +2,12 @@ package p2p import ( "sync" - - "github.com/tendermint/tendermint/p2p/types" ) // IPeerSet has a (immutable) subset of the methods of PeerSet. type IPeerSet interface { - Has(key types.ID) bool - Get(key types.ID) Peer + Has(key ID) bool + Get(key ID) Peer List() []Peer Size() int } @@ -20,7 +18,7 @@ type IPeerSet interface { // Iteration over the peers is super fast and thread-safe. type PeerSet struct { mtx sync.Mutex - lookup map[types.ID]*peerSetItem + lookup map[ID]*peerSetItem list []Peer } @@ -32,7 +30,7 @@ type peerSetItem struct { // NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. func NewPeerSet() *PeerSet { return &PeerSet{ - lookup: make(map[types.ID]*peerSetItem), + lookup: make(map[ID]*peerSetItem), list: make([]Peer, 0, 256), } } @@ -43,7 +41,7 @@ func (ps *PeerSet) Add(peer Peer) error { ps.mtx.Lock() defer ps.mtx.Unlock() if ps.lookup[peer.ID()] != nil { - return types.ErrSwitchDuplicatePeer + return ErrSwitchDuplicatePeer } index := len(ps.list) @@ -56,7 +54,7 @@ func (ps *PeerSet) Add(peer Peer) error { // Has returns true iff the PeerSet contains // the peer referred to by this peerKey. -func (ps *PeerSet) Has(peerKey types.ID) bool { +func (ps *PeerSet) Has(peerKey ID) bool { ps.mtx.Lock() _, ok := ps.lookup[peerKey] ps.mtx.Unlock() @@ -64,7 +62,7 @@ func (ps *PeerSet) Has(peerKey types.ID) bool { } // Get looks up a peer by the provided peerKey. -func (ps *PeerSet) Get(peerKey types.ID) Peer { +func (ps *PeerSet) Get(peerKey ID) Peer { ps.mtx.Lock() defer ps.mtx.Unlock() item, ok := ps.lookup[peerKey] diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 7d7ed106..e906eb8e 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -8,14 +8,13 @@ import ( "github.com/stretchr/testify/assert" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tendermint/p2p/types" cmn "github.com/tendermint/tmlibs/common" ) // Returns an empty dummy peer func randPeer() *peer { return &peer{ - nodeInfo: types.NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, @@ -120,7 +119,7 @@ func TestPeerSetAddDuplicate(t *testing.T) { // Our next procedure is to ensure that only one addition // succeeded and that the rest are each ErrSwitchDuplicatePeer. - wantErrCount, gotErrCount := n-1, errsTally[types.ErrSwitchDuplicatePeer] + wantErrCount, gotErrCount := n-1, errsTally[ErrSwitchDuplicatePeer] assert.Equal(t, wantErrCount, gotErrCount, "invalid ErrSwitchDuplicatePeer count") wantNilErrCount, gotNilErrCount := 1, errsTally[nil] diff --git a/p2p/peer_test.go b/p2p/peer_test.go index dc13cf9d..978775c8 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -10,8 +10,7 @@ import ( "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + tmconn "github.com/tendermint/tendermint/p2p/conn" ) func TestPeerBasic(t *testing.T) { @@ -82,7 +81,7 @@ func TestPeerSend(t *testing.T) { assert.True(p.Send(0x01, "Asylum")) } -func createOutboundPeerAndPerformHandshake(addr *types.NetAddress, config *PeerConfig) (*peer, error) { +func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*peer, error) { chDescs := []*tmconn.ChannelDescriptor{ {ID: 0x01, Priority: 1}, } @@ -92,7 +91,7 @@ func createOutboundPeerAndPerformHandshake(addr *types.NetAddress, config *PeerC if err != nil { return nil, err } - err = p.HandshakeTimeout(types.NodeInfo{ + err = p.HandshakeTimeout(NodeInfo{ PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", @@ -107,11 +106,11 @@ func createOutboundPeerAndPerformHandshake(addr *types.NetAddress, config *PeerC type remotePeer struct { PrivKey crypto.PrivKey Config *PeerConfig - addr *types.NetAddress + addr *NetAddress quit chan struct{} } -func (p *remotePeer) Addr() *types.NetAddress { +func (p *remotePeer) Addr() *NetAddress { return p.addr } @@ -124,7 +123,7 @@ func (p *remotePeer) Start() { if e != nil { golog.Fatalf("net.Listen tcp :0: %+v", e) } - p.addr = types.NewNetAddress("", l.Addr()) + p.addr = NewNetAddress("", l.Addr()) p.quit = make(chan struct{}) go p.accept(l) } @@ -143,7 +142,7 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } - err = peer.HandshakeTimeout(types.NodeInfo{ + err = peer.HandshakeTimeout(NodeInfo{ PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 93f35211..b7f60682 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -15,9 +15,8 @@ import ( "time" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tendermint/p2p" cmn "github.com/tendermint/tmlibs/common" - - "github.com/tendermint/tendermint/p2p/types" ) const ( @@ -32,25 +31,25 @@ type AddrBook interface { cmn.Service // Add our own addresses so we don't later add ourselves - AddOurAddress(*types.NetAddress) + AddOurAddress(*p2p.NetAddress) // Add and remove an address - AddAddress(addr *types.NetAddress, src *types.NetAddress) error - RemoveAddress(addr *types.NetAddress) + AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error + RemoveAddress(addr *p2p.NetAddress) // Do we need more peers? NeedMoreAddrs() bool // Pick an address to dial - PickAddress(newBias int) *types.NetAddress + PickAddress(newBias int) *p2p.NetAddress // Mark address - MarkGood(*types.NetAddress) - MarkAttempt(*types.NetAddress) - MarkBad(*types.NetAddress) + MarkGood(*p2p.NetAddress) + MarkAttempt(*p2p.NetAddress) + MarkBad(*p2p.NetAddress) // Send a selection of addresses to peers - GetSelection() []*types.NetAddress + GetSelection() []*p2p.NetAddress // TODO: remove ListOfKnownAddresses() []*knownAddress @@ -71,8 +70,8 @@ type addrBook struct { // accessed concurrently mtx sync.Mutex rand *rand.Rand - ourAddrs map[string]*types.NetAddress - addrLookup map[types.ID]*knownAddress // new & old + ourAddrs map[string]*p2p.NetAddress + addrLookup map[p2p.ID]*knownAddress // new & old bucketsOld []map[string]*knownAddress bucketsNew []map[string]*knownAddress nOld int @@ -86,8 +85,8 @@ type addrBook struct { func NewAddrBook(filePath string, routabilityStrict bool) *addrBook { am := &addrBook{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), // TODO: seed from outside - ourAddrs: make(map[string]*types.NetAddress), - addrLookup: make(map[types.ID]*knownAddress), + ourAddrs: make(map[string]*p2p.NetAddress), + addrLookup: make(map[p2p.ID]*knownAddress), filePath: filePath, routabilityStrict: routabilityStrict, } @@ -139,7 +138,7 @@ func (a *addrBook) Wait() { //------------------------------------------------------- // AddOurAddress one of our addresses. -func (a *addrBook) AddOurAddress(addr *types.NetAddress) { +func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() a.Logger.Info("Add our address to book", "addr", addr) @@ -148,14 +147,14 @@ func (a *addrBook) AddOurAddress(addr *types.NetAddress) { // AddAddress implements AddrBook - adds the given address as received from the given source. // NOTE: addr must not be nil -func (a *addrBook) AddAddress(addr *types.NetAddress, src *types.NetAddress) error { +func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error { a.mtx.Lock() defer a.mtx.Unlock() return a.addAddress(addr, src) } // RemoveAddress implements AddrBook - removes the address from the book. -func (a *addrBook) RemoveAddress(addr *types.NetAddress) { +func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -177,7 +176,7 @@ func (a *addrBook) NeedMoreAddrs() bool { // and determines how biased we are to pick an address from a new bucket. // PickAddress returns nil if the AddrBook is empty or if we try to pick // from an empty bucket. -func (a *addrBook) PickAddress(newBias int) *types.NetAddress { +func (a *addrBook) PickAddress(newBias int) *p2p.NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -223,7 +222,7 @@ func (a *addrBook) PickAddress(newBias int) *types.NetAddress { // MarkGood implements AddrBook - it marks the peer as good and // moves it into an "old" bucket. -func (a *addrBook) MarkGood(addr *types.NetAddress) { +func (a *addrBook) MarkGood(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -237,7 +236,7 @@ func (a *addrBook) MarkGood(addr *types.NetAddress) { } // MarkAttempt implements AddrBook - it marks that an attempt was made to connect to the address. -func (a *addrBook) MarkAttempt(addr *types.NetAddress) { +func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] @@ -249,13 +248,13 @@ func (a *addrBook) MarkAttempt(addr *types.NetAddress) { // MarkBad implements AddrBook. Currently it just ejects the address. // TODO: black list for some amount of time -func (a *addrBook) MarkBad(addr *types.NetAddress) { +func (a *addrBook) MarkBad(addr *p2p.NetAddress) { a.RemoveAddress(addr) } // GetSelection implements AddrBook. // It randomly selects some addresses (old & new). Suitable for peer-exchange protocols. -func (a *addrBook) GetSelection() []*types.NetAddress { +func (a *addrBook) GetSelection() []*p2p.NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -263,7 +262,7 @@ func (a *addrBook) GetSelection() []*types.NetAddress { return nil } - allAddr := make([]*types.NetAddress, a.size()) + allAddr := make([]*p2p.NetAddress, a.size()) i := 0 for _, ka := range a.addrLookup { allAddr[i] = ka.Addr @@ -466,7 +465,7 @@ func (a *addrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { // adds the address to a "new" bucket. if its already in one, // it only adds it probabilistically -func (a *addrBook) addAddress(addr, src *types.NetAddress) error { +func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { if a.routabilityStrict && !addr.Routable() { return fmt.Errorf("Cannot add non-routable address %v", addr) } @@ -573,7 +572,7 @@ func (a *addrBook) moveToOld(ka *knownAddress) { // doublesha256( key + sourcegroup + // int64(doublesha256(key + group + sourcegroup))%bucket_per_group ) % num_new_buckets -func (a *addrBook) calcNewBucket(addr, src *types.NetAddress) int { +func (a *addrBook) calcNewBucket(addr, src *p2p.NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(a.groupKey(addr))...) @@ -594,7 +593,7 @@ func (a *addrBook) calcNewBucket(addr, src *types.NetAddress) int { // doublesha256( key + group + // int64(doublesha256(key + addr))%buckets_per_group ) % num_old_buckets -func (a *addrBook) calcOldBucket(addr *types.NetAddress) int { +func (a *addrBook) calcOldBucket(addr *p2p.NetAddress) int { data1 := []byte{} data1 = append(data1, []byte(a.key)...) data1 = append(data1, []byte(addr.String())...) @@ -616,7 +615,7 @@ func (a *addrBook) calcOldBucket(addr *types.NetAddress) int { // This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string // "local" for a local address and the string "unroutable" for an unroutable // address. -func (a *addrBook) groupKey(na *types.NetAddress) string { +func (a *addrBook) groupKey(na *p2p.NetAddress) string { if a.routabilityStrict && na.Local() { return "local" } diff --git a/p2p/pex/addrbook_test.go b/p2p/pex/addrbook_test.go index 206e3401..166d3184 100644 --- a/p2p/pex/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) @@ -169,8 +169,8 @@ func TestAddrBookHandlesDuplicates(t *testing.T) { } type netAddressPair struct { - addr *types.NetAddress - src *types.NetAddress + addr *p2p.NetAddress + src *p2p.NetAddress } func randNetAddressPairs(t *testing.T, n int) []netAddressPair { @@ -181,7 +181,7 @@ func randNetAddressPairs(t *testing.T, n int) []netAddressPair { return randAddrs } -func randIPv4Address(t *testing.T) *types.NetAddress { +func randIPv4Address(t *testing.T) *p2p.NetAddress { for { ip := fmt.Sprintf("%v.%v.%v.%v", rand.Intn(254)+1, @@ -190,9 +190,9 @@ func randIPv4Address(t *testing.T) *types.NetAddress { rand.Intn(255), ) port := rand.Intn(65535-1) + 1 - id := types.ID(hex.EncodeToString(cmn.RandBytes(types.IDByteLength))) - idAddr := types.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) - addr, err := types.NewNetAddressString(idAddr) + id := p2p.ID(hex.EncodeToString(cmn.RandBytes(p2p.IDByteLength))) + idAddr := p2p.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) + addr, err := p2p.NewNetAddressString(idAddr) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/pex/known_address.go b/p2p/pex/known_address.go index db6d021f..e26fdb7b 100644 --- a/p2p/pex/known_address.go +++ b/p2p/pex/known_address.go @@ -3,14 +3,14 @@ package pex import ( "time" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p" ) // knownAddress tracks information about a known network address // that is used to determine how viable an address is. type knownAddress struct { - Addr *types.NetAddress - Src *types.NetAddress + Addr *p2p.NetAddress + Src *p2p.NetAddress Attempts int32 LastAttempt time.Time LastSuccess time.Time @@ -18,7 +18,7 @@ type knownAddress struct { Buckets []int } -func newKnownAddress(addr *types.NetAddress, src *types.NetAddress) *knownAddress { +func newKnownAddress(addr *p2p.NetAddress, src *p2p.NetAddress) *knownAddress { return &knownAddress{ Addr: addr, Src: src, @@ -29,7 +29,7 @@ func newKnownAddress(addr *types.NetAddress, src *types.NetAddress) *knownAddres } } -func (ka *knownAddress) ID() types.ID { +func (ka *knownAddress) ID() p2p.ID { return ka.Addr.ID } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 24c9417f..53075a1d 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -13,8 +13,7 @@ import ( cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" ) type Peer = p2p.Peer @@ -117,8 +116,8 @@ func (r *PEXReactor) OnStop() { } // GetChannels implements Reactor -func (r *PEXReactor) GetChannels() []*tmconn.ChannelDescriptor { - return []*tmconn.ChannelDescriptor{ +func (r *PEXReactor) GetChannels() []*conn.ChannelDescriptor { + return []*conn.ChannelDescriptor{ { ID: PexChannel, Priority: 1, @@ -231,7 +230,7 @@ func (r *PEXReactor) RequestAddrs(p Peer) { // ReceiveAddrs adds the given addrs to the addrbook if theres an open // 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 []*types.NetAddress, src Peer) error { +func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { id := string(src.ID()) if !r.requestsSent.Has(id) { @@ -250,7 +249,7 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*types.NetAddress, src Peer) error { } // SendAddrs sends addrs to the peer. -func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*types.NetAddress) { +func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*p2p.NetAddress) { p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) } @@ -300,7 +299,7 @@ func (r *PEXReactor) ensurePeers() { // NOTE: range here is [10, 90]. Too high ? newBias := cmn.MinInt(numOutPeers, 8)*10 + 10 - toDial := make(map[types.ID]*types.NetAddress) + toDial := make(map[p2p.ID]*p2p.NetAddress) // Try maxAttempts times to pick numToDial addresses to dial maxAttempts := numToDial * 3 for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ { @@ -323,11 +322,11 @@ func (r *PEXReactor) ensurePeers() { // Dial picked addresses for _, item := range toDial { - go func(picked *types.NetAddress) { + go func(picked *p2p.NetAddress) { _, err := r.Switch.DialPeerWithAddress(picked, false) if err != nil { // TODO: detect more "bad peer" scenarios - if _, ok := err.(types.ErrSwitchAuthenticationFailure); ok { + if _, ok := err.(p2p.ErrSwitchAuthenticationFailure); ok { r.book.MarkBad(picked) } else { r.book.MarkAttempt(picked) @@ -360,7 +359,7 @@ func (r *PEXReactor) checkSeeds() error { if lSeeds == 0 { return nil } - _, errs := types.NewNetAddressStrings(r.config.Seeds) + _, errs := p2p.NewNetAddressStrings(r.config.Seeds) for _, err := range errs { if err != nil { return err @@ -375,7 +374,7 @@ func (r *PEXReactor) dialSeeds() { if lSeeds == 0 { return } - seedAddrs, _ := types.NewNetAddressStrings(r.config.Seeds) + seedAddrs, _ := p2p.NewNetAddressStrings(r.config.Seeds) perm := rand.Perm(lSeeds) // perm := r.Switch.rng.Perm(lSeeds) @@ -420,7 +419,7 @@ func (r *PEXReactor) crawlPeersRoutine() { // network crawling performed during seed/crawler mode. type crawlPeerInfo struct { // The listening address of a potential peer we learned about - Addr *types.NetAddress + Addr *p2p.NetAddress // The last time we attempt to reach this address LastAttempt time.Time @@ -544,7 +543,7 @@ func (m *pexRequestMessage) String() string { A message with announced peer addresses. */ type pexAddrsMessage struct { - Addrs []*types.NetAddress + Addrs []*p2p.NetAddress } func (m *pexAddrsMessage) String() string { diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index 439914ac..c52e45b4 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -17,8 +17,7 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" ) var ( @@ -101,7 +100,7 @@ func TestPEXReactorRunning(t *testing.T) { // fill the address book and add listeners for _, s := range switches { - addr, _ := types.NewNetAddressString(s.NodeInfo().ListenAddr) + addr, _ := p2p.NewNetAddressString(s.NodeInfo().ListenAddr) book.AddAddress(addr, addr) s.AddListener(p2p.NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true, log.TestingLogger())) } @@ -171,7 +170,7 @@ func TestPEXReactorReceive(t *testing.T) { r.RequestAddrs(peer) size := book.Size() - addrs := []*types.NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) assert.Equal(size+1, book.Size()) @@ -246,7 +245,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { assert.True(r.requestsSent.Has(id)) assert.True(sw.Peers().Has(peer.ID())) - addrs := []*types.NetAddress{peer.NodeInfo().NetAddress()} + addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) // receive some addrs. should clear the request @@ -340,7 +339,7 @@ func TestPEXReactorCrawlStatus(t *testing.T) { type mockPeer struct { *cmn.BaseService pubKey crypto.PubKey - addr *types.NetAddress + addr *p2p.NetAddress outbound, persistent bool } @@ -355,17 +354,17 @@ func newMockPeer() mockPeer { return mp } -func (mp mockPeer) ID() types.ID { return types.PubKeyToID(mp.pubKey) } +func (mp mockPeer) ID() p2p.ID { return p2p.PubKeyToID(mp.pubKey) } func (mp mockPeer) IsOutbound() bool { return mp.outbound } func (mp mockPeer) IsPersistent() bool { return mp.persistent } -func (mp mockPeer) NodeInfo() types.NodeInfo { - return types.NodeInfo{ +func (mp mockPeer) NodeInfo() p2p.NodeInfo { + return p2p.NodeInfo{ PubKey: mp.pubKey, ListenAddr: mp.addr.DialString(), } } -func (mp mockPeer) Status() tmconn.ConnectionStatus { return tmconn.ConnectionStatus{} } -func (mp mockPeer) Send(byte, interface{}) bool { return false } -func (mp mockPeer) TrySend(byte, interface{}) bool { return false } -func (mp mockPeer) Set(string, interface{}) {} -func (mp mockPeer) Get(string) interface{} { return nil } +func (mp mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } +func (mp mockPeer) Send(byte, interface{}) bool { return false } +func (mp mockPeer) TrySend(byte, interface{}) bool { return false } +func (mp mockPeer) Set(string, interface{}) {} +func (mp mockPeer) Get(string) interface{} { return nil } diff --git a/p2p/switch.go b/p2p/switch.go index c9938374..f29d1b27 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -11,8 +11,7 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" cmn "github.com/tendermint/tmlibs/common" ) @@ -35,7 +34,7 @@ const ( //----------------------------------------------------------------------------- type AddrBook interface { - AddAddress(addr *types.NetAddress, src *types.NetAddress) error + AddAddress(addr *NetAddress, src *NetAddress) error } //----------------------------------------------------------------------------- @@ -51,12 +50,12 @@ type Switch struct { peerConfig *PeerConfig listeners []Listener reactors map[string]Reactor - chDescs []*tmconn.ChannelDescriptor + chDescs []*conn.ChannelDescriptor reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo types.NodeInfo // our node info - nodeKey *types.NodeKey // our node privkey + nodeInfo NodeInfo // our node info + nodeKey *NodeKey // our node privkey filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -69,7 +68,7 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { config: config, peerConfig: DefaultPeerConfig(), reactors: make(map[string]Reactor), - chDescs: make([]*tmconn.ChannelDescriptor, 0), + chDescs: make([]*conn.ChannelDescriptor, 0), reactorsByCh: make(map[byte]Reactor), peers: NewPeerSet(), dialing: cmn.NewCMap(), @@ -143,19 +142,19 @@ func (sw *Switch) IsListening() bool { // SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo types.NodeInfo) { +func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { sw.nodeInfo = nodeInfo } // NodeInfo returns the switch's NodeInfo. // NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() types.NodeInfo { +func (sw *Switch) NodeInfo() NodeInfo { return sw.nodeInfo } // SetNodeKey sets the switch's private key for authenticated encryption. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeKey(nodeKey *types.NodeKey) { +func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { sw.nodeKey = nodeKey } @@ -314,13 +313,13 @@ func (sw *Switch) reconnectToPeer(peer Peer) { // Dialing // IsDialing returns true if the switch is currently dialing the given ID. -func (sw *Switch) IsDialing(id types.ID) bool { +func (sw *Switch) IsDialing(id ID) bool { return sw.dialing.Has(string(id)) } // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent bool) error { - netAddrs, errs := types.NewNetAddressStrings(peers) + netAddrs, errs := NewNetAddressStrings(peers) for _, err := range errs { sw.Logger.Error("Error in peer's address", "err", err) } @@ -357,7 +356,7 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. -func (sw *Switch) DialPeerWithAddress(addr *types.NetAddress, persistent bool) (Peer, error) { +func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { sw.dialing.Set(string(addr.ID), addr) defer sw.dialing.Delete(string(addr.ID)) return sw.addOutboundPeerWithConfig(addr, sw.peerConfig, persistent) @@ -443,7 +442,7 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er // dial the peer; make secret connection; authenticate against the dialed ID; // add the peer. -func (sw *Switch) addOutboundPeerWithConfig(addr *types.NetAddress, config *PeerConfig, persistent bool) (Peer, error) { +func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { sw.Logger.Info("Dialing peer", "address", addr) peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config, persistent) if err != nil { @@ -457,7 +456,7 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *types.NetAddress, config *Peer peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) } else if addr.ID != peer.ID() { peer.CloseConn() - return nil, types.ErrSwitchAuthenticationFailure{addr, peer.ID()} + return nil, ErrSwitchAuthenticationFailure{addr, peer.ID()} } err = sw.addPeer(peer) @@ -478,12 +477,12 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *types.NetAddress, config *Peer func (sw *Switch) addPeer(peer *peer) error { // Avoid self if sw.nodeKey.ID() == peer.ID() { - return types.ErrSwitchConnectToSelf + return ErrSwitchConnectToSelf } // Avoid duplicate if sw.peers.Has(peer.ID()) { - return types.ErrSwitchDuplicatePeer + return ErrSwitchDuplicatePeer } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index ae7e89e7..75f9640b 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -16,8 +16,7 @@ import ( "github.com/tendermint/tmlibs/log" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" ) var ( @@ -30,7 +29,7 @@ func init() { } type PeerMessage struct { - PeerID types.ID + PeerID ID Bytes []byte Counter int } @@ -39,7 +38,7 @@ type TestReactor struct { BaseReactor mtx sync.Mutex - channels []*tmconn.ChannelDescriptor + channels []*conn.ChannelDescriptor peersAdded []Peer peersRemoved []Peer logMessages bool @@ -47,7 +46,7 @@ type TestReactor struct { msgsReceived map[byte][]PeerMessage } -func NewTestReactor(channels []*tmconn.ChannelDescriptor, logMessages bool) *TestReactor { +func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { tr := &TestReactor{ channels: channels, logMessages: logMessages, @@ -58,7 +57,7 @@ func NewTestReactor(channels []*tmconn.ChannelDescriptor, logMessages bool) *Tes return tr } -func (tr *TestReactor) GetChannels() []*tmconn.ChannelDescriptor { +func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { return tr.channels } @@ -102,11 +101,11 @@ func MakeSwitchPair(t testing.TB, initSwitch func(int, *Switch) *Switch) (*Switc func initSwitchFunc(i int, sw *Switch) *Switch { // Make two reactors of two channels each - sw.AddReactor("foo", NewTestReactor([]*tmconn.ChannelDescriptor{ + sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ {ID: byte(0x00), Priority: 10}, {ID: byte(0x01), Priority: 10}, }, true)) - sw.AddReactor("bar", NewTestReactor([]*tmconn.ChannelDescriptor{ + sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ {ID: byte(0x02), Priority: 10}, {ID: byte(0x03), Priority: 10}, }, true)) @@ -163,7 +162,7 @@ func TestConnAddrFilter(t *testing.T) { defer s1.Stop() defer s2.Stop() - c1, c2 := tmconn.NetPipe() + c1, c2 := conn.NetPipe() s1.SetAddrFilter(func(addr net.Addr) error { if addr.String() == c1.RemoteAddr().String() { @@ -199,7 +198,7 @@ func TestConnPubKeyFilter(t *testing.T) { defer s1.Stop() defer s2.Stop() - c1, c2 := tmconn.NetPipe() + c1, c2 := conn.NetPipe() // set pubkey filter s1.SetPubKeyFilter(func(pubkey crypto.PubKey) error { @@ -306,11 +305,11 @@ func BenchmarkSwitches(b *testing.B) { s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { // Make bar reactors of bar channels each - sw.AddReactor("foo", NewTestReactor([]*tmconn.ChannelDescriptor{ + sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ {ID: byte(0x00), Priority: 10}, {ID: byte(0x01), Priority: 10}, }, false)) - sw.AddReactor("bar", NewTestReactor([]*tmconn.ChannelDescriptor{ + sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ {ID: byte(0x02), Priority: 10}, {ID: byte(0x03), Priority: 10}, }, false)) diff --git a/p2p/test_util.go b/p2p/test_util.go index aad6fb23..c4c0fe0b 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -9,8 +9,7 @@ import ( "github.com/tendermint/tmlibs/log" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" ) func AddPeerToSwitch(sw *Switch, peer Peer) { @@ -20,22 +19,22 @@ func AddPeerToSwitch(sw *Switch, peer Peer) { func CreateRandomPeer(outbound bool) *peer { addr, netAddr := CreateRoutableAddr() p := &peer{ - nodeInfo: types.NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: netAddr.DialString(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, - mconn: &tmconn.MConnection{}, + mconn: &conn.MConnection{}, } p.SetLogger(log.TestingLogger().With("peer", addr)) return p } -func CreateRoutableAddr() (addr string, netAddr *types.NetAddress) { +func CreateRoutableAddr() (addr string, netAddr *NetAddress) { for { var err error addr = cmn.Fmt("%X@%v.%v.%v.%v:46656", cmn.RandBytes(20), rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) - netAddr, err = types.NewNetAddressString(addr) + netAddr, err = NewNetAddressString(addr) if err != nil { panic(err) } @@ -78,7 +77,7 @@ func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Swit func Connect2Switches(switches []*Switch, i, j int) { switchI := switches[i] switchJ := switches[j] - c1, c2 := tmconn.NetPipe() + c1, c2 := conn.NetPipe() doneCh := make(chan struct{}) go func() { err := switchI.addPeerWithConnection(c1) @@ -130,13 +129,13 @@ func StartSwitches(switches []*Switch) error { func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { // new switch, add reactors // TODO: let the config be passed in? - nodeKey := &types.NodeKey{ + nodeKey := &NodeKey{ PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } s := NewSwitch(cfg) s.SetLogger(log.TestingLogger()) s = initSwitch(i, s) - s.SetNodeInfo(types.NodeInfo{ + s.SetNodeInfo(NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, diff --git a/p2p/types.go b/p2p/types.go index db7469ec..b11765bb 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -1,12 +1,8 @@ package p2p import ( - "github.com/tendermint/tendermint/p2p/tmconn" - "github.com/tendermint/tendermint/p2p/types" + "github.com/tendermint/tendermint/p2p/conn" ) -type ID = types.ID -type NodeInfo = types.NodeInfo - -type ChannelDescriptor = tmconn.ChannelDescriptor -type ConnectionStatus = tmconn.ConnectionStatus +type ChannelDescriptor = conn.ChannelDescriptor +type ConnectionStatus = conn.ConnectionStatus From 6366eb9d99e8539cd80d6b6cbaedb152658ab7b9 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 3 Jan 2018 10:46:43 +0100 Subject: [PATCH 096/124] Cleanup build and structure --- docs/.python-version | 1 + docs/Makefile | 3 + docs/specification/new-spec/blockchain.md | 53 +++--- docs/specification/new-spec/consensus.md | 217 ++++++++++++---------- docs/specification/new-spec/encoding.md | 71 +++---- docs/specification/new-spec/state.md | 20 +- 6 files changed, 193 insertions(+), 172 deletions(-) create mode 100644 docs/.python-version diff --git a/docs/.python-version b/docs/.python-version new file mode 100644 index 00000000..9bbf4924 --- /dev/null +++ b/docs/.python-version @@ -0,0 +1 @@ +2.7.14 diff --git a/docs/Makefile b/docs/Makefile index f8d1790d..442c9be6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,6 +12,9 @@ BUILDDIR = _build help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +install: + @pip install -r requirements.txt + .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new diff --git a/docs/specification/new-spec/blockchain.md b/docs/specification/new-spec/blockchain.md index ce2529f8..f029d7d4 100644 --- a/docs/specification/new-spec/blockchain.md +++ b/docs/specification/new-spec/blockchain.md @@ -2,7 +2,7 @@ Here we describe the data structures in the Tendermint blockchain and the rules for validating them. -# Data Structures +## Data Structures The Tendermint blockchains consists of a short list of basic data types: `Block`, `Header`, `Vote`, `BlockID`, `Signature`, and `Evidence`. @@ -12,7 +12,7 @@ The Tendermint blockchains consists of a short list of basic data types: A block consists of a header, a list of transactions, a list of votes (the commit), and a list of evidence if malfeasance (ie. signing conflicting votes). -``` +```go type Block struct { Header Header Txs [][]byte @@ -26,7 +26,7 @@ type Block struct { A block header contains metadata about the block and about the consensus, as well as commitments to the data in the current block, the previous block, and the results returned by the application: -``` +```go type Header struct { // block metadata Version string // Version string @@ -66,7 +66,7 @@ the block during consensus, is the Merkle root of the complete serialized block cut into parts. The `BlockID` includes these two hashes, as well as the number of parts. -``` +```go type BlockID struct { Hash []byte Parts PartsHeader @@ -83,7 +83,7 @@ type PartsHeader struct { A vote is a signed message from a validator for a particular block. The vote includes information about the validator signing it. -``` +```go type Vote struct { Timestamp int64 Address []byte @@ -96,7 +96,6 @@ type Vote struct { } ``` - There are two types of votes: a prevote has `vote.Type == 1` and a precommit has `vote.Type == 2`. @@ -111,7 +110,7 @@ Currently, Tendermint supports Ed25519 and Secp256k1. An ED25519 signature has `Type == 0x1`. It looks like: -``` +```go // Implements Signature type Ed25519Signature struct { Type int8 = 0x1 @@ -125,7 +124,7 @@ where `Signature` is the 64 byte signature. A `Secp256k1` signature has `Type == 0x2`. It looks like: -``` +```go // Implements Signature type Secp256k1Signature struct { Type int8 = 0x2 @@ -135,7 +134,7 @@ type Secp256k1Signature struct { where `Signature` is the DER encoded signature, ie: -``` +```hex 0x30 <0x02> 0x2 . ``` @@ -143,7 +142,7 @@ where `Signature` is the DER encoded signature, ie: TODO -# Validation +## Validation Here we describe the validation rules for every element in a block. Blocks which do not satisfy these rules are considered invalid. @@ -159,7 +158,7 @@ and other results from the application. Elements of an object are accessed as expected, ie. `block.Header`. See [here](state.md) for the definition of `state`. -## Header +### Header A Header is valid if its corresponding fields are valid. @@ -173,7 +172,7 @@ Arbitrary constant string. ### Height -``` +```go block.Header.Height > 0 block.Header.Height == prevBlock.Header.Height + 1 ``` @@ -190,7 +189,7 @@ block being voted on. ### NumTxs -``` +```go block.Header.NumTxs == len(block.Txs) ``` @@ -198,7 +197,7 @@ Number of transactions included in the block. ### TxHash -``` +```go block.Header.TxHash == SimpleMerkleRoot(block.Txs) ``` @@ -206,7 +205,7 @@ Simple Merkle root of the transactions in the block. ### LastCommitHash -``` +```go block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit) ``` @@ -217,7 +216,7 @@ The first block has `block.Header.LastCommitHash == []byte{}` ### TotalTxs -``` +```go block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs ``` @@ -227,7 +226,7 @@ The first block has `block.Header.TotalTxs = block.Header.NumberTxs`. ### LastBlockID -``` +```go prevBlockParts := MakeParts(prevBlock, state.LastConsensusParams.BlockGossip.BlockPartSize) block.Header.LastBlockID == BlockID { Hash: SimpleMerkleRoot(prevBlock.Header), @@ -245,7 +244,7 @@ The first block has `block.Header.LastBlockID == BlockID{}`. ### ResultsHash -``` +```go block.ResultsHash == SimpleMerkleRoot(state.LastResults) ``` @@ -255,7 +254,7 @@ The first block has `block.Header.ResultsHash == []byte{}`. ### AppHash -``` +```go block.AppHash == state.AppHash ``` @@ -265,7 +264,7 @@ The first block has `block.Header.AppHash == []byte{}`. ### ValidatorsHash -``` +```go block.ValidatorsHash == SimpleMerkleRoot(state.Validators) ``` @@ -275,7 +274,7 @@ May be updated by the application. ### ConsensusParamsHash -``` +```go block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams) ``` @@ -284,7 +283,7 @@ May be updated by the application. ### Proposer -``` +```go block.Header.Proposer in state.Validators ``` @@ -296,7 +295,7 @@ and we do not track the initial round the block was proposed. ### EvidenceHash -``` +```go block.EvidenceHash == SimpleMerkleRoot(block.Evidence) ``` @@ -310,7 +309,7 @@ Arbitrary length array of arbitrary length byte-arrays. The first height is an exception - it requires the LastCommit to be empty: -``` +```go if block.Header.Height == 1 { len(b.LastCommit) == 0 } @@ -318,7 +317,7 @@ if block.Header.Height == 1 { Otherwise, we require: -``` +```go len(block.LastCommit) == len(state.LastValidators) talliedVotingPower := 0 for i, vote := range block.LastCommit{ @@ -356,7 +355,7 @@ For signing, votes are encoded in JSON, and the ChainID is included, in the form We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the CanonicalSignBytes using the given ChainID: -``` +```go func (v Vote) Verify(chainID string, pubKey PubKey) bool { return pubKey.Verify(v.Signature, CanonicalSignBytes(chainID, v)) } @@ -384,7 +383,7 @@ Once a block is validated, it can be executed against the state. The state follows the recursive equation: -``` +```go app = NewABCIApp state(1) = InitialState state(h+1) <- Execute(state(h), app, block(h)) diff --git a/docs/specification/new-spec/consensus.md b/docs/specification/new-spec/consensus.md index 5c681056..ffa2bd5d 100644 --- a/docs/specification/new-spec/consensus.md +++ b/docs/specification/new-spec/consensus.md @@ -1,197 +1,210 @@ -# Tendermint Consensus +# Tendermint Consensus Reactor -Tendermint consensus is a distributed protocol executed by validator processes to agree on -the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where -each round is a try to reach agreement on the next block. A round starts by having a dedicated -process (called proposer) suggesting to other processes what should be the next block with +Tendermint Consensus is a distributed protocol executed by validator processes to agree on +the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where +each round is a try to reach agreement on the next block. A round starts by having a dedicated +process (called proposer) suggesting to other processes what should be the next block with the `ProposalMessage`. -The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote messages, prevote -and precommit votes). Note that a proposal message is just a suggestion what the next block should be; a -validator might vote with a `VoteMessage` for a different block. If in some round, enough number -of processes vote for the same block, then this block is committed and later added to the blockchain. +The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote messages, prevote +and precommit votes). Note that a proposal message is just a suggestion what the next block should be; a +validator might vote with a `VoteMessage` for a different block. If in some round, enough number +of processes vote for the same block, then this block is committed and later added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the validator. -The internals of the protocol and how it ensures safety and liveness properties are +The internals of the protocol and how it ensures safety and liveness properties are explained [here](https://github.com/tendermint/spec). For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block -as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they -reach agreement on the `BlockID` (see `BlockID` definition in [Blockchain](blockchain.md) section) +as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they +reach agreement on the `BlockID` (see `BlockID` definition in [Blockchain](blockchain.md) section) that uniquely identifies each block. The block itself is disseminated to validator processes using -peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a -number of block parts, that are then gossiped between processes using `BlockPartMessage`. +peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a +number of block parts, that are then gossiped between processes using `BlockPartMessage`. -Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected -only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers -all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can +Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected +only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers +all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can reach agreement on some block, and also obtain the content of the chosen block (block parts). As part of -the gossiping protocol, processes also send auxiliary messages that inform peers about the -executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and +the gossiping protocol, processes also send auxiliary messages that inform peers about the +executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and also messages that inform peers what votes the process has seen (`HasVoteMessage`, `VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping protocol -to determine what messages a process should send to its peers. - +to determine what messages a process should send to its peers. + We now describe the content of each message exchanged during Tendermint consensus protocol. ## ProposalMessage -ProposalMessage is sent when a new block is proposed. It is a suggestion of what the + +ProposalMessage is sent when a new block is proposed. It is a suggestion of what the next block in the blockchain should be. -``` + +```go type ProposalMessage struct { - Proposal Proposal + Proposal Proposal } ``` + ### Proposal + Proposal contains height and round for which this proposal is made, BlockID as a unique identifier of proposed block, timestamp, and two fields (POLRound and POLBlockID) that are needed for termination of the consensus. -The message is signed by the validator private key. +The message is signed by the validator private key. -``` +```go type Proposal struct { - Height int64 - Round int - Timestamp Time - BlockID BlockID - POLRound int - POLBlockID BlockID - Signature Signature + Height int64 + Round int + Timestamp Time + BlockID BlockID + POLRound int + POLBlockID BlockID + Signature Signature } ``` NOTE: In the current version of the Tendermint, the consensus value in proposal is represented with -PartSetHeader, and with BlockID in vote message. It should be aligned as suggested in this spec as -BlockID contains PartSetHeader. +PartSetHeader, and with BlockID in vote message. It should be aligned as suggested in this spec as +BlockID contains PartSetHeader. ## VoteMessage + VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the current round). -Vote is defined in [Blockchain](blockchain.md) section and contains validator's information (validator address -and index), height and round for which the vote is sent, vote type, blockID if process vote for some +Vote is defined in [Blockchain](blockchain.md) section and contains validator's information (validator address +and index), height and round for which the vote is sent, vote type, blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The message is signed by the validator private key. -``` + +```go type VoteMessage struct { - Vote Vote + Vote Vote } ``` ## BlockPartMessage -BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round -and the block part. -``` +BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round +and the block part. + +```go type BlockPartMessage struct { - Height int64 - Round int - Part Part + Height int64 + Round int + Part Part } ``` -## ProposalHeartbeatMessage -ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions +## ProposalHeartbeatMessage + +ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions to be able to create a next block proposal. -``` +```go type ProposalHeartbeatMessage struct { Heartbeat Heartbeat } ``` ### Heartbeat -Heartbeat contains validator information (address and index), -height, round and sequence number. It is signed by the private key of the validator. -``` +Heartbeat contains validator information (address and index), +height, round and sequence number. It is signed by the private key of the validator. + +```go type Heartbeat struct { - ValidatorAddress []byte - ValidatorIndex int - Height int64 - Round int - Sequence int - Signature Signature + ValidatorAddress []byte + ValidatorIndex int + Height int64 + Round int + Sequence int + Signature Signature } ``` ## NewRoundStepMessage -NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. It is -used in the gossip part of the Tendermint protocol to inform peers about a current height/round/step + +NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. It is +used in the gossip part of the Tendermint protocol to inform peers about a current height/round/step a process is in. -``` +```go type NewRoundStepMessage struct { - Height int64 - Round int - Step RoundStepType - SecondsSinceStartTime int - LastCommitRound int + Height int64 + Round int + Step RoundStepType + SecondsSinceStartTime int + LastCommitRound int } ``` ## CommitStepMessage + CommitStepMessage is sent when an agreement on some block is reached. It contains height for which agreement is reached, block parts header that describes the decided block and is used to obtain all block parts, -and a bit array of the block parts a process currently has, so its peers can know what parts -it is missing so they can send them. +and a bit array of the block parts a process currently has, so its peers can know what parts +it is missing so they can send them. -``` +```go type CommitStepMessage struct { - Height int64 - BlockID BlockID - BlockParts BitArray + Height int64 + BlockID BlockID + BlockParts BitArray } ``` -TODO: We use BlockID instead of BlockPartsHeader (in current implementation) for symmetry. +TODO: We use BlockID instead of BlockPartsHeader (in current implementation) for symmetry. ## ProposalPOLMessage + ProposalPOLMessage is sent when a previous block is re-proposed. It is used to inform peers in what round the process learned for this block (ProposalPOLRound), -and what prevotes for the re-proposed block the process has. - -``` +and what prevotes for the re-proposed block the process has. + +```go type ProposalPOLMessage struct { - Height int64 - ProposalPOLRound int - ProposalPOL BitArray + Height int64 + ProposalPOLRound int + ProposalPOL BitArray } ``` - ## HasVoteMessage -HasVoteMessage is sent to indicate that a particular vote has been received. It contains height, -round, vote type and the index of the validator that is the originator of the corresponding vote. -``` +HasVoteMessage is sent to indicate that a particular vote has been received. It contains height, +round, vote type and the index of the validator that is the originator of the corresponding vote. + +```go type HasVoteMessage struct { - Height int64 - Round int - Type byte - Index int + Height int64 + Round int + Type byte + Index int } ``` ## VoteSetMaj23Message -VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. -It contains height, round, vote type and the BlockID. -``` +VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. +It contains height, round, vote type and the BlockID. + +```go type VoteSetMaj23Message struct { - Height int64 - Round int - Type byte - BlockID BlockID + Height int64 + Round int + Type byte + BlockID BlockID } ``` ## VoteSetBitsMessage -VoteSetBitsMessage is sent to communicate the bit-array of votes a process has seen for a given -BlockID. It contains height, round, vote type, BlockID and a bit array of + +VoteSetBitsMessage is sent to communicate the bit-array of votes a process has seen for a given +BlockID. It contains height, round, vote type, BlockID and a bit array of the votes a process has. -``` +```go type VoteSetBitsMessage struct { - Height int64 - Round int - Type byte - BlockID BlockID - Votes BitArray + Height int64 + Round int + Type byte + BlockID BlockID + Votes BitArray } ``` - diff --git a/docs/specification/new-spec/encoding.md b/docs/specification/new-spec/encoding.md index f401dde7..205b8574 100644 --- a/docs/specification/new-spec/encoding.md +++ b/docs/specification/new-spec/encoding.md @@ -2,9 +2,11 @@ ## Binary Serialization (TMBIN) -Tendermint aims to encode data structures in a manner similar to how the corresponding Go structs are laid out in memory. +Tendermint aims to encode data structures in a manner similar to how the corresponding Go structs +are laid out in memory. Variable length items are length-prefixed. -While the encoding was inspired by Go, it is easily implemented in other languages as well given its intuitive design. +While the encoding was inspired by Go, it is easily implemented in other languages as well given its +intuitive design. XXX: This is changing to use real varints and 4-byte-prefixes. See https://github.com/tendermint/go-wire/tree/sdk2. @@ -19,7 +21,7 @@ Negative integers are encoded via twos-complement. Examples: -``` +```go encode(uint8(6)) == [0x06] encode(uint32(6)) == [0x00, 0x00, 0x00, 0x06] @@ -36,10 +38,9 @@ Negative integers are encoded by flipping the leading bit of the length-prefix t Zero is encoded as `0x00`. It is not length-prefixed. - Examples: -``` +```go encode(uint(6)) == [0x01, 0x06] encode(uint(70000)) == [0x03, 0x01, 0x11, 0x70] @@ -58,7 +59,7 @@ The empty string is encoded as `0x00`. It is not length-prefixed. Examples: -``` +```go encode("") == [0x00] encode("a") == [0x01, 0x01, 0x61] encode("hello") == [0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F] @@ -72,7 +73,7 @@ There is no length-prefix. Examples: -``` +```go encode([4]int8{1, 2, 3, 4}) == [0x01, 0x02, 0x03, 0x04] encode([4]int16{1, 2, 3, 4}) == [0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04] encode([4]int{1, 2, 3, 4}) == [0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x04] @@ -81,14 +82,15 @@ encode([2]string{"abc", "efg"}) == [0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x ### Slices (variable length) -An encoded variable-length array is a length prefix followed by the concatenation of the encoding of its elements. +An encoded variable-length array is a length prefix followed by the concatenation of the encoding of +its elements. The length-prefix is itself encoded as an `int`. An empty slice is encoded as `0x00`. It is not length-prefixed. Examples: -``` +```go encode([]int8{}) == [0x00] encode([]int8{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x02, 0x03, 0x04] encode([]int16{1, 2, 3, 4}) == [0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04] @@ -97,10 +99,11 @@ encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x ``` ### BitArray + BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode value of each array element. -``` +```go type BitArray struct { Bits int Elems []uint64 @@ -116,7 +119,7 @@ Times before then are invalid. Examples: -``` +```go encode(time.Time("Jan 1 00:00:00 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] encode(time.Time("Jan 1 00:00:01 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x3B, 0x9A, 0xCA, 0x00] // 1,000,000,000 ns encode(time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")) == [0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00] @@ -129,7 +132,7 @@ There is no length-prefix. Examples: -``` +```go type MyStruct struct{ A int B string @@ -139,7 +142,6 @@ encode(MyStruct{4, "hello", time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")}) == [0x01, 0x04, 0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00] ``` - ## Merkle Trees Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure. @@ -148,23 +150,24 @@ RIPEMD160 is always used as the hashing function. The function `SimpleMerkleRoot` is a simple recursive function defined as follows: -``` +```go func SimpleMerkleRoot(hashes [][]byte) []byte{ - switch len(hashes) { - case 0: - return nil - case 1: - return hashes[0] - default: - left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2]) - right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:]) - return RIPEMD160(append(left, right)) - } + switch len(hashes) { + case 0: + return nil + case 1: + return hashes[0] + default: + left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2]) + right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:]) + return RIPEMD160(append(left, right)) + } } ``` Note we abuse notion and call `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`. -For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to field name and then hashing them. +For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to +field name and then hashing them. For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements. ## JSON (TMJSON) @@ -172,10 +175,12 @@ For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `str Signed messages (eg. votes, proposals) in the consensus are encoded in TMJSON, rather than TMBIN. TMJSON is JSON where `[]byte` are encoded as uppercase hex, rather than base64. -When signing, the elements of a message are sorted by key and the sorted message is embedded in an outer JSON that includes a `chain_id` field. -We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look like: +When signing, the elements of a message are sorted by key and the sorted message is embedded in an +outer JSON that includes a `chain_id` field. +We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look +like: -``` +```json {"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2} ``` @@ -187,16 +192,16 @@ Note how the fields within each level are sorted. TMBIN encode an object and slice it into parts. -``` +```go MakeParts(object, partSize) ``` ### Part -``` +```go type Part struct { - Index int - Bytes byte[] - Proof byte[] + Index int + Bytes byte[] + Proof byte[] } ``` diff --git a/docs/specification/new-spec/state.md b/docs/specification/new-spec/state.md index 1d790027..3594de57 100644 --- a/docs/specification/new-spec/state.md +++ b/docs/specification/new-spec/state.md @@ -2,13 +2,13 @@ ## State -The state contains information whose cryptographic digest is included in block headers, -and thus is necessary for validating new blocks. -For instance, the Merkle root of the results from executing the previous block, or the Merkle root of the current validators. -While neither the results of transactions now the validators are ever included in the blockchain itself, -the Merkle roots are, and hence we need a separate data structure to track them. +The state contains information whose cryptographic digest is included in block headers, and thus is +necessary for validating new blocks. For instance, the Merkle root of the results from executing the +previous block, or the Merkle root of the current validators. While neither the results of +transactions now the validators are ever included in the blockchain itself, the Merkle roots are, +and hence we need a separate data structure to track them. -``` +```go type State struct { LastResults []Result AppHash []byte @@ -22,7 +22,7 @@ type State struct { ### Result -``` +```go type Result struct { Code uint32 Data []byte @@ -46,7 +46,7 @@ represented in the tags. A validator is an active participant in the consensus with a public key and a voting power. Validator's also contain an address which is derived from the PubKey: -``` +```go type Validator struct { Address []byte PubKey PubKey @@ -59,7 +59,7 @@ so that there is a canonical order for computing the SimpleMerkleRoot. We also define a `TotalVotingPower` function, to return the total voting power: -``` +```go func TotalVotingPower(vals []Validators) int64{ sum := 0 for v := range vals{ @@ -82,7 +82,7 @@ TODO: We define an `Execute` function that takes a state and a block, executes the block against the application, and returns an updated state. -``` +```go Execute(s State, app ABCIApp, block Block) State { abciResponses := app.ApplyBlock(block) From a30315276b4e59bd6253637d8d5e7e8c4a610ca4 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 3 Jan 2018 11:29:19 +0100 Subject: [PATCH 097/124] Formatting and documentation --- blockchain/pool.go | 9 ++- blockchain/pool_test.go | 5 +- blockchain/reactor.go | 22 ++++--- blockchain/reactor_test.go | 7 ++- blockchain/store.go | 4 +- blockchain/store_test.go | 34 ++++++---- .../new-spec/blockchain_reactor.md | 17 +++++ docs/specification/new-spec/consensus.md | 62 ++++++++++--------- 8 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 docs/specification/new-spec/blockchain_reactor.md diff --git a/blockchain/pool.go b/blockchain/pool.go index f3148e6c..164d3b3b 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -5,15 +5,15 @@ import ( "sync" "time" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" flow "github.com/tendermint/tmlibs/flowrate" "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/types" ) /* - eg, L = latency = 0.1s P = num peers = 10 FN = num full nodes @@ -23,7 +23,6 @@ eg, L = latency = 0.1s B/S = CB/P/BS = 12.8 blocks/s 12.8 * 0.1 = 1.28 blocks on conn - */ const ( @@ -503,7 +502,7 @@ func (bpr *bpRequester) requestRoutine() { OUTER_LOOP: for { // Pick a peer to send request to. - var peer *bpPeer = nil + var peer *bpPeer PICK_PEER_LOOP: for { if !bpr.IsRunning() || !bpr.pool.IsRunning() { diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 0cdbc3a9..ce16899a 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -5,10 +5,11 @@ import ( "testing" "time" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/types" ) func init() { diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 701e04f6..f8b1fc52 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -8,11 +8,13 @@ import ( "time" wire "github.com/tendermint/go-wire" + + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" ) const ( @@ -56,9 +58,12 @@ type BlockchainReactor struct { } // NewBlockchainReactor returns new reactor instance. -func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *BlockStore, fastSync bool) *BlockchainReactor { +func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *BlockStore, + fastSync bool) *BlockchainReactor { + if state.LastBlockHeight != store.Height() { - cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight, store.Height())) + cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight, + store.Height())) } requestsCh := make(chan BlockRequest, defaultChannelCapacity) @@ -138,7 +143,9 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { // if we have it. Otherwise, we'll respond saying we don't have it. // According to the Tendermint spec, if all nodes are honest, // no node should be requesting for a block that's non-existent. -func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, src p2p.Peer) (queued bool) { +func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, + src p2p.Peer) (queued bool) { + block := bcR.store.LoadBlock(msg.Height) if block != nil { msg := &bcBlockResponseMessage{Block: block} @@ -293,7 +300,7 @@ FOR_LOOP: // TODO This is bad, are we zombie? cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) } - blocksSynced += 1 + blocksSynced++ // update the consensus params bcR.updateConsensusParams(state.ConsensusParams) @@ -315,7 +322,8 @@ FOR_LOOP: // BroadcastStatusRequest broadcasts `BlockStore` height. func (bcR *BlockchainReactor) BroadcastStatusRequest() error { - bcR.Switch.Broadcast(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusRequestMessage{bcR.store.Height()}}) + bcR.Switch.Broadcast(BlockchainChannel, + struct{ BlockchainMessage }{&bcStatusRequestMessage{bcR.store.Height()}}) return nil } diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 06f6c36c..6f5b14ff 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -4,6 +4,7 @@ import ( "testing" wire "github.com/tendermint/go-wire" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -28,7 +29,8 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe // Make the blockchainReactor itself fastSync := true var nilApp proxy.AppConnConsensus - blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp, types.MockMempool{}, types.MockEvidencePool{}) + blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp, + types.MockMempool{}, types.MockEvidencePool{}) bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor.SetLogger(logger.With("module", "blockchain")) @@ -130,7 +132,8 @@ func newbcrTestPeer(id p2p.ID) *bcrTestPeer { func (tp *bcrTestPeer) lastValue() interface{} { return <-tp.ch } func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { - if _, ok := value.(struct{ BlockchainMessage }).BlockchainMessage.(*bcStatusResponseMessage); ok { + if _, ok := value.(struct{ BlockchainMessage }). + BlockchainMessage.(*bcStatusResponseMessage); ok { // Discard status response messages since they skew our results // We only want to deal with: // + bcBlockResponseMessage diff --git a/blockchain/store.go b/blockchain/store.go index 1033999f..91d2b220 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -8,9 +8,11 @@ import ( "sync" wire "github.com/tendermint/go-wire" - "github.com/tendermint/tendermint/types" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" + + "github.com/tendermint/tendermint/types" ) /* diff --git a/blockchain/store_test.go b/blockchain/store_test.go index 1fd88dac..933329c4 100644 --- a/blockchain/store_test.go +++ b/blockchain/store_test.go @@ -13,9 +13,11 @@ import ( "github.com/stretchr/testify/require" wire "github.com/tendermint/go-wire" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/tendermint/types" ) func TestLoadBlockStoreStateJSON(t *testing.T) { @@ -104,7 +106,8 @@ var ( partSet = block.MakePartSet(2) part1 = partSet.GetPart(0) part2 = partSet.GetPart(1) - seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} + seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, + Timestamp: time.Now().UTC()}}} ) // TODO: This test should be simplified ... @@ -124,7 +127,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // save a block block := makeBlock(bs.Height()+1, state) validPartSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} + seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, + Timestamp: time.Now().UTC()}}} bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") @@ -143,7 +147,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // End of setup, test data - commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} + commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10, + Timestamp: time.Now().UTC()}}} tuples := []struct { block *types.Block parts *types.PartSet @@ -263,7 +268,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus")) } bCommit := bs.LoadBlockCommit(commitHeight) - return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, meta: bBlockMeta}, nil + return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, + meta: bBlockMeta}, nil }) if subStr := tuple.wantPanic; subStr != "" { @@ -290,10 +296,12 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { continue } if tuple.eraseSeenCommitInDB { - assert.Nil(t, qua.seenCommit, "erased the seenCommit in the DB hence we should get back a nil seenCommit") + assert.Nil(t, qua.seenCommit, + "erased the seenCommit in the DB hence we should get back a nil seenCommit") } if tuple.eraseCommitInDB { - assert.Nil(t, qua.commit, "erased the commit in the DB hence we should get back a nil commit") + assert.Nil(t, qua.commit, + "erased the commit in the DB hence we should get back a nil commit") } } } @@ -331,7 +339,8 @@ func TestLoadBlockPart(t *testing.T) { gotPart, _, panicErr := doFn(loadPart) require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, res, "a properly saved block should return a proper block") - require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(), "expecting successful retrieval of previously saved block") + require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(), + "expecting successful retrieval of previously saved block") } func TestLoadBlockMeta(t *testing.T) { @@ -360,7 +369,8 @@ func TestLoadBlockMeta(t *testing.T) { gotMeta, _, panicErr := doFn(loadMeta) require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ") - require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta), "expecting successful retrieval of previously saved blockMeta") + require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta), + "expecting successful retrieval of previously saved blockMeta") } func TestBlockFetchAtHeight(t *testing.T) { @@ -369,13 +379,15 @@ func TestBlockFetchAtHeight(t *testing.T) { block := makeBlock(bs.Height()+1, state) partSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} + seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, + Timestamp: time.Now().UTC()}}} bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") blockAtHeight := bs.LoadBlock(bs.Height()) - require.Equal(t, block.Hash(), blockAtHeight.Hash(), "expecting a successful load of the last saved block") + require.Equal(t, block.Hash(), blockAtHeight.Hash(), + "expecting a successful load of the last saved block") blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1) require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1") diff --git a/docs/specification/new-spec/blockchain_reactor.md b/docs/specification/new-spec/blockchain_reactor.md new file mode 100644 index 00000000..20493220 --- /dev/null +++ b/docs/specification/new-spec/blockchain_reactor.md @@ -0,0 +1,17 @@ +# Blockchain Reactor + +The Blockchain Reactor's high level responsibility is to maintain connection to a reasonable number +of peers in the network, request blocks from them or provide them with blocks, validate and persist +the blocks to disk and play blocks to the ABCI app. + +## Block Reactor + +* coordinates synching with other peers + +## Block Pool + +* maintain connections to other peers + +## Block Store + +* persists blocks to disk diff --git a/docs/specification/new-spec/consensus.md b/docs/specification/new-spec/consensus.md index ffa2bd5d..1f311c44 100644 --- a/docs/specification/new-spec/consensus.md +++ b/docs/specification/new-spec/consensus.md @@ -5,30 +5,31 @@ the next block to be added to the Tendermint blockchain. The protocol proceeds i each round is a try to reach agreement on the next block. A round starts by having a dedicated process (called proposer) suggesting to other processes what should be the next block with the `ProposalMessage`. -The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote messages, prevote -and precommit votes). Note that a proposal message is just a suggestion what the next block should be; a -validator might vote with a `VoteMessage` for a different block. If in some round, enough number -of processes vote for the same block, then this block is committed and later added to the blockchain. -`ProposalMessage` and `VoteMessage` are signed by the private key of the validator. -The internals of the protocol and how it ensures safety and liveness properties are +The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote +messages, prevote and precommit votes). Note that a proposal message is just a suggestion what the +next block should be; a validator might vote with a `VoteMessage` for a different block. If in some +round, enough number of processes vote for the same block, then this block is committed and later +added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the +validator. The internals of the protocol and how it ensures safety and liveness properties are explained [here](https://github.com/tendermint/spec). -For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block -as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they -reach agreement on the `BlockID` (see `BlockID` definition in [Blockchain](blockchain.md) section) -that uniquely identifies each block. The block itself is disseminated to validator processes using -peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a -number of block parts, that are then gossiped between processes using `BlockPartMessage`. +For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the +block as the block size is big, i.e., they don't embed the block inside `Proposal` and +`VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in +[Blockchain](blockchain.md) section) that uniquely identifies each block. The block itself is +disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a +proposer first splitting a block into a number of block parts, that are then gossiped between +processes using `BlockPartMessage`. Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can -reach agreement on some block, and also obtain the content of the chosen block (block parts). As part of -the gossiping protocol, processes also send auxiliary messages that inform peers about the +reach agreement on some block, and also obtain the content of the chosen block (block parts). As +part of the gossiping protocol, processes also send auxiliary messages that inform peers about the executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and also messages that inform peers what votes the process has seen (`HasVoteMessage`, -`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping protocol -to determine what messages a process should send to its peers. +`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping +protocol to determine what messages a process should send to its peers. We now describe the content of each message exchanged during Tendermint consensus protocol. @@ -45,9 +46,9 @@ type ProposalMessage struct { ### Proposal -Proposal contains height and round for which this proposal is made, BlockID as a unique identifier of proposed -block, timestamp, and two fields (POLRound and POLBlockID) that are needed for termination of the consensus. -The message is signed by the validator private key. +Proposal contains height and round for which this proposal is made, BlockID as a unique identifier +of proposed block, timestamp, and two fields (POLRound and POLBlockID) that are needed for +termination of the consensus. The message is signed by the validator private key. ```go type Proposal struct { @@ -67,10 +68,11 @@ BlockID contains PartSetHeader. ## VoteMessage -VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the current round). -Vote is defined in [Blockchain](blockchain.md) section and contains validator's information (validator address -and index), height and round for which the vote is sent, vote type, blockID if process vote for some -block (`nil` otherwise) and a timestamp when the vote is sent. The message is signed by the validator private key. +VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the +current round). Vote is defined in [Blockchain](blockchain.md) section and contains validator's +information (validator address and index), height and round for which the vote is sent, vote type, +blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The +message is signed by the validator private key. ```go type VoteMessage struct { @@ -120,9 +122,9 @@ type Heartbeat struct { ## NewRoundStepMessage -NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. It is -used in the gossip part of the Tendermint protocol to inform peers about a current height/round/step -a process is in. +NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. +It is used in the gossip part of the Tendermint protocol to inform peers about a current +height/round/step a process is in. ```go type NewRoundStepMessage struct { @@ -136,10 +138,10 @@ type NewRoundStepMessage struct { ## CommitStepMessage -CommitStepMessage is sent when an agreement on some block is reached. It contains height for which agreement -is reached, block parts header that describes the decided block and is used to obtain all block parts, -and a bit array of the block parts a process currently has, so its peers can know what parts -it is missing so they can send them. +CommitStepMessage is sent when an agreement on some block is reached. It contains height for which +agreement is reached, block parts header that describes the decided block and is used to obtain all +block parts, and a bit array of the block parts a process currently has, so its peers can know what +parts it is missing so they can send them. ```go type CommitStepMessage struct { From 940145b36871b3ac4255d677f61b94af9a3862bf Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 3 Jan 2018 11:57:42 +0100 Subject: [PATCH 098/124] Bullet points for reactor and poolRoutine --- blockchain/reactor.go | 16 +++++++++------- .../specification/new-spec/blockchain_reactor.md | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index f8b1fc52..c92064d7 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -49,12 +49,13 @@ type BlockchainReactor struct { // immutable initialState sm.State - blockExec *sm.BlockExecutor - store *BlockStore - pool *BlockPool - fastSync bool - requestsCh chan BlockRequest - timeoutsCh chan p2p.ID + blockExec *sm.BlockExecutor + store *BlockStore + pool *BlockPool + fastSync bool + + requestsCh <-chan BlockRequest + timeoutsCh <-chan p2p.ID } // NewBlockchainReactor returns new reactor instance. @@ -127,7 +128,8 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { // AddPeer implements Reactor by sending our state to peer. func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { - if !peer.Send(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) { + if !peer.Send(BlockchainChannel, + struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) { // doing nothing, will try later in `poolRoutine` } // peer is added to the pool once we receive the first diff --git a/docs/specification/new-spec/blockchain_reactor.md b/docs/specification/new-spec/blockchain_reactor.md index 20493220..ded1bc27 100644 --- a/docs/specification/new-spec/blockchain_reactor.md +++ b/docs/specification/new-spec/blockchain_reactor.md @@ -6,7 +6,21 @@ the blocks to disk and play blocks to the ABCI app. ## Block Reactor -* coordinates synching with other peers +* coordinates the pool for synching +* coordinates the store for persistence +* coordinates the playing of blocks towards the app using a sm.BlockExecutor +* handles switching between fastsync and consensus +* it is a p2p.BaseReactor +* starts the pool.Start() and its poolRoutine() +* registers all the concrete types and interfaces for serialisation + +### poolRoutine + +* requests blocks from a specific peer based on the pool +* periodically asks for status updates +* tries to switch to consensus +* tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores + them on disk ## Block Pool From 0eb85161aa6c5ddece4979e886babff4d28754fe Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 3 Jan 2018 14:39:00 +0100 Subject: [PATCH 099/124] More specification --- blockchain/reactor.go | 6 +- .../new-spec/blockchain_reactor.md | 69 ++++++++++++++++--- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index c92064d7..ee84a794 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -182,7 +182,8 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes)) case *bcStatusRequestMessage: // Send peer our state. - queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) + queued := src.TrySend(BlockchainChannel, + struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) if !queued { // sorry } @@ -300,7 +301,8 @@ FOR_LOOP: state, err = bcR.blockExec.ApplyBlock(state, firstID, first) if err != nil { // TODO This is bad, are we zombie? - cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) + cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v", + first.Height, first.Hash(), err)) } blocksSynced++ diff --git a/docs/specification/new-spec/blockchain_reactor.md b/docs/specification/new-spec/blockchain_reactor.md index ded1bc27..7d7dcb38 100644 --- a/docs/specification/new-spec/blockchain_reactor.md +++ b/docs/specification/new-spec/blockchain_reactor.md @@ -1,12 +1,44 @@ # Blockchain Reactor -The Blockchain Reactor's high level responsibility is to maintain connection to a reasonable number -of peers in the network, request blocks from them or provide them with blocks, validate and persist -the blocks to disk and play blocks to the ABCI app. +The Blockchain Reactor's high level responsibility is to request blocks from peers or provide them +with blocks, validate and persist the blocks to disk and play blocks to the ABCI app. + +## Message Types + +```go +const ( + msgTypeBlockRequest = byte(0x10) + msgTypeBlockResponse = byte(0x11) + msgTypeNoBlockResponse = byte(0x12) + msgTypeStatusResponse = byte(0x20) + msgTypeStatusRequest = byte(0x21) +) +``` + +```go +type bcBlockRequestMessage struct { + Height int64 +} + +type bcNoBlockResponseMessage struct { + Height int64 +} + +type bcBlockResponseMessage struct { + Block *types.Block +} + +type bcStatusRequestMessage struct { + Height int64 + +type bcStatusResponseMessage struct { + Height int64 +} +``` ## Block Reactor -* coordinates the pool for synching +* coordinates the pool for syncing * coordinates the store for persistence * coordinates the playing of blocks towards the app using a sm.BlockExecutor * handles switching between fastsync and consensus @@ -16,16 +48,37 @@ the blocks to disk and play blocks to the ABCI app. ### poolRoutine -* requests blocks from a specific peer based on the pool -* periodically asks for status updates -* tries to switch to consensus +* listens to these channels: + * pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends + a &bcBlockRequestMessage for a specific height + * pool signals timeout of a specific peer by posting to timeoutsCh + * switchToConsensusTicker to periodically try and switch to consensus + * trySyncTicker to periodically check if we have fallen behind and then catch-up sync + * if there aren't any new blocks available on the pool it skips syncing * tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores them on disk +* implements Receive which is called by the switch/peer + * calls AddBlock on the pool when it receives a new block from a peer ## Block Pool -* maintain connections to other peers +* responsible for downloading blocks from peers +* makeRequestersRoutine() + * removes timeout peers + * starts new requesters by calling makeNextRequester() +* requestRoutine(): + * picks a peer and sends the request, then blocks until: + * pool is stopped by listening to pool.Quit + * requester is stopped by listening to Quit + * request is redone + * we receive a block + * gotBlockCh is strange ## Block Store * persists blocks to disk + +# TODO + +* How does the switch from bcR to conR happen? Does conR persist blocks to disk too? +* What is the interaction between the consensus and blockchain reactors? From 2c6ed302b7fa91371be68e5cc06be655dea16246 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 4 Jan 2018 11:52:53 -0500 Subject: [PATCH 100/124] minor changes [ci skip] --- docs/specification/new-spec/blockchain_reactor.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/specification/new-spec/blockchain_reactor.md b/docs/specification/new-spec/blockchain_reactor.md index 7d7dcb38..5633eb05 100644 --- a/docs/specification/new-spec/blockchain_reactor.md +++ b/docs/specification/new-spec/blockchain_reactor.md @@ -1,7 +1,14 @@ # Blockchain Reactor -The Blockchain Reactor's high level responsibility is to request blocks from peers or provide them -with blocks, validate and persist the blocks to disk and play blocks to the ABCI app. +The Blockchain Reactor's high level responsibility is to enable peers who are +far behind the current state of the consensus to quickly catch up by downloading +many blocks in parallel, verifying their commits, and executing them against the +ABCI application. + +Tendermint full nodes run the Blockchain Reactor as a service to provide blocks +to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, +where they actively make requests for more blocks until they sync up. +Once caught up, they disable "fast_sync" mode, and turn on the Consensus Reactor. ## Message Types @@ -25,7 +32,7 @@ type bcNoBlockResponseMessage struct { } type bcBlockResponseMessage struct { - Block *types.Block + Block Block } type bcStatusRequestMessage struct { @@ -36,7 +43,7 @@ type bcStatusResponseMessage struct { } ``` -## Block Reactor +## Blockchain Reactor * coordinates the pool for syncing * coordinates the store for persistence From ee674f919fa165c3c263f03d4f30747e3828fe91 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 21 Jan 2018 13:32:04 -0500 Subject: [PATCH 101/124] StopPeerForError in blockchain and consensus --- blockchain/pool.go | 5 +++-- blockchain/reactor.go | 14 +++++++++----- consensus/reactor.go | 6 +++++- consensus/state.go | 18 +++++++++--------- consensus/types/height_vote_set.go | 9 +++++---- mempool/reactor.go | 4 ++-- state/execution.go | 3 +-- types/validator_set.go | 1 - types/vote_set.go | 10 ++++++---- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/blockchain/pool.go b/blockchain/pool.go index 164d3b3b..bb589684 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -195,7 +195,8 @@ func (pool *BlockPool) PopRequest() { // Invalidates the block at pool.height, // Remove the peer and redo request from others. -func (pool *BlockPool) RedoRequest(height int64) { +// Returns the ID of the removed peer. +func (pool *BlockPool) RedoRequest(height int64) p2p.ID { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -205,8 +206,8 @@ func (pool *BlockPool) RedoRequest(height int64) { cmn.PanicSanity("Expected block to be non-nil") } // RemovePeer will redo all requesters associated with this peer. - // TODO: record this malfeasance pool.removePeer(request.peerID) + return request.peerID } // TODO: ensure that blocks come in order for each peer. diff --git a/blockchain/reactor.go b/blockchain/reactor.go index ee84a794..1bb82c23 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -3,6 +3,7 @@ package blockchain import ( "bytes" "errors" + "fmt" "reflect" "sync" "time" @@ -171,7 +172,6 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg) - // TODO: improve logic to satisfy megacheck switch msg := msg.(type) { case *bcBlockRequestMessage: if queued := bcR.respondToPeer(msg, src); !queued { @@ -287,16 +287,20 @@ FOR_LOOP: chainID, firstID, first.Height, second.LastCommit) if err != nil { bcR.Logger.Error("Error in validation", "err", err) - bcR.pool.RedoRequest(first.Height) + peerID := bcR.pool.RedoRequest(first.Height) + peer := bcR.Switch.Peers().Get(peerID) + if peer != nil { + bcR.Switch.StopPeerForError(peer, fmt.Errorf("BlockchainReactor validation error: %v", err)) + } break SYNC_LOOP } else { bcR.pool.PopRequest() + // TODO: batch saves so we dont persist to disk every block bcR.store.SaveBlock(first, firstParts, second.LastCommit) - // NOTE: we could improve performance if we - // didn't make the app commit to disk every block - // ... but we would need a way to get the hash without it persisting + // TODO: same thing for app - but we would need a way to + // get the hash without persisting the state var err error state, err = bcR.blockExec.ApplyBlock(state, firstID, first) if err != nil { diff --git a/consensus/reactor.go b/consensus/reactor.go index 3f6ab506..44ff745c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -205,7 +205,11 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) return } // Peer claims to have a maj23 for some BlockID at H,R,S, - votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.ID(), msg.BlockID) + 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 *cmn.BitArray diff --git a/consensus/state.go b/consensus/state.go index 56070b03..7b8c8e08 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -771,17 +771,17 @@ func (cs *ConsensusState) enterPropose(height int64, round int) { return } - if !cs.isProposer() { - cs.Logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) - if cs.Validators.HasAddress(cs.privValidator.GetAddress()) { - cs.Logger.Debug("This node is a validator") - } else { - cs.Logger.Debug("This node is not a validator") - } - } else { - cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) + if cs.Validators.HasAddress(cs.privValidator.GetAddress()) { cs.Logger.Debug("This node is a validator") + } else { + cs.Logger.Debug("This node is not a validator") + } + + if cs.isProposer() { + cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) cs.decideProposal(height, round) + } else { + cs.Logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) } } diff --git a/consensus/types/height_vote_set.go b/consensus/types/height_vote_set.go index 1435cf42..17ef334d 100644 --- a/consensus/types/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "strings" "sync" @@ -207,15 +208,15 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { - return + return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_) } voteSet := hvs.getVoteSet(round, type_) if voteSet == nil { - return + return nil // something we don't know about yet } - voteSet.SetPeerMaj23(peerID, blockID) + return voteSet.SetPeerMaj23(peerID, blockID) } diff --git a/mempool/reactor.go b/mempool/reactor.go index 4523f824..4e43bb0c 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -101,8 +101,8 @@ type PeerState interface { } // Send new mempool txs to peer. -// TODO: Handle mempool or reactor shutdown? -// As is this routine may block forever if no new txs come in. +// TODO: Handle mempool or reactor shutdown - as is this routine +// may block forever if no new txs come in. func (memR *MempoolReactor) broadcastTxRoutine(peer p2p.Peer) { if !memR.config.Broadcast { return diff --git a/state/execution.go b/state/execution.go index 921799b8..56635da1 100644 --- a/state/execution.go +++ b/state/execution.go @@ -190,11 +190,10 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, } } - // TODO: determine which validators were byzantine byzantineVals := make([]*abci.Evidence, len(block.Evidence.Evidence)) for i, ev := range block.Evidence.Evidence { byzantineVals[i] = &abci.Evidence{ - PubKey: ev.Address(), // XXX + PubKey: ev.Address(), // XXX/TODO Height: ev.Height(), } } diff --git a/types/validator_set.go b/types/validator_set.go index 3876c19d..7e895aba 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -21,7 +21,6 @@ import ( // upon calling .IncrementAccum(). // NOTE: Not goroutine-safe. // NOTE: All get/set to validators should copy the value for safety. -// TODO: consider validator Accum overflow type ValidatorSet struct { // NOTE: persisted via reflect, must be exported. Validators []*Validator `json:"validators"` diff --git a/types/vote_set.go b/types/vote_set.go index a97676f6..584a45e6 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -291,7 +291,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower // this can cause memory issues. // TODO: implement ability to remove peers too // NOTE: VoteSet must not be nil -func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { +func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) error { if voteSet == nil { cmn.PanicSanity("SetPeerMaj23() on nil VoteSet") } @@ -303,9 +303,10 @@ func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { // Make sure peer hasn't already told us something. if existing, ok := voteSet.peerMaj23s[peerID]; ok { if existing.Equals(blockID) { - return // Nothing to do + return nil // Nothing to do } else { - return // TODO bad peer! + return fmt.Errorf("SetPeerMaj23: Received conflicting blockID from peer %v. Got %v, expected %v", + peerID, blockID, existing) } } voteSet.peerMaj23s[peerID] = blockID @@ -314,7 +315,7 @@ func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { votesByBlock, ok := voteSet.votesByBlock[blockKey] if ok { if votesByBlock.peerMaj23 { - return // Nothing to do + return nil // Nothing to do } else { votesByBlock.peerMaj23 = true // No need to copy votes, already there. @@ -324,6 +325,7 @@ func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { voteSet.votesByBlock[blockKey] = votesByBlock // No need to copy votes, no votes to copy over. } + return nil } func (voteSet *VoteSet) BitArray() *cmn.BitArray { From 3090b05eb43a31e3c653dc114c0b4d2185da30d5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 21 Jan 2018 16:26:59 -0500 Subject: [PATCH 102/124] p2p: use conn.Close when peer is nil --- p2p/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/switch.go b/p2p/switch.go index 3f026556..ccf2c5eb 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -426,7 +426,7 @@ func (sw *Switch) listenerRoutine(l Listener) { func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error { peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) if err != nil { - peer.CloseConn() + conn.Close() // peer is nil return err } peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) From dfdfd6c98ea41738864150ac657397775261e26e Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 22 Jan 2018 13:10:54 +0100 Subject: [PATCH 103/124] Small fix in example --- docs/specification/new-spec/proposer-selection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specification/new-spec/proposer-selection.md b/docs/specification/new-spec/proposer-selection.md index a83cb65e..01fa95b8 100644 --- a/docs/specification/new-spec/proposer-selection.md +++ b/docs/specification/new-spec/proposer-selection.md @@ -18,7 +18,7 @@ with the total voting power N, during a sequence of rounds of size N, every proc equal to its voting power. We now look at a few particular cases to understand better how fairness should be implemented. -If we have 4 processes with the following voting power distribution (p0,4), (p1, 2), (p2, 3), (p3, 4) at some round r, +If we have 4 processes with the following voting power distribution (p0,4), (p1, 2), (p2, 2), (p3, 2) at some round r, we have the following sequence of proposer selections in the following rounds: `p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, etc` From 5f3048bd09112219b1a870c781633dfc74f0df5f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 23 Jan 2018 16:56:14 +0400 Subject: [PATCH 104/124] call FlushSync before calling CommitSync if we call it after, we might receive a "fresh" transaction from `broadcast_tx_sync` before old transactions (which were not committed). Refs #1091 ``` Commit is called with a lock on the mempool, meaning no calls to CheckTx can start. However, since CheckTx is called async in the mempool connection, some CheckTx might have already "sailed", when the lock is released in the mempool and Commit proceeds. Then, that spurious CheckTx has not yet "begun" in the ABCI app (stuck in transport?). Instead, ABCI app manages to start to process the Commit. Next, the spurious, "sailed" CheckTx happens in the wrong place. ``` --- mempool/mempool.go | 9 ++++++--- state/execution.go | 8 ++++++++ types/services.go | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 04dbe50a..0cdd1dee 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -159,6 +159,12 @@ func (mem *Mempool) Size() int { return mem.txs.Len() } +// Flushes the mempool connection to ensure async resCb calls are done e.g. +// from CheckTx. +func (mem *Mempool) FlushAppConn() error { + return mem.proxyAppConn.FlushSync() +} + // Flush removes all transactions from the mempool and cache func (mem *Mempool) Flush() { mem.proxyMtx.Lock() @@ -347,9 +353,6 @@ func (mem *Mempool) collectTxs(maxTxs int) types.Txs { // NOTE: this should be called *after* block is committed by consensus. // NOTE: unsafe; Lock/Unlock must be managed by caller func (mem *Mempool) Update(height int64, txs types.Txs) error { - if err := mem.proxyAppConn.FlushSync(); err != nil { // To flush async resCb calls e.g. from CheckTx - return err - } // First, create a lookup map of txns in new txs. txsMap := make(map[string]struct{}) for _, tx := range txs { diff --git a/state/execution.go b/state/execution.go index 921799b8..6eb50f2f 100644 --- a/state/execution.go +++ b/state/execution.go @@ -127,6 +127,14 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) { blockExec.mempool.Lock() defer blockExec.mempool.Unlock() + // while mempool is Locked, flush to ensure all async requests have completed + // in the ABCI app before Commit. + err := blockExec.mempool.FlushAppConn() + if err != nil { + blockExec.logger.Error("Client error during mempool.FlushAppConn", "err", err) + return nil, err + } + // Commit block, get hash back res, err := blockExec.proxyApp.CommitSync() if err != nil { diff --git a/types/services.go b/types/services.go index 6900fae7..6b2be8a5 100644 --- a/types/services.go +++ b/types/services.go @@ -27,6 +27,7 @@ type Mempool interface { Reap(int) Txs Update(height int64, txs Txs) error Flush() + FlushAppConn() error TxsAvailable() <-chan int64 EnableTxsAvailable() @@ -44,6 +45,7 @@ func (m MockMempool) CheckTx(tx Tx, cb func(*abci.Response)) error { return nil func (m MockMempool) Reap(n int) Txs { return Txs{} } func (m MockMempool) Update(height int64, txs Txs) error { return nil } func (m MockMempool) Flush() {} +func (m MockMempool) FlushAppConn() error { return nil } func (m MockMempool) TxsAvailable() <-chan int64 { return make(chan int64) } func (m MockMempool) EnableTxsAvailable() {} From a745fe2eed41e03d60a004f0d3a493e9edeb0dfa Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 23 Jan 2018 20:37:38 +0400 Subject: [PATCH 105/124] mercy for developers with slow Internet --- circle.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 9d03bc46..fd5fe180 100644 --- a/circle.yml +++ b/circle.yml @@ -31,4 +31,5 @@ test: - cd "$PROJECT_PATH" && mv test_integrations.log "${CIRCLE_ARTIFACTS}" - cd "$PROJECT_PATH" && bash <(curl -s https://codecov.io/bash) -f coverage.txt - cd "$PROJECT_PATH" && mv coverage.txt "${CIRCLE_ARTIFACTS}" - - cd "$PROJECT_PATH" && cp test/logs/messages "${CIRCLE_ARTIFACTS}/docker_logs.txt" + - cd "$PROJECT_PATH" && cp test/logs/messages "${CIRCLE_ARTIFACTS}/docker.log" + - cd "${CIRCLE_ARTIFACTS}" && tar czf logs.tar.gz *.log From 21ce5856b3441dd6d5caca16c7a22c0ab3b27d86 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 21:26:19 -0500 Subject: [PATCH 106/124] p2p: notes about ListenAddr --- p2p/node_info.go | 23 ++++++----------------- p2p/peer.go | 2 -- p2p/switch.go | 2 ++ 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/p2p/node_info.go b/p2p/node_info.go index 552c464d..72873add 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -2,8 +2,6 @@ package p2p import ( "fmt" - "net" - "strconv" "strings" crypto "github.com/tendermint/go-crypto" @@ -42,7 +40,8 @@ func (info NodeInfo) Validate(pubKey crypto.PubKey) error { return nil } -// CONTRACT: two nodes are compatible if the major/minor versions match and network match +// CompatibleWith checks if two NodeInfo are compatible with eachother. +// CONTRACT: two nodes are compatible if the major/minor versions match and network match. func (info NodeInfo) CompatibleWith(other NodeInfo) error { iMajor, iMinor, _, iErr := splitVersion(info.Version) oMajor, oMinor, _, oErr := splitVersion(other.Version) @@ -79,6 +78,10 @@ func (info NodeInfo) ID() ID { return PubKeyToID(info.PubKey) } +// NetAddress returns a NetAddress derived from the NodeInfo - +// it includes the authenticated peer ID and the self-reported +// ListenAddr. Note that the ListenAddr is not authenticated and +// may not match that address actually dialed if its an outbound peer. func (info NodeInfo) NetAddress() *NetAddress { id := PubKeyToID(info.PubKey) addr := info.ListenAddr @@ -89,20 +92,6 @@ func (info NodeInfo) NetAddress() *NetAddress { return netAddr } -func (info NodeInfo) ListenHost() string { - host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas - return host -} - -func (info NodeInfo) ListenPort() int { - _, port, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas - port_i, err := strconv.Atoi(port) - if err != nil { - return -1 - } - return port_i -} - func (info NodeInfo) String() string { return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.ListenAddr, info.Version, info.Other) } diff --git a/p2p/peer.go b/p2p/peer.go index 60f9dceb..e427b0d9 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -269,8 +269,6 @@ func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) err return errors.Wrap(err, "Error removing deadline") } - // TODO: fix the peerNodeInfo.ListenAddr - p.nodeInfo = peerNodeInfo return nil } diff --git a/p2p/switch.go b/p2p/switch.go index f29d1b27..fb9a6ac2 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -267,6 +267,8 @@ func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { // If no success after all that, it stops trying, and leaves it // to the PEX/Addrbook to find the peer again func (sw *Switch) reconnectToPeer(peer Peer) { + // NOTE this will connect to the self reported address, + // not necessarily the original we dialed netAddr := peer.NodeInfo().NetAddress() start := time.Now() sw.Logger.Info("Reconnecting to peer", "peer", peer) From 775bb85efbcf29b85d6d553d5b71b160d6e4650e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 21:30:53 -0500 Subject: [PATCH 107/124] p2p/pex: wait to connect to all peers in reactor test --- p2p/pex/pex_reactor_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index c52e45b4..82dafecd 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -111,7 +111,7 @@ func TestPEXReactorRunning(t *testing.T) { require.Nil(t, err) } - assertSomePeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second) + assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1) // stop them for _, s := range switches { @@ -119,7 +119,7 @@ func TestPEXReactorRunning(t *testing.T) { } } -func assertSomePeersWithTimeout(t *testing.T, switches []*p2p.Switch, checkPeriod, timeout time.Duration) { +func assertPeersWithTimeout(t *testing.T, switches []*p2p.Switch, checkPeriod, timeout time.Duration, nPeers int) { ticker := time.NewTicker(checkPeriod) remaining := timeout for { @@ -129,7 +129,7 @@ func assertSomePeersWithTimeout(t *testing.T, switches []*p2p.Switch, checkPerio allGood := true for _, s := range switches { outbound, inbound, _ := s.NumPeers() - if outbound+inbound == 0 { + if outbound+inbound < nPeers { allGood = false } } @@ -296,7 +296,7 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { defer sw.Stop() // 3. check that peer at least connects to seed - assertSomePeersWithTimeout(t, []*p2p.Switch{sw}, 10*time.Millisecond, 10*time.Second) + assertPeersWithTimeout(t, []*p2p.Switch{sw}, 10*time.Millisecond, 10*time.Second, 1) } func TestPEXReactorCrawlStatus(t *testing.T) { From 87087b8acd523889bde0714c3a8033535fbf47c0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 21:41:13 -0500 Subject: [PATCH 108/124] consensus: minor cosmetic --- consensus/state.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 7b8c8e08..adf85d08 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -86,7 +86,7 @@ type ConsensusState struct { cstypes.RoundState state sm.State // State until height-1. - // state changes may be triggered by msgs from peers, + // state changes may be triggered by: msgs from peers, // msgs from ourself, or by timeouts peerMsgQueue chan msgInfo internalMsgQueue chan msgInfo @@ -771,11 +771,12 @@ func (cs *ConsensusState) enterPropose(height int64, round int) { return } - if cs.Validators.HasAddress(cs.privValidator.GetAddress()) { - cs.Logger.Debug("This node is a validator") - } else { + // if not a validator, we're done + if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) { cs.Logger.Debug("This node is not a validator") + return } + cs.Logger.Debug("This node is a validator") if cs.isProposer() { cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) From 85816877c6775dcffa8eb187ead28eada5dcc653 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 22:21:17 -0500 Subject: [PATCH 109/124] config: fix addrbook path to go in config --- config/config.go | 4 +++- p2p/pex/file.go | 4 ++-- p2p/pex/known_address.go | 14 +++++++------- test/p2p/pex/test_addrbook.sh | 8 ++++---- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/config/config.go b/config/config.go index 072606b4..e34d9b95 100644 --- a/config/config.go +++ b/config/config.go @@ -22,11 +22,13 @@ var ( defaultGenesisJSONName = "genesis.json" defaultPrivValName = "priv_validator.json" defaultNodeKeyName = "node_key.json" + defaultAddrBookName = "addrbook.json" defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName) defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) + defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) ) // Config defines the top level configuration for a Tendermint node @@ -278,7 +280,7 @@ type P2PConfig struct { func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:46656", - AddrBook: "addrbook.json", + AddrBook: defaultAddrBookPath, AddrBookStrict: true, MaxNumPeers: 50, FlushThrottleTimeout: 100, diff --git a/p2p/pex/file.go b/p2p/pex/file.go index 521fcfcf..38142dd9 100644 --- a/p2p/pex/file.go +++ b/p2p/pex/file.go @@ -10,8 +10,8 @@ import ( /* Loading & Saving */ type addrBookJSON struct { - Key string - Addrs []*knownAddress + Key string `json:"key"` + Addrs []*knownAddress `json:"addrs"` } func (a *addrBook) saveToFile(filePath string) { diff --git a/p2p/pex/known_address.go b/p2p/pex/known_address.go index e26fdb7b..085eb10f 100644 --- a/p2p/pex/known_address.go +++ b/p2p/pex/known_address.go @@ -9,13 +9,13 @@ import ( // knownAddress tracks information about a known network address // that is used to determine how viable an address is. type knownAddress struct { - Addr *p2p.NetAddress - Src *p2p.NetAddress - Attempts int32 - LastAttempt time.Time - LastSuccess time.Time - BucketType byte - Buckets []int + Addr *p2p.NetAddress `json:"addr"` + Src *p2p.NetAddress `json:"src"` + Attempts int32 `json:"attempts"` + LastAttempt time.Time `json:"last_attempt"` + LastSuccess time.Time `json:"last_success"` + BucketType byte `json:"bucket_type"` + Buckets []int `json:"buckets"` } func newKnownAddress(addr *p2p.NetAddress, src *p2p.NetAddress) *knownAddress { diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index 1dd26b17..d54bcf42 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -17,18 +17,18 @@ CLIENT_NAME="pex_addrbook_$ID" echo "1. restart peer $ID" docker stop "local_testnet_$ID" # preserve addrbook.json -docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" "/tmp/addrbook.json" +docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/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" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" -docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" +docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/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/mach1/core/addrbook.json" +# docker exec "local_testnet_$ID" cat "/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json" echo "" # if the client runs forever, it means addrbook wasn't saved or was empty @@ -44,7 +44,7 @@ echo "1. restart peer $ID" docker stop "local_testnet_$ID" set +e #CIRCLE docker rm -vf "local_testnet_$ID" -set -e +set -e # NOTE that we do not provide persistent_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" From 8f3bd3f2095b32f1e8fdac8044995303d9f590d4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 22:25:39 -0500 Subject: [PATCH 110/124] p2p: addrBook.Save() on DialPeersAsync --- p2p/pex/addrbook.go | 9 +++++++++ p2p/switch.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index b7f60682..3a3be6e4 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -27,6 +27,7 @@ const ( // AddrBook is an address book used for tracking peers // so we can gossip about them to others and select // peers to dial. +// TODO: break this up? type AddrBook interface { cmn.Service @@ -53,6 +54,9 @@ type AddrBook interface { // TODO: remove ListOfKnownAddresses() []*knownAddress + + // Persist to disk + Save() } var _ AddrBook = (*addrBook)(nil) @@ -314,6 +318,11 @@ func (a *addrBook) size() int { //---------------------------------------------------------- +// Save persists the address book to disk. +func (a *addrBook) Save() { + a.saveToFile(a.filePath) // thread safe +} + func (a *addrBook) saveRoutine() { defer a.wg.Done() diff --git a/p2p/switch.go b/p2p/switch.go index fb9a6ac2..7b09279c 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -35,6 +35,7 @@ const ( type AddrBook interface { AddAddress(addr *NetAddress, src *NetAddress) error + Save() } //----------------------------------------------------------------------------- @@ -337,6 +338,9 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b // TODO: move this out of here ? addrBook.AddAddress(netAddr, ourAddr) } + // Persist some peers to disk right away. + // NOTE: integration tests depend on this + addrBook.Save() } // permute the list, dial them in random order. From 4051391039735ec919b42c90e7718448ab38bb61 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 22:06:40 -0500 Subject: [PATCH 111/124] blockchain: test wip for hard to test functionality [ci skip] --- blockchain/reactor_test.go | 47 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 6f5b14ff..26747ea6 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -49,7 +49,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe return bcReactor } -func TestNoBlockMessageResponse(t *testing.T) { +func TestNoBlockResponse(t *testing.T) { maxBlockHeight := int64(20) bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) @@ -73,7 +73,7 @@ func TestNoBlockMessageResponse(t *testing.T) { } // receive a request message from peer, - // wait to hear response + // wait for our response to be received on the peer for _, tt := range tests { reqBlockMsg := &bcBlockRequestMessage{tt.height} reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg}) @@ -97,6 +97,49 @@ func TestNoBlockMessageResponse(t *testing.T) { } } +/* +// NOTE: This is too hard to test without +// an easy way to add test peer to switch +// or without significant refactoring of the module. +// Alternatively we could actually dial a TCP conn but +// that seems extreme. +func TestBadBlockStopsPeer(t *testing.T) { + maxBlockHeight := int64(20) + + bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) + bcr.Start() + defer bcr.Stop() + + // Add some peers in + peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12))) + + // XXX: This doesn't add the peer to anything, + // so it's hard to check that it's later removed + bcr.AddPeer(peer) + assert.True(t, bcr.Switch.Peers().Size() > 0) + + // send a bad block from the peer + // default blocks already dont have commits, so should fail + block := bcr.store.LoadBlock(3) + msg := &bcBlockResponseMessage{Block: block} + peer.Send(BlockchainChannel, struct{ BlockchainMessage }{msg}) + + ticker := time.NewTicker(time.Millisecond * 10) + timer := time.NewTimer(time.Second * 2) +LOOP: + for { + select { + case <-ticker.C: + if bcr.Switch.Peers().Size() == 0 { + break LOOP + } + case <-timer.C: + t.Fatal("Timed out waiting to disconnect peer") + } + } +} +*/ + //---------------------------------------------- // utility funcs From 50129ad8aca2517d1d4fbde7d3fbc7ec384f374b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 21 Jan 2018 11:45:04 -0500 Subject: [PATCH 112/124] p2p: add Channels to NodeInfo and don't send for unknown channels --- node/node.go | 40 +++++++++++++++++++++++++--------------- p2p/node_info.go | 47 ++++++++++++++++++++++++++++++++++++++++++----- p2p/peer.go | 27 +++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/node/node.go b/node/node.go index bdbf12f8..b02012f9 100644 --- a/node/node.go +++ b/node/node.go @@ -18,7 +18,7 @@ import ( bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/consensus" + cs "github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/evidence" mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" @@ -104,14 +104,14 @@ type Node struct { // services eventBus *types.EventBus // pub/sub for services stateDB dbm.DB - blockStore *bc.BlockStore // store the blockchain to disk - bcReactor *bc.BlockchainReactor // for fast-syncing - mempoolReactor *mempl.MempoolReactor // for gossipping transactions - consensusState *consensus.ConsensusState // latest consensus state - consensusReactor *consensus.ConsensusReactor // for participating in the consensus - evidencePool *evidence.EvidencePool // tracking evidence - proxyApp proxy.AppConns // connection to the application - rpcListeners []net.Listener // rpc servers + blockStore *bc.BlockStore // store the blockchain to disk + bcReactor *bc.BlockchainReactor // for fast-syncing + mempoolReactor *mempl.MempoolReactor // for gossipping transactions + consensusState *cs.ConsensusState // latest consensus state + consensusReactor *cs.ConsensusReactor // for participating in the consensus + evidencePool *evidence.EvidencePool // tracking evidence + proxyApp proxy.AppConns // connection to the application + rpcListeners []net.Listener // rpc servers txIndexer txindex.TxIndexer indexerService *txindex.IndexerService } @@ -159,7 +159,7 @@ func NewNode(config *cfg.Config, // and sync tendermint and the app by performing a handshake // and replaying any necessary blocks consensusLogger := logger.With("module", "consensus") - handshaker := consensus.NewHandshaker(stateDB, state, blockStore) + handshaker := cs.NewHandshaker(stateDB, state, blockStore) handshaker.SetLogger(consensusLogger) proxyApp := proxy.NewAppConns(clientCreator, handshaker) proxyApp.SetLogger(logger.With("module", "proxy")) @@ -220,13 +220,13 @@ func NewNode(config *cfg.Config, bcReactor.SetLogger(logger.With("module", "blockchain")) // Make ConsensusReactor - consensusState := consensus.NewConsensusState(config.Consensus, state.Copy(), + consensusState := cs.NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evidencePool) consensusState.SetLogger(consensusLogger) if privValidator != nil { consensusState.SetPrivValidator(privValidator) } - consensusReactor := consensus.NewConsensusReactor(consensusState, fastSync) + consensusReactor := cs.NewConsensusReactor(consensusState, fastSync) consensusReactor.SetLogger(consensusLogger) p2pLogger := logger.With("module", "p2p") @@ -503,12 +503,12 @@ func (n *Node) BlockStore() *bc.BlockStore { } // ConsensusState returns the Node's ConsensusState. -func (n *Node) ConsensusState() *consensus.ConsensusState { +func (n *Node) ConsensusState() *cs.ConsensusState { return n.consensusState } // ConsensusReactor returns the Node's ConsensusReactor. -func (n *Node) ConsensusReactor() *consensus.ConsensusReactor { +func (n *Node) ConsensusReactor() *cs.ConsensusReactor { return n.consensusReactor } @@ -552,16 +552,26 @@ func (n *Node) makeNodeInfo(pubKey crypto.PubKey) p2p.NodeInfo { PubKey: pubKey, Network: n.genesisDoc.ChainID, Version: version.Version, + Channels: []byte{ + bc.BlockchainChannel, + cs.StateChannel, cs.DataChannel, cs.VoteChannel, cs.VoteSetBitsChannel, + mempl.MempoolChannel, + evidence.EvidenceChannel, + }, Moniker: n.config.Moniker, Other: []string{ cmn.Fmt("wire_version=%v", wire.Version), cmn.Fmt("p2p_version=%v", p2p.Version), - cmn.Fmt("consensus_version=%v", consensus.Version), + cmn.Fmt("consensus_version=%v", cs.Version), cmn.Fmt("rpc_version=%v/%v", rpc.Version, rpccore.Version), cmn.Fmt("tx_index=%v", txIndexerStatus), }, } + if n.config.P2P.PexReactor { + nodeInfo.Channels = append(nodeInfo.Channels, pex.PexChannel) + } + rpcListenAddr := n.config.RPC.ListenAddress nodeInfo.Other = append(nodeInfo.Other, cmn.Fmt("rpc_addr=%v", rpcListenAddr)) diff --git a/p2p/node_info.go b/p2p/node_info.go index 72873add..205c63ac 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -7,7 +7,10 @@ import ( crypto "github.com/tendermint/go-crypto" ) -const maxNodeInfoSize = 10240 // 10Kb +const ( + maxNodeInfoSize = 10240 // 10Kb + maxNumChannels = 16 // plenty of room for upgrades, for now +) func MaxNodeInfoSize() int { return maxNodeInfoSize @@ -21,8 +24,9 @@ type NodeInfo struct { ListenAddr string `json:"listen_addr"` // accepting incoming // Check compatibility - Network string `json:"network"` // network/chain ID - Version string `json:"version"` // major.minor.revision + Network string `json:"network"` // network/chain ID + Version string `json:"version"` // major.minor.revision + Channels []byte `json:"channels"` // channels this node knows about // Sanitize Moniker string `json:"moniker"` // arbitrary moniker @@ -30,18 +34,33 @@ type NodeInfo struct { } // Validate checks the self-reported NodeInfo is safe. -// It returns an error if the info.PubKey doesn't match the given pubKey. +// It returns an error if the info.PubKey doesn't match the given pubKey, +// or if there are too many Channels or any duplicate Channels. // TODO: constraints for Moniker/Other? Or is that for the UI ? func (info NodeInfo) Validate(pubKey crypto.PubKey) error { if !info.PubKey.Equals(pubKey) { return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)", info.PubKey, pubKey) } + + if len(info.Channels) > maxNumChannels { + return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) + } + + channels := make(map[byte]struct{}) + for _, ch := range info.Channels { + _, ok := channels[ch] + if ok { + return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) + } + channels[ch] = struct{}{} + } return nil } // CompatibleWith checks if two NodeInfo are compatible with eachother. -// CONTRACT: two nodes are compatible if the major/minor versions match and network match. +// CONTRACT: two nodes are compatible if the major/minor versions match and network match +// and they have at least one channel in common. func (info NodeInfo) CompatibleWith(other NodeInfo) error { iMajor, iMinor, _, iErr := splitVersion(info.Version) oMajor, oMinor, _, oErr := splitVersion(other.Version) @@ -71,6 +90,24 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network) } + // if we have no channels, we're just testing + if len(info.Channels) == 0 { + return nil + } + + // for each of our channels, check if they have it + found := false + for _, ch1 := range info.Channels { + for _, ch2 := range other.Channels { + if ch1 == ch2 { + found = true + break // only need one + } + } + } + if !found { + return fmt.Errorf("Peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels) + } return nil } diff --git a/p2p/peer.go b/p2p/peer.go index e427b0d9..1fa937af 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -48,7 +48,8 @@ type peer struct { persistent bool config *PeerConfig - nodeInfo NodeInfo + nodeInfo NodeInfo // peer's node info + channels []byte // channels the peer knows about Data *cmn.CMap // User data. } @@ -204,6 +205,8 @@ func (p *peer) Send(chID byte, msg interface{}) bool { // see Switch#Broadcast, where we fetch the list of peers and loop over // them - while we're looping, one peer may be removed and stopped. return false + } else if !p.hasChannel(chID) { + return false } return p.mconn.Send(chID, msg) } @@ -213,6 +216,8 @@ func (p *peer) Send(chID byte, msg interface{}) bool { func (p *peer) TrySend(chID byte, msg interface{}) bool { if !p.IsRunning() { return false + } else if !p.hasChannel(chID) { + return false } return p.mconn.TrySend(chID, msg) } @@ -227,6 +232,17 @@ func (p *peer) Set(key string, data interface{}) { p.Data.Set(key, data) } +// hasChannel returns true if the peer reported +// knowing about the given chID. +func (p *peer) hasChannel(chID byte) bool { + for _, ch := range p.channels { + if ch == chID { + return true + } + } + return false +} + //--------------------------------------------------- // methods used by the Switch @@ -269,10 +285,17 @@ func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) err return errors.Wrap(err, "Error removing deadline") } - p.nodeInfo = peerNodeInfo + p.setNodeInfo(peerNodeInfo) return nil } +func (p *peer) setNodeInfo(nodeInfo NodeInfo) { + p.nodeInfo = nodeInfo + // cache the channels so we dont copy nodeInfo + // every time we check hasChannel + p.channels = nodeInfo.Channels +} + // Addr returns peer's remote network address. func (p *peer) Addr() net.Addr { return p.conn.RemoteAddr() From 260affd03773c5bd6e112e110d48fe2e3cf72927 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 21 Jan 2018 18:19:38 -0500 Subject: [PATCH 113/124] docs consolidation --- docs/specification/new-spec/README.md | 27 ++++++---- docs/specification/new-spec/blockchain.md | 38 ++++++++++++-- .../block_sync/impl.md} | 44 ----------------- .../new-spec/reactors/block_sync/reactor.md | 49 +++++++++++++++++++ .../consensus}/consensus-reactor.md | 0 .../{ => reactors/consensus}/consensus.md | 0 .../consensus}/proposer-selection.md | 0 .../new-spec/reactors/mempool}/README.md | 0 .../new-spec/reactors/mempool}/concurrency.md | 0 .../new-spec/reactors/mempool}/config.md | 0 .../reactors/mempool}/functionality.md | 0 .../new-spec/reactors/mempool}/messages.md | 0 .../new-spec/{p2p => reactors/pex}/pex.md | 0 docs/specification/new-spec/state.md | 38 ++++---------- 14 files changed, 108 insertions(+), 88 deletions(-) rename docs/specification/new-spec/{blockchain_reactor.md => reactors/block_sync/impl.md} (61%) create mode 100644 docs/specification/new-spec/reactors/block_sync/reactor.md rename docs/specification/new-spec/{ => reactors/consensus}/consensus-reactor.md (100%) rename docs/specification/new-spec/{ => reactors/consensus}/consensus.md (100%) rename docs/specification/new-spec/{ => reactors/consensus}/proposer-selection.md (100%) rename {mempool/docs => docs/specification/new-spec/reactors/mempool}/README.md (100%) rename {mempool/docs => docs/specification/new-spec/reactors/mempool}/concurrency.md (100%) rename {mempool/docs => docs/specification/new-spec/reactors/mempool}/config.md (100%) rename {mempool/docs => docs/specification/new-spec/reactors/mempool}/functionality.md (100%) rename {mempool/docs => docs/specification/new-spec/reactors/mempool}/messages.md (100%) rename docs/specification/new-spec/{p2p => reactors/pex}/pex.md (100%) diff --git a/docs/specification/new-spec/README.md b/docs/specification/new-spec/README.md index 179048dd..5b2f50cd 100644 --- a/docs/specification/new-spec/README.md +++ b/docs/specification/new-spec/README.md @@ -1,7 +1,8 @@ # Tendermint Specification This is a markdown specification of the Tendermint blockchain. -It defines the base data structures used in the blockchain and how they are validated. +It defines the base data structures, how they are validated, +and how they are communicated over the network. XXX: this spec is a work in progress and not yet complete - see github [isses](https://github.com/tendermint/tendermint/issues) and @@ -14,12 +15,25 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ## Contents +### Data Structures + - [Overview](#overview) - [Encoding and Digests](encoding.md) - [Blockchain](blockchain.md) - [State](state.md) -- [Consensus](consensus.md) -- [P2P](p2p/node.md) + +### P2P and Network Protocols + +- [The Base P2P Layer](p2p/README.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP conns +- [Peer Exchange (PEX)](pex/README.md): gossip known peer addresses so peers can find eachother +- [Block Sync](block_sync/README.md): gossip blocks so peers can catch up quickly +- [Consensus](consensus/README.md): gossip votes and block parts so new blocks can be committed +- [Mempool](mempool/README.md): gossip transactions so they get included in blocks +- [Evidence](evidence/README.md): TODO + +### More +- [Light Client](light_client/README.md): TODO +- [Persistence](persistence/README.md): TODO ## Overview @@ -60,10 +74,3 @@ Also note that information like the transaction results and the validator set ar directly included in the block - only their cryptographic digests (Merkle roots) are. Hence, verification of a block requires a separate data structure to store this information. We call this the `State`. Block verification also requires access to the previous block. - -## TODO - -- Light Client -- P2P -- Reactor protocols (consensus, mempool, blockchain, pex) - diff --git a/docs/specification/new-spec/blockchain.md b/docs/specification/new-spec/blockchain.md index f029d7d4..93e4df6d 100644 --- a/docs/specification/new-spec/blockchain.md +++ b/docs/specification/new-spec/blockchain.md @@ -10,7 +10,7 @@ The Tendermint blockchains consists of a short list of basic data types: ## Block A block consists of a header, a list of transactions, a list of votes (the commit), -and a list of evidence if malfeasance (ie. signing conflicting votes). +and a list of evidence of malfeasance (ie. signing conflicting votes). ```go type Block struct { @@ -366,10 +366,10 @@ against the given signature and message bytes. ## Evidence +TODO ``` - - +TODO ``` Every piece of evidence contains two conflicting votes from a single validator that @@ -384,7 +384,35 @@ Once a block is validated, it can be executed against the state. The state follows the recursive equation: ```go -app = NewABCIApp state(1) = InitialState -state(h+1) <- Execute(state(h), app, block(h)) +state(h+1) <- Execute(state(h), ABCIApp, block(h)) ``` + +Where `InitialState` includes the initial consensus parameters and validator set, +and `ABCIApp` is an ABCI application that can return results and changes to the validator +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) + + return State{ + LastResults: abciResponses.DeliverTxResults, + AppHash: abciResponses.AppHash, + Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges), + LastValidators: state.Validators, + ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges), + } +} + +type ABCIResponses struct { + DeliverTxResults []Result + ValidatorChanges []Validator + ConsensusParamChanges ConsensusParams + AppHash []byte +} +``` + + diff --git a/docs/specification/new-spec/blockchain_reactor.md b/docs/specification/new-spec/reactors/block_sync/impl.md similarity index 61% rename from docs/specification/new-spec/blockchain_reactor.md rename to docs/specification/new-spec/reactors/block_sync/impl.md index 5633eb05..6be61a33 100644 --- a/docs/specification/new-spec/blockchain_reactor.md +++ b/docs/specification/new-spec/reactors/block_sync/impl.md @@ -1,47 +1,3 @@ -# Blockchain Reactor - -The Blockchain Reactor's high level responsibility is to enable peers who are -far behind the current state of the consensus to quickly catch up by downloading -many blocks in parallel, verifying their commits, and executing them against the -ABCI application. - -Tendermint full nodes run the Blockchain Reactor as a service to provide blocks -to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, -where they actively make requests for more blocks until they sync up. -Once caught up, they disable "fast_sync" mode, and turn on the Consensus Reactor. - -## Message Types - -```go -const ( - msgTypeBlockRequest = byte(0x10) - msgTypeBlockResponse = byte(0x11) - msgTypeNoBlockResponse = byte(0x12) - msgTypeStatusResponse = byte(0x20) - msgTypeStatusRequest = byte(0x21) -) -``` - -```go -type bcBlockRequestMessage struct { - Height int64 -} - -type bcNoBlockResponseMessage struct { - Height int64 -} - -type bcBlockResponseMessage struct { - Block Block -} - -type bcStatusRequestMessage struct { - Height int64 - -type bcStatusResponseMessage struct { - Height int64 -} -``` ## Blockchain Reactor diff --git a/docs/specification/new-spec/reactors/block_sync/reactor.md b/docs/specification/new-spec/reactors/block_sync/reactor.md new file mode 100644 index 00000000..11297d02 --- /dev/null +++ b/docs/specification/new-spec/reactors/block_sync/reactor.md @@ -0,0 +1,49 @@ +# Blockchain Reactor + +The Blockchain Reactor's high level responsibility is to enable peers who are +far behind the current state of the consensus to quickly catch up by downloading +many blocks in parallel, verifying their commits, and executing them against the +ABCI application. + +Tendermint full nodes run the Blockchain Reactor as a service to provide blocks +to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, +where they actively make requests for more blocks until they sync up. +Once caught up, "fast_sync" mode is disabled and the node switches to +using the Consensus Reactor. , and turn on the Consensus Reactor. + +## Message Types + +```go +const ( + msgTypeBlockRequest = byte(0x10) + msgTypeBlockResponse = byte(0x11) + msgTypeNoBlockResponse = byte(0x12) + msgTypeStatusResponse = byte(0x20) + msgTypeStatusRequest = byte(0x21) +) +``` + +```go +type bcBlockRequestMessage struct { + Height int64 +} + +type bcNoBlockResponseMessage struct { + Height int64 +} + +type bcBlockResponseMessage struct { + Block Block +} + +type bcStatusRequestMessage struct { + Height int64 + +type bcStatusResponseMessage struct { + Height int64 +} +``` + +## Protocol + +TODO diff --git a/docs/specification/new-spec/consensus-reactor.md b/docs/specification/new-spec/reactors/consensus/consensus-reactor.md similarity index 100% rename from docs/specification/new-spec/consensus-reactor.md rename to docs/specification/new-spec/reactors/consensus/consensus-reactor.md diff --git a/docs/specification/new-spec/consensus.md b/docs/specification/new-spec/reactors/consensus/consensus.md similarity index 100% rename from docs/specification/new-spec/consensus.md rename to docs/specification/new-spec/reactors/consensus/consensus.md diff --git a/docs/specification/new-spec/proposer-selection.md b/docs/specification/new-spec/reactors/consensus/proposer-selection.md similarity index 100% rename from docs/specification/new-spec/proposer-selection.md rename to docs/specification/new-spec/reactors/consensus/proposer-selection.md diff --git a/mempool/docs/README.md b/docs/specification/new-spec/reactors/mempool/README.md similarity index 100% rename from mempool/docs/README.md rename to docs/specification/new-spec/reactors/mempool/README.md diff --git a/mempool/docs/concurrency.md b/docs/specification/new-spec/reactors/mempool/concurrency.md similarity index 100% rename from mempool/docs/concurrency.md rename to docs/specification/new-spec/reactors/mempool/concurrency.md diff --git a/mempool/docs/config.md b/docs/specification/new-spec/reactors/mempool/config.md similarity index 100% rename from mempool/docs/config.md rename to docs/specification/new-spec/reactors/mempool/config.md diff --git a/mempool/docs/functionality.md b/docs/specification/new-spec/reactors/mempool/functionality.md similarity index 100% rename from mempool/docs/functionality.md rename to docs/specification/new-spec/reactors/mempool/functionality.md diff --git a/mempool/docs/messages.md b/docs/specification/new-spec/reactors/mempool/messages.md similarity index 100% rename from mempool/docs/messages.md rename to docs/specification/new-spec/reactors/mempool/messages.md diff --git a/docs/specification/new-spec/p2p/pex.md b/docs/specification/new-spec/reactors/pex/pex.md similarity index 100% rename from docs/specification/new-spec/p2p/pex.md rename to docs/specification/new-spec/reactors/pex/pex.md diff --git a/docs/specification/new-spec/state.md b/docs/specification/new-spec/state.md index 3594de57..abd32edb 100644 --- a/docs/specification/new-spec/state.md +++ b/docs/specification/new-spec/state.md @@ -3,10 +3,15 @@ ## State The state contains information whose cryptographic digest is included in block headers, and thus is -necessary for validating new blocks. For instance, the Merkle root of the results from executing the -previous block, or the Merkle root of the current validators. While neither the results of -transactions now the validators are ever included in the blockchain itself, the Merkle roots are, -and hence we need a separate data structure to track them. +necessary for validating new blocks. For instance, the set of validators 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 +included in a block or gossipped over the network, and we never compute +its hash. However, the types it contains are part of the specification, since +their Merkle roots are included in blocks. + +For details on an implementation of `State` with persistence, see TODO ```go type State struct { @@ -77,28 +82,3 @@ TODO: TODO: -## Execution - -We define an `Execute` function that takes a state and a block, -executes the block against the application, and returns an updated state. - -```go -Execute(s State, app ABCIApp, block Block) State { - abciResponses := app.ApplyBlock(block) - - return State{ - LastResults: abciResponses.DeliverTxResults, - AppHash: abciResponses.AppHash, - Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges), - LastValidators: state.Validators, - ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges), - } -} - -type ABCIResponses struct { - DeliverTxResults []Result - ValidatorChanges []Validator - ConsensusParamChanges ConsensusParams - AppHash []byte -} -``` From 99034904f81a368ce152e041a1ce2b3bb9b00e27 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Jan 2018 23:40:33 -0500 Subject: [PATCH 114/124] p2p: fix tests for required channels --- p2p/peer.go | 3 +++ p2p/peer_test.go | 20 ++++++++++++-------- p2p/test_util.go | 18 +++++++++++------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 1fa937af..67ce411c 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -240,6 +240,9 @@ func (p *peer) hasChannel(chID byte) bool { return true } } + // NOTE: probably will want to remove this + // but could be helpful while the feature is new + p.Logger.Debug("Unknown channel for peer", "channel", chID, "channels", p.channels) return false } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 978775c8..a2f5ed05 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -13,6 +13,8 @@ import ( tmconn "github.com/tendermint/tendermint/p2p/conn" ) +const testCh = 0x01 + func TestPeerBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -77,25 +79,26 @@ func TestPeerSend(t *testing.T) { defer p.Stop() - assert.True(p.CanSend(0x01)) - assert.True(p.Send(0x01, "Asylum")) + assert.True(p.CanSend(testCh)) + assert.True(p.Send(testCh, "Asylum")) } func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*peer, error) { chDescs := []*tmconn.ChannelDescriptor{ - {ID: 0x01, Priority: 1}, + {ID: testCh, Priority: 1}, } - reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} + reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)} pk := crypto.GenPrivKeyEd25519().Wrap() p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config, false) if err != nil { return nil, err } err = p.HandshakeTimeout(NodeInfo{ - PubKey: pk.PubKey(), - Moniker: "host_peer", - Network: "testing", - Version: "123.123.123", + PubKey: pk.PubKey(), + Moniker: "host_peer", + Network: "testing", + Version: "123.123.123", + Channels: []byte{testCh}, }, 1*time.Second) if err != nil { return nil, err @@ -148,6 +151,7 @@ func (p *remotePeer) accept(l net.Listener) { Network: "testing", Version: "123.123.123", ListenAddr: l.Addr().String(), + Channels: []byte{testCh}, }, 1*time.Second) if err != nil { golog.Fatalf("Failed to perform handshake: %+v", err) diff --git a/p2p/test_util.go b/p2p/test_util.go index c4c0fe0b..dea48dfd 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -132,16 +132,20 @@ func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f nodeKey := &NodeKey{ PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } - s := NewSwitch(cfg) - s.SetLogger(log.TestingLogger()) - s = initSwitch(i, s) - s.SetNodeInfo(NodeInfo{ + sw := NewSwitch(cfg) + sw.SetLogger(log.TestingLogger()) + sw = initSwitch(i, sw) + ni := NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), - }) - s.SetNodeKey(nodeKey) - return s + } + for ch, _ := range sw.reactorsByCh { + ni.Channels = append(ni.Channels, ch) + } + sw.SetNodeInfo(ni) + sw.SetNodeKey(nodeKey) + return sw } From b6eb275b22daab5583a0c4a30e9252db67a7c5a2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 24 Jan 2018 14:27:37 -0500 Subject: [PATCH 115/124] p2p: fix break in double loop --- p2p/node_info.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/node_info.go b/p2p/node_info.go index 205c63ac..a5bb9da5 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -97,11 +97,12 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { // for each of our channels, check if they have it found := false +OUTER_LOOP: for _, ch1 := range info.Channels { for _, ch2 := range other.Channels { if ch1 == ch2 { found = true - break // only need one + break OUTER_LOOP // only need one } } } From 3ae26bd6e6fdf4685e32960d9959da026c0ac435 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 24 Jan 2018 23:34:57 -0500 Subject: [PATCH 116/124] consensus: fix SetLogger in tests --- config/config.go | 2 +- consensus/byzantine_test.go | 4 +++- consensus/common_test.go | 17 ++++------------- consensus/reactor_test.go | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/config/config.go b/config/config.go index e34d9b95..6395c60f 100644 --- a/config/config.go +++ b/config/config.go @@ -451,7 +451,7 @@ func TestConsensusConfig() *ConsensusConfig { config.TimeoutCommit = 10 config.SkipTimeoutCommit = true config.PeerGossipSleepDuration = 5 - config.PeerQueryMaj23SleepDuration = 50 + config.PeerQueryMaj23SleepDuration = 250 return config } diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 2f5f3f76..38df1ecc 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -33,7 +33,9 @@ func TestByzantine(t *testing.T) { css := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter) // give the byzantine validator a normal ticker - css[0].SetTimeoutTicker(NewTimeoutTicker()) + ticker := NewTimeoutTicker() + ticker.SetLogger(css[0].Logger) + css[0].SetTimeoutTicker(ticker) switches := make([]*p2p.Switch, N) p2pLogger := logger.With("module", "p2p") diff --git a/consensus/common_test.go b/consensus/common_test.go index 40a320e4..c27b50c4 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -267,7 +267,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S stateDB := dbm.NewMemDB() blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) - cs.SetLogger(log.TestingLogger()) + cs.SetLogger(log.TestingLogger().With("module", "consensus")) cs.SetPrivValidator(pv) eventBus := types.NewEventBus() @@ -285,14 +285,6 @@ func loadPrivValidator(config *cfg.Config) *types.PrivValidatorFS { return privValidator } -func fixedConsensusStateDummy(config *cfg.Config, logger log.Logger) *ConsensusState { - state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile()) - privValidator := loadPrivValidator(config) - cs := newConsensusState(state, privValidator, dummy.NewDummyApplication()) - cs.SetLogger(logger) - return cs -} - func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) { // Get State state, privVals := randGenesisState(nValidators, false, 10) @@ -300,7 +292,6 @@ func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) { vss := make([]*validatorStub, nValidators) cs := newConsensusState(state, privVals[0], counter.NewCounterApplication(true)) - cs.SetLogger(log.TestingLogger()) for i := 0; i < nValidators; i++ { vss[i] = NewValidatorStub(privVals[i], i) @@ -346,7 +337,7 @@ func consensusLogger() log.Logger { } } return term.FgBgColor{} - }) + }).With("module", "consensus") } func randConsensusNet(nValidators int, testName string, tickerFunc func() TimeoutTicker, appFunc func() abci.Application, configOpts ...func(*cfg.Config)) []*ConsensusState { @@ -366,8 +357,8 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou app.InitChain(abci.RequestInitChain{Validators: vals}) css[i] = newConsensusStateWithConfig(thisConfig, state, privVals[i], app) - css[i].SetLogger(logger.With("validator", i)) css[i].SetTimeoutTicker(tickerFunc()) + css[i].SetLogger(logger.With("validator", i, "module", "consensus")) } return css } @@ -395,8 +386,8 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF app.InitChain(abci.RequestInitChain{Validators: vals}) css[i] = newConsensusStateWithConfig(thisConfig, state, privVal, app) - css[i].SetLogger(logger.With("validator", i)) css[i].SetTimeoutTicker(tickerFunc()) + css[i].SetLogger(logger.With("validator", i, "module", "consensus")) } return css } diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index c1e2a462..0fb8ecd6 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -35,7 +35,7 @@ func startConsensusNet(t *testing.T, css []*ConsensusState, N int) ([]*Consensus /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states - reactors[i].SetLogger(css[i].Logger.With("validator", "i", "module", "consensus")) + reactors[i].SetLogger(css[i].Logger) // eventBus is already started with the cs eventBuses[i] = css[i].eventBus From ab13806276dc75a7f38d092c2ff255b218ccb958 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 25 Jan 2018 01:01:27 -0500 Subject: [PATCH 117/124] consensus: print go routines in failed test --- consensus/reactor_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 0fb8ecd6..f66f4e9e 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "runtime" "runtime/pprof" "sync" "testing" @@ -410,7 +411,15 @@ func timeoutWaitGroup(t *testing.T, n int, f func(int), css []*ConsensusState) { t.Log(cs.GetRoundState()) t.Log("") } + os.Stdout.Write([]byte("pprof.Lookup('goroutine'):\n")) pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) + capture() panic("Timed out waiting for all validators to commit a block") } } + +func capture() { + trace := make([]byte, 10240000) + count := runtime.Stack(trace, true) + fmt.Printf("Stack of %d bytes: %s\n", count, trace) +} From fb109db33dd446eee4911f237721f2ebccf54de5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 25 Jan 2018 02:10:01 -0500 Subject: [PATCH 118/124] update changelog --- CHANGELOG.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0205c427..a378f4a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,11 +29,30 @@ BUG FIXES: ## 0.16.0 (TBD) BREAKING CHANGES: -- [p2p] old `seeds` is now `persistent_peers` (persistent peers to which TM will always connect to) -- [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) +- [config] use $TMHOME/config for all config and json files +- [p2p] old `--p2p.seeds` is now `--p2p.persistent_peers` (persistent peers to which TM will always connect to) +- [p2p] now `--p2p.seeds` only used for getting addresses (if addrbook is empty; not persistent) +- [p2p] NodeInfo: remove RemoteAddr and add Channels + - we must have at least one overlapping channel with peer + - we only send msgs for channels the peer advertised FEATURES: -- [p2p] added new `/dial_persistent_peers` **unsafe** endpoint +- [p2p] added new `/dial_peers&persistent=_` **unsafe** endpoint +- [p2p] persistent node key in `$THMHOME/config/node_key.json` +- [p2p] introduce peer ID and authenticate peers by ID using addresses like `ID@IP:PORT` +- [p2p] new seed mode in pex reactor crawls the network and serves as a seed. TODO: `--p2p.seed_mode` +- [config] MempoolConfig.CacheSize + +IMPROVEMENT: +- [p2p] stricter rules in the PEX reactor for better handling of abuse +- [p2p] various improvements to code structure including subpackages for `pex` and `conn` +- [docs] new spec! + +BUG FIX: +- [blockchain] StopPeerForError on timeout +- [consensus] StopPeerForError on a bad Maj23 message +- [state] flush mempool conn before calling commit +- [types] fix priv val signing things that only differ by timestamp ## 0.15.0 (December 29, 2017) From baff4bd8cc0596b9f082cc5ff828740ebd90c835 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 24 Jan 2018 17:14:38 -0500 Subject: [PATCH 119/124] p2p/conn: better handling for some stop conditions --- p2p/conn/connection.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 71b2a13d..83d87e58 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -189,11 +189,11 @@ func (c *MConnection) OnStop() { close(c.quit) } c.conn.Close() // nolint: errcheck + // We can't close pong safely here because // recvRoutine may write to it after we've stopped. // Though it doesn't need to get closed at all, // we close it @ recvRoutine. - // close(c.pong) } func (c *MConnection) String() string { @@ -450,7 +450,11 @@ FOR_LOOP: case packetTypePing: // TODO: prevent abuse, as they cause flush()'s. c.Logger.Debug("Receive Ping") - c.pong <- struct{}{} + select { + case c.pong <- struct{}{}: + case <-c.quit: + break FOR_LOOP + } case packetTypePong: // do nothing c.Logger.Debug("Receive Pong") @@ -470,6 +474,7 @@ FOR_LOOP: err := fmt.Errorf("Unknown channel %X", pkt.ChannelID) c.Logger.Error("Connection failed @ recvRoutine", "conn", c, "err", err) c.stopForError(err) + break FOR_LOOP } msgBytes, err := channel.recvMsgPacket(pkt) @@ -489,6 +494,7 @@ FOR_LOOP: err := fmt.Errorf("Unknown message type %X", pktType) c.Logger.Error("Connection failed @ recvRoutine", "conn", c, "err", err) c.stopForError(err) + break FOR_LOOP } // TODO: shouldn't this go in the sendRoutine? From 0a7a190cd1526fe31cf66e4e5896632d0bdb7c7c Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 22 Jan 2018 13:40:32 +0100 Subject: [PATCH 120/124] Fix vagrantfile If you get an error, please run `vagrant box update`. --- .gitignore | 1 + Vagrantfile | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 22a6be0b..b031ce18 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ test/logs coverage.txt docs/_build docs/tools +*.log scripts/wal2json/wal2json scripts/cutWALUntil/cutWALUntil diff --git a/Vagrantfile b/Vagrantfile index 12cfce47..abbb719f 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -21,29 +21,31 @@ Vagrant.configure("2") do |config| # install base requirements apt-get update + apt-get upgrade -y apt-get install -y --no-install-recommends wget curl jq \ make shellcheck bsdmainutils psmisc apt-get install -y docker-ce golang-1.9-go + apt-get install -y language-pack-en + + apt-get autoremove -y # needed for docker - usermod -a -G docker ubuntu + usermod -a -G docker vagrant - # use "EOF" not EOF to avoid variable substitution of $PATH - cat << "EOF" >> /home/ubuntu/.bash_profile -export PATH=$PATH:/usr/lib/go-1.9/bin:/home/ubuntu/go/bin -export GOPATH=/home/ubuntu/go -export LC_ALL=en_US.UTF-8 -cd go/src/github.com/tendermint/tendermint -EOF + # set env variables + echo 'export PATH=$PATH:/usr/lib/go-1.9/bin:/home/vagrant/go/bin' >> /home/vagrant/.bash_profile + echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile + echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile - mkdir -p /home/ubuntu/go/bin - mkdir -p /home/ubuntu/go/src/github.com/tendermint - ln -s /vagrant /home/ubuntu/go/src/github.com/tendermint/tendermint + mkdir -p /home/vagrant/go/bin + mkdir -p /home/vagrant/go/src/github.com/tendermint + ln -s /vagrant /home/vagrant/go/src/github.com/tendermint/tendermint - chown -R ubuntu:ubuntu /home/ubuntu/go - chown ubuntu:ubuntu /home/ubuntu/.bash_profile + chown -R vagrant:vagrant /home/vagrant/go + chown vagrant:vagrant /home/vagrant/.bash_profile # get all deps and tools, ready to install/test - su - ubuntu -c 'cd /home/ubuntu/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps' + source /home/vagrant/.bash_profile + cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps SHELL end From 2f147ec000a0f5f4394224c716845a50d4aff1ed Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 22 Jan 2018 14:55:58 +0100 Subject: [PATCH 121/124] Remove upgrade step --- Vagrantfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index abbb719f..9213f32f 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -21,7 +21,6 @@ Vagrant.configure("2") do |config| # install base requirements apt-get update - apt-get upgrade -y apt-get install -y --no-install-recommends wget curl jq \ make shellcheck bsdmainutils psmisc apt-get install -y docker-ce golang-1.9-go From fc860c3a07865772a13c4cb270f7ad97532ccdbb Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 22 Jan 2018 15:33:09 +0100 Subject: [PATCH 122/124] Final Vagrantfile --- Vagrantfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 9213f32f..3979c779 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -26,6 +26,7 @@ Vagrant.configure("2") do |config| apt-get install -y docker-ce golang-1.9-go apt-get install -y language-pack-en + # cleanup apt-get autoremove -y # needed for docker @@ -44,7 +45,7 @@ Vagrant.configure("2") do |config| chown vagrant:vagrant /home/vagrant/.bash_profile # get all deps and tools, ready to install/test - source /home/vagrant/.bash_profile - cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps + su - vagrant -c 'source /home/vagrant/.bash_profile' + su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps' SHELL end From 4b63b3aa0ba915607a3e8a20cc96f2e3e306d8ff Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Thu, 25 Jan 2018 11:35:14 +0100 Subject: [PATCH 123/124] Switch to correct directory in Vagrant --- CONTRIBUTING.md | 5 ++++- Vagrantfile | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 787fd718..5b980071 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,9 @@ Run `bash scripts/glide/status.sh` to get a list of vendored dependencies that m If you are a [Vagrant](https://www.vagrantup.com/) user, all you have to do to get started hacking Tendermint is: +In case you installed Vagrant in 2017, you might need to run +`vagrant box update` to upgrade to the latest `ubuntu/xenial64`. + ``` vagrant up vagrant ssh @@ -97,4 +100,4 @@ especially `go-p2p` and `go-rpc`, as their versions are referenced in tendermint - push to hotfix-vX.X.X to run the extended integration tests on the CI - merge hotfix-vX.X.X to master - merge hotfix-vX.X.X to develop -- delete the hotfix-vX.X.X branch \ No newline at end of file +- delete the hotfix-vX.X.X branch diff --git a/Vagrantfile b/Vagrantfile index 3979c779..ee878649 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -36,6 +36,7 @@ Vagrant.configure("2") do |config| echo 'export PATH=$PATH:/usr/lib/go-1.9/bin:/home/vagrant/go/bin' >> /home/vagrant/.bash_profile echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile + echo 'cd go/src/github.com/tendermint/tendermint' >> /home/vagrant/.bash_profile mkdir -p /home/vagrant/go/bin mkdir -p /home/vagrant/go/src/github.com/tendermint From 4a99a2a07d47407dcfdc7d8ae4adf979933efe05 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 26 Jan 2018 01:18:33 -0500 Subject: [PATCH 124/124] update contributing.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b980071..b991bcc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,18 +42,18 @@ Run `bash scripts/glide/status.sh` to get a list of vendored dependencies that m ## Vagrant -If you are a [Vagrant](https://www.vagrantup.com/) user, all you have to do to get started hacking Tendermint is: +If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started hacking Tendermint with the commands below. -In case you installed Vagrant in 2017, you might need to run +NOTE: In case you installed Vagrant in 2017, you might need to run `vagrant box update` to upgrade to the latest `ubuntu/xenial64`. ``` vagrant up vagrant ssh -cd ~/go/src/github.com/tendermint/tendermint make test ``` + ## Testing All repos should be hooked up to circle.