You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1199 lines
36 KiB

p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ```
6 years ago
p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ```
6 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ```
6 years ago
p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ```
6 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
  1. package p2p
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "math"
  8. "math/rand"
  9. "net"
  10. "net/url"
  11. "runtime/debug"
  12. "sort"
  13. "strconv"
  14. "sync"
  15. "time"
  16. "github.com/tendermint/tendermint/libs/cmap"
  17. "github.com/tendermint/tendermint/libs/log"
  18. "github.com/tendermint/tendermint/libs/service"
  19. tmconn "github.com/tendermint/tendermint/p2p/conn"
  20. )
  21. // PeerAddress is a peer address URL.
  22. type PeerAddress struct {
  23. *url.URL
  24. }
  25. // ParsePeerAddress parses a peer address URL into a PeerAddress.
  26. func ParsePeerAddress(address string) (PeerAddress, error) {
  27. u, err := url.Parse(address)
  28. if err != nil || u == nil {
  29. return PeerAddress{}, fmt.Errorf("unable to parse peer address %q: %w", address, err)
  30. }
  31. if u.Scheme == "" {
  32. u.Scheme = string(defaultProtocol)
  33. }
  34. pa := PeerAddress{URL: u}
  35. if err = pa.Validate(); err != nil {
  36. return PeerAddress{}, err
  37. }
  38. return pa, nil
  39. }
  40. // NodeID returns the address node ID.
  41. func (a PeerAddress) NodeID() NodeID {
  42. return NodeID(a.User.Username())
  43. }
  44. // Resolve resolves a PeerAddress into a set of Endpoints, by expanding
  45. // out a DNS name in Host to its IP addresses. Field mapping:
  46. //
  47. // Scheme → Endpoint.Protocol
  48. // Host → Endpoint.IP
  49. // User → Endpoint.PeerID
  50. // Port → Endpoint.Port
  51. // Path+Query+Fragment,Opaque → Endpoint.Path
  52. //
  53. func (a PeerAddress) Resolve(ctx context.Context) ([]Endpoint, error) {
  54. ips, err := net.DefaultResolver.LookupIP(ctx, "ip", a.Host)
  55. if err != nil {
  56. return nil, err
  57. }
  58. port, err := a.parsePort()
  59. if err != nil {
  60. return nil, err
  61. }
  62. path := a.Path
  63. if a.RawPath != "" {
  64. path = a.RawPath
  65. }
  66. if a.Opaque != "" { // used for e.g. "about:blank" style URLs
  67. path = a.Opaque
  68. }
  69. if a.RawQuery != "" {
  70. path += "?" + a.RawQuery
  71. }
  72. if a.RawFragment != "" {
  73. path += "#" + a.RawFragment
  74. }
  75. endpoints := make([]Endpoint, len(ips))
  76. for i, ip := range ips {
  77. endpoints[i] = Endpoint{
  78. PeerID: a.NodeID(),
  79. Protocol: Protocol(a.Scheme),
  80. IP: ip,
  81. Port: port,
  82. Path: path,
  83. }
  84. }
  85. return endpoints, nil
  86. }
  87. // Validates validates a PeerAddress.
  88. func (a PeerAddress) Validate() error {
  89. if a.Scheme == "" {
  90. return errors.New("no protocol")
  91. }
  92. if id := a.User.Username(); id == "" {
  93. return errors.New("no peer ID")
  94. } else if err := NodeID(id).Validate(); err != nil {
  95. return fmt.Errorf("invalid peer ID: %w", err)
  96. }
  97. if a.Hostname() == "" && len(a.Query()) == 0 && a.Opaque == "" {
  98. return errors.New("no host or path given")
  99. }
  100. if port, err := a.parsePort(); err != nil {
  101. return err
  102. } else if port > 0 && a.Hostname() == "" {
  103. return errors.New("cannot specify port without host")
  104. }
  105. return nil
  106. }
  107. // parsePort returns the port number as a uint16.
  108. func (a PeerAddress) parsePort() (uint16, error) {
  109. if portString := a.Port(); portString != "" {
  110. port64, err := strconv.ParseUint(portString, 10, 16)
  111. if err != nil {
  112. return 0, fmt.Errorf("invalid port %q: %w", portString, err)
  113. }
  114. return uint16(port64), nil
  115. }
  116. return 0, nil
  117. }
  118. // PeerStatus specifies peer statuses.
  119. type PeerStatus string
  120. const (
  121. PeerStatusNew = PeerStatus("new") // New peer which we haven't tried to contact yet.
  122. PeerStatusUp = PeerStatus("up") // Peer which we have an active connection to.
  123. PeerStatusDown = PeerStatus("down") // Peer which we're temporarily disconnected from.
  124. PeerStatusRemoved = PeerStatus("removed") // Peer which has been removed.
  125. PeerStatusBanned = PeerStatus("banned") // Peer which is banned for misbehavior.
  126. )
  127. // PeerError is a peer error reported by a reactor via the Error channel. The
  128. // severity may cause the peer to be disconnected or banned depending on policy.
  129. type PeerError struct {
  130. PeerID NodeID
  131. Err error
  132. Severity PeerErrorSeverity
  133. }
  134. // PeerErrorSeverity determines the severity of a peer error.
  135. type PeerErrorSeverity string
  136. const (
  137. PeerErrorSeverityLow PeerErrorSeverity = "low" // Mostly ignored.
  138. PeerErrorSeverityHigh PeerErrorSeverity = "high" // May disconnect.
  139. PeerErrorSeverityCritical PeerErrorSeverity = "critical" // Ban.
  140. )
  141. // PeerUpdatesCh defines a wrapper around a PeerUpdate go channel that allows
  142. // a reactor to listen for peer updates and safely close it when stopping.
  143. type PeerUpdatesCh struct {
  144. closeOnce sync.Once
  145. // updatesCh defines the go channel in which the router sends peer updates to
  146. // reactors. Each reactor will have its own PeerUpdatesCh to listen for updates
  147. // from.
  148. updatesCh chan PeerUpdate
  149. // doneCh is used to signal that a PeerUpdatesCh is closed. It is the
  150. // reactor's responsibility to invoke Close.
  151. doneCh chan struct{}
  152. }
  153. // NewPeerUpdates returns a reference to a new PeerUpdatesCh.
  154. func NewPeerUpdates(updatesCh chan PeerUpdate) *PeerUpdatesCh {
  155. return &PeerUpdatesCh{
  156. updatesCh: updatesCh,
  157. doneCh: make(chan struct{}),
  158. }
  159. }
  160. // Updates returns a read-only go channel where a consuming reactor can listen
  161. // for peer updates sent from the router.
  162. func (puc *PeerUpdatesCh) Updates() <-chan PeerUpdate {
  163. return puc.updatesCh
  164. }
  165. // Close closes the PeerUpdatesCh channel. It should only be closed by the respective
  166. // reactor when stopping and ensure nothing is listening for updates.
  167. //
  168. // NOTE: After a PeerUpdatesCh is closed, the router may safely assume it can no
  169. // longer send on the internal updatesCh, however it should NEVER explicitly close
  170. // it as that could result in panics by sending on a closed channel.
  171. func (puc *PeerUpdatesCh) Close() {
  172. puc.closeOnce.Do(func() {
  173. close(puc.doneCh)
  174. })
  175. }
  176. // Done returns a read-only version of the PeerUpdatesCh's internal doneCh go
  177. // channel that should be used by a router to signal when it is safe to explicitly
  178. // not send any peer updates.
  179. func (puc *PeerUpdatesCh) Done() <-chan struct{} {
  180. return puc.doneCh
  181. }
  182. // PeerUpdate is a peer status update for reactors.
  183. type PeerUpdate struct {
  184. PeerID NodeID
  185. Status PeerStatus
  186. }
  187. // PeerScore is a numeric score assigned to a peer (higher is better).
  188. type PeerScore uint16
  189. const (
  190. // PeerScorePersistent is added for persistent peers.
  191. PeerScorePersistent PeerScore = 100
  192. )
  193. // PeerManager manages peer lifecycle information, using a peerStore for
  194. // underlying storage. Its primary purpose is to determine which peers to
  195. // connect to next, make sure a peer only has a single active connection (either
  196. // inbound or outbound), and evict peers to make room for higher-scored peers.
  197. // It does not manage actual connections (this is handled by the Router),
  198. // only the peer lifecycle state.
  199. //
  200. // For an outbound connection, the flow is as follows:
  201. // - DialNext: returns a peer address to dial, marking the peer as dialing.
  202. // - DialFailed: reports a dial failure, unmarking the peer as dialing.
  203. // - Dialed: successfully dialed, unmarking as dialing and marking as connected
  204. // (or erroring if already connected).
  205. // - Ready: routing is up, broadcasts a PeerStatusUp peer update to subscribers.
  206. // - Disconnected: peer disconnects, unmarking as connected and broadcasts a
  207. // PeerStatusDown peer update.
  208. //
  209. // For an inbound connection, the flow is as follows:
  210. // - Accepted: successfully accepted connection, marking as connected (or erroring
  211. // if already connected).
  212. // - Ready: routing is up, broadcasts a PeerStatusUp peer update to subscribers.
  213. // - Disconnected: peer disconnects, unmarking as connected and broadcasts a
  214. // PeerStatusDown peer update.
  215. //
  216. // If we need to evict a peer, typically because we have connected to additional
  217. // higher-scored peers and need to shed lower-scored ones, the flow is as follows:
  218. // - EvictNext: returns a peer ID to evict, marking peer as evicting.
  219. // - Disconnected: peer was disconnected, unmarking as connected and evicting,
  220. // and broadcasts a PeerStatusDown peer update.
  221. //
  222. // We track dialing and connected states independently. This allows us to accept
  223. // an inbound connection from a peer while the router is also dialing an
  224. // outbound connection to that same peer, which will cause the dialer to
  225. // eventually error (when attempting to mark the peer as connected). This also
  226. // avoids race conditions where multiple goroutines may end up dialing a peer if
  227. // an incoming connection was briefly accepted and disconnected while we were
  228. // also dialing.
  229. type PeerManager struct {
  230. options PeerManagerOptions
  231. mtx sync.Mutex
  232. store *peerStore
  233. dialing map[NodeID]bool
  234. connected map[NodeID]bool
  235. evicting map[NodeID]bool
  236. subscriptions map[*PeerUpdatesCh]*PeerUpdatesCh // keyed by struct identity (address)
  237. }
  238. // PeerManagerOptions specifies options for a PeerManager.
  239. type PeerManagerOptions struct {
  240. // PersistentPeers are peers that we want to maintain persistent connections
  241. // to. These will be scored higher than other peers, and if
  242. // MaxConnectedUpgrade is non-zero any lower-scored peers will be evicted if
  243. // necessary to make room for these.
  244. PersistentPeers []NodeID
  245. // MaxConnected is the maximum number of connected peers (inbound and
  246. // outbound). 0 means no limit.
  247. MaxConnected uint16
  248. // MaxConnectedUpgrade is the maximum number of additional connections to
  249. // use for probing any better-scored peers to upgrade to when all connection
  250. // slots are full. 0 disables peer upgrading.
  251. //
  252. // For example, if we are already connected to MaxConnected peers, but we
  253. // know or learn about better-scored peers (e.g. configured persistent
  254. // peers) that we are not connected too, then we can probe these peers by
  255. // using up to MaxConnectedUpgrade connections, and once connected evict the
  256. // lowest-scored connected peers. This also works for inbound connections,
  257. // i.e. if a higher-scored peer attempts to connect to us, we can accept
  258. // the connection and evict a lower-scored peer.
  259. MaxConnectedUpgrade uint16
  260. // MinRetryTime is the minimum time to wait between retries. Retry times
  261. // double for each retry, up to MaxRetryTime. 0 disables retries.
  262. MinRetryTime time.Duration
  263. // MaxRetryTime is the maximum time to wait between retries. 0 means
  264. // no maximum, in which case the retry time will keep doubling.
  265. MaxRetryTime time.Duration
  266. // MaxRetryTimePersistent is the maximum time to wait between retries for
  267. // peers listed in PersistentPeers. 0 uses MaxRetryTime instead.
  268. MaxRetryTimePersistent time.Duration
  269. // RetryTimeJitter is the upper bound of a random interval added to
  270. // retry times, to avoid thundering herds. 0 disables jutter.
  271. RetryTimeJitter time.Duration
  272. }
  273. // isPersistent is a convenience function that checks if the given peer ID
  274. // is contained in PersistentPeers. It just uses a linear search, since
  275. // PersistentPeers is expected to be small.
  276. func (o PeerManagerOptions) isPersistent(id NodeID) bool {
  277. for _, p := range o.PersistentPeers {
  278. if id == p {
  279. return true
  280. }
  281. }
  282. return false
  283. }
  284. // NewPeerManager creates a new peer manager.
  285. func NewPeerManager(options PeerManagerOptions) *PeerManager {
  286. return &PeerManager{
  287. options: options,
  288. // FIXME: Once the store persists data, we need to update existing
  289. // peers in the store with any new information, e.g. changes to
  290. // PersistentPeers configuration.
  291. store: newPeerStore(),
  292. dialing: map[NodeID]bool{},
  293. connected: map[NodeID]bool{},
  294. evicting: map[NodeID]bool{},
  295. subscriptions: map[*PeerUpdatesCh]*PeerUpdatesCh{},
  296. }
  297. }
  298. // Add adds a peer to the manager, given as an address. If the peer already
  299. // exists, the address is added to it.
  300. func (m *PeerManager) Add(address PeerAddress) error {
  301. if err := address.Validate(); err != nil {
  302. return err
  303. }
  304. m.mtx.Lock()
  305. defer m.mtx.Unlock()
  306. peer, err := m.store.Get(address.NodeID())
  307. if err != nil {
  308. return err
  309. }
  310. if peer == nil {
  311. peer = &peerInfo{
  312. ID: address.NodeID(),
  313. Persistent: m.options.isPersistent(address.NodeID()),
  314. }
  315. }
  316. peer.AddAddress(address)
  317. return m.store.Set(peer)
  318. }
  319. // Subscribe subscribes to peer updates. The caller must consume the peer
  320. // updates in a timely fashion and close the subscription when done, since
  321. // delivery is guaranteed and will block peer connection/disconnection
  322. // otherwise.
  323. func (m *PeerManager) Subscribe() *PeerUpdatesCh {
  324. // FIXME: We may want to use a size 1 buffer here. When the router
  325. // broadcasts a peer update it has to loop over all of the
  326. // subscriptions, and we want to avoid blocking and waiting for a
  327. // context switch before continuing to the next subscription. This also
  328. // prevents tail latencies from compounding across updates. We also want
  329. // to make sure the subscribers are reasonably in sync, so it should be
  330. // kept at 1. However, this should be benchmarked first.
  331. peerUpdates := NewPeerUpdates(make(chan PeerUpdate))
  332. m.mtx.Lock()
  333. m.subscriptions[peerUpdates] = peerUpdates
  334. m.mtx.Unlock()
  335. go func() {
  336. <-peerUpdates.Done()
  337. m.mtx.Lock()
  338. delete(m.subscriptions, peerUpdates)
  339. m.mtx.Unlock()
  340. }()
  341. return peerUpdates
  342. }
  343. // broadcast broadcasts a peer update to all subscriptions. The caller must
  344. // already hold the mutex lock. This means the mutex is held for the duration
  345. // of the broadcast, which we want to make sure all subscriptions receive all
  346. // updates in the same order.
  347. //
  348. // FIXME: Consider using more fine-grained mutexes here, and/or a channel to
  349. // enforce ordering of updates.
  350. func (m *PeerManager) broadcast(peerUpdate PeerUpdate) {
  351. for _, sub := range m.subscriptions {
  352. select {
  353. case sub.updatesCh <- peerUpdate:
  354. case <-sub.doneCh:
  355. }
  356. }
  357. }
  358. // DialNext finds an appropriate peer address to dial, and marks it as dialing.
  359. // The peer will not be returned again until Dialed() or DialFailed() is called
  360. // for the peer and it is no longer connected. Returns an empty ID if no
  361. // appropriate peers are available, or if all connection slots are full.
  362. //
  363. // We allow dialing MaxConnected+MaxConnectedUpgrade peers. Including
  364. // MaxConnectedUpgrade allows us to dial additional peers beyond MaxConnected if
  365. // they have a higher score than any other connected or dialing peer. If we are
  366. // successful in dialing, and thus have more than MaxConnected connected peers,
  367. // the lower-scored peer will be evicted via EvictNext().
  368. func (m *PeerManager) DialNext() (NodeID, PeerAddress, error) {
  369. m.mtx.Lock()
  370. defer m.mtx.Unlock()
  371. if m.options.MaxConnected > 0 &&
  372. len(m.connected)+len(m.dialing) >= int(m.options.MaxConnected)+int(m.options.MaxConnectedUpgrade) {
  373. return "", PeerAddress{}, nil
  374. }
  375. ranked, err := m.store.Ranked()
  376. if err != nil {
  377. return "", PeerAddress{}, err
  378. }
  379. for _, peer := range ranked {
  380. if m.dialing[peer.ID] || m.connected[peer.ID] {
  381. continue
  382. }
  383. for _, addressInfo := range peer.AddressInfo {
  384. if time.Since(addressInfo.LastDialFailure) < m.retryDelay(peer, addressInfo.DialFailures) {
  385. continue
  386. }
  387. // At this point we have an eligible address to dial. If we're full
  388. // but have peer upgrade capacity (as checked above), we need to
  389. // make sure there exists an evictable peer of a lower score that we
  390. // can replace. If so, we can go ahead and dial this peer, and
  391. // EvictNext() will evict a lower-scored one later.
  392. //
  393. // If we don't find one, there is no point in trying additional
  394. // peers, since they will all have the same or lower score than this
  395. // peer (since they're ordered by score via peerStore.Ranked).
  396. //
  397. // FIXME: There is a race condition here where, if there exists a
  398. // single lower-scored peer, we may end up dialing multiple
  399. // higher-scored new peers that all expect the same lower-scored
  400. // peer to be evicted, causing us to take on too many peers. We may
  401. // need to reserve the eviction for this specific peer such that
  402. // others can't claim it.
  403. if m.options.MaxConnected > 0 &&
  404. len(m.connected) >= int(m.options.MaxConnected) &&
  405. !m.peerIsUpgrade(peer, ranked) {
  406. return "", PeerAddress{}, nil
  407. }
  408. m.dialing[peer.ID] = true
  409. return peer.ID, addressInfo.Address, nil
  410. }
  411. }
  412. return "", PeerAddress{}, nil
  413. }
  414. // retryDelay calculates a dial retry delay using exponential backoff, based on
  415. // retry settings in PeerManagerOptions. If MinRetryTime is 0, this returns
  416. // MaxInt64 (i.e. an infinite retry delay, effectively disabling retries).
  417. func (m *PeerManager) retryDelay(peer *peerInfo, failures uint32) time.Duration {
  418. if failures == 0 {
  419. return 0
  420. }
  421. if m.options.MinRetryTime == 0 {
  422. return time.Duration(math.MaxInt64)
  423. }
  424. maxDelay := m.options.MaxRetryTime
  425. if peer.Persistent && m.options.MaxRetryTimePersistent > 0 {
  426. maxDelay = m.options.MaxRetryTimePersistent
  427. }
  428. delay := m.options.MinRetryTime * time.Duration(math.Pow(2, float64(failures)))
  429. if maxDelay > 0 && delay > maxDelay {
  430. delay = maxDelay
  431. }
  432. // FIXME: This should use a PeerManager-scoped RNG.
  433. delay += time.Duration(rand.Int63n(int64(m.options.RetryTimeJitter))) // nolint:gosec
  434. return delay
  435. }
  436. // DialFailed reports a failed dial attempt. This will make the peer available
  437. // for dialing again when appropriate.
  438. //
  439. // FIXME: This should probably delete or mark bad addresses/peers after some time.
  440. func (m *PeerManager) DialFailed(peerID NodeID, address PeerAddress) error {
  441. m.mtx.Lock()
  442. defer m.mtx.Unlock()
  443. delete(m.dialing, peerID)
  444. peer, err := m.store.Get(peerID)
  445. if err != nil || peer == nil { // Peer may have been removed while dialing, ignore.
  446. return err
  447. }
  448. if addressInfo := peer.LookupAddressInfo(address); addressInfo != nil {
  449. addressInfo.LastDialFailure = time.Now().UTC()
  450. addressInfo.DialFailures++
  451. return m.store.Set(peer)
  452. }
  453. return nil
  454. }
  455. // Dialed marks a peer as successfully dialed. Any further incoming connections
  456. // will be rejected, and once disconnected the peer may be dialed again.
  457. func (m *PeerManager) Dialed(peerID NodeID, address PeerAddress) error {
  458. m.mtx.Lock()
  459. defer m.mtx.Unlock()
  460. delete(m.dialing, peerID)
  461. if m.connected[peerID] {
  462. return fmt.Errorf("peer %v is already connected", peerID)
  463. }
  464. if m.options.MaxConnected > 0 &&
  465. len(m.connected) >= int(m.options.MaxConnected)+int(m.options.MaxConnectedUpgrade) {
  466. return fmt.Errorf("already connected to maximum number of peers")
  467. }
  468. peer, err := m.store.Get(peerID)
  469. if err != nil {
  470. return err
  471. } else if peer == nil {
  472. return fmt.Errorf("peer %q was removed while dialing", peerID)
  473. }
  474. m.connected[peerID] = true
  475. now := time.Now().UTC()
  476. peer.LastConnected = now
  477. if addressInfo := peer.LookupAddressInfo(address); addressInfo != nil {
  478. addressInfo.DialFailures = 0
  479. addressInfo.LastDialSuccess = now
  480. }
  481. return m.store.Set(peer)
  482. }
  483. // Accepted marks an incoming peer connection successfully accepted. If the peer
  484. // is already connected or we don't allow additional connections then this will
  485. // return an error.
  486. //
  487. // If MaxConnectedUpgrade is non-zero, the accepted peer is better-scored than any
  488. // other connected peer, and the number of connections does not exceed
  489. // MaxConnected + MaxConnectedUpgrade then we accept the connection and rely on
  490. // EvictNext() to evict lower-scored peers.
  491. //
  492. // NOTE: We can't take an address here, since e.g. TCP uses a different port
  493. // number for outbound traffic than inbound traffic, so the peer's endpoint
  494. // wouldn't necessarily be an appropriate address to dial.
  495. func (m *PeerManager) Accepted(peerID NodeID) error {
  496. m.mtx.Lock()
  497. defer m.mtx.Unlock()
  498. if m.connected[peerID] {
  499. return fmt.Errorf("peer %q is already connected", peerID)
  500. }
  501. if m.options.MaxConnected > 0 &&
  502. len(m.connected) >= int(m.options.MaxConnected)+int(m.options.MaxConnectedUpgrade) {
  503. return fmt.Errorf("already connected to maximum number of peers")
  504. }
  505. peer, err := m.store.Get(peerID)
  506. if err != nil {
  507. return err
  508. }
  509. if peer == nil {
  510. peer = &peerInfo{
  511. ID: peerID,
  512. Persistent: m.options.isPersistent(peerID),
  513. }
  514. }
  515. // If we're already full (i.e. at MaxConnected), but we allow upgrades (and we
  516. // know from the check above that we have upgrade capacity), then we can look
  517. // for a lower-scored evictable peer, and if found we can accept this connection
  518. // anyway and let EvictNext() evict the lower-scored peer for us.
  519. //
  520. // FIXME: There is a race condition here where, if there exists a single
  521. // lower-scored peer, we may end up accepting multiple higher-scored new
  522. // peers that all expect the same lower-scored peer to be evicted, causing
  523. // us to take on too many peers. We may need to reserve the eviction for
  524. // this specific peer such that others can't claim it.
  525. if m.options.MaxConnected > 0 && len(m.connected) >= int(m.options.MaxConnected) {
  526. ranked, err := m.store.Ranked()
  527. if err != nil {
  528. return err
  529. }
  530. if !m.peerIsUpgrade(peer, ranked) {
  531. return fmt.Errorf("already connected to maximum number of peers")
  532. }
  533. }
  534. m.connected[peerID] = true
  535. peer.LastConnected = time.Now().UTC()
  536. return m.store.Set(peer)
  537. }
  538. // Ready marks a peer as ready, broadcasting status updates to subscribers. The
  539. // peer must already be marked as connected. This is separate from Dialed() and
  540. // Accepted() to allow the router to set up its internal queues before reactors
  541. // start sending messages.
  542. func (m *PeerManager) Ready(peerID NodeID) {
  543. m.mtx.Lock()
  544. defer m.mtx.Unlock()
  545. connected := m.connected[peerID]
  546. if connected {
  547. m.broadcast(PeerUpdate{
  548. PeerID: peerID,
  549. Status: PeerStatusUp,
  550. })
  551. }
  552. }
  553. // Disconnected unmarks a peer as connected, allowing new connections to be
  554. // established.
  555. func (m *PeerManager) Disconnected(peerID NodeID) error {
  556. m.mtx.Lock()
  557. defer m.mtx.Unlock()
  558. delete(m.connected, peerID)
  559. delete(m.evicting, peerID)
  560. m.broadcast(PeerUpdate{
  561. PeerID: peerID,
  562. Status: PeerStatusDown,
  563. })
  564. return nil
  565. }
  566. // EvictNext returns the next peer to evict (i.e. disconnect), or an empty ID if
  567. // no peers should be evicted. The evicted peer will be a lowest-scored peer
  568. // that is currently connected and not already being evicted.
  569. func (m *PeerManager) EvictNext() (NodeID, error) {
  570. m.mtx.Lock()
  571. defer m.mtx.Unlock()
  572. if m.options.MaxConnected == 0 ||
  573. len(m.connected)-len(m.evicting) <= int(m.options.MaxConnected) {
  574. return "", nil
  575. }
  576. ranked, err := m.store.Ranked()
  577. if err != nil {
  578. return "", err
  579. }
  580. for i := len(ranked) - 1; i >= 0; i-- {
  581. peer := ranked[i]
  582. if m.connected[peer.ID] && !m.evicting[peer.ID] {
  583. m.evicting[peer.ID] = true
  584. return peer.ID, nil
  585. }
  586. }
  587. return "", nil
  588. }
  589. // peerIsUpgrade checks whether connecting to a given peer would be an
  590. // upgrade, i.e. that there exists a lower-scored peer that is already
  591. // connected and not scheduled for eviction, such that connecting to
  592. // the peer would cause a lower-scored peer to be evicted if we're full.
  593. func (m *PeerManager) peerIsUpgrade(peer *peerInfo, ranked []*peerInfo) bool {
  594. for i := len(ranked) - 1; i >= 0; i-- {
  595. candidate := ranked[i]
  596. if candidate.Score() >= peer.Score() {
  597. return false
  598. }
  599. if m.connected[candidate.ID] && !m.evicting[candidate.ID] {
  600. return true
  601. }
  602. }
  603. return false
  604. }
  605. // GetHeight returns a peer's height, as reported via SetHeight. If the peer
  606. // or height is unknown, this returns 0.
  607. //
  608. // FIXME: This is a temporary workaround for the peer state stored via the
  609. // legacy Peer.Set() and Peer.Get() APIs, used to share height state between the
  610. // consensus and mempool reactors. These dependencies should be removed from the
  611. // reactors, and instead query this information independently via new P2P
  612. // protocol additions.
  613. func (m *PeerManager) GetHeight(peerID NodeID) (int64, error) {
  614. m.mtx.Lock()
  615. defer m.mtx.Unlock()
  616. peer, err := m.store.Get(peerID)
  617. if err != nil || peer == nil {
  618. return 0, err
  619. }
  620. return peer.Height, nil
  621. }
  622. // SetHeight stores a peer's height, making it available via GetHeight. If the
  623. // peer is unknown, it is created.
  624. //
  625. // FIXME: This is a temporary workaround for the peer state stored via the
  626. // legacy Peer.Set() and Peer.Get() APIs, used to share height state between the
  627. // consensus and mempool reactors. These dependencies should be removed from the
  628. // reactors, and instead query this information independently via new P2P
  629. // protocol additions.
  630. func (m *PeerManager) SetHeight(peerID NodeID, height int64) error {
  631. m.mtx.Lock()
  632. defer m.mtx.Unlock()
  633. peer, err := m.store.Get(peerID)
  634. if err != nil {
  635. return err
  636. }
  637. if peer == nil {
  638. peer = &peerInfo{
  639. ID: peerID,
  640. Persistent: m.options.isPersistent(peerID),
  641. }
  642. }
  643. peer.Height = height
  644. return m.store.Set(peer)
  645. }
  646. // peerStore stores information about peers. It is currently a bare-bones
  647. // in-memory store, and will be fleshed out later.
  648. //
  649. // peerStore is not thread-safe, since it assumes it is only used by PeerManager
  650. // which handles concurrency control. This allows the manager to execute multiple
  651. // operations atomically while it holds the mutex.
  652. type peerStore struct {
  653. peers map[NodeID]peerInfo
  654. }
  655. // newPeerStore creates a new peer store.
  656. func newPeerStore() *peerStore {
  657. return &peerStore{
  658. peers: map[NodeID]peerInfo{},
  659. }
  660. }
  661. // Get fetches a peer, returning nil if not found.
  662. func (s *peerStore) Get(id NodeID) (*peerInfo, error) {
  663. peer, ok := s.peers[id]
  664. if !ok {
  665. return nil, nil
  666. }
  667. return &peer, nil
  668. }
  669. // Set stores peer data.
  670. func (s *peerStore) Set(peer *peerInfo) error {
  671. if peer == nil {
  672. return errors.New("peer cannot be nil")
  673. }
  674. s.peers[peer.ID] = *peer
  675. return nil
  676. }
  677. // List retrieves all peers.
  678. func (s *peerStore) List() ([]*peerInfo, error) {
  679. peers := []*peerInfo{}
  680. for _, peer := range s.peers {
  681. peer := peer
  682. peers = append(peers, &peer)
  683. }
  684. return peers, nil
  685. }
  686. // Ranked returns a list of peers ordered by score (better peers first).
  687. // Peers with equal scores are returned in an arbitrary order.
  688. //
  689. // This is used to determine which peers to connect to and which peers to evict
  690. // in order to make room for better peers.
  691. //
  692. // FIXME: For now, we simply generate the list on every call, but this can get
  693. // expensive since it's called fairly frequently. We may want to either cache
  694. // this, or store peers in a data structure that maintains order (e.g. a heap or
  695. // ordered map).
  696. func (s *peerStore) Ranked() ([]*peerInfo, error) {
  697. peers, err := s.List()
  698. if err != nil {
  699. return nil, err
  700. }
  701. sort.Slice(peers, func(i, j int) bool {
  702. // FIXME: If necessary, consider precomputing scores before sorting,
  703. // to reduce the number of Score() calls.
  704. return peers[i].Score() > peers[j].Score()
  705. })
  706. return peers, nil
  707. }
  708. // peerInfo contains peer information stored in a peerStore.
  709. type peerInfo struct {
  710. ID NodeID
  711. AddressInfo []*addressInfo
  712. Persistent bool
  713. Height int64
  714. LastConnected time.Time
  715. }
  716. // AddAddress adds an address to a peer, unless it already exists. It does not
  717. // validate the address. Returns true if the address was new.
  718. func (p *peerInfo) AddAddress(address PeerAddress) bool {
  719. if p.LookupAddressInfo(address) != nil {
  720. return false
  721. }
  722. p.AddressInfo = append(p.AddressInfo, &addressInfo{Address: address})
  723. return true
  724. }
  725. // LookupAddressInfo returns address info for an address, or nil if unknown.
  726. func (p *peerInfo) LookupAddressInfo(address PeerAddress) *addressInfo {
  727. // We just do a linear search for now.
  728. addressString := address.String()
  729. for _, info := range p.AddressInfo {
  730. if info.Address.String() == addressString {
  731. return info
  732. }
  733. }
  734. return nil
  735. }
  736. // Score calculates a score for the peer. Higher-scored peers will be
  737. // preferred over lower scores.
  738. func (p *peerInfo) Score() PeerScore {
  739. var score PeerScore
  740. if p.Persistent {
  741. score += PeerScorePersistent
  742. }
  743. return score
  744. }
  745. // addressInfo contains information and statistics about an address.
  746. type addressInfo struct {
  747. Address PeerAddress
  748. LastDialSuccess time.Time
  749. LastDialFailure time.Time
  750. DialFailures uint32 // since last successful dial
  751. }
  752. // ============================================================================
  753. // Types and business logic below may be deprecated.
  754. //
  755. // TODO: Rename once legacy p2p types are removed.
  756. // ref: https://github.com/tendermint/tendermint/issues/5670
  757. // ============================================================================
  758. //go:generate mockery --case underscore --name Peer
  759. const metricsTickerDuration = 10 * time.Second
  760. // Peer is an interface representing a peer connected on a reactor.
  761. type Peer interface {
  762. service.Service
  763. FlushStop()
  764. ID() NodeID // peer's cryptographic ID
  765. RemoteIP() net.IP // remote IP of the connection
  766. RemoteAddr() net.Addr // remote address of the connection
  767. IsOutbound() bool // did we dial the peer
  768. IsPersistent() bool // do we redial this peer when we disconnect
  769. CloseConn() error // close original connection
  770. NodeInfo() NodeInfo // peer's info
  771. Status() tmconn.ConnectionStatus
  772. SocketAddr() *NetAddress // actual address of the socket
  773. Send(byte, []byte) bool
  774. TrySend(byte, []byte) bool
  775. Set(string, interface{})
  776. Get(string) interface{}
  777. }
  778. //----------------------------------------------------------
  779. // peerConn contains the raw connection and its config.
  780. type peerConn struct {
  781. outbound bool
  782. persistent bool
  783. conn Connection
  784. ip net.IP // cached RemoteIP()
  785. }
  786. func newPeerConn(outbound, persistent bool, conn Connection) peerConn {
  787. return peerConn{
  788. outbound: outbound,
  789. persistent: persistent,
  790. conn: conn,
  791. }
  792. }
  793. // ID only exists for SecretConnection.
  794. func (pc peerConn) ID() NodeID {
  795. return NodeIDFromPubKey(pc.conn.PubKey())
  796. }
  797. // Return the IP from the connection RemoteAddr
  798. func (pc peerConn) RemoteIP() net.IP {
  799. if pc.ip == nil {
  800. pc.ip = pc.conn.RemoteEndpoint().IP
  801. }
  802. return pc.ip
  803. }
  804. // peer implements Peer.
  805. //
  806. // Before using a peer, you will need to perform a handshake on connection.
  807. type peer struct {
  808. service.BaseService
  809. // raw peerConn and the multiplex connection
  810. peerConn
  811. // peer's node info and the channel it knows about
  812. // channels = nodeInfo.Channels
  813. // cached to avoid copying nodeInfo in hasChannel
  814. nodeInfo NodeInfo
  815. channels []byte
  816. reactors map[byte]Reactor
  817. onPeerError func(Peer, interface{})
  818. // User data
  819. Data *cmap.CMap
  820. metrics *Metrics
  821. metricsTicker *time.Ticker
  822. }
  823. type PeerOption func(*peer)
  824. func newPeer(
  825. pc peerConn,
  826. reactorsByCh map[byte]Reactor,
  827. onPeerError func(Peer, interface{}),
  828. options ...PeerOption,
  829. ) *peer {
  830. nodeInfo := pc.conn.NodeInfo()
  831. p := &peer{
  832. peerConn: pc,
  833. nodeInfo: nodeInfo,
  834. channels: nodeInfo.Channels, // TODO
  835. reactors: reactorsByCh,
  836. onPeerError: onPeerError,
  837. Data: cmap.NewCMap(),
  838. metricsTicker: time.NewTicker(metricsTickerDuration),
  839. metrics: NopMetrics(),
  840. }
  841. p.BaseService = *service.NewBaseService(nil, "Peer", p)
  842. for _, option := range options {
  843. option(p)
  844. }
  845. return p
  846. }
  847. // onError calls the peer error callback.
  848. func (p *peer) onError(err interface{}) {
  849. p.onPeerError(p, err)
  850. }
  851. // String representation.
  852. func (p *peer) String() string {
  853. if p.outbound {
  854. return fmt.Sprintf("Peer{%v %v out}", p.conn, p.ID())
  855. }
  856. return fmt.Sprintf("Peer{%v %v in}", p.conn, p.ID())
  857. }
  858. //---------------------------------------------------
  859. // Implements service.Service
  860. // SetLogger implements BaseService.
  861. func (p *peer) SetLogger(l log.Logger) {
  862. p.Logger = l
  863. }
  864. // OnStart implements BaseService.
  865. func (p *peer) OnStart() error {
  866. if err := p.BaseService.OnStart(); err != nil {
  867. return err
  868. }
  869. go p.processMessages()
  870. go p.metricsReporter()
  871. return nil
  872. }
  873. // processMessages processes messages received from the connection.
  874. func (p *peer) processMessages() {
  875. defer func() {
  876. if r := recover(); r != nil {
  877. p.Logger.Error("peer message processing panic", "err", r, "stack", string(debug.Stack()))
  878. p.onError(fmt.Errorf("panic during peer message processing: %v", r))
  879. }
  880. }()
  881. for {
  882. chID, msg, err := p.conn.ReceiveMessage()
  883. if err != nil {
  884. p.onError(err)
  885. return
  886. }
  887. reactor, ok := p.reactors[chID]
  888. if !ok {
  889. p.onError(fmt.Errorf("unknown channel %v", chID))
  890. return
  891. }
  892. reactor.Receive(chID, p, msg)
  893. }
  894. }
  895. // FlushStop mimics OnStop but additionally ensures that all successful
  896. // .Send() calls will get flushed before closing the connection.
  897. // NOTE: it is not safe to call this method more than once.
  898. func (p *peer) FlushStop() {
  899. p.metricsTicker.Stop()
  900. p.BaseService.OnStop()
  901. if err := p.conn.FlushClose(); err != nil {
  902. p.Logger.Debug("error while stopping peer", "err", err)
  903. }
  904. }
  905. // OnStop implements BaseService.
  906. func (p *peer) OnStop() {
  907. p.metricsTicker.Stop()
  908. p.BaseService.OnStop()
  909. if err := p.conn.Close(); err != nil {
  910. p.Logger.Debug("error while stopping peer", "err", err)
  911. }
  912. }
  913. //---------------------------------------------------
  914. // Implements Peer
  915. // ID returns the peer's ID - the hex encoded hash of its pubkey.
  916. func (p *peer) ID() NodeID {
  917. return p.nodeInfo.ID()
  918. }
  919. // IsOutbound returns true if the connection is outbound, false otherwise.
  920. func (p *peer) IsOutbound() bool {
  921. return p.peerConn.outbound
  922. }
  923. // IsPersistent returns true if the peer is persitent, false otherwise.
  924. func (p *peer) IsPersistent() bool {
  925. return p.peerConn.persistent
  926. }
  927. // NodeInfo returns a copy of the peer's NodeInfo.
  928. func (p *peer) NodeInfo() NodeInfo {
  929. return p.nodeInfo
  930. }
  931. // SocketAddr returns the address of the socket.
  932. // For outbound peers, it's the address dialed (after DNS resolution).
  933. // For inbound peers, it's the address returned by the underlying connection
  934. // (not what's reported in the peer's NodeInfo).
  935. func (p *peer) SocketAddr() *NetAddress {
  936. return p.peerConn.conn.RemoteEndpoint().NetAddress()
  937. }
  938. // Status returns the peer's ConnectionStatus.
  939. func (p *peer) Status() tmconn.ConnectionStatus {
  940. return p.conn.Status()
  941. }
  942. // Send msg bytes to the channel identified by chID byte. Returns false if the
  943. // send queue is full after timeout, specified by MConnection.
  944. func (p *peer) Send(chID byte, msgBytes []byte) bool {
  945. if !p.IsRunning() {
  946. // see Switch#Broadcast, where we fetch the list of peers and loop over
  947. // them - while we're looping, one peer may be removed and stopped.
  948. return false
  949. } else if !p.hasChannel(chID) {
  950. return false
  951. }
  952. res, err := p.conn.SendMessage(chID, msgBytes)
  953. if err == io.EOF {
  954. return false
  955. } else if err != nil {
  956. p.onError(err)
  957. return false
  958. }
  959. if res {
  960. labels := []string{
  961. "peer_id", string(p.ID()),
  962. "chID", fmt.Sprintf("%#x", chID),
  963. }
  964. p.metrics.PeerSendBytesTotal.With(labels...).Add(float64(len(msgBytes)))
  965. }
  966. return res
  967. }
  968. // TrySend msg bytes to the channel identified by chID byte. Immediately returns
  969. // false if the send queue is full.
  970. func (p *peer) TrySend(chID byte, msgBytes []byte) bool {
  971. if !p.IsRunning() {
  972. return false
  973. } else if !p.hasChannel(chID) {
  974. return false
  975. }
  976. res, err := p.conn.TrySendMessage(chID, msgBytes)
  977. if err == io.EOF {
  978. return false
  979. } else if err != nil {
  980. p.onError(err)
  981. return false
  982. }
  983. if res {
  984. labels := []string{
  985. "peer_id", string(p.ID()),
  986. "chID", fmt.Sprintf("%#x", chID),
  987. }
  988. p.metrics.PeerSendBytesTotal.With(labels...).Add(float64(len(msgBytes)))
  989. }
  990. return res
  991. }
  992. // Get the data for a given key.
  993. func (p *peer) Get(key string) interface{} {
  994. return p.Data.Get(key)
  995. }
  996. // Set sets the data for the given key.
  997. func (p *peer) Set(key string, data interface{}) {
  998. p.Data.Set(key, data)
  999. }
  1000. // hasChannel returns true if the peer reported
  1001. // knowing about the given chID.
  1002. func (p *peer) hasChannel(chID byte) bool {
  1003. for _, ch := range p.channels {
  1004. if ch == chID {
  1005. return true
  1006. }
  1007. }
  1008. // NOTE: probably will want to remove this
  1009. // but could be helpful while the feature is new
  1010. p.Logger.Debug(
  1011. "Unknown channel for peer",
  1012. "channel",
  1013. chID,
  1014. "channels",
  1015. p.channels,
  1016. )
  1017. return false
  1018. }
  1019. // CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all.
  1020. func (p *peer) CloseConn() error {
  1021. return p.peerConn.conn.Close()
  1022. }
  1023. //---------------------------------------------------
  1024. // methods only used for testing
  1025. // TODO: can we remove these?
  1026. // CloseConn closes the underlying connection
  1027. func (pc *peerConn) CloseConn() {
  1028. pc.conn.Close()
  1029. }
  1030. // RemoteAddr returns peer's remote network address.
  1031. func (p *peer) RemoteAddr() net.Addr {
  1032. endpoint := p.conn.RemoteEndpoint()
  1033. return &net.TCPAddr{
  1034. IP: endpoint.IP,
  1035. Port: int(endpoint.Port),
  1036. }
  1037. }
  1038. //---------------------------------------------------
  1039. func PeerMetrics(metrics *Metrics) PeerOption {
  1040. return func(p *peer) {
  1041. p.metrics = metrics
  1042. }
  1043. }
  1044. func (p *peer) metricsReporter() {
  1045. for {
  1046. select {
  1047. case <-p.metricsTicker.C:
  1048. status := p.conn.Status()
  1049. var sendQueueSize float64
  1050. for _, chStatus := range status.Channels {
  1051. sendQueueSize += float64(chStatus.SendQueueSize)
  1052. }
  1053. p.metrics.PeerPendingSendBytes.With("peer_id", string(p.ID())).Set(sendQueueSize)
  1054. case <-p.Quit():
  1055. return
  1056. }
  1057. }
  1058. }