Merge pull request #619 from XinFinOrg/revert-2464-short

Revert EIP 2464
This commit is contained in:
Wanwiset Peerapatanapokin 2024-08-25 02:23:46 +07:00 committed by GitHub
commit 2b8eb6f98e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
210 changed files with 3874 additions and 9985 deletions

View file

@ -29,7 +29,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
) )
@ -86,7 +85,7 @@ func main() {
} }
if *writeAddr { if *writeAddr {
fmt.Printf("%v\n", enode.PubkeyToIDV4(&nodeKey.PublicKey)) fmt.Printf("%v\n", discover.PubkeyID(&nodeKey.PublicKey))
os.Exit(0) os.Exit(0)
} }
@ -119,17 +118,16 @@ func main() {
} }
if *runv5 { if *runv5 {
if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { if _, err := discv5.ListenUDP(nodeKey, conn, realaddr, "", restrictList); err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
} else { } else {
db, _ := enode.OpenDB("")
ln := enode.NewLocalNode(db, nodeKey)
cfg := discover.Config{ cfg := discover.Config{
PrivateKey: nodeKey, PrivateKey: nodeKey,
NetRestrict: restrictList, AnnounceAddr: realaddr,
NetRestrict: restrictList,
} }
if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { if _, err := discover.ListenUDP(conn, cfg); err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
} }

View file

@ -54,8 +54,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -262,10 +262,8 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
return nil, err return nil, err
} }
for _, boot := range enodes { for _, boot := range enodes {
old, err := enode.ParseV4(boot.String()) old, _ := discover.ParseNode(boot.String())
if err != nil { stack.Server().AddPeer(old)
stack.Server().AddPeer(old)
}
} }
// Attach to the client and retrieve and interesting metadatas // Attach to the client and retrieve and interesting metadatas
api, err := stack.Attach() api, err := stack.Attach()

View file

@ -19,20 +19,21 @@
// Here is an example of creating a 2 node network with the first node // Here is an example of creating a 2 node network with the first node
// connected to the second: // connected to the second:
// //
// $ p2psim node create // $ p2psim node create
// Created node01 // Created node01
// //
// $ p2psim node start node01 // $ p2psim node start node01
// Started node01 // Started node01
// //
// $ p2psim node create // $ p2psim node create
// Created node02 // Created node02
// //
// $ p2psim node start node02 // $ p2psim node start node02
// Started node02 // Started node02
//
// $ p2psim node connect node01 node02
// Connected node01 to node02
// //
// $ p2psim node connect node01 node02
// Connected node01 to node02
package main package main
import ( import (
@ -46,7 +47,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations" "github.com/XinFinOrg/XDPoSChain/p2p/simulations"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
@ -282,7 +283,7 @@ func createNode(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
config.ID = enode.PubkeyToIDV4(&privKey.PublicKey) config.ID = discover.PubkeyID(&privKey.PublicKey)
config.PrivateKey = privKey config.PrivateKey = privKey
} }
if services := ctx.String("services"); services != "" { if services := ctx.String("services"); services != "" {

View file

@ -51,8 +51,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/metrics/exp" "github.com/XinFinOrg/XDPoSChain/metrics/exp"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -696,10 +696,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
case ctx.GlobalBool(XDCTestnetFlag.Name): case ctx.GlobalBool(XDCTestnetFlag.Name):
urls = params.TestnetBootnodes urls = params.TestnetBootnodes
} }
cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls))
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
for _, url := range urls { for _, url := range urls {
node, err := enode.ParseV4(url) node, err := discover.ParseNode(url)
if err != nil { if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err) log.Error("Bootstrap URL invalid", "enode", url, "err", err)
continue continue

View file

@ -40,7 +40,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/whisper/mailserver" "github.com/XinFinOrg/XDPoSChain/whisper/mailserver"
whisper "github.com/XinFinOrg/XDPoSChain/whisper/whisperv6" whisper "github.com/XinFinOrg/XDPoSChain/whisper/whisperv6"
@ -174,7 +174,7 @@ func initialize() {
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
done = make(chan struct{}) done = make(chan struct{})
var peers []*enode.Node var peers []*discover.Node
var err error var err error
if *generateKey { if *generateKey {
@ -202,7 +202,7 @@ func initialize() {
if len(*argEnode) == 0 { if len(*argEnode) == 0 {
argEnode = scanLineA("Please enter the peer's enode: ") argEnode = scanLineA("Please enter the peer's enode: ")
} }
peer := enode.MustParseV4(*argEnode) peer := discover.MustParseNode(*argEnode)
peers = append(peers, peer) peers = append(peers, peer)
} }
@ -748,11 +748,11 @@ func requestExpiredMessagesLoop() {
} }
func extractIDFromEnode(s string) []byte { func extractIDFromEnode(s string) []byte {
n, err := enode.ParseV4(s) n, err := discover.ParseNode(s)
if err != nil { if err != nil {
utils.Fatalf("Failed to parse enode: %s", err) utils.Fatalf("Failed to parse enode: %s", err)
} }
return n.ID().Bytes() return n.ID[:]
} }
// obfuscateBloom adds 16 random bits to the the bloom // obfuscateBloom adds 16 random bits to the the bloom

View file

@ -1,247 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package forkid implements EIP-2124 (https://eips.ethereum.org/EIPS/eip-2124).
package forkid
import (
"encoding/binary"
"errors"
"hash/crc32"
"math"
"math/big"
"reflect"
"strings"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
)
var (
// ErrRemoteStale is returned by the validator if a remote fork checksum is a
// subset of our already applied forks, but the announced next fork block is
// not on our already passed chain.
ErrRemoteStale = errors.New("remote needs update")
// ErrLocalIncompatibleOrStale is returned by the validator if a remote fork
// checksum does not match any local checksum variation, signalling that the
// two chains have diverged in the past at some point (possibly at genesis).
ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update")
)
// ID is a fork identifier as defined by EIP-2124.
type ID struct {
Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers
Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known
}
// Filter is a fork id filter to validate a remotely advertised ID.
type Filter func(id ID) error
// NewID calculates the Ethereum fork ID from the chain config and head.
func NewID(chain *core.BlockChain) ID {
return newID(
chain.Config(),
chain.Genesis().Hash(),
chain.CurrentHeader().Number.Uint64(),
)
}
// newID is the internal version of NewID, which takes extracted values as its
// arguments instead of a chain. The reason is to allow testing the IDs without
// having to simulate an entire blockchain.
func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
// Calculate the starting checksum from the genesis hash
hash := crc32.ChecksumIEEE(genesis[:])
// Calculate the current fork checksum and the next fork block
var next uint64
for _, fork := range gatherForks(config) {
if fork <= head {
// Fork already passed, checksum the previous hash and the fork number
hash = checksumUpdate(hash, fork)
continue
}
next = fork
break
}
return ID{Hash: checksumToBytes(hash), Next: next}
}
// NewFilter creates an filter that returns if a fork ID should be rejected or not
// based on the local chain's status.
func NewFilter(chain *core.BlockChain) Filter {
return newFilter(
chain.Config(),
chain.Genesis().Hash(),
func() uint64 {
return chain.CurrentHeader().Number.Uint64()
},
)
}
// newFilter is the internal version of NewFilter, taking closures as its arguments
// instead of a chain. The reason is to allow testing it without having to simulate
// an entire blockchain.
func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) func(id ID) error {
// Calculate the all the valid fork hash and fork next combos
var (
forks = gatherForks(config)
sums = make([][4]byte, len(forks)+1) // 0th is the genesis
)
hash := crc32.ChecksumIEEE(genesis[:])
sums[0] = checksumToBytes(hash)
for i, fork := range forks {
hash = checksumUpdate(hash, fork)
sums[i+1] = checksumToBytes(hash)
}
// Add two sentries to simplify the fork checks and don't require special
// casing the last one.
forks = append(forks, math.MaxUint64) // Last fork will never be passed
// Create a validator that will filter out incompatible chains
return func(id ID) error {
// Run the fork checksum validation ruleset:
// 1. If local and remote FORK_CSUM matches, compare local head to FORK_NEXT.
// The two nodes are in the same fork state currently. They might know
// of differing future forks, but that's not relevant until the fork
// triggers (might be postponed, nodes might be updated to match).
// 1a. A remotely announced but remotely not passed block is already passed
// locally, disconnect, since the chains are incompatible.
// 1b. No remotely announced fork; or not yet passed locally, connect.
// 2. If the remote FORK_CSUM is a subset of the local past forks and the
// remote FORK_NEXT matches with the locally following fork block number,
// connect.
// Remote node is currently syncing. It might eventually diverge from
// us, but at this current point in time we don't have enough information.
// 3. If the remote FORK_CSUM is a superset of the local past forks and can
// be completed with locally known future forks, connect.
// Local node is currently syncing. It might eventually diverge from
// the remote, but at this current point in time we don't have enough
// information.
// 4. Reject in all other cases.
head := headfn()
for i, fork := range forks {
// If our head is beyond this fork, continue to the next (we have a dummy
// fork of maxuint64 as the last item to always fail this check eventually).
if head >= fork {
continue
}
// Found the first unpassed fork block, check if our current state matches
// the remote checksum (rule #1).
if sums[i] == id.Hash {
// Fork checksum matched, check if a remote future fork block already passed
// locally without the local node being aware of it (rule #1a).
if id.Next > 0 && head >= id.Next {
return ErrLocalIncompatibleOrStale
}
// Haven't passed locally a remote-only fork, accept the connection (rule #1b).
return nil
}
// The local and remote nodes are in different forks currently, check if the
// remote checksum is a subset of our local forks (rule #2).
for j := 0; j < i; j++ {
if sums[j] == id.Hash {
// Remote checksum is a subset, validate based on the announced next fork
if forks[j] != id.Next {
return ErrRemoteStale
}
return nil
}
}
// Remote chain is not a subset of our local one, check if it's a superset by
// any chance, signalling that we're simply out of sync (rule #3).
for j := i + 1; j < len(sums); j++ {
if sums[j] == id.Hash {
// Yay, remote checksum is a superset, ignore upcoming forks
return nil
}
}
// No exact, subset or superset match. We are on differing chains, reject.
return ErrLocalIncompatibleOrStale
}
log.Error("Impossible fork ID validation", "id", id)
return nil // Something's very wrong, accept rather than reject
}
}
// checksum calculates the IEEE CRC32 checksum of a block number.
func checksum(fork uint64) uint32 {
var blob [8]byte
binary.BigEndian.PutUint64(blob[:], fork)
return crc32.ChecksumIEEE(blob[:])
}
// checksumUpdate calculates the next IEEE CRC32 checksum based on the previous
// one and a fork block number (equivalent to CRC32(original-blob || fork)).
func checksumUpdate(hash uint32, fork uint64) uint32 {
var blob [8]byte
binary.BigEndian.PutUint64(blob[:], fork)
return crc32.Update(hash, crc32.IEEETable, blob[:])
}
// checksumToBytes converts a uint32 checksum into a [4]byte array.
func checksumToBytes(hash uint32) [4]byte {
var blob [4]byte
binary.BigEndian.PutUint32(blob[:], hash)
return blob
}
// gatherForks gathers all the known forks and creates a sorted list out of them.
func gatherForks(config *params.ChainConfig) []uint64 {
// Gather all the fork block numbers via reflection
kind := reflect.TypeOf(params.ChainConfig{})
conf := reflect.ValueOf(config).Elem()
var forks []uint64
for i := 0; i < kind.NumField(); i++ {
// Fetch the next field and skip non-fork rules
field := kind.Field(i)
if !strings.HasSuffix(field.Name, "Block") {
continue
}
if field.Type != reflect.TypeOf(new(big.Int)) {
continue
}
// Extract the fork rule block number and aggregate it
rule := conf.Field(i).Interface().(*big.Int)
if rule != nil {
forks = append(forks, rule.Uint64())
}
}
// Sort the fork block numbers to permit chronologival XOR
for i := 0; i < len(forks); i++ {
for j := i + 1; j < len(forks); j++ {
if forks[i] > forks[j] {
forks[i], forks[j] = forks[j], forks[i]
}
}
}
// Deduplicate block numbers applying multiple forks
for i := 1; i < len(forks); i++ {
if forks[i] == forks[i-1] {
forks = append(forks[:i], forks[i+1:]...)
i--
}
}
// Skip any forks in block 0, that's the genesis ruleset
if len(forks) > 0 && forks[0] == 0 {
forks = forks[1:]
}
return forks
}

View file

@ -1,294 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package forkid
import (
"bytes"
"math"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
var ( //from go-ethereum
MainnetChainConfig = &params.ChainConfig{
ChainId: big.NewInt(1),
HomesteadBlock: big.NewInt(1150000),
DAOForkBlock: big.NewInt(1920000),
DAOForkSupport: true,
EIP150Block: big.NewInt(2463000),
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
EIP155Block: big.NewInt(2675000),
EIP158Block: big.NewInt(2675000),
ByzantiumBlock: big.NewInt(4370000),
ConstantinopleBlock: big.NewInt(7280000),
PetersburgBlock: big.NewInt(7280000),
Ethash: new(params.EthashConfig),
}
RopstenChainConfig = &params.ChainConfig{
ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
EIP155Block: big.NewInt(10),
EIP158Block: big.NewInt(10),
ByzantiumBlock: big.NewInt(1700000),
ConstantinopleBlock: big.NewInt(4230000),
PetersburgBlock: big.NewInt(4939394),
Ethash: new(params.EthashConfig),
}
RinkebyChainConfig = &params.ChainConfig{
ChainId: big.NewInt(4),
HomesteadBlock: big.NewInt(1),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
EIP155Block: big.NewInt(3),
EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(1035301),
ConstantinopleBlock: big.NewInt(3660663),
PetersburgBlock: big.NewInt(4321234),
Clique: &params.CliqueConfig{
Period: 15,
Epoch: 30000,
},
}
GoerliChainConfig = &params.ChainConfig{
ChainId: big.NewInt(5),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
Clique: &params.CliqueConfig{
Period: 15,
Epoch: 30000,
},
}
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
)
// TestCreation tests that different genesis and fork rule combinations result in
// the correct fork ID.
func TestCreation(t *testing.T) {
type testcase struct {
head uint64
want ID
}
tests := []struct {
config *params.ChainConfig
genesis common.Hash
cases []testcase
}{
// Mainnet test cases
{
MainnetChainConfig,
MainnetGenesisHash,
[]testcase{
{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 0}}, // First and last Constantinople, first Petersburg block
{7987396, ID{Hash: checksumToBytes(0x668db0af), Next: 0}}, // Today Petersburg block
},
},
// Ropsten test cases
{
RopstenChainConfig,
RopstenGenesisHash,
[]testcase{
{0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block
{9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block
{10, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // First Spurious block
{1699999, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // Last Spurious block
{1700000, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // First Byzantium block
{4229999, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // Last Byzantium block
{4230000, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // First Constantinople block
{4939393, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // Last Constantinople block
{4939394, ID{Hash: checksumToBytes(0xd6e2149b), Next: 0}}, // First Petersburg block
{5822692, ID{Hash: checksumToBytes(0xd6e2149b), Next: 0}}, // Today Petersburg block
},
},
// Rinkeby test cases
{
RinkebyChainConfig,
RinkebyGenesisHash,
[]testcase{
{0, ID{Hash: checksumToBytes(0x3b8e0691), Next: 1}}, // Unsynced, last Frontier block
{1, ID{Hash: checksumToBytes(0x60949295), Next: 2}}, // First and last Homestead block
{2, ID{Hash: checksumToBytes(0x8bde40dd), Next: 3}}, // First and last Tangerine block
{3, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // First Spurious block
{1035300, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // Last Spurious block
{1035301, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // First Byzantium block
{3660662, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // Last Byzantium block
{3660663, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // First Constantinople block
{4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block
{4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}}, // First Petersburg block
{4586649, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}}, // Today Petersburg block
},
},
// Goerli test cases
{
GoerliChainConfig,
GoerliGenesisHash,
[]testcase{
{0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 0}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block
{795329, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 0}}, // Today Petersburg block
},
},
}
for i, tt := range tests {
for j, ttt := range tt.cases {
if have := newID(tt.config, tt.genesis, ttt.head); have != ttt.want {
t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want)
}
}
}
}
// TestValidation tests that a local peer correctly validates and accepts a remote
// fork ID.
func TestValidation(t *testing.T) {
tests := []struct {
head uint64
id ID
err error
}{
// Local is mainnet Petersburg, remote announces the same. No future fork is announced.
{7987396, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil},
// Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork
// at block 0xffffffff, but that is uncertain.
{7987396, ID{Hash: checksumToBytes(0x668db0af), Next: math.MaxUint64}, nil},
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
// also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork).
// In this case we don't know if Petersburg passed yet or not.
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
// also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We
// don't know if Petersburg passed yet (will pass) or not.
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
// also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil},
// Local is mainnet exactly on Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote
// is simply out of sync, accept.
{7280000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
// Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote
// is simply out of sync, accept.
{7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
// Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. Remote
// is definitely out of sync. It may or may not need the Petersburg update, we don't know yet.
{7987396, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
// Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept.
{7279999, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil},
// Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local
// out of sync. Local also knows about a future fork, but that is uncertain yet.
{4369999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
// Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks.
// Remote needs software update.
{7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale},
// Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg +
// 0xffffffff. Local needs software update, reject.
{7987396, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
// Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg +
// 0xffffffff. Local needs software update, reject.
{7279999, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
// Local is mainnet Petersburg, remote is Rinkeby Petersburg.
{7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale},
// Local is mainnet Petersburg, far in the future. Remote announces Gopherium (non existing fork)
// at some future block 88888888, for itself, but past block for local. Local is incompatible.
//
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
{88888888, ID{Hash: checksumToBytes(0x668db0af), Next: 88888888}, ErrLocalIncompatibleOrStale},
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
// fork) at block 7279999, before Petersburg. Local is incompatible.
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale},
}
for i, tt := range tests {
filter := newFilter(MainnetChainConfig, MainnetGenesisHash, func() uint64 { return tt.head })
if err := filter(tt.id); err != tt.err {
t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err)
}
}
}
// Tests that IDs are properly RLP encoded (specifically important because we
// use uint32 to store the hash, but we need to encode it as [4]byte).
func TestEncoding(t *testing.T) {
tests := []struct {
id ID
want []byte
}{
{ID{Hash: checksumToBytes(0), Next: 0}, common.Hex2Bytes("c6840000000080")},
{ID{Hash: checksumToBytes(0xdeadbeef), Next: 0xBADDCAFE}, common.Hex2Bytes("ca84deadbeef84baddcafe,")},
{ID{Hash: checksumToBytes(math.MaxUint32), Next: math.MaxUint64}, common.Hex2Bytes("ce84ffffffff88ffffffffffffffff")},
}
for i, tt := range tests {
have, err := rlp.EncodeToBytes(tt.id)
if err != nil {
t.Errorf("test %d: failed to encode forkid: %v", i, err)
continue
}
if !bytes.Equal(have, tt.want) {
t.Errorf("test %d: RLP mismatch: have %x, want %x", i, have, tt.want)
}
}
}

View file

@ -154,25 +154,6 @@ func (h *Header) Size() common.StorageSize {
return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8) return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8)
} }
// SanityCheck checks a few basic things -- these checks are way beyond what
// any 'sane' production values should hold, and can mainly be used to prevent
// that the unbounded fields are stuffed with junk data to add processing
// overhead
func (h *Header) SanityCheck() error {
if h.Number != nil && !h.Number.IsUint64() {
return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen())
}
if h.Difficulty != nil {
if diffLen := h.Difficulty.BitLen(); diffLen > 80 {
return fmt.Errorf("too large block difficulty: bitlen %d", diffLen)
}
}
if eLen := len(h.Extra); eLen > 100*1024 {
return fmt.Errorf("too large block extradata: size %d", eLen)
}
return nil
}
// Body is a simple (mutable, non-safe) data container for storing and moving // Body is a simple (mutable, non-safe) data container for storing and moving
// a block's data contents (transactions and uncles) together. // a block's data contents (transactions and uncles) together.
type Body struct { type Body struct {
@ -388,12 +369,6 @@ func (b *Block) Size() common.StorageSize {
return common.StorageSize(c) return common.StorageSize(c)
} }
// SanityCheck can be used to prevent that unbounded fields are
// stuffed with junk data to add processing overhead
func (b *Block) SanityCheck() error {
return b.header.SanityCheck()
}
type writeCounter common.StorageSize type writeCounter common.StorageSize
func (c *writeCounter) Write(b []byte) (int, error) { func (c *writeCounter) Write(b []byte) (int, error) {

View file

@ -51,7 +51,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/miner" "github.com/XinFinOrg/XDPoSChain/miner"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
@ -70,9 +69,7 @@ type Ethereum struct {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
// Channel for shutting down the service // Channel for shutting down the service
shutdownChan chan bool shutdownChan chan bool // Channel for shutting down the ethereum
server *p2p.Server
// Handlers // Handlers
txPool *core.TxPool txPool *core.TxPool
@ -288,8 +285,8 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
return block, false, nil return block, false, nil
} }
eth.protocolManager.blockFetcher.SetSignHook(signHook) eth.protocolManager.fetcher.SetSignHook(signHook)
eth.protocolManager.blockFetcher.SetAppendM2HeaderHook(appendM2HeaderHook) eth.protocolManager.fetcher.SetAppendM2HeaderHook(appendM2HeaderHook)
/* /*
XDPoS1.0 Specific hooks XDPoS1.0 Specific hooks
@ -524,29 +521,22 @@ func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) Engine() consensus.Engine { return s.engine }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) IsListening() bool { return true } // Always listening
func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) } func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *Ethereum) NetVersion() uint64 { return s.networkId } func (s *Ethereum) NetVersion() uint64 { return s.networkId }
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured
// network protocols to start. // network protocols to start.
func (s *Ethereum) Protocols() []p2p.Protocol { func (s *Ethereum) Protocols() []p2p.Protocol {
protos := make([]p2p.Protocol, len(ProtocolVersions)) if s.lesServer == nil {
for i, vsn := range ProtocolVersions { return s.protocolManager.SubProtocols
protos[i] = s.protocolManager.makeProtocol(vsn)
protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
} }
if s.lesServer != nil { return append(s.protocolManager.SubProtocols, s.lesServer.Protocols()...)
protos = append(protos, s.lesServer.Protocols()...)
}
return protos
} }
// Start implements node.Service, starting all internal goroutines needed by the // Start implements node.Service, starting all internal goroutines needed by the
// Ethereum protocol implementation. // Ethereum protocol implementation.
func (s *Ethereum) Start(srvr *p2p.Server) error { func (s *Ethereum) Start(srvr *p2p.Server) error {
s.startEthEntryUpdate(srvr.LocalNode())
// Start the bloom bits servicing goroutines // Start the bloom bits servicing goroutines
s.startBloomHandlers() s.startBloomHandlers()

View file

@ -674,12 +674,9 @@ func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisa
func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) }
func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) }
func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) }
func TestCanonicalSynchronisation64Light(t *testing.T) {testCanonicalSynchronisation(t, 64, LightSync)} func TestCanonicalSynchronisation64Light(t *testing.T) {
func TestCanonicalSynchronisation100Full(t *testing.T) { testCanonicalSynchronisation(t, 100, FullSync) } testCanonicalSynchronisation(t, 64, LightSync)
func TestCanonicalSynchronisation100Fast(t *testing.T) { testCanonicalSynchronisation(t, 100, FastSync) } }
func TestCanonicalSynchronisation101Full(t *testing.T) { testCanonicalSynchronisation(t, 101, FullSync) }
func TestCanonicalSynchronisation101Fast(t *testing.T) { testCanonicalSynchronisation(t, 101, FastSync) }
func TestCanonicalSynchronisation101Light(t *testing.T) {testCanonicalSynchronisation(t, 101, LightSync)}
func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -707,10 +704,6 @@ func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) }
func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) } func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) }
func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) }
func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) }
func TestThrottling100Full(t *testing.T) { testThrottling(t, 100, FullSync) }
func TestThrottling100Fast(t *testing.T) { testThrottling(t, 100, FastSync) }
func TestThrottling101Full(t *testing.T) { testThrottling(t, 101, FullSync) }
func TestThrottling101Fast(t *testing.T) { testThrottling(t, 101, FastSync) }
func testThrottling(t *testing.T, protocol int, mode SyncMode) { func testThrottling(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -798,11 +791,6 @@ func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) }
func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) }
func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) }
func TestForkedSync64Light(t *testing.T) { testForkedSync(t, 64, LightSync) } func TestForkedSync64Light(t *testing.T) { testForkedSync(t, 64, LightSync) }
func TestForkedSync100Full(t *testing.T) { testForkedSync(t, 100, FullSync) }
func TestForkedSync100Fast(t *testing.T) { testForkedSync(t, 100, FastSync) }
func TestForkedSync101Full(t *testing.T) { testForkedSync(t, 101, FullSync) }
func TestForkedSync101Fast(t *testing.T) { testForkedSync(t, 101, FastSync) }
func TestForkedSync101Light(t *testing.T) { testForkedSync(t, 101, LightSync) }
func testForkedSync(t *testing.T, protocol int, mode SyncMode) { func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -838,11 +826,6 @@ func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastS
func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) }
func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) }
func TestHeavyForkedSync64Light(t *testing.T) { testHeavyForkedSync(t, 64, LightSync) } func TestHeavyForkedSync64Light(t *testing.T) { testHeavyForkedSync(t, 64, LightSync) }
func TestHeavyForkedSync100Full(t *testing.T) { testHeavyForkedSync(t, 100, FullSync) }
func TestHeavyForkedSync100Fast(t *testing.T) { testHeavyForkedSync(t, 100, FastSync) }
func TestHeavyForkedSync101Full(t *testing.T) { testHeavyForkedSync(t, 101, FullSync) }
func TestHeavyForkedSync101Fast(t *testing.T) { testHeavyForkedSync(t, 101, FastSync) }
func TestHeavyForkedSync101Light(t *testing.T) { testHeavyForkedSync(t, 101, LightSync) }
func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -879,11 +862,6 @@ func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, F
func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) }
func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) }
func TestBoundedForkedSync64Light(t *testing.T) { testBoundedForkedSync(t, 64, LightSync) } func TestBoundedForkedSync64Light(t *testing.T) { testBoundedForkedSync(t, 64, LightSync) }
func TestBoundedForkedSync100Full(t *testing.T) { testBoundedForkedSync(t, 100, FullSync) }
func TestBoundedForkedSync100Fast(t *testing.T) { testBoundedForkedSync(t, 100, FastSync) }
func TestBoundedForkedSync101Full(t *testing.T) { testBoundedForkedSync(t, 101, FullSync) }
func TestBoundedForkedSync101Fast(t *testing.T) { testBoundedForkedSync(t, 101, FastSync) }
func TestBoundedForkedSync101Light(t *testing.T) { testBoundedForkedSync(t, 101, LightSync) }
func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -913,18 +891,12 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
// Tests that chain forks are contained within a certain interval of the current // Tests that chain forks are contained within a certain interval of the current
// chain head for short but heavy forks too. These are a bit special because they // chain head for short but heavy forks too. These are a bit special because they
// take different ancestor lookup paths. // take different ancestor lookup paths.
func TestBoundedHeavyForkedSync62(t *testing.T) { testBoundedHeavyForkedSync(t, 62, FullSync) } func TestBoundedHeavyForkedSync62(t *testing.T) { testBoundedHeavyForkedSync(t, 62, FullSync) }
func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) } func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) }
func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) } func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) }
func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) }
func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) }
func TestBoundedHeavyForkedSync64Light(t *testing.T) { testBoundedHeavyForkedSync(t, 64, LightSync) } func TestBoundedHeavyForkedSync64Light(t *testing.T) { testBoundedHeavyForkedSync(t, 64, LightSync) }
func TestBoundedHeavyForkedSync100Full(t *testing.T) { testBoundedHeavyForkedSync(t, 100, FullSync) }
func TestBoundedHeavyForkedSync100Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 100, FastSync) }
func TestBoundedHeavyForkedSync100Light(t *testing.T) { testBoundedHeavyForkedSync(t, 100, LightSync) }
func TestBoundedHeavyForkedSync101Full(t *testing.T) { testBoundedHeavyForkedSync(t, 101, FullSync) }
func TestBoundedHeavyForkedSync101Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 101, FastSync) }
func TestBoundedHeavyForkedSync101Light(t *testing.T) { testBoundedHeavyForkedSync(t, 101, LightSync) }
func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -989,18 +961,12 @@ func TestInactiveDownloader63(t *testing.T) {
} }
// Tests that a canceled download wipes all previously accumulated state. // Tests that a canceled download wipes all previously accumulated state.
func TestCancel62(t *testing.T) { testCancel(t, 62, FullSync) } func TestCancel62(t *testing.T) { testCancel(t, 62, FullSync) }
func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) } func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) }
func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) } func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) }
func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) }
func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) }
func TestCancel64Light(t *testing.T) { testCancel(t, 64, LightSync) } func TestCancel64Light(t *testing.T) { testCancel(t, 64, LightSync) }
func TestCancel100Full(t *testing.T) { testCancel(t, 100, FullSync) }
func TestCancel100Fast(t *testing.T) { testCancel(t, 100, FastSync) }
func TestCancel100Light(t *testing.T) { testCancel(t, 100, LightSync) }
func TestCancel101Full(t *testing.T) { testCancel(t, 101, FullSync) }
func TestCancel101Fast(t *testing.T) { testCancel(t, 101, FastSync) }
func TestCancel101Light(t *testing.T) { testCancel(t, 101, LightSync) }
func testCancel(t *testing.T, protocol int, mode SyncMode) { func testCancel(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1036,18 +1002,12 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) {
} }
// Tests that synchronisation from multiple peers works as intended (multi thread sanity test). // Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
func TestMultiSynchronisation62(t *testing.T) { testMultiSynchronisation(t, 62, FullSync) } func TestMultiSynchronisation62(t *testing.T) { testMultiSynchronisation(t, 62, FullSync) }
func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) } func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) }
func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) } func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) }
func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) }
func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) }
func TestMultiSynchronisation64Light(t *testing.T) { testMultiSynchronisation(t, 64, LightSync) } func TestMultiSynchronisation64Light(t *testing.T) { testMultiSynchronisation(t, 64, LightSync) }
func TestMultiSynchronisation100Full(t *testing.T) { testMultiSynchronisation(t, 100, FullSync) }
func TestMultiSynchronisation100Fast(t *testing.T) { testMultiSynchronisation(t, 100, FastSync) }
func TestMultiSynchronisation100Light(t *testing.T) { testMultiSynchronisation(t, 100, LightSync) }
func TestMultiSynchronisation101Full(t *testing.T) { testMultiSynchronisation(t, 101, FullSync) }
func TestMultiSynchronisation101Fast(t *testing.T) { testMultiSynchronisation(t, 101, FastSync) }
func TestMultiSynchronisation101Light(t *testing.T) { testMultiSynchronisation(t, 101, LightSync) }
func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1072,18 +1032,12 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
// Tests that synchronisations behave well in multi-version protocol environments // Tests that synchronisations behave well in multi-version protocol environments
// and not wreak havoc on other nodes in the network. // and not wreak havoc on other nodes in the network.
func TestMultiProtoSynchronisation62(t *testing.T) { testMultiProtoSync(t, 62, FullSync) } func TestMultiProtoSynchronisation62(t *testing.T) { testMultiProtoSync(t, 62, FullSync) }
func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) } func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) }
func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) } func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) }
func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) }
func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) }
func TestMultiProtoSynchronisation64Light(t *testing.T) { testMultiProtoSync(t, 64, LightSync) } func TestMultiProtoSynchronisation64Light(t *testing.T) { testMultiProtoSync(t, 64, LightSync) }
func TestMultiProtoSynchronisation100Full(t *testing.T) { testMultiProtoSync(t, 100, FullSync) }
func TestMultiProtoSynchronisation100Fast(t *testing.T) { testMultiProtoSync(t, 100, FastSync) }
func TestMultiProtoSynchronisation100Light(t *testing.T) { testMultiProtoSync(t, 100, LightSync) }
func TestMultiProtoSynchronisation101Full(t *testing.T) { testMultiProtoSync(t, 101, FullSync) }
func TestMultiProtoSynchronisation101Fast(t *testing.T) { testMultiProtoSync(t, 101, FastSync) }
func TestMultiProtoSynchronisation101Light(t *testing.T) { testMultiProtoSync(t, 101, LightSync) }
func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1099,8 +1053,6 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
tester.newPeer("peer 62", 62, hashes, headers, blocks, nil) tester.newPeer("peer 62", 62, hashes, headers, blocks, nil)
tester.newPeer("peer 63", 63, hashes, headers, blocks, receipts) tester.newPeer("peer 63", 63, hashes, headers, blocks, receipts)
tester.newPeer("peer 64", 64, hashes, headers, blocks, receipts) tester.newPeer("peer 64", 64, hashes, headers, blocks, receipts)
tester.newPeer("peer 100", 100, hashes, headers, blocks, receipts)
tester.newPeer("peer 101", 101, hashes, headers, blocks, receipts)
// Synchronise with the requested peer and make sure all blocks were retrieved // Synchronise with the requested peer and make sure all blocks were retrieved
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
@ -1109,7 +1061,7 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
assertOwnChain(t, tester, targetBlocks+1) assertOwnChain(t, tester, targetBlocks+1)
// Check that no peers have been dropped off // Check that no peers have been dropped off
for _, version := range []int{62, 63, 64, 100, 101} { for _, version := range []int{62, 63, 64} {
peer := fmt.Sprintf("peer %d", version) peer := fmt.Sprintf("peer %d", version)
if _, ok := tester.peerHashes[peer]; !ok { if _, ok := tester.peerHashes[peer]; !ok {
t.Errorf("%s dropped", peer) t.Errorf("%s dropped", peer)
@ -1119,18 +1071,12 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
// Tests that if a block is empty (e.g. header only), no body request should be // Tests that if a block is empty (e.g. header only), no body request should be
// made, and instead the header should be assembled into a whole block in itself. // made, and instead the header should be assembled into a whole block in itself.
func TestEmptyShortCircuit62(t *testing.T) { testEmptyShortCircuit(t, 62, FullSync) } func TestEmptyShortCircuit62(t *testing.T) { testEmptyShortCircuit(t, 62, FullSync) }
func TestEmptyShortCircuit63Full(t *testing.T) { testEmptyShortCircuit(t, 63, FullSync) } func TestEmptyShortCircuit63Full(t *testing.T) { testEmptyShortCircuit(t, 63, FullSync) }
func TestEmptyShortCircuit63Fast(t *testing.T) { testEmptyShortCircuit(t, 63, FastSync) } func TestEmptyShortCircuit63Fast(t *testing.T) { testEmptyShortCircuit(t, 63, FastSync) }
func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) }
func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) }
func TestEmptyShortCircuit64Light(t *testing.T) { testEmptyShortCircuit(t, 64, LightSync) } func TestEmptyShortCircuit64Light(t *testing.T) { testEmptyShortCircuit(t, 64, LightSync) }
func TestEmptyShortCircuit100Full(t *testing.T) { testEmptyShortCircuit(t, 100, FullSync) }
func TestEmptyShortCircuit100Fast(t *testing.T) { testEmptyShortCircuit(t, 100, FastSync) }
func TestEmptyShortCircuit100Light(t *testing.T) { testEmptyShortCircuit(t, 100, LightSync) }
func TestEmptyShortCircuit101Full(t *testing.T) { testEmptyShortCircuit(t, 101, FullSync) }
func TestEmptyShortCircuit101Fast(t *testing.T) { testEmptyShortCircuit(t, 101, FastSync) }
func TestEmptyShortCircuit101Light(t *testing.T) { testEmptyShortCircuit(t, 101, LightSync) }
func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1180,18 +1126,12 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
// Tests that headers are enqueued continuously, preventing malicious nodes from // Tests that headers are enqueued continuously, preventing malicious nodes from
// stalling the downloader by feeding gapped header chains. // stalling the downloader by feeding gapped header chains.
func TestMissingHeaderAttack62(t *testing.T) { testMissingHeaderAttack(t, 62, FullSync) } func TestMissingHeaderAttack62(t *testing.T) { testMissingHeaderAttack(t, 62, FullSync) }
func TestMissingHeaderAttack63Full(t *testing.T) { testMissingHeaderAttack(t, 63, FullSync) } func TestMissingHeaderAttack63Full(t *testing.T) { testMissingHeaderAttack(t, 63, FullSync) }
func TestMissingHeaderAttack63Fast(t *testing.T) { testMissingHeaderAttack(t, 63, FastSync) } func TestMissingHeaderAttack63Fast(t *testing.T) { testMissingHeaderAttack(t, 63, FastSync) }
func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) }
func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) }
func TestMissingHeaderAttack64Light(t *testing.T) { testMissingHeaderAttack(t, 64, LightSync) } func TestMissingHeaderAttack64Light(t *testing.T) { testMissingHeaderAttack(t, 64, LightSync) }
func TestMissingHeaderAttack100Full(t *testing.T) { testMissingHeaderAttack(t, 100, FullSync) }
func TestMissingHeaderAttack100Fast(t *testing.T) { testMissingHeaderAttack(t, 100, FastSync) }
func TestMissingHeaderAttack100Light(t *testing.T) { testMissingHeaderAttack(t, 100, LightSync) }
func TestMissingHeaderAttack101Full(t *testing.T) { testMissingHeaderAttack(t, 101, FullSync) }
func TestMissingHeaderAttack101Fast(t *testing.T) { testMissingHeaderAttack(t, 101, FastSync) }
func TestMissingHeaderAttack101Light(t *testing.T) { testMissingHeaderAttack(t, 101, LightSync) }
func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1221,18 +1161,12 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
// Tests that if requested headers are shifted (i.e. first is missing), the queue // Tests that if requested headers are shifted (i.e. first is missing), the queue
// detects the invalid numbering. // detects the invalid numbering.
func TestShiftedHeaderAttack62(t *testing.T) { testShiftedHeaderAttack(t, 62, FullSync) } func TestShiftedHeaderAttack62(t *testing.T) { testShiftedHeaderAttack(t, 62, FullSync) }
func TestShiftedHeaderAttack63Full(t *testing.T) { testShiftedHeaderAttack(t, 63, FullSync) } func TestShiftedHeaderAttack63Full(t *testing.T) { testShiftedHeaderAttack(t, 63, FullSync) }
func TestShiftedHeaderAttack63Fast(t *testing.T) { testShiftedHeaderAttack(t, 63, FastSync) } func TestShiftedHeaderAttack63Fast(t *testing.T) { testShiftedHeaderAttack(t, 63, FastSync) }
func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) }
func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) }
func TestShiftedHeaderAttack64Light(t *testing.T) { testShiftedHeaderAttack(t, 64, LightSync) } func TestShiftedHeaderAttack64Light(t *testing.T) { testShiftedHeaderAttack(t, 64, LightSync) }
func TestShiftedHeaderAttack100Full(t *testing.T) { testShiftedHeaderAttack(t, 100, FullSync) }
func TestShiftedHeaderAttack100Fast(t *testing.T) { testShiftedHeaderAttack(t, 100, FastSync) }
func TestShiftedHeaderAttack100Light(t *testing.T) { testShiftedHeaderAttack(t, 100, LightSync) }
func TestShiftedHeaderAttack101Full(t *testing.T) { testShiftedHeaderAttack(t, 101, FullSync) }
func TestShiftedHeaderAttack101Fast(t *testing.T) { testShiftedHeaderAttack(t, 101, FastSync) }
func TestShiftedHeaderAttack101Light(t *testing.T) { testShiftedHeaderAttack(t, 101, LightSync) }
func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1264,13 +1198,9 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
// Tests that upon detecting an invalid header, the recent ones are rolled back // Tests that upon detecting an invalid header, the recent ones are rolled back
// for various failure scenarios. Afterwards a full sync is attempted to make // for various failure scenarios. Afterwards a full sync is attempted to make
// sure no state was corrupted. // sure no state was corrupted.
func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) }
func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) }
func TestInvalidHeaderRollback64Light(t *testing.T) { testInvalidHeaderRollback(t, 64, LightSync) } func TestInvalidHeaderRollback64Light(t *testing.T) { testInvalidHeaderRollback(t, 64, LightSync) }
func TestInvalidHeaderRollback100Fast(t *testing.T) { testInvalidHeaderRollback(t, 100, FastSync) }
func TestInvalidHeaderRollback100Light(t *testing.T) { testInvalidHeaderRollback(t, 100, LightSync) }
func TestInvalidHeaderRollback101Fast(t *testing.T) { testInvalidHeaderRollback(t, 101, FastSync) }
func TestInvalidHeaderRollback101Light(t *testing.T) { testInvalidHeaderRollback(t, 101, LightSync) }
func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1357,18 +1287,12 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
// Tests that a peer advertising an high TD doesn't get to stall the downloader // Tests that a peer advertising an high TD doesn't get to stall the downloader
// afterwards by not sending any useful hashes. // afterwards by not sending any useful hashes.
func TestHighTDStarvationAttack62(t *testing.T) { testHighTDStarvationAttack(t, 62, FullSync) } func TestHighTDStarvationAttack62(t *testing.T) { testHighTDStarvationAttack(t, 62, FullSync) }
func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) } func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) }
func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) } func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) }
func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) }
func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) }
func TestHighTDStarvationAttack64Light(t *testing.T) { testHighTDStarvationAttack(t, 64, LightSync) } func TestHighTDStarvationAttack64Light(t *testing.T) { testHighTDStarvationAttack(t, 64, LightSync) }
func TestHighTDStarvationAttack100Full(t *testing.T) { testHighTDStarvationAttack(t, 100, FullSync) }
func TestHighTDStarvationAttack100Fast(t *testing.T) { testHighTDStarvationAttack(t, 100, FastSync) }
func TestHighTDStarvationAttack100Light(t *testing.T) { testHighTDStarvationAttack(t, 100, LightSync) }
func TestHighTDStarvationAttack101Full(t *testing.T) { testHighTDStarvationAttack(t, 101, FullSync) }
func TestHighTDStarvationAttack101Fast(t *testing.T) { testHighTDStarvationAttack(t, 101, FastSync) }
func TestHighTDStarvationAttack101Light(t *testing.T) { testHighTDStarvationAttack(t, 101, LightSync) }
func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1385,11 +1309,9 @@ func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
} }
// Tests that misbehaving peers are disconnected, whilst behaving ones are not. // Tests that misbehaving peers are disconnected, whilst behaving ones are not.
func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) } func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) }
func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) } func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) }
func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) }
func TestBlockHeaderAttackerDropping100(t *testing.T) { testBlockHeaderAttackerDropping(t, 100) }
func TestBlockHeaderAttackerDropping101(t *testing.T) { testBlockHeaderAttackerDropping(t, 101) }
func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
t.Parallel() t.Parallel()
@ -1445,18 +1367,12 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
// Tests that synchronisation progress (origin block number, current block number // Tests that synchronisation progress (origin block number, current block number
// and highest block number) is tracked and updated correctly. // and highest block number) is tracked and updated correctly.
func TestSyncProgress62(t *testing.T) { testSyncProgress(t, 62, FullSync) } func TestSyncProgress62(t *testing.T) { testSyncProgress(t, 62, FullSync) }
func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) } func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) }
func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) } func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) }
func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) }
func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) }
func TestSyncProgress64Light(t *testing.T) { testSyncProgress(t, 64, LightSync) } func TestSyncProgress64Light(t *testing.T) { testSyncProgress(t, 64, LightSync) }
func TestSyncProgress100Full(t *testing.T) { testSyncProgress(t, 100, FullSync) }
func TestSyncProgress100Fast(t *testing.T) { testSyncProgress(t, 100, FastSync) }
func TestSyncProgress100Light(t *testing.T) { testSyncProgress(t, 100, LightSync) }
func TestSyncProgress101Full(t *testing.T) { testSyncProgress(t, 101, FullSync) }
func TestSyncProgress101Fast(t *testing.T) { testSyncProgress(t, 101, FastSync) }
func TestSyncProgress101Light(t *testing.T) { testSyncProgress(t, 101, LightSync) }
func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1524,18 +1440,12 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that synchronisation progress (origin block number and highest block // Tests that synchronisation progress (origin block number and highest block
// number) is tracked and updated correctly in case of a fork (or manual head // number) is tracked and updated correctly in case of a fork (or manual head
// revertal). // revertal).
func TestForkedSyncProgress62(t *testing.T) { testForkedSyncProgress(t, 62, FullSync) } func TestForkedSyncProgress62(t *testing.T) { testForkedSyncProgress(t, 62, FullSync) }
func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) } func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) }
func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) } func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) }
func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) }
func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) }
func TestForkedSyncProgress64Light(t *testing.T) { testForkedSyncProgress(t, 64, LightSync) } func TestForkedSyncProgress64Light(t *testing.T) { testForkedSyncProgress(t, 64, LightSync) }
func TestForkedSyncProgress100Full(t *testing.T) { testForkedSyncProgress(t, 100, FullSync) }
func TestForkedSyncProgress100Fast(t *testing.T) { testForkedSyncProgress(t, 100, FastSync) }
func TestForkedSyncProgress100Light(t *testing.T) { testForkedSyncProgress(t, 100, LightSync) }
func TestForkedSyncProgress101Full(t *testing.T) { testForkedSyncProgress(t, 101, FullSync) }
func TestForkedSyncProgress101Fast(t *testing.T) { testForkedSyncProgress(t, 101, FastSync) }
func TestForkedSyncProgress101Light(t *testing.T) { testForkedSyncProgress(t, 101, LightSync) }
func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1606,18 +1516,12 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that if synchronisation is aborted due to some failure, then the progress // Tests that if synchronisation is aborted due to some failure, then the progress
// origin is not updated in the next sync cycle, as it should be considered the // origin is not updated in the next sync cycle, as it should be considered the
// continuation of the previous sync and not a new instance. // continuation of the previous sync and not a new instance.
func TestFailedSyncProgress62(t *testing.T) { testFailedSyncProgress(t, 62, FullSync) } func TestFailedSyncProgress62(t *testing.T) { testFailedSyncProgress(t, 62, FullSync) }
func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) } func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) }
func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) } func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) }
func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) }
func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) }
func TestFailedSyncProgress64Light(t *testing.T) { testFailedSyncProgress(t, 64, LightSync) } func TestFailedSyncProgress64Light(t *testing.T) { testFailedSyncProgress(t, 64, LightSync) }
func TestFailedSyncProgress100Full(t *testing.T) { testFailedSyncProgress(t, 100, FullSync) }
func TestFailedSyncProgress100Fast(t *testing.T) { testFailedSyncProgress(t, 100, FastSync) }
func TestFailedSyncProgress100Light(t *testing.T) { testFailedSyncProgress(t, 100, LightSync) }
func TestFailedSyncProgress101Full(t *testing.T) { testFailedSyncProgress(t, 101, FullSync) }
func TestFailedSyncProgress101Fast(t *testing.T) { testFailedSyncProgress(t, 101, FastSync) }
func TestFailedSyncProgress101Light(t *testing.T) { testFailedSyncProgress(t, 101, LightSync) }
func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1689,18 +1593,12 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
// Tests that if an attacker fakes a chain height, after the attack is detected, // Tests that if an attacker fakes a chain height, after the attack is detected,
// the progress height is successfully reduced at the next sync invocation. // the progress height is successfully reduced at the next sync invocation.
func TestFakedSyncProgress62(t *testing.T) { testFakedSyncProgress(t, 62, FullSync) } func TestFakedSyncProgress62(t *testing.T) { testFakedSyncProgress(t, 62, FullSync) }
func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) } func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) }
func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) } func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) }
func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) }
func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) }
func TestFakedSyncProgress64Light(t *testing.T) { testFakedSyncProgress(t, 64, LightSync) } func TestFakedSyncProgress64Light(t *testing.T) { testFakedSyncProgress(t, 64, LightSync) }
func TestFakedSyncProgress100Full(t *testing.T) { testFakedSyncProgress(t, 100, FullSync) }
func TestFakedSyncProgress100Fast(t *testing.T) { testFakedSyncProgress(t, 100, FastSync) }
func TestFakedSyncProgress100Light(t *testing.T) { testFakedSyncProgress(t, 100, LightSync) }
func TestFakedSyncProgress101Full(t *testing.T) { testFakedSyncProgress(t, 101, FullSync) }
func TestFakedSyncProgress101Fast(t *testing.T) { testFakedSyncProgress(t, 101, FastSync) }
func TestFakedSyncProgress101Light(t *testing.T) { testFakedSyncProgress(t, 101, LightSync) }
func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
t.Parallel() t.Parallel()
@ -1788,12 +1686,6 @@ func TestDeliverHeadersHang(t *testing.T) {
{64, FullSync}, {64, FullSync},
{64, FastSync}, {64, FastSync},
{64, LightSync}, {64, LightSync},
{100, FullSync},
{100, FastSync},
{100, LightSync},
{101, FullSync},
{101, FastSync},
{101, LightSync},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) { t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) {

View file

@ -477,7 +477,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock() defer p.lock.RUnlock()
return p.headerThroughput return p.headerThroughput
} }
return ps.idlePeers(62, 200, idle, throughput) return ps.idlePeers(62, 101, idle, throughput)
} }
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
@ -491,7 +491,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock() defer p.lock.RUnlock()
return p.blockThroughput return p.blockThroughput
} }
return ps.idlePeers(62, 200, idle, throughput) return ps.idlePeers(62, 101, idle, throughput)
} }
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
@ -505,7 +505,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock() defer p.lock.RUnlock()
return p.receiptThroughput return p.receiptThroughput
} }
return ps.idlePeers(63, 200, idle, throughput) return ps.idlePeers(63, 101, idle, throughput)
} }
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
@ -519,7 +519,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock() defer p.lock.RUnlock()
return p.stateThroughput return p.stateThroughput
} }
return ps.idlePeers(63, 200, idle, throughput) return ps.idlePeers(63, 101, idle, throughput)
} }
// idlePeers retrieves a flat list of all currently idle peers satisfying the // idlePeers retrieves a flat list of all currently idle peers satisfying the

View file

@ -1,61 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// ethEntry is the "eth" ENR entry which advertises eth protocol
// on the discovery network.
type ethEntry struct {
ForkID forkid.ID // Fork identifier per EIP-2124
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
}
// ENRKey implements enr.Entry.
func (e ethEntry) ENRKey() string {
return "eth"
}
func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) {
var newHead = make(chan core.ChainHeadEvent, 10)
sub := eth.blockchain.SubscribeChainHeadEvent(newHead)
go func() {
defer sub.Unsubscribe()
for {
select {
case <-newHead:
ln.Set(eth.currentEthEntry())
case <-sub.Err():
// Would be nice to sync with eth.Stop, but there is no
// good way to do that.
return
}
}
}()
}
func (eth *Ethereum) currentEthEntry() *ethEntry {
return &ethEntry{ForkID: forkid.NewID(eth.blockchain)}
}

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package fetcher contains the announcement based blocks or transaction synchronisation. // Package fetcher contains the block announcement based synchronisation.
package fetcher package fetcher
import ( import (
@ -29,40 +29,16 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
) )
const ( const (
arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block is explicitly requested
gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block/transaction fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block
) maxUncleDist = 7 // Maximum allowed backward distance from the chain head
maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
const ( hashLimit = 256 // Maximum number of unique blocks a peer may have announced
maxUncleDist = 7 // Maximum allowed backward distance from the chain head blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
hashLimit = 256 // Maximum number of unique blocks a peer may have announced
blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
)
var (
blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil)
blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil)
blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil)
blockAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/dos", nil)
blockBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/in", nil)
blockBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/broadcasts/out", nil)
blockBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/drop", nil)
blockBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/dos", nil)
headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/headers", nil)
bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/bodies", nil)
headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/in", nil)
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil)
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil)
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
) )
var ( var (
@ -90,20 +66,17 @@ type blockBroadcasterFn func(block *types.Block, propagate bool)
// chainHeightFn is a callback type to retrieve the current chain height. // chainHeightFn is a callback type to retrieve the current chain height.
type chainHeightFn func() uint64 type chainHeightFn func() uint64
// chainInsertFn is a callback type to insert a batch of blocks into the local chain.
type chainInsertFn func(types.Blocks) (int, error)
// blockInsertFn is a callback type to insert a batch of blocks into the local chain. // blockInsertFn is a callback type to insert a batch of blocks into the local chain.
type blockInsertFn func(types.Block) (error) type blockInsertFn func(block *types.Block) error
type blockPrepareFn func(block *types.Block) error type blockPrepareFn func(block *types.Block) error
// peerDropFn is a callback type for dropping a peer detected as malicious. // peerDropFn is a callback type for dropping a peer detected as malicious.
type peerDropFn func(id string) type peerDropFn func(id string)
// blockAnnounce is the hash notification of the availability of a new block in the // announce is the hash notification of the availability of a new block in the
// network. // network.
type blockAnnounce struct { type announce struct {
hash common.Hash // Hash of the block being announced hash common.Hash // Hash of the block being announced
number uint64 // Number of the block being announced (0 = unknown | old protocol) number uint64 // Number of the block being announced (0 = unknown | old protocol)
header *types.Header // Header of the block partially reassembled (new protocol) header *types.Header // Header of the block partially reassembled (new protocol)
@ -131,18 +104,18 @@ type bodyFilterTask struct {
time time.Time // Arrival time of the blocks' contents time time.Time // Arrival time of the blocks' contents
} }
// blockInject represents a schedules import operation. // inject represents a schedules import operation.
type blockInject struct { type inject struct {
origin string origin string
block *types.Block block *types.Block
} }
// BlockFetcher is responsible for accumulating block announcements from various peers // Fetcher is responsible for accumulating block announcements from various peers
// and scheduling them for retrieval. // and scheduling them for retrieval.
type BlockFetcher struct { type Fetcher struct {
// Various event channels // Various event channels
notify chan *blockAnnounce notify chan *announce
inject chan *blockInject inject chan *inject
blockFilter chan chan []*types.Block blockFilter chan chan []*types.Block
headerFilter chan chan *headerFilterTask headerFilter chan chan *headerFilterTask
@ -152,16 +125,16 @@ type BlockFetcher struct {
quit chan struct{} quit chan struct{}
// Announce states // Announce states
announces map[string]int // Per peer blockAnnounce counts to prevent memory exhaustion announces map[string]int // Per peer announce counts to prevent memory exhaustion
announced map[common.Hash][]*blockAnnounce // Announced blocks, scheduled for fetching announced map[common.Hash][]*announce // Announced blocks, scheduled for fetching
fetching map[common.Hash]*blockAnnounce // Announced blocks, currently fetching fetching map[common.Hash]*announce // Announced blocks, currently fetching
fetched map[common.Hash][]*blockAnnounce // Blocks with headers fetched, scheduled for body retrieval fetched map[common.Hash][]*announce // Blocks with headers fetched, scheduled for body retrieval
completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing completing map[common.Hash]*announce // Blocks with headers, currently body-completing
// Block cache // Block cache
queue *prque.Prque // Queue containing the import operations (block number sorted) queue *prque.Prque // Queue containing the import operations (block number sorted)
queues map[string]int // Per peer block counts to prevent memory exhaustion queues map[string]int // Per peer block counts to prevent memory exhaustion
queued map[common.Hash]*blockInject // Set of already queued blocks (to dedupe imports) queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
knowns *lru.ARCCache knowns *lru.ARCCache
// Callbacks // Callbacks
getBlock blockRetrievalFn // Retrieves a block from the local chain getBlock blockRetrievalFn // Retrieves a block from the local chain
@ -169,69 +142,38 @@ type BlockFetcher struct {
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
chainHeight chainHeightFn // Retrieves the current chain's height chainHeight chainHeightFn // Retrieves the current chain's height
// insertChain chainInsertFn // Injects a batch of blocks into the chain insertBlock blockInsertFn // Injects a batch of blocks into the chain
insertBlock blockInsertFn // Injects a batch of blocks into the chain prepareBlock blockPrepareFn
prepareBlock blockPrepareFn dropPeer peerDropFn // Drops a peer for misbehaving
dropPeer peerDropFn // Drops a peer for misbehaving
// Testing hooks // Testing hooks
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue
fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch
completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62) completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62)
importedHook func(*types.Block) // Method to call upon successful block import (both eth/61 and eth/62)
signHook func(*types.Block) error signHook func(*types.Block) error
appendM2HeaderHook func(*types.Block) (*types.Block, bool, error) appendM2HeaderHook func(*types.Block) (*types.Block, bool, error)
} }
// // New creates a block fetcher to retrieve blocks based on hash announcements. // New creates a block fetcher to retrieve blocks based on hash announcements.
// func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher { func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
// knownBlocks, _ := lru.NewARC(blockLimit)
// return &Fetcher{
// notify: make(chan *announce),
// inject: make(chan *inject),
// blockFilter: make(chan chan []*types.Block),
// headerFilter: make(chan chan *headerFilterTask),
// bodyFilter: make(chan chan *bodyFilterTask),
// done: make(chan common.Hash),
// quit: make(chan struct{}),
// announces: make(map[string]int),
// announced: make(map[common.Hash][]*announce),
// fetching: make(map[common.Hash]*announce),
// fetched: make(map[common.Hash][]*announce),
// completing: make(map[common.Hash]*announce),
// queue: prque.New(nil),
// queues: make(map[string]int),
// queued: make(map[common.Hash]*inject),
// knowns: knownBlocks,
// getBlock: getBlock,
// verifyHeader: verifyHeader,
// handleProposedBlock: handleProposedBlock,
// broadcastBlock: broadcastBlock,
// chainHeight: chainHeight,
// insertBlock: insertBlock,
// prepareBlock: prepareBlock,
// dropPeer: dropPeer,
// NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements.
func NewBlockFetcher(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *BlockFetcher {
knownBlocks, _ := lru.NewARC(blockLimit) knownBlocks, _ := lru.NewARC(blockLimit)
return &BlockFetcher{ return &Fetcher{
notify: make(chan *blockAnnounce), notify: make(chan *announce),
inject: make(chan *blockInject), inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask), headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask), bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash), done: make(chan common.Hash),
quit: make(chan struct{}), quit: make(chan struct{}),
announces: make(map[string]int), announces: make(map[string]int),
announced: make(map[common.Hash][]*blockAnnounce), announced: make(map[common.Hash][]*announce),
fetching: make(map[common.Hash]*blockAnnounce), fetching: make(map[common.Hash]*announce),
fetched: make(map[common.Hash][]*blockAnnounce), fetched: make(map[common.Hash][]*announce),
completing: make(map[common.Hash]*blockAnnounce), completing: make(map[common.Hash]*announce),
queue: prque.New(nil), queue: prque.New(nil),
queues: make(map[string]int), queues: make(map[string]int),
queued: make(map[common.Hash]*blockInject), queued: make(map[common.Hash]*inject),
knowns: knownBlocks, knowns: knownBlocks,
getBlock: getBlock, getBlock: getBlock,
verifyHeader: verifyHeader, verifyHeader: verifyHeader,
@ -239,29 +181,28 @@ func NewBlockFetcher(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, h
broadcastBlock: broadcastBlock, broadcastBlock: broadcastBlock,
chainHeight: chainHeight, chainHeight: chainHeight,
insertBlock: insertBlock, insertBlock: insertBlock,
// insertChain: insertChain, prepareBlock: prepareBlock,
prepareBlock: prepareBlock, dropPeer: dropPeer,
dropPeer: dropPeer,
} }
} }
// Start boots up the announcement based synchroniser, accepting and processing // Start boots up the announcement based synchroniser, accepting and processing
// hash notifications and block fetches until termination requested. // hash notifications and block fetches until termination requested.
func (f *BlockFetcher) Start() { func (f *Fetcher) Start() {
go f.loop() go f.loop()
} }
// Stop terminates the announcement based synchroniser, canceling all pending // Stop terminates the announcement based synchroniser, canceling all pending
// operations. // operations.
func (f *BlockFetcher) Stop() { func (f *Fetcher) Stop() {
close(f.quit) close(f.quit)
} }
// Notify announces the fetcher of the potential availability of a new block in // Notify announces the fetcher of the potential availability of a new block in
// the network. // the network.
func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time, func (f *Fetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error { headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error {
block := &blockAnnounce{ block := &announce{
hash: hash, hash: hash,
number: number, number: number,
time: time, time: time,
@ -277,9 +218,9 @@ func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time
} }
} }
// Enqueue tries to fill gaps the fetcher's future import queue. // Enqueue tries to fill gaps the the fetcher's future import queue.
func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error { func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
op := &blockInject{ op := &inject{
origin: peer, origin: peer,
block: block, block: block,
} }
@ -293,7 +234,7 @@ func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error {
// FilterHeaders extracts all the headers that were explicitly requested by the fetcher, // FilterHeaders extracts all the headers that were explicitly requested by the fetcher,
// returning those that should be handled differently. // returning those that should be handled differently.
func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header { func (f *Fetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header {
log.Trace("Filtering headers", "peer", peer, "headers", len(headers)) log.Trace("Filtering headers", "peer", peer, "headers", len(headers))
// Send the filter channel to the fetcher // Send the filter channel to the fetcher
@ -321,7 +262,7 @@ func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time
// FilterBodies extracts all the block bodies that were explicitly requested by // FilterBodies extracts all the block bodies that were explicitly requested by
// the fetcher, returning those that should be handled differently. // the fetcher, returning those that should be handled differently.
func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) { func (f *Fetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) {
log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles)) log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles))
// Send the filter channel to the fetcher // Send the filter channel to the fetcher
@ -349,7 +290,7 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac
// Loop is the main fetcher loop, checking and processing various notification // Loop is the main fetcher loop, checking and processing various notification
// events. // events.
func (f *BlockFetcher) loop() { func (f *Fetcher) loop() {
// Iterate the block fetching until a quit is requested // Iterate the block fetching until a quit is requested
fetchTimer := time.NewTimer(0) fetchTimer := time.NewTimer(0)
completeTimer := time.NewTimer(0) completeTimer := time.NewTimer(0)
@ -364,49 +305,48 @@ func (f *BlockFetcher) loop() {
// Import any queued blocks that could potentially fit // Import any queued blocks that could potentially fit
height := f.chainHeight() height := f.chainHeight()
for !f.queue.Empty() { for !f.queue.Empty() {
op := f.queue.PopItem().(*blockInject) op := f.queue.PopItem().(*inject)
hash := op.block.Hash()
if f.queueChangeHook != nil { if f.queueChangeHook != nil {
f.queueChangeHook(hash, false) f.queueChangeHook(op.block.Hash(), false)
} }
// If too high up the chain or phase, continue later // If too high up the chain or phase, continue later
number := op.block.NumberU64() number := op.block.NumberU64()
if number > height+1 { if number > height+1 {
f.queue.Push(op, -int64(number)) f.queue.Push(op, -int64(op.block.NumberU64()))
if f.queueChangeHook != nil { if f.queueChangeHook != nil {
f.queueChangeHook(hash, true) f.queueChangeHook(op.block.Hash(), true)
} }
break break
} }
// Otherwise if fresh and still unknown, try and import // Otherwise if fresh and still unknown, try and import
hash := op.block.Hash()
if number+maxUncleDist < height || f.getBlock(hash) != nil { if number+maxUncleDist < height || f.getBlock(hash) != nil {
f.forgetBlock(hash) f.forgetBlock(hash)
continue continue
} }
f.insert(op.origin, op.block) f.insert(op.origin, op.block)
} }
// Wait for an outside event to occur // Wait for an outside event to occur
select { select {
case <-f.quit: case <-f.quit:
// BlockFetcher terminating, abort all operations // Fetcher terminating, abort all operations
return return
case notification := <-f.notify: case notification := <-f.notify:
// A block was announced, make sure the peer isn't DOSing us // A block was announced, make sure the peer isn't DOSing us
blockAnnounceInMeter.Mark(1) propAnnounceInMeter.Mark(1)
count := f.announces[notification.origin] + 1 count := f.announces[notification.origin] + 1
if count > hashLimit { if count > hashLimit {
log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit) log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit)
blockAnnounceDOSMeter.Mark(1) propAnnounceDOSMeter.Mark(1)
break break
} }
// If we have a valid block number, check that it's potentially useful // If we have a valid block number, check that it's potentially useful
if notification.number > 0 { if notification.number > 0 {
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
blockAnnounceDropMeter.Mark(1) propAnnounceDropMeter.Mark(1)
break break
} }
} }
@ -428,7 +368,7 @@ func (f *BlockFetcher) loop() {
case op := <-f.inject: case op := <-f.inject:
// A direct block insertion was requested, try and fill any pending gaps // A direct block insertion was requested, try and fill any pending gaps
blockBroadcastInMeter.Mark(1) propBroadcastInMeter.Mark(1)
f.enqueue(op.origin, op.block) f.enqueue(op.origin, op.block)
case hash := <-f.done: case hash := <-f.done:
@ -514,8 +454,8 @@ func (f *BlockFetcher) loop() {
headerFilterInMeter.Mark(int64(len(task.headers))) headerFilterInMeter.Mark(int64(len(task.headers)))
// Split the batch of headers into unknown ones (to return to the caller), // Split the batch of headers into unknown ones (to return to the caller),
// known incomplete ones (requiring body retrievals) and completed blocks. // knowns incomplete ones (requiring body retrievals) and completed blocks.
unknown, incomplete, complete := []*types.Header{}, []*blockAnnounce{}, []*types.Block{} unknown, incomplete, complete := []*types.Header{}, []*announce{}, []*types.Block{}
for _, header := range task.headers { for _, header := range task.headers {
hash := header.Hash() hash := header.Hash()
@ -551,7 +491,7 @@ func (f *BlockFetcher) loop() {
f.forgetHash(hash) f.forgetHash(hash)
} }
} else { } else {
// BlockFetcher doesn't know about it, add to the return list // Fetcher doesn't know about it, add to the return list
unknown = append(unknown, header) unknown = append(unknown, header)
} }
} }
@ -638,8 +578,8 @@ func (f *BlockFetcher) loop() {
} }
} }
// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout. // rescheduleFetch resets the specified fetch timer to the next announce timeout.
func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) { func (f *Fetcher) rescheduleFetch(fetch *time.Timer) {
// Short circuit if no blocks are announced // Short circuit if no blocks are announced
if len(f.announced) == 0 { if len(f.announced) == 0 {
return return
@ -655,7 +595,7 @@ func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) {
} }
// rescheduleComplete resets the specified completion timer to the next fetch timeout. // rescheduleComplete resets the specified completion timer to the next fetch timeout.
func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) { func (f *Fetcher) rescheduleComplete(complete *time.Timer) {
// Short circuit if no headers are fetched // Short circuit if no headers are fetched
if len(f.fetched) == 0 { if len(f.fetched) == 0 {
return return
@ -672,7 +612,7 @@ func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) {
// enqueue schedules a new future import operation, if the block to be imported // enqueue schedules a new future import operation, if the block to be imported
// has not yet been seen. // has not yet been seen.
func (f *BlockFetcher) enqueue(peer string, block *types.Block) { func (f *Fetcher) enqueue(peer string, block *types.Block) {
hash := block.Hash() hash := block.Hash()
if f.knowns.Contains(hash) { if f.knowns.Contains(hash) {
log.Trace("Discarded propagated block, known block", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit) log.Trace("Discarded propagated block, known block", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit)
@ -682,20 +622,20 @@ func (f *BlockFetcher) enqueue(peer string, block *types.Block) {
count := f.queues[peer] + 1 count := f.queues[peer] + 1
if count > blockLimit { if count > blockLimit {
log.Debug("Discarded propagated block, exceeded allowance", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit) log.Debug("Discarded propagated block, exceeded allowance", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit)
blockBroadcastDOSMeter.Mark(1) propBroadcastDOSMeter.Mark(1)
f.forgetHash(hash) f.forgetHash(hash)
return return
} }
// Discard any past or too distant blocks // Discard any past or too distant blocks
if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
log.Debug("Discarded propagated block, too far away", "peer", peer, "number", block.Number(), "hash", hash, "distance", dist) log.Debug("Discarded propagated block, too far away", "peer", peer, "number", block.Number(), "hash", hash, "distance", dist)
blockBroadcastDropMeter.Mark(1) propBroadcastDropMeter.Mark(1)
f.forgetHash(hash) f.forgetHash(hash)
return return
} }
// Schedule the block for future importing // Schedule the block for future importing
if _, ok := f.queued[hash]; !ok { if _, ok := f.queued[hash]; !ok {
op := &blockInject{ op := &inject{
origin: peer, origin: peer,
block: block, block: block,
} }
@ -713,7 +653,7 @@ func (f *BlockFetcher) enqueue(peer string, block *types.Block) {
// insert spawns a new goroutine to run a block insertion into the chain. If the // insert spawns a new goroutine to run a block insertion into the chain. If the
// block's number is at the same height as the current import phase, it updates // block's number is at the same height as the current import phase, it updates
// the phase states accordingly. // the phase states accordingly.
func (f *BlockFetcher) insert(peer string, block *types.Block) { func (f *Fetcher) insert(peer string, block *types.Block) {
hash := block.Hash() hash := block.Hash()
// Run the import on a new thread // Run the import on a new thread
@ -727,18 +667,17 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash()) log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash())
return return
} }
fastBroadCast := true //TODO: double check if we need fastBroadCast logic fastBroadCast := true
again: again:
err := f.verifyHeader(block.Header()) err := f.verifyHeader(block.Header())
// Quickly validate the header and propagate the block if it passes // Quickly validate the header and propagate the block if it passes
switch err { switch err {
case nil: case nil:
// All ok, quickly propagate to our peers // All ok, quickly propagate to our peers
blockBroadcastOutTimer.UpdateSince(block.ReceivedAt) propBroadcastOutTimer.UpdateSince(block.ReceivedAt)
if fastBroadCast { if fastBroadCast {
go f.broadcastBlock(block, true) go f.broadcastBlock(block, true)
} }
case consensus.ErrFutureBlock: case consensus.ErrFutureBlock:
delay := time.Unix(block.Time().Int64(), 0).Sub(time.Now()) // nolint: gosimple delay := time.Unix(block.Time().Int64(), 0).Sub(time.Now()) // nolint: gosimple
log.Info("Receive future block", "number", block.NumberU64(), "hash", block.Hash().Hex(), "delay", delay) log.Info("Receive future block", "number", block.NumberU64(), "hash", block.Hash().Hex(), "delay", delay)
@ -769,7 +708,7 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
} }
block = newBlock block = newBlock
fastBroadCast = false fastBroadCast = false
goto again //TODO: doublecheck if goto again logic is required goto again
default: default:
// Something went very wrong, drop the peer // Something went very wrong, drop the peer
log.Warn("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) log.Warn("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
@ -777,7 +716,7 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
return return
} }
// Run the actual import and log any issues // Run the actual import and log any issues
if err := f.insertBlock(*block); err != nil { if err := f.insertBlock(block); err != nil {
log.Warn("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) log.Warn("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
return return
} }
@ -793,25 +732,20 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
log.Warn("[insert] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash()) log.Warn("[insert] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash())
} }
// If import succeeded, broadcast the block // If import succeeded, broadcast the block
blockAnnounceOutTimer.UpdateSince(block.ReceivedAt) propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
if !fastBroadCast { if !fastBroadCast {
go f.broadcastBlock(block, false) go f.broadcastBlock(block, true)
}
// Invoke the testing hook if needed
if f.importedHook != nil {
f.importedHook(block)
} }
}() }()
} }
// forgetHash removes all traces of a block announcement from the fetcher's // forgetHash removes all traces of a block announcement from the fetcher's
// internal state. // internal state.
func (f *BlockFetcher) forgetHash(hash common.Hash) { func (f *Fetcher) forgetHash(hash common.Hash) {
// Remove all pending announces and decrement DOS counters // Remove all pending announces and decrement DOS counters
for _, announce := range f.announced[hash] { for _, announce := range f.announced[hash] {
f.announces[announce.origin]-- f.announces[announce.origin]--
if f.announces[announce.origin] <= 0 { if f.announces[announce.origin] == 0 {
delete(f.announces, announce.origin) delete(f.announces, announce.origin)
} }
} }
@ -822,7 +756,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
// Remove any pending fetches and decrement the DOS counters // Remove any pending fetches and decrement the DOS counters
if announce := f.fetching[hash]; announce != nil { if announce := f.fetching[hash]; announce != nil {
f.announces[announce.origin]-- f.announces[announce.origin]--
if f.announces[announce.origin] <= 0 { if f.announces[announce.origin] == 0 {
delete(f.announces, announce.origin) delete(f.announces, announce.origin)
} }
delete(f.fetching, hash) delete(f.fetching, hash)
@ -831,7 +765,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
// Remove any pending completion requests and decrement the DOS counters // Remove any pending completion requests and decrement the DOS counters
for _, announce := range f.fetched[hash] { for _, announce := range f.fetched[hash] {
f.announces[announce.origin]-- f.announces[announce.origin]--
if f.announces[announce.origin] <= 0 { if f.announces[announce.origin] == 0 {
delete(f.announces, announce.origin) delete(f.announces, announce.origin)
} }
} }
@ -840,7 +774,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
// Remove any pending completions and decrement the DOS counters // Remove any pending completions and decrement the DOS counters
if announce := f.completing[hash]; announce != nil { if announce := f.completing[hash]; announce != nil {
f.announces[announce.origin]-- f.announces[announce.origin]--
if f.announces[announce.origin] <= 0 { if f.announces[announce.origin] == 0 {
delete(f.announces, announce.origin) delete(f.announces, announce.origin)
} }
delete(f.completing, hash) delete(f.completing, hash)
@ -849,7 +783,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
// forgetBlock removes all traces of a queued block from the fetcher's internal // forgetBlock removes all traces of a queued block from the fetcher's internal
// state. // state.
func (f *BlockFetcher) forgetBlock(hash common.Hash) { func (f *Fetcher) forgetBlock(hash common.Hash) {
if insert := f.queued[hash]; insert != nil { if insert := f.queued[hash]; insert != nil {
f.queues[insert.origin]-- f.queues[insert.origin]--
if f.queues[insert.origin] == 0 { if f.queues[insert.origin] == 0 {
@ -860,11 +794,11 @@ func (f *BlockFetcher) forgetBlock(hash common.Hash) {
} }
// Bind double validate hook before block imported into chain. // Bind double validate hook before block imported into chain.
func (f *BlockFetcher) SetSignHook(signHook func(*types.Block) error) { func (f *Fetcher) SetSignHook(signHook func(*types.Block) error) {
f.signHook = signHook f.signHook = signHook
} }
// Bind append m2 to block header hook when imported into chain. // Bind append m2 to block header hook when imported into chain.
func (f *BlockFetcher) SetAppendM2HeaderHook(appendM2HeaderHook func(*types.Block) (*types.Block, bool, error)) { func (f *Fetcher) SetAppendM2HeaderHook(appendM2HeaderHook func(*types.Block) (*types.Block, bool, error)) {
f.appendM2HeaderHook = appendM2HeaderHook f.appendM2HeaderHook = appendM2HeaderHook
} }

View file

@ -77,7 +77,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
// fetcherTester is a test simulator for mocking out local block chain. // fetcherTester is a test simulator for mocking out local block chain.
type fetcherTester struct { type fetcherTester struct {
fetcher *BlockFetcher fetcher *Fetcher
hashes []common.Hash // Hash chain belonging to the tester hashes []common.Hash // Hash chain belonging to the tester
blocks map[common.Hash]*types.Block // Blocks belonging to the tester blocks map[common.Hash]*types.Block // Blocks belonging to the tester
@ -93,7 +93,7 @@ func newTester() *fetcherTester {
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
drops: make(map[string]bool), drops: make(map[string]bool),
} }
tester.fetcher = NewBlockFetcher(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer) tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
tester.fetcher.Start() tester.fetcher.Start()
return tester return tester
@ -150,7 +150,7 @@ func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) {
} }
// insertBlock injects a new blocks into the simulated chain. // insertBlock injects a new blocks into the simulated chain.
func (f *fetcherTester) insertBlock(block types.Block) error { func (f *fetcherTester) insertBlock(block *types.Block) error {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
@ -164,7 +164,7 @@ func (f *fetcherTester) insertBlock(block types.Block) error {
} }
// Otherwise build our current chain // Otherwise build our current chain
f.hashes = append(f.hashes, block.Hash()) f.hashes = append(f.hashes, block.Hash())
f.blocks[block.Hash()] = &block f.blocks[block.Hash()] = block
return nil return nil
} }
@ -311,11 +311,9 @@ func verifyProposeBlockHandlerCalled(t *testing.T, proposedBlockChan chan *types
// Tests that a fetcher accepts block announcements and initiates retrievals for // Tests that a fetcher accepts block announcements and initiates retrievals for
// them, successfully importing into the local chain. // them, successfully importing into the local chain.
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) } func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) } func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) }
func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) } func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) }
func TestSequentialAnnouncements100(t *testing.T) { testSequentialAnnouncements(t, 100) }
func TestSequentialAnnouncements101(t *testing.T) { testSequentialAnnouncements(t, 101) }
func testSequentialAnnouncements(t *testing.T, protocol int) { func testSequentialAnnouncements(t *testing.T, protocol int) {
// Create a chain of blocks to import // Create a chain of blocks to import
@ -348,11 +346,9 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
// Tests that if blocks are announced by multiple peers (or even the same buggy // Tests that if blocks are announced by multiple peers (or even the same buggy
// peer), they will only get downloaded at most once. // peer), they will only get downloaded at most once.
func TestConcurrentAnnouncements62(t *testing.T) { testConcurrentAnnouncements(t, 62) } func TestConcurrentAnnouncements62(t *testing.T) { testConcurrentAnnouncements(t, 62) }
func TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) } func TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) }
func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) } func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) }
func TestConcurrentAnnouncements100(t *testing.T) { testConcurrentAnnouncements(t, 100) }
func TestConcurrentAnnouncements101(t *testing.T) { testConcurrentAnnouncements(t, 101) }
func testConcurrentAnnouncements(t *testing.T, protocol int) { func testConcurrentAnnouncements(t *testing.T, protocol int) {
// Create a chain of blocks to import // Create a chain of blocks to import
@ -398,11 +394,9 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
// Tests that announcements arriving while a previous is being fetched still // Tests that announcements arriving while a previous is being fetched still
// results in a valid import. // results in a valid import.
func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) } func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) }
func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) } func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) }
func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) } func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) }
func TestOverlappingAnnouncements100(t *testing.T) { testOverlappingAnnouncements(t, 100) }
func TestOverlappingAnnouncements101(t *testing.T) { testOverlappingAnnouncements(t, 101) }
func testOverlappingAnnouncements(t *testing.T, protocol int) { func testOverlappingAnnouncements(t *testing.T, protocol int) {
// Create a chain of blocks to import // Create a chain of blocks to import
@ -437,11 +431,9 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
} }
// Tests that announces already being retrieved will not be duplicated. // Tests that announces already being retrieved will not be duplicated.
func TestPendingDeduplication62(t *testing.T) { testPendingDeduplication(t, 62) } func TestPendingDeduplication62(t *testing.T) { testPendingDeduplication(t, 62) }
func TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) } func TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) }
func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) } func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) }
func TestPendingDeduplication100(t *testing.T) { testPendingDeduplication(t, 100) }
func TestPendingDeduplication101(t *testing.T) { testPendingDeduplication(t, 101) }
func testPendingDeduplication(t *testing.T, protocol int) { func testPendingDeduplication(t *testing.T, protocol int) {
// Create a hash and corresponding block // Create a hash and corresponding block
@ -482,11 +474,9 @@ func testPendingDeduplication(t *testing.T, protocol int) {
// Tests that announcements retrieved in a random order are cached and eventually // Tests that announcements retrieved in a random order are cached and eventually
// imported when all the gaps are filled in. // imported when all the gaps are filled in.
func TestRandomArrivalImport62(t *testing.T) { testRandomArrivalImport(t, 62) } func TestRandomArrivalImport62(t *testing.T) { testRandomArrivalImport(t, 62) }
func TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) } func TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) }
func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) } func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) }
func TestRandomArrivalImport100(t *testing.T) { testRandomArrivalImport(t, 100) }
func TestRandomArrivalImport101(t *testing.T) { testRandomArrivalImport(t, 101) }
func testRandomArrivalImport(t *testing.T, protocol int) { func testRandomArrivalImport(t *testing.T, protocol int) {
// Create a chain of blocks to import, and choose one to delay // Create a chain of blocks to import, and choose one to delay
@ -518,11 +508,9 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
// Tests that direct block enqueues (due to block propagation vs. hash announce) // Tests that direct block enqueues (due to block propagation vs. hash announce)
// are correctly schedule, filling and import queue gaps. // are correctly schedule, filling and import queue gaps.
func TestQueueGapFill62(t *testing.T) { testQueueGapFill(t, 62) } func TestQueueGapFill62(t *testing.T) { testQueueGapFill(t, 62) }
func TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) } func TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) }
func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) } func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) }
func TestQueueGapFill100(t *testing.T) { testQueueGapFill(t, 100) }
func TestQueueGapFill101(t *testing.T) { testQueueGapFill(t, 101) }
func testQueueGapFill(t *testing.T, protocol int) { func testQueueGapFill(t *testing.T, protocol int) {
// Create a chain of blocks to import, and choose one to not announce at all // Create a chain of blocks to import, and choose one to not announce at all
@ -554,11 +542,9 @@ func testQueueGapFill(t *testing.T, protocol int) {
// Tests that blocks arriving from various sources (multiple propagations, hash // Tests that blocks arriving from various sources (multiple propagations, hash
// announces, etc) do not get scheduled for import multiple times. // announces, etc) do not get scheduled for import multiple times.
func TestImportDeduplication62(t *testing.T) { testImportDeduplication(t, 62) } func TestImportDeduplication62(t *testing.T) { testImportDeduplication(t, 62) }
func TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) } func TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) }
func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) } func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) }
func TestImportDeduplication100(t *testing.T) { testImportDeduplication(t, 100) }
func TestImportDeduplication101(t *testing.T) { testImportDeduplication(t, 101) }
func testImportDeduplication(t *testing.T, protocol int) { func testImportDeduplication(t *testing.T, protocol int) {
// Create two blocks to import (one for duplication, the other for stalling) // Create two blocks to import (one for duplication, the other for stalling)
@ -570,7 +556,7 @@ func testImportDeduplication(t *testing.T, protocol int) {
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
counter := uint32(0) counter := uint32(0)
tester.fetcher.insertBlock = func(block types.Block) error { tester.fetcher.insertBlock = func(block *types.Block) error {
atomic.AddUint32(&counter, uint32(1)) atomic.AddUint32(&counter, uint32(1))
return tester.insertBlock(block) return tester.insertBlock(block)
} }
@ -634,11 +620,9 @@ func TestDistantPropagationDiscarding(t *testing.T) {
// Tests that announcements with numbers much lower or higher than out current // Tests that announcements with numbers much lower or higher than out current
// head get discarded to prevent wasting resources on useless blocks from faulty // head get discarded to prevent wasting resources on useless blocks from faulty
// peers. // peers.
func TestDistantAnnouncementDiscarding62(t *testing.T) { testDistantAnnouncementDiscarding(t, 62) } func TestDistantAnnouncementDiscarding62(t *testing.T) { testDistantAnnouncementDiscarding(t, 62) }
func TestDistantAnnouncementDiscarding63(t *testing.T) { testDistantAnnouncementDiscarding(t, 63) } func TestDistantAnnouncementDiscarding63(t *testing.T) { testDistantAnnouncementDiscarding(t, 63) }
func TestDistantAnnouncementDiscarding64(t *testing.T) { testDistantAnnouncementDiscarding(t, 64) } func TestDistantAnnouncementDiscarding64(t *testing.T) { testDistantAnnouncementDiscarding(t, 64) }
func TestDistantAnnouncementDiscarding100(t *testing.T) { testDistantAnnouncementDiscarding(t, 100) }
func TestDistantAnnouncementDiscarding101(t *testing.T) { testDistantAnnouncementDiscarding(t, 101) }
func testDistantAnnouncementDiscarding(t *testing.T, protocol int) { func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
// Create a long chain to import and define the discard boundaries // Create a long chain to import and define the discard boundaries
@ -679,11 +663,9 @@ func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
// Tests that peers announcing blocks with invalid numbers (i.e. not matching // Tests that peers announcing blocks with invalid numbers (i.e. not matching
// the headers provided afterwards) get dropped as malicious. // the headers provided afterwards) get dropped as malicious.
func TestInvalidNumberAnnouncement62(t *testing.T) { testInvalidNumberAnnouncement(t, 62) } func TestInvalidNumberAnnouncement62(t *testing.T) { testInvalidNumberAnnouncement(t, 62) }
func TestInvalidNumberAnnouncement63(t *testing.T) { testInvalidNumberAnnouncement(t, 63) } func TestInvalidNumberAnnouncement63(t *testing.T) { testInvalidNumberAnnouncement(t, 63) }
func TestInvalidNumberAnnouncement64(t *testing.T) { testInvalidNumberAnnouncement(t, 64) } func TestInvalidNumberAnnouncement64(t *testing.T) { testInvalidNumberAnnouncement(t, 64) }
func TestInvalidNumberAnnouncement100(t *testing.T) { testInvalidNumberAnnouncement(t, 100) }
func TestInvalidNumberAnnouncement101(t *testing.T) { testInvalidNumberAnnouncement(t, 101) }
func testInvalidNumberAnnouncement(t *testing.T, protocol int) { func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
// Create a single block to import and check numbers against // Create a single block to import and check numbers against
@ -729,11 +711,9 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
// Tests that if a block is empty (i.e. header only), no body request should be // Tests that if a block is empty (i.e. header only), no body request should be
// made, and instead the header should be assembled into a whole block in itself. // made, and instead the header should be assembled into a whole block in itself.
func TestEmptyBlockShortCircuit62(t *testing.T) { testEmptyBlockShortCircuit(t, 62) } func TestEmptyBlockShortCircuit62(t *testing.T) { testEmptyBlockShortCircuit(t, 62) }
func TestEmptyBlockShortCircuit63(t *testing.T) { testEmptyBlockShortCircuit(t, 63) } func TestEmptyBlockShortCircuit63(t *testing.T) { testEmptyBlockShortCircuit(t, 63) }
func TestEmptyBlockShortCircuit64(t *testing.T) { testEmptyBlockShortCircuit(t, 64) } func TestEmptyBlockShortCircuit64(t *testing.T) { testEmptyBlockShortCircuit(t, 64) }
func TestEmptyBlockShortCircuit100(t *testing.T) { testEmptyBlockShortCircuit(t, 100) }
func TestEmptyBlockShortCircuit101(t *testing.T) { testEmptyBlockShortCircuit(t, 101) }
func testEmptyBlockShortCircuit(t *testing.T, protocol int) { func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
// Create a chain of blocks to import // Create a chain of blocks to import
@ -775,11 +755,9 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
// Tests that a peer is unable to use unbounded memory with sending infinite // Tests that a peer is unable to use unbounded memory with sending infinite
// block announcements to a node, but that even in the face of such an attack, // block announcements to a node, but that even in the face of such an attack,
// the fetcher remains operational. // the fetcher remains operational.
func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) } func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) }
func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) } func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) }
func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) } func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) }
func TestHashMemoryExhaustionAttack100(t *testing.T) { testHashMemoryExhaustionAttack(t, 100) }
func TestHashMemoryExhaustionAttack101(t *testing.T) { testHashMemoryExhaustionAttack(t, 101) }
func testHashMemoryExhaustionAttack(t *testing.T, protocol int) { func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
// Create a tester with instrumented import hooks // Create a tester with instrumented import hooks

43
eth/fetcher/metrics.go Normal file
View file

@ -0,0 +1,43 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the metrics collected by the fetcher.
package fetcher
import (
"github.com/XinFinOrg/XDPoSChain/metrics"
)
var (
propAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/in", nil)
propAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/announces/out", nil)
propAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/drop", nil)
propAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/dos", nil)
propBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/in", nil)
propBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/broadcasts/out", nil)
propBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/drop", nil)
propBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/dos", nil)
headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/headers", nil)
bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/bodies", nil)
headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/in", nil)
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/out", nil)
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/in", nil)
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/out", nil)
)

View file

@ -1,894 +0,0 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package fetcher
import (
"bytes"
"fmt"
mrand "math/rand"
"sort"
"time"
mapset "github.com/deckarep/golang-set"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
)
const (
// maxTxAnnounces is the maximum number of unique transaction a peer
// can announce in a short time.
maxTxAnnounces = 4096
// maxTxRetrievals is the maximum transaction number can be fetched in one
// request. The rationale to pick 256 is:
// - In eth protocol, the softResponseLimit is 2MB. Nowadays according to
// Etherscan the average transaction size is around 200B, so in theory
// we can include lots of transaction in a single protocol packet.
// - However the maximum size of a single transaction is raised to 128KB,
// so pick a middle value here to ensure we can maximize the efficiency
// of the retrieval and response size overflow won't happen in most cases.
maxTxRetrievals = 256
// maxTxUnderpricedSetSize is the size of the underpriced transaction set that
// is used to track recent transactions that have been dropped so we don't
// re-request them.
maxTxUnderpricedSetSize = 32768
// txArriveTimeout is the time allowance before an announced transaction is
// explicitly requested.
txArriveTimeout = 500 * time.Millisecond
// txGatherSlack is the interval used to collate almost-expired announces
// with network fetches.
txGatherSlack = 100 * time.Millisecond
)
var (
// txFetchTimeout is the maximum allotted time to return an explicitly
// requested transaction.
txFetchTimeout = 5 * time.Second
)
var (
txAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/in", nil)
txAnnounceKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/known", nil)
txAnnounceUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/underpriced", nil)
txAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/dos", nil)
txBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/in", nil)
txBroadcastKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/known", nil)
txBroadcastUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/underpriced", nil)
txBroadcastOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/otherreject", nil)
txRequestOutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/out", nil)
txRequestFailMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/fail", nil)
txRequestDoneMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/done", nil)
txRequestTimeoutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/timeout", nil)
txReplyInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/in", nil)
txReplyKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/known", nil)
txReplyUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/underpriced", nil)
txReplyOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/otherreject", nil)
txFetcherWaitingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/peers", nil)
txFetcherWaitingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/hashes", nil)
txFetcherQueueingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/peers", nil)
txFetcherQueueingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/hashes", nil)
txFetcherFetchingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/peers", nil)
txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil)
)
// txAnnounce is the notification of the availability of a batch
// of new transactions in the network.
type txAnnounce struct {
origin string // Identifier of the peer originating the notification
hashes []common.Hash // Batch of transaction hashes being announced
}
// txRequest represents an in-flight transaction retrieval request destined to
// a specific peers.
type txRequest struct {
hashes []common.Hash // Transactions having been requested
stolen map[common.Hash]struct{} // Deliveries by someone else (don't re-request)
time mclock.AbsTime // Timestamp of the request
}
// txDelivery is the notification that a batch of transactions have been added
// to the pool and should be untracked.
type txDelivery struct {
origin string // Identifier of the peer originating the notification
hashes []common.Hash // Batch of transaction hashes having been delivered
direct bool // Whether this is a direct reply or a broadcast
}
// txDrop is the notiication that a peer has disconnected.
type txDrop struct {
peer string
}
// TxFetcher is responsible for retrieving new transaction based on announcements.
//
// The fetcher operates in 3 stages:
// - Transactions that are newly discovered are moved into a wait list.
// - After ~500ms passes, transactions from the wait list that have not been
// broadcast to us in whole are moved into a queueing area.
// - When a connected peer doesn't have in-flight retrieval requests, any
// transaction queued up (and announced by the peer) are allocated to the
// peer and moved into a fetching status until it's fulfilled or fails.
//
// The invariants of the fetcher are:
// - Each tracked transaction (hash) must only be present in one of the
// three stages. This ensures that the fetcher operates akin to a finite
// state automata and there's do data leak.
// - Each peer that announced transactions may be scheduled retrievals, but
// only ever one concurrently. This ensures we can immediately know what is
// missing from a reply and reschedule it.
type TxFetcher struct {
notify chan *txAnnounce
cleanup chan *txDelivery
drop chan *txDrop
quit chan struct{}
underpriced mapset.Set // Transactions discarded as too cheap (don't re-fetch)
// Stage 1: Waiting lists for newly discovered transactions that might be
// broadcast without needing explicit request/reply round trips.
waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast
waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist
waitslots map[string]map[common.Hash]struct{} // Waiting announcement sgroupped by peer (DoS protection)
// Stage 2: Queue of transactions that waiting to be allocated to some peer
// to be retrieved directly.
announces map[string]map[common.Hash]struct{} // Set of announced transactions, grouped by origin peer
announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash
// Stage 3: Set of transactions currently being retrieved, some which may be
// fulfilled and some rescheduled. Note, this step shares 'announces' from the
// previous stage to avoid having to duplicate (need it for DoS checks).
fetching map[common.Hash]string // Transaction set currently being retrieved
requests map[string]*txRequest // In-flight transaction retrievals
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
// Callbacks
hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
step chan struct{} // Notification channel when the fetcher loop iterates
clock mclock.Clock // Time wrapper to simulate in tests
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
}
// NewTxFetcher creates a transaction fetcher to retrieve transaction
// based on hash announcements.
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error) *TxFetcher {
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil)
}
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
// a simulated version and the internal randomness with a deterministic one.
func NewTxFetcherForTests(
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
return &TxFetcher{
notify: make(chan *txAnnounce),
cleanup: make(chan *txDelivery),
drop: make(chan *txDrop),
quit: make(chan struct{}),
waitlist: make(map[common.Hash]map[string]struct{}),
waittime: make(map[common.Hash]mclock.AbsTime),
waitslots: make(map[string]map[common.Hash]struct{}),
announces: make(map[string]map[common.Hash]struct{}),
announced: make(map[common.Hash]map[string]struct{}),
fetching: make(map[common.Hash]string),
requests: make(map[string]*txRequest),
alternates: make(map[common.Hash]map[string]struct{}),
underpriced: mapset.NewSet(),
hasTx: hasTx,
addTxs: addTxs,
fetchTxs: fetchTxs,
clock: clock,
rand: rand,
}
}
// Notify announces the fetcher of the potential availability of a new batch of
// transactions in the network.
func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
// Keep track of all the announced transactions
txAnnounceInMeter.Mark(int64(len(hashes)))
// Skip any transaction announcements that we already know of, or that we've
// previously marked as cheap and discarded. This check is of course racey,
// because multiple concurrent notifies will still manage to pass it, but it's
// still valuable to check here because it runs concurrent to the internal
// loop, so anything caught here is time saved internally.
var (
unknowns = make([]common.Hash, 0, len(hashes))
duplicate, underpriced int64
)
for _, hash := range hashes {
switch {
case f.hasTx(hash):
duplicate++
case f.underpriced.Contains(hash):
underpriced++
default:
unknowns = append(unknowns, hash)
}
}
txAnnounceKnownMeter.Mark(duplicate)
txAnnounceUnderpricedMeter.Mark(underpriced)
// If anything's left to announce, push it into the internal loop
if len(unknowns) == 0 {
return nil
}
announce := &txAnnounce{
origin: peer,
hashes: unknowns,
}
select {
case f.notify <- announce:
return nil
case <-f.quit:
return errTerminated
}
}
// Enqueue imports a batch of received transaction into the transaction pool
// and the fetcher. This method may be called by both transaction broadcasts and
// direct request replies. The differentiation is important so the fetcher can
// re-shedule missing transactions as soon as possible.
func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error {
// Keep track of all the propagated transactions
if direct {
txReplyInMeter.Mark(int64(len(txs)))
} else {
txBroadcastInMeter.Mark(int64(len(txs)))
}
// Push all the transactions into the pool, tracking underpriced ones to avoid
// re-requesting them and dropping the peer in case of malicious transfers.
var (
added = make([]common.Hash, 0, len(txs))
duplicate int64
underpriced int64
otherreject int64
)
errs := f.addTxs(txs)
for i, err := range errs {
if err != nil {
// Track the transaction hash if the price is too low for us.
// Avoid re-request this transaction when we receive another
// announcement.
if err == core.ErrUnderpriced || err == core.ErrReplaceUnderpriced {
for f.underpriced.Cardinality() >= maxTxUnderpricedSetSize {
f.underpriced.Pop()
}
f.underpriced.Add(txs[i].Hash())
}
// Track a few interesting failure types
switch err {
case nil: // Noop, but need to handle to not count these
case core.ErrAlreadyKnown:
duplicate++
case core.ErrUnderpriced, core.ErrReplaceUnderpriced:
underpriced++
default:
otherreject++
}
}
added = append(added, txs[i].Hash())
}
if direct {
txReplyKnownMeter.Mark(duplicate)
txReplyUnderpricedMeter.Mark(underpriced)
txReplyOtherRejectMeter.Mark(otherreject)
} else {
txBroadcastKnownMeter.Mark(duplicate)
txBroadcastUnderpricedMeter.Mark(underpriced)
txBroadcastOtherRejectMeter.Mark(otherreject)
}
select {
case f.cleanup <- &txDelivery{origin: peer, hashes: added, direct: direct}:
return nil
case <-f.quit:
return errTerminated
}
}
// Drop should be called when a peer disconnects. It cleans up all the internal
// data structures of the given node.
func (f *TxFetcher) Drop(peer string) error {
select {
case f.drop <- &txDrop{peer: peer}:
return nil
case <-f.quit:
return errTerminated
}
}
// Start boots up the announcement based synchroniser, accepting and processing
// hash notifications and block fetches until termination requested.
func (f *TxFetcher) Start() {
go f.loop()
}
// Stop terminates the announcement based synchroniser, canceling all pending
// operations.
func (f *TxFetcher) Stop() {
close(f.quit)
}
func (f *TxFetcher) loop() {
var (
waitTimer = new(mclock.Timer)
timeoutTimer = new(mclock.Timer)
waitTrigger = make(chan struct{}, 1)
timeoutTrigger = make(chan struct{}, 1)
)
for {
select {
case ann := <-f.notify:
// Drop part of the new announcements if there are too many accumulated.
// Note, we could but do not filter already known transactions here as
// the probability of something arriving between this call and the pre-
// filter outside is essentially zero.
used := len(f.waitslots[ann.origin]) + len(f.announces[ann.origin])
if used >= maxTxAnnounces {
// This can happen if a set of transactions are requested but not
// all fulfilled, so the remainder are rescheduled without the cap
// check. Should be fine as the limit is in the thousands and the
// request size in the hundreds.
txAnnounceDOSMeter.Mark(int64(len(ann.hashes)))
break
}
want := used + len(ann.hashes)
if want > maxTxAnnounces {
txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces))
ann.hashes = ann.hashes[:want-maxTxAnnounces]
}
// All is well, schedule the remainder of the transactions
idleWait := len(f.waittime) == 0
_, oldPeer := f.announces[ann.origin]
for _, hash := range ann.hashes {
// If the transaction is already downloading, add it to the list
// of possible alternates (in case the current retrieval fails) and
// also account it for the peer.
if f.alternates[hash] != nil {
f.alternates[hash][ann.origin] = struct{}{}
// Stage 2 and 3 share the set of origins per tx
if announces := f.announces[ann.origin]; announces != nil {
announces[hash] = struct{}{}
} else {
f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
}
continue
}
// If the transaction is not downloading, but is already queued
// from a different peer, track it for the new peer too.
if f.announced[hash] != nil {
f.announced[hash][ann.origin] = struct{}{}
// Stage 2 and 3 share the set of origins per tx
if announces := f.announces[ann.origin]; announces != nil {
announces[hash] = struct{}{}
} else {
f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
}
continue
}
// If the transaction is already known to the fetcher, but not
// yet downloading, add the peer as an alternate origin in the
// waiting list.
if f.waitlist[hash] != nil {
f.waitlist[hash][ann.origin] = struct{}{}
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
waitslots[hash] = struct{}{}
} else {
f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
}
continue
}
// Transaction unknown to the fetcher, insert it into the waiting list
f.waitlist[hash] = map[string]struct{}{ann.origin: struct{}{}}
f.waittime[hash] = f.clock.Now()
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
waitslots[hash] = struct{}{}
} else {
f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
}
}
// If a new item was added to the waitlist, schedule it into the fetcher
if idleWait && len(f.waittime) > 0 {
f.rescheduleWait(waitTimer, waitTrigger)
}
// If this peer is new and announced something already queued, maybe
// request transactions from them
if !oldPeer && len(f.announces[ann.origin]) > 0 {
f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: struct{}{}})
}
case <-waitTrigger:
// At least one transaction's waiting time ran out, push all expired
// ones into the retrieval queues
actives := make(map[string]struct{})
for hash, instance := range f.waittime {
if time.Duration(f.clock.Now()-instance)+txGatherSlack > txArriveTimeout {
// Transaction expired without propagation, schedule for retrieval
if f.announced[hash] != nil {
panic("announce tracker already contains waitlist item")
}
f.announced[hash] = f.waitlist[hash]
for peer := range f.waitlist[hash] {
if announces := f.announces[peer]; announces != nil {
announces[hash] = struct{}{}
} else {
f.announces[peer] = map[common.Hash]struct{}{hash: struct{}{}}
}
delete(f.waitslots[peer], hash)
if len(f.waitslots[peer]) == 0 {
delete(f.waitslots, peer)
}
actives[peer] = struct{}{}
}
delete(f.waittime, hash)
delete(f.waitlist, hash)
}
}
// If transactions are still waiting for propagation, reschedule the wait timer
if len(f.waittime) > 0 {
f.rescheduleWait(waitTimer, waitTrigger)
}
// If any peers became active and are idle, request transactions from them
if len(actives) > 0 {
f.scheduleFetches(timeoutTimer, timeoutTrigger, actives)
}
case <-timeoutTrigger:
// Clean up any expired retrievals and avoid re-requesting them from the
// same peer (either overloaded or malicious, useless in both cases). We
// could also penalize (Drop), but there's nothing to gain, and if could
// possibly further increase the load on it.
for peer, req := range f.requests {
if time.Duration(f.clock.Now()-req.time)+txGatherSlack > txFetchTimeout {
txRequestTimeoutMeter.Mark(int64(len(req.hashes)))
// Reschedule all the not-yet-delivered fetches to alternate peers
for _, hash := range req.hashes {
// Skip rescheduling hashes already delivered by someone else
if req.stolen != nil {
if _, ok := req.stolen[hash]; ok {
continue
}
}
// Move the delivery back from fetching to queued
if _, ok := f.announced[hash]; ok {
panic("announced tracker already contains alternate item")
}
if f.alternates[hash] != nil { // nil if tx was broadcast during fetch
f.announced[hash] = f.alternates[hash]
}
delete(f.announced[hash], peer)
if len(f.announced[hash]) == 0 {
delete(f.announced, hash)
}
delete(f.announces[peer], hash)
delete(f.alternates, hash)
delete(f.fetching, hash)
}
if len(f.announces[peer]) == 0 {
delete(f.announces, peer)
}
// Keep track of the request as dangling, but never expire
f.requests[peer].hashes = nil
}
}
// Schedule a new transaction retrieval
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
// No idea if we sheduled something or not, trigger the timer if needed
// TODO(karalabe): this is kind of lame, can't we dump it into scheduleFetches somehow?
f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
case delivery := <-f.cleanup:
// Independent if the delivery was direct or broadcast, remove all
// traces of the hash from internal trackers
for _, hash := range delivery.hashes {
if _, ok := f.waitlist[hash]; ok {
for peer, txset := range f.waitslots {
delete(txset, hash)
if len(txset) == 0 {
delete(f.waitslots, peer)
}
}
delete(f.waitlist, hash)
delete(f.waittime, hash)
} else {
for peer, txset := range f.announces {
delete(txset, hash)
if len(txset) == 0 {
delete(f.announces, peer)
}
}
delete(f.announced, hash)
delete(f.alternates, hash)
// If a transaction currently being fetched from a different
// origin was delivered (delivery stolen), mark it so the
// actual delivery won't double schedule it.
if origin, ok := f.fetching[hash]; ok && (origin != delivery.origin || !delivery.direct) {
stolen := f.requests[origin].stolen
if stolen == nil {
f.requests[origin].stolen = make(map[common.Hash]struct{})
stolen = f.requests[origin].stolen
}
stolen[hash] = struct{}{}
}
delete(f.fetching, hash)
}
}
// In case of a direct delivery, also reschedule anything missing
// from the original query
if delivery.direct {
// Mark the reqesting successful (independent of individual status)
txRequestDoneMeter.Mark(int64(len(delivery.hashes)))
// Make sure something was pending, nuke it
req := f.requests[delivery.origin]
if req == nil {
log.Warn("Unexpected transaction delivery", "peer", delivery.origin)
break
}
delete(f.requests, delivery.origin)
// Anything not delivered should be re-scheduled (with or without
// this peer, depending on the response cutoff)
delivered := make(map[common.Hash]struct{})
for _, hash := range delivery.hashes {
delivered[hash] = struct{}{}
}
cutoff := len(req.hashes) // If nothing is delivered, assume everything is missing, don't retry!!!
for i, hash := range req.hashes {
if _, ok := delivered[hash]; ok {
cutoff = i
}
}
// Reschedule missing hashes from alternates, not-fulfilled from alt+self
for i, hash := range req.hashes {
// Skip rescheduling hashes already delivered by someone else
if req.stolen != nil {
if _, ok := req.stolen[hash]; ok {
continue
}
}
if _, ok := delivered[hash]; !ok {
if i < cutoff {
delete(f.alternates[hash], delivery.origin)
delete(f.announces[delivery.origin], hash)
if len(f.announces[delivery.origin]) == 0 {
delete(f.announces, delivery.origin)
}
}
if len(f.alternates[hash]) > 0 {
if _, ok := f.announced[hash]; ok {
panic(fmt.Sprintf("announced tracker already contains alternate item: %v", f.announced[hash]))
}
f.announced[hash] = f.alternates[hash]
}
}
delete(f.alternates, hash)
delete(f.fetching, hash)
}
// Something was delivered, try to rechedule requests
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) // Partial delivery may enable others to deliver too
}
case drop := <-f.drop:
// A peer was dropped, remove all traces of it
if _, ok := f.waitslots[drop.peer]; ok {
for hash := range f.waitslots[drop.peer] {
delete(f.waitlist[hash], drop.peer)
if len(f.waitlist[hash]) == 0 {
delete(f.waitlist, hash)
delete(f.waittime, hash)
}
}
delete(f.waitslots, drop.peer)
if len(f.waitlist) > 0 {
f.rescheduleWait(waitTimer, waitTrigger)
}
}
// Clean up any active requests
var request *txRequest
if request = f.requests[drop.peer]; request != nil {
for _, hash := range request.hashes {
// Skip rescheduling hashes already delivered by someone else
if request.stolen != nil {
if _, ok := request.stolen[hash]; ok {
continue
}
}
// Undelivered hash, reschedule if there's an alternative origin available
delete(f.alternates[hash], drop.peer)
if len(f.alternates[hash]) == 0 {
delete(f.alternates, hash)
} else {
f.announced[hash] = f.alternates[hash]
delete(f.alternates, hash)
}
delete(f.fetching, hash)
}
delete(f.requests, drop.peer)
}
// Clean up general announcement tracking
if _, ok := f.announces[drop.peer]; ok {
for hash := range f.announces[drop.peer] {
delete(f.announced[hash], drop.peer)
if len(f.announced[hash]) == 0 {
delete(f.announced, hash)
}
}
delete(f.announces, drop.peer)
}
// If a request was cancelled, check if anything needs to be rescheduled
if request != nil {
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
}
case <-f.quit:
return
}
// No idea what happened, but bump some sanity metrics
txFetcherWaitingPeers.Update(int64(len(f.waitslots)))
txFetcherWaitingHashes.Update(int64(len(f.waitlist)))
txFetcherQueueingPeers.Update(int64(len(f.announces) - len(f.requests)))
txFetcherQueueingHashes.Update(int64(len(f.announced)))
txFetcherFetchingPeers.Update(int64(len(f.requests)))
txFetcherFetchingHashes.Update(int64(len(f.fetching)))
// Loop did something, ping the step notifier if needed (tests)
if f.step != nil {
f.step <- struct{}{}
}
}
}
// rescheduleWait iterates over all the transactions currently in the waitlist
// and schedules the movement into the fetcher for the earliest.
//
// The method has a granularity of 'gatherSlack', since there's not much point in
// spinning over all the transactions just to maybe find one that should trigger
// a few ms earlier.
func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
if *timer != nil {
(*timer).Stop()
}
now := f.clock.Now()
earliest := now
for _, instance := range f.waittime {
if earliest > instance {
earliest = instance
if txArriveTimeout-time.Duration(now-earliest) < gatherSlack {
break
}
}
}
*timer = f.clock.AfterFunc(txArriveTimeout-time.Duration(now-earliest), func() {
trigger <- struct{}{}
})
}
// rescheduleTimeout iterates over all the transactions currently in flight and
// schedules a cleanup run when the first would trigger.
//
// The method has a granularity of 'gatherSlack', since there's not much point in
// spinning over all the transactions just to maybe find one that should trigger
// a few ms earlier.
//
// This method is a bit "flaky" "by design". In theory the timeout timer only ever
// should be rescheduled if some request is pending. In practice, a timeout will
// cause the timer to be rescheduled every 5 secs (until the peer comes through or
// disconnects). This is a limitation of the fetcher code because we don't trac
// pending requests and timed out requests separatey. Without double tracking, if
// we simply didn't reschedule the timer on all-timeout then the timer would never
// be set again since len(request) > 0 => something's running.
func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}) {
if *timer != nil {
(*timer).Stop()
}
now := f.clock.Now()
earliest := now
for _, req := range f.requests {
// If this request already timed out, skip it altogether
if req.hashes == nil {
continue
}
if earliest > req.time {
earliest = req.time
if txFetchTimeout-time.Duration(now-earliest) < gatherSlack {
break
}
}
}
*timer = f.clock.AfterFunc(txFetchTimeout-time.Duration(now-earliest), func() {
trigger <- struct{}{}
})
}
// scheduleFetches starts a batch of retrievals for all available idle peers.
func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, whitelist map[string]struct{}) {
// Gather the set of peers we want to retrieve from (default to all)
actives := whitelist
if actives == nil {
actives = make(map[string]struct{})
for peer := range f.announces {
actives[peer] = struct{}{}
}
}
if len(actives) == 0 {
return
}
// For each active peer, try to schedule some transaction fetches
idle := len(f.requests) == 0
f.forEachPeer(actives, func(peer string) {
if f.requests[peer] != nil {
return // continue in the for-each
}
if len(f.announces[peer]) == 0 {
return // continue in the for-each
}
hashes := make([]common.Hash, 0, maxTxRetrievals)
f.forEachHash(f.announces[peer], func(hash common.Hash) bool {
if _, ok := f.fetching[hash]; !ok {
// Mark the hash as fetching and stash away possible alternates
f.fetching[hash] = peer
if _, ok := f.alternates[hash]; ok {
panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash]))
}
f.alternates[hash] = f.announced[hash]
delete(f.announced, hash)
// Accumulate the hash and stop if the limit was reached
hashes = append(hashes, hash)
if len(hashes) >= maxTxRetrievals {
return false // break in the for-each
}
}
return true // continue in the for-each
})
// If any hashes were allocated, request them from the peer
if len(hashes) > 0 {
f.requests[peer] = &txRequest{hashes: hashes, time: f.clock.Now()}
txRequestOutMeter.Mark(int64(len(hashes)))
go func(peer string, hashes []common.Hash) {
// Try to fetch the transactions, but in case of a request
// failure (e.g. peer disconnected), reschedule the hashes.
if err := f.fetchTxs(peer, hashes); err != nil {
txRequestFailMeter.Mark(int64(len(hashes)))
f.Drop(peer)
}
}(peer, hashes)
}
})
// If a new request was fired, schedule a timeout timer
if idle && len(f.requests) > 0 {
f.rescheduleTimeout(timer, timeout)
}
}
// forEachPeer does a range loop over a map of peers in production, but during
// testing it does a deterministic sorted random to allow reproducing issues.
func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) {
// If we're running production, use whatever Go's map gives us
if f.rand == nil {
for peer := range peers {
do(peer)
}
return
}
// We're running the test suite, make iteration deterministic
list := make([]string, 0, len(peers))
for peer := range peers {
list = append(list, peer)
}
sort.Strings(list)
rotateStrings(list, f.rand.Intn(len(list)))
for _, peer := range list {
do(peer)
}
}
// forEachHash does a range loop over a map of hashes in production, but during
// testing it does a deterministic sorted random to allow reproducing issues.
func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash common.Hash) bool) {
// If we're running production, use whatever Go's map gives us
if f.rand == nil {
for hash := range hashes {
if !do(hash) {
return
}
}
return
}
// We're running the test suite, make iteration deterministic
list := make([]common.Hash, 0, len(hashes))
for hash := range hashes {
list = append(list, hash)
}
sortHashes(list)
rotateHashes(list, f.rand.Intn(len(list)))
for _, hash := range list {
if !do(hash) {
return
}
}
}
// rotateStrings rotates the contents of a slice by n steps. This method is only
// used in tests to simulate random map iteration but keep it deterministic.
func rotateStrings(slice []string, n int) {
orig := make([]string, len(slice))
copy(orig, slice)
for i := 0; i < len(orig); i++ {
slice[i] = orig[(i+n)%len(orig)]
}
}
// sortHashes sorts a slice of hashes. This method is only used in tests in order
// to simulate random map iteration but keep it deterministic.
func sortHashes(slice []common.Hash) {
for i := 0; i < len(slice); i++ {
for j := i + 1; j < len(slice); j++ {
if bytes.Compare(slice[i][:], slice[j][:]) > 0 {
slice[i], slice[j] = slice[j], slice[i]
}
}
}
}
// rotateHashes rotates the contents of a slice by n steps. This method is only
// used in tests to simulate random map iteration but keep it deterministic.
func rotateHashes(slice []common.Hash, n int) {
orig := make([]common.Hash, len(slice))
copy(orig, slice)
for i := 0; i < len(orig); i++ {
slice[i] = orig[(i+n)%len(orig)]
}
}

File diff suppressed because it is too large Load diff

View file

@ -20,18 +20,18 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
lru "github.com/hashicorp/golang-lru"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/bft" "github.com/XinFinOrg/XDPoSChain/eth/bft"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
@ -40,10 +40,9 @@ import (
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
lru "github.com/hashicorp/golang-lru"
) )
const ( const (
@ -53,30 +52,26 @@ const (
// txChanSize is the size of channel listening to NewTxsEvent. // txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
// minimim number of peers to broadcast entire blocks and transactions too.
minBroadcastPeers = 4
) )
var ( var (
daoChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the DAO handshake challenge daoChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the DAO handshake challenge
) )
// errIncompatibleConfig is returned if the requested protocols and configs are
// not compatible (low protocol version restrictions and high requirements).
var errIncompatibleConfig = errors.New("incompatible configuration")
func errResp(code errCode, format string, v ...interface{}) error { func errResp(code errCode, format string, v ...interface{}) error {
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
} }
type ProtocolManager struct { type ProtocolManager struct {
networkId uint64 networkId uint64
// networkID uint64
forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node
fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks)
acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
checkpointNumber uint64 // Block number for the sync progress validator to cross reference
checkpointHash common.Hash // Block hash for the sync progress validator to cross reference
txpool txPool txpool txPool
orderpool orderPool orderpool orderPool
lendingpool lendingPool lendingpool lendingPool
@ -84,11 +79,12 @@ type ProtocolManager struct {
chainconfig *params.ChainConfig chainconfig *params.ChainConfig
maxPeers int maxPeers int
downloader *downloader.Downloader downloader *downloader.Downloader
blockFetcher *fetcher.BlockFetcher fetcher *fetcher.Fetcher
txFetcher *fetcher.TxFetcher peers *peerSet
peers *peerSet bft *bft.Bfter
bft *bft.Bfter
SubProtocols []p2p.Protocol
eventMux *event.TypeMux eventMux *event.TypeMux
txsCh chan core.NewTxsEvent txsCh chan core.NewTxsEvent
@ -116,9 +112,6 @@ type ProtocolManager struct {
knownVotes *lru.Cache knownVotes *lru.Cache
knownSyncInfos *lru.Cache knownSyncInfos *lru.Cache
knownTimeouts *lru.Cache knownTimeouts *lru.Cache
// Test fields or hooks
broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation
} }
// NewProtocolManagerEx add order pool to protocol // NewProtocolManagerEx add order pool to protocol
@ -145,13 +138,11 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
// Create the protocol manager with the base fields // Create the protocol manager with the base fields
manager := &ProtocolManager{ manager := &ProtocolManager{
networkId: networkID, networkId: networkID,
forkFilter: forkid.NewFilter(blockchain), eventMux: mux,
eventMux: mux, txpool: txpool,
txpool: txpool, blockchain: blockchain,
blockchain: blockchain, chainconfig: config,
chainconfig: config,
// whitelist: whitelist,
peers: newPeerSet(), peers: newPeerSet(),
newPeerCh: make(chan *peer), newPeerCh: make(chan *peer),
noMorePeers: make(chan struct{}), noMorePeers: make(chan struct{}),
@ -176,53 +167,44 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
if mode == downloader.FastSync { if mode == downloader.FastSync {
manager.fastSync = uint32(1) manager.fastSync = uint32(1)
} }
// // Initiate a sub-protocol for every implemented version we can handle // Initiate a sub-protocol for every implemented version we can handle
// manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions)) manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions))
// for i, version := range ProtocolVersions { for i, version := range ProtocolVersions {
// // Skip protocol version if incompatible with the mode of operation // Skip protocol version if incompatible with the mode of operation
// if mode == downloader.FastSync && version < eth63 { if mode == downloader.FastSync && version < eth63 {
// continue continue
// } }
// // Compatible; initialise the sub-protocol // Compatible; initialise the sub-protocol
// version := version // Closure for the run version := version // Closure for the run
// manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{ manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
// Name: ProtocolName, Name: ProtocolName,
// Version: version, Version: version,
// Length: ProtocolLengths[i], Length: ProtocolLengths[i],
// Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
// peer := manager.newPeer(int(version), p, rw) peer := manager.newPeer(int(version), p, rw)
// select { select {
// case manager.newPeerCh <- peer: case manager.newPeerCh <- peer:
// manager.wg.Add(1) manager.wg.Add(1)
// defer manager.wg.Done() defer manager.wg.Done()
// return manager.handle(peer) return manager.handle(peer)
// case <-manager.quitSync: case <-manager.quitSync:
// return p2p.DiscQuitting return p2p.DiscQuitting
// } }
// }, },
// NodeInfo: func() interface{} { NodeInfo: func() interface{} {
// return manager.NodeInfo() return manager.NodeInfo()
// }, },
// PeerInfo: func(id enode.ID) interface{} { PeerInfo: func(id discover.NodeID) interface{} {
// if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
// return p.Info() return p.Info()
// } }
// return nil return nil
// }, },
// }) })
// } }
// if len(manager.SubProtocols) == 0 { if len(manager.SubProtocols) == 0 {
// return nil, errIncompatibleConfig return nil, errIncompatibleConfig
// } }
// // Construct the downloader (long sync) and its backing state bloom if fast
// // sync is requested. The downloader is responsible for deallocating the state
// // bloom when it's done.
// var stateBloom *trie.SyncBloom
// if atomic.LoadUint32(&manager.fastSync) == 1 {
// stateBloom = trie.NewSyncBloom(uint64(cacheLimit), chaindb)
// }
// manager.downloader = downloader.New(manager.checkpointNumber, chaindb, stateBloom, manager.eventMux, blockchain, nil, manager.removePeer)
var handleProposedBlock func(header *types.Header) error var handleProposedBlock func(header *types.Header) error
if config.XDPoS != nil { if config.XDPoS != nil {
@ -246,32 +228,14 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
return blockchain.CurrentBlock().NumberU64() return blockchain.CurrentBlock().NumberU64()
} }
inserter := func(block types.Block) (error) { inserter := func(block *types.Block) error {
// If sync hasn't reached the checkpoint yet, deny importing weird blocks. // If fast sync is running, deny importing weird blocks
//
// Ideally we would also compare the head block's timestamp and similarly reject
// the propagated block if the head is too old. Unfortunately there is a corner
// case when starting new networks, where the genesis might be ancient (0 unix)
// which would prevent full nodes from accepting it.
if manager.blockchain.CurrentBlock().NumberU64() < manager.checkpointNumber {
log.Warn("Unsynced yet, discarded propagated block", "number", block.Number(), "hash", block.Hash())
return nil
}
// If fast sync is running, deny importing weird blocks. This is a problematic
// clause when starting up a new network, because fast-syncing miners might not
// accept each others' blocks until a restart. Unfortunately we haven't figured
// out a way yet where nodes can decide unilaterally whether the network is new
// or not. This should be fixed if we figure out a solution.
if atomic.LoadUint32(&manager.fastSync) == 1 { if atomic.LoadUint32(&manager.fastSync) == 1 {
log.Warn("Fast syncing, discarded propagated block", "number", block.Number(), "hash", block.Hash()) log.Warn("Discarded bad propagated block", "number", block.Number(), "hash", block.Hash())
return nil return nil
} }
err := manager.blockchain.InsertBlock(&block) atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
// n, err := manager.blockchain.InsertChain(blocks) //TODO: only use InsertChain like go-eth return manager.blockchain.InsertBlock(block)
if err == nil {
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
}
return err
} }
prepare := func(block *types.Block) error { prepare := func(block *types.Block) error {
@ -283,7 +247,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
return manager.blockchain.PrepareBlock(block) return manager.blockchain.PrepareBlock(block)
} }
manager.blockFetcher = fetcher.NewBlockFetcher(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer) manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
//Define bft function //Define bft function
broadcasts := bft.BroadcastFns{ broadcasts := bft.BroadcastFns{
Vote: manager.BroadcastVote, Vote: manager.BroadcastVote,
@ -296,15 +260,6 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
manager.bft.SetConsensusFuns(engine) manager.bft.SetConsensusFuns(engine)
} }
fetchTx := func(peer string, hashes []common.Hash) error {
p := manager.peers.Peer(peer)
if p == nil {
return errors.New("unknown peer")
}
return p.RequestTxs(hashes)
}
manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx)
return manager, nil return manager, nil
} }
@ -314,40 +269,6 @@ func (pm *ProtocolManager) addOrderPoolProtocol(orderpool orderPool) {
func (pm *ProtocolManager) addLendingPoolProtocol(lendingpool lendingPool) { func (pm *ProtocolManager) addLendingPoolProtocol(lendingpool lendingPool) {
pm.lendingpool = lendingpool pm.lendingpool = lendingpool
} }
func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol {
length, ok := protocolLengths[version]
if !ok {
panic("makeProtocol for unknown version")
}
return p2p.Protocol{
Name: protocolName,
Version: version,
Length: length,
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := pm.newPeer(int(version), p, rw, pm.txpool.Get)
select {
case pm.newPeerCh <- peer:
pm.wg.Add(1)
defer pm.wg.Done()
return pm.handle(peer)
case <-pm.quitSync:
return p2p.DiscQuitting
}
},
NodeInfo: func() interface{} {
return pm.NodeInfo()
},
PeerInfo: func(id enode.ID) interface{} {
if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
return p.Info()
}
return nil
},
}
}
func (pm *ProtocolManager) removePeer(id string) { func (pm *ProtocolManager) removePeer(id string) {
// Short circuit if the peer was already removed // Short circuit if the peer was already removed
peer := pm.peers.Peer(id) peer := pm.peers.Peer(id)
@ -358,8 +279,6 @@ func (pm *ProtocolManager) removePeer(id string) {
// Unregister the peer from the downloader and Ethereum peer set // Unregister the peer from the downloader and Ethereum peer set
pm.downloader.UnregisterPeer(id) pm.downloader.UnregisterPeer(id)
pm.txFetcher.Drop(id)
if err := pm.peers.Unregister(id); err != nil { if err := pm.peers.Unregister(id); err != nil {
log.Debug("Peer removal failed", "peer", id, "err", err) log.Debug("Peer removal failed", "peer", id, "err", err)
} }
@ -392,7 +311,7 @@ func (pm *ProtocolManager) Start(maxPeers int) {
// start sync handlers // start sync handlers
go pm.syncer() go pm.syncer()
go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. go pm.txsyncLoop()
} }
func (pm *ProtocolManager) Stop() { func (pm *ProtocolManager) Stop() {
@ -426,8 +345,8 @@ func (pm *ProtocolManager) Stop() {
log.Info("Ethereum protocol stopped") log.Info("Ethereum protocol stopped")
} }
func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
return newPeer(pv, p, rw, getPooledTx) return newPeer(pv, p, newMeteredMsgWriter(rw))
} }
// handle is the callback invoked to manage the life cycle of an eth peer. When // handle is the callback invoked to manage the life cycle of an eth peer. When
@ -447,10 +366,13 @@ func (pm *ProtocolManager) handle(p *peer) error {
number = head.Number.Uint64() number = head.Number.Uint64()
td = pm.blockchain.GetTd(hash, number) td = pm.blockchain.GetTd(hash, number)
) )
if err := p.Handshake(pm.networkId, td, hash, genesis.Hash(), forkid.NewID(pm.blockchain), pm.forkFilter); err != nil { if err := p.Handshake(pm.networkId, td, hash, genesis.Hash()); err != nil {
p.Log().Debug("Ethereum handshake failed", "err", err) p.Log().Debug("Ethereum handshake failed", "err", err)
return err return err
} }
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
rw.Init(p.version)
}
// Register the peer locally // Register the peer locally
err := pm.peers.Register(p) err := pm.peers.Register(p)
if err != nil && err != p2p.ErrAddPairPeer { if err != nil && err != p2p.ErrAddPairPeer {
@ -466,6 +388,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
// Propagate existing transactions. new transactions appearing // Propagate existing transactions. new transactions appearing
// after this will be sent via broadcasts. // after this will be sent via broadcasts.
pm.syncTransactions(p) pm.syncTransactions(p)
// If we're DAO hard-fork aware, validate any remote peer with regard to the hard-fork // If we're DAO hard-fork aware, validate any remote peer with regard to the hard-fork
if daoBlock := pm.chainconfig.DAOForkBlock; daoBlock != nil { if daoBlock := pm.chainconfig.DAOForkBlock; daoBlock != nil {
// Request the peer's DAO fork header for extra-data validation // Request the peer's DAO fork header for extra-data validation
@ -503,8 +426,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if err != nil { if err != nil {
return err return err
} }
if msg.Size > protocolMaxMsgSize { if msg.Size > ProtocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
} }
defer msg.Discard() defer msg.Discard()
@ -637,7 +560,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return nil return nil
} }
// Irrelevant of the fork checks, send the header to the fetcher just in case // Irrelevant of the fork checks, send the header to the fetcher just in case
headers = pm.blockFetcher.FilterHeaders(p.id, headers, time.Now()) headers = pm.fetcher.FilterHeaders(p.id, headers, time.Now())
} }
if len(headers) > 0 || !filter { if len(headers) > 0 || !filter {
err := pm.downloader.DeliverHeaders(p.id, headers) err := pm.downloader.DeliverHeaders(p.id, headers)
@ -680,26 +603,26 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)
} }
// Deliver them all to the downloader for queuing // Deliver them all to the downloader for queuing
transactions := make([][]*types.Transaction, len(request)) trasactions := make([][]*types.Transaction, len(request))
uncles := make([][]*types.Header, len(request)) uncles := make([][]*types.Header, len(request))
for i, body := range request { for i, body := range request {
transactions[i] = body.Transactions trasactions[i] = body.Transactions
uncles[i] = body.Uncles uncles[i] = body.Uncles
} }
// Filter out any explicitly requested bodies, deliver the rest to the downloader // Filter out any explicitly requested bodies, deliver the rest to the downloader
filter := len(transactions) > 0 || len(uncles) > 0 filter := len(trasactions) > 0 || len(uncles) > 0
if filter { if filter {
transactions, uncles = pm.blockFetcher.FilterBodies(p.id, transactions, uncles, time.Now()) trasactions, uncles = pm.fetcher.FilterBodies(p.id, trasactions, uncles, time.Now())
} }
if len(transactions) > 0 || len(uncles) > 0 || !filter { if len(trasactions) > 0 || len(uncles) > 0 || !filter {
err := pm.downloader.DeliverBodies(p.id, transactions, uncles) err := pm.downloader.DeliverBodies(p.id, trasactions, uncles)
if err != nil { if err != nil {
log.Debug("Failed to deliver bodies", "err", err) log.Debug("Failed to deliver bodies", "err", err)
} }
} }
case isEth63OrHigher(p.version) && msg.Code == GetNodeDataMsg: case p.version >= eth63 && msg.Code == GetNodeDataMsg:
// Decode the retrieval message // Decode the retrieval message
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
if _, err := msgStream.List(); err != nil { if _, err := msgStream.List(); err != nil {
@ -726,7 +649,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
return p.SendNodeData(data) return p.SendNodeData(data)
case isEth63OrHigher(p.version) && msg.Code == NodeDataMsg: case p.version >= eth63 && msg.Code == NodeDataMsg:
// A batch of node state data arrived to one of our previous requests // A batch of node state data arrived to one of our previous requests
var data [][]byte var data [][]byte
if err := msg.Decode(&data); err != nil { if err := msg.Decode(&data); err != nil {
@ -737,7 +660,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
log.Debug("Failed to deliver node state data", "err", err) log.Debug("Failed to deliver node state data", "err", err)
} }
case isEth63OrHigher(p.version) && msg.Code == GetReceiptsMsg: case p.version >= eth63 && msg.Code == GetReceiptsMsg:
// Decode the retrieval message // Decode the retrieval message
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
if _, err := msgStream.List(); err != nil { if _, err := msgStream.List(); err != nil {
@ -773,7 +696,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
return p.SendReceiptsRLP(receipts) return p.SendReceiptsRLP(receipts)
case isEth63OrHigher(p.version) && msg.Code == ReceiptsMsg: case p.version >= eth63 && msg.Code == ReceiptsMsg:
// A batch of receipts arrived to one of our previous requests // A batch of receipts arrived to one of our previous requests
var receipts [][]*types.Receipt var receipts [][]*types.Receipt
if err := msg.Decode(&receipts); err != nil { if err := msg.Decode(&receipts); err != nil {
@ -801,7 +724,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
} }
for _, block := range unknown { for _, block := range unknown {
pm.blockFetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies) pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
} }
case msg.Code == NewBlockMsg: case msg.Code == NewBlockMsg:
@ -810,23 +733,12 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if err := msg.Decode(&request); err != nil { if err := msg.Decode(&request); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err) return errResp(ErrDecode, "%v: %v", msg, err)
} }
if hash := types.CalcUncleHash(request.Block.Uncles()); hash != request.Block.UncleHash() {
log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash())
break // TODO(karalabe): return error eventually, but wait a few releases
}
if hash := types.DeriveSha(request.Block.Transactions()); hash != request.Block.TxHash() {
log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash())
break // TODO(karalabe): return error eventually, but wait a few releases
}
if err := request.sanityCheck(); err != nil {
return err
}
request.Block.ReceivedAt = msg.ReceivedAt request.Block.ReceivedAt = msg.ReceivedAt
request.Block.ReceivedFrom = p request.Block.ReceivedFrom = p
// Mark the peer as owning the block and schedule it for import // Mark the peer as owning the block and schedule it for import
p.MarkBlock(request.Block.Hash()) p.MarkBlock(request.Block.Hash())
pm.blockFetcher.Enqueue(p.id, request.Block) pm.fetcher.Enqueue(p.id, request.Block)
// Assuming the block is importable by the peer, but possibly not yet done so, // Assuming the block is importable by the peer, but possibly not yet done so,
// calculate the head hash and TD that the peer truly must have. // calculate the head hash and TD that the peer truly must have.
@ -847,59 +759,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
} }
case msg.Code == NewPooledTransactionHashesMsg && isEth65OrHigher(p.version): case msg.Code == TxMsg:
// New transaction announcement arrived, make sure we have
// a valid and fresh chain to handle them
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break
}
var hashes []common.Hash
if err := msg.Decode(&hashes); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Schedule all the unknown hashes for retrieval
for _, hash := range hashes {
p.MarkTransaction(hash)
}
pm.txFetcher.Notify(p.id, hashes)
case msg.Code == GetPooledTransactionsMsg && isEth65OrHigher(p.version):
// Decode the retrieval message
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
if _, err := msgStream.List(); err != nil {
return err
}
// Gather transactions until the fetch or network limits is reached
var (
hash common.Hash
bytes int
hashes []common.Hash
txs []rlp.RawValue
)
for bytes < softResponseLimit {
// Retrieve the hash of the next block
if err := msgStream.Decode(&hash); err == rlp.EOL {
break
} else if err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Retrieve the requested transaction, skipping if unknown to us
tx := pm.txpool.Get(hash)
if tx == nil {
continue
}
// If known, encode and queue for response packet
if encoded, err := rlp.EncodeToBytes(tx); err != nil {
log.Error("Failed to encode transaction", "err", err)
} else {
hashes = append(hashes, hash)
txs = append(txs, encoded)
bytes += len(encoded)
}
}
return p.SendPooledTransactionsRLP(hashes, txs)
case msg.Code == TransactionMsg || (msg.Code == PooledTransactionsMsg && isEth65OrHigher(p.version)):
// Transactions arrived, make sure we have a valid and fresh chain to handle them // Transactions arrived, make sure we have a valid and fresh chain to handle them
if atomic.LoadUint32(&pm.acceptTxs) == 0 { if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break break
@ -925,7 +785,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
} }
pm.txFetcher.Enqueue(p.id, txs, msg.Code == PooledTransactionsMsg) pm.txpool.AddRemotes(txs)
case msg.Code == OrderTxMsg: case msg.Code == OrderTxMsg:
// Transactions arrived, make sure we have a valid and fresh chain to handle them // Transactions arrived, make sure we have a valid and fresh chain to handle them
@ -1066,73 +926,37 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
return return
} }
// Send the block to a subset of our peers // Send the block to a subset of our peers
transferLen := int(math.Sqrt(float64(len(peers)))) for _, peer := range peers {
if transferLen < minBroadcastPeers { peer.SendNewBlock(block, td)
transferLen = minBroadcastPeers
} }
if transferLen > len(peers) { log.Trace("Propagated block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
transferLen = len(peers)
}
transfer := peers[:transferLen]
for _, peer := range transfer {
peer.AsyncSendNewBlock(block, td)
}
log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
return return
} }
// Otherwise if the block is indeed in out own chain, announce it // Otherwise if the block is indeed in out own chain, announce it
if pm.blockchain.HasBlock(hash, block.NumberU64()) { if pm.blockchain.HasBlock(hash, block.NumberU64()) {
for _, peer := range peers { for _, peer := range peers {
peer.AsyncSendNewBlockHash(block) peer.SendNewBlockHashes([]common.Hash{hash}, []uint64{block.NumberU64()})
} }
log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
} }
} }
// BroadcastTransactions will propagate a batch of transactions to all peers which are not known to // BroadcastTxs will propagate a batch of transactions to all peers which are not known to
// already have the given transaction. // already have the given transaction.
func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propagate bool) { func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
var ( var txset = make(map[*peer]types.Transactions)
txset = make(map[*peer][]common.Hash)
annos = make(map[*peer][]common.Hash)
)
// Broadcast transactions to a batch of peers not knowing about it
if propagate {
for _, tx := range txs {
peers := pm.peers.PeersWithoutTx(tx.Hash())
// Send the block to a subset of our peers // Broadcast transactions to a batch of peers not knowing about it
transferLen := int(math.Sqrt(float64(len(peers))))
if transferLen < minBroadcastPeers {
transferLen = minBroadcastPeers
}
if transferLen > len(peers) {
transferLen = len(peers)
}
transfer := peers[:transferLen]
for _, peer := range transfer {
txset[peer] = append(txset[peer], tx.Hash())
}
log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
}
for peer, hashes := range txset {
peer.AsyncSendTransactions(hashes)
}
return
}
// Otherwise only broadcast the announcement to peers
for _, tx := range txs { for _, tx := range txs {
peers := pm.peers.PeersWithoutTx(tx.Hash()) peers := pm.peers.PeersWithoutTx(tx.Hash())
for _, peer := range peers { for _, peer := range peers {
annos[peer] = append(annos[peer], tx.Hash()) txset[peer] = append(txset[peer], tx)
} }
log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
} }
for peer, hashes := range annos { // FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
if peer.version >= eth65 { //implement for peer, txs := range txset {
peer.AsyncSendPooledTransactionHashes(hashes) peer.SendTransactions(txs)
} else {
peer.AsyncSendTransactions(hashes)
}
} }
} }
@ -1228,13 +1052,7 @@ func (pm *ProtocolManager) txBroadcastLoop() {
for { for {
select { select {
case event := <-pm.txsCh: case event := <-pm.txsCh:
// For testing purpose only, disable propagation pm.BroadcastTxs(event.Txs)
if pm.broadcastTxAnnouncesOnly {
pm.BroadcastTransactions(event.Txs, false)
continue
}
pm.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers
pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest
// Err() channel will be closed when unsubscribing. // Err() channel will be closed when unsubscribing.
case <-pm.txsSub.Err(): case <-pm.txsSub.Err():

View file

@ -17,7 +17,6 @@
package eth package eth
import ( import (
"fmt"
"math" "math"
"math/big" "math/big"
"math/rand" "math/rand"
@ -39,11 +38,38 @@ import (
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
// Tests that protocol versions and modes of operations are matched up properly.
func TestProtocolCompatibility(t *testing.T) {
// Define the compatibility chart
tests := []struct {
version uint
mode downloader.SyncMode
compatible bool
}{
{61, downloader.FullSync, true}, {62, downloader.FullSync, true}, {63, downloader.FullSync, true},
{61, downloader.FastSync, false}, {62, downloader.FastSync, false}, {63, downloader.FastSync, true},
}
// Make sure anything we screw up is restored
backup := ProtocolVersions
defer func() { ProtocolVersions = backup }()
// Try all available compatibility configs and check for errors
for i, tt := range tests {
ProtocolVersions = []uint{tt.version}
pm, _, err := newTestProtocolManager(tt.mode, 0, nil, nil)
if pm != nil {
defer pm.Stop()
}
if (err == nil && !tt.compatible) || (err != nil && tt.compatible) {
t.Errorf("test %d: compatibility mismatch: have error %v, want compatibility %v", i, err, tt.compatible)
}
}
}
// Tests that block headers can be retrieved from a remote chain based on user queries. // Tests that block headers can be retrieved from a remote chain based on user queries.
func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) } func TestGetBlockHeaders62(t *testing.T) { testGetBlockHeaders(t, 62) }
func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) }
func TestGetBlockHeaders100(t *testing.T) { testGetBlockHeaders(t, 100) }
func TestGetBlockHeaders101(t *testing.T) { testGetBlockHeaders(t, 101) }
func testGetBlockHeaders(t *testing.T, protocol int) { func testGetBlockHeaders(t *testing.T, protocol int) {
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil) pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil)
@ -201,10 +227,8 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
} }
// Tests that block contents can be retrieved from a remote chain based on their hashes. // Tests that block contents can be retrieved from a remote chain based on their hashes.
func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) } func TestGetBlockBodies62(t *testing.T) { testGetBlockBodies(t, 62) }
func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) }
func TestGetBlockBodies100(t *testing.T) { testGetBlockBodies(t, 100) }
func TestGetBlockBodies101(t *testing.T) { testGetBlockBodies(t, 101) }
func testGetBlockBodies(t *testing.T, protocol int) { func testGetBlockBodies(t *testing.T, protocol int) {
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil) pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil)
@ -275,10 +299,7 @@ func testGetBlockBodies(t *testing.T, protocol int) {
} }
// Tests that the node state database can be retrieved based on hashes. // Tests that the node state database can be retrieved based on hashes.
func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) } func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) }
func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) }
func TestGetNodeData100(t *testing.T) { testGetNodeData(t, 100) }
func TestGetNodeData101(t *testing.T) { testGetNodeData(t, 101) }
func testGetNodeData(t *testing.T, protocol int) { func testGetNodeData(t *testing.T, protocol int) {
// Define three accounts to simulate transactions with // Define three accounts to simulate transactions with
@ -372,10 +393,7 @@ func testGetNodeData(t *testing.T, protocol int) {
} }
// Tests that the transaction receipts can be retrieved based on hashes. // Tests that the transaction receipts can be retrieved based on hashes.
func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) } func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) }
func TestGetReceipt64(t *testing.T) { testGetReceipt(t, 64) }
func TestGetReceipt100(t *testing.T) { testGetReceipt(t, 100) }
func TestGetReceipt101(t *testing.T) { testGetReceipt(t, 101) }
func testGetReceipt(t *testing.T, protocol int) { func testGetReceipt(t *testing.T, protocol int) {
// Define three accounts to simulate transactions with // Define three accounts to simulate transactions with
@ -433,245 +451,75 @@ func testGetReceipt(t *testing.T, protocol int) {
} }
} }
// // Tests that post eth protocol handshake, DAO fork-enabled clients also execute // Tests that post eth protocol handshake, DAO fork-enabled clients also execute
// // a DAO "challenge" verifying each others' DAO fork headers to ensure they're on // a DAO "challenge" verifying each others' DAO fork headers to ensure they're on
// // compatible chains. // compatible chains.
// func TestDAOChallengeNoVsNo(t *testing.T) { testDAOChallenge(t, false, false, false) } func TestDAOChallengeNoVsNo(t *testing.T) { testDAOChallenge(t, false, false, false) }
// func TestDAOChallengeNoVsPro(t *testing.T) { testDAOChallenge(t, false, true, false) } func TestDAOChallengeNoVsPro(t *testing.T) { testDAOChallenge(t, false, true, false) }
// func TestDAOChallengeProVsNo(t *testing.T) { testDAOChallenge(t, true, false, false) } func TestDAOChallengeProVsNo(t *testing.T) { testDAOChallenge(t, true, false, false) }
// func TestDAOChallengeProVsPro(t *testing.T) { testDAOChallenge(t, true, true, false) } func TestDAOChallengeProVsPro(t *testing.T) { testDAOChallenge(t, true, true, false) }
// func TestDAOChallengeNoVsTimeout(t *testing.T) { testDAOChallenge(t, false, false, true) } func TestDAOChallengeNoVsTimeout(t *testing.T) { testDAOChallenge(t, false, false, true) }
// func TestDAOChallengeProVsTimeout(t *testing.T) { testDAOChallenge(t, true, true, true) } func TestDAOChallengeProVsTimeout(t *testing.T) { testDAOChallenge(t, true, true, true) }
// func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool) { func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool) {
// // Reduce the DAO handshake challenge timeout // Reduce the DAO handshake challenge timeout
// if timeout { if timeout {
// defer func(old time.Duration) { daoChallengeTimeout = old }(daoChallengeTimeout) defer func(old time.Duration) { daoChallengeTimeout = old }(daoChallengeTimeout)
// daoChallengeTimeout = 500 * time.Millisecond daoChallengeTimeout = 500 * time.Millisecond
// }
// // Create a DAO aware protocol manager
// var (
// evmux = new(event.TypeMux)
// pow = ethash.NewFaker()
// db = rawdb.NewMemoryDatabase()
// config = &params.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked}
// gspec = &core.Genesis{Config: config}
// genesis = gspec.MustCommit(db)
// blockchain, _ = core.NewBlockChain(db, nil, config, pow, vm.Config{})
// )
// (&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block
// // If checkpointing is enabled, create and inject a fake CHT and the corresponding
// // chllenge response.
// var response *types.Header
// var cht *params.TrustedCheckpoint
// if checkpoint {
// index := uint64(rand.Intn(500))
// number := (index+1)*params.CHTFrequency - 1
// response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
// cht = &params.TrustedCheckpoint{
// SectionIndex: index,
// SectionHead: response.Hash(),
// }
// }
// // Create a checkpoint aware protocol manager
// blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil)
// if err != nil {
// t.Fatalf("failed to create new blockchain: %v", err)
// }
// // pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db)
// pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil)
// if err != nil {
// t.Fatalf("failed to start test protocol manager: %v", err)
// }
// pm.Start(1000)
// defer pm.Stop()
// // Connect a new peer and check that we receive the DAO challenge
// peer, _ := newTestPeer("peer", eth63, pm, true)
// defer peer.close()
// challenge := &getBlockHeadersData{
// Origin: hashOrNumber{Number: config.DAOForkBlock.Uint64()},
// Amount: 1,
// Skip: 0,
// Reverse: false,
// }
// if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil {
// t.Fatalf("challenge mismatch: %v", err)
// }
// // Create a block to reply to the challenge if no timeout is simulated
// if !timeout {
// blocks, _ := core.GenerateChain(&params.ChainConfig{}, genesis, ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
// if remoteForked {
// block.SetExtra(params.DAOForkBlockExtra)
// }
// })
// if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{blocks[0].Header()}); err != nil {
// t.Fatalf("failed to answer challenge: %v", err)
// }
// time.Sleep(100 * time.Millisecond) // Sleep to avoid the verification racing with the drops
// } else {
// // Otherwise wait until the test timeout passes
// time.Sleep(daoChallengeTimeout + 500*time.Millisecond)
// }
// // Verify that depending on fork side, the remote peer is maintained or dropped
// if localForked == remoteForked && !timeout {
// if peers := pm.peers.Len(); peers != 1 {
// t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
// }
// } else {
// if peers := pm.peers.Len(); peers != 0 {
// t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
// }
// }
// }
func TestBroadcastBlock(t *testing.T) {
var tests = []struct {
totalPeers int
broadcastExpected int
}{
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 4},
{9, 4},
{12, 4},
{16, 4},
{26, 5},
{100, 10},
} }
for _, test := range tests { // Create a DAO aware protocol manager
testBroadcastBlock(t, test.totalPeers, test.broadcastExpected)
}
}
func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
var ( var (
evmux = new(event.TypeMux) evmux = new(event.TypeMux)
pow = ethash.NewFaker() pow = ethash.NewFaker()
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
config = &params.ChainConfig{} config = &params.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked}
gspec = &core.Genesis{Config: config} gspec = &core.Genesis{Config: config}
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = core.NewBlockChain(db, nil, config, pow, vm.Config{})
) )
blockchain, err := core.NewBlockChain(db, nil, config, pow, vm.Config{}) pm, err := NewProtocolManager(config, downloader.FullSync, ethconfig.Defaults.NetworkId, evmux, new(testTxPool), pow, blockchain, db)
if err != nil {
t.Fatalf("failed to create new blockchain: %v", err)
}
pm, err := NewProtocolManager(config, downloader.FullSync, ethconfig.Defaults.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db)
if err != nil { if err != nil {
t.Fatalf("failed to start test protocol manager: %v", err) t.Fatalf("failed to start test protocol manager: %v", err)
} }
pm.Start(1000) pm.Start(1000)
defer pm.Stop() defer pm.Stop()
var peers []*testPeer
for i := 0; i < totalPeers; i++ {
peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true)
defer peer.close()
peers = append(peers, peer)
}
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
pm.BroadcastBlock(chain[0], true /*propagate*/)
errCh := make(chan error, totalPeers) // Connect a new peer and check that we receive the DAO challenge
doneCh := make(chan struct{}, totalPeers) peer, _ := newTestPeer("peer", eth63, pm, true)
for _, peer := range peers { defer peer.close()
go func(p *testPeer) {
if err := p2p.ExpectMsg(p.app, NewBlockMsg, &newBlockData{Block: chain[0], TD: big.NewInt(131136)}); err != nil { challenge := &getBlockHeadersData{
errCh <- err Origin: hashOrNumber{Number: config.DAOForkBlock.Uint64()},
} else { Amount: 1,
doneCh <- struct{}{} Skip: 0,
Reverse: false,
}
if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil {
t.Fatalf("challenge mismatch: %v", err)
}
// Create a block to reply to the challenge if no timeout is simulated
if !timeout {
blocks, _ := core.GenerateChain(&params.ChainConfig{}, genesis, ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
if remoteForked {
block.SetExtra(params.DAOForkBlockExtra)
} }
}(peer) })
} if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{blocks[0].Header()}); err != nil {
timeout := time.After(2 * time.Second) t.Fatalf("failed to answer challenge: %v", err)
var receivedCount int
outer:
for {
select {
case err = <-errCh:
break outer
case <-doneCh:
receivedCount++
if receivedCount == totalPeers {
break outer
}
case <-timeout:
break outer
} }
time.Sleep(100 * time.Millisecond) // Sleep to avoid the verification racing with the drops
} else {
// Otherwise wait until the test timeout passes
time.Sleep(daoChallengeTimeout + 500*time.Millisecond)
} }
for _, peer := range peers { // Verify that depending on fork side, the remote peer is maintained or dropped
peer.app.Close() if localForked == remoteForked && !timeout {
} if peers := pm.peers.Len(); peers != 1 {
if err != nil { t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
t.Errorf("error matching block by peer: %v", err)
}
if receivedCount != broadcastExpected {
t.Errorf("block broadcast to %d peers, expected %d", receivedCount, broadcastExpected)
}
}
// Tests that a propagated malformed block (uncles or transactions don't match
// with the hashes in the header) gets discarded and not broadcast forward.
func TestBroadcastMalformedBlock(t *testing.T) {
// Create a live node to test propagation with
var (
engine = ethash.NewFaker()
db = rawdb.NewMemoryDatabase()
config = &params.ChainConfig{}
gspec = &core.Genesis{Config: config}
genesis = gspec.MustCommit(db)
)
blockchain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{})
if err != nil {
t.Fatalf("failed to create new blockchain: %v", err)
}
pm, err := NewProtocolManager(config, downloader.FullSync, ethconfig.Defaults.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db)
if err != nil {
t.Fatalf("failed to start test protocol manager: %v", err)
}
pm.Start(2)
defer pm.Stop()
// Create two peers, one to send the malformed block with and one to check
// propagation
source, _ := newTestPeer("source", eth63, pm, true)
defer source.close()
sink, _ := newTestPeer("sink", eth63, pm, true)
defer sink.close()
// Create various combinations of malformed blocks
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
malformedUncles := chain[0].Header()
malformedUncles.UncleHash[0]++
malformedTransactions := chain[0].Header()
malformedTransactions.TxHash[0]++
malformedEverything := chain[0].Header()
malformedEverything.UncleHash[0]++
malformedEverything.TxHash[0]++
// Keep listening to broadcasts and notify if any arrives
notify := make(chan struct{})
go func() {
if _, err := sink.app.ReadMsg(); err == nil {
notify <- struct{}{}
} }
}() } else {
// Try to broadcast all malformations and ensure they all get discarded if peers := pm.peers.Len(); peers != 0 {
for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} { t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
block := types.NewBlockWithHeader(header).WithBody(chain[0].Transactions(), chain[0].Uncles())
if err := p2p.Send(source.app, NewBlockMsg, []interface{}{block, big.NewInt(131136)}); err != nil {
t.Fatalf("failed to broadcast block: %v", err)
}
select {
case <-notify:
t.Fatalf("malformed block forwarded")
case <-time.After(100 * time.Millisecond):
} }
} }
} }

View file

@ -22,7 +22,6 @@ package eth
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand" "crypto/rand"
"fmt"
"math/big" "math/big"
"sort" "sort"
"sync" "sync"
@ -31,7 +30,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
@ -41,7 +39,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -70,8 +68,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
panic(err) panic(err)
} }
// pm, err := NewProtocolManager(gspec.Config, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db) pm, err := NewProtocolManager(gspec.Config, mode, ethconfig.Defaults.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db)
pm, err := NewProtocolManager(gspec.Config, mode, ethconfig.Defaults.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -94,43 +91,22 @@ func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks i
// testTxPool is a fake, helper transaction pool for testing purposes // testTxPool is a fake, helper transaction pool for testing purposes
type testTxPool struct { type testTxPool struct {
txFeed event.Feed txFeed event.Feed
pool map[common.Hash]*types.Transaction // Hash map of collected transactions pool []*types.Transaction // Collection of all transactions
added chan<- []*types.Transaction // Notification channel for new transactions added chan<- []*types.Transaction // Notification channel for new transactions
lock sync.RWMutex // Protects the transaction pool lock sync.RWMutex // Protects the transaction pool
} }
// Has returns an indicator whether txpool has a transaction
// cached with the given hash.
func (p *testTxPool) Has(hash common.Hash) bool {
p.lock.Lock()
defer p.lock.Unlock()
return p.pool[hash] != nil
}
// Get retrieves the transaction from local txpool with given
// tx hash.
func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
p.lock.Lock()
defer p.lock.Unlock()
return p.pool[hash]
}
// AddRemotes appends a batch of transactions to the pool, and notifies any // AddRemotes appends a batch of transactions to the pool, and notifies any
// listeners if the addition channel is non nil // listeners if the addition channel is non nil
func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
for _, tx := range txs { p.pool = append(p.pool, txs...)
p.pool[tx.Hash()] = tx
}
if p.added != nil { if p.added != nil {
p.added <- txs p.added <- txs
} }
p.txFeed.Send(core.NewTxsEvent{Txs: txs})
return make([]error, len(txs)) return make([]error, len(txs))
} }
@ -174,10 +150,10 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te
app, net := p2p.MsgPipe() app, net := p2p.MsgPipe()
// Generate a random id and create the peer // Generate a random id and create the peer
var id enode.ID var id discover.NodeID
rand.Read(id[:]) rand.Read(id[:])
peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get) peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net)
// Start the peer on a new thread // Start the peer on a new thread
errc := make(chan error, 1) errc := make(chan error, 1)
@ -197,38 +173,22 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te
head = pm.blockchain.CurrentHeader() head = pm.blockchain.CurrentHeader()
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
) )
tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkid.NewID(pm.blockchain), forkid.NewFilter(pm.blockchain)) tp.handshake(nil, td, head.Hash(), genesis.Hash())
} }
return tp, errc return tp, errc
} }
// handshake simulates a trivial handshake that expects the same state from the // handshake simulates a trivial handshake that expects the same state from the
// remote side as we are simulating locally. // remote side as we are simulating locally.
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) { func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) {
var msg interface{} msg := &statusData{
switch { ProtocolVersion: uint32(p.version),
case isEth63(p.version): NetworkId: ethconfig.Defaults.NetworkId,
msg = &statusData63{ TD: td,
ProtocolVersion: uint32(p.version), CurrentBlock: head,
NetworkId: ethconfig.Defaults.NetworkId, GenesisBlock: genesis,
TD: td,
CurrentBlock: head,
GenesisBlock: genesis,
}
case isEth64OrHigher(p.version):
msg = &statusData{
ProtocolVersion: uint32(p.version),
NetworkID: ethconfig.Defaults.NetworkId,
TD: td,
Head: head,
Genesis: genesis,
ForkID: forkID,
}
default:
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
} }
if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil { if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil {
fmt.Println("p2p expect msg err", err)
t.Fatalf("status recv: %v", err) t.Fatalf("status recv: %v", err)
} }
if err := p2p.Send(p.app, StatusMsg, msg); err != nil { if err := p2p.Send(p.app, StatusMsg, msg); err != nil {

139
eth/metrics.go Normal file
View file

@ -0,0 +1,139 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"github.com/XinFinOrg/XDPoSChain/metrics"
"github.com/XinFinOrg/XDPoSChain/p2p"
)
var (
propTxnInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/packets", nil)
propTxnInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/traffic", nil)
propTxnOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/packets", nil)
propTxnOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/traffic", nil)
propHashInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/packets", nil)
propHashInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/traffic", nil)
propHashOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/packets", nil)
propHashOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/traffic", nil)
propBlockInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/packets", nil)
propBlockInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/traffic", nil)
propBlockOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/packets", nil)
propBlockOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/traffic", nil)
reqHeaderInPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/in/packets", nil)
reqHeaderInTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/in/traffic", nil)
reqHeaderOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/out/packets", nil)
reqHeaderOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/out/traffic", nil)
reqBodyInPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/packets", nil)
reqBodyInTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/traffic", nil)
reqBodyOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/packets", nil)
reqBodyOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/traffic", nil)
reqStateInPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/in/packets", nil)
reqStateInTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/in/traffic", nil)
reqStateOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/out/packets", nil)
reqStateOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/out/traffic", nil)
reqReceiptInPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/packets", nil)
reqReceiptInTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/traffic", nil)
reqReceiptOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/packets", nil)
reqReceiptOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/traffic", nil)
miscInPacketsMeter = metrics.NewRegisteredMeter("eth/misc/in/packets", nil)
miscInTrafficMeter = metrics.NewRegisteredMeter("eth/misc/in/traffic", nil)
miscOutPacketsMeter = metrics.NewRegisteredMeter("eth/misc/out/packets", nil)
miscOutTrafficMeter = metrics.NewRegisteredMeter("eth/misc/out/traffic", nil)
)
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
// accumulating the above defined metrics based on the data stream contents.
type meteredMsgReadWriter struct {
p2p.MsgReadWriter // Wrapped message stream to meter
version int // Protocol version to select correct meters
}
// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the
// metrics system is disabled, this function returns the original object.
func newMeteredMsgWriter(rw p2p.MsgReadWriter) p2p.MsgReadWriter {
if !metrics.Enabled {
return rw
}
return &meteredMsgReadWriter{MsgReadWriter: rw}
}
// Init sets the protocol version used by the stream to know which meters to
// increment in case of overlapping message ids between protocol versions.
func (rw *meteredMsgReadWriter) Init(version int) {
rw.version = version
}
func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
// Read the message and short circuit in case of an error
msg, err := rw.MsgReadWriter.ReadMsg()
if err != nil {
return msg, err
}
// Account for the data traffic
packets, traffic := miscInPacketsMeter, miscInTrafficMeter
switch {
case msg.Code == BlockHeadersMsg:
packets, traffic = reqHeaderInPacketsMeter, reqHeaderInTrafficMeter
case msg.Code == BlockBodiesMsg:
packets, traffic = reqBodyInPacketsMeter, reqBodyInTrafficMeter
case rw.version >= eth63 && msg.Code == NodeDataMsg:
packets, traffic = reqStateInPacketsMeter, reqStateInTrafficMeter
case rw.version >= eth63 && msg.Code == ReceiptsMsg:
packets, traffic = reqReceiptInPacketsMeter, reqReceiptInTrafficMeter
case msg.Code == NewBlockHashesMsg:
packets, traffic = propHashInPacketsMeter, propHashInTrafficMeter
case msg.Code == NewBlockMsg:
packets, traffic = propBlockInPacketsMeter, propBlockInTrafficMeter
case msg.Code == TxMsg:
packets, traffic = propTxnInPacketsMeter, propTxnInTrafficMeter
}
packets.Mark(1)
traffic.Mark(int64(msg.Size))
return msg, err
}
func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
// Account for the data traffic
packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
switch {
case msg.Code == BlockHeadersMsg:
packets, traffic = reqHeaderOutPacketsMeter, reqHeaderOutTrafficMeter
case msg.Code == BlockBodiesMsg:
packets, traffic = reqBodyOutPacketsMeter, reqBodyOutTrafficMeter
case rw.version >= eth63 && msg.Code == NodeDataMsg:
packets, traffic = reqStateOutPacketsMeter, reqStateOutTrafficMeter
case rw.version >= eth63 && msg.Code == ReceiptsMsg:
packets, traffic = reqReceiptOutPacketsMeter, reqReceiptOutTrafficMeter
case msg.Code == NewBlockHashesMsg:
packets, traffic = propHashOutPacketsMeter, propHashOutTrafficMeter
case msg.Code == NewBlockMsg:
packets, traffic = propBlockOutPacketsMeter, propBlockOutTrafficMeter
case msg.Code == TxMsg:
packets, traffic = propTxnOutPacketsMeter, propTxnOutTrafficMeter
}
packets.Mark(1)
traffic.Mark(int64(msg.Size))
// Send the packet to the p2p layer
return rw.MsgReadWriter.WriteMsg(msg)
}

View file

@ -24,7 +24,6 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -45,38 +44,9 @@ const (
maxKnownVote = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) maxKnownVote = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownTimeout = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) maxKnownTimeout = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownSyncInfo = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) maxKnownSyncInfo = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
// maxQueuedTxs is the maximum number of transactions to queue up before dropping handshakeTimeout = 5 * time.Second
// older broadcasts.
maxQueuedTxs = 4096
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
// before dropping older announcements.
maxQueuedTxAnns = 4096
// maxQueuedBlocks is the maximum number of block propagations to queue up before
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
// that might cover uncles should be enough.
maxQueuedBlocks = 4
// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
// dropping broadcasts. Similarly to block propagations, there's no point to queue
// above some healthy uncle limit, so use that.
maxQueuedBlockAnns = 4
handshakeTimeout = 5 * time.Second
) )
// max is a helper function which returns the larger of the two given integers.
func max(a, b int) int {
if a > b {
return a
}
return b
}
// propEvent is a block propagation, waiting for its turn in the broadcast queue.
type propEvent struct {
block *types.Block
td *big.Int
}
// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known // PeerInfo represents a short summary of the Ethereum sub-protocol metadata known
// about a connected peer. // about a connected peer.
type PeerInfo struct { type PeerInfo struct {
@ -99,199 +69,36 @@ type peer struct {
td *big.Int td *big.Int
lock sync.RWMutex lock sync.RWMutex
knownBlocks mapset.Set // Set of block hashes known to be known by this peer knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer knownBlocks mapset.Set // Set of block hashes known to be known by this peer
knownOrderTxs mapset.Set // Set of order transaction hashes known to be known by this peer knownOrderTxs mapset.Set // Set of order transaction hashes known to be known by this peer
knownLendingTxs mapset.Set // Set of lending transaction hashes known to be known by this peer knownLendingTxs mapset.Set // Set of lending transaction hashes known to be known by this peer
knownVote mapset.Set // Set of BFT Vote known to be known by this peer
knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer
knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer
queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer knownVote mapset.Set // Set of BFT Vote known to be known by this peer
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer
knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer`
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool
term chan struct{} // Termination channel to stop the broadcaster
} }
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID()
return &peer{ return &peer{
Peer: p, Peer: p,
rw: rw, rw: rw,
version: version, version: version,
id: fmt.Sprintf("%x", p.ID().Bytes()[:8]), id: fmt.Sprintf("%x", id[:8]),
knownTxs: mapset.NewSet(), knownTxs: mapset.NewSet(),
knownBlocks: mapset.NewSet(), knownBlocks: mapset.NewSet(),
knownOrderTxs: mapset.NewSet(), knownOrderTxs: mapset.NewSet(),
knownLendingTxs: mapset.NewSet(), knownLendingTxs: mapset.NewSet(),
knownVote: mapset.NewSet(),
knownTimeout: mapset.NewSet(), knownVote: mapset.NewSet(),
knownSyncInfo: mapset.NewSet(), knownTimeout: mapset.NewSet(),
queuedBlocks: make(chan *propEvent, maxQueuedBlocks), knownSyncInfo: mapset.NewSet(),
queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
txBroadcast: make(chan []common.Hash),
txAnnounce: make(chan []common.Hash),
getPooledTx: getPooledTx,
term: make(chan struct{}),
} }
} }
// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
// to the remote peer. The goal is to have an async writer that does not lock up
// node internals and at the same time rate limits queued data.
func (p *peer) broadcastBlocks() {
for {
select {
case prop := <-p.queuedBlocks:
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
return
}
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
case block := <-p.queuedBlockAnns:
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
return
}
p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
case <-p.term:
return
}
}
}
// broadcastTransactions is a write loop that schedules transaction broadcasts
// to the remote peer. The goal is to have an async writer that does not lock up
// node internals and at the same time rate limits queued data.
func (p *peer) broadcastTransactions() {
var (
queue []common.Hash // Queue of hashes to broadcast as full transactions
done chan struct{} // Non-nil if background broadcaster is running
fail = make(chan error) // Channel used to receive network error
)
for {
// If there's no in-flight broadcast running, check if a new one is needed
if done == nil && len(queue) > 0 {
// Pile transaction until we reach our allowed network limit
var (
hashes []common.Hash
txs []*types.Transaction
size common.StorageSize
)
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
if tx := p.getPooledTx(queue[i]); tx != nil {
txs = append(txs, tx)
size += tx.Size()
}
hashes = append(hashes, queue[i])
}
queue = queue[:copy(queue, queue[len(hashes):])]
// If there's anything available to transfer, fire up an async writer
if len(txs) > 0 {
done = make(chan struct{})
go func() {
if err := p.sendTransactions(txs); err != nil {
fail <- err
return
}
close(done)
p.Log().Trace("Sent transactions", "count", len(txs))
}()
}
}
// Transfer goroutine may or may not have been started, listen for events
select {
case hashes := <-p.txBroadcast:
// New batch of transactions to be broadcast, queue them (with cap)
queue = append(queue, hashes...)
if len(queue) > maxQueuedTxs {
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
}
case <-done:
done = nil
case <-fail:
return
case <-p.term:
return
}
}
}
// announceTransactions is a write loop that schedules transaction broadcasts
// to the remote peer. The goal is to have an async writer that does not lock up
// node internals and at the same time rate limits queued data.
func (p *peer) announceTransactions() {
var (
queue []common.Hash // Queue of hashes to announce as transaction stubs
done chan struct{} // Non-nil if background announcer is running
fail = make(chan error) // Channel used to receive network error
)
for {
// If there's no in-flight announce running, check if a new one is needed
if done == nil && len(queue) > 0 {
// Pile transaction hashes until we reach our allowed network limit
var (
hashes []common.Hash
pending []common.Hash
size common.StorageSize
)
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
if p.getPooledTx(queue[i]) != nil {
pending = append(pending, queue[i])
size += common.HashLength
}
hashes = append(hashes, queue[i])
}
queue = queue[:copy(queue, queue[len(hashes):])]
// If there's anything available to transfer, fire up an async writer
if len(pending) > 0 {
done = make(chan struct{})
go func() {
if err := p.sendPooledTransactionHashes(pending); err != nil {
fail <- err
return
}
close(done)
p.Log().Trace("Sent transaction announcements", "count", len(pending))
}()
}
}
// Transfer goroutine may or may not have been started, listen for events
select {
case hashes := <-p.txAnnounce:
// New batch of transactions to be broadcast, queue them (with cap)
queue = append(queue, hashes...)
if len(queue) > maxQueuedTxAnns {
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
}
case <-done:
done = nil
case <-fail:
return
case <-p.term:
return
}
}
}
// close signals the broadcast goroutine to terminate.
func (p *peer) close() {
close(p.term)
}
// Info gathers and returns a collection of metadata known about a peer. // Info gathers and returns a collection of metadata known about a peer.
func (p *peer) Info() *PeerInfo { func (p *peer) Info() *PeerInfo {
hash, td := p.Head() hash, td := p.Head()
@ -392,41 +199,16 @@ func (p *peer) MarkSyncInfo(hash common.Hash) {
p.knownSyncInfo.Add(hash) p.knownSyncInfo.Add(hash)
} }
// SendTransactions64 sends transactions to the peer and includes the hashes // SendTransactions sends transactions to the peer and includes the hashes
// in its transaction hash set for future reference. // in its transaction hash set for future reference.
// func (p *peer) SendTransactions(txs types.Transactions) error {
// This method is legacy support for initial transaction exchange in eth/64 and for p.knownTxs.Cardinality() >= maxKnownTxs {
// prior. For eth/65 and higher use SendPooledTransactionHashes.
func (p *peer) SendTransactions64(txs types.Transactions) error {
return p.sendTransactions(txs)
}
// // SendTransactions sends transactions to the peer and includes the hashes
// // in its transaction hash set for future reference.
// func (p *peer) SendTransactions(txs types.Transactions) error {
// for p.knownTxs.Cardinality() >= maxKnownTxs {
// p.knownTxs.Pop()
// }
// for _, tx := range txs {
// p.knownTxs.Add(tx.Hash())
// return p2p.Send(p.rw, TxMsg, txs)
// }
// sendTransactions sends transactions to the peer and includes the hashes
// in its transaction hash set for future reference.
//
// This method is a helper used by the async transaction sender. Don't call it
// directly as the queueing (memory) and transmission (bandwidth) costs should
// not be managed directly.
func (p *peer) sendTransactions(txs types.Transactions) error {
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
p.knownTxs.Pop() p.knownTxs.Pop()
} }
for _, tx := range txs { for _, tx := range txs {
p.knownTxs.Add(tx.Hash()) p.knownTxs.Add(tx.Hash())
} }
return p2p.Send(p.rw, TransactionMsg, txs) return p2p.Send(p.rw, TxMsg, txs)
} }
// SendTransactions sends transactions to the peer and includes the hashes // SendTransactions sends transactions to the peer and includes the hashes
@ -442,24 +224,6 @@ func (p *peer) SendOrderTransactions(txs types.OrderTransactions) error {
return p2p.Send(p.rw, OrderTxMsg, txs) return p2p.Send(p.rw, OrderTxMsg, txs)
} }
// AsyncSendTransactions queues a list of transactions (by hash) to eventually
// propagate to a remote peer. The number of pending sends are capped (new ones
// will force old sends to be dropped)
func (p *peer) AsyncSendTransactions(hashes []common.Hash) {
select {
case p.txBroadcast <- hashes:
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
p.knownTxs.Pop()
}
for _, hash := range hashes {
p.knownTxs.Add(hash)
}
case <-p.term:
p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
}
}
// SendTransactions sends transactions to the peer and includes the hashes // SendTransactions sends transactions to the peer and includes the hashes
// in its transaction hash set for future reference. // in its transaction hash set for future reference.
func (p *peer) SendLendingTransactions(txs types.LendingTransactions) error { func (p *peer) SendLendingTransactions(txs types.LendingTransactions) error {
@ -473,64 +237,13 @@ func (p *peer) SendLendingTransactions(txs types.LendingTransactions) error {
return p2p.Send(p.rw, LendingTxMsg, txs) return p2p.Send(p.rw, LendingTxMsg, txs)
} }
// sendPooledTransactionHashes sends transaction hashes to the peer and includes
// them in its transaction hash set for future reference.
//
// This method is a helper used by the async transaction announcer. Don't call it
// directly as the queueing (memory) and transmission (bandwidth) costs should
// not be managed directly.
func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error {
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
p.knownTxs.Pop()
}
for _, hash := range hashes {
p.knownTxs.Add(hash)
}
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes)
}
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
// announce to a remote peer. The number of pending sends are capped (new ones
// will force old sends to be dropped)
func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
select {
case p.txAnnounce <- hashes:
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
p.knownTxs.Pop()
}
for _, hash := range hashes {
p.knownTxs.Add(hash)
}
case <-p.term:
p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
}
}
// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
// hashes in its transaction hash set for future reference.
//
// Note, the method assumes the hashes are correct and correspond to the list of
// transactions being sent.
func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
p.knownTxs.Pop()
}
for _, hash := range hashes {
p.knownTxs.Add(hash)
}
return p2p.Send(p.rw, PooledTransactionsMsg, txs)
}
// SendNewBlockHashes announces the availability of a number of blocks through // SendNewBlockHashes announces the availability of a number of blocks through
// a hash notification. // a hash notification.
func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
// Mark all the block hashes as known, but ensure we don't overflow our limits for p.knownBlocks.Cardinality() >= maxKnownBlocks {
for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
p.knownBlocks.Pop() p.knownBlocks.Pop()
} }
for _, hash := range hashes { for _, hash := range hashes {
p.knownBlocks.Add(hash) p.knownBlocks.Add(hash)
} }
@ -542,16 +255,6 @@ func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error
return p2p.Send(p.rw, NewBlockHashesMsg, request) return p2p.Send(p.rw, NewBlockHashesMsg, request)
} }
// // SendNewBlock propagates an entire block to a remote peer.
// func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
// // Mark all the block hash as known, but ensure we don't overflow our limits
// for p.knownBlocks.Cardinality() >= maxKnownBlocks {
// p.knownBlocks.Pop()
// }
// p.knownBlocks.Add(block.Hash())
// return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
// }
// SendNewBlock propagates an entire block to a remote peer. // SendNewBlock propagates an entire block to a remote peer.
func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error { func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
for p.knownBlocks.Cardinality() >= maxKnownBlocks { for p.knownBlocks.Cardinality() >= maxKnownBlocks {
@ -566,37 +269,6 @@ func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
} }
} }
// AsyncSendNewBlockHash queues the availability of a block for propagation to a
// remote peer. If the peer's broadcast queue is full, the event is silently
// dropped.
func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
select {
case p.queuedBlockAnns <- block:
// Mark all the block hash as known, but ensure we don't overflow our limits
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
p.knownBlocks.Pop()
}
p.knownBlocks.Add(block.Hash())
default:
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
}
}
// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
// the peer's broadcast queue is full, the event is silently dropped.
func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
select {
case p.queuedBlocks <- &propEvent{block: block, td: td}:
// Mark all the block hash as known, but ensure we don't overflow our limits
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
p.knownBlocks.Pop()
}
p.knownBlocks.Add(block.Hash())
default:
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
}
}
// SendBlockHeaders sends a batch of block headers to the remote peer. // SendBlockHeaders sends a batch of block headers to the remote peer.
func (p *peer) SendBlockHeaders(headers []*types.Header) error { func (p *peer) SendBlockHeaders(headers []*types.Header) error {
if p.pairRw != nil { if p.pairRw != nil {
@ -765,54 +437,24 @@ func (p *peer) RequestReceipts(hashes []common.Hash) error {
} }
} }
// RequestTxs fetches a batch of transactions from a remote node.
func (p *peer) RequestTxs(hashes []common.Hash) error {
p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes)
}
// Handshake executes the eth protocol handshake, negotiating version number, // Handshake executes the eth protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks. // network IDs, difficulties, head and genesis blocks.
func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash) error {
// Send out own handshake in a new thread // Send out own handshake in a new thread
errc := make(chan error, 2) errc := make(chan error, 2)
var status statusData // safe to read after two values have been received from errc
var (
status63 statusData63 // safe to read after two values have been received from errc
status statusData // safe to read after two values have been received from errc
)
go func() { go func() {
switch { errc <- p2p.Send(p.rw, StatusMsg, &statusData{
case isEth63(p.version): ProtocolVersion: uint32(p.version),
errc <- p2p.Send(p.rw, StatusMsg, &statusData63{ NetworkId: network,
ProtocolVersion: uint32(p.version), TD: td,
NetworkId: network, CurrentBlock: head,
TD: td, GenesisBlock: genesis,
CurrentBlock: head, })
GenesisBlock: genesis,
})
case isEth64OrHigher(p.version):
errc <- p2p.Send(p.rw, StatusMsg, &statusData{
ProtocolVersion: uint32(p.version),
NetworkID: network,
TD: td,
Head: head,
Genesis: genesis,
ForkID: forkID,
})
default:
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
}
}() }()
go func() { go func() {
switch { errc <- p.readStatus(network, &status, genesis)
case isEth63(p.version):
errc <- p.readStatusLegacy(network, &status63, genesis)
case isEth64OrHigher(p.version):
errc <- p.readStatus(network, &status, genesis, forkFilter)
default:
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
}
}() }()
timeout := time.NewTimer(handshakeTimeout) timeout := time.NewTimer(handshakeTimeout)
defer timeout.Stop() defer timeout.Stop()
@ -826,18 +468,11 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis
return p2p.DiscReadTimeout return p2p.DiscReadTimeout
} }
} }
switch { p.td, p.head = status.TD, status.CurrentBlock
case isEth63(p.version):
p.td, p.head = status63.TD, status63.CurrentBlock
case isEth64OrHigher(p.version):
p.td, p.head = status.TD, status.Head
default:
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
}
return nil return nil
} }
func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis common.Hash) error { func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash) (err error) {
msg, err := p.rw.ReadMsg() msg, err := p.rw.ReadMsg()
if err != nil { if err != nil {
return err return err
@ -845,18 +480,18 @@ func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis co
if msg.Code != StatusMsg { if msg.Code != StatusMsg {
return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
} }
if msg.Size > protocolMaxMsgSize { if msg.Size > ProtocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
} }
// Decode the handshake and make sure everything matches // Decode the handshake and make sure everything matches
if err := msg.Decode(&status); err != nil { if err := msg.Decode(&status); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)
} }
if status.GenesisBlock != genesis { if status.GenesisBlock != genesis {
return errResp(ErrGenesisMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8]) return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8])
} }
if status.NetworkId != network { if status.NetworkId != network {
return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkId, network) return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network)
} }
if int(status.ProtocolVersion) != p.version { if int(status.ProtocolVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
@ -864,36 +499,6 @@ func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis co
return nil return nil
} }
func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash, forkFilter forkid.Filter) error {
msg, err := p.rw.ReadMsg()
if err != nil {
return err
}
if msg.Code != StatusMsg {
return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
}
if msg.Size > protocolMaxMsgSize {
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
}
// Decode the handshake and make sure everything matches
if err := msg.Decode(&status); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
if status.NetworkID != network {
return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkID, network)
}
if int(status.ProtocolVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
}
if status.Genesis != genesis {
return errResp(ErrGenesisMismatch, "%x (!= %x)", status.Genesis, genesis)
}
if err := forkFilter(status.ForkID); err != nil {
return errResp(ErrForkIDRejected, "%v", err)
}
return nil
}
// String implements fmt.Stringer. // String implements fmt.Stringer.
func (p *peer) String() string { func (p *peer) String() string {
return fmt.Sprintf("Peer %s [%s]", p.id, return fmt.Sprintf("Peer %s [%s]", p.id,
@ -935,11 +540,6 @@ func (ps *peerSet) Register(p *peer) error {
return p2p.ErrAddPairPeer return p2p.ErrAddPairPeer
} }
ps.peers[p.id] = p ps.peers[p.id] = p
go p.broadcastBlocks()
go p.broadcastTransactions()
go p.announceTransactions()
return nil return nil
} }

View file

@ -23,7 +23,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -31,74 +30,28 @@ import (
// Constants to match up protocol versions and messages // Constants to match up protocol versions and messages
const ( const (
eth63 = 63 eth62 = 62
eth64 = 64 eth63 = 63
eth65 = 65 xdpos2 = 100
xdpos2 = 100 //xdpos2.1 = eth62+eth63
xdpos22 = 101 //xdpos2.2 = eth65
) )
// XDC needs the below functions because direct number equality doesn't work (eg. version >= 63) // Official short name of the protocol used during capability negotiation.
// we should try to match protocols 1 to 1 from now on, bump xdpos along with any new eth (eg. eth66 = xdpos23 only) var ProtocolName = "eth"
// try to follow the exact comparison from go-ethereum as much as possible (eg. version >= 63 <> isEth63OrHigher(version))
func isEth63(version int) bool { // Supported versions of the eth protocol (first is primary).
switch { var ProtocolVersions = []uint{xdpos2, eth63, eth62}
case version == 63:
return true
case version == 100:
return true
default:
return false
}
}
func isEth64(version int) bool {
switch {
case version == 64:
return true
default:
return false
}
}
func isEth65(version int) bool {
switch {
case version == 65:
return true
case version == 101:
return true
default:
return false
}
}
func isEth63OrHigher(version int) bool { // Number of implemented message corresponding to different protocol versions.
return isEth63(version) || isEth64(version) || isEth65(version) var ProtocolLengths = []uint64{227, 17, 8}
}
func isEth64OrHigher(version int) bool { const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
return isEth64(version) || isEth65(version)
}
func isEth65OrHigher(version int) bool {
return isEth65(version)
}
// protocolName is the official short name of the protocol used during capability negotiation.
const protocolName = "eth"
// ProtocolVersions are the supported versions of the eth protocol (first is primary).
var ProtocolVersions = []uint{xdpos22, xdpos2, eth65, eth64, eth63}
// protocolLengths are the number of implemented message corresponding to different protocol versions.
var protocolLengths = map[uint]uint64{xdpos22: 227, xdpos2: 227, eth65: 17, eth64: 17, eth63: 17}
const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
// eth protocol message codes // eth protocol message codes
const ( const (
// Protocol messages belonging to eth/62
StatusMsg = 0x00 StatusMsg = 0x00
NewBlockHashesMsg = 0x01 NewBlockHashesMsg = 0x01
TransactionMsg = 0x02 TxMsg = 0x02
GetBlockHeadersMsg = 0x03 GetBlockHeadersMsg = 0x03
BlockHeadersMsg = 0x04 BlockHeadersMsg = 0x04
GetBlockBodiesMsg = 0x05 GetBlockBodiesMsg = 0x05
@ -112,14 +65,6 @@ const (
GetReceiptsMsg = 0x0f GetReceiptsMsg = 0x0f
ReceiptsMsg = 0x10 ReceiptsMsg = 0x10
// New protocol message codes introduced in eth65
//
// Previously these message ids were used by some legacy and unsupported
// eth protocols, reown them here.
NewPooledTransactionHashesMsg = 0x28 //originally 0x08 but clash with OrderTxMsg
GetPooledTransactionsMsg = 0x29 //originally 0x09 but clash with LendingTxMsg
PooledTransactionsMsg = 0x0a
// Protocol messages belonging to xdpos2/100 // Protocol messages belonging to xdpos2/100
VoteMsg = 0xe0 VoteMsg = 0xe0
TimeoutMsg = 0xe1 TimeoutMsg = 0xe1
@ -133,11 +78,11 @@ const (
ErrDecode ErrDecode
ErrInvalidMsgCode ErrInvalidMsgCode
ErrProtocolVersionMismatch ErrProtocolVersionMismatch
ErrNetworkIDMismatch ErrNetworkIdMismatch
ErrGenesisMismatch ErrGenesisBlockMismatch
ErrForkIDRejected
ErrNoStatusMsg ErrNoStatusMsg
ErrExtraStatusMsg ErrExtraStatusMsg
ErrSuspendedPeer
) )
func (e errCode) String() string { func (e errCode) String() string {
@ -150,22 +95,14 @@ var errorToString = map[int]string{
ErrDecode: "Invalid message", ErrDecode: "Invalid message",
ErrInvalidMsgCode: "Invalid message code", ErrInvalidMsgCode: "Invalid message code",
ErrProtocolVersionMismatch: "Protocol version mismatch", ErrProtocolVersionMismatch: "Protocol version mismatch",
ErrNetworkIDMismatch: "Network ID mismatch", ErrNetworkIdMismatch: "NetworkId mismatch",
ErrGenesisMismatch: "Genesis mismatch", ErrGenesisBlockMismatch: "Genesis block mismatch",
ErrForkIDRejected: "Fork ID rejected",
ErrNoStatusMsg: "No status message", ErrNoStatusMsg: "No status message",
ErrExtraStatusMsg: "Extra status message", ErrExtraStatusMsg: "Extra status message",
ErrSuspendedPeer: "Suspended peer",
} }
type txPool interface { type txPool interface {
// Has returns an indicator whether txpool has a transaction
// cached with the given hash.
Has(hash common.Hash) bool
// Get retrieves the transaction from local txpool with given
// tx hash.
Get(hash common.Hash) *types.Transaction
// AddRemotes should add the given transactions to the pool. // AddRemotes should add the given transactions to the pool.
AddRemotes([]*types.Transaction) []error AddRemotes([]*types.Transaction) []error
@ -204,8 +141,8 @@ type lendingPool interface {
SubscribeTxPreEvent(chan<- core.LendingTxPreEvent) event.Subscription SubscribeTxPreEvent(chan<- core.LendingTxPreEvent) event.Subscription
} }
// statusData63 is the network packet for the status message for eth/63. // statusData is the network packet for the status message.
type statusData63 struct { type statusData struct {
ProtocolVersion uint32 ProtocolVersion uint32
NetworkId uint64 NetworkId uint64
TD *big.Int TD *big.Int
@ -213,16 +150,6 @@ type statusData63 struct {
GenesisBlock common.Hash GenesisBlock common.Hash
} }
// statusData is the network packet for the status message for eth/64 and later.
type statusData struct {
ProtocolVersion uint32
NetworkID uint64
TD *big.Int
Head common.Hash
Genesis common.Hash
ForkID forkid.ID
}
// newBlockHashesData is the network packet for the block announcements. // newBlockHashesData is the network packet for the block announcements.
type newBlockHashesData []struct { type newBlockHashesData []struct {
Hash common.Hash // Hash of one particular block being announced Hash common.Hash // Hash of one particular block being announced
@ -279,19 +206,6 @@ type newBlockData struct {
TD *big.Int TD *big.Int
} }
// sanityCheck verifies that the values are reasonable, as a DoS protection
func (request *newBlockData) sanityCheck() error {
if err := request.Block.SanityCheck(); err != nil {
return err
}
//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
// larger, it will still fit within 100 bits
if tdlen := request.TD.BitLen(); tdlen > 100 {
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
}
return nil
}
// blockBody represents the data content of a single block. // blockBody represents the data content of a single block.
type blockBody struct { type blockBody struct {
Transactions []*types.Transaction // Transactions contained within a block Transactions []*types.Transaction // Transactions contained within a block

View file

@ -18,26 +18,16 @@ package eth
import ( import (
"fmt" "fmt"
"math/big"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/forkid"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig" "github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -48,7 +38,10 @@ func init() {
var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
// Tests that handshake failures are detected and reported correctly. // Tests that handshake failures are detected and reported correctly.
func TestStatusMsgErrors63(t *testing.T) { func TestStatusMsgErrors62(t *testing.T) { testStatusMsgErrors(t, 62) }
func TestStatusMsgErrors63(t *testing.T) { testStatusMsgErrors(t, 63) }
func testStatusMsgErrors(t *testing.T, protocol int) {
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
var ( var (
genesis = pm.blockchain.Genesis() genesis = pm.blockchain.Genesis()
@ -63,24 +56,25 @@ func TestStatusMsgErrors63(t *testing.T) {
wantError error wantError error
}{ }{
{ {
code: TransactionMsg, data: []interface{}{}, code: TxMsg, data: []interface{}{},
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
}, },
{ {
code: StatusMsg, data: statusData63{10, ethconfig.Defaults.NetworkId, td, head.Hash(), genesis.Hash()}, code: StatusMsg, data: statusData{10, ethconfig.Defaults.NetworkId, td, head.Hash(), genesis.Hash()},
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 63), wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", protocol),
}, },
{ {
code: StatusMsg, data: statusData63{63, 999, td, head.Hash(), genesis.Hash()}, code: StatusMsg, data: statusData{uint32(protocol), 999, td, head.Hash(), genesis.Hash()},
wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", ethconfig.Defaults.NetworkId), wantError: errResp(ErrNetworkIdMismatch, "999 (!= 88)"),
}, },
{ {
code: StatusMsg, data: statusData63{63, ethconfig.Defaults.NetworkId, td, head.Hash(), common.Hash{3}}, code: StatusMsg, data: statusData{uint32(protocol), ethconfig.Defaults.NetworkId, td, head.Hash(), common.Hash{3}},
wantError: errResp(ErrGenesisMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]), wantError: errResp(ErrGenesisBlockMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]),
}, },
} }
for i, test := range tests { for i, test := range tests {
p, errc := newTestPeer("peer", 63, pm, false) p, errc := newTestPeer("peer", protocol, pm, false)
// The send call might hang until reset because // The send call might hang until reset because
// the protocol might not read the payload. // the protocol might not read the payload.
go p2p.Send(p.app, test.code, test.data) go p2p.Send(p.app, test.code, test.data)
@ -99,164 +93,9 @@ func TestStatusMsgErrors63(t *testing.T) {
} }
} }
func TestStatusMsgErrors64(t *testing.T) {
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
var (
genesis = pm.blockchain.Genesis()
head = pm.blockchain.CurrentHeader()
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
forkID = forkid.NewID(pm.blockchain)
)
defer pm.Stop()
tests := []struct {
code uint64
data interface{}
wantError error
}{
{
code: TransactionMsg, data: []interface{}{},
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
},
{
code: StatusMsg, data: statusData{10, ethconfig.Defaults.NetworkId, td, head.Hash(), genesis.Hash(), forkID},
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 64),
},
{
code: StatusMsg, data: statusData{64, 999, td, head.Hash(), genesis.Hash(), forkID},
wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", ethconfig.Defaults.NetworkId),
},
{
code: StatusMsg, data: statusData{64, ethconfig.Defaults.NetworkId, td, head.Hash(), common.Hash{3}, forkID},
wantError: errResp(ErrGenesisMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis.Hash()),
},
{
code: StatusMsg, data: statusData{64, ethconfig.Defaults.NetworkId, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
wantError: errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()),
},
}
for i, test := range tests {
p, errc := newTestPeer("peer", 64, pm, false)
// The send call might hang until reset because
// the protocol might not read the payload.
go p2p.Send(p.app, test.code, test.data)
select {
case err := <-errc:
if err == nil {
t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError)
} else if err.Error() != test.wantError.Error() {
t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError)
}
case <-time.After(2 * time.Second):
t.Errorf("protocol did not shut down within 2 seconds")
}
p.close()
}
}
func TestForkIDSplit(t *testing.T) {
var (
engine = ethash.NewFaker()
configNoFork = &params.ChainConfig{HomesteadBlock: big.NewInt(1)}
configProFork = &params.ChainConfig{
HomesteadBlock: big.NewInt(1),
EIP150Block: big.NewInt(2),
EIP155Block: big.NewInt(2),
EIP158Block: big.NewInt(2),
ByzantiumBlock: big.NewInt(3),
}
dbNoFork = rawdb.NewMemoryDatabase()
dbProFork = rawdb.NewMemoryDatabase()
gspecNoFork = &core.Genesis{Config: configNoFork}
gspecProFork = &core.Genesis{Config: configProFork}
genesisNoFork = gspecNoFork.MustCommit(dbNoFork)
genesisProFork = gspecProFork.MustCommit(dbProFork)
chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{})
chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{})
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
ethNoFork, _ = NewProtocolManager(configNoFork, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork)
ethProFork, _ = NewProtocolManager(configProFork, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork)
)
ethNoFork.Start(1000)
ethProFork.Start(1000)
// Both nodes should allow the other to connect (same genesis, next fork is the same)
p2pNoFork, p2pProFork := p2p.MsgPipe()
peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
errc := make(chan error, 2)
go func() { errc <- ethNoFork.handle(peerProFork) }()
go func() { errc <- ethProFork.handle(peerNoFork) }()
select {
case err := <-errc:
t.Fatalf("frontier nofork <-> profork failed: %v", err)
case <-time.After(250 * time.Millisecond):
p2pNoFork.Close()
p2pProFork.Close()
}
// Progress into Homestead. Fork's match, so we don't care what the future holds
chainNoFork.InsertChain(blocksNoFork[:1])
chainProFork.InsertChain(blocksProFork[:1])
p2pNoFork, p2pProFork = p2p.MsgPipe()
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
errc = make(chan error, 2)
go func() { errc <- ethNoFork.handle(peerProFork) }()
go func() { errc <- ethProFork.handle(peerNoFork) }()
select {
case err := <-errc:
t.Fatalf("homestead nofork <-> profork failed: %v", err)
case <-time.After(250 * time.Millisecond):
p2pNoFork.Close()
p2pProFork.Close()
}
// Progress into Spurious. Forks mismatch, signalling differing chains, reject
chainNoFork.InsertChain(blocksNoFork[1:2])
chainProFork.InsertChain(blocksProFork[1:2])
p2pNoFork, p2pProFork = p2p.MsgPipe()
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
errc = make(chan error, 2)
go func() { errc <- ethNoFork.handle(peerProFork) }()
go func() { errc <- ethProFork.handle(peerNoFork) }()
var successes int
for i := 0; i < 2; i++ {
select {
case err := <-errc:
if err == nil {
successes++
if successes == 2 { // Only one side disconnects
t.Fatalf("fork ID rejection didn't happen")
}
}
case <-time.After(250 * time.Millisecond):
t.Fatalf("split peers not rejected")
}
}
}
// This test checks that received transactions are added to the local pool. // This test checks that received transactions are added to the local pool.
func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) } func TestRecvTransactions62(t *testing.T) { testRecvTransactions(t, 62) }
func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) }
func TestRecvTransactions100(t *testing.T) { testRecvTransactions(t, 100) }
func TestRecvTransactions101(t *testing.T) { testRecvTransactions(t, 101) }
func testRecvTransactions(t *testing.T, protocol int) { func testRecvTransactions(t *testing.T, protocol int) {
txAdded := make(chan []*types.Transaction) txAdded := make(chan []*types.Transaction)
@ -267,7 +106,7 @@ func testRecvTransactions(t *testing.T, protocol int) {
defer p.close() defer p.close()
tx := newTestTransaction(testAccount, 0, 0) tx := newTestTransaction(testAccount, 0, 0)
if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil { if err := p2p.Send(p.app, TxMsg, []interface{}{tx}); err != nil {
t.Fatalf("send error: %v", err) t.Fatalf("send error: %v", err)
} }
select { select {
@ -283,26 +122,20 @@ func testRecvTransactions(t *testing.T, protocol int) {
} }
// This test checks that pending transactions are sent. // This test checks that pending transactions are sent.
func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) } func TestSendTransactions62(t *testing.T) { testSendTransactions(t, 62) }
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) }
func TestSendTransactions100(t *testing.T) { testSendTransactions(t, 100) }
func TestSendTransactions101(t *testing.T) { testSendTransactions(t, 101) }
func testSendTransactions(t *testing.T, protocol int) { func testSendTransactions(t *testing.T, protocol int) {
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
defer pm.Stop() defer pm.Stop()
// Fill the pool with big transactions (use a subscription to wait until all // Fill the pool with big transactions.
// the transactions are announced to avoid spurious events causing extra
// broadcasts).
const txsize = txsyncPackSize / 10 const txsize = txsyncPackSize / 10
alltxs := make([]*types.Transaction, 100) alltxs := make([]*types.Transaction, 100)
for nonce := range alltxs { for nonce := range alltxs {
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize) alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
} }
pm.txpool.AddRemotes(alltxs) pm.txpool.AddRemotes(alltxs)
time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame)
// Connect several peers. They should all receive the pending transactions. // Connect several peers. They should all receive the pending transactions.
var wg sync.WaitGroup var wg sync.WaitGroup
@ -314,50 +147,18 @@ func testSendTransactions(t *testing.T, protocol int) {
seen[tx.Hash()] = false seen[tx.Hash()] = false
} }
for n := 0; n < len(alltxs) && !t.Failed(); { for n := 0; n < len(alltxs) && !t.Failed(); {
var forAllHashes func(callback func(hash common.Hash)) var txs []*types.Transaction
switch { msg, err := p.app.ReadMsg()
case isEth63(protocol): if err != nil {
fallthrough t.Errorf("%v: read error: %v", p.Peer, err)
case isEth64(protocol): } else if msg.Code != TxMsg {
msg, err := p.app.ReadMsg() t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
if err != nil {
t.Errorf("%v: read error: %v", p.Peer, err)
continue
} else if msg.Code != TransactionMsg {
t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
continue
}
var txs []*types.Transaction
if err := msg.Decode(&txs); err != nil {
t.Errorf("%v: %v", p.Peer, err)
continue
}
forAllHashes = func(callback func(hash common.Hash)) {
for _, tx := range txs {
callback(tx.Hash())
}
}
case isEth65(protocol):
msg, err := p.app.ReadMsg()
if err != nil {
t.Errorf("%v: read error: %v", p.Peer, err)
continue
} else if msg.Code != NewPooledTransactionHashesMsg {
t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code)
continue
}
var hashes []common.Hash
if err := msg.Decode(&hashes); err != nil {
t.Errorf("%v: %v", p.Peer, err)
continue
}
forAllHashes = func(callback func(hash common.Hash)) {
for _, h := range hashes {
callback(h)
}
}
} }
forAllHashes(func(hash common.Hash) { if err := msg.Decode(&txs); err != nil {
t.Errorf("%v: %v", p.Peer, err)
}
for _, tx := range txs {
hash := tx.Hash()
seentx, want := seen[hash] seentx, want := seen[hash]
if seentx { if seentx {
t.Errorf("%v: got tx more than once: %x", p.Peer, hash) t.Errorf("%v: got tx more than once: %x", p.Peer, hash)
@ -367,7 +168,7 @@ func testSendTransactions(t *testing.T, protocol int) {
} }
seen[hash] = true seen[hash] = true
n++ n++
}) }
} }
} }
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
@ -377,52 +178,6 @@ func testSendTransactions(t *testing.T, protocol int) {
} }
wg.Wait() wg.Wait()
} }
func TestTransactionPropagation(t *testing.T) { testSyncTransaction(t, true) }
func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) }
func testSyncTransaction(t *testing.T, propagtion bool) {
// Create a protocol manager for transaction fetcher and sender
pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
defer pmFetcher.Stop()
pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
pmSender.broadcastTxAnnouncesOnly = !propagtion
defer pmSender.Stop()
// Sync up the two peers
io1, io2 := p2p.MsgPipe()
go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get))
go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get))
time.Sleep(250 * time.Millisecond)
pmFetcher.synchronise(pmFetcher.peers.BestPeer())
atomic.StoreUint32(&pmFetcher.acceptTxs, 1)
newTxs := make(chan core.NewTxsEvent, 1024)
sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs)
defer sub.Unsubscribe()
// Fill the pool with new transactions
alltxs := make([]*types.Transaction, 1024)
for nonce := range alltxs {
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0)
}
pmSender.txpool.AddRemotes(alltxs)
var got int
loop:
for {
select {
case ev := <-newTxs:
got += len(ev.Txs)
if got == 1024 {
break loop
}
case <-time.NewTimer(time.Second).C:
t.Fatal("Failed to retrieve all transaction")
}
}
}
// Tests that the custom union field encoder and decoder works correctly. // Tests that the custom union field encoder and decoder works correctly.
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {

View file

@ -25,7 +25,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
const ( const (
@ -44,12 +44,6 @@ type txsync struct {
// syncTransactions starts sending all currently pending transactions to the given peer. // syncTransactions starts sending all currently pending transactions to the given peer.
func (pm *ProtocolManager) syncTransactions(p *peer) { func (pm *ProtocolManager) syncTransactions(p *peer) {
// Assemble the set of transaction to broadcast or announce to the remote
// peer. Fun fact, this is quite an expensive operation as it needs to sort
// the transactions if the sorting is not cached yet. However, with a random
// order, insertions could overflow the non-executable queues and get dropped.
//
// TODO(karalabe): Figure out if we could get away with random order somehow
var txs types.Transactions var txs types.Transactions
pending, _ := pm.txpool.Pending() pending, _ := pm.txpool.Pending()
for _, batch := range pending { for _, batch := range pending {
@ -58,40 +52,26 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
if len(txs) == 0 { if len(txs) == 0 {
return return
} }
// The eth/65 protocol introduces proper transaction announcements, so instead
// of dripping transactions across multiple peers, just send the entire list as
// an announcement and let the remote side decide what they need (likely nothing).
if isEth65OrHigher(p.version) {
hashes := make([]common.Hash, len(txs))
for i, tx := range txs {
hashes[i] = tx.Hash()
}
p.AsyncSendPooledTransactionHashes(hashes)
return
}
// Out of luck, peer is running legacy protocols, drop the txs over
select { select {
case pm.txsyncCh <- &txsync{p: p, txs: txs}: case pm.txsyncCh <- &txsync{p, txs}:
case <-pm.quitSync: case <-pm.quitSync:
} }
} }
// txsyncLoop64 takes care of the initial transaction sync for each new // txsyncLoop takes care of the initial transaction sync for each new
// connection. When a new peer appears, we relay all currently pending // connection. When a new peer appears, we relay all currently pending
// transactions. In order to minimise egress bandwidth usage, we send // transactions. In order to minimise egress bandwidth usage, we send
// the transactions in small packs to one peer at a time. // the transactions in small packs to one peer at a time.
func (pm *ProtocolManager) txsyncLoop64() { func (pm *ProtocolManager) txsyncLoop() {
var ( var (
pending = make(map[enode.ID]*txsync) pending = make(map[discover.NodeID]*txsync)
sending = false // whether a send is active sending = false // whether a send is active
pack = new(txsync) // the pack that is being sent pack = new(txsync) // the pack that is being sent
done = make(chan error, 1) // result of the send done = make(chan error, 1) // result of the send
) )
// send starts a sending a pack of transactions from the sync. // send starts a sending a pack of transactions from the sync.
send := func(s *txsync) { send := func(s *txsync) {
if isEth65OrHigher(s.p.version) {
panic("initial transaction syncer running on eth/65+")
}
// Fill pack with transactions up to the target size. // Fill pack with transactions up to the target size.
size := common.StorageSize(0) size := common.StorageSize(0)
pack.p = s.p pack.p = s.p
@ -108,7 +88,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
// Send the pack in the background. // Send the pack in the background.
s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size) s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
sending = true sending = true
go func() { done <- pack.p.SendTransactions64(pack.txs) }() go func() { done <- pack.p.SendTransactions(pack.txs) }()
} }
// pick chooses the next pending sync. // pick chooses the next pending sync.
@ -153,11 +133,9 @@ func (pm *ProtocolManager) txsyncLoop64() {
// downloading hashes and blocks as well as handling the announcement handler. // downloading hashes and blocks as well as handling the announcement handler.
func (pm *ProtocolManager) syncer() { func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms // Start and ensure cleanup of sync mechanisms
pm.blockFetcher.Start() pm.fetcher.Start()
pm.txFetcher.Start()
pm.bft.Start() pm.bft.Start()
defer pm.blockFetcher.Stop() defer pm.fetcher.Stop()
defer pm.txFetcher.Stop()
defer pm.bft.Stop() defer pm.bft.Stop()
defer pm.downloader.Terminate() defer pm.downloader.Terminate()

View file

@ -23,18 +23,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) }
func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) }
func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) }
func TestFastSyncDisabling100(t *testing.T) { testFastSyncDisabling(t, 100) }
func TestFastSyncDisabling101(t *testing.T) { testFastSyncDisabling(t, 101) }
// Tests that fast sync gets disabled as soon as a real block is successfully // Tests that fast sync gets disabled as soon as a real block is successfully
// imported into the blockchain. // imported into the blockchain.
func testFastSyncDisabling(t *testing.T, protocol int) { func TestFastSyncDisabling(t *testing.T) {
// Create a pristine protocol manager, check that fast sync is left enabled // Create a pristine protocol manager, check that fast sync is left enabled
pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
if atomic.LoadUint32(&pmEmpty.fastSync) == 0 { if atomic.LoadUint32(&pmEmpty.fastSync) == 0 {
@ -48,8 +42,8 @@ func testFastSyncDisabling(t *testing.T, protocol int) {
// Sync up the two peers // Sync up the two peers
io1, io2 := p2p.MsgPipe() io1, io2 := p2p.MsgPipe()
go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get)) go pmFull.handle(pmFull.newPeer(63, p2p.NewPeer(discover.NodeID{}, "empty", nil), io2))
go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get)) go pmEmpty.handle(pmEmpty.newPeer(63, p2p.NewPeer(discover.NodeID{}, "full", nil), io1))
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
pmEmpty.synchronise(pmEmpty.peers.BestPeer()) pmEmpty.synchronise(pmEmpty.peers.BestPeer())

View file

@ -1,44 +0,0 @@
# bmt keystore rlp trie whisperv6
base: ubuntu:16.04
targets:
- name: rlp
language: go
version: "1.13"
corpus: ./fuzzers/rlp/corpus
harness:
function: Fuzz
package: github.com/ethereum/go-ethereum/tests/fuzzers/rlp
checkout: github.com/ethereum/go-ethereum/
- name: keystore
language: go
version: "1.13"
corpus: ./fuzzers/keystore/corpus
harness:
function: Fuzz
package: github.com/ethereum/go-ethereum/tests/fuzzers/keystore
checkout: github.com/ethereum/go-ethereum/
- name: trie
language: go
version: "1.13"
corpus: ./fuzzers/trie/corpus
harness:
function: Fuzz
package: github.com/ethereum/go-ethereum/tests/fuzzers/trie
checkout: github.com/ethereum/go-ethereum/
- name: txfetcher
language: go
version: "1.13"
corpus: ./fuzzers/txfetcher/corpus
harness:
function: Fuzz
package: github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher
checkout: github.com/ethereum/go-ethereum/
- name: whisperv6
language: go
version: "1.13"
corpus: ./fuzzers/whisperv6/corpus
harness:
function: Fuzz
package: github.com/ethereum/go-ethereum/tests/fuzzers/whisperv6
checkout: github.com/ethereum/go-ethereum/

View file

@ -186,16 +186,6 @@ web3._extend({
call: 'admin_removePeer', call: 'admin_removePeer',
params: 1 params: 1
}), }),
new web3._extend.Method({
name: 'addTrustedPeer',
call: 'admin_addTrustedPeer',
params: 1
}),
new web3._extend.Method({
name: 'removeTrustedPeer',
call: 'admin_removeTrustedPeer',
params: 1
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'exportChain', name: 'exportChain',
call: 'admin_exportChain', call: 'admin_exportChain',

View file

@ -112,6 +112,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config) (*LightEthereum, er
} }
leth.relay = NewLesTxRelay(peers, leth.reqDist) leth.relay = NewLesTxRelay(peers, leth.reqDist)
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg)
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
leth.odr = NewLesOdr(chainDb, leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer, leth.retriever) leth.odr = NewLesOdr(chainDb, leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer, leth.retriever)
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil { if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {

View file

@ -23,6 +23,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"net"
"sync" "sync"
"time" "time"
@ -39,6 +40,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/light" "github.com/XinFinOrg/XDPoSChain/light"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -165,7 +167,8 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
var entry *poolEntry var entry *poolEntry
peer := manager.newPeer(int(version), networkId, p, rw) peer := manager.newPeer(int(version), networkId, p, rw)
if manager.serverPool != nil { if manager.serverPool != nil {
entry = manager.serverPool.connect(peer, peer.Node()) addr := p.RemoteAddr().(*net.TCPAddr)
entry = manager.serverPool.connect(peer, addr.IP, uint16(addr.Port))
} }
peer.poolEntry = entry peer.poolEntry = entry
select { select {
@ -187,6 +190,12 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
NodeInfo: func() interface{} { NodeInfo: func() interface{} {
return manager.NodeInfo() return manager.NodeInfo()
}, },
PeerInfo: func(id discover.NodeID) interface{} {
if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
return p.Info()
}
return nil
},
}) })
} }
if len(manager.SubProtocols) == 0 { if len(manager.SubProtocols) == 0 {
@ -385,11 +394,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err) return errResp(ErrDecode, "%v: %v", msg, err)
} }
if err := req.sanityCheck(); err != nil {
return err
}
if p.requestAnnounceType == announceTypeSigned { if p.requestAnnounceType == announceTypeSigned {
if err := req.checkSignature(p.ID()); err != nil { if err := req.checkSignature(p.pubKey); err != nil {
p.Log().Trace("Invalid announcement signature", "err", err) p.Log().Trace("Invalid announcement signature", "err", err)
return err return err
} }

View file

@ -37,7 +37,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/les/flowcontrol" "github.com/XinFinOrg/XDPoSChain/les/flowcontrol"
"github.com/XinFinOrg/XDPoSChain/light" "github.com/XinFinOrg/XDPoSChain/light"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -223,7 +223,7 @@ func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, sh
app, net := p2p.MsgPipe() app, net := p2p.MsgPipe()
// Generate a random id and create the peer // Generate a random id and create the peer
var id enode.ID var id discover.NodeID
rand.Read(id[:]) rand.Read(id[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
@ -260,7 +260,7 @@ func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer,
app, net := p2p.MsgPipe() app, net := p2p.MsgPipe()
// Generate a random id and create the peer // Generate a random id and create the peer
var id enode.ID var id discover.NodeID
rand.Read(id[:]) rand.Read(id[:])
peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)

View file

@ -18,6 +18,7 @@
package les package les
import ( import (
"crypto/ecdsa"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -50,6 +51,7 @@ const (
type peer struct { type peer struct {
*p2p.Peer *p2p.Peer
pubKey *ecdsa.PublicKey
rw p2p.MsgReadWriter rw p2p.MsgReadWriter
@ -78,9 +80,11 @@ type peer struct {
func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID() id := p.ID()
pubKey, _ := id.Pubkey()
return &peer{ return &peer{
Peer: p, Peer: p,
pubKey: pubKey,
rw: rw, rw: rw,
version: version, version: version,
network: network, network: network,

View file

@ -18,7 +18,9 @@
package les package les
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -27,7 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/crypto/secp256k1"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -137,14 +139,6 @@ type announceData struct {
Update keyValueList Update keyValueList
} }
// sanityCheck verifies that the values are reasonable, as a DoS protection
func (a *announceData) sanityCheck() error {
if tdlen := a.Td.BitLen(); tdlen > 100 {
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
}
return nil
}
// sign adds a signature to the block announcement by the given privKey // sign adds a signature to the block announcement by the given privKey
func (a *announceData) sign(privKey *ecdsa.PrivateKey) { func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td}) rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
@ -153,20 +147,22 @@ func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
} }
// checkSignature verifies if the block announcement has a valid signature by the given pubKey // checkSignature verifies if the block announcement has a valid signature by the given pubKey
func (a *announceData) checkSignature(id enode.ID) error { func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error {
var sig []byte var sig []byte
if err := a.Update.decode().get("sign", &sig); err != nil { if err := a.Update.decode().get("sign", &sig); err != nil {
return err return err
} }
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td}) rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) recPubkey, err := secp256k1.RecoverPubkey(crypto.Keccak256(rlp), sig)
if err != nil { if err != nil {
return err return err
} }
if id == enode.PubkeyToIDV4(recPubkey) { pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
if bytes.Equal(pbytes, recPubkey) {
return nil return nil
} else {
return errors.New("Wrong signature")
} }
return errors.New("wrong signature")
} }
type blockInfo struct { type blockInfo struct {

View file

@ -14,10 +14,10 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les package les
import ( import (
"crypto/ecdsa"
"fmt" "fmt"
"io" "io"
"math" "math"
@ -28,12 +28,11 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common/mclock" "github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -74,6 +73,7 @@ const (
// and a short term value which is adjusted exponentially with a factor of // and a short term value which is adjusted exponentially with a factor of
// pstatRecentAdjust with each dial/connection and also returned exponentially // pstatRecentAdjust with each dial/connection and also returned exponentially
// to the average with the time constant pstatReturnToMeanTC // to the average with the time constant pstatReturnToMeanTC
pstatRecentAdjust = 0.1
pstatReturnToMeanTC = time.Hour pstatReturnToMeanTC = time.Hour
// node address selection weight is dropped by a factor of exp(-addrFailDropLn) after // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after
// each unsuccessful connection (restored after a successful one) // each unsuccessful connection (restored after a successful one)
@ -83,31 +83,14 @@ const (
responseScoreTC = time.Millisecond * 100 responseScoreTC = time.Millisecond * 100
delayScoreTC = time.Second * 5 delayScoreTC = time.Second * 5
timeoutPow = 10 timeoutPow = 10
// peerSelectMinWeight is added to calculated weights at request peer selection
// to give poorly performing peers a little chance of coming back
peerSelectMinWeight = 0.005
// initStatsWeight is used to initialize previously unknown peers with good // initStatsWeight is used to initialize previously unknown peers with good
// statistics to give a chance to prove themselves // statistics to give a chance to prove themselves
initStatsWeight = 1 initStatsWeight = 1
) )
// connReq represents a request for peer connection.
type connReq struct {
p *peer
node *enode.Node
result chan *poolEntry
}
// disconnReq represents a request for peer disconnection.
type disconnReq struct {
entry *poolEntry
stopped bool
done chan struct{}
}
// registerReq represents a request for peer registration.
type registerReq struct {
entry *poolEntry
done chan struct{}
}
// serverPool implements a pool for storing and selecting newly discovered and already // serverPool implements a pool for storing and selecting newly discovered and already
// known light server nodes. It received discovered nodes, stores statistics about // known light server nodes. It received discovered nodes, stores statistics about
// known nodes and takes care of always having enough good quality servers connected. // known nodes and takes care of always having enough good quality servers connected.
@ -115,16 +98,18 @@ type serverPool struct {
db ethdb.Database db ethdb.Database
dbKey []byte dbKey []byte
server *p2p.Server server *p2p.Server
quit chan struct{}
wg *sync.WaitGroup
connWg sync.WaitGroup connWg sync.WaitGroup
topic discv5.Topic topic discv5.Topic
discSetPeriod chan time.Duration discSetPeriod chan time.Duration
discNodes chan *enode.Node discNodes chan *discv5.Node
discLookups chan bool discLookups chan bool
trustedNodes map[enode.ID]*enode.Node entries map[discover.NodeID]*poolEntry
entries map[enode.ID]*poolEntry lock sync.Mutex
timeout, enableRetry chan *poolEntry timeout, enableRetry chan *poolEntry
adjustStats chan poolStatAdjust adjustStats chan poolStatAdjust
@ -132,32 +117,22 @@ type serverPool struct {
knownSelect, newSelect *weightedRandomSelect knownSelect, newSelect *weightedRandomSelect
knownSelected, newSelected int knownSelected, newSelected int
fastDiscover bool fastDiscover bool
connCh chan *connReq
disconnCh chan *disconnReq
registerCh chan *registerReq
closeCh chan struct{}
wg sync.WaitGroup
} }
// newServerPool creates a new serverPool instance // newServerPool creates a new serverPool instance
func newServerPool(db ethdb.Database, ulcServers []string) *serverPool { func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
pool := &serverPool{ pool := &serverPool{
db: db, db: db,
entries: make(map[enode.ID]*poolEntry), quit: quit,
wg: wg,
entries: make(map[discover.NodeID]*poolEntry),
timeout: make(chan *poolEntry, 1), timeout: make(chan *poolEntry, 1),
adjustStats: make(chan poolStatAdjust, 100), adjustStats: make(chan poolStatAdjust, 100),
enableRetry: make(chan *poolEntry, 1), enableRetry: make(chan *poolEntry, 1),
connCh: make(chan *connReq),
disconnCh: make(chan *disconnReq),
registerCh: make(chan *registerReq),
closeCh: make(chan struct{}),
knownSelect: newWeightedRandomSelect(), knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(), newSelect: newWeightedRandomSelect(),
fastDiscover: true, fastDiscover: true,
trustedNodes: parseTrustedNodes(ulcServers),
} }
pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
return pool return pool
@ -167,52 +142,18 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
pool.server = server pool.server = server
pool.topic = topic pool.topic = topic
pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
pool.wg.Add(1)
pool.loadNodes() pool.loadNodes()
pool.connectToTrustedNodes()
if pool.server.DiscV5 != nil { if pool.server.DiscV5 != nil {
pool.discSetPeriod = make(chan time.Duration, 1) pool.discSetPeriod = make(chan time.Duration, 1)
pool.discNodes = make(chan *enode.Node, 100) pool.discNodes = make(chan *discv5.Node, 100)
pool.discLookups = make(chan bool, 100) pool.discLookups = make(chan bool, 100)
go pool.discoverNodes() go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
} }
pool.checkDial()
pool.wg.Add(1)
go pool.eventLoop() go pool.eventLoop()
pool.checkDial()
// Inject the bootstrap nodes as initial dial candiates.
pool.wg.Add(1)
go func() {
defer pool.wg.Done()
for _, n := range server.BootstrapNodes {
select {
case pool.discNodes <- n:
case <-pool.closeCh:
return
}
}
}()
}
func (pool *serverPool) stop() {
close(pool.closeCh)
pool.wg.Wait()
}
// discoverNodes wraps SearchTopic, converting result nodes to enode.Node.
func (pool *serverPool) discoverNodes() {
ch := make(chan *discv5.Node)
go func() {
pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, ch, pool.discLookups)
close(ch)
}()
for n := range ch {
pubkey, err := decodePubkey64(n.ID[:])
if err != nil {
continue
}
pool.discNodes <- enode.NewV4(pubkey, n.IP, int(n.TCP), int(n.UDP))
}
} }
// connect should be called upon any incoming connection. If the connection has been // connect should be called upon any incoming connection. If the connection has been
@ -220,45 +161,84 @@ func (pool *serverPool) discoverNodes() {
// Otherwise, the connection should be rejected. // Otherwise, the connection should be rejected.
// Note that whenever a connection has been accepted and a pool entry has been returned, // Note that whenever a connection has been accepted and a pool entry has been returned,
// disconnect should also always be called. // disconnect should also always be called.
func (pool *serverPool) connect(p *peer, node *enode.Node) *poolEntry { func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry {
log.Debug("Connect new entry", "enode", p.id) pool.lock.Lock()
req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)} defer pool.lock.Unlock()
select { entry := pool.entries[p.ID()]
case pool.connCh <- req: if entry == nil {
case <-pool.closeCh: entry = pool.findOrNewNode(p.ID(), ip, port)
}
p.Log().Debug("Connecting to new peer", "state", entry.state)
if entry.state == psConnected || entry.state == psRegistered {
return nil return nil
} }
return <-req.result pool.connWg.Add(1)
entry.peer = p
entry.state = psConnected
addr := &poolEntryAddress{
ip: ip,
port: port,
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
return entry
} }
// registered should be called after a successful handshake // registered should be called after a successful handshake
func (pool *serverPool) registered(entry *poolEntry) { func (pool *serverPool) registered(entry *poolEntry) {
log.Debug("Registered new entry", "enode", entry.node.ID()) log.Debug("Registered new entry", "enode", entry.id)
req := &registerReq{entry: entry, done: make(chan struct{})} pool.lock.Lock()
select { defer pool.lock.Unlock()
case pool.registerCh <- req:
case <-pool.closeCh: entry.state = psRegistered
return entry.regTime = mclock.Now()
if !entry.known {
pool.newQueue.remove(entry)
entry.known = true
} }
<-req.done pool.knownQueue.setLatest(entry)
entry.shortRetry = shortRetryCnt
} }
// disconnect should be called when ending a connection. Service quality statistics // disconnect should be called when ending a connection. Service quality statistics
// can be updated optionally (not updated if no registration happened, in this case // can be updated optionally (not updated if no registration happened, in this case
// only connection statistics are updated, just like in case of timeout) // only connection statistics are updated, just like in case of timeout)
func (pool *serverPool) disconnect(entry *poolEntry) { func (pool *serverPool) disconnect(entry *poolEntry) {
stopped := false log.Debug("Disconnected old entry", "enode", entry.id)
select { pool.lock.Lock()
case <-pool.closeCh: defer pool.lock.Unlock()
stopped = true
default:
}
log.Debug("Disconnected old entry", "enode", entry.node.ID())
req := &disconnReq{entry: entry, stopped: stopped, done: make(chan struct{})}
// Block until disconnection request is served. if entry.state == psRegistered {
pool.disconnCh <- req connTime := mclock.Now() - entry.regTime
<-req.done connAdjust := float64(connTime) / float64(targetConnTime)
if connAdjust > 1 {
connAdjust = 1
}
stopped := false
select {
case <-pool.quit:
stopped = true
default:
}
if stopped {
entry.connectStats.add(1, connAdjust)
} else {
entry.connectStats.add(connAdjust, 1)
}
}
entry.state = psNotConnected
if entry.knownSelected {
pool.knownSelected--
} else {
pool.newSelected--
}
pool.setRetryDial(entry)
pool.connWg.Done()
} }
const ( const (
@ -296,57 +276,30 @@ func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration,
// eventLoop handles pool events and mutex locking for all internal functions // eventLoop handles pool events and mutex locking for all internal functions
func (pool *serverPool) eventLoop() { func (pool *serverPool) eventLoop() {
defer pool.wg.Done()
lookupCnt := 0 lookupCnt := 0
var convTime mclock.AbsTime var convTime mclock.AbsTime
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
pool.discSetPeriod <- time.Millisecond * 100 pool.discSetPeriod <- time.Millisecond * 100
} }
// disconnect updates service quality statistics depending on the connection time
// and disconnection initiator.
disconnect := func(req *disconnReq, stopped bool) {
// Handle peer disconnection requests.
entry := req.entry
if entry.state == psRegistered {
connAdjust := float64(mclock.Now()-entry.regTime) / float64(targetConnTime)
if connAdjust > 1 {
connAdjust = 1
}
if stopped {
// disconnect requested by ourselves.
entry.connectStats.add(1, connAdjust)
} else {
// disconnect requested by server side.
entry.connectStats.add(connAdjust, 1)
}
}
entry.state = psNotConnected
if entry.knownSelected {
pool.knownSelected--
} else {
pool.newSelected--
}
pool.setRetryDial(entry)
pool.connWg.Done()
close(req.done)
}
for { for {
select { select {
case entry := <-pool.timeout: case entry := <-pool.timeout:
pool.lock.Lock()
if !entry.removed { if !entry.removed {
pool.checkDialTimeout(entry) pool.checkDialTimeout(entry)
} }
pool.lock.Unlock()
case entry := <-pool.enableRetry: case entry := <-pool.enableRetry:
pool.lock.Lock()
if !entry.removed { if !entry.removed {
entry.delayedRetry = false entry.delayedRetry = false
pool.updateCheckDial(entry) pool.updateCheckDial(entry)
} }
pool.lock.Unlock()
case adj := <-pool.adjustStats: case adj := <-pool.adjustStats:
pool.lock.Lock()
switch adj.adjustType { switch adj.adjustType {
case pseBlockDelay: case pseBlockDelay:
adj.entry.delayStats.add(float64(adj.time), 1) adj.entry.delayStats.add(float64(adj.time), 1)
@ -356,12 +309,13 @@ func (pool *serverPool) eventLoop() {
case pseResponseTimeout: case pseResponseTimeout:
adj.entry.timeoutStats.add(1, 1) adj.entry.timeoutStats.add(1, 1)
} }
pool.lock.Unlock()
case node := <-pool.discNodes: case node := <-pool.discNodes:
if pool.trustedNodes[node.ID()] == nil { pool.lock.Lock()
entry := pool.findOrNewNode(node) entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP)
pool.updateCheckDial(entry) pool.updateCheckDial(entry)
} pool.lock.Unlock()
case conv := <-pool.discLookups: case conv := <-pool.discLookups:
if conv { if conv {
@ -377,92 +331,31 @@ func (pool *serverPool) eventLoop() {
} }
} }
case req := <-pool.connCh: case <-pool.quit:
if pool.trustedNodes[req.p.ID()] != nil {
// ignore trusted nodes
req.result <- &poolEntry{trusted: true}
} else {
// Handle peer connection requests.
entry := pool.entries[req.p.ID()]
if entry == nil {
entry = pool.findOrNewNode(req.node)
}
if entry.state == psConnected || entry.state == psRegistered {
req.result <- nil
continue
}
pool.connWg.Add(1)
entry.peer = req.p
entry.state = psConnected
addr := &poolEntryAddress{
ip: req.node.IP(),
port: uint16(req.node.TCP()),
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
req.result <- entry
}
case req := <-pool.registerCh:
if req.entry.trusted {
continue
}
// Handle peer registration requests.
entry := req.entry
entry.state = psRegistered
entry.regTime = mclock.Now()
if !entry.known {
pool.newQueue.remove(entry)
entry.known = true
}
pool.knownQueue.setLatest(entry)
entry.shortRetry = shortRetryCnt
close(req.done)
case req := <-pool.disconnCh:
if req.entry.trusted {
continue
}
// Handle peer disconnection requests.
disconnect(req, req.stopped)
case <-pool.closeCh:
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
close(pool.discSetPeriod) close(pool.discSetPeriod)
} }
pool.connWg.Wait()
// Spawn a goroutine to close the disconnCh after all connections are disconnected.
go func() {
pool.connWg.Wait()
close(pool.disconnCh)
}()
// Handle all remaining disconnection requests before exit.
for req := range pool.disconnCh {
disconnect(req, true)
}
pool.saveNodes() pool.saveNodes()
pool.wg.Done()
return return
} }
} }
} }
func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry { func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry {
now := mclock.Now() now := mclock.Now()
entry := pool.entries[node.ID()] entry := pool.entries[id]
if entry == nil { if entry == nil {
log.Debug("Discovered new entry", "id", node.ID()) log.Debug("Discovered new entry", "id", id)
entry = &poolEntry{ entry = &poolEntry{
node: node, id: id,
addr: make(map[string]*poolEntryAddress), addr: make(map[string]*poolEntryAddress),
addrSelect: *newWeightedRandomSelect(), addrSelect: *newWeightedRandomSelect(),
shortRetry: shortRetryCnt, shortRetry: shortRetryCnt,
} }
pool.entries[node.ID()] = entry pool.entries[id] = entry
// initialize previously unknown peers with good statistics to give a chance to prove themselves // initialize previously unknown peers with good statistics to give a chance to prove themselves
entry.connectStats.add(1, initStatsWeight) entry.connectStats.add(1, initStatsWeight)
entry.delayStats.add(0, initStatsWeight) entry.delayStats.add(0, initStatsWeight)
@ -470,7 +363,10 @@ func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry {
entry.timeoutStats.add(0, initStatsWeight) entry.timeoutStats.add(0, initStatsWeight)
} }
entry.lastDiscovered = now entry.lastDiscovered = now
addr := &poolEntryAddress{ip: node.IP(), port: uint16(node.TCP())} addr := &poolEntryAddress{
ip: ip,
port: port,
}
if a, ok := entry.addr[addr.strKey()]; ok { if a, ok := entry.addr[addr.strKey()]; ok {
addr = a addr = a
} else { } else {
@ -497,48 +393,17 @@ func (pool *serverPool) loadNodes() {
return return
} }
for _, e := range list { for _, e := range list {
log.Debug("Loaded server stats", "id", e.node.ID(), "fails", e.lastConnected.fails, log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails,
"conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight), "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight),
"delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight), "delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight),
"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
pool.entries[e.node.ID()] = e pool.entries[e.id] = e
if pool.trustedNodes[e.node.ID()] == nil { pool.knownQueue.setLatest(e)
pool.knownQueue.setLatest(e) pool.knownSelect.update((*knownEntry)(e))
pool.knownSelect.update((*knownEntry)(e))
}
} }
} }
// connectToTrustedNodes adds trusted server nodes as static trusted peers.
//
// Note: trusted nodes are not handled by the server pool logic, they are not
// added to either the known or new selection pools. They are connected/reconnected
// by p2p.Server whenever possible.
func (pool *serverPool) connectToTrustedNodes() {
//connect to trusted nodes
for _, node := range pool.trustedNodes {
pool.server.AddTrustedPeer(node)
pool.server.AddPeer(node)
log.Debug("Added trusted node", "id", node.ID().String())
}
}
// parseTrustedNodes returns valid and parsed enodes
func parseTrustedNodes(trustedNodes []string) map[enode.ID]*enode.Node {
nodes := make(map[enode.ID]*enode.Node)
for _, node := range trustedNodes {
node, err := enode.ParseV4(node)
if err != nil {
log.Warn("Trusted node URL invalid", "enode", node, "err", err)
continue
}
nodes[node.ID()] = node
}
return nodes
}
// saveNodes saves known nodes and their statistics into the database. Nodes are // saveNodes saves known nodes and their statistics into the database. Nodes are
// ordered from least to most recently connected. // ordered from least to most recently connected.
func (pool *serverPool) saveNodes() { func (pool *serverPool) saveNodes() {
@ -559,7 +424,7 @@ func (pool *serverPool) removeEntry(entry *poolEntry) {
pool.newSelect.remove((*discoveredEntry)(entry)) pool.newSelect.remove((*discoveredEntry)(entry))
pool.knownSelect.remove((*knownEntry)(entry)) pool.knownSelect.remove((*knownEntry)(entry))
entry.removed = true entry.removed = true
delete(pool.entries, entry.node.ID()) delete(pool.entries, entry.id)
} }
// setRetryDial starts the timer which will enable dialing a certain node again // setRetryDial starts the timer which will enable dialing a certain node again
@ -573,10 +438,10 @@ func (pool *serverPool) setRetryDial(entry *poolEntry) {
entry.delayedRetry = true entry.delayedRetry = true
go func() { go func() {
select { select {
case <-pool.closeCh: case <-pool.quit:
case <-time.After(delay): case <-time.After(delay):
select { select {
case <-pool.closeCh: case <-pool.quit:
case pool.enableRetry <- entry: case pool.enableRetry <- entry:
} }
} }
@ -637,15 +502,15 @@ func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
pool.newSelected++ pool.newSelected++
} }
addr := entry.addrSelect.choose().(*poolEntryAddress) addr := entry.addrSelect.choose().(*poolEntryAddress)
log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected)
entry.dialed = addr entry.dialed = addr
go func() { go func() {
pool.server.AddPeer(entry.node) pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port))
select { select {
case <-pool.closeCh: case <-pool.quit:
case <-time.After(dialTimeout): case <-time.After(dialTimeout):
select { select {
case <-pool.closeCh: case <-pool.quit:
case pool.timeout <- entry: case pool.timeout <- entry:
} }
} }
@ -658,7 +523,7 @@ func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
if entry.state != psDialed { if entry.state != psDialed {
return return
} }
log.Debug("Dial timeout", "lesaddr", entry.node.ID().String()+"@"+entry.dialed.strKey()) log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey())
entry.state = psNotConnected entry.state = psNotConnected
if entry.knownSelected { if entry.knownSelected {
pool.knownSelected-- pool.knownSelected--
@ -680,58 +545,41 @@ const (
// poolEntry represents a server node and stores its current state and statistics. // poolEntry represents a server node and stores its current state and statistics.
type poolEntry struct { type poolEntry struct {
peer *peer peer *peer
pubkey [64]byte // secp256k1 key of the node id discover.NodeID
addr map[string]*poolEntryAddress addr map[string]*poolEntryAddress
node *enode.Node
lastConnected, dialed *poolEntryAddress lastConnected, dialed *poolEntryAddress
addrSelect weightedRandomSelect addrSelect weightedRandomSelect
lastDiscovered mclock.AbsTime lastDiscovered mclock.AbsTime
known, knownSelected, trusted bool known, knownSelected bool
connectStats, delayStats poolStats connectStats, delayStats poolStats
responseStats, timeoutStats poolStats responseStats, timeoutStats poolStats
state int state int
regTime mclock.AbsTime regTime mclock.AbsTime
queueIdx int queueIdx int
removed bool removed bool
delayedRetry bool delayedRetry bool
shortRetry int shortRetry int
} }
// poolEntryEnc is the RLP encoding of poolEntry.
type poolEntryEnc struct {
Pubkey []byte
IP net.IP
Port uint16
Fails uint
CStat, DStat, RStat, TStat poolStats
}
func (e *poolEntry) EncodeRLP(w io.Writer) error { func (e *poolEntry) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &poolEntryEnc{ return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats})
Pubkey: encodePubkey64(e.node.Pubkey()),
IP: e.lastConnected.ip,
Port: e.lastConnected.port,
Fails: e.lastConnected.fails,
CStat: e.connectStats,
DStat: e.delayStats,
RStat: e.responseStats,
TStat: e.timeoutStats,
})
} }
func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
var entry poolEntryEnc var entry struct {
ID discover.NodeID
IP net.IP
Port uint16
Fails uint
CStat, DStat, RStat, TStat poolStats
}
if err := s.Decode(&entry); err != nil { if err := s.Decode(&entry); err != nil {
return err return err
} }
pubkey, err := decodePubkey64(entry.Pubkey)
if err != nil {
return err
}
addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port)) e.id = entry.ID
e.addr = make(map[string]*poolEntryAddress) e.addr = make(map[string]*poolEntryAddress)
e.addr[addr.strKey()] = addr e.addr[addr.strKey()] = addr
e.addrSelect = *newWeightedRandomSelect() e.addrSelect = *newWeightedRandomSelect()
@ -746,14 +594,6 @@ func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
return nil return nil
} }
func encodePubkey64(pub *ecdsa.PublicKey) []byte {
return crypto.FromECDSAPub(pub)[1:]
}
func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) {
return crypto.UnmarshalPubkey(append([]byte{0x04}, b...))
}
// discoveredEntry implements wrsItem // discoveredEntry implements wrsItem
type discoveredEntry poolEntry type discoveredEntry poolEntry
@ -765,8 +605,9 @@ func (e *discoveredEntry) Weight() int64 {
t := time.Duration(mclock.Now() - e.lastDiscovered) t := time.Duration(mclock.Now() - e.lastDiscovered)
if t <= discoverExpireStart { if t <= discoverExpireStart {
return 1000000000 return 1000000000
} else {
return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
} }
return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
} }
// knownEntry implements wrsItem // knownEntry implements wrsItem

View file

@ -1,4 +0,0 @@
package metrics
const epsilon = 0.0000000000000001
const epsilonPercentile = .00000000001

View file

@ -1,9 +1,6 @@
package metrics package metrics
import ( import "testing"
"math"
"testing"
)
func BenchmarkEWMA(b *testing.B) { func BenchmarkEWMA(b *testing.B) {
a := NewEWMA1() a := NewEWMA1()
@ -18,67 +15,67 @@ func TestEWMA1(t *testing.T) {
a := NewEWMA1() a := NewEWMA1()
a.Update(3) a.Update(3)
a.Tick() a.Tick()
if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.22072766470286553-rate) > epsilon { if rate := a.Rate(); 0.22072766470286553 != rate {
t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate) t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.08120116994196772-rate) > epsilon { if rate := a.Rate(); 0.08120116994196772 != rate {
t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate) t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.029872241020718428-rate) > epsilon { if rate := a.Rate(); 0.029872241020718428 != rate {
t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate) t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.01098938333324054-rate) > epsilon { if rate := a.Rate(); 0.01098938333324054 != rate {
t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate) t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.004042768199451294-rate) > epsilon { if rate := a.Rate(); 0.004042768199451294 != rate {
t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate) t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.0014872513059998212-rate) > epsilon { if rate := a.Rate(); 0.0014872513059998212 != rate {
t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate) t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.0005471291793327122-rate) > epsilon { if rate := a.Rate(); 0.0005471291793327122 != rate {
t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate) t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.00020127757674150815-rate) > epsilon { if rate := a.Rate(); 0.00020127757674150815 != rate {
t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate) t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(7.404588245200814e-05-rate) > epsilon { if rate := a.Rate(); 7.404588245200814e-05 != rate {
t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate) t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(2.7239957857491083e-05-rate) > epsilon { if rate := a.Rate(); 2.7239957857491083e-05 != rate {
t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate) t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(1.0021020474147462e-05-rate) > epsilon { if rate := a.Rate(); 1.0021020474147462e-05 != rate {
t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate) t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(3.6865274119969525e-06-rate) > epsilon { if rate := a.Rate(); 3.6865274119969525e-06 != rate {
t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate) t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(1.3561976441886433e-06-rate) > epsilon { if rate := a.Rate(); 1.3561976441886433e-06 != rate {
t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate) t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(4.989172314621449e-07-rate) > epsilon { if rate := a.Rate(); 4.989172314621449e-07 != rate {
t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate) t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(1.8354139230109722e-07-rate) > epsilon { if rate := a.Rate(); 1.8354139230109722e-07 != rate {
t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate) t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)
} }
} }
@ -87,67 +84,67 @@ func TestEWMA5(t *testing.T) {
a := NewEWMA5() a := NewEWMA5()
a.Update(3) a.Update(3)
a.Tick() a.Tick()
if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.49123845184678905-rate) > epsilon { if rate := a.Rate(); 0.49123845184678905 != rate {
t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate) t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.4021920276213837-rate) > epsilon { if rate := a.Rate(); 0.4021920276213837 != rate {
t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate) t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.32928698165641596-rate) > epsilon { if rate := a.Rate(); 0.32928698165641596 != rate {
t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate) t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.269597378470333-rate) > epsilon { if rate := a.Rate(); 0.269597378470333 != rate {
t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate) t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.2207276647028654-rate) > epsilon { if rate := a.Rate(); 0.2207276647028654 != rate {
t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate) t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.18071652714732128-rate) > epsilon { if rate := a.Rate(); 0.18071652714732128 != rate {
t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate) t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.14795817836496392-rate) > epsilon { if rate := a.Rate(); 0.14795817836496392 != rate {
t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate) t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.12113791079679326-rate) > epsilon { if rate := a.Rate(); 0.12113791079679326 != rate {
t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate) t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.09917933293295193-rate) > epsilon { if rate := a.Rate(); 0.09917933293295193 != rate {
t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate) t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.08120116994196763-rate) > epsilon { if rate := a.Rate(); 0.08120116994196763 != rate {
t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate) t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.06648189501740036-rate) > epsilon { if rate := a.Rate(); 0.06648189501740036 != rate {
t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate) t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.05443077197364752-rate) > epsilon { if rate := a.Rate(); 0.05443077197364752 != rate {
t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate) t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.04456414692860035-rate) > epsilon { if rate := a.Rate(); 0.04456414692860035 != rate {
t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate) t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.03648603757513079-rate) > epsilon { if rate := a.Rate(); 0.03648603757513079 != rate {
t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate) t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.0298722410207183831020718428-rate) > epsilon { if rate := a.Rate(); 0.0298722410207183831020718428 != rate {
t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate) t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)
} }
} }
@ -156,67 +153,67 @@ func TestEWMA15(t *testing.T) {
a := NewEWMA15() a := NewEWMA15()
a.Update(3) a.Update(3)
a.Tick() a.Tick()
if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { if rate := a.Rate(); 0.6 != rate {
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.5613041910189706-rate) > epsilon { if rate := a.Rate(); 0.5613041910189706 != rate {
t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate) t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.5251039914257684-rate) > epsilon { if rate := a.Rate(); 0.5251039914257684 != rate {
t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate) t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.4912384518467888184678905-rate) > epsilon { if rate := a.Rate(); 0.4912384518467888184678905 != rate {
t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate) t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.459557003018789-rate) > epsilon { if rate := a.Rate(); 0.459557003018789 != rate {
t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate) t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.4299187863442732-rate) > epsilon { if rate := a.Rate(); 0.4299187863442732 != rate {
t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate) t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.4021920276213831-rate) > epsilon { if rate := a.Rate(); 0.4021920276213831 != rate {
t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate) t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.37625345116383313-rate) > epsilon { if rate := a.Rate(); 0.37625345116383313 != rate {
t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate) t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.3519877317060185-rate) > epsilon { if rate := a.Rate(); 0.3519877317060185 != rate {
t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate) t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.3292869816564153165641596-rate) > epsilon { if rate := a.Rate(); 0.3292869816564153165641596 != rate {
t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate) t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.3080502714195546-rate) > epsilon { if rate := a.Rate(); 0.3080502714195546 != rate {
t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate) t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.2881831806538789-rate) > epsilon { if rate := a.Rate(); 0.2881831806538789 != rate {
t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate) t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.26959737847033216-rate) > epsilon { if rate := a.Rate(); 0.26959737847033216 != rate {
t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate) t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.2522102307052083-rate) > epsilon { if rate := a.Rate(); 0.2522102307052083 != rate {
t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate) t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.23594443252115815-rate) > epsilon { if rate := a.Rate(); 0.23594443252115815 != rate {
t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate) t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate)
} }
elapseMinute(a) elapseMinute(a)
if rate := a.Rate(); math.Abs(0.2207276647028646247028654470286553-rate) > epsilon { if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate {
t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate) t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)
} }
} }

View file

@ -27,7 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/metrics" "github.com/XinFinOrg/XDPoSChain/metrics"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
@ -52,7 +52,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
return false, ErrNodeStopped return false, ErrNodeStopped
} }
// Try to add the url as a static peer and return // Try to add the url as a static peer and return
node, err := enode.ParseV4(url) node, err := discover.ParseNode(url)
if err != nil { if err != nil {
return false, fmt.Errorf("invalid enode: %v", err) return false, fmt.Errorf("invalid enode: %v", err)
} }
@ -60,7 +60,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
return true, nil return true, nil
} }
// RemovePeer disconnects from a remote node if the connection exists // RemovePeer disconnects from a a remote node if the connection exists
func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
@ -68,7 +68,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
return false, ErrNodeStopped return false, ErrNodeStopped
} }
// Try to remove the url as a static peer and return // Try to remove the url as a static peer and return
node, err := enode.ParseV4(url) node, err := discover.ParseNode(url)
if err != nil { if err != nil {
return false, fmt.Errorf("invalid enode: %v", err) return false, fmt.Errorf("invalid enode: %v", err)
} }
@ -76,37 +76,6 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
return true, nil return true, nil
} }
// AddTrustedPeer allows a remote node to always connect, even if slots are full
func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
return false, ErrNodeStopped
}
node, err := enode.ParseV4(url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
server.AddTrustedPeer(node)
return true, nil
}
// RemoveTrustedPeer removes a remote node from the trusted peer set, but it
// does not disconnect it automatically.
func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
return false, ErrNodeStopped
}
node, err := enode.ParseV4(url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
server.RemoveTrustedPeer(node)
return true, nil
}
// PeerEvents creates an RPC subscription which receives peer events from the // PeerEvents creates an RPC subscription which receives peer events from the
// node's p2p.Server // node's p2p.Server
func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {

View file

@ -32,7 +32,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
const ( const (
@ -336,18 +336,18 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey {
} }
// StaticNodes returns a list of node enode URLs configured as static nodes. // StaticNodes returns a list of node enode URLs configured as static nodes.
func (c *Config) StaticNodes() []*enode.Node { func (c *Config) StaticNodes() []*discover.Node {
return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes)) return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes))
} }
// TrustedNodes returns a list of node enode URLs configured as trusted nodes. // TrustedNodes returns a list of node enode URLs configured as trusted nodes.
func (c *Config) TrustedNodes() []*enode.Node { func (c *Config) TrustedNodes() []*discover.Node {
return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes)) return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes))
} }
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json // parsePersistentNodes parses a list of discovery node URLs loaded from a .json
// file from within the data directory. // file from within the data directory.
func (c *Config) parsePersistentNodes(path string) []*enode.Node { func (c *Config) parsePersistentNodes(path string) []*discover.Node {
// Short circuit if no node config is present // Short circuit if no node config is present
if c.DataDir == "" { if c.DataDir == "" {
return nil return nil
@ -362,12 +362,12 @@ func (c *Config) parsePersistentNodes(path string) []*enode.Node {
return nil return nil
} }
// Interpret the list as a discovery node array // Interpret the list as a discovery node array
var nodes []*enode.Node var nodes []*discover.Node
for _, url := range nodelist { for _, url := range nodelist {
if url == "" { if url == "" {
continue continue
} }
node, err := enode.ParseV4(url) node, err := discover.ParseNode(url)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
continue continue

View file

@ -453,9 +453,9 @@ func TestProtocolGather(t *testing.T) {
Count int Count int
Maker InstrumentingWrapper Maker InstrumentingWrapper
}{ }{
"zero": {0, InstrumentedServiceMakerA}, "Zero Protocols": {0, InstrumentedServiceMakerA},
"one": {1, InstrumentedServiceMakerB}, "Single Protocol": {1, InstrumentedServiceMakerB},
"many": {10, InstrumentedServiceMakerC}, "Many Protocols": {25, InstrumentedServiceMakerC},
} }
for id, config := range services { for id, config := range services {
protocols := make([]p2p.Protocol, config.Count) protocols := make([]p2p.Protocol, config.Count)
@ -479,7 +479,7 @@ func TestProtocolGather(t *testing.T) {
defer stack.Stop() defer stack.Stop()
protocols := stack.Server().Protocols protocols := stack.Server().Protocols
if len(protocols) != 11 { if len(protocols) != 26 {
t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26)
} }
for id, config := range services { for id, config := range services {

View file

@ -18,13 +18,14 @@ package p2p
import ( import (
"container/heap" "container/heap"
"crypto/rand"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
) )
@ -49,7 +50,7 @@ const (
// NodeDialer is used to connect to nodes in the network, typically by using // NodeDialer is used to connect to nodes in the network, typically by using
// an underlying net.Dialer but also using net.Pipe in tests // an underlying net.Dialer but also using net.Pipe in tests
type NodeDialer interface { type NodeDialer interface {
Dial(*enode.Node) (net.Conn, error) Dial(*discover.Node) (net.Conn, error)
} }
// TCPDialer implements the NodeDialer interface by using a net.Dialer to // TCPDialer implements the NodeDialer interface by using a net.Dialer to
@ -59,8 +60,8 @@ type TCPDialer struct {
} }
// Dial creates a TCP connection to the node // Dial creates a TCP connection to the node
func (t TCPDialer) Dial(dest *enode.Node) (net.Conn, error) { func (t TCPDialer) Dial(dest *discover.Node) (net.Conn, error) {
addr := &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()} addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)}
return t.Dialer.Dial("tcp", addr.String()) return t.Dialer.Dial("tcp", addr.String())
} }
@ -71,24 +72,24 @@ type dialstate struct {
maxDynDials int maxDynDials int
ntab discoverTable ntab discoverTable
netrestrict *netutil.Netlist netrestrict *netutil.Netlist
self enode.ID
lookupRunning bool lookupRunning bool
dialing map[enode.ID]connFlag dialing map[discover.NodeID]connFlag
lookupBuf []*enode.Node // current discovery lookup results lookupBuf []*discover.Node // current discovery lookup results
randomNodes []*enode.Node // filled from Table randomNodes []*discover.Node // filled from Table
static map[enode.ID]*dialTask static map[discover.NodeID]*dialTask
hist *dialHistory hist *dialHistory
start time.Time // time when the dialer was first used start time.Time // time when the dialer was first used
bootnodes []*enode.Node // default dials when there are no peers bootnodes []*discover.Node // default dials when there are no peers
} }
type discoverTable interface { type discoverTable interface {
Self() *discover.Node
Close() Close()
Resolve(*enode.Node) *enode.Node Resolve(target discover.NodeID) *discover.Node
LookupRandom() []*enode.Node Lookup(target discover.NodeID) []*discover.Node
ReadRandomNodes([]*enode.Node) int ReadRandomNodes([]*discover.Node) int
} }
// the dial history remembers recent dials. // the dial history remembers recent dials.
@ -96,7 +97,7 @@ type dialHistory []pastDial
// pastDial is an entry in the dial history. // pastDial is an entry in the dial history.
type pastDial struct { type pastDial struct {
id enode.ID id discover.NodeID
exp time.Time exp time.Time
} }
@ -108,7 +109,7 @@ type task interface {
// fields cannot be accessed while the task is running. // fields cannot be accessed while the task is running.
type dialTask struct { type dialTask struct {
flags connFlag flags connFlag
dest *enode.Node dest *discover.Node
lastResolved time.Time lastResolved time.Time
resolveDelay time.Duration resolveDelay time.Duration
} }
@ -117,7 +118,7 @@ type dialTask struct {
// Only one discoverTask is active at any time. // Only one discoverTask is active at any time.
// discoverTask.Do performs a random lookup. // discoverTask.Do performs a random lookup.
type discoverTask struct { type discoverTask struct {
results []*enode.Node results []*discover.Node
} }
// A waitExpireTask is generated if there are no other tasks // A waitExpireTask is generated if there are no other tasks
@ -126,16 +127,15 @@ type waitExpireTask struct {
time.Duration time.Duration
} }
func newDialState(self enode.ID, static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate { func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate {
s := &dialstate{ s := &dialstate{
maxDynDials: maxdyn, maxDynDials: maxdyn,
ntab: ntab, ntab: ntab,
self: self,
netrestrict: netrestrict, netrestrict: netrestrict,
static: make(map[enode.ID]*dialTask), static: make(map[discover.NodeID]*dialTask),
dialing: make(map[enode.ID]connFlag), dialing: make(map[discover.NodeID]connFlag),
bootnodes: make([]*enode.Node, len(bootnodes)), bootnodes: make([]*discover.Node, len(bootnodes)),
randomNodes: make([]*enode.Node, maxdyn/2), randomNodes: make([]*discover.Node, maxdyn/2),
hist: new(dialHistory), hist: new(dialHistory),
} }
copy(s.bootnodes, bootnodes) copy(s.bootnodes, bootnodes)
@ -145,32 +145,32 @@ func newDialState(self enode.ID, static []*enode.Node, bootnodes []*enode.Node,
return s return s
} }
func (s *dialstate) addStatic(n *enode.Node) { func (s *dialstate) addStatic(n *discover.Node) {
// This overwrites the task instead of updating an existing // This overwites the task instead of updating an existing
// entry, giving users the opportunity to force a resolve operation. // entry, giving users the opportunity to force a resolve operation.
s.static[n.ID()] = &dialTask{flags: staticDialedConn, dest: n} s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n}
} }
func (s *dialstate) removeStatic(n *enode.Node) { func (s *dialstate) removeStatic(n *discover.Node) {
// This removes a task so future attempts to connect will not be made. // This removes a task so future attempts to connect will not be made.
delete(s.static, n.ID()) delete(s.static, n.ID)
// This removes a previous dial timestamp so that application // This removes a previous dial timestamp so that application
// can force a server to reconnect with chosen peer immediately. // can force a server to reconnect with chosen peer immediately.
s.hist.remove(n.ID()) s.hist.remove(n.ID)
} }
func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Time) []task { func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task {
if s.start.IsZero() { if s.start.IsZero() {
s.start = now s.start = now
} }
var newtasks []task var newtasks []task
addDial := func(flag connFlag, n *enode.Node) bool { addDial := func(flag connFlag, n *discover.Node) bool {
if err := s.checkDial(n, peers); err != nil { if err := s.checkDial(n, peers); err != nil {
log.Trace("Skipping dial candidate", "id", n.ID(), "addr", &net.TCPAddr{IP: n.IP(), Port: n.TCP()}, "err", err) log.Trace("Skipping dial candidate", "id", n.ID, "addr", &net.TCPAddr{IP: n.IP, Port: int(n.TCP)}, "err", err)
return false return false
} }
s.dialing[n.ID()] = flag s.dialing[n.ID] = flag
newtasks = append(newtasks, &dialTask{flags: flag, dest: n}) newtasks = append(newtasks, &dialTask{flags: flag, dest: n})
return true return true
} }
@ -196,8 +196,8 @@ func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Ti
err := s.checkDial(t.dest, peers) err := s.checkDial(t.dest, peers)
switch err { switch err {
case errNotWhitelisted, errSelf: case errNotWhitelisted, errSelf:
log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}, "err", err) log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}, "err", err)
delete(s.static, t.dest.ID()) delete(s.static, t.dest.ID)
case nil: case nil:
s.dialing[id] = t.flags s.dialing[id] = t.flags
newtasks = append(newtasks, t) newtasks = append(newtasks, t)
@ -260,18 +260,21 @@ var (
errNotWhitelisted = errors.New("not contained in netrestrict whitelist") errNotWhitelisted = errors.New("not contained in netrestrict whitelist")
) )
func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error { func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer) error {
_, dialing := s.dialing[n.ID()] _, dialing := s.dialing[n.ID]
switch { switch {
case dialing: case dialing:
return errAlreadyDialing return errAlreadyDialing
case peers[n.ID()] != nil: case peers[n.ID] != nil:
return errAlreadyConnected exitsPeer := peers[n.ID]
case n.ID() == s.self: if exitsPeer.PairPeer != nil {
return errAlreadyConnected
}
case s.ntab != nil && n.ID == s.ntab.Self().ID:
return errSelf return errSelf
case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()): case s.netrestrict != nil && !s.netrestrict.Contains(n.IP):
return errNotWhitelisted return errNotWhitelisted
case s.hist.contains(n.ID()): case s.hist.contains(n.ID):
return errRecentlyDialed return errRecentlyDialed
} }
return nil return nil
@ -280,8 +283,8 @@ func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error {
func (s *dialstate) taskDone(t task, now time.Time) { func (s *dialstate) taskDone(t task, now time.Time) {
switch t := t.(type) { switch t := t.(type) {
case *dialTask: case *dialTask:
s.hist.add(t.dest.ID(), now.Add(dialHistoryExpiration)) s.hist.add(t.dest.ID, now.Add(dialHistoryExpiration))
delete(s.dialing, t.dest.ID()) delete(s.dialing, t.dest.ID)
case *discoverTask: case *discoverTask:
s.lookupRunning = false s.lookupRunning = false
s.lookupBuf = append(s.lookupBuf, t.results...) s.lookupBuf = append(s.lookupBuf, t.results...)
@ -339,7 +342,7 @@ func (t *dialTask) resolve(srv *Server) bool {
if time.Since(t.lastResolved) < t.resolveDelay { if time.Since(t.lastResolved) < t.resolveDelay {
return false return false
} }
resolved := srv.ntab.Resolve(t.dest) resolved := srv.ntab.Resolve(t.dest.ID)
t.lastResolved = time.Now() t.lastResolved = time.Now()
if resolved == nil { if resolved == nil {
t.resolveDelay *= 2 t.resolveDelay *= 2
@ -352,7 +355,7 @@ func (t *dialTask) resolve(srv *Server) bool {
// The node was found. // The node was found.
t.resolveDelay = initialResolveDelay t.resolveDelay = initialResolveDelay
t.dest = resolved t.dest = resolved
log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}) log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)})
return true return true
} }
@ -361,7 +364,7 @@ type dialError struct {
} }
// dial performs the actual connection attempt. // dial performs the actual connection attempt.
func (t *dialTask) dial(srv *Server, dest *enode.Node) error { func (t *dialTask) dial(srv *Server, dest *discover.Node) error {
fd, err := srv.Dialer.Dial(dest) fd, err := srv.Dialer.Dial(dest)
if err != nil { if err != nil {
return &dialError{err} return &dialError{err}
@ -371,8 +374,7 @@ func (t *dialTask) dial(srv *Server, dest *enode.Node) error {
} }
func (t *dialTask) String() string { func (t *dialTask) String() string {
id := t.dest.ID() return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP)
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
} }
func (t *discoverTask) Do(srv *Server) { func (t *discoverTask) Do(srv *Server) {
@ -384,7 +386,9 @@ func (t *discoverTask) Do(srv *Server) {
time.Sleep(next.Sub(now)) time.Sleep(next.Sub(now))
} }
srv.lastLookup = time.Now() srv.lastLookup = time.Now()
t.results = srv.ntab.LookupRandom() var target discover.NodeID
rand.Read(target[:])
t.results = srv.ntab.Lookup(target)
} }
func (t *discoverTask) String() string { func (t *discoverTask) String() string {
@ -406,11 +410,11 @@ func (t waitExpireTask) String() string {
func (h dialHistory) min() pastDial { func (h dialHistory) min() pastDial {
return h[0] return h[0]
} }
func (h *dialHistory) add(id enode.ID, exp time.Time) { func (h *dialHistory) add(id discover.NodeID, exp time.Time) {
heap.Push(h, pastDial{id, exp}) heap.Push(h, pastDial{id, exp})
} }
func (h *dialHistory) remove(id enode.ID) bool { func (h *dialHistory) remove(id discover.NodeID) bool {
for i, v := range *h { for i, v := range *h {
if v.id == id { if v.id == id {
heap.Remove(h, i) heap.Remove(h, i)
@ -419,7 +423,7 @@ func (h *dialHistory) remove(id enode.ID) bool {
} }
return false return false
} }
func (h dialHistory) contains(id enode.ID) bool { func (h dialHistory) contains(id discover.NodeID) bool {
for _, v := range h { for _, v := range h {
if v.id == id { if v.id == id {
return true return true

View file

@ -23,8 +23,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
) )
@ -49,10 +48,10 @@ func runDialTest(t *testing.T, test dialtest) {
vtime time.Time vtime time.Time
running int running int
) )
pm := func(ps []*Peer) map[enode.ID]*Peer { pm := func(ps []*Peer) map[discover.NodeID]*Peer {
m := make(map[enode.ID]*Peer) m := make(map[discover.NodeID]*Peer)
for _, p := range ps { for _, p := range ps {
m[p.ID()] = p m[p.rw.id] = p
} }
return m return m
} }
@ -70,7 +69,6 @@ func runDialTest(t *testing.T, test dialtest) {
t.Errorf("round %d: new tasks mismatch:\ngot %v\nwant %v\nstate: %v\nrunning: %v\n", t.Errorf("round %d: new tasks mismatch:\ngot %v\nwant %v\nstate: %v\nrunning: %v\n",
i, spew.Sdump(new), spew.Sdump(round.new), spew.Sdump(test.init), spew.Sdump(running)) i, spew.Sdump(new), spew.Sdump(round.new), spew.Sdump(test.init), spew.Sdump(running))
} }
t.Log("tasks:", spew.Sdump(new))
// Time advances by 16 seconds on every round. // Time advances by 16 seconds on every round.
vtime = vtime.Add(16 * time.Second) vtime = vtime.Add(16 * time.Second)
@ -78,79 +76,79 @@ func runDialTest(t *testing.T, test dialtest) {
} }
} }
type fakeTable []*enode.Node type fakeTable []*discover.Node
func (t fakeTable) Self() *enode.Node { return new(enode.Node) } func (t fakeTable) Self() *discover.Node { return new(discover.Node) }
func (t fakeTable) Close() {} func (t fakeTable) Close() {}
func (t fakeTable) LookupRandom() []*enode.Node { return nil } func (t fakeTable) Lookup(discover.NodeID) []*discover.Node { return nil }
func (t fakeTable) Resolve(*enode.Node) *enode.Node { return nil } func (t fakeTable) Resolve(discover.NodeID) *discover.Node { return nil }
func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) } func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) }
// This test checks that dynamic dials are launched from discovery results. // This test checks that dynamic dials are launched from discovery results.
func TestDialStateDynDial(t *testing.T) { func TestDialStateDynDial(t *testing.T) {
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, nil, nil, fakeTable{}, 5, nil), init: newDialState(nil, nil, fakeTable{}, 5, nil),
rounds: []round{ rounds: []round{
// A discovery query is launched. // A discovery query is launched.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
new: []task{&discoverTask{}}, new: []task{&discoverTask{}},
}, },
// Dynamic dials are launched when it completes. // Dynamic dials are launched when it completes.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
done: []task{ done: []task{
&discoverTask{results: []*enode.Node{ &discoverTask{results: []*discover.Node{
newNode(uintID(2), nil), // this one is already connected and not dialed. {ID: uintID(2)}, // this one is already connected and not dialed.
newNode(uintID(3), nil), {ID: uintID(3)},
newNode(uintID(4), nil), {ID: uintID(4)},
newNode(uintID(5), nil), {ID: uintID(5)},
newNode(uintID(6), nil), // these are not tried because max dyn dials is 5 {ID: uintID(6)}, // these are not tried because max dyn dials is 5
newNode(uintID(7), nil), // ... {ID: uintID(7)}, // ...
}}, }},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
}, },
}, },
// Some of the dials complete but no new ones are launched yet because // Some of the dials complete but no new ones are launched yet because
// the sum of active dial count and dynamic peer count is == maxDynDials. // the sum of active dial count and dynamic peer count is == maxDynDials.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
}, },
}, },
// No new dial tasks are launched in the this round because // No new dial tasks are launched in the this round because
// maxDynDials has been reached. // maxDynDials has been reached.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
new: []task{ new: []task{
&waitExpireTask{Duration: 14 * time.Second}, &waitExpireTask{Duration: 14 * time.Second},
@ -160,31 +158,29 @@ func TestDialStateDynDial(t *testing.T) {
// results from last discovery lookup are reused. // results from last discovery lookup are reused.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
},
new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)},
}, },
new: []task{},
}, },
// More peers (3,4) drop off and dial for ID 6 completes. // More peers (3,4) drop off and dial for ID 6 completes.
// The last query result from the discovery lookup is reused // The last query result from the discovery lookup is reused
// and a new one is spawned because more candidates are needed. // and a new one is spawned because more candidates are needed.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
&discoverTask{}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
}, },
}, },
// Peer 7 is connected, but there still aren't enough dynamic peers // Peer 7 is connected, but there still aren't enough dynamic peers
@ -192,29 +188,29 @@ func TestDialStateDynDial(t *testing.T) {
// no new is started. // no new is started.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(7)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
}, },
}, },
// Finish the running node discovery with an empty set. A new lookup // Finish the running node discovery with an empty set. A new lookup
// should be immediately requested. // should be immediately requested.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(7)}},
}, },
done: []task{ done: []task{
&discoverTask{}, &discoverTask{},
}, },
new: []task{ new: []task{
&discoverTask{}, &waitExpireTask{Duration: 14 * time.Second},
}, },
}, },
}, },
@ -223,34 +219,34 @@ func TestDialStateDynDial(t *testing.T) {
// Tests that bootnodes are dialed if no peers are connectd, but not otherwise. // Tests that bootnodes are dialed if no peers are connectd, but not otherwise.
func TestDialStateDynDialBootnode(t *testing.T) { func TestDialStateDynDialBootnode(t *testing.T) {
bootnodes := []*enode.Node{ bootnodes := []*discover.Node{
newNode(uintID(1), nil), {ID: uintID(1)},
newNode(uintID(2), nil), {ID: uintID(2)},
newNode(uintID(3), nil), {ID: uintID(3)},
} }
table := fakeTable{ table := fakeTable{
newNode(uintID(4), nil), {ID: uintID(4)},
newNode(uintID(5), nil), {ID: uintID(5)},
newNode(uintID(6), nil), {ID: uintID(6)},
newNode(uintID(7), nil), {ID: uintID(7)},
newNode(uintID(8), nil), {ID: uintID(8)},
} }
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, nil, bootnodes, table, 5, nil), init: newDialState(nil, bootnodes, table, 5, nil),
rounds: []round{ rounds: []round{
// 2 dynamic dials attempted, bootnodes pending fallback interval // 2 dynamic dials attempted, bootnodes pending fallback interval
{ {
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
&discoverTask{}, &discoverTask{},
}, },
}, },
// No dials succeed, bootnodes still pending fallback interval // No dials succeed, bootnodes still pending fallback interval
{ {
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
}, },
// No dials succeed, bootnodes still pending fallback interval // No dials succeed, bootnodes still pending fallback interval
@ -258,51 +254,54 @@ func TestDialStateDynDialBootnode(t *testing.T) {
// No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached // No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached
{ {
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
}, },
// No dials succeed, 2nd bootnode is attempted // No dials succeed, 2nd bootnode is attempted
{ {
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
}, },
// No dials succeed, 3rd bootnode is attempted // No dials succeed, 3rd bootnode is attempted
{ {
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
}, },
// No dials succeed, 1st bootnode is attempted again, expired random nodes retried // No dials succeed, 1st bootnode is attempted again, expired random nodes retried
{ {
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
}, },
// Random dial succeeds, no more bootnodes are attempted // Random dial succeeds, no more bootnodes are attempted
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
},
new: []task{
&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
}, },
}, },
}, },
@ -313,79 +312,83 @@ func TestDialStateDynDialFromTable(t *testing.T) {
// This table always returns the same random nodes // This table always returns the same random nodes
// in the order given below. // in the order given below.
table := fakeTable{ table := fakeTable{
newNode(uintID(1), nil), {ID: uintID(1)},
newNode(uintID(2), nil), {ID: uintID(2)},
newNode(uintID(3), nil), {ID: uintID(3)},
newNode(uintID(4), nil), {ID: uintID(4)},
newNode(uintID(5), nil), {ID: uintID(5)},
newNode(uintID(6), nil), {ID: uintID(6)},
newNode(uintID(7), nil), {ID: uintID(7)},
newNode(uintID(8), nil), {ID: uintID(8)},
} }
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, nil, nil, table, 10, nil), init: newDialState(nil, nil, table, 10, nil),
rounds: []round{ rounds: []round{
// 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed.
{ {
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
&discoverTask{}, &discoverTask{},
}, },
}, },
// Dialing nodes 1,2 succeeds. Dials from the lookup are launched. // Dialing nodes 1,2 succeeds. Dials from the lookup are launched.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
&discoverTask{results: []*enode.Node{ &discoverTask{results: []*discover.Node{
newNode(uintID(10), nil), {ID: uintID(10)},
newNode(uintID(11), nil), {ID: uintID(11)},
newNode(uintID(12), nil), {ID: uintID(12)},
}}, }},
}, },
new: []task{ new: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
&discoverTask{}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
}, },
}, },
// Dialing nodes 3,4,5 fails. The dials from the lookup succeed. // Dialing nodes 3,4,5 fails. The dials from the lookup succeed.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
}, },
done: []task{ done: []task{
&dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
&dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
},
new: []task{
&discoverTask{},
}, },
}, },
// Waiting for expiry. No waitExpireTask is launched because the // Waiting for expiry. No waitExpireTask is launched because the
// discovery query is still running. // discovery query is still running.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
}, },
}, },
// Nodes 3,4 are not tried again because only the first two // Nodes 3,4 are not tried again because only the first two
@ -393,44 +396,36 @@ func TestDialStateDynDialFromTable(t *testing.T) {
// already connected. // already connected.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
}, },
}, },
}, },
}) })
} }
func newNode(id enode.ID, ip net.IP) *enode.Node {
var r enr.Record
if ip != nil {
r.Set(enr.IP(ip))
}
return enode.SignNull(&r, id)
}
// This test checks that candidates that do not match the netrestrict list are not dialed. // This test checks that candidates that do not match the netrestrict list are not dialed.
func TestDialStateNetRestrict(t *testing.T) { func TestDialStateNetRestrict(t *testing.T) {
// This table always returns the same random nodes // This table always returns the same random nodes
// in the order given below. // in the order given below.
table := fakeTable{ table := fakeTable{
newNode(uintID(1), net.ParseIP("127.0.0.1")), {ID: uintID(1), IP: net.ParseIP("127.0.0.1")},
newNode(uintID(2), net.ParseIP("127.0.0.2")), {ID: uintID(2), IP: net.ParseIP("127.0.0.2")},
newNode(uintID(3), net.ParseIP("127.0.0.3")), {ID: uintID(3), IP: net.ParseIP("127.0.0.3")},
newNode(uintID(4), net.ParseIP("127.0.0.4")), {ID: uintID(4), IP: net.ParseIP("127.0.0.4")},
newNode(uintID(5), net.ParseIP("127.0.2.5")), {ID: uintID(5), IP: net.ParseIP("127.0.2.5")},
newNode(uintID(6), net.ParseIP("127.0.2.6")), {ID: uintID(6), IP: net.ParseIP("127.0.2.6")},
newNode(uintID(7), net.ParseIP("127.0.2.7")), {ID: uintID(7), IP: net.ParseIP("127.0.2.7")},
newNode(uintID(8), net.ParseIP("127.0.2.8")), {ID: uintID(8), IP: net.ParseIP("127.0.2.8")},
} }
restrict := new(netutil.Netlist) restrict := new(netutil.Netlist)
restrict.Add("127.0.2.0/24") restrict.Add("127.0.2.0/24")
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, nil, nil, table, 10, restrict), init: newDialState(nil, nil, table, 10, restrict),
rounds: []round{ rounds: []round{
{ {
new: []task{ new: []task{
@ -444,82 +439,85 @@ func TestDialStateNetRestrict(t *testing.T) {
// This test checks that static dials are launched. // This test checks that static dials are launched.
func TestDialStateStaticDial(t *testing.T) { func TestDialStateStaticDial(t *testing.T) {
wantStatic := []*enode.Node{ wantStatic := []*discover.Node{
newNode(uintID(1), nil), {ID: uintID(1)},
newNode(uintID(2), nil), {ID: uintID(2)},
newNode(uintID(3), nil), {ID: uintID(3)},
newNode(uintID(4), nil), {ID: uintID(4)},
newNode(uintID(5), nil), {ID: uintID(5)},
} }
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil), init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
rounds: []round{ rounds: []round{
// Static dials are launched for the nodes that // Static dials are launched for the nodes that
// aren't yet connected. // aren't yet connected.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
new: []task{ new: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
}, },
// No new tasks are launched in this round because all static // No new tasks are launched in this round because all static
// nodes are either connected or still being dialed. // nodes are either connected or still being dialed.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
},
new: []task{
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
done: []task{ done: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
}, },
// No new dial tasks are launched because all static // No new dial tasks are launched because all static
// nodes are now connected. // nodes are now connected.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(4)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
}, },
done: []task{ done: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
new: []task{ new: []task{
&waitExpireTask{Duration: 14 * time.Second}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
}, },
}, },
// Wait a round for dial history to expire, no new tasks should spawn. // Wait a round for dial history to expire, no new tasks should spawn.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(4)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
}, },
}, },
// If a static node is dropped, it should be immediately redialed, // If a static node is dropped, it should be immediately redialed,
// irrespective whether it was originally static or dynamic. // irrespective whether it was originally static or dynamic.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
},
new: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)},
}, },
new: []task{},
}, },
}, },
}) })
@ -527,9 +525,9 @@ func TestDialStateStaticDial(t *testing.T) {
// This test checks that static peers will be redialed immediately if they were re-added to a static list. // This test checks that static peers will be redialed immediately if they were re-added to a static list.
func TestDialStaticAfterReset(t *testing.T) { func TestDialStaticAfterReset(t *testing.T) {
wantStatic := []*enode.Node{ wantStatic := []*discover.Node{
newNode(uintID(1), nil), {ID: uintID(1)},
newNode(uintID(2), nil), {ID: uintID(2)},
} }
rounds := []round{ rounds := []round{
@ -537,100 +535,104 @@ func TestDialStaticAfterReset(t *testing.T) {
{ {
peers: nil, peers: nil,
new: []task{ new: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
}, },
// No new dial tasks, all peers are connected. // No new dial tasks, all peers are connected.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(1)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(2)}},
}, },
done: []task{ done: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
new: []task{ new: []task{
&waitExpireTask{Duration: 30 * time.Second}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
}, },
} }
dTest := dialtest{ dTest := dialtest{
init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil), init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
rounds: rounds, rounds: rounds,
} }
runDialTest(t, dTest) runDialTest(t, dTest)
for _, n := range wantStatic { for _, n := range wantStatic {
dTest.init.removeStatic(n) dTest.init.removeStatic(n)
dTest.init.addStatic(n) dTest.init.addStatic(n)
delete(dTest.init.dialing, n.ID)
} }
// without removing peers they will be considered recently dialed // without removing peers they will be considered recently dialed
runDialTest(t, dTest) runDialTest(t, dTest)
} }
// This test checks that past dials are not retried for some time. // This test checks that past dials are not retried for some time.
func TestDialStateCache(t *testing.T) { func TestDialStateCache(t *testing.T) {
wantStatic := []*enode.Node{ wantStatic := []*discover.Node{
newNode(uintID(1), nil), {ID: uintID(1)},
newNode(uintID(2), nil), {ID: uintID(2)},
newNode(uintID(3), nil), {ID: uintID(3)},
} }
runDialTest(t, dialtest{ runDialTest(t, dialtest{
init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil), init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
rounds: []round{ rounds: []round{
// Static dials are launched for the nodes that // Static dials are launched for the nodes that
// aren't yet connected. // aren't yet connected.
{ {
peers: nil, peers: nil,
new: []task{ new: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
}, },
// No new tasks are launched in this round because all static // No new tasks are launched in this round because all static
// nodes are either connected or still being dialed. // nodes are either connected or still being dialed.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(1)}},
{rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: staticDialedConn, id: uintID(2)}},
}, },
done: []task{ done: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
},
new: []task{
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
}, },
}, },
// A salvage task is launched to wait for node 3's history // A salvage task is launched to wait for node 3's history
// entry to expire. // entry to expire.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
done: []task{ done: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
},
new: []task{
&waitExpireTask{Duration: 14 * time.Second},
}, },
}, },
// Still waiting for node 3's entry to expire in the cache. // Still waiting for node 3's entry to expire in the cache.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
}, },
// The cache entry for node 3 has expired and is retried. // The cache entry for node 3 has expired and is retried.
{ {
peers: []*Peer{ peers: []*Peer{
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
{rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
}, },
new: []task{ new: []task{
&dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
}, },
}, },
}, },
@ -638,12 +640,12 @@ func TestDialStateCache(t *testing.T) {
} }
func TestDialResolve(t *testing.T) { func TestDialResolve(t *testing.T) {
resolved := newNode(uintID(1), net.IP{127, 0, 55, 234}) resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
table := &resolveMock{answer: resolved} table := &resolveMock{answer: resolved}
state := newDialState(enode.ID{}, nil, nil, table, 0, nil) state := newDialState(nil, nil, table, 0, nil)
// Check that the task is generated with an incomplete ID. // Check that the task is generated with an incomplete ID.
dest := newNode(uintID(1), nil) dest := discover.NewNode(uintID(1), nil, 0, 0)
state.addStatic(dest) state.addStatic(dest)
tasks := state.newTasks(0, nil, time.Time{}) tasks := state.newTasks(0, nil, time.Time{})
if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) { if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) {
@ -654,7 +656,7 @@ func TestDialResolve(t *testing.T) {
config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}} config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}}
srv := &Server{ntab: table, Config: config} srv := &Server{ntab: table, Config: config}
tasks[0].Do(srv) tasks[0].Do(srv)
if !reflect.DeepEqual(table.resolveCalls, []*enode.Node{dest}) { if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) {
t.Fatalf("wrong resolve calls, got %v", table.resolveCalls) t.Fatalf("wrong resolve calls, got %v", table.resolveCalls)
} }
@ -682,25 +684,25 @@ next:
return true return true
} }
func uintID(i uint32) enode.ID { func uintID(i uint32) discover.NodeID {
var id enode.ID var id discover.NodeID
binary.BigEndian.PutUint32(id[:], i) binary.BigEndian.PutUint32(id[:], i)
return id return id
} }
// implements discoverTable for TestDialResolve // implements discoverTable for TestDialResolve
type resolveMock struct { type resolveMock struct {
resolveCalls []*enode.Node resolveCalls []discover.NodeID
answer *enode.Node answer *discover.Node
} }
func (t *resolveMock) Resolve(n *enode.Node) *enode.Node { func (t *resolveMock) Resolve(id discover.NodeID) *discover.Node {
t.resolveCalls = append(t.resolveCalls, n) t.resolveCalls = append(t.resolveCalls, id)
return t.answer return t.answer
} }
func (t *resolveMock) Self() *enode.Node { return new(enode.Node) } func (t *resolveMock) Self() *discover.Node { return new(discover.Node) }
func (t *resolveMock) Close() {} func (t *resolveMock) Close() {}
func (t *resolveMock) LookupRandom() []*enode.Node { return nil } func (t *resolveMock) Bootstrap([]*discover.Node) {}
func (t *resolveMock) ReadRandomNodes(buf []*enode.Node) int { return 0 } func (t *resolveMock) Lookup(discover.NodeID) []*discover.Node { return nil }
func (t *resolveMock) Bootstrap([]*enode.Node) {} func (t *resolveMock) ReadRandomNodes(buf []*discover.Node) int { return 0 }

View file

@ -14,17 +14,20 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode // Contains the node database, storing previously seen nodes and any collected
// metadata about them for QoS purposes.
package discover
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt"
"os" "os"
"sync" "sync"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
@ -35,55 +38,58 @@ import (
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
// Keys in the node database.
const (
dbVersionKey = "version" // Version of the database to flush if changes
dbItemPrefix = "n:" // Identifier to prefix node entries with
dbDiscoverRoot = ":discover"
dbDiscoverSeq = dbDiscoverRoot + ":seq"
dbDiscoverPing = dbDiscoverRoot + ":lastping"
dbDiscoverPong = dbDiscoverRoot + ":lastpong"
dbDiscoverFindFails = dbDiscoverRoot + ":findfail"
dbLocalRoot = ":local"
dbLocalSeq = dbLocalRoot + ":seq"
)
var ( var (
dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element.
dbCleanupCycle = time.Hour // Time period for running the expiration task. nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
dbVersion = 7 nodeDBCleanupCycle = time.Hour // Time period for running the expiration task.
) )
// DB is the node database, storing previously seen nodes and any collected metadata about // nodeDB stores all nodes we know about.
// them for QoS purposes. type nodeDB struct {
type DB struct {
lvl *leveldb.DB // Interface to the database itself lvl *leveldb.DB // Interface to the database itself
self NodeID // Own node id to prevent adding it into the database
runner sync.Once // Ensures we can start at most one expirer runner sync.Once // Ensures we can start at most one expirer
quit chan struct{} // Channel to signal the expiring thread to stop quit chan struct{} // Channel to signal the expiring thread to stop
} }
// OpenDB opens a node database for storing and retrieving infos about known peers in the // Schema layout for the node database
// network. If no path is given an in-memory, temporary database is constructed. var (
func OpenDB(path string) (*DB, error) { nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with
nodeDBDiscoverRoot = ":discover"
nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
)
// newNodeDB creates a new node database for storing and retrieving infos about
// known peers in the network. If no path is given, an in-memory, temporary
// database is constructed.
func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
if path == "" { if path == "" {
return newMemoryDB() return newMemoryNodeDB(self)
} }
return newPersistentDB(path) return newPersistentNodeDB(path, version, self)
} }
// newMemoryNodeDB creates a new in-memory node database without a persistent backend. // newMemoryNodeDB creates a new in-memory node database without a persistent
func newMemoryDB() (*DB, error) { // backend.
func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
db, err := leveldb.Open(storage.NewMemStorage(), nil) db, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &DB{lvl: db, quit: make(chan struct{})}, nil return &nodeDB{
lvl: db,
self: self,
quit: make(chan struct{}),
}, nil
} }
// newPersistentNodeDB creates/opens a leveldb backed persistent node database, // newPersistentNodeDB creates/opens a leveldb backed persistent node database,
// also flushing its contents in case of a version mismatch. // also flushing its contents in case of a version mismatch.
func newPersistentDB(path string) (*DB, error) { func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
opts := &opt.Options{OpenFilesCacheCapacity: 5} opts := &opt.Options{OpenFilesCacheCapacity: 5}
db, err := leveldb.OpenFile(path, opts) db, err := leveldb.OpenFile(path, opts)
if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
@ -95,13 +101,13 @@ func newPersistentDB(path string) (*DB, error) {
// The nodes contained in the cache correspond to a certain protocol version. // The nodes contained in the cache correspond to a certain protocol version.
// Flush all nodes if the version doesn't match. // Flush all nodes if the version doesn't match.
currentVer := make([]byte, binary.MaxVarintLen64) currentVer := make([]byte, binary.MaxVarintLen64)
currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))] currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))]
blob, err := db.Get([]byte(dbVersionKey), nil) blob, err := db.Get(nodeDBVersionKey, nil)
switch err { switch err {
case leveldb.ErrNotFound: case leveldb.ErrNotFound:
// Version not found (i.e. empty cache), insert it // Version not found (i.e. empty cache), insert it
if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil { if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil {
db.Close() db.Close()
return nil, err return nil, err
} }
@ -113,37 +119,42 @@ func newPersistentDB(path string) (*DB, error) {
if err = os.RemoveAll(path); err != nil { if err = os.RemoveAll(path); err != nil {
return nil, err return nil, err
} }
return newPersistentDB(path) return newPersistentNodeDB(path, version, self)
} }
} }
return &DB{lvl: db, quit: make(chan struct{})}, nil return &nodeDB{
lvl: db,
self: self,
quit: make(chan struct{}),
}, nil
} }
// makeKey generates the leveldb key-blob from a node id and its particular // makeKey generates the leveldb key-blob from a node id and its particular
// field of interest. // field of interest.
func makeKey(id ID, field string) []byte { func makeKey(id NodeID, field string) []byte {
if (id == ID{}) { if bytes.Equal(id[:], nodeDBNilNodeID[:]) {
return []byte(field) return []byte(field)
} }
return append([]byte(dbItemPrefix), append(id[:], field...)...) return append(nodeDBItemPrefix, append(id[:], field...)...)
} }
// splitKey tries to split a database key into a node id and a field part. // splitKey tries to split a database key into a node id and a field part.
func splitKey(key []byte) (id ID, field string) { func splitKey(key []byte) (id NodeID, field string) {
// If the key is not of a node, return it plainly // If the key is not of a node, return it plainly
if !bytes.HasPrefix(key, []byte(dbItemPrefix)) { if !bytes.HasPrefix(key, nodeDBItemPrefix) {
return ID{}, string(key) return NodeID{}, string(key)
} }
// Otherwise split the id and field // Otherwise split the id and field
item := key[len(dbItemPrefix):] item := key[len(nodeDBItemPrefix):]
copy(id[:], item[:len(id)]) copy(id[:], item[:len(id)])
field = string(item[len(id):]) field = string(item[len(id):])
return id, field return id, field
} }
// fetchInt64 retrieves an integer associated with a particular key. // fetchInt64 retrieves an integer instance associated with a particular
func (db *DB) fetchInt64(key []byte) int64 { // database key.
func (db *nodeDB) fetchInt64(key []byte) int64 {
blob, err := db.lvl.Get(key, nil) blob, err := db.lvl.Get(key, nil)
if err != nil { if err != nil {
return 0 return 0
@ -155,80 +166,41 @@ func (db *DB) fetchInt64(key []byte) int64 {
return val return val
} }
// storeInt64 stores an integer in the given key. // storeInt64 update a specific database entry to the current time instance as a
func (db *DB) storeInt64(key []byte, n int64) error { // unix timestamp.
func (db *nodeDB) storeInt64(key []byte, n int64) error {
blob := make([]byte, binary.MaxVarintLen64) blob := make([]byte, binary.MaxVarintLen64)
blob = blob[:binary.PutVarint(blob, n)] blob = blob[:binary.PutVarint(blob, n)]
return db.lvl.Put(key, blob, nil) return db.lvl.Put(key, blob, nil)
} }
// fetchUint64 retrieves an integer associated with a particular key. // node retrieves a node with a given id from the database.
func (db *DB) fetchUint64(key []byte) uint64 { func (db *nodeDB) node(id NodeID) *Node {
blob, err := db.lvl.Get(key, nil) blob, err := db.lvl.Get(makeKey(id, nodeDBDiscoverRoot), nil)
if err != nil {
return 0
}
val, _ := binary.Uvarint(blob)
return val
}
// storeUint64 stores an integer in the given key.
func (db *DB) storeUint64(key []byte, n uint64) error {
blob := make([]byte, binary.MaxVarintLen64)
blob = blob[:binary.PutUvarint(blob, n)]
return db.lvl.Put(key, blob, nil)
}
// Node retrieves a node with a given id from the database.
func (db *DB) Node(id ID) *Node {
blob, err := db.lvl.Get(makeKey(id, dbDiscoverRoot), nil)
if err != nil { if err != nil {
return nil return nil
} }
return mustDecodeNode(id[:], blob)
}
func mustDecodeNode(id, data []byte) *Node {
node := new(Node) node := new(Node)
if err := rlp.DecodeBytes(data, &node.r); err != nil { if err := rlp.DecodeBytes(blob, node); err != nil {
panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err)) log.Error("Failed to decode node RLP", "err", err)
return nil
} }
// Restore node id cache. node.sha = crypto.Keccak256Hash(node.ID[:])
copy(node.id[:], id)
return node return node
} }
// UpdateNode inserts - potentially overwriting - a node into the peer database. // updateNode inserts - potentially overwriting - a node into the peer database.
func (db *DB) UpdateNode(node *Node) error { func (db *nodeDB) updateNode(node *Node) error {
if node.Seq() < db.NodeSeq(node.ID()) { blob, err := rlp.EncodeToBytes(node)
return nil
}
blob, err := rlp.EncodeToBytes(&node.r)
if err != nil { if err != nil {
return err return err
} }
if err := db.lvl.Put(makeKey(node.ID(), dbDiscoverRoot), blob, nil); err != nil { return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil)
return err
}
return db.storeUint64(makeKey(node.ID(), dbDiscoverSeq), node.Seq())
} }
// NodeSeq returns the stored record sequence number of the given node. // deleteNode deletes all information/keys associated with a node.
func (db *DB) NodeSeq(id ID) uint64 { func (db *nodeDB) deleteNode(id NodeID) error {
return db.fetchUint64(makeKey(id, dbDiscoverSeq))
}
// Resolve returns the stored record of the node if it has a larger sequence
// number than n.
func (db *DB) Resolve(n *Node) *Node {
if n.Seq() > db.NodeSeq(n.ID()) {
return n
}
return db.Node(n.ID())
}
// DeleteNode deletes all information/keys associated with a node.
func (db *DB) DeleteNode(id ID) error {
deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil) deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
for deleter.Next() { for deleter.Next() {
if err := db.lvl.Delete(deleter.Key(), nil); err != nil { if err := db.lvl.Delete(deleter.Key(), nil); err != nil {
@ -247,14 +219,14 @@ func (db *DB) DeleteNode(id ID) error {
// it would require significant overhead to exactly trace the first successful // it would require significant overhead to exactly trace the first successful
// convergence, it's simpler to "ensure" the correct state when an appropriate // convergence, it's simpler to "ensure" the correct state when an appropriate
// condition occurs (i.e. a successful bonding), and discard further events. // condition occurs (i.e. a successful bonding), and discard further events.
func (db *DB) ensureExpirer() { func (db *nodeDB) ensureExpirer() {
db.runner.Do(func() { go db.expirer() }) db.runner.Do(func() { go db.expirer() })
} }
// expirer should be started in a go routine, and is responsible for looping ad // expirer should be started in a go routine, and is responsible for looping ad
// infinitum and dropping stale data from the database. // infinitum and dropping stale data from the database.
func (db *DB) expirer() { func (db *nodeDB) expirer() {
tick := time.NewTicker(dbCleanupCycle) tick := time.NewTicker(nodeDBCleanupCycle)
defer tick.Stop() defer tick.Stop()
for { for {
select { select {
@ -270,8 +242,8 @@ func (db *DB) expirer() {
// expireNodes iterates over the database and deletes all nodes that have not // expireNodes iterates over the database and deletes all nodes that have not
// been seen (i.e. received a pong from) for some allotted time. // been seen (i.e. received a pong from) for some allotted time.
func (db *DB) expireNodes() error { func (db *nodeDB) expireNodes() error {
threshold := time.Now().Add(-dbNodeExpiration) threshold := time.Now().Add(-nodeDBNodeExpiration)
// Find discovered nodes that are older than the allowance // Find discovered nodes that are older than the allowance
it := db.lvl.NewIterator(nil, nil) it := db.lvl.NewIterator(nil, nil)
@ -280,70 +252,65 @@ func (db *DB) expireNodes() error {
for it.Next() { for it.Next() {
// Skip the item if not a discovery node // Skip the item if not a discovery node
id, field := splitKey(it.Key()) id, field := splitKey(it.Key())
if field != dbDiscoverRoot { if field != nodeDBDiscoverRoot {
continue continue
} }
// Skip the node if not expired yet (and not self) // Skip the node if not expired yet (and not self)
if seen := db.LastPongReceived(id); seen.After(threshold) { if !bytes.Equal(id[:], db.self[:]) {
continue if seen := db.bondTime(id); seen.After(threshold) {
continue
}
} }
// Otherwise delete all associated information // Otherwise delete all associated information
db.DeleteNode(id) db.deleteNode(id)
} }
return nil return nil
} }
// LastPingReceived retrieves the time of the last ping packet received from // lastPing retrieves the time of the last ping packet send to a remote node,
// a remote node. // requesting binding.
func (db *DB) LastPingReceived(id ID) time.Time { func (db *nodeDB) lastPing(id NodeID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPing)), 0) return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
} }
// UpdateLastPingReceived updates the last time we tried contacting a remote node. // updateLastPing updates the last time we tried contacting a remote node.
func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error { func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, dbDiscoverPing), instance.Unix()) return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
} }
// LastPongReceived retrieves the time of the last successful pong from remote node. // bondTime retrieves the time of the last successful pong from remote node.
func (db *DB) LastPongReceived(id ID) time.Time { func (db *nodeDB) bondTime(id NodeID) time.Time {
// Launch expirer return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
db.ensureExpirer()
return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPong)), 0)
} }
// UpdateLastPongReceived updates the last pong time of a node. // hasBond reports whether the given node is considered bonded.
func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error { func (db *nodeDB) hasBond(id NodeID) bool {
return db.storeInt64(makeKey(id, dbDiscoverPong), instance.Unix()) return time.Since(db.bondTime(id)) < nodeDBNodeExpiration
} }
// FindFails retrieves the number of findnode failures since bonding. // updateBondTime updates the last pong time of a node.
func (db *DB) FindFails(id ID) int { func (db *nodeDB) updateBondTime(id NodeID, instance time.Time) error {
return int(db.fetchInt64(makeKey(id, dbDiscoverFindFails))) return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
} }
// UpdateFindFails updates the number of findnode failures since bonding. // findFails retrieves the number of findnode failures since bonding.
func (db *DB) UpdateFindFails(id ID, fails int) error { func (db *nodeDB) findFails(id NodeID) int {
return db.storeInt64(makeKey(id, dbDiscoverFindFails), int64(fails)) return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
} }
// LocalSeq retrieves the local record sequence counter. // updateFindFails updates the number of findnode failures since bonding.
func (db *DB) localSeq(id ID) uint64 { func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
return db.fetchUint64(makeKey(id, dbLocalSeq)) return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
} }
// storeLocalSeq stores the local record sequence counter. // querySeeds retrieves random nodes to be used as potential seed nodes
func (db *DB) storeLocalSeq(id ID, n uint64) {
db.storeUint64(makeKey(id, dbLocalSeq), n)
}
// QuerySeeds retrieves random nodes to be used as potential seed nodes
// for bootstrapping. // for bootstrapping.
func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node { func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node {
var ( var (
now = time.Now() now = time.Now()
nodes = make([]*Node, 0, n) nodes = make([]*Node, 0, n)
it = db.lvl.NewIterator(nil, nil) it = db.lvl.NewIterator(nil, nil)
id ID id NodeID
) )
defer it.Release() defer it.Release()
@ -355,18 +322,21 @@ seek:
ctr := id[0] ctr := id[0]
rand.Read(id[:]) rand.Read(id[:])
id[0] = ctr + id[0]%16 id[0] = ctr + id[0]%16
it.Seek(makeKey(id, dbDiscoverRoot)) it.Seek(makeKey(id, nodeDBDiscoverRoot))
n := nextNode(it) n := nextNode(it)
if n == nil { if n == nil {
id[0] = 0 id[0] = 0
continue seek // iterator exhausted continue seek // iterator exhausted
} }
if now.Sub(db.LastPongReceived(n.ID())) > maxAge { if n.ID == db.self {
continue seek
}
if now.Sub(db.bondTime(n.ID)) > maxAge {
continue seek continue seek
} }
for i := range nodes { for i := range nodes {
if nodes[i].ID() == n.ID() { if nodes[i].ID == n.ID {
continue seek // duplicate continue seek // duplicate
} }
} }
@ -380,16 +350,21 @@ seek:
func nextNode(it iterator.Iterator) *Node { func nextNode(it iterator.Iterator) *Node {
for end := false; !end; end = !it.Next() { for end := false; !end; end = !it.Next() {
id, field := splitKey(it.Key()) id, field := splitKey(it.Key())
if field != dbDiscoverRoot { if field != nodeDBDiscoverRoot {
continue continue
} }
return mustDecodeNode(id[:], it.Value()) var n Node
if err := rlp.DecodeBytes(it.Value(), &n); err != nil {
log.Warn("Failed to decode node RLP", "id", id, "err", err)
continue
}
return &n
} }
return nil return nil
} }
// close flushes and closes the database files. // close flushes and closes the database files.
func (db *DB) Close() { func (db *nodeDB) close() {
close(db.quit) close(db.quit)
db.lvl.Close() db.lvl.Close()
} }

View file

@ -14,12 +14,10 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode package discover
import ( import (
"bytes" "bytes"
"fmt"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -29,21 +27,24 @@ import (
) )
var nodeDBKeyTests = []struct { var nodeDBKeyTests = []struct {
id ID id NodeID
field string field string
key []byte key []byte
}{ }{
{ {
id: ID{}, id: NodeID{},
field: "version", field: "version",
key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field
}, },
{ {
id: HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
field: ":discover", field: ":discover",
key: []byte{ key: []byte{0x6e, 0x3a, // prefix
0x6e, 0x3a, // prefix 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // node id
0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id 0x43, 0xd5, 0xad, 0x55, 0xa2, 0xee, 0x3f, 0x56, //
0xc6, 0xcb, 0xc1, 0xc6, 0x4a, 0x5c, 0x8d, 0x65, //
0x9f, 0x51, 0xfc, 0xd5, 0x1b, 0xac, 0xe2, 0x43, //
0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, //
0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
@ -52,7 +53,7 @@ var nodeDBKeyTests = []struct {
}, },
} }
func TestDBKeys(t *testing.T) { func TestNodeDBKeys(t *testing.T) {
for i, tt := range nodeDBKeyTests { for i, tt := range nodeDBKeyTests {
if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) { if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) {
t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key) t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key)
@ -76,9 +77,9 @@ var nodeDBInt64Tests = []struct {
{key: []byte{0x03}, value: 3}, {key: []byte{0x03}, value: 3},
} }
func TestDBInt64(t *testing.T) { func TestNodeDBInt64(t *testing.T) {
db, _ := OpenDB("") db, _ := newNodeDB("", Version, NodeID{})
defer db.Close() defer db.close()
tests := nodeDBInt64Tests tests := nodeDBInt64Tests
for i := 0; i < len(tests); i++ { for i := 0; i < len(tests); i++ {
@ -99,9 +100,9 @@ func TestDBInt64(t *testing.T) {
} }
} }
func TestDBFetchStore(t *testing.T) { func TestNodeDBFetchStore(t *testing.T) {
node := NewV4( node := NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{192, 168, 0, 1}, net.IP{192, 168, 0, 1},
30303, 30303,
30303, 30303,
@ -109,47 +110,47 @@ func TestDBFetchStore(t *testing.T) {
inst := time.Now() inst := time.Now()
num := 314 num := 314
db, _ := OpenDB("") db, _ := newNodeDB("", Version, NodeID{})
defer db.Close() defer db.close()
// Check fetch/store operations on a node ping object // Check fetch/store operations on a node ping object
if stored := db.LastPingReceived(node.ID()); stored.Unix() != 0 { if stored := db.lastPing(node.ID); stored.Unix() != 0 {
t.Errorf("ping: non-existing object: %v", stored) t.Errorf("ping: non-existing object: %v", stored)
} }
if err := db.UpdateLastPingReceived(node.ID(), inst); err != nil { if err := db.updateLastPing(node.ID, inst); err != nil {
t.Errorf("ping: failed to update: %v", err) t.Errorf("ping: failed to update: %v", err)
} }
if stored := db.LastPingReceived(node.ID()); stored.Unix() != inst.Unix() { if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node pong object // Check fetch/store operations on a node pong object
if stored := db.LastPongReceived(node.ID()); stored.Unix() != 0 { if stored := db.bondTime(node.ID); stored.Unix() != 0 {
t.Errorf("pong: non-existing object: %v", stored) t.Errorf("pong: non-existing object: %v", stored)
} }
if err := db.UpdateLastPongReceived(node.ID(), inst); err != nil { if err := db.updateBondTime(node.ID, inst); err != nil {
t.Errorf("pong: failed to update: %v", err) t.Errorf("pong: failed to update: %v", err)
} }
if stored := db.LastPongReceived(node.ID()); stored.Unix() != inst.Unix() { if stored := db.bondTime(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node findnode-failure object // Check fetch/store operations on a node findnode-failure object
if stored := db.FindFails(node.ID()); stored != 0 { if stored := db.findFails(node.ID); stored != 0 {
t.Errorf("find-node fails: non-existing object: %v", stored) t.Errorf("find-node fails: non-existing object: %v", stored)
} }
if err := db.UpdateFindFails(node.ID(), num); err != nil { if err := db.updateFindFails(node.ID, num); err != nil {
t.Errorf("find-node fails: failed to update: %v", err) t.Errorf("find-node fails: failed to update: %v", err)
} }
if stored := db.FindFails(node.ID()); stored != num { if stored := db.findFails(node.ID); stored != num {
t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
} }
// Check fetch/store operations on an actual node object // Check fetch/store operations on an actual node object
if stored := db.Node(node.ID()); stored != nil { if stored := db.node(node.ID); stored != nil {
t.Errorf("node: non-existing object: %v", stored) t.Errorf("node: non-existing object: %v", stored)
} }
if err := db.UpdateNode(node); err != nil { if err := db.updateNode(node); err != nil {
t.Errorf("node: failed to update: %v", err) t.Errorf("node: failed to update: %v", err)
} }
if stored := db.Node(node.ID()); stored == nil { if stored := db.node(node.ID); stored == nil {
t.Errorf("node: not found") t.Errorf("node: not found")
} else if !reflect.DeepEqual(stored, node) { } else if !reflect.DeepEqual(stored, node) {
t.Errorf("node: data mismatch: have %v, want %v", stored, node) t.Errorf("node: data mismatch: have %v, want %v", stored, node)
@ -163,8 +164,8 @@ var nodeDBSeedQueryNodes = []struct {
// This one should not be in the result set because its last // This one should not be in the result set because its last
// pong time is too far in the past. // pong time is too far in the past.
{ {
node: NewV4( node: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 3}, net.IP{127, 0, 0, 3},
30303, 30303,
30303, 30303,
@ -174,8 +175,8 @@ var nodeDBSeedQueryNodes = []struct {
// This one shouldn't be in in the result set because its // This one shouldn't be in in the result set because its
// nodeID is the local node's ID. // nodeID is the local node's ID.
{ {
node: NewV4( node: NewNode(
hexPubkey("ff93ff820abacd4351b0f14e47b324bc82ff014c226f3f66a53535734a3c150e7e38ca03ef0964ba55acddc768f5e99cd59dea95ddd4defbab1339c92fa319b2"), MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 3}, net.IP{127, 0, 0, 3},
30303, 30303,
30303, 30303,
@ -185,8 +186,8 @@ var nodeDBSeedQueryNodes = []struct {
// These should be in the result set. // These should be in the result set.
{ {
node: NewV4( node: NewNode(
hexPubkey("c2b5eb3f5dde05f815b63777809ee3e7e0cbb20035a6b00ce327191e6eaa8f26a8d461c9112b7ab94698e7361fa19fd647e603e73239002946d76085b6f928d6"), MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 1}, net.IP{127, 0, 0, 1},
30303, 30303,
30303, 30303,
@ -194,8 +195,8 @@ var nodeDBSeedQueryNodes = []struct {
pong: time.Now().Add(-2 * time.Second), pong: time.Now().Add(-2 * time.Second),
}, },
{ {
node: NewV4( node: NewNode(
hexPubkey("6ca1d400c8ddf8acc94bcb0dd254911ad71a57bed5e0ae5aa205beed59b28c2339908e97990c493499613cff8ecf6c3dc7112a8ead220cdcd00d8847ca3db755"), MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 2}, net.IP{127, 0, 0, 2},
30303, 30303,
30303, 30303,
@ -203,92 +204,57 @@ var nodeDBSeedQueryNodes = []struct {
pong: time.Now().Add(-3 * time.Second), pong: time.Now().Add(-3 * time.Second),
}, },
{ {
node: NewV4( node: NewNode(
hexPubkey("234dc63fe4d131212b38236c4c3411288d7bec61cbf7b120ff12c43dc60c96182882f4291d209db66f8a38e986c9c010ff59231a67f9515c7d1668b86b221a47"), MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 3}, net.IP{127, 0, 0, 3},
30303, 30303,
30303, 30303,
), ),
pong: time.Now().Add(-1 * time.Second), pong: time.Now().Add(-1 * time.Second),
}, },
{
node: NewV4(
hexPubkey("c013a50b4d1ebce5c377d8af8cb7114fd933ffc9627f96ad56d90fef5b7253ec736fd07ef9a81dc2955a997e54b7bf50afd0aa9f110595e2bec5bb7ce1657004"),
net.IP{127, 0, 0, 3},
30303,
30303,
),
pong: time.Now().Add(-2 * time.Second),
},
{
node: NewV4(
hexPubkey("f141087e3e08af1aeec261ff75f48b5b1637f594ea9ad670e50051646b0416daa3b134c28788cbe98af26992a47652889cd8577ccc108ac02c6a664db2dc1283"),
net.IP{127, 0, 0, 3},
30303,
30303,
),
pong: time.Now().Add(-2 * time.Second),
},
} }
func TestDBSeedQuery(t *testing.T) { func TestNodeDBSeedQuery(t *testing.T) {
// Querying seeds uses seeks an might not find all nodes db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID)
// every time when the database is small. Run the test multiple defer db.close()
// times to avoid flakes.
const attempts = 15
var err error
for i := 0; i < attempts; i++ {
if err = testSeedQuery(); err == nil {
return
}
}
if err != nil {
t.Errorf("no successful run in %d attempts: %v", attempts, err)
}
}
func testSeedQuery() error {
db, _ := OpenDB("")
defer db.Close()
// Insert a batch of nodes for querying // Insert a batch of nodes for querying
for i, seed := range nodeDBSeedQueryNodes { for i, seed := range nodeDBSeedQueryNodes {
if err := db.UpdateNode(seed.node); err != nil { if err := db.updateNode(seed.node); err != nil {
return fmt.Errorf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil {
return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) t.Fatalf("node %d: failed to insert bondTime: %v", i, err)
} }
} }
// Retrieve the entire batch and check for duplicates // Retrieve the entire batch and check for duplicates
seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour)
have := make(map[ID]struct{}) have := make(map[NodeID]struct{})
for _, seed := range seeds { for _, seed := range seeds {
have[seed.ID()] = struct{}{} have[seed.ID] = struct{}{}
} }
want := make(map[ID]struct{}) want := make(map[NodeID]struct{})
for _, seed := range nodeDBSeedQueryNodes[1:] { for _, seed := range nodeDBSeedQueryNodes[2:] {
want[seed.node.ID()] = struct{}{} want[seed.node.ID] = struct{}{}
} }
if len(seeds) != len(want) { if len(seeds) != len(want) {
return fmt.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want))
} }
for id := range have { for id := range have {
if _, ok := want[id]; !ok { if _, ok := want[id]; !ok {
return fmt.Errorf("extra seed: %v", id) t.Errorf("extra seed: %v", id)
} }
} }
for id := range want { for id := range want {
if _, ok := have[id]; !ok { if _, ok := have[id]; !ok {
return fmt.Errorf("missing seed: %v", id) t.Errorf("missing seed: %v", id)
} }
} }
return nil
} }
func TestDBPersistency(t *testing.T) { func TestNodeDBPersistency(t *testing.T) {
root, err := ioutil.TempDir("", "nodedb-") root, err := os.MkdirTemp("", "nodedb-")
if err != nil { if err != nil {
t.Fatalf("failed to create temporary data folder: %v", err) t.Fatalf("failed to create temporary data folder: %v", err)
} }
@ -300,24 +266,34 @@ func TestDBPersistency(t *testing.T) {
) )
// Create a persistent database and store some values // Create a persistent database and store some values
db, err := OpenDB(filepath.Join(root, "database")) db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{})
if err != nil { if err != nil {
t.Fatalf("failed to create persistent database: %v", err) t.Fatalf("failed to create persistent database: %v", err)
} }
if err := db.storeInt64(testKey, testInt); err != nil { if err := db.storeInt64(testKey, testInt); err != nil {
t.Fatalf("failed to store value: %v.", err) t.Fatalf("failed to store value: %v.", err)
} }
db.Close() db.close()
// Reopen the database and check the value // Reopen the database and check the value
db, err = OpenDB(filepath.Join(root, "database")) db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{})
if err != nil { if err != nil {
t.Fatalf("failed to open persistent database: %v", err) t.Fatalf("failed to open persistent database: %v", err)
} }
if val := db.fetchInt64(testKey); val != testInt { if val := db.fetchInt64(testKey); val != testInt {
t.Fatalf("value mismatch: have %v, want %v", val, testInt) t.Fatalf("value mismatch: have %v, want %v", val, testInt)
} }
db.Close() db.close()
// Change the database version and check flush
db, err = newNodeDB(filepath.Join(root, "database"), Version+1, NodeID{})
if err != nil {
t.Fatalf("failed to open persistent database: %v", err)
}
if val := db.fetchInt64(testKey); val != 0 {
t.Fatalf("value mismatch: have %v, want %v", val, 0)
}
db.close()
} }
var nodeDBExpirationNodes = []struct { var nodeDBExpirationNodes = []struct {
@ -326,36 +302,36 @@ var nodeDBExpirationNodes = []struct {
exp bool exp bool
}{ }{
{ {
node: NewV4( node: NewNode(
hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"), MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 1}, net.IP{127, 0, 0, 1},
30303, 30303,
30303, 30303,
), ),
pong: time.Now().Add(-dbNodeExpiration + time.Minute), pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute),
exp: false, exp: false,
}, { }, {
node: NewV4( node: NewNode(
hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"), MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 2}, net.IP{127, 0, 0, 2},
30303, 30303,
30303, 30303,
), ),
pong: time.Now().Add(-dbNodeExpiration - time.Minute), pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute),
exp: true, exp: true,
}, },
} }
func TestDBExpiration(t *testing.T) { func TestNodeDBExpiration(t *testing.T) {
db, _ := OpenDB("") db, _ := newNodeDB("", Version, NodeID{})
defer db.Close() defer db.close()
// Add all the test nodes and set their last pong time // Add all the test nodes and set their last pong time
for i, seed := range nodeDBExpirationNodes { for i, seed := range nodeDBExpirationNodes {
if err := db.UpdateNode(seed.node); err != nil { if err := db.updateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err) t.Fatalf("node %d: failed to update bondTime: %v", i, err)
} }
} }
@ -364,9 +340,40 @@ func TestDBExpiration(t *testing.T) {
t.Fatalf("failed to expire nodes: %v", err) t.Fatalf("failed to expire nodes: %v", err)
} }
for i, seed := range nodeDBExpirationNodes { for i, seed := range nodeDBExpirationNodes {
node := db.Node(seed.node.ID()) node := db.node(seed.node.ID)
if (node == nil && !seed.exp) || (node != nil && seed.exp) { if (node == nil && !seed.exp) || (node != nil && seed.exp) {
t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp) t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp)
} }
} }
} }
func TestNodeDBSelfExpiration(t *testing.T) {
// Find a node in the tests that shouldn't expire, and assign it as self
var self NodeID
for _, node := range nodeDBExpirationNodes {
if !node.exp {
self = node.node.ID
break
}
}
db, _ := newNodeDB("", Version, self)
defer db.close()
// Add all the test nodes and set their last pong time
for i, seed := range nodeDBExpirationNodes {
if err := db.updateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err)
}
if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err)
}
}
// Expire the nodes and make sure self has been evacuated too
if err := db.expireNodes(); err != nil {
t.Fatalf("failed to expire nodes: %v", err)
}
node := db.node(self)
if node != nil {
t.Errorf("self not evacuated")
}
}

View file

@ -18,87 +18,415 @@ package discover
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic"
"encoding/hex"
"errors" "errors"
"fmt"
"math/big" "math/big"
"math/rand"
"net" "net"
"net/url"
"regexp"
"strconv"
"strings"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/secp256k1" "github.com/XinFinOrg/XDPoSChain/crypto/secp256k1"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
) )
// node represents a host on the network. const NodeIDBits = 512
// Node represents a host on the network.
// The fields of Node may not be modified. // The fields of Node may not be modified.
type node struct { type Node struct {
enode.Node IP net.IP // len 4 for IPv4 or 16 for IPv6
addedAt time.Time // time when the node was added to the table UDP, TCP uint16 // port numbers
ID NodeID // the node's public key
// This is a cached copy of sha3(ID) which is used for node
// distance calculations. This is part of Node in order to make it
// possible to write tests that need a node at a certain distance.
// In those tests, the content of sha will not actually correspond
// with ID.
sha common.Hash
// Time when the node was added to the table.
addedAt time.Time
} }
type encPubkey [64]byte // NewNode creates a new node. It is mostly meant to be used for
// testing purposes.
func encodePubkey(key *ecdsa.PublicKey) encPubkey { func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node {
var e encPubkey if ipv4 := ip.To4(); ipv4 != nil {
math.ReadBits(key.X, e[:len(e)/2]) ip = ipv4
math.ReadBits(key.Y, e[len(e)/2:]) }
return e return &Node{
IP: ip,
UDP: udpPort,
TCP: tcpPort,
ID: id,
sha: crypto.Keccak256Hash(id[:]),
}
} }
func decodePubkey(e encPubkey) (*ecdsa.PublicKey, error) { func (n *Node) addr() *net.UDPAddr {
return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
}
// Incomplete returns true for nodes with no IP address.
func (n *Node) Incomplete() bool {
return n.IP == nil
}
// checks whether n is a valid complete node.
func (n *Node) validateComplete() error {
if n.Incomplete() {
return errors.New("incomplete node")
}
if n.UDP == 0 {
return errors.New("missing UDP port")
}
if n.TCP == 0 {
return errors.New("missing TCP port")
}
if n.IP.IsMulticast() || n.IP.IsUnspecified() {
return errors.New("invalid IP (multicast/unspecified)")
}
_, err := n.ID.Pubkey() // validate the key (on curve, etc.)
return err
}
// The string representation of a Node is a URL.
// Please see ParseNode for a description of the format.
func (n *Node) String() string {
u := url.URL{Scheme: "enode"}
if n.Incomplete() {
u.Host = fmt.Sprintf("%x", n.ID[:])
} else {
addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
u.User = url.User(fmt.Sprintf("%x", n.ID[:]))
u.Host = addr.String()
if n.UDP != n.TCP {
u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
}
}
return u.String()
}
var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
// ParseNode parses a node designator.
//
// There are two basic forms of node designators
// - incomplete nodes, which only have the public key (node ID)
// - complete nodes, which contain the public key and IP/Port information
//
// For incomplete nodes, the designator must look like one of these
//
// enode://<hex node id>
// <hex node id>
//
// For complete nodes, the node ID is encoded in the username portion
// of the URL, separated from the host by an @ sign. The hostname can
// only be given as an IP address, DNS domain names are not allowed.
// The port in the host name section is the TCP listening port. If the
// TCP and UDP (discovery) ports differ, the UDP port is specified as
// query parameter "discport".
//
// In the following example, the node URL describes
// a node with IP address 10.3.58.6, TCP listening port 30303
// and UDP discovery port 30301.
//
// enode://<hex node id>@10.3.58.6:30303?discport=30301
func ParseNode(rawurl string) (*Node, error) {
if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
id, err := HexID(m[1])
if err != nil {
return nil, fmt.Errorf("invalid node ID (%v)", err)
}
return NewNode(id, nil, 0, 0), nil
}
return parseComplete(rawurl)
}
func parseComplete(rawurl string) (*Node, error) {
var (
id NodeID
ip net.IP
tcpPort, udpPort uint64
)
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
if u.Scheme != "enode" {
return nil, errors.New("invalid URL scheme, want \"enode\"")
}
// Parse the Node ID from the user portion.
if u.User == nil {
return nil, errors.New("does not contain node ID")
}
if id, err = HexID(u.User.String()); err != nil {
return nil, fmt.Errorf("invalid node ID (%v)", err)
}
// Parse the IP address.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return nil, fmt.Errorf("invalid host: %v", err)
}
if ip = net.ParseIP(host); ip == nil {
return nil, errors.New("invalid IP address")
}
// Ensure the IP is 4 bytes long for IPv4 addresses.
if ipv4 := ip.To4(); ipv4 != nil {
ip = ipv4
}
// Parse the port numbers.
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
return nil, errors.New("invalid port")
}
udpPort = tcpPort
qv := u.Query()
if qv.Get("discport") != "" {
udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
if err != nil {
return nil, errors.New("invalid discport in query")
}
}
return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil
}
// MustParseNode parses a node URL. It panics if the URL is not valid.
func MustParseNode(rawurl string) *Node {
n, err := ParseNode(rawurl)
if err != nil {
panic("invalid node URL: " + err.Error())
}
return n
}
// MarshalText implements encoding.TextMarshaler.
func (n *Node) MarshalText() ([]byte, error) {
return []byte(n.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (n *Node) UnmarshalText(text []byte) error {
dec, err := ParseNode(string(text))
if err == nil {
*n = *dec
}
return err
}
// NodeID is a unique identifier for each node.
// The node identifier is a marshaled elliptic curve public key.
type NodeID [NodeIDBits / 8]byte
// Bytes returns a byte slice representation of the NodeID
func (n NodeID) Bytes() []byte {
return n[:]
}
// NodeID prints as a long hexadecimal number.
func (n NodeID) String() string {
return fmt.Sprintf("%x", n[:])
}
// The Go syntax representation of a NodeID is a call to HexID.
func (n NodeID) GoString() string {
return fmt.Sprintf("discover.HexID(\"%x\")", n[:])
}
// TerminalString returns a shortened hex string for terminal logging.
func (n NodeID) TerminalString() string {
return hex.EncodeToString(n[:8])
}
// MarshalText implements the encoding.TextMarshaler interface.
func (n NodeID) MarshalText() ([]byte, error) {
return []byte(hex.EncodeToString(n[:])), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (n *NodeID) UnmarshalText(text []byte) error {
id, err := HexID(string(text))
if err != nil {
return err
}
*n = id
return nil
}
// BytesID converts a byte slice to a NodeID
func BytesID(b []byte) (NodeID, error) {
var id NodeID
if len(b) != len(id) {
return id, fmt.Errorf("wrong length, want %d bytes", len(id))
}
copy(id[:], b)
return id, nil
}
// MustBytesID converts a byte slice to a NodeID.
// It panics if the byte slice is not a valid NodeID.
func MustBytesID(b []byte) NodeID {
id, err := BytesID(b)
if err != nil {
panic(err)
}
return id
}
// HexID converts a hex string to a NodeID.
// The string may be prefixed with 0x.
func HexID(in string) (NodeID, error) {
var id NodeID
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
if err != nil {
return id, err
} else if len(b) != len(id) {
return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
}
copy(id[:], b)
return id, nil
}
// MustHexID converts a hex string to a NodeID.
// It panics if the string is not a valid NodeID.
func MustHexID(in string) NodeID {
id, err := HexID(in)
if err != nil {
panic(err)
}
return id
}
// PubkeyID returns a marshaled representation of the given public key.
func PubkeyID(pub *ecdsa.PublicKey) NodeID {
var id NodeID
pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
if len(pbytes)-1 != len(id) {
panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes)))
}
copy(id[:], pbytes[1:])
return id
}
// Pubkey returns the public key represented by the node ID.
// It returns an error if the ID is not a point on the curve.
func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) {
p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
half := len(e) / 2 half := len(id) / 2
p.X.SetBytes(e[:half]) p.X.SetBytes(id[:half])
p.Y.SetBytes(e[half:]) p.Y.SetBytes(id[half:])
if !p.Curve.IsOnCurve(p.X, p.Y) { if !p.Curve.IsOnCurve(p.X, p.Y) {
return nil, errors.New("invalid secp256k1 curve point") return nil, errors.New("id is invalid secp256k1 curve point")
} }
return p, nil return p, nil
} }
func (e encPubkey) id() enode.ID { // recoverNodeID computes the public key used to sign the
return enode.ID(crypto.Keccak256Hash(e[:]))
}
// recoverNodeKey computes the public key used to sign the
// given hash from the signature. // given hash from the signature.
func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) { func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
pubkey, err := secp256k1.RecoverPubkey(hash, sig) pubkey, err := secp256k1.RecoverPubkey(hash, sig)
if err != nil { if err != nil {
return key, err return id, err
} }
copy(key[:], pubkey[1:]) if len(pubkey)-1 != len(id) {
return key, nil return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8)
}
func wrapNode(n *enode.Node) *node {
return &node{Node: *n}
}
func wrapNodes(ns []*enode.Node) []*node {
result := make([]*node, len(ns))
for i, n := range ns {
result[i] = wrapNode(n)
} }
return result for i := range id {
} id[i] = pubkey[i+1]
func unwrapNode(n *node) *enode.Node {
return &n.Node
}
func unwrapNodes(ns []*node) []*enode.Node {
result := make([]*enode.Node, len(ns))
for i, n := range ns {
result[i] = unwrapNode(n)
} }
return result return id, nil
} }
func (n *node) addr() *net.UDPAddr { // distcmp compares the distances a->target and b->target.
return &net.UDPAddr{IP: n.IP(), Port: n.UDP()} // Returns -1 if a is closer to target, 1 if b is closer to target
// and 0 if they are equal.
func distcmp(target, a, b common.Hash) int {
for i := range target {
da := a[i] ^ target[i]
db := b[i] ^ target[i]
if da > db {
return 1
} else if da < db {
return -1
}
}
return 0
} }
func (n *node) String() string { // table of leading zero counts for bytes [0..255]
return n.Node.String() var lzcount = [256]int{
8, 7, 6, 6, 5, 5, 5, 5,
4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
}
// logdist returns the logarithmic distance between a and b, log2(a ^ b).
func logdist(a, b common.Hash) int {
lz := 0
for i := range a {
x := a[i] ^ b[i]
if x == 0 {
lz += 8
} else {
lz += lzcount[x]
break
}
}
return len(a)*8 - lz
}
// hashAtDistance returns a random hash such that logdist(a, b) == n
func hashAtDistance(a common.Hash, n int) (b common.Hash) {
if n == 0 {
return a
}
// flip bit at position n, fill the rest with random bits
b = a
pos := len(a) - n/8 - 1
bit := byte(0x01) << (byte(n%8) - 1)
if bit == 0 {
pos++
bit = 0x80
}
b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
for i := pos + 1; i < len(a); i++ {
b[i] = byte(rand.Intn(255))
}
return b
} }

View file

@ -1,4 +1,4 @@
// Copyright 2018 The go-ethereum Authors // Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // The go-ethereum library is free software: you can redistribute it and/or modify
@ -14,19 +14,45 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode package discover
import ( import (
"bytes" "bytes"
"crypto/ecdsa" "fmt"
"math/big" "math/big"
"math/rand"
"net" "net"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"testing/quick" "testing/quick"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
) )
func ExampleNewNode() {
id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439")
// Complete nodes contain UDP and TCP endpoints:
n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303)
fmt.Println("n1:", n1)
fmt.Println("n1.Incomplete() ->", n1.Incomplete())
// An incomplete node can be created by passing zero values
// for all parameters except id.
n2 := NewNode(id, nil, 0, 0)
fmt.Println("n2:", n2)
fmt.Println("n2.Incomplete() ->", n2.Incomplete())
// Output:
// n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150
// n1.Incomplete() -> false
// n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439
// n2.Incomplete() -> true
}
var parseNodeTests = []struct { var parseNodeTests = []struct {
rawurl string rawurl string
wantError string wantError string
@ -55,8 +81,8 @@ var parseNodeTests = []struct {
}, },
{ {
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1}, net.IP{0x7f, 0x0, 0x0, 0x1},
52150, 52150,
52150, 52150,
@ -64,8 +90,8 @@ var parseNodeTests = []struct {
}, },
{ {
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("::"), net.ParseIP("::"),
52150, 52150,
52150, 52150,
@ -73,8 +99,8 @@ var parseNodeTests = []struct {
}, },
{ {
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
52150, 52150,
52150, 52150,
@ -82,25 +108,25 @@ var parseNodeTests = []struct {
}, },
{ {
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1}, net.IP{0x7f, 0x0, 0x0, 0x1},
52150,
22334, 22334,
52150,
), ),
}, },
// Incomplete nodes with no address. // Incomplete nodes with no address.
{ {
rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
nil, 0, 0, nil, 0, 0,
), ),
}, },
{ {
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
wantResult: NewV4( wantResult: NewNode(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
nil, 0, 0, nil, 0, 0,
), ),
}, },
@ -120,17 +146,9 @@ var parseNodeTests = []struct {
}, },
} }
func hexPubkey(h string) *ecdsa.PublicKey {
k, err := parsePubkey(h)
if err != nil {
panic(err)
}
return k
}
func TestParseNode(t *testing.T) { func TestParseNode(t *testing.T) {
for _, test := range parseNodeTests { for _, test := range parseNodeTests {
n, err := ParseV4(test.rawurl) n, err := ParseNode(test.rawurl)
if test.wantError != "" { if test.wantError != "" {
if err == nil { if err == nil {
t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError)
@ -145,7 +163,7 @@ func TestParseNode(t *testing.T) {
continue continue
} }
if !reflect.DeepEqual(n, test.wantResult) { if !reflect.DeepEqual(n, test.wantResult) {
t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.rawurl, n, test.wantResult) t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult)
} }
} }
} }
@ -163,9 +181,9 @@ func TestNodeString(t *testing.T) {
} }
func TestHexID(t *testing.T) { func TestHexID(t *testing.T) {
ref := ID{0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188} ref := NodeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") id1 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
if id1 != ref { if id1 != ref {
t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:])
@ -175,14 +193,17 @@ func TestHexID(t *testing.T) {
} }
} }
func TestID_textEncoding(t *testing.T) { func TestNodeID_textEncoding(t *testing.T) {
ref := ID{ ref := NodeID{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
0x31, 0x32, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60,
0x61, 0x62, 0x63, 0x64,
} }
hex := "0102030405060708091011121314151617181920212223242526272829303132" hex := "01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364"
text, err := ref.MarshalText() text, err := ref.MarshalText()
if err != nil { if err != nil {
@ -192,7 +213,7 @@ func TestID_textEncoding(t *testing.T) {
t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text)
} }
id := new(ID) id := new(NodeID)
if err := id.UnmarshalText(text); err != nil { if err := id.UnmarshalText(text); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -201,43 +222,114 @@ func TestID_textEncoding(t *testing.T) {
} }
} }
func TestNodeID_recover(t *testing.T) {
prv := newkey()
hash := make([]byte, 32)
sig, err := crypto.Sign(hash, prv)
if err != nil {
t.Fatalf("signing error: %v", err)
}
pub := PubkeyID(&prv.PublicKey)
recpub, err := recoverNodeID(hash, sig)
if err != nil {
t.Fatalf("recovery error: %v", err)
}
if pub != recpub {
t.Errorf("recovered wrong pubkey:\ngot: %v\nwant: %v", recpub, pub)
}
ecdsa, err := pub.Pubkey()
if err != nil {
t.Errorf("Pubkey error: %v", err)
}
if !reflect.DeepEqual(ecdsa, &prv.PublicKey) {
t.Errorf("Pubkey mismatch:\n got: %#v\n want: %#v", ecdsa, &prv.PublicKey)
}
}
func TestNodeID_pubkeyBad(t *testing.T) {
ecdsa, err := NodeID{}.Pubkey()
if err == nil {
t.Error("expected error for zero ID")
}
if ecdsa != nil {
t.Error("expected nil result")
}
}
func TestNodeID_distcmp(t *testing.T) { func TestNodeID_distcmp(t *testing.T) {
distcmpBig := func(target, a, b ID) int { distcmpBig := func(target, a, b common.Hash) int {
tbig := new(big.Int).SetBytes(target[:]) tbig := new(big.Int).SetBytes(target[:])
abig := new(big.Int).SetBytes(a[:]) abig := new(big.Int).SetBytes(a[:])
bbig := new(big.Int).SetBytes(b[:]) bbig := new(big.Int).SetBytes(b[:])
return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig))
} }
if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil { if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil {
t.Error(err) t.Error(err)
} }
} }
// The random tests is likely to miss the case where a and b are equal, // the random tests is likely to miss the case where they're equal.
// this test checks it explicitly.
func TestNodeID_distcmpEqual(t *testing.T) { func TestNodeID_distcmpEqual(t *testing.T) {
base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
x := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} x := common.Hash{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
if DistCmp(base, x, x) != 0 { if distcmp(base, x, x) != 0 {
t.Errorf("DistCmp(base, x, x) != 0") t.Errorf("distcmp(base, x, x) != 0")
} }
} }
func TestNodeID_logdist(t *testing.T) { func TestNodeID_logdist(t *testing.T) {
logdistBig := func(a, b ID) int { logdistBig := func(a, b common.Hash) int {
abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
return new(big.Int).Xor(abig, bbig).BitLen() return new(big.Int).Xor(abig, bbig).BitLen()
} }
if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil { if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil {
t.Error(err) t.Error(err)
} }
} }
// The random tests is likely to miss the case where a and b are equal, // the random tests is likely to miss the case where they're equal.
// this test checks it explicitly.
func TestNodeID_logdistEqual(t *testing.T) { func TestNodeID_logdistEqual(t *testing.T) {
x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
if LogDist(x, x) != 0 { if logdist(x, x) != 0 {
t.Errorf("LogDist(x, x) != 0") t.Errorf("logdist(x, x) != 0")
} }
} }
func TestNodeID_hashAtDistance(t *testing.T) {
// we don't use quick.Check here because its output isn't
// very helpful when the test fails.
cfg := quickcfg()
for i := 0; i < cfg.MaxCount; i++ {
a := gen(common.Hash{}, cfg.Rand).(common.Hash)
dist := cfg.Rand.Intn(len(common.Hash{}) * 8)
result := hashAtDistance(a, dist)
actualdist := logdist(result, a)
if dist != actualdist {
t.Log("a: ", a)
t.Log("result:", result)
t.Fatalf("#%d: distance of result is %d, want %d", i, actualdist, dist)
}
}
}
func quickcfg() *quick.Config {
return &quick.Config{
MaxCount: 5000,
Rand: rand.New(rand.NewSource(time.Now().Unix())),
}
}
// TODO: The Generate method can be dropped when we require Go >= 1.5
// because testing/quick learned to generate arrays in 1.5.
func (NodeID) Generate(rand *rand.Rand, size int) reflect.Value {
var id NodeID
m := rand.Intn(len(id))
for i := len(id) - 1; i > m; i-- {
id[i] = byte(rand.Uint32())
}
return reflect.ValueOf(id)
}

View file

@ -23,9 +23,9 @@
package discover package discover
import ( import (
"crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
mrand "math/rand" mrand "math/rand"
"net" "net"
@ -36,14 +36,13 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
) )
const ( const (
alpha = 3 // Kademlia concurrency factor alpha = 3 // Kademlia concurrency factor
bucketSize = 16 // Kademlia bucket size bucketSize = 200 // Kademlia bucket size
maxReplacements = 10 // Size of per-bucket replacement list maxReplacements = 10 // Size of per-bucket replacement list
// We keep buckets for the upper 1/15 of distances because // We keep buckets for the upper 1/15 of distances because
// it's very unlikely we'll ever encounter a node that's closer. // it's very unlikely we'll ever encounter a node that's closer.
@ -55,54 +54,76 @@ const (
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
tableIPLimit, tableSubnet = 10, 24 tableIPLimit, tableSubnet = 10, 24
maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped maxBondingPingPongs = 16 // Limit on the number of concurrent ping/pong interactions
refreshInterval = 30 * time.Minute maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped
revalidateInterval = 10 * time.Second
copyNodesInterval = 30 * time.Second refreshInterval = 30 * time.Minute
seedMinTableTime = 5 * time.Minute revalidateInterval = 10 * time.Second
seedCount = 30 copyNodesInterval = 30 * time.Second
seedMaxAge = 5 * 24 * time.Hour seedMinTableTime = 5 * time.Minute
seedCount = 30
seedMaxAge = 5 * 24 * time.Hour
) )
type Table struct { type Table struct {
mutex sync.Mutex // protects buckets, bucket content, nursery, rand mutex sync.Mutex // protects buckets, bucket content, nursery, rand
buckets [nBuckets]*bucket // index of known nodes by distance buckets [nBuckets]*bucket // index of known nodes by distance
nursery []*node // bootstrap nodes nursery []*Node // bootstrap nodes
rand *mrand.Rand // source of randomness, periodically reseeded rand *mrand.Rand // source of randomness, periodically reseeded
ips netutil.DistinctNetSet ips netutil.DistinctNetSet
db *enode.DB // database of known nodes db *nodeDB // database of known nodes
net transport
refreshReq chan chan struct{} refreshReq chan chan struct{}
initDone chan struct{} initDone chan struct{}
closeReq chan struct{} closeReq chan struct{}
closed chan struct{} closed chan struct{}
nodeAddedHook func(*node) // for testing bondmu sync.Mutex
bonding map[NodeID]*bondproc
bondslots chan struct{} // limits total number of active bonding processes
nodeAddedHook func(*Node) // for testing
net transport
self *Node // metadata of the local node
}
type bondproc struct {
err error
n *Node
done chan struct{}
} }
// transport is implemented by the UDP transport. // transport is implemented by the UDP transport.
// it is an interface so we can test without opening lots of UDP // it is an interface so we can test without opening lots of UDP
// sockets and without generating a private key. // sockets and without generating a private key.
type transport interface { type transport interface {
self() *enode.Node ping(NodeID, *net.UDPAddr) error
ping(enode.ID, *net.UDPAddr) error waitping(NodeID) error
findnode(toid enode.ID, addr *net.UDPAddr, target encPubkey) ([]*node, error) findnode(toid NodeID, addr *net.UDPAddr, target NodeID) ([]*Node, error)
close() close()
} }
// bucket contains nodes, ordered by their last activity. the entry // bucket contains nodes, ordered by their last activity. the entry
// that was most recently active is the first element in entries. // that was most recently active is the first element in entries.
type bucket struct { type bucket struct {
entries []*node // live entries, sorted by time of last contact entries []*Node // live entries, sorted by time of last contact
replacements []*node // recently seen nodes to be used if revalidation fails replacements []*Node // recently seen nodes to be used if revalidation fails
ips netutil.DistinctNetSet ips netutil.DistinctNetSet
} }
func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error) { func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string, bootnodes []*Node) (*Table, error) {
// If no node database was given, use an in-memory one
db, err := newNodeDB(nodeDBPath, Version, ourID)
if err != nil {
return nil, err
}
tab := &Table{ tab := &Table{
net: t, net: t,
db: db, db: db,
self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
bonding: make(map[NodeID]*bondproc),
bondslots: make(chan struct{}, maxBondingPingPongs),
refreshReq: make(chan chan struct{}), refreshReq: make(chan chan struct{}),
initDone: make(chan struct{}), initDone: make(chan struct{}),
closeReq: make(chan struct{}), closeReq: make(chan struct{}),
@ -113,22 +134,24 @@ func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error
if err := tab.setFallbackNodes(bootnodes); err != nil { if err := tab.setFallbackNodes(bootnodes); err != nil {
return nil, err return nil, err
} }
for i := 0; i < cap(tab.bondslots); i++ {
tab.bondslots <- struct{}{}
}
for i := range tab.buckets { for i := range tab.buckets {
tab.buckets[i] = &bucket{ tab.buckets[i] = &bucket{
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
} }
} }
tab.seedRand() tab.seedRand()
tab.loadSeedNodes() tab.loadSeedNodes(false)
// Start the background expiration goroutine after loading seeds so that the search for
// seed nodes also considers older nodes that would otherwise be removed by the
// expiration.
tab.db.ensureExpirer()
go tab.loop() go tab.loop()
return tab, nil return tab, nil
} }
func (tab *Table) self() *enode.Node {
return tab.net.self()
}
func (tab *Table) seedRand() { func (tab *Table) seedRand() {
var b [8]byte var b [8]byte
crand.Read(b[:]) crand.Read(b[:])
@ -138,9 +161,16 @@ func (tab *Table) seedRand() {
tab.mutex.Unlock() tab.mutex.Unlock()
} }
// ReadRandomNodes fills the given slice with random nodes from the table. The results // Self returns the local node.
// are guaranteed to be unique for a single invocation, no node will appear twice. // The returned node should not be modified by the caller.
func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { func (tab *Table) Self() *Node {
return tab.self
}
// ReadRandomNodes fills the given slice with random nodes from the
// table. It will not write the same node more than once. The nodes in
// the slice are copies and can be modified by the caller.
func (tab *Table) ReadRandomNodes(buf []*Node) (n int) {
if !tab.isInitDone() { if !tab.isInitDone() {
return 0 return 0
} }
@ -148,10 +178,10 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
// Find all non-empty buckets and get a fresh slice of their entries. // Find all non-empty buckets and get a fresh slice of their entries.
var buckets [][]*node var buckets [][]*Node
for _, b := range &tab.buckets { for _, b := range tab.buckets {
if len(b.entries) > 0 { if len(b.entries) > 0 {
buckets = append(buckets, b.entries) buckets = append(buckets, b.entries[:])
} }
} }
if len(buckets) == 0 { if len(buckets) == 0 {
@ -166,7 +196,7 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
var i, j int var i, j int
for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
b := buckets[j] b := buckets[j]
buf[i] = unwrapNode(b[0]) buf[i] = &(*b[0])
buckets[j] = b[1:] buckets[j] = b[1:]
if len(b) == 1 { if len(b) == 1 {
buckets = append(buckets[:j], buckets[j+1:]...) buckets = append(buckets[:j], buckets[j+1:]...)
@ -180,10 +210,6 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
// Close terminates the network listener and flushes the node database. // Close terminates the network listener and flushes the node database.
func (tab *Table) Close() { func (tab *Table) Close() {
if tab.net != nil {
tab.net.close()
}
select { select {
case <-tab.closed: case <-tab.closed:
// already closed. // already closed.
@ -195,13 +221,20 @@ func (tab *Table) Close() {
// setFallbackNodes sets the initial points of contact. These nodes // setFallbackNodes sets the initial points of contact. These nodes
// are used to connect to the network if the table is empty and there // are used to connect to the network if the table is empty and there
// are no known nodes in the database. // are no known nodes in the database.
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error { func (tab *Table) setFallbackNodes(nodes []*Node) error {
for _, n := range nodes { for _, n := range nodes {
if err := n.ValidateComplete(); err != nil { if err := n.validateComplete(); err != nil {
return fmt.Errorf("bad bootstrap node %q: %v", n, err) return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err)
} }
} }
tab.nursery = wrapNodes(nodes) tab.nursery = make([]*Node, 0, len(nodes))
for _, n := range nodes {
cpy := *n
// Recompute cpy.sha because the node might not have been
// created by NewNode or ParseNode.
cpy.sha = crypto.Keccak256Hash(n.ID[:])
tab.nursery = append(tab.nursery, &cpy)
}
return nil return nil
} }
@ -217,48 +250,47 @@ func (tab *Table) isInitDone() bool {
// Resolve searches for a specific node with the given ID. // Resolve searches for a specific node with the given ID.
// It returns nil if the node could not be found. // It returns nil if the node could not be found.
func (tab *Table) Resolve(n *enode.Node) *enode.Node { func (tab *Table) Resolve(targetID NodeID) *Node {
// If the node is present in the local table, no // If the node is present in the local table, no
// network interaction is required. // network interaction is required.
hash := n.ID() hash := crypto.Keccak256Hash(targetID[:])
tab.mutex.Lock() tab.mutex.Lock()
cl := tab.closest(hash, 1) cl := tab.closest(hash, 1)
tab.mutex.Unlock() tab.mutex.Unlock()
if len(cl.entries) > 0 && cl.entries[0].ID() == hash { if len(cl.entries) > 0 && cl.entries[0].ID == targetID {
return unwrapNode(cl.entries[0]) return cl.entries[0]
} }
// Otherwise, do a network lookup. // Otherwise, do a network lookup.
result := tab.lookup(encodePubkey(n.Pubkey()), true) result := tab.Lookup(targetID)
for _, n := range result { for _, n := range result {
if n.ID() == hash { if n.ID == targetID {
return unwrapNode(n) return n
} }
} }
return nil return nil
} }
// LookupRandom finds random nodes in the network. // Lookup performs a network search for nodes close
func (tab *Table) LookupRandom() []*enode.Node { // to the given target. It approaches the target by querying
var target encPubkey // nodes that are closer to it on each iteration.
crand.Read(target[:]) // The given target does not need to be an actual node
return unwrapNodes(tab.lookup(target, true)) // identifier.
func (tab *Table) Lookup(targetID NodeID) []*Node {
return tab.lookup(targetID, true)
} }
// lookup performs a network search for nodes close to the given target. It approaches the func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
// target by querying nodes that are closer to it on each iteration. The given target does
// not need to be an actual node identifier.
func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
var ( var (
target = enode.ID(crypto.Keccak256Hash(targetKey[:])) target = crypto.Keccak256Hash(targetID[:])
asked = make(map[enode.ID]bool) asked = make(map[NodeID]bool)
seen = make(map[enode.ID]bool) seen = make(map[NodeID]bool)
reply = make(chan []*node, alpha) reply = make(chan []*Node, alpha)
pendingQueries = 0 pendingQueries = 0
result *nodesByDistance result *nodesByDistance
) )
// don't query further if we hit ourself. // don't query further if we hit ourself.
// unlikely to happen often in practice. // unlikely to happen often in practice.
asked[tab.self().ID()] = true asked[tab.self.ID] = true
for { for {
tab.mutex.Lock() tab.mutex.Lock()
@ -280,10 +312,25 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
// ask the alpha closest nodes that we haven't asked yet // ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
n := result.entries[i] n := result.entries[i]
if !asked[n.ID()] { if !asked[n.ID] {
asked[n.ID()] = true asked[n.ID] = true
pendingQueries++ pendingQueries++
go tab.findnode(n, targetKey, reply) go func() {
// Find potential neighbors to bond with
r, err := tab.net.findnode(n.ID, n.addr(), targetID)
if err != nil {
// Bump the failure counter to detect and evacuate non-bonded entries
fails := tab.db.findFails(n.ID) + 1
tab.db.updateFindFails(n.ID, fails)
log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails)
if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
tab.delete(n)
}
}
reply <- tab.bondall(r)
}()
} }
} }
if pendingQueries == 0 { if pendingQueries == 0 {
@ -292,8 +339,8 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
} }
// wait for the next reply // wait for the next reply
for _, n := range <-reply { for _, n := range <-reply {
if n != nil && !seen[n.ID()] { if n != nil && !seen[n.ID] {
seen[n.ID()] = true seen[n.ID] = true
result.push(n, bucketSize) result.push(n, bucketSize)
} }
} }
@ -302,29 +349,6 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
return result.entries return result.entries
} }
func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) {
fails := tab.db.FindFails(n.ID())
r, err := tab.net.findnode(n.ID(), n.addr(), targetKey)
if err != nil || len(r) == 0 {
fails++
tab.db.UpdateFindFails(n.ID(), fails)
log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err)
if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails)
tab.delete(n)
}
} else if fails > 0 {
tab.db.UpdateFindFails(n.ID(), fails-1)
}
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
// just remove those again during revalidation.
for _, n := range r {
tab.add(n)
}
reply <- r
}
func (tab *Table) refresh() <-chan struct{} { func (tab *Table) refresh() <-chan struct{} {
done := make(chan struct{}) done := make(chan struct{})
select { select {
@ -341,8 +365,8 @@ func (tab *Table) loop() {
revalidate = time.NewTimer(tab.nextRevalidateTime()) revalidate = time.NewTimer(tab.nextRevalidateTime())
refresh = time.NewTicker(refreshInterval) refresh = time.NewTicker(refreshInterval)
copyNodes = time.NewTicker(copyNodesInterval) copyNodes = time.NewTicker(copyNodesInterval)
revalidateDone = make(chan struct{})
refreshDone = make(chan struct{}) // where doRefresh reports completion refreshDone = make(chan struct{}) // where doRefresh reports completion
revalidateDone chan struct{} // where doRevalidate reports completion
waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs
) )
defer refresh.Stop() defer refresh.Stop()
@ -373,27 +397,26 @@ loop:
} }
waiting, refreshDone = nil, nil waiting, refreshDone = nil, nil
case <-revalidate.C: case <-revalidate.C:
revalidateDone = make(chan struct{})
go tab.doRevalidate(revalidateDone) go tab.doRevalidate(revalidateDone)
case <-revalidateDone: case <-revalidateDone:
revalidate.Reset(tab.nextRevalidateTime()) revalidate.Reset(tab.nextRevalidateTime())
revalidateDone = nil
case <-copyNodes.C: case <-copyNodes.C:
go tab.copyLiveNodes() go tab.copyBondedNodes()
case <-tab.closeReq: case <-tab.closeReq:
break loop break loop
} }
} }
if tab.net != nil {
tab.net.close()
}
if refreshDone != nil { if refreshDone != nil {
<-refreshDone <-refreshDone
} }
for _, ch := range waiting { for _, ch := range waiting {
close(ch) close(ch)
} }
if revalidateDone != nil { tab.db.close()
<-revalidateDone
}
close(tab.closed) close(tab.closed)
} }
@ -406,14 +429,10 @@ func (tab *Table) doRefresh(done chan struct{}) {
// Load nodes from the database and insert // Load nodes from the database and insert
// them. This should yield a few previously seen nodes that are // them. This should yield a few previously seen nodes that are
// (hopefully) still alive. // (hopefully) still alive.
tab.loadSeedNodes() tab.loadSeedNodes(true)
// Run self lookup to discover new neighbor nodes. // Run self lookup to discover new neighbor nodes.
// We can only do this if we have a secp256k1 identity. tab.lookup(tab.self.ID, false)
var key ecdsa.PublicKey
if err := tab.self().Load((*enode.Secp256k1)(&key)); err == nil {
tab.lookup(encodePubkey(&key), false)
}
// The Kademlia paper specifies that the bucket refresh should // The Kademlia paper specifies that the bucket refresh should
// perform a lookup in the least recently used bucket. We cannot // perform a lookup in the least recently used bucket. We cannot
@ -422,19 +441,22 @@ func (tab *Table) doRefresh(done chan struct{}) {
// sha3 preimage that falls into a chosen bucket. // sha3 preimage that falls into a chosen bucket.
// We perform a few lookups with a random target instead. // We perform a few lookups with a random target instead.
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
var target encPubkey var target NodeID
crand.Read(target[:]) crand.Read(target[:])
tab.lookup(target, false) tab.lookup(target, false)
} }
} }
func (tab *Table) loadSeedNodes() { func (tab *Table) loadSeedNodes(bond bool) {
seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge)) seeds := tab.db.querySeeds(seedCount, seedMaxAge)
seeds = append(seeds, tab.nursery...) seeds = append(seeds, tab.nursery...)
if bond {
seeds = tab.bondall(seeds)
}
for i := range seeds { for i := range seeds {
seed := seeds[i] seed := seeds[i]
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }} age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.bondTime(seed.ID)) }}
log.Debug("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age)
tab.add(seed) tab.add(seed)
} }
} }
@ -451,28 +473,28 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
} }
// Ping the selected node and wait for a pong. // Ping the selected node and wait for a pong.
err := tab.net.ping(last.ID(), last.addr()) err := tab.ping(last.ID, last.addr())
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
b := tab.buckets[bi] b := tab.buckets[bi]
if err == nil { if err == nil {
// The node responded, move it to the front. // The node responded, move it to the front.
log.Debug("Revalidated node", "b", bi, "id", last.ID()) log.Debug("Revalidated node", "b", bi, "id", last.ID)
b.bump(last) b.bump(last)
return return
} }
// No reply received, pick a replacement or delete the node if there aren't // No reply received, pick a replacement or delete the node if there aren't
// any replacements. // any replacements.
if r := tab.replace(b, last); r != nil { if r := tab.replace(b, last); r != nil {
log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "r", r.ID(), "rip", r.IP()) log.Debug("Replaced dead node", "b", bi, "id", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP)
} else { } else {
log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP()) log.Debug("Removed dead node", "b", bi, "id", last.ID, "ip", last.IP)
} }
} }
// nodeToRevalidate returns the last node in a random, non-empty bucket. // nodeToRevalidate returns the last node in a random, non-empty bucket.
func (tab *Table) nodeToRevalidate() (n *node, bi int) { func (tab *Table) nodeToRevalidate() (n *Node, bi int) {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
@ -493,17 +515,17 @@ func (tab *Table) nextRevalidateTime() time.Duration {
return time.Duration(tab.rand.Int63n(int64(revalidateInterval))) return time.Duration(tab.rand.Int63n(int64(revalidateInterval)))
} }
// copyLiveNodes adds nodes from the table to the database if they have been in the table // copyBondedNodes adds nodes from the table to the database if they have been in the table
// longer then minTableTime. // longer then minTableTime.
func (tab *Table) copyLiveNodes() { func (tab *Table) copyBondedNodes() {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
now := time.Now() now := time.Now()
for _, b := range &tab.buckets { for _, b := range tab.buckets {
for _, n := range b.entries { for _, n := range b.entries {
if now.Sub(n.addedAt) >= seedMinTableTime { if now.Sub(n.addedAt) >= seedMinTableTime {
tab.db.UpdateNode(unwrapNode(n)) tab.db.updateNode(n)
} }
} }
} }
@ -511,12 +533,12 @@ func (tab *Table) copyLiveNodes() {
// closest returns the n nodes in the table that are closest to the // closest returns the n nodes in the table that are closest to the
// given id. The caller must hold tab.mutex. // given id. The caller must hold tab.mutex.
func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
// This is a very wasteful way to find the closest nodes but // This is a very wasteful way to find the closest nodes but
// obviously correct. I believe that tree-based buckets would make // obviously correct. I believe that tree-based buckets would make
// this easier to implement efficiently. // this easier to implement efficiently.
close := &nodesByDistance{target: target} close := &nodesByDistance{target: target}
for _, b := range &tab.buckets { for _, b := range tab.buckets {
for _, n := range b.entries { for _, n := range b.entries {
close.push(n, nresults) close.push(n, nresults)
} }
@ -525,76 +547,176 @@ func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance {
} }
func (tab *Table) len() (n int) { func (tab *Table) len() (n int) {
for _, b := range &tab.buckets { for _, b := range tab.buckets {
n += len(b.entries) n += len(b.entries)
} }
return n return n
} }
// bondall bonds with all given nodes concurrently and returns
// those nodes for which bonding has probably succeeded.
func (tab *Table) bondall(nodes []*Node) (result []*Node) {
rc := make(chan *Node, len(nodes))
for i := range nodes {
go func(n *Node) {
nn, _ := tab.bond(false, n.ID, n.addr(), n.TCP)
rc <- nn
}(nodes[i])
}
for range nodes {
if n := <-rc; n != nil {
result = append(result, n)
}
}
return result
}
// bond ensures the local node has a bond with the given remote node.
// It also attempts to insert the node into the table if bonding succeeds.
// The caller must not hold tab.mutex.
//
// A bond is must be established before sending findnode requests.
// Both sides must have completed a ping/pong exchange for a bond to
// exist. The total number of active bonding processes is limited in
// order to restrain network use.
//
// bond is meant to operate idempotently in that bonding with a remote
// node which still remembers a previously established bond will work.
// The remote node will simply not send a ping back, causing waitping
// to time out.
//
// If pinged is true, the remote node has just pinged us and one half
// of the process can be skipped.
func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) {
if id == tab.self.ID {
return nil, errors.New("is self")
}
if pinged && !tab.isInitDone() {
return nil, errors.New("still initializing")
}
// Start bonding if we haven't seen this node for a while or if it failed findnode too often.
node, fails := tab.db.node(id), tab.db.findFails(id)
age := time.Since(tab.db.bondTime(id))
var result error
if fails > 0 || age > nodeDBNodeExpiration {
log.Trace("Starting bonding ping/pong", "id", id, "known", node != nil, "failcount", fails, "age", age)
tab.bondmu.Lock()
w := tab.bonding[id]
if w != nil {
// Wait for an existing bonding process to complete.
tab.bondmu.Unlock()
<-w.done
} else {
// Register a new bonding process.
w = &bondproc{done: make(chan struct{})}
tab.bonding[id] = w
tab.bondmu.Unlock()
// Do the ping/pong. The result goes into w.
tab.pingpong(w, pinged, id, addr, tcpPort)
// Unregister the process after it's done.
tab.bondmu.Lock()
delete(tab.bonding, id)
tab.bondmu.Unlock()
}
// Retrieve the bonding results
result = w.err
if result == nil {
node = w.n
}
}
// Add the node to the table even if the bonding ping/pong
// fails. It will be relaced quickly if it continues to be
// unresponsive.
if node != nil {
tab.add(node)
tab.db.updateFindFails(id, 0)
}
return node, result
}
func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) {
// Request a bonding slot to limit network usage
<-tab.bondslots
defer func() { tab.bondslots <- struct{}{} }()
// Ping the remote side and wait for a pong.
if w.err = tab.ping(id, addr); w.err != nil {
close(w.done)
return
}
if !pinged {
// Give the remote node a chance to ping us before we start
// sending findnode requests. If they still remember us,
// waitping will simply time out.
tab.net.waitping(id)
}
// Bonding succeeded, update the node database.
w.n = NewNode(id, addr.IP, uint16(addr.Port), tcpPort)
close(w.done)
}
// ping a remote endpoint and wait for a reply, also updating the node
// database accordingly.
func (tab *Table) ping(id NodeID, addr *net.UDPAddr) error {
tab.db.updateLastPing(id, time.Now())
if err := tab.net.ping(id, addr); err != nil {
return err
}
tab.db.updateBondTime(id, time.Now())
return nil
}
// bucket returns the bucket for the given node ID hash. // bucket returns the bucket for the given node ID hash.
func (tab *Table) bucket(id enode.ID) *bucket { func (tab *Table) bucket(sha common.Hash) *bucket {
d := enode.LogDist(tab.self().ID(), id) d := logdist(tab.self.sha, sha)
if d <= bucketMinDistance { if d <= bucketMinDistance {
return tab.buckets[0] return tab.buckets[0]
} }
return tab.buckets[d-bucketMinDistance-1] return tab.buckets[d-bucketMinDistance-1]
} }
// add attempts to add the given node to its corresponding bucket. If the bucket has space // add attempts to add the given node its corresponding bucket. If the
// available, adding the node succeeds immediately. Otherwise, the node is added if the // bucket has space available, adding the node succeeds immediately.
// least recently active node in the bucket does not respond to a ping packet. // Otherwise, the node is added if the least recently active node in
// the bucket does not respond to a ping packet.
// //
// The caller must not hold tab.mutex. // The caller must not hold tab.mutex.
func (tab *Table) add(n *node) { func (tab *Table) add(new *Node) {
if n.ID() == tab.self().ID() {
return
}
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
b := tab.bucket(n.ID())
if !tab.bumpOrAdd(b, n) {
// Node is not in table. Add it to the replacement list.
tab.addReplacement(b, n)
}
}
// addThroughPing adds the given node to the table. Compared to plain b := tab.bucket(new.sha)
// 'add' there is an additional safety measure: if the table is still if !tab.bumpOrAdd(b, new) {
// initializing the node is not added. This prevents an attack where the // Node is not in table. Add it to the replacement list.
// table could be filled by just sending ping repeatedly. tab.addReplacement(b, new)
//
// The caller must not hold tab.mutex.
func (tab *Table) addThroughPing(n *node) {
if !tab.isInitDone() {
return
} }
tab.add(n)
} }
// stuff adds nodes the table to the end of their corresponding bucket // stuff adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex. // if the bucket is not full. The caller must not hold tab.mutex.
func (tab *Table) stuff(nodes []*node) { func (tab *Table) stuff(nodes []*Node) {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
for _, n := range nodes { for _, n := range nodes {
if n.ID() == tab.self().ID() { if n.ID == tab.self.ID {
continue // don't add self continue // don't add self
} }
b := tab.bucket(n.ID()) b := tab.bucket(n.sha)
if len(b.entries) < bucketSize { if len(b.entries) < bucketSize {
tab.bumpOrAdd(b, n) tab.bumpOrAdd(b, n)
} }
} }
} }
// delete removes an entry from the node table. It is used to evacuate dead nodes. // delete removes an entry from the node table (used to evacuate
func (tab *Table) delete(node *node) { // failed/non-bonded discovery peers).
func (tab *Table) delete(node *Node) {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
tab.deleteInBucket(tab.bucket(node.ID()), node) tab.deleteInBucket(tab.bucket(node.sha), node)
} }
func (tab *Table) addIP(b *bucket, ip net.IP) bool { func (tab *Table) addIP(b *bucket, ip net.IP) bool {
@ -621,27 +743,27 @@ func (tab *Table) removeIP(b *bucket, ip net.IP) {
b.ips.Remove(ip) b.ips.Remove(ip)
} }
func (tab *Table) addReplacement(b *bucket, n *node) { func (tab *Table) addReplacement(b *bucket, n *Node) {
for _, e := range b.replacements { for _, e := range b.replacements {
if e.ID() == n.ID() { if e.ID == n.ID {
return // already in list return // already in list
} }
} }
if !tab.addIP(b, n.IP()) { if !tab.addIP(b, n.IP) {
return return
} }
var removed *node var removed *Node
b.replacements, removed = pushNode(b.replacements, n, maxReplacements) b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
if removed != nil { if removed != nil {
tab.removeIP(b, removed.IP()) tab.removeIP(b, removed.IP)
} }
} }
// replace removes n from the replacement list and replaces 'last' with it if it is the // replace removes n from the replacement list and replaces 'last' with it if it is the
// last entry in the bucket. If 'last' isn't the last entry, it has either been replaced // last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
// with someone else or became active. // with someone else or became active.
func (tab *Table) replace(b *bucket, last *node) *node { func (tab *Table) replace(b *bucket, last *Node) *Node {
if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID() != last.ID() { if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID != last.ID {
// Entry has moved, don't replace it. // Entry has moved, don't replace it.
return nil return nil
} }
@ -653,15 +775,15 @@ func (tab *Table) replace(b *bucket, last *node) *node {
r := b.replacements[tab.rand.Intn(len(b.replacements))] r := b.replacements[tab.rand.Intn(len(b.replacements))]
b.replacements = deleteNode(b.replacements, r) b.replacements = deleteNode(b.replacements, r)
b.entries[len(b.entries)-1] = r b.entries[len(b.entries)-1] = r
tab.removeIP(b, last.IP()) tab.removeIP(b, last.IP)
return r return r
} }
// bump moves the given node to the front of the bucket entry list // bump moves the given node to the front of the bucket entry list
// if it is contained in that list. // if it is contained in that list.
func (b *bucket) bump(n *node) bool { func (b *bucket) bump(n *Node) bool {
for i := range b.entries { for i := range b.entries {
if b.entries[i].ID() == n.ID() { if b.entries[i].ID == n.ID {
// move it to the front // move it to the front
copy(b.entries[1:], b.entries[:i]) copy(b.entries[1:], b.entries[:i])
b.entries[0] = n b.entries[0] = n
@ -673,11 +795,11 @@ func (b *bucket) bump(n *node) bool {
// bumpOrAdd moves n to the front of the bucket entry list or adds it if the list isn't // bumpOrAdd moves n to the front of the bucket entry list or adds it if the list isn't
// full. The return value is true if n is in the bucket. // full. The return value is true if n is in the bucket.
func (tab *Table) bumpOrAdd(b *bucket, n *node) bool { func (tab *Table) bumpOrAdd(b *bucket, n *Node) bool {
if b.bump(n) { if b.bump(n) {
return true return true
} }
if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP()) { if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP) {
return false return false
} }
b.entries, _ = pushNode(b.entries, n, bucketSize) b.entries, _ = pushNode(b.entries, n, bucketSize)
@ -689,13 +811,13 @@ func (tab *Table) bumpOrAdd(b *bucket, n *node) bool {
return true return true
} }
func (tab *Table) deleteInBucket(b *bucket, n *node) { func (tab *Table) deleteInBucket(b *bucket, n *Node) {
b.entries = deleteNode(b.entries, n) b.entries = deleteNode(b.entries, n)
tab.removeIP(b, n.IP()) tab.removeIP(b, n.IP)
} }
// pushNode adds n to the front of list, keeping at most max items. // pushNode adds n to the front of list, keeping at most max items.
func pushNode(list []*node, n *node, max int) ([]*node, *node) { func pushNode(list []*Node, n *Node, max int) ([]*Node, *Node) {
if len(list) < max { if len(list) < max {
list = append(list, nil) list = append(list, nil)
} }
@ -706,9 +828,9 @@ func pushNode(list []*node, n *node, max int) ([]*node, *node) {
} }
// deleteNode removes n from list. // deleteNode removes n from list.
func deleteNode(list []*node, n *node) []*node { func deleteNode(list []*Node, n *Node) []*Node {
for i := range list { for i := range list {
if list[i].ID() == n.ID() { if list[i].ID == n.ID {
return append(list[:i], list[i+1:]...) return append(list[:i], list[i+1:]...)
} }
} }
@ -718,14 +840,14 @@ func deleteNode(list []*node, n *node) []*node {
// nodesByDistance is a list of nodes, ordered by // nodesByDistance is a list of nodes, ordered by
// distance to target. // distance to target.
type nodesByDistance struct { type nodesByDistance struct {
entries []*node entries []*Node
target enode.ID target common.Hash
} }
// push adds the given node to the list, keeping the total size below maxElems. // push adds the given node to the list, keeping the total size below maxElems.
func (h *nodesByDistance) push(n *node, maxElems int) { func (h *nodesByDistance) push(n *Node, maxElems int) {
ix := sort.Search(len(h.entries), func(i int) bool { ix := sort.Search(len(h.entries), func(i int) bool {
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0 return distcmp(h.target, h.entries[i].sha, n.sha) > 0
}) })
if len(h.entries) < maxElems { if len(h.entries) < maxElems {
h.entries = append(h.entries, n) h.entries = append(h.entries, n)

View file

@ -20,6 +20,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/rand" "math/rand"
"sync"
"net" "net"
"reflect" "reflect"
@ -27,9 +28,8 @@ import (
"testing/quick" "testing/quick"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
) )
func TestTable_pingReplace(t *testing.T) { func TestTable_pingReplace(t *testing.T) {
@ -49,28 +49,30 @@ func TestTable_pingReplace(t *testing.T) {
func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
defer db.Close()
// Wait for init so bond is accepted. // Wait for init so bond is accepted.
<-tab.initDone <-tab.initDone
// Fill up the sender's bucket. // fill up the sender's bucket.
pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99)
pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 99, 99))
last := fillBucket(tab, pingSender) last := fillBucket(tab, pingSender)
// Add the sender as if it just pinged us. Revalidate should replace the last node in // this call to bond should replace the last node
// its bucket if it is unresponsive. Revalidate again to ensure that // in its bucket if the node is not responding.
transport.dead[last.ID()] = !lastInBucketIsResponding transport.dead[last.ID] = !lastInBucketIsResponding
transport.dead[pingSender.ID()] = !newNodeIsResponding transport.dead[pingSender.ID] = !newNodeIsResponding
tab.add(pingSender) tab.bond(true, pingSender.ID, &net.UDPAddr{}, 0)
tab.doRevalidate(make(chan struct{}, 1))
tab.doRevalidate(make(chan struct{}, 1)) tab.doRevalidate(make(chan struct{}, 1))
if !transport.pinged[last.ID()] { // first ping goes to sender (bonding pingback)
// Oldest node in bucket is pinged to see whether it is still alive. if !transport.pinged[pingSender.ID] {
t.Error("table did not ping back sender")
}
if !transport.pinged[last.ID] {
// second ping goes to oldest node in bucket
// to see whether it is still alive.
t.Error("table did not ping last node in bucket") t.Error("table did not ping last node in bucket")
} }
@ -80,14 +82,14 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
if !lastInBucketIsResponding && !newNodeIsResponding { if !lastInBucketIsResponding && !newNodeIsResponding {
wantSize-- wantSize--
} }
if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize { if l := len(tab.bucket(pingSender.sha).entries); l != wantSize {
t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize) t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize)
} }
if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding { if found := contains(tab.bucket(pingSender.sha).entries, last.ID); found != lastInBucketIsResponding {
t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding) t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding)
} }
wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry { if found := contains(tab.bucket(pingSender.sha).entries, pingSender.ID); found != wantNewEntry {
t.Errorf("new entry found: %t, want: %t", found, wantNewEntry) t.Errorf("new entry found: %t, want: %t", found, wantNewEntry)
} }
} }
@ -100,9 +102,9 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
Values: func(args []reflect.Value, rand *rand.Rand) { Values: func(args []reflect.Value, rand *rand.Rand) {
// generate a random list of nodes. this will be the content of the bucket. // generate a random list of nodes. this will be the content of the bucket.
n := rand.Intn(bucketSize-1) + 1 n := rand.Intn(bucketSize-1) + 1
nodes := make([]*node, n) nodes := make([]*Node, n)
for i := range nodes { for i := range nodes {
nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200)) nodes[i] = nodeAtDistance(common.Hash{}, 200)
} }
args[0] = reflect.ValueOf(nodes) args[0] = reflect.ValueOf(nodes)
// generate random bump positions. // generate random bump positions.
@ -114,8 +116,8 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
}, },
} }
prop := func(nodes []*node, bumps []int) (ok bool) { prop := func(nodes []*Node, bumps []int) (ok bool) {
b := &bucket{entries: make([]*node, len(nodes))} b := &bucket{entries: make([]*Node, len(nodes))}
copy(b.entries, nodes) copy(b.entries, nodes)
for i, pos := range bumps { for i, pos := range bumps {
b.bump(b.entries[pos]) b.bump(b.entries[pos])
@ -137,12 +139,12 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
// This checks that the table-wide IP limit is applied correctly. // This checks that the table-wide IP limit is applied correctly.
func TestTable_IPLimit(t *testing.T) { func TestTable_IPLimit(t *testing.T) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
defer db.Close()
for i := 0; i < tableIPLimit+1; i++ { for i := 0; i < tableIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) n := nodeAtDistance(tab.self.sha, i)
n.IP = net.IP{172, 0, 1, byte(i)}
tab.add(n) tab.add(n)
} }
if tab.len() > tableIPLimit { if tab.len() > tableIPLimit {
@ -150,16 +152,16 @@ func TestTable_IPLimit(t *testing.T) {
} }
} }
// This checks that the per-bucket IP limit is applied correctly. // This checks that the table-wide IP limit is applied correctly.
func TestTable_BucketIPLimit(t *testing.T) { func TestTable_BucketIPLimit(t *testing.T) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
defer db.Close()
d := 3 d := 3
for i := 0; i < bucketIPLimit+1; i++ { for i := 0; i < bucketIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) n := nodeAtDistance(tab.self.sha, d)
n.IP = net.IP{172, 0, 1, byte(i)}
tab.add(n) tab.add(n)
} }
if tab.len() > bucketIPLimit { if tab.len() > bucketIPLimit {
@ -167,18 +169,70 @@ func TestTable_BucketIPLimit(t *testing.T) {
} }
} }
// fillBucket inserts nodes into the given bucket until
// it is full. The node's IDs dont correspond to their
// hashes.
func fillBucket(tab *Table, n *Node) (last *Node) {
ld := logdist(tab.self.sha, n.sha)
b := tab.bucket(n.sha)
for len(b.entries) < bucketSize {
b.entries = append(b.entries, nodeAtDistance(tab.self.sha, ld))
}
return b.entries[bucketSize-1]
}
// nodeAtDistance creates a node for which logdist(base, n.sha) == ld.
// The node's ID does not correspond to n.sha.
func nodeAtDistance(base common.Hash, ld int) (n *Node) {
n = new(Node)
n.sha = hashAtDistance(base, ld)
n.IP = net.IP{byte(ld), 0, 2, byte(ld)}
copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID
return n
}
type pingRecorder struct {
mu sync.Mutex
dead, pinged map[NodeID]bool
}
func newPingRecorder() *pingRecorder {
return &pingRecorder{
dead: make(map[NodeID]bool),
pinged: make(map[NodeID]bool),
}
}
func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
return nil, nil
}
func (t *pingRecorder) close() {}
func (t *pingRecorder) waitping(from NodeID) error {
return nil // remote always pings
}
func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error {
t.mu.Lock()
defer t.mu.Unlock()
t.pinged[toid] = true
if t.dead[toid] {
return errTimeout
} else {
return nil
}
}
func TestTable_closest(t *testing.T) { func TestTable_closest(t *testing.T) {
t.Parallel() t.Parallel()
test := func(test *closeTest) bool { test := func(test *closeTest) bool {
// for any node table, Target and N // for any node table, Target and N
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, _ := newTable(transport, test.Self, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
defer db.Close()
tab.stuff(test.All) tab.stuff(test.All)
// check that closest(Target, N) returns nodes // check that doClosest(Target, N) returns nodes
result := tab.closest(test.Target, test.N).entries result := tab.closest(test.Target, test.N).entries
if hasDuplicates(result) { if hasDuplicates(result) {
t.Errorf("result contains duplicates") t.Errorf("result contains duplicates")
@ -204,15 +258,15 @@ func TestTable_closest(t *testing.T) {
// check that the result nodes have minimum distance to target. // check that the result nodes have minimum distance to target.
for _, b := range tab.buckets { for _, b := range tab.buckets {
for _, n := range b.entries { for _, n := range b.entries {
if contains(result, n.ID()) { if contains(result, n.ID) {
continue // don't run the check below for nodes in result continue // don't run the check below for nodes in result
} }
farthestResult := result[len(result)-1].ID() farthestResult := result[len(result)-1].sha
if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 { if distcmp(test.Target, n.sha, farthestResult) < 0 {
t.Errorf("table contains node that is closer to target but it's not in result") t.Errorf("table contains node that is closer to target but it's not in result")
t.Logf(" Target: %v", test.Target) t.Logf(" Target: %v", test.Target)
t.Logf(" Farthest Result: %v", farthestResult) t.Logf(" Farthest Result: %v", farthestResult)
t.Logf(" ID: %v", n.ID()) t.Logf(" ID: %v", n.ID)
return false return false
} }
} }
@ -229,26 +283,25 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
MaxCount: 200, MaxCount: 200,
Rand: rand.New(rand.NewSource(time.Now().Unix())), Rand: rand.New(rand.NewSource(time.Now().Unix())),
Values: func(args []reflect.Value, rand *rand.Rand) { Values: func(args []reflect.Value, rand *rand.Rand) {
args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000))) args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000)))
}, },
} }
test := func(buf []*enode.Node) bool { test := func(buf []*Node) bool {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
defer db.Close()
<-tab.initDone <-tab.initDone
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
ld := cfg.Rand.Intn(len(tab.buckets)) ld := cfg.Rand.Intn(len(tab.buckets))
tab.stuff([]*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)})
} }
gotN := tab.ReadRandomNodes(buf) gotN := tab.ReadRandomNodes(buf)
if gotN != tab.len() { if gotN != tab.len() {
t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len()) t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len())
return false return false
} }
if hasDuplicates(wrapNodes(buf[:gotN])) { if hasDuplicates(buf[:gotN]) {
t.Errorf("result contains duplicates") t.Errorf("result contains duplicates")
return false return false
} }
@ -260,308 +313,302 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
} }
type closeTest struct { type closeTest struct {
Self enode.ID Self NodeID
Target enode.ID Target common.Hash
All []*node All []*Node
N int N int
} }
func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
t := &closeTest{ t := &closeTest{
Self: gen(enode.ID{}, rand).(enode.ID), Self: gen(NodeID{}, rand).(NodeID),
Target: gen(enode.ID{}, rand).(enode.ID), Target: gen(common.Hash{}, rand).(common.Hash),
N: rand.Intn(bucketSize), N: rand.Intn(bucketSize),
} }
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { for _, id := range gen([]NodeID{}, rand).([]NodeID) {
n := enode.SignNull(new(enr.Record), id) t.All = append(t.All, &Node{ID: id})
t.All = append(t.All, wrapNode(n))
} }
return reflect.ValueOf(t) return reflect.ValueOf(t)
} }
func TestTable_Lookup(t *testing.T) { //func TestTable_Lookup(t *testing.T) {
tab, db := newTestTable(lookupTestnet) // bucketSizeTest := 16
defer tab.Close() // self := nodeAtDistance(common.Hash{}, 0)
defer db.Close() // tab, _ := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "", nil)
// defer tab.Close()
// lookup on empty table returns no nodes //
if results := tab.lookup(lookupTestnet.target, false); len(results) > 0 { // // lookup on empty table returns no nodes
t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) // if results := tab.Lookup(lookupTestnet.target); len(results) > 0 {
} // t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
// seed table with initial node (otherwise lookup will terminate immediately) // }
seedKey, _ := decodePubkey(lookupTestnet.dists[256][0]) // // seed table with initial node (otherwise lookup will terminate immediately)
seed := wrapNode(enode.NewV4(seedKey, net.IP{}, 0, 256)) // seed := NewNode(lookupTestnet.dists[256][0], net.IP{}, 256, 0)
tab.stuff([]*node{seed}) // tab.stuff([]*Node{seed})
//
results := tab.lookup(lookupTestnet.target, true) // results := tab.Lookup(lookupTestnet.target)
t.Logf("results:") // t.Logf("results:")
for _, e := range results { // for _, e := range results {
t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.targetSha, e.ID()), e.ID().Bytes()) // t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:])
} // }
if len(results) != bucketSize { // if len(results) != bucketSizeTest {
t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) // t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSizeTest)
} // }
if hasDuplicates(results) { // if hasDuplicates(results) {
t.Errorf("result set contains duplicate entries") // t.Errorf("result set contains duplicate entries")
} // }
if !sortedByDistanceTo(lookupTestnet.targetSha, results) { // if !sortedByDistanceTo(lookupTestnet.targetSha, results) {
t.Errorf("result set not sorted by distance to target") // t.Errorf("result set not sorted by distance to target")
} // }
// TODO: check result nodes are actually closest // // TODO: check result nodes are actually closest
} //}
// This is the test network for the Lookup test. // This is the test network for the Lookup test.
// The nodes were obtained by running testnet.mine with a random NodeID as target. // The nodes were obtained by running testnet.mine with a random NodeID as target.
var lookupTestnet = &preminedTestnet{ var lookupTestnet = &preminedTestnet{
target: hexEncPubkey("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"),
targetSha: enode.HexID("5c944ee51c5ae9f72a95eccb8aed0374eecb5119d720cbea6813e8e0d6ad9261"), targetSha: common.Hash{0x5c, 0x94, 0x4e, 0xe5, 0x1c, 0x5a, 0xe9, 0xf7, 0x2a, 0x95, 0xec, 0xcb, 0x8a, 0xed, 0x3, 0x74, 0xee, 0xcb, 0x51, 0x19, 0xd7, 0x20, 0xcb, 0xea, 0x68, 0x13, 0xe8, 0xe0, 0xd6, 0xad, 0x92, 0x61},
dists: [257][]encPubkey{ dists: [257][]NodeID{
240: { 240: {
hexEncPubkey("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"),
hexEncPubkey("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"),
}, },
244: { 244: {
hexEncPubkey("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"),
}, },
246: { 246: {
hexEncPubkey("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"),
hexEncPubkey("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"),
hexEncPubkey("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"),
}, },
247: { 247: {
hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
hexEncPubkey("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"),
hexEncPubkey("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"),
hexEncPubkey("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"),
hexEncPubkey("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"),
hexEncPubkey("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"),
hexEncPubkey("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"),
}, },
248: { 248: {
hexEncPubkey("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"),
hexEncPubkey("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"),
hexEncPubkey("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"),
hexEncPubkey("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"),
hexEncPubkey("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"),
hexEncPubkey("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"),
hexEncPubkey("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"),
hexEncPubkey("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"),
hexEncPubkey("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"),
hexEncPubkey("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"),
hexEncPubkey("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"),
hexEncPubkey("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"),
hexEncPubkey("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"),
hexEncPubkey("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"),
hexEncPubkey("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"),
hexEncPubkey("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"),
}, },
249: { 249: {
hexEncPubkey("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"),
hexEncPubkey("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"),
hexEncPubkey("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"),
hexEncPubkey("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"),
hexEncPubkey("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"),
hexEncPubkey("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"),
hexEncPubkey("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"),
hexEncPubkey("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"),
hexEncPubkey("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"),
hexEncPubkey("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"),
hexEncPubkey("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"),
hexEncPubkey("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"),
hexEncPubkey("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"),
hexEncPubkey("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"),
hexEncPubkey("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"),
hexEncPubkey("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"),
}, },
250: { 250: {
hexEncPubkey("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"),
hexEncPubkey("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"),
hexEncPubkey("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"),
hexEncPubkey("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"),
hexEncPubkey("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"),
hexEncPubkey("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"),
hexEncPubkey("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"),
hexEncPubkey("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"),
hexEncPubkey("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"),
hexEncPubkey("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"),
hexEncPubkey("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"),
hexEncPubkey("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"),
hexEncPubkey("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"),
hexEncPubkey("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"),
hexEncPubkey("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"),
hexEncPubkey("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"),
}, },
251: { 251: {
hexEncPubkey("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"),
hexEncPubkey("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"),
hexEncPubkey("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"),
hexEncPubkey("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"),
hexEncPubkey("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"),
hexEncPubkey("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"),
hexEncPubkey("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"),
hexEncPubkey("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"),
hexEncPubkey("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"),
hexEncPubkey("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"),
hexEncPubkey("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"),
hexEncPubkey("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"),
hexEncPubkey("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"),
hexEncPubkey("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"),
hexEncPubkey("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"),
hexEncPubkey("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"),
}, },
252: { 252: {
hexEncPubkey("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"),
hexEncPubkey("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"),
hexEncPubkey("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"),
hexEncPubkey("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"),
hexEncPubkey("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"),
hexEncPubkey("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"),
hexEncPubkey("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"),
hexEncPubkey("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"),
hexEncPubkey("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"),
hexEncPubkey("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"),
hexEncPubkey("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"),
hexEncPubkey("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"),
hexEncPubkey("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"),
hexEncPubkey("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"),
hexEncPubkey("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"),
hexEncPubkey("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"),
}, },
253: { 253: {
hexEncPubkey("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"),
hexEncPubkey("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"),
hexEncPubkey("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"),
hexEncPubkey("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"),
hexEncPubkey("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"),
hexEncPubkey("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"),
hexEncPubkey("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"),
hexEncPubkey("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"),
hexEncPubkey("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"),
hexEncPubkey("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"),
hexEncPubkey("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"),
hexEncPubkey("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"),
hexEncPubkey("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"),
hexEncPubkey("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"),
hexEncPubkey("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"),
hexEncPubkey("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"),
}, },
254: { 254: {
hexEncPubkey("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"),
hexEncPubkey("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"),
hexEncPubkey("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"),
hexEncPubkey("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"),
hexEncPubkey("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"),
hexEncPubkey("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"),
hexEncPubkey("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"),
hexEncPubkey("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"),
hexEncPubkey("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"),
hexEncPubkey("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"),
hexEncPubkey("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"),
hexEncPubkey("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"),
hexEncPubkey("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"),
hexEncPubkey("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"),
hexEncPubkey("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"),
hexEncPubkey("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"),
}, },
255: { 255: {
hexEncPubkey("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"),
hexEncPubkey("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"),
hexEncPubkey("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"),
hexEncPubkey("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"),
hexEncPubkey("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"),
hexEncPubkey("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"),
hexEncPubkey("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"),
hexEncPubkey("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"),
hexEncPubkey("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"),
hexEncPubkey("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"),
hexEncPubkey("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"),
hexEncPubkey("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"),
hexEncPubkey("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"),
hexEncPubkey("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"),
hexEncPubkey("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"),
hexEncPubkey("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"),
}, },
256: { 256: {
hexEncPubkey("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"),
hexEncPubkey("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"),
hexEncPubkey("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"),
hexEncPubkey("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"),
hexEncPubkey("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"),
hexEncPubkey("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"),
hexEncPubkey("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"),
hexEncPubkey("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"),
hexEncPubkey("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"),
hexEncPubkey("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"),
hexEncPubkey("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"),
hexEncPubkey("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"),
hexEncPubkey("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"),
hexEncPubkey("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"),
hexEncPubkey("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"),
hexEncPubkey("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"),
}, },
}, },
} }
type preminedTestnet struct { type preminedTestnet struct {
target encPubkey target NodeID
targetSha enode.ID // sha3(target) targetSha common.Hash // sha3(target)
dists [hashBits + 1][]encPubkey dists [hashBits + 1][]NodeID
} }
func (tn *preminedTestnet) self() *enode.Node { func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
return nullNode
}
func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
// current log distance is encoded in port number // current log distance is encoded in port number
// fmt.Println("findnode query at dist", toaddr.Port) // fmt.Println("findnode query at dist", toaddr.Port)
if toaddr.Port == 0 { if toaddr.Port == 0 {
panic("query to node at distance 0") panic("query to node at distance 0")
} }
next := toaddr.Port - 1 next := uint16(toaddr.Port) - 1
var result []*node var result []*Node
for i, ekey := range tn.dists[toaddr.Port] { for i, id := range tn.dists[toaddr.Port] {
key, _ := decodePubkey(ekey) result = append(result, NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i)))
node := wrapNode(enode.NewV4(key, net.ParseIP("127.0.0.1"), i, next))
result = append(result, node)
} }
return result, nil return result, nil
} }
func (*preminedTestnet) close() {} func (*preminedTestnet) close() {}
func (*preminedTestnet) waitping(from enode.ID) error { return nil } func (*preminedTestnet) waitping(from NodeID) error { return nil }
func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil } func (*preminedTestnet) ping(toid NodeID, toaddr *net.UDPAddr) error { return nil }
// mine generates a testnet struct literal with nodes at // mine generates a testnet struct literal with nodes at
// various distances to the given target. // various distances to the given target.
func (tn *preminedTestnet) mine(target encPubkey) { func (n *preminedTestnet) mine(target NodeID) {
tn.target = target n.target = target
tn.targetSha = tn.target.id() n.targetSha = crypto.Keccak256Hash(n.target[:])
found := 0 found := 0
for found < bucketSize*10 { for found < bucketSize*10 {
k := newkey() k := newkey()
key := encodePubkey(&k.PublicKey) id := PubkeyID(&k.PublicKey)
ld := enode.LogDist(tn.targetSha, key.id()) sha := crypto.Keccak256Hash(id[:])
if len(tn.dists[ld]) < bucketSize { ld := logdist(n.targetSha, sha)
tn.dists[ld] = append(tn.dists[ld], key) if len(n.dists[ld]) < bucketSize {
n.dists[ld] = append(n.dists[ld], id)
fmt.Println("found ID with ld", ld) fmt.Println("found ID with ld", ld)
found++ found++
} }
} }
fmt.Println("&preminedTestnet{") fmt.Println("&preminedTestnet{")
fmt.Printf(" target: %#v,\n", tn.target) fmt.Printf(" target: %#v,\n", n.target)
fmt.Printf(" targetSha: %#v,\n", tn.targetSha) fmt.Printf(" targetSha: %#v,\n", n.targetSha)
fmt.Printf(" dists: [%d][]encPubkey{\n", len(tn.dists)) fmt.Printf(" dists: [%d][]NodeID{\n", len(n.dists))
for ld, ns := range tn.dists { for ld, ns := range n.dists {
if len(ns) == 0 { if len(ns) == 0 {
continue continue
} }
fmt.Printf(" %d: []encPubkey{\n", ld) fmt.Printf(" %d: []NodeID{\n", ld)
for _, n := range ns { for _, n := range ns {
fmt.Printf(" hexEncPubkey(\"%x\"),\n", n[:]) fmt.Printf(" MustHexID(\"%x\"),\n", n[:])
} }
fmt.Println(" },") fmt.Println(" },")
} }
@ -569,6 +616,40 @@ func (tn *preminedTestnet) mine(target encPubkey) {
fmt.Println("}") fmt.Println("}")
} }
func hasDuplicates(slice []*Node) bool {
seen := make(map[NodeID]bool)
for i, e := range slice {
if e == nil {
panic(fmt.Sprintf("nil *Node at %d", i))
}
if seen[e.ID] {
return true
}
seen[e.ID] = true
}
return false
}
func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool {
var last common.Hash
for i, e := range slice {
if i > 0 && distcmp(distbase, e.sha, last) < 0 {
return false
}
last = e.sha
}
return true
}
func contains(ns []*Node, id NodeID) bool {
for _, n := range ns {
if n.ID == id {
return true
}
}
return false
}
// gen wraps quick.Value so it's easier to use. // gen wraps quick.Value so it's easier to use.
// it generates a random value of the given value's type. // it generates a random value of the given value's type.
func gen(typ interface{}, rand *rand.Rand) interface{} { func gen(typ interface{}, rand *rand.Rand) interface{} {
@ -579,13 +660,6 @@ func gen(typ interface{}, rand *rand.Rand) interface{} {
return v.Interface() return v.Interface()
} }
func quickcfg() *quick.Config {
return &quick.Config{
MaxCount: 5000,
Rand: rand.New(rand.NewSource(time.Now().Unix())),
}
}
func newkey() *ecdsa.PrivateKey { func newkey() *ecdsa.PrivateKey {
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {

View file

@ -1,182 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package discover
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
"math/rand"
"net"
"sync"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
)
var nullNode *enode.Node
func init() {
var r enr.Record
r.Set(enr.IP{0, 0, 0, 0})
nullNode = enode.SignNull(&r, enode.ID{})
}
func newTestTable(t transport) (*Table, *enode.DB) {
db, _ := enode.OpenDB("")
tab, _ := newTable(t, db, nil)
return tab, db
}
// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node {
var r enr.Record
r.Set(enr.IP(ip))
return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
}
// idAtDistance returns a random hash such that enode.LogDist(a, b) == n
func idAtDistance(a enode.ID, n int) (b enode.ID) {
if n == 0 {
return a
}
// flip bit at position n, fill the rest with random bits
b = a
pos := len(a) - n/8 - 1
bit := byte(0x01) << (byte(n%8) - 1)
if bit == 0 {
pos++
bit = 0x80
}
b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
for i := pos + 1; i < len(a); i++ {
b[i] = byte(rand.Intn(255))
}
return b
}
func intIP(i int) net.IP {
return net.IP{byte(i), 0, 2, byte(i)}
}
// fillBucket inserts nodes into the given bucket until it is full.
func fillBucket(tab *Table, n *node) (last *node) {
ld := enode.LogDist(tab.self().ID(), n.ID())
b := tab.bucket(n.ID())
for len(b.entries) < bucketSize {
b.entries = append(b.entries, nodeAtDistance(tab.self().ID(), ld, intIP(ld)))
}
return b.entries[bucketSize-1]
}
type pingRecorder struct {
mu sync.Mutex
dead, pinged map[enode.ID]bool
n *enode.Node
}
func newPingRecorder() *pingRecorder {
var r enr.Record
r.Set(enr.IP{0, 0, 0, 0})
n := enode.SignNull(&r, enode.ID{})
return &pingRecorder{
dead: make(map[enode.ID]bool),
pinged: make(map[enode.ID]bool),
n: n,
}
}
func (t *pingRecorder) self() *enode.Node {
return nullNode
}
func (t *pingRecorder) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
return nil, nil
}
func (t *pingRecorder) waitping(from enode.ID) error {
return nil // remote always pings
}
func (t *pingRecorder) ping(toid enode.ID, toaddr *net.UDPAddr) error {
t.mu.Lock()
defer t.mu.Unlock()
t.pinged[toid] = true
if t.dead[toid] {
return errTimeout
} else {
return nil
}
}
func (t *pingRecorder) close() {}
func hasDuplicates(slice []*node) bool {
seen := make(map[enode.ID]bool)
for i, e := range slice {
if e == nil {
panic(fmt.Sprintf("nil *Node at %d", i))
}
if seen[e.ID()] {
return true
}
seen[e.ID()] = true
}
return false
}
func contains(ns []*node, id enode.ID) bool {
for _, n := range ns {
if n.ID() == id {
return true
}
}
return false
}
func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
var last enode.ID
for i, e := range slice {
if i > 0 && enode.DistCmp(distbase, e.ID(), last) < 0 {
return false
}
last = e.ID()
}
return true
}
func hexEncPubkey(h string) (ret encPubkey) {
b, err := hex.DecodeString(h)
if err != nil {
panic(err)
}
if len(b) != len(ret) {
panic("invalid length")
}
copy(ret[:], b)
return ret
}
func hexPubkey(h string) *ecdsa.PublicKey {
k, err := decodePubkey(hexEncPubkey(h))
if err != nil {
panic(err)
}
return k
}

View file

@ -23,12 +23,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -49,9 +48,9 @@ var (
// Timeouts // Timeouts
const ( const (
respTimeout = 500 * time.Millisecond respTimeout = 500 * time.Millisecond
expiration = 20 * time.Second sendTimeout = 500 * time.Millisecond
bondExpiration = 24 * time.Hour expiration = 20 * time.Second
ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP
ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning
@ -92,7 +91,7 @@ type (
// findnode is a query for nodes close to the given target. // findnode is a query for nodes close to the given target.
findnode struct { findnode struct {
Target encPubkey Target NodeID // doesn't need to be an actual public key
Expiration uint64 Expiration uint64
// Ignore additional fields (for forward compatibility). // Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"` Rest []rlp.RawValue `rlp:"tail"`
@ -110,7 +109,7 @@ type (
IP net.IP // len 4 for IPv4 or 16 for IPv6 IP net.IP // len 4 for IPv4 or 16 for IPv6
UDP uint16 // for discovery protocol UDP uint16 // for discovery protocol
TCP uint16 // for RLPx protocol TCP uint16 // for RLPx protocol
ID encPubkey ID NodeID
} }
rpcEndpoint struct { rpcEndpoint struct {
@ -121,16 +120,14 @@ type (
) )
func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
ip := net.IP{} ip := addr.IP.To4()
if ip4 := addr.IP.To4(); ip4 != nil { if ip == nil {
ip = ip4 ip = addr.IP.To16()
} else if ip6 := addr.IP.To16(); ip6 != nil {
ip = ip6
} }
return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
} }
func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {
if rn.UDP <= 1024 { if rn.UDP <= 1024 {
return nil, errors.New("low port") return nil, errors.New("low port")
} }
@ -140,26 +137,17 @@ func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) {
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
return nil, errors.New("not contained in netrestrict whitelist") return nil, errors.New("not contained in netrestrict whitelist")
} }
key, err := decodePubkey(rn.ID) n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP)
if err != nil { err := n.validateComplete()
return nil, err
}
n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
err = n.ValidateComplete()
return n, err return n, err
} }
func nodeToRPC(n *node) rpcNode { func nodeToRPC(n *Node) rpcNode {
var key ecdsa.PublicKey return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP}
var ekey encPubkey
if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
ekey = encodePubkey(&key)
}
return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
} }
type packet interface { type packet interface {
handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error
name() string name() string
} }
@ -170,19 +158,20 @@ type conn interface {
LocalAddr() net.Addr LocalAddr() net.Addr
} }
// udp implements the discovery v4 UDP wire protocol. // udp implements the RPC protocol.
type udp struct { type udp struct {
conn conn conn conn
netrestrict *netutil.Netlist netrestrict *netutil.Netlist
priv *ecdsa.PrivateKey priv *ecdsa.PrivateKey
localNode *enode.LocalNode ourEndpoint rpcEndpoint
db *enode.DB
tab *Table
wg sync.WaitGroup
addpending chan *pending addpending chan *pending
gotreply chan reply gotreply chan reply
closing chan struct{}
closing chan struct{}
nat nat.Interface
*Table
} }
// pending represents a pending reply. // pending represents a pending reply.
@ -196,7 +185,7 @@ type udp struct {
// to all the callback functions for that node. // to all the callback functions for that node.
type pending struct { type pending struct {
// these fields must match in the reply. // these fields must match in the reply.
from enode.ID from NodeID
ptype byte ptype byte
// time when the request must complete // time when the request must complete
@ -214,7 +203,7 @@ type pending struct {
} }
type reply struct { type reply struct {
from enode.ID from NodeID
ptype byte ptype byte
data interface{} data interface{}
// loop indicates whether there was // loop indicates whether there was
@ -234,106 +223,82 @@ type Config struct {
PrivateKey *ecdsa.PrivateKey PrivateKey *ecdsa.PrivateKey
// These settings are optional: // These settings are optional:
NetRestrict *netutil.Netlist // network whitelist AnnounceAddr *net.UDPAddr // local address announced in the DHT
Bootnodes []*enode.Node // list of bootstrap nodes NodeDBPath string // if set, the node database is stored at this filesystem location
Unhandled chan<- ReadPacket // unhandled packets are sent on this channel NetRestrict *netutil.Netlist // network whitelist
Bootnodes []*Node // list of bootstrap nodes
Unhandled chan<- ReadPacket // unhandled packets are sent on this channel
} }
// ListenUDP returns a new table that listens for UDP packets on laddr. // ListenUDP returns a new table that listens for UDP packets on laddr.
func ListenUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, error) { func ListenUDP(c conn, cfg Config) (*Table, error) {
tab, _, err := newUDP(c, ln, cfg) tab, _, err := newUDP(c, cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Info("UDP listener up", "self", tab.self)
return tab, nil return tab, nil
} }
func newUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, *udp, error) { func newUDP(c conn, cfg Config) (*Table, *udp, error) {
udp := &udp{ udp := &udp{
conn: c, conn: c,
priv: cfg.PrivateKey, priv: cfg.PrivateKey,
netrestrict: cfg.NetRestrict, netrestrict: cfg.NetRestrict,
localNode: ln,
db: ln.Database(),
closing: make(chan struct{}), closing: make(chan struct{}),
gotreply: make(chan reply), gotreply: make(chan reply),
addpending: make(chan *pending), addpending: make(chan *pending),
} }
tab, err := newTable(udp, ln.Database(), cfg.Bootnodes) realaddr := c.LocalAddr().(*net.UDPAddr)
if cfg.AnnounceAddr != nil {
realaddr = cfg.AnnounceAddr
}
// TODO: separate TCP port
udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port))
tab, err := newTable(udp, PubkeyID(&cfg.PrivateKey.PublicKey), realaddr, cfg.NodeDBPath, cfg.Bootnodes)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
udp.tab = tab udp.Table = tab
udp.wg.Add(2)
go udp.loop() go udp.loop()
go udp.readLoop(cfg.Unhandled) go udp.readLoop(cfg.Unhandled)
return udp.tab, udp, nil return udp.Table, udp, nil
}
func (t *udp) self() *enode.Node {
return t.localNode.Node()
} }
func (t *udp) close() { func (t *udp) close() {
close(t.closing) close(t.closing)
t.conn.Close() t.conn.Close()
t.wg.Wait() // TODO: wait for the loops to end.
}
func (t *udp) ourEndpoint() rpcEndpoint {
n := t.self()
a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
return makeEndpoint(a, uint16(n.TCP()))
} }
// ping sends a ping message to the given node and waits for a reply. // ping sends a ping message to the given node and waits for a reply.
func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error { func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error {
return <-t.sendPing(toid, toaddr, nil)
}
// sendPing sends a ping message to the given node and invokes the callback
// when the reply arrives.
func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error {
req := &ping{ req := &ping{
Version: 4, Version: Version,
From: t.ourEndpoint(), From: t.ourEndpoint,
To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB
Expiration: uint64(time.Now().Add(expiration).Unix()), Expiration: uint64(time.Now().Add(expiration).Unix()),
} }
packet, hash, err := encodePacket(t.priv, pingXDC, req) packet, hash, err := encodePacket(t.priv, pingXDC, req)
if err != nil { if err != nil {
errc := make(chan error, 1) return err
errc <- err
return errc
} }
errc := t.pending(toid, pongPacket, func(p interface{}) bool { errc := t.pending(toid, pongPacket, func(p interface{}) bool {
ok := bytes.Equal(p.(*pong).ReplyTok, hash) return bytes.Equal(p.(*pong).ReplyTok, hash)
if ok && callback != nil {
callback()
}
return ok
}) })
t.localNode.UDPContact(toaddr)
t.write(toaddr, req.name(), packet) t.write(toaddr, req.name(), packet)
return errc return <-errc
} }
func (t *udp) waitping(from enode.ID) error { func (t *udp) waitping(from NodeID) error {
return <-t.pending(from, pingXDC, func(interface{}) bool { return true }) return <-t.pending(from, pingXDC, func(interface{}) bool { return true })
} }
// findnode sends a findnode request to the given node and waits until // findnode sends a findnode request to the given node and waits until
// the node has sent up to k neighbors. // the node has sent up to k neighbors.
func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
// If we haven't seen a ping from the destination node for a while, it won't remember nodes := make([]*Node, 0, bucketSize)
// our endpoint proof and reject findnode. Solicit a ping first.
if time.Since(t.db.LastPingReceived(toid)) > bondExpiration {
t.ping(toid, toaddr)
t.waitping(toid)
}
nodes := make([]*node, 0, bucketSize)
nreceived := 0 nreceived := 0
errc := t.pending(toid, neighborsPacket, func(r interface{}) bool { errc := t.pending(toid, neighborsPacket, func(r interface{}) bool {
reply := r.(*neighbors) reply := r.(*neighbors)
@ -358,7 +323,7 @@ func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]
// pending adds a reply callback to the pending reply queue. // pending adds a reply callback to the pending reply queue.
// see the documentation of type pending for a detailed explanation. // see the documentation of type pending for a detailed explanation.
func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool) <-chan error { func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <-chan error {
ch := make(chan error, 1) ch := make(chan error, 1)
p := &pending{from: id, ptype: ptype, callback: callback, errc: ch} p := &pending{from: id, ptype: ptype, callback: callback, errc: ch}
select { select {
@ -370,7 +335,7 @@ func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool)
return ch return ch
} }
func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool { func (t *udp) handleReply(from NodeID, ptype byte, req packet) bool {
matched := make(chan bool, 1) matched := make(chan bool, 1)
select { select {
case t.gotreply <- reply{from, ptype, req, matched}: case t.gotreply <- reply{from, ptype, req, matched}:
@ -384,8 +349,6 @@ func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool {
// loop runs in its own goroutine. it keeps track of // loop runs in its own goroutine. it keeps track of
// the refresh timer and the pending reply queue. // the refresh timer and the pending reply queue.
func (t *udp) loop() { func (t *udp) loop() {
defer t.wg.Done()
var ( var (
plist = list.New() plist = list.New()
timeout = time.NewTimer(0) timeout = time.NewTimer(0)
@ -547,11 +510,10 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet,
// readLoop runs in its own goroutine. it handles incoming UDP packets. // readLoop runs in its own goroutine. it handles incoming UDP packets.
func (t *udp) readLoop(unhandled chan<- ReadPacket) { func (t *udp) readLoop(unhandled chan<- ReadPacket) {
defer t.wg.Done() defer t.conn.Close()
if unhandled != nil { if unhandled != nil {
defer close(unhandled) defer close(unhandled)
} }
// Discovery packets are defined to be no larger than 1280 bytes. // Discovery packets are defined to be no larger than 1280 bytes.
// Packets larger than this size will be cut at the end and treated // Packets larger than this size will be cut at the end and treated
// as invalid because their hash won't match. // as invalid because their hash won't match.
@ -587,20 +549,19 @@ func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
return err return err
} }
func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
if len(buf) < headSize+1 { if len(buf) < headSize+1 {
return nil, encPubkey{}, nil, errPacketTooSmall return nil, NodeID{}, nil, errPacketTooSmall
} }
hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:]
shouldhash := crypto.Keccak256(buf[macSize:]) shouldhash := crypto.Keccak256(buf[macSize:])
if !bytes.Equal(hash, shouldhash) { if !bytes.Equal(hash, shouldhash) {
return nil, encPubkey{}, nil, errBadHash return nil, NodeID{}, nil, errBadHash
} }
fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig) fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig)
if err != nil { if err != nil {
return nil, fromKey, hash, err return nil, NodeID{}, hash, err
} }
var req packet var req packet
switch ptype := sigdata[0]; ptype { switch ptype := sigdata[0]; ptype {
case pingXDC: case pingXDC:
@ -612,80 +573,68 @@ func decodePacket(buf []byte) (packet, encPubkey, []byte, error) {
case neighborsPacket: case neighborsPacket:
req = new(neighbors) req = new(neighbors)
default: default:
return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) return nil, fromID, hash, fmt.Errorf("unknown type: %d", ptype)
} }
s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
err = s.Decode(req) err = s.Decode(req)
return req, fromKey, hash, err return req, fromID, hash, err
} }
func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
if expired(req.Expiration) { if expired(req.Expiration) {
return errExpired return errExpired
} }
key, err := decodePubkey(fromKey)
if err != nil {
return fmt.Errorf("invalid public key: %v", err)
}
t.send(from, pongPacket, &pong{ t.send(from, pongPacket, &pong{
To: makeEndpoint(from, req.From.TCP), To: makeEndpoint(from, req.From.TCP),
ReplyTok: mac, ReplyTok: mac,
Expiration: uint64(time.Now().Add(expiration).Unix()), Expiration: uint64(time.Now().Add(expiration).Unix()),
}) })
n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port)) if !t.handleReply(fromID, pingXDC, req) {
t.handleReply(n.ID(), pingXDC, req) // Note: we're ignoring the provided IP address right now
if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration { go t.bond(true, fromID, from, req.From.TCP)
t.sendPing(n.ID(), from, func() { t.tab.addThroughPing(n) })
} else {
t.tab.addThroughPing(n)
} }
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
t.db.UpdateLastPingReceived(n.ID(), time.Now())
return nil return nil
} }
func (req *ping) name() string { return "PING XDC/v4" } func (req *ping) name() string { return "PING XDC/v4" }
func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { func (req *pong) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
if expired(req.Expiration) { if expired(req.Expiration) {
return errExpired return errExpired
} }
fromID := fromKey.id()
if !t.handleReply(fromID, pongPacket, req) { if !t.handleReply(fromID, pongPacket, req) {
return errUnsolicitedReply return errUnsolicitedReply
} }
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
t.db.UpdateLastPongReceived(fromID, time.Now())
return nil return nil
} }
func (req *pong) name() string { return "PONG/v4" } func (req *pong) name() string { return "PONG/v4" }
func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
if expired(req.Expiration) { if expired(req.Expiration) {
return errExpired return errExpired
} }
fromID := fromKey.id() if !t.db.hasBond(fromID) {
if time.Since(t.db.LastPongReceived(fromID)) > bondExpiration { // No bond exists, we don't process the packet. This prevents
// No endpoint proof pong exists, we don't process the packet. This prevents an // an attack vector where the discovery protocol could be used
// attack vector where the discovery protocol could be used to amplify traffic in a // to amplify traffic in a DDOS attack. A malicious actor
// DDOS attack. A malicious actor would send a findnode request with the IP address // would send a findnode request with the IP address and UDP
// and UDP port of the target as the source address. The recipient of the findnode // port of the target as the source address. The recipient of
// packet would then send a neighbors packet (which is a much bigger packet than // the findnode packet would then send a neighbors packet
// findnode) to the victim. // (which is a much bigger packet than findnode) to the victim.
return errUnknownNode return errUnknownNode
} }
target := enode.ID(crypto.Keccak256Hash(req.Target[:])) target := crypto.Keccak256Hash(req.Target[:])
t.tab.mutex.Lock() t.mutex.Lock()
closest := t.tab.closest(target, bucketSize).entries closest := t.closest(target, bucketSize).entries
t.tab.mutex.Unlock() t.mutex.Unlock()
log.Trace("find neighbors ", "from", from, "fromID", fromID, "closest", len(closest)) log.Trace("find neighbors ", "from", from, "fromID", fromID, "closest", len(closest))
p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
var sent bool var sent bool
// Send neighbors in chunks with at most maxNeighbors per packet // Send neighbors in chunks with at most maxNeighbors per packet
// to stay below the 1280 byte limit. // to stay below the 1280 byte limit.
for _, n := range closest { for _, n := range closest {
if netutil.CheckRelayIP(from.IP, n.IP()) == nil { if netutil.CheckRelayIP(from.IP, n.IP) == nil {
p.Nodes = append(p.Nodes, nodeToRPC(n)) p.Nodes = append(p.Nodes, nodeToRPC(n))
} }
if len(p.Nodes) == maxNeighbors { if len(p.Nodes) == maxNeighbors {
@ -702,11 +651,11 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []
func (req *findnode) name() string { return "FINDNODE/v4" } func (req *findnode) name() string { return "FINDNODE/v4" }
func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
if expired(req.Expiration) { if expired(req.Expiration) {
return errExpired return errExpired
} }
if !t.handleReply(fromKey.id(), neighborsPacket, req) { if !t.handleReply(fromID, neighborsPacket, req) {
return errUnsolicitedReply return errUnsolicitedReply
} }
return nil return nil

View file

@ -35,7 +35,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
) )
@ -47,7 +46,7 @@ func init() {
// shared test variables // shared test variables
var ( var (
futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) futureExp = uint64(time.Now().Add(10 * time.Hour).Unix())
testTarget = encPubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2}
testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4}
testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6}
@ -71,9 +70,7 @@ func newUDPTest(t *testing.T) *udpTest {
remotekey: newkey(), remotekey: newkey(),
remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303}, remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
} }
db, _ := enode.OpenDB("") test.table, test.udp, _ = newUDP(test.pipe, Config{PrivateKey: test.localkey})
ln := enode.NewLocalNode(db, test.localkey)
test.table, test.udp, _ = newUDP(test.pipe, ln, Config{PrivateKey: test.localkey})
// Wait for initial refresh so the table doesn't send unexpected findnode. // Wait for initial refresh so the table doesn't send unexpected findnode.
<-test.table.initDone <-test.table.initDone
return test return test
@ -139,7 +136,7 @@ func TestUDP_pingTimeout(t *testing.T) {
defer test.table.Close() defer test.table.Close()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
toid := enode.ID{1, 2, 3, 4} toid := NodeID{1, 2, 3, 4}
if err := test.udp.ping(toid, toaddr); err != errTimeout { if err := test.udp.ping(toid, toaddr); err != errTimeout {
t.Error("expected timeout error, got", err) t.Error("expected timeout error, got", err)
} }
@ -223,8 +220,8 @@ func TestUDP_findnodeTimeout(t *testing.T) {
defer test.table.Close() defer test.table.Close()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
toid := enode.ID{1, 2, 3, 4} toid := NodeID{1, 2, 3, 4}
target := encPubkey{4, 5, 6, 7} target := NodeID{4, 5, 6, 7}
result, err := test.udp.findnode(toid, toaddr, target) result, err := test.udp.findnode(toid, toaddr, target)
if err != errTimeout { if err != errTimeout {
t.Error("expected timeout error, got", err) t.Error("expected timeout error, got", err)
@ -242,30 +239,28 @@ func TestUDP_findnode(t *testing.T) {
// put a few nodes into the table. their exact // put a few nodes into the table. their exact
// distribution shouldn't matter much, although we need to // distribution shouldn't matter much, although we need to
// take care not to overflow any bucket. // take care not to overflow any bucket.
nodes := &nodesByDistance{target: testTarget.id()} targetHash := crypto.Keccak256Hash(testTarget[:])
for i := 0; i < bucketSize; i++ { nodes := &nodesByDistance{target: targetHash}
key := newkey() for i := 0; i < bucketSizeTest; i++ {
n := wrapNode(enode.NewV4(&key.PublicKey, net.IP{10, 13, 0, 1}, 0, i)) nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSizeTest)
nodes.push(n, bucketSize)
} }
test.table.stuff(nodes.entries) test.table.stuff(nodes.entries)
// ensure there's a bond with the test node, // ensure there's a bond with the test node,
// findnode won't be accepted otherwise. // findnode won't be accepted otherwise.
remoteID := encodePubkey(&test.remotekey.PublicKey).id() test.table.db.updateBondTime(PubkeyID(&test.remotekey.PublicKey), time.Now())
test.table.db.UpdateLastPongReceived(remoteID, time.Now())
// check that closest neighbors are returned. // check that closest neighbors are returned.
test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp}) test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp})
expected := test.table.closest(testTarget.id(), bucketSize) expected := test.table.closest(targetHash, bucketSizeTest)
waitNeighbors := func(want []*node) { waitNeighbors := func(want []*Node) {
test.waitPacketOut(func(p *neighbors) { test.waitPacketOut(func(p *neighbors) {
if len(p.Nodes) != len(want) { if len(p.Nodes) != len(want) {
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSizeTest) t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSizeTest)
} }
for i := range p.Nodes { for i := range p.Nodes {
if p.Nodes[i].ID.id() != want[i].ID() { if p.Nodes[i].ID != want[i].ID {
t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i]) t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i])
} }
} }
@ -279,13 +274,10 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.table.Close() defer test.table.Close()
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
test.table.db.UpdateLastPingReceived(rid, time.Now())
// queue a pending findnode request // queue a pending findnode request
resultc, errc := make(chan []*node), make(chan error) resultc, errc := make(chan []*Node), make(chan error)
go func() { go func() {
rid := encodePubkey(&test.remotekey.PublicKey).id() rid := PubkeyID(&test.remotekey.PublicKey)
ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
if err != nil && len(ns) == 0 { if err != nil && len(ns) == 0 {
errc <- err errc <- err
@ -303,11 +295,11 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
}) })
// send the reply as two packets. // send the reply as two packets.
list := []*node{ list := []*Node{
wrapNode(enode.MustParseV4("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")), MustParseNode("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"),
wrapNode(enode.MustParseV4("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")), MustParseNode("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"),
wrapNode(enode.MustParseV4("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")), MustParseNode("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"),
wrapNode(enode.MustParseV4("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")), MustParseNode("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"),
} }
rpclist := make([]rpcNode, len(list)) rpclist := make([]rpcNode, len(list))
for i := range list { for i := range list {
@ -332,8 +324,8 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
func TestUDP_successfulPing(t *testing.T) { func TestUDP_successfulPing(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
added := make(chan *node, 1) added := make(chan *Node, 1)
test.table.nodeAddedHook = func(n *node) { added <- n } test.table.nodeAddedHook = func(n *Node) { added <- n }
defer test.table.Close() defer test.table.Close()
// The remote side sends a ping packet to initiate the exchange. // The remote side sends a ping packet to initiate the exchange.
@ -358,13 +350,12 @@ func TestUDP_successfulPing(t *testing.T) {
// remote is unknown, the table pings back. // remote is unknown, the table pings back.
hash, _ := test.waitPacketOut(func(p *ping) error { hash, _ := test.waitPacketOut(func(p *ping) error {
if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint) {
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) t.Errorf("got ping.From %v, want %v", p.From, test.udp.ourEndpoint)
} }
wantTo := rpcEndpoint{ wantTo := rpcEndpoint{
// The mirrored UDP address is the UDP packet sender. // The mirrored UDP address is the UDP packet sender.
IP: test.remoteaddr.IP, IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
UDP: uint16(test.remoteaddr.Port),
TCP: 0, TCP: 0,
} }
if !reflect.DeepEqual(p.To, wantTo) { if !reflect.DeepEqual(p.To, wantTo) {
@ -378,18 +369,18 @@ func TestUDP_successfulPing(t *testing.T) {
// pong packet. // pong packet.
select { select {
case n := <-added: case n := <-added:
rid := encodePubkey(&test.remotekey.PublicKey).id() rid := PubkeyID(&test.remotekey.PublicKey)
if n.ID() != rid { if n.ID != rid {
t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid) t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid)
} }
if !n.IP().Equal(test.remoteaddr.IP) { if !n.IP.Equal(test.remoteaddr.IP) {
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP) t.Errorf("node has wrong IP: got %v, want: %v", n.IP, test.remoteaddr.IP)
} }
if int(n.UDP()) != test.remoteaddr.Port { if int(n.UDP) != test.remoteaddr.Port {
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port) t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP, test.remoteaddr.Port)
} }
if n.TCP() != int(testRemote.TCP) { if n.TCP != testRemote.TCP {
t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP) t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP, testRemote.TCP)
} }
case <-time.After(2 * time.Second): case <-time.After(2 * time.Second):
t.Errorf("node was not added within 2 seconds") t.Errorf("node was not added within 2 seconds")
@ -442,7 +433,7 @@ var testPackets = []struct {
{ {
input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
wantPacket: &findnode{ wantPacket: &findnode{
Target: hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), Target: MustHexID("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
Expiration: 1136239445, Expiration: 1136239445,
Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
}, },
@ -452,25 +443,25 @@ var testPackets = []struct {
wantPacket: &neighbors{ wantPacket: &neighbors{
Nodes: []rpcNode{ Nodes: []rpcNode{
{ {
ID: hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), ID: MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
IP: net.ParseIP("99.33.22.55").To4(), IP: net.ParseIP("99.33.22.55").To4(),
UDP: 4444, UDP: 4444,
TCP: 4445, TCP: 4445,
}, },
{ {
ID: hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), ID: MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
IP: net.ParseIP("1.2.3.4").To4(), IP: net.ParseIP("1.2.3.4").To4(),
UDP: 1, UDP: 1,
TCP: 1, TCP: 1,
}, },
{ {
ID: hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), ID: MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
UDP: 3333, UDP: 3333,
TCP: 3333, TCP: 3333,
}, },
{ {
ID: hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), ID: MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
UDP: 999, UDP: 999,
TCP: 1000, TCP: 1000,
@ -484,14 +475,13 @@ var testPackets = []struct {
func TestForwardCompatibility(t *testing.T) { func TestForwardCompatibility(t *testing.T) {
testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
wantNodeKey := encodePubkey(&testkey.PublicKey) wantNodeID := PubkeyID(&testkey.PublicKey)
for _, test := range testPackets { for _, test := range testPackets {
input, err := hex.DecodeString(test.input) input, err := hex.DecodeString(test.input)
if err != nil { if err != nil {
t.Fatalf("invalid hex: %s", test.input) t.Fatalf("invalid hex: %s", test.input)
} }
packet, nodekey, _, err := decodePacket(input) packet, nodeid, _, err := decodePacket(input)
if err != nil { if err != nil {
t.Errorf("did not accept packet %s\n%v", test.input, err) t.Errorf("did not accept packet %s\n%v", test.input, err)
continue continue
@ -499,8 +489,8 @@ func TestForwardCompatibility(t *testing.T) {
if !reflect.DeepEqual(packet, test.wantPacket) { if !reflect.DeepEqual(packet, test.wantPacket) {
t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
} }
if nodekey != wantNodeKey { if nodeid != wantNodeID {
t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) t.Errorf("got id %v\nwant id %v", nodeid, wantNodeID)
} }
} }
} }

View file

@ -238,8 +238,7 @@ type udp struct {
} }
// ListenUDP returns a new table that listens for UDP packets on laddr. // ListenUDP returns a new table that listens for UDP packets on laddr.
func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) { func ListenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) {
realaddr := conn.LocalAddr().(*net.UDPAddr)
transport, err := listenUDP(priv, conn, realaddr) transport, err := listenUDP(priv, conn, realaddr)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -1,160 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"crypto/ecdsa"
"fmt"
"io"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// List of known secure identity schemes.
var ValidSchemes = enr.SchemeMap{
"v4": V4ID{},
}
var ValidSchemesForTesting = enr.SchemeMap{
"v4": V4ID{},
"null": NullID{},
}
// v4ID is the "v4" identity scheme.
type V4ID struct{}
// SignV4 signs a record using the v4 scheme.
func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
// Copy r to avoid modifying it if signing fails.
cpy := *r
cpy.Set(enr.ID("v4"))
cpy.Set(Secp256k1(privkey.PublicKey))
h := sha3.NewKeccak256()
rlp.Encode(h, cpy.AppendElements(nil))
sig, err := crypto.Sign(h.Sum(nil), privkey)
if err != nil {
return err
}
sig = sig[:len(sig)-1] // remove v
if err = cpy.SetSig(V4ID{}, sig); err == nil {
*r = cpy
}
return err
}
func (V4ID) Verify(r *enr.Record, sig []byte) error {
var entry s256raw
if err := r.Load(&entry); err != nil {
return err
} else if len(entry) != 33 {
return fmt.Errorf("invalid public key")
}
h := sha3.NewKeccak256()
rlp.Encode(h, r.AppendElements(nil))
if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
return enr.ErrInvalidSig
}
return nil
}
func (V4ID) NodeAddr(r *enr.Record) []byte {
var pubkey Secp256k1
err := r.Load(&pubkey)
if err != nil {
return nil
}
buf := make([]byte, 64)
math.ReadBits(pubkey.X, buf[:32])
math.ReadBits(pubkey.Y, buf[32:])
return crypto.Keccak256(buf)
}
// Secp256k1 is the "secp256k1" key, which holds a public key.
type Secp256k1 ecdsa.PublicKey
func (v Secp256k1) ENRKey() string { return "secp256k1" }
// EncodeRLP implements rlp.Encoder.
func (v Secp256k1) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, crypto.CompressPubkey((*ecdsa.PublicKey)(&v)))
}
// DecodeRLP implements rlp.Decoder.
func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
buf, err := s.Bytes()
if err != nil {
return err
}
pk, err := crypto.DecompressPubkey(buf)
if err != nil {
return err
}
*v = (Secp256k1)(*pk)
return nil
}
// s256raw is an unparsed secp256k1 public key entry.
type s256raw []byte
func (s256raw) ENRKey() string { return "secp256k1" }
// v4CompatID is a weaker and insecure version of the "v4" scheme which only checks for the
// presence of a secp256k1 public key, but doesn't verify the signature.
type v4CompatID struct {
V4ID
}
func (v4CompatID) Verify(r *enr.Record, sig []byte) error {
var pubkey Secp256k1
return r.Load(&pubkey)
}
func signV4Compat(r *enr.Record, pubkey *ecdsa.PublicKey) {
r.Set((*Secp256k1)(pubkey))
if err := r.SetSig(v4CompatID{}, []byte{}); err != nil {
panic(err)
}
}
// NullID is the "null" ENR identity scheme. This scheme stores the node
// ID in the record without any signature.
type NullID struct{}
func (NullID) Verify(r *enr.Record, sig []byte) error {
return nil
}
func (NullID) NodeAddr(r *enr.Record) []byte {
var id ID
r.Load(enr.WithEntry("nulladdr", &id))
return id[:]
}
func SignNull(r *enr.Record, id ID) *Node {
r.Set(enr.ID("null"))
r.Set(enr.WithEntry("nulladdr", id))
if err := r.SetSig(NullID{}, []byte{}); err != nil {
panic(err)
}
return &Node{r: *r, id: id}
}

View file

@ -1,74 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
privkey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
pubkey = &privkey.PublicKey
)
func TestEmptyNodeID(t *testing.T) {
var r enr.Record
if addr := ValidSchemes.NodeAddr(&r); addr != nil {
t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
}
require.NoError(t, SignV4(&r, privkey))
expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
assert.Equal(t, expected, hex.EncodeToString(ValidSchemes.NodeAddr(&r)))
}
// Checks that failure to sign leaves the record unmodified.
func TestSignError(t *testing.T) {
invalidKey := &ecdsa.PrivateKey{D: new(big.Int), PublicKey: *pubkey}
var r enr.Record
emptyEnc, _ := rlp.EncodeToBytes(&r)
if err := SignV4(&r, invalidKey); err == nil {
t.Fatal("expected error from SignV4")
}
newEnc, _ := rlp.EncodeToBytes(&r)
if !bytes.Equal(newEnc, emptyEnc) {
t.Fatal("record modified even though signing failed")
}
}
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
func TestGetSetSecp256k1(t *testing.T) {
var r enr.Record
if err := SignV4(&r, privkey); err != nil {
t.Fatal(err)
}
var pk Secp256k1
require.NoError(t, r.Load(&pk))
assert.EqualValues(t, pubkey, &pk)
}

View file

@ -1,246 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"crypto/ecdsa"
"fmt"
"net"
"reflect"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil"
)
const (
// IP tracker configuration
iptrackMinStatements = 10
iptrackWindow = 5 * time.Minute
iptrackContactWindow = 10 * time.Minute
)
// LocalNode produces the signed node record of a local node, i.e. a node run in the
// current process. Setting ENR entries via the Set method updates the record. A new version
// of the record is signed on demand when the Node method is called.
type LocalNode struct {
cur atomic.Value // holds a non-nil node pointer while the record is up-to-date.
id ID
key *ecdsa.PrivateKey
db *DB
// everything below is protected by a lock
mu sync.Mutex
seq uint64
entries map[string]enr.Entry
udpTrack *netutil.IPTracker // predicts external UDP endpoint
staticIP net.IP
fallbackIP net.IP
fallbackUDP int
}
// NewLocalNode creates a local node.
func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
ln := &LocalNode{
id: PubkeyToIDV4(&key.PublicKey),
db: db,
key: key,
udpTrack: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
entries: make(map[string]enr.Entry),
}
ln.seq = db.localSeq(ln.id)
ln.invalidate()
return ln
}
// Database returns the node database associated with the local node.
func (ln *LocalNode) Database() *DB {
return ln.db
}
// Node returns the current version of the local node record.
func (ln *LocalNode) Node() *Node {
n := ln.cur.Load().(*Node)
if n != nil {
return n
}
// Record was invalidated, sign a new copy.
ln.mu.Lock()
defer ln.mu.Unlock()
ln.sign()
return ln.cur.Load().(*Node)
}
// ID returns the local node ID.
func (ln *LocalNode) ID() ID {
return ln.id
}
// Set puts the given entry into the local record, overwriting
// any existing value.
func (ln *LocalNode) Set(e enr.Entry) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.set(e)
}
func (ln *LocalNode) set(e enr.Entry) {
val, exists := ln.entries[e.ENRKey()]
if !exists || !reflect.DeepEqual(val, e) {
ln.entries[e.ENRKey()] = e
ln.invalidate()
}
}
// Delete removes the given entry from the local record.
func (ln *LocalNode) Delete(e enr.Entry) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.delete(e)
}
func (ln *LocalNode) delete(e enr.Entry) {
_, exists := ln.entries[e.ENRKey()]
if exists {
delete(ln.entries, e.ENRKey())
ln.invalidate()
}
}
// SetStaticIP sets the local IP to the given one unconditionally.
// This disables endpoint prediction.
func (ln *LocalNode) SetStaticIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.staticIP = ip
ln.updateEndpoints()
}
// SetFallbackIP sets the last-resort IP address. This address is used
// if no endpoint prediction can be made and no static IP is set.
func (ln *LocalNode) SetFallbackIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.fallbackIP = ip
ln.updateEndpoints()
}
// SetFallbackUDP sets the last-resort UDP port. This port is used
// if no endpoint prediction can be made.
func (ln *LocalNode) SetFallbackUDP(port int) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.fallbackUDP = port
ln.updateEndpoints()
}
// UDPEndpointStatement should be called whenever a statement about the local node's
// UDP endpoint is received. It feeds the local endpoint predictor.
func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.udpTrack.AddStatement(fromaddr.String(), endpoint.String())
ln.updateEndpoints()
}
// UDPContact should be called whenever the local node has announced itself to another node
// via UDP. It feeds the local endpoint predictor.
func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.udpTrack.AddContact(toaddr.String())
ln.updateEndpoints()
}
func (ln *LocalNode) updateEndpoints() {
// Determine the endpoints.
newIP := ln.fallbackIP
newUDP := ln.fallbackUDP
if ln.staticIP != nil {
newIP = ln.staticIP
} else if ip, port := predictAddr(ln.udpTrack); ip != nil {
newIP = ip
newUDP = port
}
// Update the record.
if newIP != nil && !newIP.IsUnspecified() {
ln.set(enr.IP(newIP))
if newUDP != 0 {
ln.set(enr.UDP(newUDP))
} else {
ln.delete(enr.UDP(0))
}
} else {
ln.delete(enr.IP{})
}
}
// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
// endpoint representation to IP and port types.
func predictAddr(t *netutil.IPTracker) (net.IP, int) {
ep := t.PredictEndpoint()
if ep == "" {
return nil, 0
}
ipString, portString, _ := net.SplitHostPort(ep)
ip := net.ParseIP(ipString)
port, _ := strconv.Atoi(portString)
return ip, port
}
func (ln *LocalNode) invalidate() {
ln.cur.Store((*Node)(nil))
}
func (ln *LocalNode) sign() {
if n := ln.cur.Load().(*Node); n != nil {
return // no changes
}
var r enr.Record
for _, e := range ln.entries {
r.Set(e)
}
ln.bumpSeq()
r.SetSeq(ln.seq)
if err := SignV4(&r, ln.key); err != nil {
panic(fmt.Errorf("enode: can't sign record: %v", err))
}
n, err := New(ValidSchemes, &r)
if err != nil {
panic(fmt.Errorf("enode: can't verify local record: %v", err))
}
ln.cur.Store(n)
log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP())
}
func (ln *LocalNode) bumpSeq() {
ln.seq++
ln.db.storeLocalSeq(ln.id, ln.seq)
}

View file

@ -1,76 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
)
func newLocalNodeForTesting() (*LocalNode, *DB) {
db, _ := OpenDB("")
key, _ := crypto.GenerateKey()
return NewLocalNode(db, key), db
}
func TestLocalNode(t *testing.T) {
ln, db := newLocalNodeForTesting()
defer db.Close()
if ln.Node().ID() != ln.ID() {
t.Fatal("inconsistent ID")
}
ln.Set(enr.WithEntry("x", uint(3)))
var x uint
if err := ln.Node().Load(enr.WithEntry("x", &x)); err != nil {
t.Fatal("can't load entry 'x':", err)
} else if x != 3 {
t.Fatal("wrong value for entry 'x':", x)
}
}
func TestLocalNodeSeqPersist(t *testing.T) {
ln, db := newLocalNodeForTesting()
defer db.Close()
if s := ln.Node().Seq(); s != 1 {
t.Fatalf("wrong initial seq %d, want 1", s)
}
ln.Set(enr.WithEntry("x", uint(1)))
if s := ln.Node().Seq(); s != 2 {
t.Fatalf("wrong seq %d after set, want 2", s)
}
// Create a new instance, it should reload the sequence number.
// The number increases just after that because a new record is
// created without the "x" entry.
ln2 := NewLocalNode(db, ln.key)
if s := ln2.Node().Seq(); s != 3 {
t.Fatalf("wrong seq %d on new instance, want 3", s)
}
// Create a new instance with a different node key on the same database.
// This should reset the sequence number.
key, _ := crypto.GenerateKey()
ln3 := NewLocalNode(db, key)
if s := ln3.Node().Seq(); s != 1 {
t.Fatalf("wrong seq %d on instance with changed key, want 1", s)
}
}

View file

@ -1,255 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"math/bits"
"math/rand"
"net"
"strings"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
)
// Node represents a host on the network.
type Node struct {
r enr.Record
id ID
}
// New wraps a node record. The record must be valid according to the given
// identity scheme.
func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
if err := r.VerifySignature(validSchemes); err != nil {
return nil, err
}
node := &Node{r: *r}
if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) {
return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{}))
}
return node, nil
}
// ID returns the node identifier.
func (n *Node) ID() ID {
return n.id
}
// Seq returns the sequence number of the underlying record.
func (n *Node) Seq() uint64 {
return n.r.Seq()
}
// Incomplete returns true for nodes with no IP address.
func (n *Node) Incomplete() bool {
return n.IP() == nil
}
// Load retrieves an entry from the underlying record.
func (n *Node) Load(k enr.Entry) error {
return n.r.Load(k)
}
// IP returns the IP address of the node.
func (n *Node) IP() net.IP {
var ip net.IP
n.Load((*enr.IP)(&ip))
return ip
}
// UDP returns the UDP port of the node.
func (n *Node) UDP() int {
var port enr.UDP
n.Load(&port)
return int(port)
}
// UDP returns the TCP port of the node.
func (n *Node) TCP() int {
var port enr.TCP
n.Load(&port)
return int(port)
}
// Pubkey returns the secp256k1 public key of the node, if present.
func (n *Node) Pubkey() *ecdsa.PublicKey {
var key ecdsa.PublicKey
if n.Load((*Secp256k1)(&key)) != nil {
return nil
}
return &key
}
// Record returns the node's record. The return value is a copy and may
// be modified by the caller.
func (n *Node) Record() *enr.Record {
cpy := n.r
return &cpy
}
// checks whether n is a valid complete node.
func (n *Node) ValidateComplete() error {
if n.Incomplete() {
return errors.New("incomplete node")
}
if n.UDP() == 0 {
return errors.New("missing UDP port")
}
ip := n.IP()
if ip.IsMulticast() || ip.IsUnspecified() {
return errors.New("invalid IP (multicast/unspecified)")
}
// Validate the node key (on curve, etc.).
var key Secp256k1
return n.Load(&key)
}
// The string representation of a Node is a URL.
// Please see ParseNode for a description of the format.
func (n *Node) String() string {
return n.v4URL()
}
// MarshalText implements encoding.TextMarshaler.
func (n *Node) MarshalText() ([]byte, error) {
return []byte(n.v4URL()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (n *Node) UnmarshalText(text []byte) error {
dec, err := ParseV4(string(text))
if err == nil {
*n = *dec
}
return err
}
// ID is a unique identifier for each node.
type ID [32]byte
// Bytes returns a byte slice representation of the ID
func (n ID) Bytes() []byte {
return n[:]
}
// ID prints as a long hexadecimal number.
func (n ID) String() string {
return fmt.Sprintf("%x", n[:])
}
// The Go syntax representation of a ID is a call to HexID.
func (n ID) GoString() string {
return fmt.Sprintf("enode.HexID(\"%x\")", n[:])
}
// TerminalString returns a shortened hex string for terminal logging.
func (n ID) TerminalString() string {
return hex.EncodeToString(n[:8])
}
// MarshalText implements the encoding.TextMarshaler interface.
func (n ID) MarshalText() ([]byte, error) {
return []byte(hex.EncodeToString(n[:])), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (n *ID) UnmarshalText(text []byte) error {
id, err := parseID(string(text))
if err != nil {
return err
}
*n = id
return nil
}
// HexID converts a hex string to an ID.
// The string may be prefixed with 0x.
// It panics if the string is not a valid ID.
func HexID(in string) ID {
id, err := parseID(in)
if err != nil {
panic(err)
}
return id
}
func parseID(in string) (ID, error) {
var id ID
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
if err != nil {
return id, err
} else if len(b) != len(id) {
return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
}
copy(id[:], b)
return id, nil
}
// DistCmp compares the distances a->target and b->target.
// Returns -1 if a is closer to target, 1 if b is closer to target
// and 0 if they are equal.
func DistCmp(target, a, b ID) int {
for i := range target {
da := a[i] ^ target[i]
db := b[i] ^ target[i]
if da > db {
return 1
} else if da < db {
return -1
}
}
return 0
}
// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
func LogDist(a, b ID) int {
lz := 0
for i := range a {
x := a[i] ^ b[i]
if x == 0 {
lz += 8
} else {
lz += bits.LeadingZeros8(x)
break
}
}
return len(a)*8 - lz
}
// RandomID returns a random ID b such that logdist(a, b) == n.
func RandomID(a ID, n int) (b ID) {
if n == 0 {
return a
}
// flip bit at position n, fill the rest with random bits
b = a
pos := len(a) - n/8 - 1
bit := byte(0x01) << (byte(n%8) - 1)
if bit == 0 {
pos++
bit = 0x80
}
b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
for i := pos + 1; i < len(a); i++ {
b[i] = byte(rand.Intn(255))
}
return b
}

View file

@ -1,62 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"encoding/hex"
"fmt"
"testing"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/stretchr/testify/assert"
)
var pyRecord, _ = hex.DecodeString("f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c01826964827634826970847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31388375647082765f")
// TestPythonInterop checks that we can decode and verify a record produced by the Python
// implementation.
func TestPythonInterop(t *testing.T) {
var r enr.Record
if err := rlp.DecodeBytes(pyRecord, &r); err != nil {
t.Fatalf("can't decode: %v", err)
}
n, err := New(ValidSchemes, &r)
if err != nil {
t.Fatalf("can't verify record: %v", err)
}
var (
wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
wantSeq = uint64(1)
wantIP = enr.IP{127, 0, 0, 1}
wantUDP = enr.UDP(30303)
)
if n.Seq() != wantSeq {
t.Errorf("wrong seq: got %d, want %d", n.Seq(), wantSeq)
}
if n.ID() != wantID {
t.Errorf("wrong id: got %x, want %x", n.ID(), wantID)
}
want := map[enr.Entry]interface{}{new(enr.IP): &wantIP, new(enr.UDP): &wantUDP}
for k, v := range want {
desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, n.Load(k), desc) {
assert.Equal(t, k, v, desc)
}
}
}

View file

@ -1,194 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package enode
import (
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
)
var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
// MustParseV4 parses a node URL. It panics if the URL is not valid.
func MustParseV4(rawurl string) *Node {
n, err := ParseV4(rawurl)
if err != nil {
panic("invalid node URL: " + err.Error())
}
return n
}
// ParseV4 parses a node URL.
//
// There are two basic forms of node URLs:
//
// - incomplete nodes, which only have the public key (node ID)
// - complete nodes, which contain the public key and IP/Port information
//
// For incomplete nodes, the designator must look like one of these
//
// enode://<hex node id>
// <hex node id>
//
// For complete nodes, the node ID is encoded in the username portion
// of the URL, separated from the host by an @ sign. The hostname can
// only be given as an IP address, DNS domain names are not allowed.
// The port in the host name section is the TCP listening port. If the
// TCP and UDP (discovery) ports differ, the UDP port is specified as
// query parameter "discport".
//
// In the following example, the node URL describes
// a node with IP address 10.3.58.6, TCP listening port 30303
// and UDP discovery port 30301.
//
// enode://<hex node id>@10.3.58.6:30303?discport=30301
func ParseV4(rawurl string) (*Node, error) {
if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
id, err := parsePubkey(m[1])
if err != nil {
return nil, fmt.Errorf("invalid node ID (%v)", err)
}
return NewV4(id, nil, 0, 0), nil
}
return parseComplete(rawurl)
}
// NewV4 creates a node from discovery v4 node information. The record
// contained in the node has a zero-length signature.
func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
var r enr.Record
if ip != nil {
r.Set(enr.IP(ip))
}
if udp != 0 {
r.Set(enr.UDP(udp))
}
if tcp != 0 {
r.Set(enr.TCP(tcp))
}
signV4Compat(&r, pubkey)
n, err := New(v4CompatID{}, &r)
if err != nil {
panic(err)
}
return n
}
func parseComplete(rawurl string) (*Node, error) {
var (
id *ecdsa.PublicKey
ip net.IP
tcpPort, udpPort uint64
)
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
if u.Scheme != "enode" {
return nil, errors.New("invalid URL scheme, want \"enode\"")
}
// Parse the Node ID from the user portion.
if u.User == nil {
return nil, errors.New("does not contain node ID")
}
if id, err = parsePubkey(u.User.String()); err != nil {
return nil, fmt.Errorf("invalid node ID (%v)", err)
}
// Parse the IP address.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return nil, fmt.Errorf("invalid host: %v", err)
}
if ip = net.ParseIP(host); ip == nil {
return nil, errors.New("invalid IP address")
}
// Ensure the IP is 4 bytes long for IPv4 addresses.
if ipv4 := ip.To4(); ipv4 != nil {
ip = ipv4
}
// Parse the port numbers.
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
return nil, errors.New("invalid port")
}
udpPort = tcpPort
qv := u.Query()
if qv.Get("discport") != "" {
udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
if err != nil {
return nil, errors.New("invalid discport in query")
}
}
return NewV4(id, ip, int(tcpPort), int(udpPort)), nil
}
// parsePubkey parses a hex-encoded secp256k1 public key.
func parsePubkey(in string) (*ecdsa.PublicKey, error) {
b, err := hex.DecodeString(in)
if err != nil {
return nil, err
} else if len(b) != 64 {
return nil, fmt.Errorf("wrong length, want %d hex chars", 128)
}
b = append([]byte{0x4}, b...)
return crypto.UnmarshalPubkey(b)
}
func (n *Node) v4URL() string {
var (
scheme enr.ID
nodeid string
key ecdsa.PublicKey
)
n.Load(&scheme)
n.Load((*Secp256k1)(&key))
switch {
case scheme == "v4" || key != ecdsa.PublicKey{}:
nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:])
default:
nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:])
}
u := url.URL{Scheme: "enode"}
if n.Incomplete() {
u.Host = nodeid
} else {
addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
u.User = url.User(nodeid)
u.Host = addr.String()
if n.UDP() != n.TCP() {
u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
}
}
return u.String()
}
// PubkeyToIDV4 derives the v4 node address from the given public key.
func PubkeyToIDV4(key *ecdsa.PublicKey) ID {
e := make([]byte, 64)
math.ReadBits(key.X, e[:len(e)/2])
math.ReadBits(key.Y, e[len(e)/2:])
return ID(crypto.Keccak256Hash(e))
}

View file

@ -15,42 +15,39 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds // Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
// arbitrary information about a node on the peer-to-peer network. Node information is // arbitrary information about a node on the peer-to-peer network.
// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry //
// Records contain named keys. To store and retrieve key/values in a record, use the Entry
// interface. // interface.
// //
// # Signature Handling // Records must be signed before transmitting them to another node. Decoding a record verifies
// // its signature. When creating a record, set the entries you want, then call Sign to add the
// Records must be signed before transmitting them to another node. // signature. Modifying a record invalidates the signature.
//
// Decoding a record doesn't check its signature. Code working with records from an
// untrusted source must always verify two things: that the record uses an identity scheme
// deemed secure, and that the signature is valid according to the declared scheme.
//
// When creating a record, set the entries you want and use a signing function provided by
// the identity scheme to add the signature. Modifying a record invalidates the signature.
// //
// Package enr supports the "secp256k1-keccak" identity scheme. // Package enr supports the "secp256k1-keccak" identity scheme.
package enr package enr
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"sort" "sort"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
const SizeLimit = 300 // maximum encoded size of a node record in bytes const SizeLimit = 300 // maximum encoded size of a node record in bytes
const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity scheme
var ( var (
// TODO: check if need below merge conflict errNoID = errors.New("unknown or unspecified identity scheme")
// errNoID = errors.New("unknown or unspecified identity scheme") errInvalidSigsize = errors.New("invalid signature size")
// errInvalidSigsize = errors.New("invalid signature size") errInvalidSig = errors.New("invalid signature")
// errInvalidSig = errors.New("invalid signature")
ErrInvalidSig = errors.New("invalid signature on node record")
errNotSorted = errors.New("record key/value pairs are not sorted by key") errNotSorted = errors.New("record key/value pairs are not sorted by key")
errDuplicateKey = errors.New("record contains duplicate key") errDuplicateKey = errors.New("record contains duplicate key")
errIncompletePair = errors.New("record contains incomplete k/v pair") errIncompletePair = errors.New("record contains incomplete k/v pair")
@ -59,32 +56,6 @@ var (
errNotFound = errors.New("no such key in record") errNotFound = errors.New("no such key in record")
) )
// An IdentityScheme is capable of verifying record signatures and
// deriving node addresses.
type IdentityScheme interface {
Verify(r *Record, sig []byte) error
NodeAddr(r *Record) []byte
}
// SchemeMap is a registry of named identity schemes.
type SchemeMap map[string]IdentityScheme
func (m SchemeMap) Verify(r *Record, sig []byte) error {
s := m[r.IdentityScheme()]
if s == nil {
return ErrInvalidSig
}
return s.Verify(r, sig)
}
func (m SchemeMap) NodeAddr(r *Record) []byte {
s := m[r.IdentityScheme()]
if s == nil {
return nil
}
return s.NodeAddr(r)
}
// Record represents a node record. The zero value is an empty record. // Record represents a node record. The zero value is an empty record.
type Record struct { type Record struct {
seq uint64 // sequence number seq uint64 // sequence number
@ -99,14 +70,19 @@ type pair struct {
v rlp.RawValue v rlp.RawValue
} }
// Signed reports whether the record has a valid signature.
func (r *Record) Signed() bool {
return r.signature != nil
}
// Seq returns the sequence number. // Seq returns the sequence number.
func (r *Record) Seq() uint64 { func (r *Record) Seq() uint64 {
return r.seq return r.seq
} }
// SetSeq updates the record sequence number. This invalidates any signature on the record. // SetSeq updates the record sequence number. This invalidates any signature on the record.
// Calling SetSeq is usually not required because setting any key in a signed record // Calling SetSeq is usually not required because signing the redord increments the
// increments the sequence number. // sequence number.
func (r *Record) SetSeq(s uint64) { func (r *Record) SetSeq(s uint64) {
r.signature = nil r.signature = nil
r.raw = nil r.raw = nil
@ -129,48 +105,39 @@ func (r *Record) Load(e Entry) error {
return &KeyError{Key: e.ENRKey(), Err: errNotFound} return &KeyError{Key: e.ENRKey(), Err: errNotFound}
} }
// Set adds or updates the given entry in the record. It panics if the value can't be // Set adds or updates the given entry in the record.
// encoded. If the record is signed, Set increments the sequence number and invalidates // It panics if the value can't be encoded.
// the sequence number.
func (r *Record) Set(e Entry) { func (r *Record) Set(e Entry) {
r.signature = nil
r.raw = nil
blob, err := rlp.EncodeToBytes(e) blob, err := rlp.EncodeToBytes(e)
if err != nil { if err != nil {
panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err)) panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err))
} }
r.invalidate()
pairs := make([]pair, len(r.pairs)) i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= e.ENRKey() })
copy(pairs, r.pairs)
i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() }) if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() {
switch {
case i < len(pairs) && pairs[i].k == e.ENRKey():
// element is present at r.pairs[i] // element is present at r.pairs[i]
pairs[i].v = blob r.pairs[i].v = blob
case i < len(r.pairs): return
} else if i < len(r.pairs) {
// insert pair before i-th elem // insert pair before i-th elem
el := pair{e.ENRKey(), blob} el := pair{e.ENRKey(), blob}
pairs = append(pairs, pair{}) r.pairs = append(r.pairs, pair{})
copy(pairs[i+1:], pairs[i:]) copy(r.pairs[i+1:], r.pairs[i:])
pairs[i] = el r.pairs[i] = el
default: return
// element should be placed at the end of r.pairs
pairs = append(pairs, pair{e.ENRKey(), blob})
} }
r.pairs = pairs
}
func (r *Record) invalidate() { // element should be placed at the end of r.pairs
if r.signature != nil { r.pairs = append(r.pairs, pair{e.ENRKey(), blob})
r.seq++
}
r.signature = nil
r.raw = nil
} }
// EncodeRLP implements rlp.Encoder. Encoding fails if // EncodeRLP implements rlp.Encoder. Encoding fails if
// the record is unsigned. // the record is unsigned.
func (r Record) EncodeRLP(w io.Writer) error { func (r Record) EncodeRLP(w io.Writer) error {
if r.signature == nil { if !r.Signed() {
return errEncodeUnsigned return errEncodeUnsigned
} }
_, err := w.Write(r.raw) _, err := w.Write(r.raw)
@ -179,34 +146,25 @@ func (r Record) EncodeRLP(w io.Writer) error {
// DecodeRLP implements rlp.Decoder. Decoding verifies the signature. // DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
func (r *Record) DecodeRLP(s *rlp.Stream) error { func (r *Record) DecodeRLP(s *rlp.Stream) error {
dec, raw, err := decodeRecord(s) raw, err := s.Raw()
if err != nil { if err != nil {
return err return err
} }
*r = dec
r.raw = raw
return nil
}
func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
raw, err = s.Raw()
if err != nil {
return dec, raw, err
}
if len(raw) > SizeLimit { if len(raw) > SizeLimit {
return dec, raw, errTooBig return errTooBig
} }
// Decode the RLP container. // Decode the RLP container.
dec := Record{raw: raw}
s = rlp.NewStream(bytes.NewReader(raw), 0) s = rlp.NewStream(bytes.NewReader(raw), 0)
if _, err := s.List(); err != nil { if _, err := s.List(); err != nil {
return dec, raw, err return err
} }
if err = s.Decode(&dec.signature); err != nil { if err = s.Decode(&dec.signature); err != nil {
return dec, raw, err return err
} }
if err = s.Decode(&dec.seq); err != nil { if err = s.Decode(&dec.seq); err != nil {
return dec, raw, err return err
} }
// The rest of the record contains sorted k/v pairs. // The rest of the record contains sorted k/v pairs.
var prevkey string var prevkey string
@ -216,73 +174,62 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
if err == rlp.EOL { if err == rlp.EOL {
break break
} }
return dec, raw, err return err
} }
if err := s.Decode(&kv.v); err != nil { if err := s.Decode(&kv.v); err != nil {
if err == rlp.EOL { if err == rlp.EOL {
return dec, raw, errIncompletePair return errIncompletePair
} }
return dec, raw, err return err
} }
if i > 0 { if i > 0 {
if kv.k == prevkey { if kv.k == prevkey {
return dec, raw, errDuplicateKey return errDuplicateKey
} }
if kv.k < prevkey { if kv.k < prevkey {
return dec, raw, errNotSorted return errNotSorted
} }
} }
dec.pairs = append(dec.pairs, kv) dec.pairs = append(dec.pairs, kv)
prevkey = kv.k prevkey = kv.k
} }
return dec, raw, s.ListEnd() if err := s.ListEnd(); err != nil {
} return err
// IdentityScheme returns the name of the identity scheme in the record.
func (r *Record) IdentityScheme() string {
var id ID
r.Load(&id)
return string(id)
}
// VerifySignature checks whether the record is signed using the given identity scheme.
func (r *Record) VerifySignature(s IdentityScheme) error {
return s.Verify(r, r.signature)
}
// SetSig sets the record signature. It returns an error if the encoded record is larger
// than the size limit or if the signature is invalid according to the passed scheme.
//
// You can also use SetSig to remove the signature explicitly by passing a nil scheme
// and signature.
//
// SetSig panics when either the scheme or the signature (but not both) are nil.
func (r *Record) SetSig(s IdentityScheme, sig []byte) error {
switch {
// Prevent storing invalid data.
case s == nil && sig != nil:
panic("enr: invalid call to SetSig with non-nil signature but nil scheme")
case s != nil && sig == nil:
panic("enr: invalid call to SetSig with nil signature but non-nil scheme")
// Verify if we have a scheme.
case s != nil:
if err := s.Verify(r, sig); err != nil {
return err
}
raw, err := r.encode(sig)
if err != nil {
return err
}
r.signature, r.raw = sig, raw
// Reset otherwise.
default:
r.signature, r.raw = nil, nil
} }
// Verify signature.
if err = dec.verifySignature(); err != nil {
return err
}
*r = dec
return nil return nil
} }
// AppendElements appends the sequence number and entries to the given slice. type s256raw []byte
func (r *Record) AppendElements(list []interface{}) []interface{} {
func (s256raw) ENRKey() string { return "secp256k1" }
// NodeAddr returns the node address. The return value will be nil if the record is
// unsigned.
func (r *Record) NodeAddr() []byte {
var entry s256raw
if r.Load(&entry) != nil {
return nil
}
return crypto.Keccak256(entry)
}
// Sign signs the record with the given private key. It updates the record's identity
// scheme, public key and increments the sequence number. Sign returns an error if the
// encoded record is larger than the size limit.
func (r *Record) Sign(privkey *ecdsa.PrivateKey) error {
r.seq = r.seq + 1
r.Set(ID_SECP256k1_KECCAK)
r.Set(Secp256k1(privkey.PublicKey))
return r.signAndEncode(privkey)
}
func (r *Record) appendPairs(list []interface{}) []interface{} {
list = append(list, r.seq) list = append(list, r.seq)
for _, p := range r.pairs { for _, p := range r.pairs {
list = append(list, p.k, p.v) list = append(list, p.k, p.v)
@ -290,15 +237,54 @@ func (r *Record) AppendElements(list []interface{}) []interface{} {
return list return list
} }
func (r *Record) encode(sig []byte) (raw []byte, err error) { func (r *Record) signAndEncode(privkey *ecdsa.PrivateKey) error {
list := make([]interface{}, 1, 2*len(r.pairs)+1) // Put record elements into a flat list. Leave room for the signature.
list[0] = sig list := make([]interface{}, 1, len(r.pairs)*2+2)
list = r.AppendElements(list) list = r.appendPairs(list)
if raw, err = rlp.EncodeToBytes(list); err != nil {
return nil, err // Sign the tail of the list.
h := sha3.NewKeccak256()
rlp.Encode(h, list[1:])
sig, err := crypto.Sign(h.Sum(nil), privkey)
if err != nil {
return err
} }
if len(raw) > SizeLimit { sig = sig[:len(sig)-1] // remove v
return nil, errTooBig
// Put signature in front.
r.signature, list[0] = sig, sig
r.raw, err = rlp.EncodeToBytes(list)
if err != nil {
return err
} }
return raw, nil if len(r.raw) > SizeLimit {
return errTooBig
}
return nil
}
func (r *Record) verifySignature() error {
// Get identity scheme, public key, signature.
var id ID
var entry s256raw
if err := r.Load(&id); err != nil {
return err
} else if id != ID_SECP256k1_KECCAK {
return errNoID
}
if err := r.Load(&entry); err != nil {
return err
} else if len(entry) != 33 {
return errors.New("invalid public key")
}
// Verify the signature.
list := make([]interface{}, 0, len(r.pairs)*2+1)
list = r.appendPairs(list)
h := sha3.NewKeccak256()
rlp.Encode(h, list)
if !crypto.VerifySignature(entry, h.Sum(nil), r.signature) {
return errInvalidSig
}
return nil
} }

View file

@ -18,17 +18,23 @@ package enr
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/hex"
"fmt" "fmt"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var (
privkey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
pubkey = &privkey.PublicKey
)
var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
func randomString(strlen int) string { func randomString(strlen int) string {
@ -48,51 +54,63 @@ func TestGetSetID(t *testing.T) {
assert.Equal(t, id, id2) assert.Equal(t, id, id2)
} }
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key. // TestGetSetIP4 tests encoding/decoding and setting/getting of the IP4 key.
func TestGetSetIP4(t *testing.T) { func TestGetSetIP4(t *testing.T) {
ip := IP{192, 168, 0, 3} ip := IP4{192, 168, 0, 3}
var r Record var r Record
r.Set(ip) r.Set(ip)
var ip2 IP var ip2 IP4
require.NoError(t, r.Load(&ip2)) require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2) assert.Equal(t, ip, ip2)
} }
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP key. // TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
func TestGetSetIP6(t *testing.T) { func TestGetSetIP6(t *testing.T) {
ip := IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68} ip := IP6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
var r Record var r Record
r.Set(ip) r.Set(ip)
var ip2 IP var ip2 IP6
require.NoError(t, r.Load(&ip2)) require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2) assert.Equal(t, ip, ip2)
} }
// TestGetSetUDP tests encoding/decoding and setting/getting of the UDP key. // TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key.
func TestGetSetUDP(t *testing.T) { func TestGetSetDiscPort(t *testing.T) {
port := UDP(30309) port := DiscPort(30309)
var r Record var r Record
r.Set(port) r.Set(port)
var port2 UDP var port2 DiscPort
require.NoError(t, r.Load(&port2)) require.NoError(t, r.Load(&port2))
assert.Equal(t, port, port2) assert.Equal(t, port, port2)
} }
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
func TestGetSetSecp256k1(t *testing.T) {
var r Record
if err := r.Sign(privkey); err != nil {
t.Fatal(err)
}
var pk Secp256k1
require.NoError(t, r.Load(&pk))
assert.EqualValues(t, pubkey, &pk)
}
func TestLoadErrors(t *testing.T) { func TestLoadErrors(t *testing.T) {
var r Record var r Record
ip4 := IP{127, 0, 0, 1} ip4 := IP4{127, 0, 0, 1}
r.Set(ip4) r.Set(ip4)
// Check error for missing keys. // Check error for missing keys.
var udp UDP var ip6 IP6
err := r.Load(&udp) err := r.Load(&ip6)
if !IsNotFound(err) { if !IsNotFound(err) {
t.Error("IsNotFound should return true for missing key") t.Error("IsNotFound should return true for missing key")
} }
assert.Equal(t, &KeyError{Key: udp.ENRKey(), Err: errNotFound}, err) assert.Equal(t, &KeyError{Key: ip6.ENRKey(), Err: errNotFound}, err)
// Check error for invalid keys. // Check error for invalid keys.
var list []uint var list []uint
@ -149,49 +167,40 @@ func TestSortedGetAndSet(t *testing.T) {
func TestDirty(t *testing.T) { func TestDirty(t *testing.T) {
var r Record var r Record
if r.Signed() {
t.Error("Signed returned true for zero record")
}
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned { if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
t.Errorf("expected errEncodeUnsigned, got %#v", err) t.Errorf("expected errEncodeUnsigned, got %#v", err)
} }
require.NoError(t, signTest([]byte{5}, &r)) require.NoError(t, r.Sign(privkey))
if len(r.signature) == 0 { if !r.Signed() {
t.Error("record is not signed") t.Error("Signed return false for signed record")
} }
_, err := rlp.EncodeToBytes(r) _, err := rlp.EncodeToBytes(r)
assert.NoError(t, err) assert.NoError(t, err)
r.SetSeq(3) r.SetSeq(3)
if len(r.signature) != 0 { if r.Signed() {
t.Error("signature still set after modification") t.Error("Signed returned true for modified record")
} }
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned { if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
t.Errorf("expected errEncodeUnsigned, got %#v", err) t.Errorf("expected errEncodeUnsigned, got %#v", err)
} }
} }
func TestSeq(t *testing.T) {
var r Record
assert.Equal(t, uint64(0), r.Seq())
r.Set(UDP(1))
assert.Equal(t, uint64(0), r.Seq())
signTest([]byte{5}, &r)
assert.Equal(t, uint64(0), r.Seq())
r.Set(UDP(2))
assert.Equal(t, uint64(1), r.Seq())
}
// TestGetSetOverwrite tests value overwrite when setting a new value with an existing key in record. // TestGetSetOverwrite tests value overwrite when setting a new value with an existing key in record.
func TestGetSetOverwrite(t *testing.T) { func TestGetSetOverwrite(t *testing.T) {
var r Record var r Record
ip := IP{192, 168, 0, 3} ip := IP4{192, 168, 0, 3}
r.Set(ip) r.Set(ip)
ip2 := IP{192, 168, 0, 4} ip2 := IP4{192, 168, 0, 4}
r.Set(ip2) r.Set(ip2)
var ip3 IP var ip3 IP4
require.NoError(t, r.Load(&ip3)) require.NoError(t, r.Load(&ip3))
assert.Equal(t, ip2, ip3) assert.Equal(t, ip2, ip3)
} }
@ -199,9 +208,9 @@ func TestGetSetOverwrite(t *testing.T) {
// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record. // TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
func TestSignEncodeAndDecode(t *testing.T) { func TestSignEncodeAndDecode(t *testing.T) {
var r Record var r Record
r.Set(UDP(30303)) r.Set(DiscPort(30303))
r.Set(IP{127, 0, 0, 1}) r.Set(IP4{127, 0, 0, 1})
require.NoError(t, signTest([]byte{5}, &r)) require.NoError(t, r.Sign(privkey))
blob, err := rlp.EncodeToBytes(r) blob, err := rlp.EncodeToBytes(r)
require.NoError(t, err) require.NoError(t, err)
@ -215,20 +224,62 @@ func TestSignEncodeAndDecode(t *testing.T) {
assert.Equal(t, blob, blob2) assert.Equal(t, blob, blob2)
} }
func TestNodeAddr(t *testing.T) {
var r Record
if addr := r.NodeAddr(); addr != nil {
t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
}
require.NoError(t, r.Sign(privkey))
expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726"
assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr()))
}
var pyRecord, _ = hex.DecodeString("f896b840954dc36583c1f4b69ab59b1375f362f06ee99f3723cd77e64b6de6d211c27d7870642a79d4516997f94091325d2a7ca6215376971455fb221d34f35b277149a1018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
// TestPythonInterop checks that we can decode and verify a record produced by the Python
// implementation.
func TestPythonInterop(t *testing.T) {
var r Record
if err := rlp.DecodeBytes(pyRecord, &r); err != nil {
t.Fatalf("can't decode: %v", err)
}
var (
wantAddr, _ = hex.DecodeString("caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726")
wantSeq = uint64(1)
wantIP = IP4{127, 0, 0, 1}
wantDiscport = DiscPort(30303)
)
if r.Seq() != wantSeq {
t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
}
if addr := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
}
want := map[Entry]interface{}{new(IP4): &wantIP, new(DiscPort): &wantDiscport}
for k, v := range want {
desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, r.Load(k), desc) {
assert.Equal(t, k, v, desc)
}
}
}
// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed. // TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
func TestRecordTooBig(t *testing.T) { func TestRecordTooBig(t *testing.T) {
var r Record var r Record
key := randomString(10) key := randomString(10)
// set a big value for random key, expect error // set a big value for random key, expect error
r.Set(WithEntry(key, randomString(SizeLimit))) r.Set(WithEntry(key, randomString(300)))
if err := signTest([]byte{5}, &r); err != errTooBig { if err := r.Sign(privkey); err != errTooBig {
t.Fatalf("expected to get errTooBig, got %#v", err) t.Fatalf("expected to get errTooBig, got %#v", err)
} }
// set an acceptable value for random key, expect no error // set an acceptable value for random key, expect no error
r.Set(WithEntry(key, randomString(100))) r.Set(WithEntry(key, randomString(100)))
require.NoError(t, signTest([]byte{5}, &r)) require.NoError(t, r.Sign(privkey))
} }
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
@ -244,7 +295,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
r.Set(WithEntry(key, &value)) r.Set(WithEntry(key, &value))
} }
require.NoError(t, signTest([]byte{5}, &r)) require.NoError(t, r.Sign(privkey))
_, err := rlp.EncodeToBytes(r) _, err := rlp.EncodeToBytes(r)
require.NoError(t, err) require.NoError(t, err)
@ -257,40 +308,11 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
} }
} }
type testSig struct{} func BenchmarkDecode(b *testing.B) {
var r Record
type testID []byte for i := 0; i < b.N; i++ {
rlp.DecodeBytes(pyRecord, &r)
func (id testID) ENRKey() string { return "testid" }
func signTest(id []byte, r *Record) error {
r.Set(ID("test"))
r.Set(testID(id))
return r.SetSig(testSig{}, makeTestSig(id, r.Seq()))
}
func makeTestSig(id []byte, seq uint64) []byte {
sig := make([]byte, 8, len(id)+8)
binary.BigEndian.PutUint64(sig[:8], seq)
sig = append(sig, id...)
return sig
}
func (testSig) Verify(r *Record, sig []byte) error {
var id []byte
if err := r.Load((*testID)(&id)); err != nil {
return err
} }
if !bytes.Equal(sig, makeTestSig(id, r.Seq())) { b.StopTimer()
return ErrInvalidSig r.NodeAddr()
}
return nil
}
func (testSig) NodeAddr(r *Record) []byte {
var id []byte
if err := r.Load((*testID)(&id)); err != nil {
return nil
}
return id
} }

View file

@ -17,10 +17,12 @@
package enr package enr
import ( import (
"crypto/ecdsa"
"fmt" "fmt"
"io" "io"
"net" "net"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -55,47 +57,87 @@ func WithEntry(k string, v interface{}) Entry {
return &generic{key: k, value: v} return &generic{key: k, value: v}
} }
// TCP is the "tcp" key, which holds the TCP port of the node. // DiscPort is the "discv5" key, which holds the UDP port for discovery v5.
type TCP uint16 type DiscPort uint16
func (v TCP) ENRKey() string { return "tcp" } func (v DiscPort) ENRKey() string { return "discv5" }
// UDP is the "udp" key, which holds the UDP port of the node.
type UDP uint16
func (v UDP) ENRKey() string { return "udp" }
// ID is the "id" key, which holds the name of the identity scheme. // ID is the "id" key, which holds the name of the identity scheme.
type ID string type ID string
const IDv4 = ID("v4") // the default identity scheme
func (v ID) ENRKey() string { return "id" } func (v ID) ENRKey() string { return "id" }
// IP is the "ip" key, which holds the IP address of the node. // IP4 is the "ip4" key, which holds a 4-byte IPv4 address.
type IP net.IP type IP4 net.IP
func (v IP) ENRKey() string { return "ip" } func (v IP4) ENRKey() string { return "ip4" }
// EncodeRLP implements rlp.Encoder. // EncodeRLP implements rlp.Encoder.
func (v IP) EncodeRLP(w io.Writer) error { func (v IP4) EncodeRLP(w io.Writer) error {
if ip4 := net.IP(v).To4(); ip4 != nil { ip4 := net.IP(v).To4()
return rlp.Encode(w, ip4) if ip4 == nil {
return fmt.Errorf("invalid IPv4 address: %v", v)
} }
return rlp.Encode(w, net.IP(v)) return rlp.Encode(w, ip4)
} }
// DecodeRLP implements rlp.Decoder. // DecodeRLP implements rlp.Decoder.
func (v *IP) DecodeRLP(s *rlp.Stream) error { func (v *IP4) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode((*net.IP)(v)); err != nil { if err := s.Decode((*net.IP)(v)); err != nil {
return err return err
} }
if len(*v) != 4 && len(*v) != 16 { if len(*v) != 4 {
return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v) return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
} }
return nil return nil
} }
// IP6 is the "ip6" key, which holds a 16-byte IPv6 address.
type IP6 net.IP
func (v IP6) ENRKey() string { return "ip6" }
// EncodeRLP implements rlp.Encoder.
func (v IP6) EncodeRLP(w io.Writer) error {
ip6 := net.IP(v)
return rlp.Encode(w, ip6)
}
// DecodeRLP implements rlp.Decoder.
func (v *IP6) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode((*net.IP)(v)); err != nil {
return err
}
if len(*v) != 16 {
return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v)
}
return nil
}
// Secp256k1 is the "secp256k1" key, which holds a public key.
type Secp256k1 ecdsa.PublicKey
func (v Secp256k1) ENRKey() string { return "secp256k1" }
// EncodeRLP implements rlp.Encoder.
func (v Secp256k1) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, crypto.CompressPubkey((*ecdsa.PublicKey)(&v)))
}
// DecodeRLP implements rlp.Decoder.
func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
buf, err := s.Bytes()
if err != nil {
return err
}
pk, err := crypto.DecompressPubkey(buf)
if err != nil {
return err
}
*v = (Secp256k1)(*pk)
return nil
}
// KeyError is an error related to a key. // KeyError is an error related to a key.
type KeyError struct { type KeyError struct {
Key string Key string

View file

@ -25,7 +25,7 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
@ -252,13 +252,13 @@ type msgEventer struct {
MsgReadWriter MsgReadWriter
feed *event.Feed feed *event.Feed
peerID enode.ID peerID discover.NodeID
Protocol string Protocol string
} }
// newMsgEventer returns a msgEventer which sends message events to the given // newMsgEventer returns a msgEventer which sends message events to the given
// feed // feed
func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto string) *msgEventer { func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID discover.NodeID, proto string) *msgEventer {
return &msgEventer{ return &msgEventer{
MsgReadWriter: rw, MsgReadWriter: rw,
feed: feed, feed: feed,

View file

@ -129,15 +129,21 @@ func Map(m Interface, c chan struct{}, protocol string, extport, intport int, na
// ExtIP assumes that the local machine is reachable on the given // ExtIP assumes that the local machine is reachable on the given
// external IP address, and that any required ports were mapped manually. // external IP address, and that any required ports were mapped manually.
// Mapping operations will not return an error but won't actually do anything. // Mapping operations will not return an error but won't actually do anything.
type ExtIP net.IP func ExtIP(ip net.IP) Interface {
if ip == nil {
panic("IP must not be nil")
}
return extIP(ip)
}
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } type extIP net.IP
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
func (n extIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
// These do nothing. // These do nothing.
func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil } func (extIP) DeleteMapping(string, int, int) error { return nil }
func (ExtIP) DeleteMapping(string, int, int) error { return nil }
// Any returns a port mapper that tries to discover any supported // Any returns a port mapper that tries to discover any supported
// mechanism on the local network. // mechanism on the local network.

View file

@ -28,7 +28,7 @@ import (
func TestAutoDiscRace(t *testing.T) { func TestAutoDiscRace(t *testing.T) {
ad := startautodisc("thing", func() Interface { ad := startautodisc("thing", func() Interface {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
return ExtIP{33, 44, 55, 66} return extIP{33, 44, 55, 66}
}) })
// Spawn a few concurrent calls to ad.ExternalIP. // Spawn a few concurrent calls to ad.ExternalIP.

View file

@ -1,130 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package netutil
import (
"time"
"github.com/XinFinOrg/XDPoSChain/common/mclock"
)
// IPTracker predicts the external endpoint, i.e. IP address and port, of the local host
// based on statements made by other hosts.
type IPTracker struct {
window time.Duration
contactWindow time.Duration
minStatements int
clock mclock.Clock
statements map[string]ipStatement
contact map[string]mclock.AbsTime
lastStatementGC mclock.AbsTime
lastContactGC mclock.AbsTime
}
type ipStatement struct {
endpoint string
time mclock.AbsTime
}
// NewIPTracker creates an IP tracker.
//
// The window parameters configure the amount of past network events which are kept. The
// minStatements parameter enforces a minimum number of statements which must be recorded
// before any prediction is made. Higher values for these parameters decrease 'flapping' of
// predictions as network conditions change. Window duration values should typically be in
// the range of minutes.
func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTracker {
return &IPTracker{
window: window,
contactWindow: contactWindow,
statements: make(map[string]ipStatement),
minStatements: minStatements,
contact: make(map[string]mclock.AbsTime),
clock: mclock.System{},
}
}
// PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by
// checking whether any statement has been received from a node we didn't contact before
// the statement was made.
func (it *IPTracker) PredictFullConeNAT() bool {
now := it.clock.Now()
it.gcContact(now)
it.gcStatements(now)
for host, st := range it.statements {
if c, ok := it.contact[host]; !ok || c > st.time {
return true
}
}
return false
}
// PredictEndpoint returns the current prediction of the external endpoint.
func (it *IPTracker) PredictEndpoint() string {
it.gcStatements(it.clock.Now())
// The current strategy is simple: find the endpoint with most statements.
counts := make(map[string]int)
maxcount, max := 0, ""
for _, s := range it.statements {
c := counts[s.endpoint] + 1
counts[s.endpoint] = c
if c > maxcount && c >= it.minStatements {
maxcount, max = c, s.endpoint
}
}
return max
}
// AddStatement records that a certain host thinks our external endpoint is the one given.
func (it *IPTracker) AddStatement(host, endpoint string) {
now := it.clock.Now()
it.statements[host] = ipStatement{endpoint, now}
if time.Duration(now-it.lastStatementGC) >= it.window {
it.gcStatements(now)
}
}
// AddContact records that a packet containing our endpoint information has been sent to a
// certain host.
func (it *IPTracker) AddContact(host string) {
now := it.clock.Now()
it.contact[host] = now
if time.Duration(now-it.lastContactGC) >= it.contactWindow {
it.gcContact(now)
}
}
func (it *IPTracker) gcStatements(now mclock.AbsTime) {
it.lastStatementGC = now
cutoff := now.Add(-it.window)
for host, s := range it.statements {
if s.time < cutoff {
delete(it.statements, host)
}
}
}
func (it *IPTracker) gcContact(now mclock.AbsTime) {
it.lastContactGC = now
cutoff := now.Add(-it.contactWindow)
for host, ct := range it.contact {
if ct < cutoff {
delete(it.contact, host)
}
}
}

View file

@ -1,138 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package netutil
import (
"fmt"
mrand "math/rand"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/common/mclock"
)
const (
opStatement = iota
opContact
opPredict
opCheckFullCone
)
type iptrackTestEvent struct {
op int
time int // absolute, in milliseconds
ip, from string
}
func TestIPTracker(t *testing.T) {
tests := map[string][]iptrackTestEvent{
"minStatements": {
{opPredict, 0, "", ""},
{opStatement, 0, "127.0.0.1", "127.0.0.2"},
{opPredict, 1000, "", ""},
{opStatement, 1000, "127.0.0.1", "127.0.0.3"},
{opPredict, 1000, "", ""},
{opStatement, 1000, "127.0.0.1", "127.0.0.4"},
{opPredict, 1000, "127.0.0.1", ""},
},
"window": {
{opStatement, 0, "127.0.0.1", "127.0.0.2"},
{opStatement, 2000, "127.0.0.1", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1", "127.0.0.4"},
{opPredict, 10000, "127.0.0.1", ""},
{opPredict, 10001, "", ""}, // first statement expired
{opStatement, 10100, "127.0.0.1", "127.0.0.2"},
{opPredict, 10200, "127.0.0.1", ""},
},
"fullcone": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opContact, 3000, "", "127.0.0.4"},
{opStatement, 3010, "127.0.0.1", "127.0.0.4"},
{opCheckFullCone, 3500, "false", ""},
},
"fullcone_2": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1", "127.0.0.4"},
{opContact, 3010, "", "127.0.0.4"},
{opCheckFullCone, 3500, "true", ""},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) { runIPTrackerTest(t, test) })
}
}
func runIPTrackerTest(t *testing.T, evs []iptrackTestEvent) {
var (
clock mclock.Simulated
it = NewIPTracker(10*time.Second, 10*time.Second, 3)
)
it.clock = &clock
for i, ev := range evs {
evtime := time.Duration(ev.time) * time.Millisecond
clock.Run(evtime - time.Duration(clock.Now()))
switch ev.op {
case opStatement:
it.AddStatement(ev.from, ev.ip)
case opContact:
it.AddContact(ev.from)
case opPredict:
if pred := it.PredictEndpoint(); pred != ev.ip {
t.Errorf("op %d: wrong prediction %q, want %q", i, pred, ev.ip)
}
case opCheckFullCone:
pred := fmt.Sprintf("%t", it.PredictFullConeNAT())
if pred != ev.ip {
t.Errorf("op %d: wrong prediction %s, want %s", i, pred, ev.ip)
}
}
}
}
// This checks that old statements and contacts are GCed even if Predict* isn't called.
func TestIPTrackerForceGC(t *testing.T) {
var (
clock mclock.Simulated
window = 10 * time.Second
rate = 50 * time.Millisecond
max = int(window/rate) + 1
it = NewIPTracker(window, window, 3)
)
it.clock = &clock
for i := 0; i < 5*max; i++ {
e1 := make([]byte, 4)
e2 := make([]byte, 4)
mrand.Read(e1)
mrand.Read(e2)
it.AddStatement(string(e1), string(e2))
it.AddContact(string(e1))
clock.Run(rate)
}
if len(it.contact) > 2*max {
t.Errorf("contacts not GCed, have %d", len(it.contact))
}
if len(it.statements) > 2*max {
t.Errorf("statements not GCed, have %d", len(it.statements))
}
}

View file

@ -28,15 +28,10 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/mclock" "github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
var (
ErrShuttingDown = errors.New("shutting down")
)
const ( const (
baseProtocolVersion = 5 baseProtocolVersion = 5
baseProtocolLength = uint64(16) baseProtocolLength = uint64(16)
@ -63,7 +58,7 @@ type protoHandshake struct {
Name string Name string
Caps []Cap Caps []Cap
ListenPort uint64 ListenPort uint64
ID []byte // secp256k1 public key ID discover.NodeID
// Ignore additional fields (for forward compatibility). // Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"` Rest []rlp.RawValue `rlp:"tail"`
@ -93,12 +88,12 @@ const (
// PeerEvent is an event emitted when peers are either added or dropped from // PeerEvent is an event emitted when peers are either added or dropped from
// a p2p.Server or when a message is sent or received on a peer connection // a p2p.Server or when a message is sent or received on a peer connection
type PeerEvent struct { type PeerEvent struct {
Type PeerEventType `json:"type"` Type PeerEventType `json:"type"`
Peer enode.ID `json:"peer"` Peer discover.NodeID `json:"peer"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"` Protocol string `json:"protocol,omitempty"`
MsgCode *uint64 `json:"msg_code,omitempty"` MsgCode *uint64 `json:"msg_code,omitempty"`
MsgSize *uint32 `json:"msg_size,omitempty"` MsgSize *uint32 `json:"msg_size,omitempty"`
} }
// Peer represents a connected remote node. // Peer represents a connected remote node.
@ -120,23 +115,17 @@ type Peer struct {
} }
// NewPeer returns a peer for testing purposes. // NewPeer returns a peer for testing purposes.
func NewPeer(id enode.ID, name string, caps []Cap) *Peer { func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer {
pipe, _ := net.Pipe() pipe, _ := net.Pipe()
node := enode.SignNull(new(enr.Record), id) conn := &conn{fd: pipe, transport: nil, id: id, caps: caps, name: name}
conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
peer := newPeer(conn, nil) peer := newPeer(conn, nil)
close(peer.closed) // ensures Disconnect doesn't block close(peer.closed) // ensures Disconnect doesn't block
return peer return peer
} }
// ID returns the node's public key. // ID returns the node's public key.
func (p *Peer) ID() enode.ID { func (p *Peer) ID() discover.NodeID {
return p.rw.node.ID() return p.rw.id
}
// Node returns the peer's node descriptor.
func (p *Peer) Node() *enode.Node {
return p.rw.node
} }
// Name returns the node name that the remote node advertised. // Name returns the node name that the remote node advertised.
@ -171,13 +160,12 @@ func (p *Peer) Disconnect(reason DiscReason) {
// String implements fmt.Stringer. // String implements fmt.Stringer.
func (p *Peer) String() string { func (p *Peer) String() string {
id := p.ID() return fmt.Sprintf("Peer %x %v ", p.rw.id[:8], p.RemoteAddr())
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
} }
// Inbound returns true if the peer is an inbound connection // Inbound returns true if the peer is an inbound connection
func (p *Peer) Inbound() bool { func (p *Peer) Inbound() bool {
return p.rw.is(inboundConn) return p.rw.flags&inboundConn != 0
} }
func newPeer(conn *conn, protocols []Protocol) *Peer { func newPeer(conn *conn, protocols []Protocol) *Peer {
@ -190,7 +178,7 @@ func newPeer(conn *conn, protocols []Protocol) *Peer {
protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
closed: make(chan struct{}), closed: make(chan struct{}),
pingRecv: make(chan struct{}, 16), pingRecv: make(chan struct{}, 16),
log: log.New("id", conn.node.ID(), "conn", conn.flags), log: log.New("id", conn.id, "conn", conn.flags),
} }
return p return p
} }
@ -441,11 +429,9 @@ func (rw *protoRW) ReadMsg() (Msg, error) {
// peer. Sub-protocol independent fields are contained and initialized here, with // peer. Sub-protocol independent fields are contained and initialized here, with
// protocol specifics delegated to all connected sub-protocols. // protocol specifics delegated to all connected sub-protocols.
type PeerInfo struct { type PeerInfo struct {
ENR string `json:"enr,omitempty"` // Ethereum Node Record ID string `json:"id"` // Unique node identifier (also the encryption key)
Enode string `json:"enode"` // Node URL Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
ID string `json:"id"` // Unique node identifier Caps []string `json:"caps"` // Sum-protocols advertised by this particular peer
Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
Caps []string `json:"caps"` // Protocols advertised by this peer
Network struct { Network struct {
LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection
RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection
@ -465,15 +451,11 @@ func (p *Peer) Info() *PeerInfo {
} }
// Assemble the generic peer metadata // Assemble the generic peer metadata
info := &PeerInfo{ info := &PeerInfo{
Enode: p.Node().String(),
ID: p.ID().String(), ID: p.ID().String(),
Name: p.Name(), Name: p.Name(),
Caps: caps, Caps: caps,
Protocols: make(map[string]interface{}), Protocols: make(map[string]interface{}),
} }
if p.Node().Seq() > 0 {
info.ENR = p.Node().String()
}
info.Network.LocalAddress = p.LocalAddr().String() info.Network.LocalAddress = p.LocalAddr().String()
info.Network.RemoteAddress = p.RemoteAddr().String() info.Network.RemoteAddress = p.RemoteAddr().String()
info.Network.Inbound = p.rw.is(inboundConn) info.Network.Inbound = p.rw.is(inboundConn)

View file

@ -45,8 +45,8 @@ var discard = Protocol{
func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
fd1, fd2 := net.Pipe() fd1, fd2 := net.Pipe()
c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)} c1 := &conn{fd: fd1, transport: newTestTransport(randomID(), fd1)}
c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)} c2 := &conn{fd: fd2, transport: newTestTransport(randomID(), fd2)}
for _, p := range protos { for _, p := range protos {
c1.caps = append(c1.caps, p.cap()) c1.caps = append(c1.caps, p.cap())
c2.caps = append(c2.caps, p.cap()) c2.caps = append(c2.caps, p.cap())

View file

@ -19,8 +19,7 @@ package p2p
import ( import (
"fmt" "fmt"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
) )
// Protocol represents a P2P subprotocol implementation. // Protocol represents a P2P subprotocol implementation.
@ -52,10 +51,7 @@ type Protocol struct {
// PeerInfo is an optional helper method to retrieve protocol specific metadata // PeerInfo is an optional helper method to retrieve protocol specific metadata
// about a certain peer in the network. If an info retrieval function is set, // about a certain peer in the network. If an info retrieval function is set,
// but returns nil, it is assumed that the protocol handshake is still running. // but returns nil, it is assumed that the protocol handshake is still running.
PeerInfo func(id enode.ID) interface{} PeerInfo func(id discover.NodeID) interface{}
// Attributes contains protocol specific information for the node record.
Attributes []enr.Entry
} }
func (p Protocol) cap() Cap { func (p Protocol) cap() Cap {
@ -68,6 +64,10 @@ type Cap struct {
Version uint Version uint
} }
func (cap Cap) RlpData() interface{} {
return []interface{}{cap.Name, cap.Version}
}
func (cap Cap) String() string { func (cap Cap) String() string {
return fmt.Sprintf("%s/%d", cap.Name, cap.Version) return fmt.Sprintf("%s/%d", cap.Name, cap.Version)
} }
@ -79,5 +79,3 @@ func (cs capsByNameAndVersion) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
func (cs capsByNameAndVersion) Less(i, j int) bool { func (cs capsByNameAndVersion) Less(i, j int) bool {
return cs[i].Name < cs[j].Name || (cs[i].Name == cs[j].Name && cs[i].Version < cs[j].Version) return cs[i].Name < cs[j].Name || (cs[i].Name == cs[j].Name && cs[i].Version < cs[j].Version)
} }
func (capsByNameAndVersion) ENRKey() string { return "cap" }

View file

@ -24,7 +24,7 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
p2ptest "github.com/XinFinOrg/XDPoSChain/p2p/testing" p2ptest "github.com/XinFinOrg/XDPoSChain/p2p/testing"
) )
@ -36,7 +36,7 @@ type hs0 struct {
// message to kill/drop the peer with nodeID // message to kill/drop the peer with nodeID
type kill struct { type kill struct {
C enode.ID C discover.NodeID
} }
// message to drop connection // message to drop connection
@ -144,7 +144,7 @@ func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTes
return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp)) return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp))
} }
func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange {
return []p2ptest.Exchange{ return []p2ptest.Exchange{
{ {
@ -172,13 +172,13 @@ func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(t, pp)
// TODO: make this more than one handshake // TODO: make this more than one handshake
node := s.Nodes[0] id := s.IDs[0]
if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var disconnects []*p2ptest.Disconnect var disconnects []*p2ptest.Disconnect
for i, err := range errs { for i, err := range errs {
disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
} }
if err := s.TestDisconnected(disconnects...); err != nil { if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err) t.Fatal(err)
@ -197,7 +197,7 @@ func TestProtoHandshakeSuccess(t *testing.T) {
runProtoHandshake(t, &protoHandshake{42, "420"}) runProtoHandshake(t, &protoHandshake{42, "420"})
} }
func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
return []p2ptest.Exchange{ return []p2ptest.Exchange{
{ {
@ -224,16 +224,16 @@ func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange {
func runModuleHandshake(t *testing.T, resp uint, errs ...error) { func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(t, pp)
node := s.Nodes[0] id := s.IDs[0]
if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil { if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var disconnects []*p2ptest.Disconnect var disconnects []*p2ptest.Disconnect
for i, err := range errs { for i, err := range errs {
disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
} }
if err := s.TestDisconnected(disconnects...); err != nil { if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err) t.Fatal(err)
@ -249,7 +249,7 @@ func TestModuleHandshakeSuccess(t *testing.T) {
} }
// testing complex interactions over multiple peers, relaying, dropping // testing complex interactions over multiple peers, relaying, dropping
func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange {
return []p2ptest.Exchange{ return []p2ptest.Exchange{
{ {
@ -305,7 +305,7 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(t, pp)
if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// after some exchanges of messages, we can test state changes // after some exchanges of messages, we can test state changes
@ -318,15 +318,15 @@ WAIT:
for { for {
select { select {
case <-tick.C: case <-tick.C:
if pp.Has(s.Nodes[0].ID()) { if pp.Has(s.IDs[0]) {
break WAIT break WAIT
} }
case <-timeout.C: case <-timeout.C:
t.Fatal("timeout") t.Fatal("timeout")
} }
} }
if !pp.Has(s.Nodes[1].ID()) { if !pp.Has(s.IDs[1]) {
t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes) t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
} }
// peer 0 sends kill request for peer with index <peer> // peer 0 sends kill request for peer with index <peer>
@ -334,8 +334,8 @@ WAIT:
Triggers: []p2ptest.Trigger{ Triggers: []p2ptest.Trigger{
{ {
Code: 2, Code: 2,
Msg: &kill{s.Nodes[peer].ID()}, Msg: &kill{s.IDs[peer]},
Peer: s.Nodes[0].ID(), Peer: s.IDs[0],
}, },
}, },
}) })
@ -350,7 +350,7 @@ WAIT:
{ {
Code: 3, Code: 3,
Msg: &drop{}, Msg: &drop{},
Peer: s.Nodes[(peer+1)%2].ID(), Peer: s.IDs[(peer+1)%2],
}, },
}, },
}) })
@ -362,14 +362,14 @@ WAIT:
// check the actual discconnect errors on the individual peers // check the actual discconnect errors on the individual peers
var disconnects []*p2ptest.Disconnect var disconnects []*p2ptest.Disconnect
for i, err := range errs { for i, err := range errs {
disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
} }
if err := s.TestDisconnected(disconnects...); err != nil { if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test if disconnected peers have been removed from peerPool // test if disconnected peers have been removed from peerPool
if pp.Has(s.Nodes[peer].ID()) { if pp.Has(s.IDs[peer]) {
t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes) t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs)
} }
} }

View file

@ -34,11 +34,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common/bitutil"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/ecies" "github.com/XinFinOrg/XDPoSChain/crypto/ecies"
"github.com/XinFinOrg/XDPoSChain/crypto/secp256k1" "github.com/XinFinOrg/XDPoSChain/crypto/secp256k1"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/golang/snappy" "github.com/golang/snappy"
) )
@ -165,7 +165,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
if err := msg.Decode(&hs); err != nil { if err := msg.Decode(&hs); err != nil {
return nil, err return nil, err
} }
if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { if (hs.ID == discover.NodeID{}) {
return nil, DiscInvalidIdentity return nil, DiscInvalidIdentity
} }
return &hs, nil return &hs, nil
@ -175,7 +175,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
// messages. the protocol handshake is the first authenticated message // messages. the protocol handshake is the first authenticated message
// and also verifies whether the encryption handshake 'worked' and the // and also verifies whether the encryption handshake 'worked' and the
// remote side actually provided the right public key. // remote side actually provided the right public key.
func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) {
var ( var (
sec secrets sec secrets
err error err error
@ -183,21 +183,23 @@ func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ec
if dial == nil { if dial == nil {
sec, err = receiverEncHandshake(t.fd, prv, nil) sec, err = receiverEncHandshake(t.fd, prv, nil)
} else { } else {
sec, err = initiatorEncHandshake(t.fd, prv, dial) sec, err = initiatorEncHandshake(t.fd, prv, dial.ID, nil)
} }
if err != nil { if err != nil {
return nil, err return discover.NodeID{}, err
} }
t.wmu.Lock() t.wmu.Lock()
t.rw = newRLPXFrameRW(t.fd, sec) t.rw = newRLPXFrameRW(t.fd, sec)
t.wmu.Unlock() t.wmu.Unlock()
return sec.Remote.ExportECDSA(), nil return sec.RemoteID, nil
} }
// encHandshake contains the state of the encryption handshake. // encHandshake contains the state of the encryption handshake.
type encHandshake struct { type encHandshake struct {
initiator bool initiator bool
remote *ecies.PublicKey // remote-pubk remoteID discover.NodeID
remotePub *ecies.PublicKey // remote-pubk
initNonce, respNonce []byte // nonce initNonce, respNonce []byte // nonce
randomPrivKey *ecies.PrivateKey // ecdhe-random randomPrivKey *ecies.PrivateKey // ecdhe-random
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
@ -206,7 +208,7 @@ type encHandshake struct {
// secrets represents the connection secrets // secrets represents the connection secrets
// which are negotiated during the encryption handshake. // which are negotiated during the encryption handshake.
type secrets struct { type secrets struct {
Remote *ecies.PublicKey RemoteID discover.NodeID
AES, MAC []byte AES, MAC []byte
EgressMAC, IngressMAC hash.Hash EgressMAC, IngressMAC hash.Hash
Token []byte Token []byte
@ -247,9 +249,9 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
s := secrets{ s := secrets{
Remote: h.remote, RemoteID: h.remoteID,
AES: aesSecret, AES: aesSecret,
MAC: crypto.Keccak256(ecdheSecret, aesSecret), MAC: crypto.Keccak256(ecdheSecret, aesSecret),
} }
// setup sha3 instances for the MACs // setup sha3 instances for the MACs
@ -271,16 +273,16 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
// staticSharedSecret returns the static shared secret, the result // staticSharedSecret returns the static shared secret, the result
// of key agreement between the local and remote static node key. // of key agreement between the local and remote static node key.
func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen) return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
} }
// initiatorEncHandshake negotiates a session token on conn. // initiatorEncHandshake negotiates a session token on conn.
// it should be called on the dialing side of the connection. // it should be called on the dialing side of the connection.
// //
// prv is the local client's private key. // prv is the local client's private key.
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) { func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) {
h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)} h := &encHandshake{initiator: true, remoteID: remoteID}
authMsg, err := h.makeAuthMsg(prv) authMsg, err := h.makeAuthMsg(prv, token)
if err != nil { if err != nil {
return s, err return s, err
} }
@ -304,11 +306,15 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ec
} }
// makeAuthMsg creates the initiator handshake message. // makeAuthMsg creates the initiator handshake message.
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, token []byte) (*authMsgV4, error) {
rpub, err := h.remoteID.Pubkey()
if err != nil {
return nil, fmt.Errorf("bad remoteID: %v", err)
}
h.remotePub = ecies.ImportECDSAPublic(rpub)
// Generate random initiator nonce. // Generate random initiator nonce.
h.initNonce = make([]byte, shaLen) h.initNonce = make([]byte, shaLen)
_, err := rand.Read(h.initNonce) if _, err := rand.Read(h.initNonce); err != nil {
if err != nil {
return nil, err return nil, err
} }
// Generate random keypair to for ECDH. // Generate random keypair to for ECDH.
@ -318,7 +324,7 @@ func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
} }
// Sign known message: static-shared-secret ^ nonce // Sign known message: static-shared-secret ^ nonce
token, err := h.staticSharedSecret(prv) token, err = h.staticSharedSecret(prv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -379,12 +385,13 @@ func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byt
func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
// Import the remote identity. // Import the remote identity.
rpub, err := importPublicKey(msg.InitiatorPubkey[:])
if err != nil {
return err
}
h.initNonce = msg.Nonce[:] h.initNonce = msg.Nonce[:]
h.remote = rpub h.remoteID = msg.InitiatorPubkey
rpub, err := h.remoteID.Pubkey()
if err != nil {
return fmt.Errorf("bad remoteID: %#v", err)
}
h.remotePub = ecies.ImportECDSAPublic(rpub)
// Generate random keypair for ECDH. // Generate random keypair for ECDH.
// If a private key is already set, use it instead of generating one (for testing). // If a private key is already set, use it instead of generating one (for testing).
@ -430,7 +437,7 @@ func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) {
n += copy(buf[n:], msg.InitiatorPubkey[:]) n += copy(buf[n:], msg.InitiatorPubkey[:])
n += copy(buf[n:], msg.Nonce[:]) n += copy(buf[n:], msg.Nonce[:])
buf[n] = 0 // token-flag buf[n] = 0 // token-flag
return ecies.Encrypt(rand.Reader, h.remote, buf, nil, nil) return ecies.Encrypt(rand.Reader, h.remotePub, buf, nil, nil)
} }
func (msg *authMsgV4) decodePlain(input []byte) { func (msg *authMsgV4) decodePlain(input []byte) {
@ -446,7 +453,7 @@ func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) {
buf := make([]byte, authRespLen) buf := make([]byte, authRespLen)
n := copy(buf, msg.RandomPubkey[:]) n := copy(buf, msg.RandomPubkey[:])
copy(buf[n:], msg.Nonce[:]) copy(buf[n:], msg.Nonce[:])
return ecies.Encrypt(rand.Reader, hs.remote, buf, nil, nil) return ecies.Encrypt(rand.Reader, hs.remotePub, buf, nil, nil)
} }
func (msg *authRespV4) decodePlain(input []byte) { func (msg *authRespV4) decodePlain(input []byte) {
@ -469,7 +476,7 @@ func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {
prefix := make([]byte, 2) prefix := make([]byte, 2)
binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead)) binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead))
enc, err := ecies.Encrypt(rand.Reader, h.remote, buf.Bytes(), nil, prefix) enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix)
return append(prefix, enc...), err return append(prefix, enc...), err
} }

View file

@ -18,7 +18,6 @@ package p2p
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"crypto/rand" "crypto/rand"
"errors" "errors"
"fmt" "fmt"
@ -33,6 +32,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/ecies" "github.com/XinFinOrg/XDPoSChain/crypto/ecies"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
) )
@ -78,9 +78,9 @@ func TestEncHandshake(t *testing.T) {
func testEncHandshake(token []byte) error { func testEncHandshake(token []byte) error {
type result struct { type result struct {
side string side string
pubkey *ecdsa.PublicKey id discover.NodeID
err error err error
} }
var ( var (
prv0, _ = crypto.GenerateKey() prv0, _ = crypto.GenerateKey()
@ -95,12 +95,14 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }() defer func() { output <- r }()
defer fd0.Close() defer fd0.Close()
r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey) dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)}
r.id, r.err = c0.doEncHandshake(prv0, dest)
if r.err != nil { if r.err != nil {
return return
} }
if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) { id1 := discover.PubkeyID(&prv1.PublicKey)
r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey) if r.id != id1 {
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id1)
} }
}() }()
go func() { go func() {
@ -108,12 +110,13 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }() defer func() { output <- r }()
defer fd1.Close() defer fd1.Close()
r.pubkey, r.err = c1.doEncHandshake(prv1, nil) r.id, r.err = c1.doEncHandshake(prv1, nil)
if r.err != nil { if r.err != nil {
return return
} }
if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) { id0 := discover.PubkeyID(&prv0.PublicKey)
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey) if r.id != id0 {
r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id0)
} }
}() }()
@ -145,12 +148,12 @@ func testEncHandshake(token []byte) error {
func TestProtocolHandshake(t *testing.T) { func TestProtocolHandshake(t *testing.T) {
var ( var (
prv0, _ = crypto.GenerateKey() prv0, _ = crypto.GenerateKey()
pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] node0 = &discover.Node{ID: discover.PubkeyID(&prv0.PublicKey), IP: net.IP{1, 2, 3, 4}, TCP: 33}
hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} hs0 = &protoHandshake{Version: 3, ID: node0.ID, Caps: []Cap{{"a", 0}, {"b", 2}}}
prv1, _ = crypto.GenerateKey() prv1, _ = crypto.GenerateKey()
pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] node1 = &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey), IP: net.IP{5, 6, 7, 8}, TCP: 44}
hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} hs1 = &protoHandshake{Version: 3, ID: node1.ID, Caps: []Cap{{"c", 1}, {"d", 3}}}
wg sync.WaitGroup wg sync.WaitGroup
) )
@ -165,13 +168,13 @@ func TestProtocolHandshake(t *testing.T) {
defer wg.Done() defer wg.Done()
defer fd0.Close() defer fd0.Close()
rlpx := newRLPX(fd0) rlpx := newRLPX(fd0)
rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey) remid, err := rlpx.doEncHandshake(prv0, node1)
if err != nil { if err != nil {
t.Errorf("dial side enc handshake failed: %v", err) t.Errorf("dial side enc handshake failed: %v", err)
return return
} }
if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { if remid != node1.ID {
t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) t.Errorf("dial side remote id mismatch: got %v, want %v", remid, node1.ID)
return return
} }
@ -191,13 +194,13 @@ func TestProtocolHandshake(t *testing.T) {
defer wg.Done() defer wg.Done()
defer fd1.Close() defer fd1.Close()
rlpx := newRLPX(fd1) rlpx := newRLPX(fd1)
rpubkey, err := rlpx.doEncHandshake(prv1, nil) remid, err := rlpx.doEncHandshake(prv1, nil)
if err != nil { if err != nil {
t.Errorf("listen side enc handshake failed: %v", err) t.Errorf("listen side enc handshake failed: %v", err)
return return
} }
if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { if remid != node0.ID {
t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) t.Errorf("listen side remote id mismatch: got %v, want %v", remid, node0.ID)
return return
} }

View file

@ -18,29 +18,20 @@
package p2p package p2p
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/hex"
"errors" "errors"
"fmt"
"net" "net"
"sort"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/mclock" "github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/discv5" "github.com/XinFinOrg/XDPoSChain/p2p/discv5"
"github.com/XinFinOrg/XDPoSChain/p2p/enode"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
"github.com/XinFinOrg/XDPoSChain/p2p/nat" "github.com/XinFinOrg/XDPoSChain/p2p/nat"
"github.com/XinFinOrg/XDPoSChain/p2p/netutil" "github.com/XinFinOrg/XDPoSChain/p2p/netutil"
"github.com/XinFinOrg/XDPoSChain/rlp"
) )
const ( const (
@ -94,7 +85,7 @@ type Config struct {
// BootstrapNodes are used to establish connectivity // BootstrapNodes are used to establish connectivity
// with the rest of the network. // with the rest of the network.
BootstrapNodes []*enode.Node BootstrapNodes []*discover.Node
// BootstrapNodesV5 are used to establish connectivity // BootstrapNodesV5 are used to establish connectivity
// with the rest of the network using the V5 discovery // with the rest of the network using the V5 discovery
@ -103,11 +94,11 @@ type Config struct {
// Static nodes are used as pre-configured connections which are always // Static nodes are used as pre-configured connections which are always
// maintained and re-connected on disconnects. // maintained and re-connected on disconnects.
StaticNodes []*enode.Node StaticNodes []*discover.Node
// Trusted nodes are used as pre-configured connections which are always // Trusted nodes are used as pre-configured connections which are always
// allowed to connect, even above the peer limit. // allowed to connect, even above the peer limit.
TrustedNodes []*enode.Node TrustedNodes []*discover.Node
// Connectivity can be restricted to certain IP networks. // Connectivity can be restricted to certain IP networks.
// If this option is set to a non-nil value, only hosts which match one of the // If this option is set to a non-nil value, only hosts which match one of the
@ -164,8 +155,6 @@ type Server struct {
lock sync.Mutex // protects running lock sync.Mutex // protects running
running bool running bool
nodedb *enode.DB
localnode *enode.LocalNode
ntab discoverTable ntab discoverTable
listener net.Listener listener net.Listener
ourHandshake *protoHandshake ourHandshake *protoHandshake
@ -177,10 +166,8 @@ type Server struct {
peerOpDone chan struct{} peerOpDone chan struct{}
quit chan struct{} quit chan struct{}
addstatic chan *enode.Node addstatic chan *discover.Node
removestatic chan *enode.Node removestatic chan *discover.Node
addtrusted chan *enode.Node
removetrusted chan *enode.Node
posthandshake chan *conn posthandshake chan *conn
addpeer chan *conn addpeer chan *conn
delpeer chan peerDrop delpeer chan peerDrop
@ -189,7 +176,7 @@ type Server struct {
log log.Logger log log.Logger
} }
type peerOpFunc func(map[enode.ID]*Peer) type peerOpFunc func(map[discover.NodeID]*Peer)
type peerDrop struct { type peerDrop struct {
*Peer *Peer
@ -197,7 +184,7 @@ type peerDrop struct {
requested bool // true if signaled by the peer requested bool // true if signaled by the peer
} }
type connFlag int32 type connFlag int
const ( const (
dynDialedConn connFlag = 1 << iota dynDialedConn connFlag = 1 << iota
@ -211,16 +198,16 @@ const (
type conn struct { type conn struct {
fd net.Conn fd net.Conn
transport transport
node *enode.Node
flags connFlag flags connFlag
cont chan error // The run loop uses cont to signal errors to SetupConn. cont chan error // The run loop uses cont to signal errors to SetupConn.
caps []Cap // valid after the protocol handshake id discover.NodeID // valid after the encryption handshake
name string // valid after the protocol handshake caps []Cap // valid after the protocol handshake
name string // valid after the protocol handshake
} }
type transport interface { type transport interface {
// The two handshakes. // The two handshakes.
doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error)
doProtoHandshake(our *protoHandshake) (*protoHandshake, error) doProtoHandshake(our *protoHandshake) (*protoHandshake, error)
// The MsgReadWriter can only be used after the encryption // The MsgReadWriter can only be used after the encryption
// handshake has completed. The code uses conn.id to track this // handshake has completed. The code uses conn.id to track this
@ -234,8 +221,8 @@ type transport interface {
func (c *conn) String() string { func (c *conn) String() string {
s := c.flags.String() s := c.flags.String()
if (c.node.ID() != enode.ID{}) { if (c.id != discover.NodeID{}) {
s += " " + c.node.ID().String() s += " " + c.id.String()
} }
s += " " + c.fd.RemoteAddr().String() s += " " + c.fd.RemoteAddr().String()
return s return s
@ -262,23 +249,7 @@ func (f connFlag) String() string {
} }
func (c *conn) is(f connFlag) bool { func (c *conn) is(f connFlag) bool {
flags := connFlag(atomic.LoadInt32((*int32)(&c.flags))) return c.flags&f != 0
return flags&f != 0
}
func (c *conn) set(f connFlag, val bool) {
flags := connFlag(atomic.LoadInt32((*int32)(&c.flags)))
if val {
flags |= f
} else {
flags &= ^f
}
atomic.StoreInt32((*int32)(&c.flags), int32(flags))
}
// LocalNode returns the local node record.
func (srv *Server) LocalNode() *enode.LocalNode {
return srv.localnode
} }
// Peers returns all connected peers. // Peers returns all connected peers.
@ -288,7 +259,7 @@ func (srv *Server) Peers() []*Peer {
// Note: We'd love to put this function into a variable but // Note: We'd love to put this function into a variable but
// that seems to cause a weird compiler error in some // that seems to cause a weird compiler error in some
// environments. // environments.
case srv.peerOp <- func(peers map[enode.ID]*Peer) { case srv.peerOp <- func(peers map[discover.NodeID]*Peer) {
for _, p := range peers { for _, p := range peers {
ps = append(ps, p) ps = append(ps, p)
} }
@ -303,7 +274,7 @@ func (srv *Server) Peers() []*Peer {
func (srv *Server) PeerCount() int { func (srv *Server) PeerCount() int {
var count int var count int
select { select {
case srv.peerOp <- func(ps map[enode.ID]*Peer) { count = len(ps) }: case srv.peerOp <- func(ps map[discover.NodeID]*Peer) { count = len(ps) }:
<-srv.peerOpDone <-srv.peerOpDone
case <-srv.quit: case <-srv.quit:
} }
@ -313,7 +284,8 @@ func (srv *Server) PeerCount() int {
// AddPeer connects to the given node and maintains the connection until the // AddPeer connects to the given node and maintains the connection until the
// server is shut down. If the connection fails for any reason, the server will // server is shut down. If the connection fails for any reason, the server will
// attempt to reconnect the peer. // attempt to reconnect the peer.
func (srv *Server) AddPeer(node *enode.Node) { func (srv *Server) AddPeer(node *discover.Node) {
select { select {
case srv.addstatic <- node: case srv.addstatic <- node:
case <-srv.quit: case <-srv.quit:
@ -321,45 +293,47 @@ func (srv *Server) AddPeer(node *enode.Node) {
} }
// RemovePeer disconnects from the given node // RemovePeer disconnects from the given node
func (srv *Server) RemovePeer(node *enode.Node) { func (srv *Server) RemovePeer(node *discover.Node) {
select { select {
case srv.removestatic <- node: case srv.removestatic <- node:
case <-srv.quit: case <-srv.quit:
} }
} }
// AddTrustedPeer adds the given node to a reserved whitelist which allows the
// node to always connect, even if the slot are full.
func (srv *Server) AddTrustedPeer(node *enode.Node) {
select {
case srv.addtrusted <- node:
case <-srv.quit:
}
}
// RemoveTrustedPeer removes the given node from the trusted peer set.
func (srv *Server) RemoveTrustedPeer(node *enode.Node) {
select {
case srv.removetrusted <- node:
case <-srv.quit:
}
}
// SubscribePeers subscribes the given channel to peer events // SubscribePeers subscribes the given channel to peer events
func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription {
return srv.peerFeed.Subscribe(ch) return srv.peerFeed.Subscribe(ch)
} }
// Self returns the local node's endpoint information. // Self returns the local node's endpoint information.
func (srv *Server) Self() *enode.Node { func (srv *Server) Self() *discover.Node {
srv.lock.Lock() srv.lock.Lock()
ln := srv.localnode defer srv.lock.Unlock()
srv.lock.Unlock()
if ln == nil { if !srv.running {
return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0) return &discover.Node{IP: net.ParseIP("0.0.0.0")}
} }
return ln.Node() return srv.makeSelf(srv.listener, srv.ntab)
}
func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover.Node {
// If the server's not running, return an empty node.
// If the node is running but discovery is off, manually assemble the node infos.
if ntab == nil {
// Inbound connections disabled, use zero address.
if listener == nil {
return &discover.Node{IP: net.ParseIP("0.0.0.0"), ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
}
// Otherwise inject the listener address too
addr := listener.Addr().(*net.TCPAddr)
return &discover.Node{
ID: discover.PubkeyID(&srv.PrivateKey.PublicKey),
IP: addr.IP,
TCP: uint16(addr.Port),
}
}
// Otherwise return the discovery node.
return ntab.Self()
} }
// Stop terminates the server and all active peer connections. // Stop terminates the server and all active peer connections.
@ -418,9 +392,7 @@ func (srv *Server) Start() (err error) {
if srv.log == nil { if srv.log == nil {
srv.log = log.New() srv.log = log.New()
} }
if srv.NoDial && srv.ListenAddr == "" { srv.log.Info("Starting P2P networking")
srv.log.Warn("P2P server will be useless, neither dialing nor listening")
}
// static fields // static fields
if srv.PrivateKey == nil { if srv.PrivateKey == nil {
@ -436,127 +408,70 @@ func (srv *Server) Start() (err error) {
srv.addpeer = make(chan *conn) srv.addpeer = make(chan *conn)
srv.delpeer = make(chan peerDrop) srv.delpeer = make(chan peerDrop)
srv.posthandshake = make(chan *conn) srv.posthandshake = make(chan *conn)
srv.addstatic = make(chan *enode.Node) srv.addstatic = make(chan *discover.Node)
srv.removestatic = make(chan *enode.Node) srv.removestatic = make(chan *discover.Node)
srv.addtrusted = make(chan *enode.Node)
srv.removetrusted = make(chan *enode.Node)
srv.peerOp = make(chan peerOpFunc) srv.peerOp = make(chan peerOpFunc)
srv.peerOpDone = make(chan struct{}) srv.peerOpDone = make(chan struct{})
if err := srv.setupLocalNode(); err != nil { var (
return err conn *net.UDPConn
} sconn *sharedUDPConn
if srv.ListenAddr != "" { realaddr *net.UDPAddr
if err := srv.setupListening(); err != nil { unhandled chan discover.ReadPacket
)
if !srv.NoDiscovery || srv.DiscoveryV5 {
addr, err := net.ResolveUDPAddr("udp", srv.ListenAddr)
if err != nil {
return err return err
} }
} conn, err = net.ListenUDP("udp", addr)
if err := srv.setupDiscovery(); err != nil { if err != nil {
return err return err
}
dynPeers := srv.maxDialedConns()
dialer := newDialState(srv.localnode.ID(), srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
srv.loopWG.Add(1)
go srv.run(dialer)
return nil
}
func (srv *Server) setupLocalNode() error {
// Create the devp2p handshake.
pubkey := crypto.FromECDSAPub(&srv.PrivateKey.PublicKey)
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: pubkey[1:]}
for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
}
sort.Sort(capsByNameAndVersion(srv.ourHandshake.Caps))
// Create the local node.
db, err := enode.OpenDB(srv.Config.NodeDatabase)
if err != nil {
return err
}
srv.nodedb = db
srv.localnode = enode.NewLocalNode(db, srv.PrivateKey)
srv.localnode.SetFallbackIP(net.IP{127, 0, 0, 1})
srv.localnode.Set(capsByNameAndVersion(srv.ourHandshake.Caps))
// TODO: check conflicts
for _, p := range srv.Protocols {
for _, e := range p.Attributes {
srv.localnode.Set(e)
} }
} realaddr = conn.LocalAddr().(*net.UDPAddr)
switch srv.NAT.(type) { if srv.NAT != nil {
case nil: if !realaddr.IP.IsLoopback() {
// No NAT interface, do nothing. go nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
case nat.ExtIP: }
// ExtIP doesn't block, set the IP right away. // TODO: react to external IP changes over time.
ip, _ := srv.NAT.ExternalIP() if ext, err := srv.NAT.ExternalIP(); err == nil {
srv.localnode.SetStaticIP(ip) realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
default:
// Ask the router about the IP. This takes a while and blocks startup,
// do it in the background.
srv.loopWG.Add(1)
go func() {
defer srv.loopWG.Done()
if ip, err := srv.NAT.ExternalIP(); err == nil {
srv.localnode.SetStaticIP(ip)
} }
}()
}
return nil
}
func (srv *Server) setupDiscovery() error {
if srv.NoDiscovery && !srv.DiscoveryV5 {
return nil
}
addr, err := net.ResolveUDPAddr("udp", srv.ListenAddr)
if err != nil {
return err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
realaddr := conn.LocalAddr().(*net.UDPAddr)
srv.log.Debug("UDP listener up", "addr", realaddr)
if srv.NAT != nil {
if !realaddr.IP.IsLoopback() {
go nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
} }
} }
srv.localnode.SetFallbackUDP(realaddr.Port)
// Discovery V4 if !srv.NoDiscovery && srv.DiscoveryV5 {
var unhandled chan discover.ReadPacket unhandled = make(chan discover.ReadPacket, 100)
var sconn *sharedUDPConn sconn = &sharedUDPConn{conn, unhandled}
}
// node table
if !srv.NoDiscovery { if !srv.NoDiscovery {
if srv.DiscoveryV5 {
unhandled = make(chan discover.ReadPacket, 100)
sconn = &sharedUDPConn{conn, unhandled}
}
cfg := discover.Config{ cfg := discover.Config{
PrivateKey: srv.PrivateKey, PrivateKey: srv.PrivateKey,
NetRestrict: srv.NetRestrict, AnnounceAddr: realaddr,
Bootnodes: srv.BootstrapNodes, NodeDBPath: srv.NodeDatabase,
Unhandled: unhandled, NetRestrict: srv.NetRestrict,
Bootnodes: srv.BootstrapNodes,
Unhandled: unhandled,
} }
ntab, err := discover.ListenUDP(conn, srv.localnode, cfg) ntab, err := discover.ListenUDP(conn, cfg)
if err != nil { if err != nil {
return err return err
} }
srv.ntab = ntab srv.ntab = ntab
} }
// Discovery V5
if srv.DiscoveryV5 { if srv.DiscoveryV5 {
var ntab *discv5.Network var (
var err error ntab *discv5.Network
err error
)
if sconn != nil { if sconn != nil {
ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict) ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase)
} else { } else {
ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict) ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase)
} }
if err != nil { if err != nil {
return err return err
@ -566,10 +481,32 @@ func (srv *Server) setupDiscovery() error {
} }
srv.DiscV5 = ntab srv.DiscV5 = ntab
} }
dynPeers := srv.maxDialedConns()
dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
// handshake
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
}
// listen/dial
if srv.ListenAddr != "" {
if err := srv.startListening(); err != nil {
return err
}
}
if srv.NoDial && srv.ListenAddr == "" {
srv.log.Warn("P2P server will be useless, neither dialing nor listening")
}
srv.loopWG.Add(1)
go srv.run(dialer)
srv.running = true
return nil return nil
} }
func (srv *Server) setupListening() error { func (srv *Server) startListening() error {
// Launch the TCP listener. // Launch the TCP listener.
listener, err := net.Listen("tcp", srv.ListenAddr) listener, err := net.Listen("tcp", srv.ListenAddr)
if err != nil { if err != nil {
@ -578,11 +515,8 @@ func (srv *Server) setupListening() error {
laddr := listener.Addr().(*net.TCPAddr) laddr := listener.Addr().(*net.TCPAddr)
srv.ListenAddr = laddr.String() srv.ListenAddr = laddr.String()
srv.listener = listener srv.listener = listener
srv.localnode.Set(enr.TCP(laddr.Port))
srv.loopWG.Add(1) srv.loopWG.Add(1)
go srv.listenLoop() go srv.listenLoop()
// Map the TCP listening port if NAT is configured. // Map the TCP listening port if NAT is configured.
if !laddr.IP.IsLoopback() && srv.NAT != nil { if !laddr.IP.IsLoopback() && srv.NAT != nil {
srv.loopWG.Add(1) srv.loopWG.Add(1)
@ -595,29 +529,27 @@ func (srv *Server) setupListening() error {
} }
type dialer interface { type dialer interface {
newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task
taskDone(task, time.Time) taskDone(task, time.Time)
addStatic(*enode.Node) addStatic(*discover.Node)
removeStatic(*enode.Node) removeStatic(*discover.Node)
} }
func (srv *Server) run(dialstate dialer) { func (srv *Server) run(dialstate dialer) {
srv.log.Info("Started P2P networking", "self", srv.localnode.Node())
defer srv.loopWG.Done() defer srv.loopWG.Done()
defer srv.nodedb.Close()
var ( var (
peers = make(map[enode.ID]*Peer) peers = make(map[discover.NodeID]*Peer)
inboundCount = 0 inboundCount = 0
trusted = make(map[enode.ID]bool, len(srv.TrustedNodes)) trusted = make(map[discover.NodeID]bool, len(srv.TrustedNodes))
taskdone = make(chan task, maxActiveDialTasks) taskdone = make(chan task, maxActiveDialTasks)
runningTasks []task runningTasks []task
queuedTasks []task // tasks that can't run yet queuedTasks []task // tasks that can't run yet
) )
// Put trusted nodes into a map to speed up checks. // Put trusted nodes into a map to speed up checks.
// Trusted peers are loaded on startup or added via AddTrustedPeer RPC. // Trusted peers are loaded on startup and cannot be
// modified while the server is running.
for _, n := range srv.TrustedNodes { for _, n := range srv.TrustedNodes {
trusted[n.ID()] = true trusted[n.ID] = true
} }
// removes t from runningTasks // removes t from runningTasks
@ -667,32 +599,12 @@ running:
case n := <-srv.removestatic: case n := <-srv.removestatic:
// This channel is used by RemovePeer to send a // This channel is used by RemovePeer to send a
// disconnect request to a peer and begin the // disconnect request to a peer and begin the
// stop keeping the node connected. // stop keeping the node connected
srv.log.Trace("Removing static node", "node", n) srv.log.Debug("Removing static node", "node", n)
dialstate.removeStatic(n) dialstate.removeStatic(n)
if p, ok := peers[n.ID()]; ok { if p, ok := peers[n.ID]; ok {
p.Disconnect(DiscRequested) p.Disconnect(DiscRequested)
} }
case n := <-srv.addtrusted:
// This channel is used by AddTrustedPeer to add an enode
// to the trusted node set.
srv.log.Trace("Adding trusted node", "node", n)
trusted[n.ID()] = true
// Mark any already-connected peer as trusted
if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, true)
}
case n := <-srv.removetrusted:
// This channel is used by RemoveTrustedPeer to remove an enode
// from the trusted node set.
srv.log.Trace("Removing trusted node", "node", n)
if _, ok := trusted[n.ID()]; ok {
delete(trusted, n.ID())
}
// Unmark any already-connected peer as trusted
if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, false)
}
case op := <-srv.peerOp: case op := <-srv.peerOp:
// This channel is used by Peers and PeerCount. // This channel is used by Peers and PeerCount.
op(peers) op(peers)
@ -707,7 +619,7 @@ running:
case c := <-srv.posthandshake: case c := <-srv.posthandshake:
// A connection has passed the encryption handshake so // A connection has passed the encryption handshake so
// the remote identity is known (but hasn't been verified yet). // the remote identity is known (but hasn't been verified yet).
if trusted[c.node.ID()] { if trusted[c.id] {
// Ensure that the trusted flag is set before checking against MaxPeers. // Ensure that the trusted flag is set before checking against MaxPeers.
c.flags |= trustedConn c.flags |= trustedConn
} }
@ -729,21 +641,19 @@ running:
if srv.EnableMsgEvents { if srv.EnableMsgEvents {
p.events = &srv.peerFeed p.events = &srv.peerFeed
} }
go srv.runPeer(p)
name := truncateName(c.name) name := truncateName(c.name)
if peers[c.node.ID()] != nil {
peers[c.node.ID()].PairPeer = p go srv.runPeer(p)
if peers[c.id] != nil {
peers[c.id].PairPeer = p
srv.log.Debug("Adding p2p pair peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1) srv.log.Debug("Adding p2p pair peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
} else { } else {
peers[c.node.ID()] = p peers[c.id] = p
srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1) srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
} }
if p.Inbound() { if p.Inbound() {
inboundCount++ inboundCount++
} }
} else {
srv.log.Debug("Error adding p2p peer", "err", err)
} }
// The dialer logic relies on the assumption that // The dialer logic relies on the assumption that
// dial tasks complete after the peer has been added or // dial tasks complete after the peer has been added or
@ -787,7 +697,7 @@ running:
} }
} }
func (srv *Server) protoHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
// Drop connections with no matching protocols. // Drop connections with no matching protocols.
if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 {
return DiscUselessPeer return DiscUselessPeer
@ -797,15 +707,19 @@ func (srv *Server) protoHandshakeChecks(peers map[enode.ID]*Peer, inboundCount i
return srv.encHandshakeChecks(peers, inboundCount, c) return srv.encHandshakeChecks(peers, inboundCount, c)
} }
func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
switch { switch {
case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers: case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers:
return DiscTooManyPeers return DiscTooManyPeers
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns(): case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
return DiscTooManyPeers return DiscTooManyPeers
case peers[c.node.ID()] != nil: case peers[c.id] != nil:
return DiscAlreadyConnected exitPeer := peers[c.id]
case c.node.ID() == srv.localnode.ID(): if exitPeer.PairPeer != nil {
return DiscAlreadyConnected
}
return nil
case c.id == srv.Self().ID:
return DiscSelf return DiscSelf
default: default:
return nil return nil
@ -815,6 +729,7 @@ func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int
func (srv *Server) maxInboundConns() int { func (srv *Server) maxInboundConns() int {
return srv.MaxPeers - srv.maxDialedConns() return srv.MaxPeers - srv.maxDialedConns()
} }
func (srv *Server) maxDialedConns() int { func (srv *Server) maxDialedConns() int {
if srv.NoDiscovery || srv.NoDial { if srv.NoDiscovery || srv.NoDial {
return 0 return 0
@ -826,11 +741,15 @@ func (srv *Server) maxDialedConns() int {
return srv.MaxPeers / r return srv.MaxPeers / r
} }
type tempError interface {
Temporary() bool
}
// listenLoop runs in its own goroutine and accepts // listenLoop runs in its own goroutine and accepts
// inbound connections. // inbound connections.
func (srv *Server) listenLoop() { func (srv *Server) listenLoop() {
defer srv.loopWG.Done() defer srv.loopWG.Done()
srv.log.Debug("TCP listener up", "addr", srv.listener.Addr()) srv.log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab))
tokens := defaultMaxPendingPeers tokens := defaultMaxPendingPeers
if srv.MaxPendingPeers > 0 { if srv.MaxPendingPeers > 0 {
@ -851,7 +770,7 @@ func (srv *Server) listenLoop() {
) )
for { for {
fd, err = srv.listener.Accept() fd, err = srv.listener.Accept()
if netutil.IsTemporaryError(err) { if tempErr, ok := err.(tempError); ok && tempErr.Temporary() {
srv.log.Debug("Temporary read error", "err", err) srv.log.Debug("Temporary read error", "err", err)
continue continue
} else if err != nil { } else if err != nil {
@ -883,17 +802,21 @@ func (srv *Server) listenLoop() {
// SetupConn runs the handshakes and attempts to add the connection // SetupConn runs the handshakes and attempts to add the connection
// as a peer. It returns when the connection has been added as a peer // as a peer. It returns when the connection has been added as a peer
// or the handshakes have failed. // or the handshakes have failed.
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error { func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) error {
self := srv.Self()
if self == nil {
return errors.New("shutdown")
}
c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)} c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)}
err := srv.setupConn(c, flags, dialDest) err := srv.setupConn(c, flags, dialDest)
if err != nil { if err != nil {
c.close(err) c.close(err)
srv.log.Trace("Setting up connection failed", "addr", fd.RemoteAddr(), "err", err) srv.log.Trace("Setting up connection failed", "id", c.id, "err", err)
} }
return err return err
} }
func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) error { func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {
// Prevent leftover pending conns from entering the handshake. // Prevent leftover pending conns from entering the handshake.
srv.lock.Lock() srv.lock.Lock()
running := srv.running running := srv.running
@ -901,30 +824,18 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
if !running { if !running {
return errServerStopped return errServerStopped
} }
// If dialing, figure out the remote public key.
var dialPubkey *ecdsa.PublicKey
if dialDest != nil {
dialPubkey = new(ecdsa.PublicKey)
if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
return fmt.Errorf("dial destination doesn't have a secp256k1 public key")
}
}
// Run the encryption handshake. // Run the encryption handshake.
remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey) var err error
if err != nil { if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != nil {
srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
return err return err
} }
if dialDest != nil { clog := srv.log.New("id", c.id, "addr", c.fd.RemoteAddr(), "conn", c.flags)
// For dialed connections, check that the remote public key matches. // For dialed connections, check that the remote public key matches.
if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 { if dialDest != nil && c.id != dialDest.ID {
return DiscUnexpectedIdentity clog.Trace("Dialed identity mismatch", "want", c, dialDest.ID)
} return DiscUnexpectedIdentity
c.node = dialDest
} else {
c.node = nodeFromConn(remotePubkey, c.fd)
} }
clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags)
err = srv.checkpoint(c, srv.posthandshake) err = srv.checkpoint(c, srv.posthandshake)
if err != nil { if err != nil {
clog.Trace("Rejected peer before protocol handshake", "err", err) clog.Trace("Rejected peer before protocol handshake", "err", err)
@ -936,14 +847,13 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
clog.Trace("Failed proto handshake", "err", err) clog.Trace("Failed proto handshake", "err", err)
return err return err
} }
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { if phs.ID != c.id {
clog.Trace("Wrong devp2p handshake identity", "phsid", fmt.Sprintf("%x", phs.ID)) clog.Trace("Wrong devp2p handshake identity", "err", phs.ID)
return DiscUnexpectedIdentity return DiscUnexpectedIdentity
} }
c.caps, c.name = phs.Caps, phs.Name c.caps, c.name = phs.Caps, phs.Name
err = srv.checkpoint(c, srv.addpeer) err = srv.checkpoint(c, srv.addpeer)
if err != nil { if err != nil {
clog.Debug("Rejected peer", "err", err, "c.node.ID()", c.node.ID())
clog.Trace("Rejected peer", "err", err) clog.Trace("Rejected peer", "err", err)
return err return err
} }
@ -953,16 +863,6 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
return nil return nil
} }
func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node {
var ip net.IP
var port int
if tcp, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
ip = tcp.IP
port = tcp.Port
}
return enode.NewV4(pubkey, ip, port, port)
}
func truncateName(s string) string { func truncateName(s string) string {
if len(s) > 20 { if len(s) > 20 {
return s[:20] + "..." return s[:20] + "..."
@ -1020,7 +920,6 @@ type NodeInfo struct {
ID string `json:"id"` // Unique node identifier (also the encryption key) ID string `json:"id"` // Unique node identifier (also the encryption key)
Name string `json:"name"` // Name of the node, including client type, version, OS, custom data Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
Enode string `json:"enode"` // Enode URL for adding this peer from remote peers Enode string `json:"enode"` // Enode URL for adding this peer from remote peers
ENR string `json:"enr"` // Ethereum Node Record
IP string `json:"ip"` // IP address of the node IP string `json:"ip"` // IP address of the node
Ports struct { Ports struct {
Discovery int `json:"discovery"` // UDP listening port for discovery protocol Discovery int `json:"discovery"` // UDP listening port for discovery protocol
@ -1032,21 +931,19 @@ type NodeInfo struct {
// NodeInfo gathers and returns a collection of metadata known about the host. // NodeInfo gathers and returns a collection of metadata known about the host.
func (srv *Server) NodeInfo() *NodeInfo { func (srv *Server) NodeInfo() *NodeInfo {
// Gather and assemble the generic node infos
node := srv.Self() node := srv.Self()
// Gather and assemble the generic node infos
info := &NodeInfo{ info := &NodeInfo{
Name: srv.Name, Name: srv.Name,
Enode: node.String(), Enode: node.String(),
ID: node.ID().String(), ID: node.ID.String(),
IP: node.IP().String(), IP: node.IP.String(),
ListenAddr: srv.ListenAddr, ListenAddr: srv.ListenAddr,
Protocols: make(map[string]interface{}), Protocols: make(map[string]interface{}),
} }
info.Ports.Discovery = node.UDP() info.Ports.Discovery = int(node.UDP)
info.Ports.Listener = node.TCP() info.Ports.Listener = int(node.TCP)
if enc, err := rlp.EncodeToBytes(node.Record()); err == nil {
info.ENR = "0x" + hex.EncodeToString(enc)
}
// Gather all the running protocol infos (only once per protocol type) // Gather all the running protocol infos (only once per protocol type)
for _, proto := range srv.Protocols { for _, proto := range srv.Protocols {

View file

@ -19,7 +19,6 @@ package p2p
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"net" "net"
"reflect" "reflect"
@ -29,22 +28,21 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/enr"
) )
// func init() { func init() {
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) // log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
// } }
type testTransport struct { type testTransport struct {
rpub *ecdsa.PublicKey id discover.NodeID
*rlpx *rlpx
closeErr error closeErr error
} }
func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport { func newTestTransport(id discover.NodeID, fd net.Conn) transport {
wrapped := newRLPX(fd).(*rlpx) wrapped := newRLPX(fd).(*rlpx)
wrapped.rw = newRLPXFrameRW(fd, secrets{ wrapped.rw = newRLPXFrameRW(fd, secrets{
MAC: zero16, MAC: zero16,
@ -52,16 +50,15 @@ func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport {
IngressMAC: sha3.NewKeccak256(), IngressMAC: sha3.NewKeccak256(),
EgressMAC: sha3.NewKeccak256(), EgressMAC: sha3.NewKeccak256(),
}) })
return &testTransport{rpub: rpub, rlpx: wrapped} return &testTransport{id: id, rlpx: wrapped}
} }
func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
return c.rpub, nil return c.id, nil
} }
func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) { func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
pubkey := crypto.FromECDSAPub(c.rpub)[1:] return &protoHandshake{ID: c.id, Name: "test"}, nil
return &protoHandshake{ID: pubkey, Name: "test"}, nil
} }
func (c *testTransport) close(err error) { func (c *testTransport) close(err error) {
@ -69,7 +66,7 @@ func (c *testTransport) close(err error) {
c.closeErr = err c.closeErr = err
} }
func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server { func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
config := Config{ config := Config{
Name: "test", Name: "test",
MaxPeers: 10, MaxPeers: 10,
@ -79,7 +76,7 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *
server := &Server{ server := &Server{
Config: config, Config: config,
newPeerHook: pf, newPeerHook: pf,
newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) }, newTransport: func(fd net.Conn) transport { return newTestTransport(id, fd) },
} }
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
t.Fatalf("Could not start server: %v", err) t.Fatalf("Could not start server: %v", err)
@ -90,11 +87,14 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *
func TestServerListen(t *testing.T) { func TestServerListen(t *testing.T) {
// start the test server // start the test server
connected := make(chan *Peer) connected := make(chan *Peer)
remid := &newkey().PublicKey remid := randomID()
srv := startTestServer(t, remid, func(p *Peer) { srv := startTestServer(t, remid, func(p *Peer) {
if p.ID() != enode.PubkeyToIDV4(remid) { if p.ID() != remid {
t.Error("peer func called with wrong node id") t.Error("peer func called with wrong node id")
} }
if p == nil {
t.Error("peer func called with nil conn")
}
connected <- p connected <- p
}) })
defer close(connected) defer close(connected)
@ -141,22 +141,21 @@ func TestServerDial(t *testing.T) {
// start the server // start the server
connected := make(chan *Peer) connected := make(chan *Peer)
remid := &newkey().PublicKey remid := randomID()
srv := startTestServer(t, remid, func(p *Peer) { connected <- p }) srv := startTestServer(t, remid, func(p *Peer) { connected <- p })
defer close(connected) defer close(connected)
defer srv.Stop() defer srv.Stop()
// tell the server to connect // tell the server to connect
tcpAddr := listener.Addr().(*net.TCPAddr) tcpAddr := listener.Addr().(*net.TCPAddr)
node := enode.NewV4(remid, tcpAddr.IP, tcpAddr.Port, 0) srv.AddPeer(&discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)})
srv.AddPeer(node)
select { select {
case conn := <-accepted: case conn := <-accepted:
defer conn.Close() defer conn.Close()
select { select {
case peer := <-connected: case peer := <-connected:
if peer.ID() != enode.PubkeyToIDV4(remid) { if peer.ID() != remid {
t.Errorf("peer has wrong id") t.Errorf("peer has wrong id")
} }
if peer.Name() != "test" { if peer.Name() != "test" {
@ -170,35 +169,26 @@ func TestServerDial(t *testing.T) {
if !reflect.DeepEqual(peers, []*Peer{peer}) { if !reflect.DeepEqual(peers, []*Peer{peer}) {
t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer}) t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer})
} }
// Test AddTrustedPeer/RemoveTrustedPeer and changing Trusted flags
// Particularly for race conditions on changing the flag state.
if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
t.Errorf("peer is trusted prematurely: %v", peer)
}
done := make(chan bool)
go func() {
srv.AddTrustedPeer(node)
if peer := srv.Peers()[0]; !peer.Info().Network.Trusted {
t.Errorf("peer is not trusted after AddTrustedPeer: %v", peer)
}
srv.RemoveTrustedPeer(node)
if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
t.Errorf("peer is trusted after RemoveTrustedPeer: %v", peer)
}
done <- true
}()
// Trigger potential race conditions
peer = srv.Peers()[0]
_ = peer.Inbound()
_ = peer.Info()
<-done
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
t.Error("server did not launch peer within one second") t.Error("server did not launch peer within one second")
} }
select {
case peer := <-connected:
if peer.ID() != remid {
t.Errorf("peer has wrong id")
}
if peer.Name() != "test" {
t.Errorf("peer has wrong name")
}
if peer.RemoteAddr().String() != conn.LocalAddr().String() {
t.Errorf("peer started with wrong conn: got %v, want %v",
peer.RemoteAddr(), conn.LocalAddr())
}
case <-time.After(1 * time.Second):
t.Error("server did not launch peer within one second")
}
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
fmt.Println("step 1: didn't work")
t.Error("server did not connect within one second") t.Error("server did not connect within one second")
} }
} }
@ -211,7 +201,7 @@ func TestServerTaskScheduling(t *testing.T) {
quit, returned = make(chan struct{}), make(chan struct{}) quit, returned = make(chan struct{}), make(chan struct{})
tc = 0 tc = 0
tg = taskgen{ tg = taskgen{
newFunc: func(running int, peers map[enode.ID]*Peer) []task { newFunc: func(running int, peers map[discover.NodeID]*Peer) []task {
tc++ tc++
return []task{&testTask{index: tc - 1}} return []task{&testTask{index: tc - 1}}
}, },
@ -226,15 +216,12 @@ func TestServerTaskScheduling(t *testing.T) {
// The Server in this test isn't actually running // The Server in this test isn't actually running
// because we're only interested in what run does. // because we're only interested in what run does.
db, _ := enode.OpenDB("")
srv := &Server{ srv := &Server{
Config: Config{MaxPeers: 10}, Config: Config{MaxPeers: 10},
localnode: enode.NewLocalNode(db, newkey()), quit: make(chan struct{}),
nodedb: db, ntab: fakeTable{},
quit: make(chan struct{}), running: true,
ntab: fakeTable{}, log: log.New(),
running: true,
log: log.New(),
} }
srv.loopWG.Add(1) srv.loopWG.Add(1)
go func() { go func() {
@ -275,14 +262,11 @@ func TestServerManyTasks(t *testing.T) {
} }
var ( var (
db, _ = enode.OpenDB("") srv = &Server{
srv = &Server{ quit: make(chan struct{}),
quit: make(chan struct{}), ntab: fakeTable{},
localnode: enode.NewLocalNode(db, newkey()), running: true,
nodedb: db, log: log.New(),
ntab: fakeTable{},
running: true,
log: log.New(),
} }
done = make(chan *testTask) done = make(chan *testTask)
start, end = 0, 0 start, end = 0, 0
@ -290,7 +274,7 @@ func TestServerManyTasks(t *testing.T) {
defer srv.Stop() defer srv.Stop()
srv.loopWG.Add(1) srv.loopWG.Add(1)
go srv.run(taskgen{ go srv.run(taskgen{
newFunc: func(running int, peers map[enode.ID]*Peer) []task { newFunc: func(running int, peers map[discover.NodeID]*Peer) []task {
start, end = end, end+maxActiveDialTasks+10 start, end = end, end+maxActiveDialTasks+10
if end > len(alltasks) { if end > len(alltasks) {
end = len(alltasks) end = len(alltasks)
@ -325,19 +309,19 @@ func TestServerManyTasks(t *testing.T) {
} }
type taskgen struct { type taskgen struct {
newFunc func(running int, peers map[enode.ID]*Peer) []task newFunc func(running int, peers map[discover.NodeID]*Peer) []task
doneFunc func(task) doneFunc func(task)
} }
func (tg taskgen) newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task { func (tg taskgen) newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task {
return tg.newFunc(running, peers) return tg.newFunc(running, peers)
} }
func (tg taskgen) taskDone(t task, now time.Time) { func (tg taskgen) taskDone(t task, now time.Time) {
tg.doneFunc(t) tg.doneFunc(t)
} }
func (tg taskgen) addStatic(*enode.Node) { func (tg taskgen) addStatic(*discover.Node) {
} }
func (tg taskgen) removeStatic(*enode.Node) { func (tg taskgen) removeStatic(*discover.Node) {
} }
type testTask struct { type testTask struct {
@ -353,14 +337,13 @@ func (t *testTask) Do(srv *Server) {
// just after the encryption handshake when the server is // just after the encryption handshake when the server is
// at capacity. Trusted connections should still be accepted. // at capacity. Trusted connections should still be accepted.
func TestServerAtCap(t *testing.T) { func TestServerAtCap(t *testing.T) {
trustedNode := newkey() trustedID := randomID()
trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey)
srv := &Server{ srv := &Server{
Config: Config{ Config: Config{
PrivateKey: newkey(), PrivateKey: newkey(),
MaxPeers: 10, MaxPeers: 10,
NoDial: true, NoDial: true,
TrustedNodes: []*enode.Node{newNode(trustedID, nil)}, TrustedNodes: []*discover.Node{{ID: trustedID}},
}, },
} }
if err := srv.Start(); err != nil { if err := srv.Start(); err != nil {
@ -368,11 +351,10 @@ func TestServerAtCap(t *testing.T) {
} }
defer srv.Stop() defer srv.Stop()
newconn := func(id enode.ID) *conn { newconn := func(id discover.NodeID) *conn {
fd, _ := net.Pipe() fd, _ := net.Pipe()
tx := newTestTransport(&trustedNode.PublicKey, fd) tx := newTestTransport(id, fd)
node := enode.SignNull(new(enr.Record), id) return &conn{fd: fd, transport: tx, flags: inboundConn, id: id, cont: make(chan error)}
return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)}
} }
// Inject a few connections to fill up the peer set. // Inject a few connections to fill up the peer set.
@ -383,8 +365,7 @@ func TestServerAtCap(t *testing.T) {
} }
} }
// Try inserting a non-trusted connection. // Try inserting a non-trusted connection.
anotherID := randomID() c := newconn(randomID())
c := newconn(anotherID)
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers { if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
t.Error("wrong error for insert:", err) t.Error("wrong error for insert:", err)
} }
@ -397,144 +378,62 @@ func TestServerAtCap(t *testing.T) {
t.Error("Server did not set trusted flag") t.Error("Server did not set trusted flag")
} }
// Remove from trusted set and try again
srv.RemoveTrustedPeer(newNode(trustedID, nil))
c = newconn(trustedID)
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
t.Error("wrong error for insert:", err)
}
// Add anotherID to trusted set and try again
srv.AddTrustedPeer(newNode(anotherID, nil))
c = newconn(anotherID)
if err := srv.checkpoint(c, srv.posthandshake); err != nil {
t.Error("unexpected error for trusted conn @posthandshake:", err)
}
if !c.is(trustedConn) {
t.Error("Server did not set trusted flag")
}
}
func TestServerPeerLimits(t *testing.T) {
srvkey := newkey()
clientkey := newkey()
clientnode := enode.NewV4(&clientkey.PublicKey, nil, 0, 0)
var tp = &setupTransport{
pubkey: &clientkey.PublicKey,
phs: protoHandshake{
ID: crypto.FromECDSAPub(&clientkey.PublicKey)[1:],
// Force "DiscUselessPeer" due to unmatching caps
// Caps: []Cap{discard.cap()},
},
}
srv := &Server{
Config: Config{
PrivateKey: srvkey,
MaxPeers: 0,
NoDial: true,
Protocols: []Protocol{discard},
},
newTransport: func(fd net.Conn) transport { return tp },
log: log.New(),
}
if err := srv.Start(); err != nil {
t.Fatalf("couldn't start server: %v", err)
}
defer srv.Stop()
// Check that server is full (MaxPeers=0)
flags := dynDialedConn
dialDest := clientnode
conn, _ := net.Pipe()
srv.SetupConn(conn, flags, dialDest)
if tp.closeErr != DiscTooManyPeers {
t.Errorf("unexpected close error: %q", tp.closeErr)
}
conn.Close()
srv.AddTrustedPeer(clientnode)
// Check that server allows a trusted peer despite being full.
conn, _ = net.Pipe()
srv.SetupConn(conn, flags, dialDest)
if tp.closeErr == DiscTooManyPeers {
t.Errorf("failed to bypass MaxPeers with trusted node: %q", tp.closeErr)
}
if tp.closeErr != DiscUselessPeer {
t.Errorf("unexpected close error: %q", tp.closeErr)
}
conn.Close()
srv.RemoveTrustedPeer(clientnode)
// Check that server is full again.
conn, _ = net.Pipe()
srv.SetupConn(conn, flags, dialDest)
if tp.closeErr != DiscTooManyPeers {
t.Errorf("unexpected close error: %q", tp.closeErr)
}
conn.Close()
} }
func TestServerSetupConn(t *testing.T) { func TestServerSetupConn(t *testing.T) {
var ( id := randomID()
clientkey, srvkey = newkey(), newkey() srvkey := newkey()
clientpub = &clientkey.PublicKey srvid := discover.PubkeyID(&srvkey.PublicKey)
srvpub = &srvkey.PublicKey
)
tests := []struct { tests := []struct {
dontstart bool dontstart bool
tt *setupTransport tt *setupTransport
flags connFlag flags connFlag
dialDest *enode.Node dialDest *discover.Node
wantCloseErr error wantCloseErr error
wantCalls string wantCalls string
}{ }{
{ {
dontstart: true, dontstart: true,
tt: &setupTransport{pubkey: clientpub}, tt: &setupTransport{id: id},
wantCalls: "close,", wantCalls: "close,",
wantCloseErr: errServerStopped, wantCloseErr: errServerStopped,
}, },
{ {
tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errors.New("read error")}, tt: &setupTransport{id: id, encHandshakeErr: errors.New("read error")},
flags: inboundConn, flags: inboundConn,
wantCalls: "doEncHandshake,close,", wantCalls: "doEncHandshake,close,",
wantCloseErr: errors.New("read error"), wantCloseErr: errors.New("read error"),
}, },
{ {
tt: &setupTransport{pubkey: clientpub}, tt: &setupTransport{id: id},
dialDest: enode.NewV4(&newkey().PublicKey, nil, 0, 0), dialDest: &discover.Node{ID: randomID()},
flags: dynDialedConn, flags: dynDialedConn,
wantCalls: "doEncHandshake,close,", wantCalls: "doEncHandshake,close,",
wantCloseErr: DiscUnexpectedIdentity, wantCloseErr: DiscUnexpectedIdentity,
}, },
{ {
tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}}, tt: &setupTransport{id: id, phs: &protoHandshake{ID: randomID()}},
dialDest: enode.NewV4(clientpub, nil, 0, 0), dialDest: &discover.Node{ID: id},
flags: dynDialedConn, flags: dynDialedConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: DiscUnexpectedIdentity, wantCloseErr: DiscUnexpectedIdentity,
}, },
{ {
tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errors.New("foo")}, tt: &setupTransport{id: id, protoHandshakeErr: errors.New("foo")},
dialDest: enode.NewV4(clientpub, nil, 0, 0), dialDest: &discover.Node{ID: id},
flags: dynDialedConn, flags: dynDialedConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: errors.New("foo"), wantCloseErr: errors.New("foo"),
}, },
{ {
tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}}, tt: &setupTransport{id: srvid, phs: &protoHandshake{ID: srvid}},
flags: inboundConn, flags: inboundConn,
wantCalls: "doEncHandshake,close,", wantCalls: "doEncHandshake,close,",
wantCloseErr: DiscSelf, wantCloseErr: DiscSelf,
}, },
{ {
tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: crypto.FromECDSAPub(clientpub)[1:]}}, tt: &setupTransport{id: id, phs: &protoHandshake{ID: id}},
flags: inboundConn, flags: inboundConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: DiscUselessPeer, wantCloseErr: DiscUselessPeer,
@ -569,26 +468,26 @@ func TestServerSetupConn(t *testing.T) {
} }
type setupTransport struct { type setupTransport struct {
pubkey *ecdsa.PublicKey id discover.NodeID
encHandshakeErr error encHandshakeErr error
phs protoHandshake
phs *protoHandshake
protoHandshakeErr error protoHandshakeErr error
calls string calls string
closeErr error closeErr error
} }
func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
c.calls += "doEncHandshake," c.calls += "doEncHandshake,"
return c.pubkey, c.encHandshakeErr return c.id, c.encHandshakeErr
} }
func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) { func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
c.calls += "doProtoHandshake," c.calls += "doProtoHandshake,"
if c.protoHandshakeErr != nil { if c.protoHandshakeErr != nil {
return nil, c.protoHandshakeErr return nil, c.protoHandshakeErr
} }
return &c.phs, nil return c.phs, nil
} }
func (c *setupTransport) close(err error) { func (c *setupTransport) close(err error) {
c.calls += "close," c.calls += "close,"
@ -611,7 +510,7 @@ func newkey() *ecdsa.PrivateKey {
return key return key
} }
func randomID() (id enode.ID) { func randomID() (id discover.NodeID) {
for i := range id { for i := range id {
id[i] = byte(rand.Intn(255)) id[i] = byte(rand.Intn(255))
} }

View file

@ -28,14 +28,10 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
) )
var (
ErrLinuxOnly = errors.New("DockerAdapter can only be used on Linux as it uses the current binary (which must be a Linux binary)")
)
// DockerAdapter is a NodeAdapter which runs simulation nodes inside Docker // DockerAdapter is a NodeAdapter which runs simulation nodes inside Docker
// containers. // containers.
// //
@ -64,7 +60,7 @@ func NewDockerAdapter() (*DockerAdapter, error) {
return &DockerAdapter{ return &DockerAdapter{
ExecAdapter{ ExecAdapter{
nodes: make(map[enode.ID]*ExecNode), nodes: make(map[discover.NodeID]*ExecNode),
}, },
}, nil }, nil
} }

View file

@ -38,7 +38,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -55,7 +55,7 @@ type ExecAdapter struct {
// simulation node are created. // simulation node are created.
BaseDir string BaseDir string
nodes map[enode.ID]*ExecNode nodes map[discover.NodeID]*ExecNode
} }
// NewExecAdapter returns an ExecAdapter which stores node data in // NewExecAdapter returns an ExecAdapter which stores node data in
@ -63,7 +63,7 @@ type ExecAdapter struct {
func NewExecAdapter(baseDir string) *ExecAdapter { func NewExecAdapter(baseDir string) *ExecAdapter {
return &ExecAdapter{ return &ExecAdapter{
BaseDir: baseDir, BaseDir: baseDir,
nodes: make(map[enode.ID]*ExecNode), nodes: make(map[discover.NodeID]*ExecNode),
} }
} }
@ -123,7 +123,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
// ExecNode starts a simulation node by exec'ing the current binary and // ExecNode starts a simulation node by exec'ing the current binary and
// running the configured services // running the configured services
type ExecNode struct { type ExecNode struct {
ID enode.ID ID discover.NodeID
Dir string Dir string
Config *execNodeConfig Config *execNodeConfig
Cmd *exec.Cmd Cmd *exec.Cmd
@ -504,7 +504,7 @@ type wsRPCDialer struct {
// DialRPC implements the RPCDialer interface by creating a WebSocket RPC // DialRPC implements the RPCDialer interface by creating a WebSocket RPC
// client of the given node // client of the given node
func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) { func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) {
addr, ok := w.addrs[id.String()] addr, ok := w.addrs[id.String()]
if !ok { if !ok {
return nil, fmt.Errorf("unknown node: %s", id) return nil, fmt.Errorf("unknown node: %s", id)

View file

@ -27,8 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/pipes"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -36,9 +35,8 @@ import (
// SimAdapter is a NodeAdapter which creates in-memory simulation nodes and // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
// connects them using in-memory net.Pipe connections // connects them using in-memory net.Pipe connections
type SimAdapter struct { type SimAdapter struct {
pipe func() (net.Conn, net.Conn, error)
mtx sync.RWMutex mtx sync.RWMutex
nodes map[enode.ID]*SimNode nodes map[discover.NodeID]*SimNode
services map[string]ServiceFunc services map[string]ServiceFunc
} }
@ -47,16 +45,7 @@ type SimAdapter struct {
// particular node are passed to the NewNode function in the NodeConfig) // particular node are passed to the NewNode function in the NodeConfig)
func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter { func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter {
return &SimAdapter{ return &SimAdapter{
pipe: pipes.NetPipe, nodes: make(map[discover.NodeID]*SimNode),
nodes: make(map[enode.ID]*SimNode),
services: services,
}
}
func NewTCPAdapter(services map[string]ServiceFunc) *SimAdapter {
return &SimAdapter{
pipe: pipes.TCPPipe,
nodes: make(map[enode.ID]*SimNode),
services: services, services: services,
} }
} }
@ -103,35 +92,40 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
} }
simNode := &SimNode{ simNode := &SimNode{
ID: id, ID: id,
config: config, config: config,
node: n, node: n,
adapter: s, adapter: s,
running: make(map[string]node.Service), running: make(map[string]node.Service),
connected: make(map[discover.NodeID]bool),
} }
s.nodes[id] = simNode s.nodes[id] = simNode
return simNode, nil return simNode, nil
} }
// Dial implements the p2p.NodeDialer interface by connecting to the node using // Dial implements the p2p.NodeDialer interface by connecting to the node using
// an in-memory net.Pipe // an in-memory net.Pipe connection
func (s *SimAdapter) Dial(dest *enode.Node) (conn net.Conn, err error) { func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) {
node, ok := s.GetNode(dest.ID()) node, ok := s.GetNode(dest.ID)
if !ok { if !ok {
return nil, fmt.Errorf("unknown node: %s", dest.ID()) return nil, fmt.Errorf("unknown node: %s", dest.ID)
}
if node.connected[dest.ID] {
return nil, fmt.Errorf("dialed node: %s", dest.ID)
} }
srv := node.Server() srv := node.Server()
if srv == nil { if srv == nil {
return nil, fmt.Errorf("node not running: %s", dest.ID()) return nil, fmt.Errorf("node not running: %s", dest.ID)
} }
pipe1, pipe2 := net.Pipe() pipe1, pipe2 := net.Pipe()
go srv.SetupConn(pipe1, 0, nil) go srv.SetupConn(pipe1, 0, nil)
node.connected[dest.ID] = true
return pipe2, nil return pipe2, nil
} }
// DialRPC implements the RPCDialer interface by creating an in-memory RPC // DialRPC implements the RPCDialer interface by creating an in-memory RPC
// client of the given node // client of the given node
func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) { func (s *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) {
node, ok := s.GetNode(id) node, ok := s.GetNode(id)
if !ok { if !ok {
return nil, fmt.Errorf("unknown node: %s", id) return nil, fmt.Errorf("unknown node: %s", id)
@ -144,7 +138,7 @@ func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) {
} }
// GetNode returns the node with the given ID if it exists // GetNode returns the node with the given ID if it exists
func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) { func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) {
s.mtx.RLock() s.mtx.RLock()
defer s.mtx.RUnlock() defer s.mtx.RUnlock()
node, ok := s.nodes[id] node, ok := s.nodes[id]
@ -156,13 +150,14 @@ func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) {
// protocols directly over that pipe // protocols directly over that pipe
type SimNode struct { type SimNode struct {
lock sync.RWMutex lock sync.RWMutex
ID enode.ID ID discover.NodeID
config *NodeConfig config *NodeConfig
adapter *SimAdapter adapter *SimAdapter
node *node.Node node *node.Node
running map[string]node.Service running map[string]node.Service
client *rpc.Client client *rpc.Client
registerOnce sync.Once registerOnce sync.Once
connected map[discover.NodeID]bool
} }
// Addr returns the node's discovery address // Addr returns the node's discovery address
@ -170,9 +165,9 @@ func (self *SimNode) Addr() []byte {
return []byte(self.Node().String()) return []byte(self.Node().String())
} }
// Node returns a node descriptor representing the SimNode // Node returns a discover.Node representing the SimNode
func (sn *SimNode) Node() *enode.Node { func (self *SimNode) Node() *discover.Node {
return sn.config.Node() return discover.NewNode(self.ID, net.IP{127, 0, 0, 1}, 30303, 30303)
} }
// Client returns an rpc.Client which can be used to communicate with the // Client returns an rpc.Client which can be used to communicate with the

View file

@ -21,14 +21,12 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"os" "os"
"strconv"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -40,6 +38,7 @@ import (
// * SimNode - An in-memory node // * SimNode - An in-memory node
// * ExecNode - A child process node // * ExecNode - A child process node
// * DockerNode - A Docker container node // * DockerNode - A Docker container node
//
type Node interface { type Node interface {
// Addr returns the node's address (e.g. an Enode URL) // Addr returns the node's address (e.g. an Enode URL)
Addr() []byte Addr() []byte
@ -78,7 +77,7 @@ type NodeAdapter interface {
type NodeConfig struct { type NodeConfig struct {
// ID is the node's ID which is used to identify the node in the // ID is the node's ID which is used to identify the node in the
// simulation network // simulation network
ID enode.ID ID discover.NodeID
// PrivateKey is the node's private key which is used by the devp2p // PrivateKey is the node's private key which is used by the devp2p
// stack to encrypt communications // stack to encrypt communications
@ -97,9 +96,7 @@ type NodeConfig struct {
Services []string Services []string
// function to sanction or prevent suggesting a peer // function to sanction or prevent suggesting a peer
Reachable func(id enode.ID) bool Reachable func(id discover.NodeID) bool
Port uint16
} }
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding // nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
@ -134,9 +131,11 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
} }
if confJSON.ID != "" { if confJSON.ID != "" {
if err := n.ID.UnmarshalText([]byte(confJSON.ID)); err != nil { nodeID, err := discover.HexID(confJSON.ID)
if err != nil {
return err return err
} }
n.ID = nodeID
} }
if confJSON.PrivateKey != "" { if confJSON.PrivateKey != "" {
@ -157,51 +156,22 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// Node returns the node descriptor represented by the config.
func (n *NodeConfig) Node() *enode.Node {
return enode.NewV4(&n.PrivateKey.PublicKey, net.IP{127, 0, 0, 1}, int(n.Port), int(n.Port))
}
// RandomNodeConfig returns node configuration with a randomly generated ID and // RandomNodeConfig returns node configuration with a randomly generated ID and
// PrivateKey // PrivateKey
func RandomNodeConfig() *NodeConfig { func RandomNodeConfig() *NodeConfig {
prvkey, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {
panic("unable to generate key") panic("unable to generate key")
} }
var id discover.NodeID
port, err := assignTCPPort() pubkey := crypto.FromECDSAPub(&key.PublicKey)
if err != nil { copy(id[:], pubkey[1:])
panic("unable to assign tcp port")
}
enodId := enode.PubkeyToIDV4(&prvkey.PublicKey)
return &NodeConfig{ return &NodeConfig{
PrivateKey: prvkey, ID: id,
ID: enodId, PrivateKey: key,
Name: fmt.Sprintf("node_%s", enodId.String()),
Port: port,
EnableMsgEvents: true,
} }
} }
func assignTCPPort() (uint16, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, err
}
l.Close()
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return 0, err
}
p, err := strconv.ParseInt(port, 10, 32)
if err != nil {
return 0, err
}
return uint16(p), nil
}
// ServiceContext is a collection of options and methods which can be utilised // ServiceContext is a collection of options and methods which can be utilised
// when starting services // when starting services
type ServiceContext struct { type ServiceContext struct {
@ -216,7 +186,7 @@ type ServiceContext struct {
// other nodes in the network (for example a simulated Swarm node which needs // other nodes in the network (for example a simulated Swarm node which needs
// to connect to a Geth node to resolve ENS names) // to connect to a Geth node to resolve ENS names)
type RPCDialer interface { type RPCDialer interface {
DialRPC(id enode.ID) (*rpc.Client, error) DialRPC(id discover.NodeID) (*rpc.Client, error)
} }
// Services is a collection of services which can be run in a simulation // Services is a collection of services which can be run in a simulation

View file

@ -28,7 +28,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations" "github.com/XinFinOrg/XDPoSChain/p2p/simulations"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
@ -96,12 +96,12 @@ func main() {
// sends a ping to all its connected peers every 10s and receives a pong in // sends a ping to all its connected peers every 10s and receives a pong in
// return // return
type pingPongService struct { type pingPongService struct {
id enode.ID id discover.NodeID
log log.Logger log log.Logger
received int64 received int64
} }
func newPingPongService(id enode.ID) *pingPongService { func newPingPongService(id discover.NodeID) *pingPongService {
return &pingPongService{ return &pingPongService{
id: id, id: id,
log: log.New("node.id", id), log: log.New("node.id", id),

View file

@ -30,7 +30,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -711,9 +711,8 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
ctx := context.Background() ctx := context.Background()
if id := params.ByName("nodeid"); id != "" { if id := params.ByName("nodeid"); id != "" {
var nodeID enode.ID
var node *Node var node *Node
if nodeID.UnmarshalText([]byte(id)) == nil { if nodeID, err := discover.HexID(id); err == nil {
node = s.network.GetNode(nodeID) node = s.network.GetNode(nodeID)
} else { } else {
node = s.network.GetNodeByName(id) node = s.network.GetNodeByName(id)
@ -726,9 +725,8 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
} }
if id := params.ByName("peerid"); id != "" { if id := params.ByName("peerid"); id != "" {
var peerID enode.ID
var peer *Node var peer *Node
if peerID.UnmarshalText([]byte(id)) == nil { if peerID, err := discover.HexID(id); err == nil {
peer = s.network.GetNode(peerID) peer = s.network.GetNode(peerID)
} else { } else {
peer = s.network.GetNodeByName(id) peer = s.network.GetNodeByName(id)

View file

@ -30,7 +30,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
@ -38,12 +38,12 @@ import (
// testService implements the node.Service interface and provides protocols // testService implements the node.Service interface and provides protocols
// and APIs which are useful for testing nodes in a simulation network // and APIs which are useful for testing nodes in a simulation network
type testService struct { type testService struct {
id enode.ID id discover.NodeID
// peerCount is incremented once a peer handshake has been performed // peerCount is incremented once a peer handshake has been performed
peerCount int64 peerCount int64
peers map[enode.ID]*testPeer peers map[discover.NodeID]*testPeer
peersMtx sync.Mutex peersMtx sync.Mutex
// state stores []byte which is used to test creating and loading // state stores []byte which is used to test creating and loading
@ -54,7 +54,7 @@ type testService struct {
func newTestService(ctx *adapters.ServiceContext) (node.Service, error) { func newTestService(ctx *adapters.ServiceContext) (node.Service, error) {
svc := &testService{ svc := &testService{
id: ctx.Config.ID, id: ctx.Config.ID,
peers: make(map[enode.ID]*testPeer), peers: make(map[discover.NodeID]*testPeer),
} }
svc.state.Store(ctx.Snapshot) svc.state.Store(ctx.Snapshot)
return svc, nil return svc, nil
@ -65,7 +65,7 @@ type testPeer struct {
dumReady chan struct{} dumReady chan struct{}
} }
func (t *testService) peer(id enode.ID) *testPeer { func (t *testService) peer(id discover.NodeID) *testPeer {
t.peersMtx.Lock() t.peersMtx.Lock()
defer t.peersMtx.Unlock() defer t.peersMtx.Unlock()
if peer, ok := t.peers[id]; ok { if peer, ok := t.peers[id]; ok {
@ -350,8 +350,7 @@ func startTestNetwork(t *testing.T, client *Client) []string {
nodeCount := 2 nodeCount := 2
nodeIDs := make([]string, nodeCount) nodeIDs := make([]string, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
config := adapters.RandomNodeConfig() node, err := client.CreateNode(nil)
node, err := client.CreateNode(config)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
@ -412,7 +411,7 @@ func (t *expectEvents) nodeEvent(id string, up bool) *Event {
Type: EventTypeNode, Type: EventTypeNode,
Node: &Node{ Node: &Node{
Config: &adapters.NodeConfig{ Config: &adapters.NodeConfig{
ID: enode.HexID(id), ID: discover.MustHexID(id),
}, },
Up: up, Up: up,
}, },
@ -423,8 +422,8 @@ func (t *expectEvents) connEvent(one, other string, up bool) *Event {
return &Event{ return &Event{
Type: EventTypeConn, Type: EventTypeConn,
Conn: &Conn{ Conn: &Conn{
One: enode.HexID(one), One: discover.MustHexID(one),
Other: enode.HexID(other), Other: discover.MustHexID(other),
Up: up, Up: up,
}, },
} }
@ -530,8 +529,7 @@ func TestHTTPNodeRPC(t *testing.T) {
// start a node in the network // start a node in the network
client := NewClient(s.URL) client := NewClient(s.URL)
config := adapters.RandomNodeConfig() node, err := client.CreateNode(nil)
node, err := client.CreateNode(config)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
@ -593,8 +591,7 @@ func TestHTTPSnapshot(t *testing.T) {
nodeCount := 2 nodeCount := 2
nodes := make([]*p2p.NodeInfo, nodeCount) nodes := make([]*p2p.NodeInfo, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
config := adapters.RandomNodeConfig() node, err := client.CreateNode(nil)
node, err := client.CreateNode(config)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }

View file

@ -25,24 +25,23 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
) )
// a map of mocker names to its function //a map of mocker names to its function
var mockerList = map[string]func(net *Network, quit chan struct{}, nodeCount int){ var mockerList = map[string]func(net *Network, quit chan struct{}, nodeCount int){
"startStop": startStop, "startStop": startStop,
"probabilistic": probabilistic, "probabilistic": probabilistic,
"boot": boot, "boot": boot,
} }
// Lookup a mocker by its name, returns the mockerFn //Lookup a mocker by its name, returns the mockerFn
func LookupMocker(mockerType string) func(net *Network, quit chan struct{}, nodeCount int) { func LookupMocker(mockerType string) func(net *Network, quit chan struct{}, nodeCount int) {
return mockerList[mockerType] return mockerList[mockerType]
} }
// Get a list of mockers (keys of the map) //Get a list of mockers (keys of the map)
// Useful for frontend to build available mocker selection //Useful for frontend to build available mocker selection
func GetMockerList() []string { func GetMockerList() []string {
list := make([]string, 0, len(mockerList)) list := make([]string, 0, len(mockerList))
for k := range mockerList { for k := range mockerList {
@ -51,7 +50,7 @@ func GetMockerList() []string {
return list return list
} }
// The boot mockerFn only connects the node in a ring and doesn't do anything else //The boot mockerFn only connects the node in a ring and doesn't do anything else
func boot(net *Network, quit chan struct{}, nodeCount int) { func boot(net *Network, quit chan struct{}, nodeCount int) {
_, err := connectNodesInRing(net, nodeCount) _, err := connectNodesInRing(net, nodeCount)
if err != nil { if err != nil {
@ -59,7 +58,7 @@ func boot(net *Network, quit chan struct{}, nodeCount int) {
} }
} }
// The startStop mockerFn stops and starts nodes in a defined period (ticker) //The startStop mockerFn stops and starts nodes in a defined period (ticker)
func startStop(net *Network, quit chan struct{}, nodeCount int) { func startStop(net *Network, quit chan struct{}, nodeCount int) {
nodes, err := connectNodesInRing(net, nodeCount) nodes, err := connectNodesInRing(net, nodeCount)
if err != nil { if err != nil {
@ -96,10 +95,10 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) {
} }
} }
// The probabilistic mocker func has a more probabilistic pattern //The probabilistic mocker func has a more probabilistic pattern
// (the implementation could probably be improved): //(the implementation could probably be improved):
// nodes are connected in a ring, then a varying number of random nodes is selected, //nodes are connected in a ring, then a varying number of random nodes is selected,
// mocker then stops and starts them in random intervals, and continues the loop //mocker then stops and starts them in random intervals, and continues the loop
func probabilistic(net *Network, quit chan struct{}, nodeCount int) { func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
nodes, err := connectNodesInRing(net, nodeCount) nodes, err := connectNodesInRing(net, nodeCount)
if err != nil { if err != nil {
@ -148,7 +147,7 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
wg.Done() wg.Done()
continue continue
} }
go func(id enode.ID) { go func(id discover.NodeID) {
time.Sleep(randWait) time.Sleep(randWait)
err := net.Start(id) err := net.Start(id)
if err != nil { if err != nil {
@ -162,12 +161,11 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
} }
// connect nodeCount number of nodes in a ring //connect nodeCount number of nodes in a ring
func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) { func connectNodesInRing(net *Network, nodeCount int) ([]discover.NodeID, error) {
ids := make([]enode.ID, nodeCount) ids := make([]discover.NodeID, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
conf := adapters.RandomNodeConfig() node, err := net.NewNode()
node, err := net.NewNodeWithConfig(conf)
if err != nil { if err != nil {
log.Error("Error creating a node! %s", err) log.Error("Error creating a node! %s", err)
return nil, err return nil, err

View file

@ -27,7 +27,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
func TestMocker(t *testing.T) { func TestMocker(t *testing.T) {
@ -82,7 +82,7 @@ func TestMocker(t *testing.T) {
defer sub.Unsubscribe() defer sub.Unsubscribe()
//wait until all nodes are started and connected //wait until all nodes are started and connected
//store every node up event in a map (value is irrelevant, mimic Set datatype) //store every node up event in a map (value is irrelevant, mimic Set datatype)
nodemap := make(map[enode.ID]bool) nodemap := make(map[discover.NodeID]bool)
wg.Add(1) wg.Add(1)
nodesComplete := false nodesComplete := false
connCount := 0 connCount := 0

View file

@ -27,11 +27,11 @@ import (
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
) )
var DialBanTimeout = 200 * time.Millisecond var dialBanTimeout = 200 * time.Millisecond
// NetworkConfig defines configuration options for starting a Network // NetworkConfig defines configuration options for starting a Network
type NetworkConfig struct { type NetworkConfig struct {
@ -51,7 +51,7 @@ type Network struct {
NetworkConfig NetworkConfig
Nodes []*Node `json:"nodes"` Nodes []*Node `json:"nodes"`
nodeMap map[enode.ID]int nodeMap map[discover.NodeID]int
Conns []*Conn `json:"conns"` Conns []*Conn `json:"conns"`
connMap map[string]int connMap map[string]int
@ -67,48 +67,64 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network
return &Network{ return &Network{
NetworkConfig: *conf, NetworkConfig: *conf,
nodeAdapter: nodeAdapter, nodeAdapter: nodeAdapter,
nodeMap: make(map[enode.ID]int), nodeMap: make(map[discover.NodeID]int),
connMap: make(map[string]int), connMap: make(map[string]int),
quitc: make(chan struct{}), quitc: make(chan struct{}),
} }
} }
// Events returns the output event feed of the Network. // Events returns the output event feed of the Network.
func (net *Network) Events() *event.Feed { func (self *Network) Events() *event.Feed {
return &net.events return &self.events
}
// NewNode adds a new node to the network with a random ID
func (self *Network) NewNode() (*Node, error) {
conf := adapters.RandomNodeConfig()
conf.Services = []string{self.DefaultService}
return self.NewNodeWithConfig(conf)
} }
// NewNodeWithConfig adds a new node to the network with the given config, // NewNodeWithConfig adds a new node to the network with the given config,
// returning an error if a node with the same ID or name already exists // returning an error if a node with the same ID or name already exists
func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) { func (self *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
// create a random ID and PrivateKey if not set
if conf.ID == (discover.NodeID{}) {
c := adapters.RandomNodeConfig()
conf.ID = c.ID
conf.PrivateKey = c.PrivateKey
}
id := conf.ID
if conf.Reachable == nil { if conf.Reachable == nil {
conf.Reachable = func(otherID enode.ID) bool { conf.Reachable = func(otherID discover.NodeID) bool {
_, err := net.InitConn(conf.ID, otherID) _, err := self.InitConn(conf.ID, otherID)
if err != nil && bytes.Compare(conf.ID.Bytes(), otherID.Bytes()) < 0 { return err == nil
return false
}
return true
} }
} }
// check the node doesn't already exist // assign a name to the node if not set
if node := net.getNode(conf.ID); node != nil { if conf.Name == "" {
return nil, fmt.Errorf("node with ID %q already exists", conf.ID) conf.Name = fmt.Sprintf("node%02d", len(self.Nodes)+1)
} }
if node := net.getNodeByName(conf.Name); node != nil {
// check the node doesn't already exist
if node := self.getNode(id); node != nil {
return nil, fmt.Errorf("node with ID %q already exists", id)
}
if node := self.getNodeByName(conf.Name); node != nil {
return nil, fmt.Errorf("node with name %q already exists", conf.Name) return nil, fmt.Errorf("node with name %q already exists", conf.Name)
} }
// if no services are configured, use the default service // if no services are configured, use the default service
if len(conf.Services) == 0 { if len(conf.Services) == 0 {
conf.Services = []string{net.DefaultService} conf.Services = []string{self.DefaultService}
} }
// use the NodeAdapter to create the node // use the NodeAdapter to create the node
adapterNode, err := net.nodeAdapter.NewNode(conf) adapterNode, err := self.nodeAdapter.NewNode(conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,28 +132,28 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error)
Node: adapterNode, Node: adapterNode,
Config: conf, Config: conf,
} }
log.Trace(fmt.Sprintf("node %v created", conf.ID)) log.Trace(fmt.Sprintf("node %v created", id))
net.nodeMap[conf.ID] = len(net.Nodes) self.nodeMap[id] = len(self.Nodes)
net.Nodes = append(net.Nodes, node) self.Nodes = append(self.Nodes, node)
// emit a "control" event // emit a "control" event
net.events.Send(ControlEvent(node)) self.events.Send(ControlEvent(node))
return node, nil return node, nil
} }
// Config returns the network configuration // Config returns the network configuration
func (net *Network) Config() *NetworkConfig { func (self *Network) Config() *NetworkConfig {
return &net.NetworkConfig return &self.NetworkConfig
} }
// StartAll starts all nodes in the network // StartAll starts all nodes in the network
func (net *Network) StartAll() error { func (self *Network) StartAll() error {
for _, node := range net.Nodes { for _, node := range self.Nodes {
if node.Up { if node.Up {
continue continue
} }
if err := net.Start(node.ID()); err != nil { if err := self.Start(node.ID()); err != nil {
return err return err
} }
} }
@ -145,12 +161,12 @@ func (net *Network) StartAll() error {
} }
// StopAll stops all nodes in the network // StopAll stops all nodes in the network
func (net *Network) StopAll() error { func (self *Network) StopAll() error {
for _, node := range net.Nodes { for _, node := range self.Nodes {
if !node.Up { if !node.Up {
continue continue
} }
if err := net.Stop(node.ID()); err != nil { if err := self.Stop(node.ID()); err != nil {
return err return err
} }
} }
@ -158,23 +174,21 @@ func (net *Network) StopAll() error {
} }
// Start starts the node with the given ID // Start starts the node with the given ID
func (net *Network) Start(id enode.ID) error { func (self *Network) Start(id discover.NodeID) error {
return net.startWithSnapshots(id, nil) return self.startWithSnapshots(id, nil)
} }
// startWithSnapshots starts the node with the given ID using the give // startWithSnapshots starts the node with the given ID using the give
// snapshots // snapshots
func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error { func (self *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error {
net.lock.Lock() node := self.GetNode(id)
defer net.lock.Unlock()
node := net.getNode(id)
if node == nil { if node == nil {
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
if node.Up { if node.Up {
return fmt.Errorf("node %v already up", id) return fmt.Errorf("node %v already up", id)
} }
log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, net.nodeAdapter.Name())) log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, self.nodeAdapter.Name()))
if err := node.Start(snapshots); err != nil { if err := node.Start(snapshots); err != nil {
log.Warn(fmt.Sprintf("start up failed: %v", err)) log.Warn(fmt.Sprintf("start up failed: %v", err))
return err return err
@ -182,7 +196,7 @@ func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte)
node.Up = true node.Up = true
log.Info(fmt.Sprintf("started node %v: %v", id, node.Up)) log.Info(fmt.Sprintf("started node %v: %v", id, node.Up))
net.events.Send(NewEvent(node)) self.events.Send(NewEvent(node))
// subscribe to peer events // subscribe to peer events
client, err := node.Client() client, err := node.Client()
@ -194,26 +208,22 @@ func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte)
if err != nil { if err != nil {
return fmt.Errorf("error getting peer events for node %v: %s", id, err) return fmt.Errorf("error getting peer events for node %v: %s", id, err)
} }
go net.watchPeerEvents(id, events, sub) go self.watchPeerEvents(id, events, sub)
return nil return nil
} }
// watchPeerEvents reads peer events from the given channel and emits // watchPeerEvents reads peer events from the given channel and emits
// corresponding network events // corresponding network events
func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub event.Subscription) { func (self *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) {
defer func() { defer func() {
sub.Unsubscribe() sub.Unsubscribe()
// assume the node is now down // assume the node is now down
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() node := self.getNode(id)
node := net.getNode(id)
if node == nil {
log.Error("Can not find node for id", "id", id)
return
}
node.Up = false node.Up = false
net.events.Send(NewEvent(node)) self.lock.Unlock()
self.events.Send(NewEvent(node))
}() }()
for { for {
select { select {
@ -225,16 +235,16 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub
switch event.Type { switch event.Type {
case p2p.PeerEventTypeAdd: case p2p.PeerEventTypeAdd:
net.DidConnect(id, peer) self.DidConnect(id, peer)
case p2p.PeerEventTypeDrop: case p2p.PeerEventTypeDrop:
net.DidDisconnect(id, peer) self.DidDisconnect(id, peer)
case p2p.PeerEventTypeMsgSend: case p2p.PeerEventTypeMsgSend:
net.DidSend(id, peer, event.Protocol, *event.MsgCode) self.DidSend(id, peer, event.Protocol, *event.MsgCode)
case p2p.PeerEventTypeMsgRecv: case p2p.PeerEventTypeMsgRecv:
net.DidReceive(peer, id, event.Protocol, *event.MsgCode) self.DidReceive(peer, id, event.Protocol, *event.MsgCode)
} }
@ -248,10 +258,8 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub
} }
// Stop stops the node with the given ID // Stop stops the node with the given ID
func (net *Network) Stop(id enode.ID) error { func (self *Network) Stop(id discover.NodeID) error {
net.lock.Lock() node := self.GetNode(id)
defer net.lock.Unlock()
node := net.getNode(id)
if node == nil { if node == nil {
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
@ -264,15 +272,15 @@ func (net *Network) Stop(id enode.ID) error {
node.Up = false node.Up = false
log.Info(fmt.Sprintf("stop node %v: %v", id, node.Up)) log.Info(fmt.Sprintf("stop node %v: %v", id, node.Up))
net.events.Send(ControlEvent(node)) self.events.Send(ControlEvent(node))
return nil return nil
} }
// Connect connects two nodes together by calling the "admin_addPeer" RPC // Connect connects two nodes together by calling the "admin_addPeer" RPC
// method on the "one" node so that it connects to the "other" node // method on the "one" node so that it connects to the "other" node
func (net *Network) Connect(oneID, otherID enode.ID) error { func (self *Network) Connect(oneID, otherID discover.NodeID) error {
log.Debug(fmt.Sprintf("connecting %s to %s", oneID, otherID)) log.Debug(fmt.Sprintf("connecting %s to %s", oneID, otherID))
conn, err := net.InitConn(oneID, otherID) conn, err := self.InitConn(oneID, otherID)
if err != nil { if err != nil {
return err return err
} }
@ -280,14 +288,14 @@ func (net *Network) Connect(oneID, otherID enode.ID) error {
if err != nil { if err != nil {
return err return err
} }
net.events.Send(ControlEvent(conn)) self.events.Send(ControlEvent(conn))
return client.Call(nil, "admin_addPeer", string(conn.other.Addr())) return client.Call(nil, "admin_addPeer", string(conn.other.Addr()))
} }
// Disconnect disconnects two nodes by calling the "admin_removePeer" RPC // Disconnect disconnects two nodes by calling the "admin_removePeer" RPC
// method on the "one" node so that it disconnects from the "other" node // method on the "one" node so that it disconnects from the "other" node
func (net *Network) Disconnect(oneID, otherID enode.ID) error { func (self *Network) Disconnect(oneID, otherID discover.NodeID) error {
conn := net.GetConn(oneID, otherID) conn := self.GetConn(oneID, otherID)
if conn == nil { if conn == nil {
return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID) return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID)
} }
@ -298,15 +306,13 @@ func (net *Network) Disconnect(oneID, otherID enode.ID) error {
if err != nil { if err != nil {
return err return err
} }
net.events.Send(ControlEvent(conn)) self.events.Send(ControlEvent(conn))
return client.Call(nil, "admin_removePeer", string(conn.other.Addr())) return client.Call(nil, "admin_removePeer", string(conn.other.Addr()))
} }
// DidConnect tracks the fact that the "one" node connected to the "other" node // DidConnect tracks the fact that the "one" node connected to the "other" node
func (net *Network) DidConnect(one, other enode.ID) error { func (self *Network) DidConnect(one, other discover.NodeID) error {
net.lock.Lock() conn, err := self.GetOrCreateConn(one, other)
defer net.lock.Unlock()
conn, err := net.getOrCreateConn(one, other)
if err != nil { if err != nil {
return fmt.Errorf("connection between %v and %v does not exist", one, other) return fmt.Errorf("connection between %v and %v does not exist", one, other)
} }
@ -314,16 +320,14 @@ func (net *Network) DidConnect(one, other enode.ID) error {
return fmt.Errorf("%v and %v already connected", one, other) return fmt.Errorf("%v and %v already connected", one, other)
} }
conn.Up = true conn.Up = true
net.events.Send(NewEvent(conn)) self.events.Send(NewEvent(conn))
return nil return nil
} }
// DidDisconnect tracks the fact that the "one" node disconnected from the // DidDisconnect tracks the fact that the "one" node disconnected from the
// "other" node // "other" node
func (net *Network) DidDisconnect(one, other enode.ID) error { func (self *Network) DidDisconnect(one, other discover.NodeID) error {
net.lock.Lock() conn := self.GetConn(one, other)
defer net.lock.Unlock()
conn := net.getConn(one, other)
if conn == nil { if conn == nil {
return fmt.Errorf("connection between %v and %v does not exist", one, other) return fmt.Errorf("connection between %v and %v does not exist", one, other)
} }
@ -331,13 +335,13 @@ func (net *Network) DidDisconnect(one, other enode.ID) error {
return fmt.Errorf("%v and %v already disconnected", one, other) return fmt.Errorf("%v and %v already disconnected", one, other)
} }
conn.Up = false conn.Up = false
conn.initiated = time.Now().Add(-DialBanTimeout) conn.initiated = time.Now().Add(-dialBanTimeout)
net.events.Send(NewEvent(conn)) self.events.Send(NewEvent(conn))
return nil return nil
} }
// DidSend tracks the fact that "sender" sent a message to "receiver" // DidSend tracks the fact that "sender" sent a message to "receiver"
func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64) error { func (self *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error {
msg := &Msg{ msg := &Msg{
One: sender, One: sender,
Other: receiver, Other: receiver,
@ -345,12 +349,12 @@ func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64
Code: code, Code: code,
Received: false, Received: false,
} }
net.events.Send(NewEvent(msg)) self.events.Send(NewEvent(msg))
return nil return nil
} }
// DidReceive tracks the fact that "receiver" received a message from "sender" // DidReceive tracks the fact that "receiver" received a message from "sender"
func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uint64) error { func (self *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error {
msg := &Msg{ msg := &Msg{
One: sender, One: sender,
Other: receiver, Other: receiver,
@ -358,45 +362,36 @@ func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uin
Code: code, Code: code,
Received: true, Received: true,
} }
net.events.Send(NewEvent(msg)) self.events.Send(NewEvent(msg))
return nil return nil
} }
// GetNode gets the node with the given ID, returning nil if the node does not // GetNode gets the node with the given ID, returning nil if the node does not
// exist // exist
func (net *Network) GetNode(id enode.ID) *Node { func (self *Network) GetNode(id discover.NodeID) *Node {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
return net.getNode(id) return self.getNode(id)
} }
// GetNode gets the node with the given name, returning nil if the node does // GetNode gets the node with the given name, returning nil if the node does
// not exist // not exist
func (net *Network) GetNodeByName(name string) *Node { func (self *Network) GetNodeByName(name string) *Node {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
return net.getNodeByName(name) return self.getNodeByName(name)
} }
// GetNodes returns the existing nodes func (self *Network) getNode(id discover.NodeID) *Node {
func (net *Network) GetNodes() (nodes []*Node) { i, found := self.nodeMap[id]
net.lock.Lock()
defer net.lock.Unlock()
nodes = append(nodes, net.Nodes...)
return nodes
}
func (net *Network) getNode(id enode.ID) *Node {
i, found := net.nodeMap[id]
if !found { if !found {
return nil return nil
} }
return net.Nodes[i] return self.Nodes[i]
} }
func (net *Network) getNodeByName(name string) *Node { func (self *Network) getNodeByName(name string) *Node {
for _, node := range net.Nodes { for _, node := range self.Nodes {
if node.Config.Name == name { if node.Config.Name == name {
return node return node
} }
@ -404,32 +399,41 @@ func (net *Network) getNodeByName(name string) *Node {
return nil return nil
} }
// GetNodes returns the existing nodes
func (self *Network) GetNodes() (nodes []*Node) {
self.lock.Lock()
defer self.lock.Unlock()
nodes = append(nodes, self.Nodes...)
return nodes
}
// GetConn returns the connection which exists between "one" and "other" // GetConn returns the connection which exists between "one" and "other"
// regardless of which node initiated the connection // regardless of which node initiated the connection
func (net *Network) GetConn(oneID, otherID enode.ID) *Conn { func (self *Network) GetConn(oneID, otherID discover.NodeID) *Conn {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
return net.getConn(oneID, otherID) return self.getConn(oneID, otherID)
} }
// GetOrCreateConn is like GetConn but creates the connection if it doesn't // GetOrCreateConn is like GetConn but creates the connection if it doesn't
// already exist // already exist
func (net *Network) GetOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { func (self *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
return net.getOrCreateConn(oneID, otherID) return self.getOrCreateConn(oneID, otherID)
} }
func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { func (self *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) {
if conn := net.getConn(oneID, otherID); conn != nil { if conn := self.getConn(oneID, otherID); conn != nil {
return conn, nil return conn, nil
} }
one := net.getNode(oneID) one := self.getNode(oneID)
if one == nil { if one == nil {
return nil, fmt.Errorf("node %v does not exist", oneID) return nil, fmt.Errorf("node %v does not exist", oneID)
} }
other := net.getNode(otherID) other := self.getNode(otherID)
if other == nil { if other == nil {
return nil, fmt.Errorf("node %v does not exist", otherID) return nil, fmt.Errorf("node %v does not exist", otherID)
} }
@ -440,18 +444,18 @@ func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) {
other: other, other: other,
} }
label := ConnLabel(oneID, otherID) label := ConnLabel(oneID, otherID)
net.connMap[label] = len(net.Conns) self.connMap[label] = len(self.Conns)
net.Conns = append(net.Conns, conn) self.Conns = append(self.Conns, conn)
return conn, nil return conn, nil
} }
func (net *Network) getConn(oneID, otherID enode.ID) *Conn { func (self *Network) getConn(oneID, otherID discover.NodeID) *Conn {
label := ConnLabel(oneID, otherID) label := ConnLabel(oneID, otherID)
i, found := net.connMap[label] i, found := self.connMap[label]
if !found { if !found {
return nil return nil
} }
return net.Conns[i] return self.Conns[i]
} }
// InitConn(one, other) retrieves the connectiton model for the connection between // InitConn(one, other) retrieves the connectiton model for the connection between
@ -462,56 +466,53 @@ func (net *Network) getConn(oneID, otherID enode.ID) *Conn {
// it also checks whether there has been recent attempt to connect the peers // it also checks whether there has been recent attempt to connect the peers
// this is cheating as the simulation is used as an oracle and know about // this is cheating as the simulation is used as an oracle and know about
// remote peers attempt to connect to a node which will then not initiate the connection // remote peers attempt to connect to a node which will then not initiate the connection
func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) { func (self *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
if oneID == otherID { if oneID == otherID {
return nil, fmt.Errorf("refusing to connect to self %v", oneID) return nil, fmt.Errorf("refusing to connect to self %v", oneID)
} }
conn, err := net.getOrCreateConn(oneID, otherID) conn, err := self.getOrCreateConn(oneID, otherID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if time.Since(conn.initiated) < dialBanTimeout {
return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID)
}
if conn.Up { if conn.Up {
return nil, fmt.Errorf("%v and %v already connected", oneID, otherID) return nil, fmt.Errorf("%v and %v already connected", oneID, otherID)
} }
if time.Since(conn.initiated) < DialBanTimeout {
return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID)
}
err = conn.nodesUp() err = conn.nodesUp()
if err != nil { if err != nil {
log.Trace(fmt.Sprintf("nodes not up: %v", err))
return nil, fmt.Errorf("nodes not up: %v", err) return nil, fmt.Errorf("nodes not up: %v", err)
} }
log.Debug("InitConn - connection initiated")
conn.initiated = time.Now() conn.initiated = time.Now()
return conn, nil return conn, nil
} }
// Shutdown stops all nodes in the network and closes the quit channel // Shutdown stops all nodes in the network and closes the quit channel
func (net *Network) Shutdown() { func (self *Network) Shutdown() {
for _, node := range net.Nodes { for _, node := range self.Nodes {
log.Debug(fmt.Sprintf("stopping node %s", node.ID().TerminalString())) log.Debug(fmt.Sprintf("stopping node %s", node.ID().TerminalString()))
if err := node.Stop(); err != nil { if err := node.Stop(); err != nil {
log.Warn(fmt.Sprintf("error stopping node %s", node.ID().TerminalString()), "err", err) log.Warn(fmt.Sprintf("error stopping node %s", node.ID().TerminalString()), "err", err)
} }
} }
close(net.quitc) close(self.quitc)
} }
// Reset resets all network properties: //Reset resets all network properties:
// emtpies the nodes and the connection list //emtpies the nodes and the connection list
func (net *Network) Reset() { func (self *Network) Reset() {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
//re-initialize the maps //re-initialize the maps
net.connMap = make(map[string]int) self.connMap = make(map[string]int)
net.nodeMap = make(map[enode.ID]int) self.nodeMap = make(map[discover.NodeID]int)
net.Nodes = nil self.Nodes = nil
net.Conns = nil self.Conns = nil
} }
// Node is a wrapper around adapters.Node which is used to track the status // Node is a wrapper around adapters.Node which is used to track the status
@ -527,47 +528,47 @@ type Node struct {
} }
// ID returns the ID of the node // ID returns the ID of the node
func (n *Node) ID() enode.ID { func (self *Node) ID() discover.NodeID {
return n.Config.ID return self.Config.ID
} }
// String returns a log-friendly string // String returns a log-friendly string
func (n *Node) String() string { func (self *Node) String() string {
return fmt.Sprintf("Node %v", n.ID().TerminalString()) return fmt.Sprintf("Node %v", self.ID().TerminalString())
} }
// NodeInfo returns information about the node // NodeInfo returns information about the node
func (n *Node) NodeInfo() *p2p.NodeInfo { func (self *Node) NodeInfo() *p2p.NodeInfo {
// avoid a panic if the node is not started yet // avoid a panic if the node is not started yet
if n.Node == nil { if self.Node == nil {
return nil return nil
} }
info := n.Node.NodeInfo() info := self.Node.NodeInfo()
info.Name = n.Config.Name info.Name = self.Config.Name
return info return info
} }
// MarshalJSON implements the json.Marshaler interface so that the encoded // MarshalJSON implements the json.Marshaler interface so that the encoded
// JSON includes the NodeInfo // JSON includes the NodeInfo
func (n *Node) MarshalJSON() ([]byte, error) { func (self *Node) MarshalJSON() ([]byte, error) {
return json.Marshal(struct { return json.Marshal(struct {
Info *p2p.NodeInfo `json:"info,omitempty"` Info *p2p.NodeInfo `json:"info,omitempty"`
Config *adapters.NodeConfig `json:"config,omitempty"` Config *adapters.NodeConfig `json:"config,omitempty"`
Up bool `json:"up"` Up bool `json:"up"`
}{ }{
Info: n.NodeInfo(), Info: self.NodeInfo(),
Config: n.Config, Config: self.Config,
Up: n.Up, Up: self.Up,
}) })
} }
// Conn represents a connection between two nodes in the network // Conn represents a connection between two nodes in the network
type Conn struct { type Conn struct {
// One is the node which initiated the connection // One is the node which initiated the connection
One enode.ID `json:"one"` One discover.NodeID `json:"one"`
// Other is the node which the connection was made to // Other is the node which the connection was made to
Other enode.ID `json:"other"` Other discover.NodeID `json:"other"`
// Up tracks whether or not the connection is active // Up tracks whether or not the connection is active
Up bool `json:"up"` Up bool `json:"up"`
@ -579,40 +580,40 @@ type Conn struct {
} }
// nodesUp returns whether both nodes are currently up // nodesUp returns whether both nodes are currently up
func (c *Conn) nodesUp() error { func (self *Conn) nodesUp() error {
if !c.one.Up { if !self.one.Up {
return fmt.Errorf("one %v is not up", c.One) return fmt.Errorf("one %v is not up", self.One)
} }
if !c.other.Up { if !self.other.Up {
return fmt.Errorf("other %v is not up", c.Other) return fmt.Errorf("other %v is not up", self.Other)
} }
return nil return nil
} }
// String returns a log-friendly string // String returns a log-friendly string
func (c *Conn) String() string { func (self *Conn) String() string {
return fmt.Sprintf("Conn %v->%v", c.One.TerminalString(), c.Other.TerminalString()) return fmt.Sprintf("Conn %v->%v", self.One.TerminalString(), self.Other.TerminalString())
} }
// Msg represents a p2p message sent between two nodes in the network // Msg represents a p2p message sent between two nodes in the network
type Msg struct { type Msg struct {
One enode.ID `json:"one"` One discover.NodeID `json:"one"`
Other enode.ID `json:"other"` Other discover.NodeID `json:"other"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Code uint64 `json:"code"` Code uint64 `json:"code"`
Received bool `json:"received"` Received bool `json:"received"`
} }
// String returns a log-friendly string // String returns a log-friendly string
func (m *Msg) String() string { func (self *Msg) String() string {
return fmt.Sprintf("Msg(%d) %v->%v", m.Code, m.One.TerminalString(), m.Other.TerminalString()) return fmt.Sprintf("Msg(%d) %v->%v", self.Code, self.One.TerminalString(), self.Other.TerminalString())
} }
// ConnLabel generates a deterministic string which represents a connection // ConnLabel generates a deterministic string which represents a connection
// between two nodes, used to compare if two connections are between the same // between two nodes, used to compare if two connections are between the same
// nodes // nodes
func ConnLabel(source, target enode.ID) string { func ConnLabel(source, target discover.NodeID) string {
var first, second enode.ID var first, second discover.NodeID
if bytes.Compare(source.Bytes(), target.Bytes()) > 0 { if bytes.Compare(source.Bytes(), target.Bytes()) > 0 {
first = target first = target
second = source second = source
@ -639,14 +640,14 @@ type NodeSnapshot struct {
} }
// Snapshot creates a network snapshot // Snapshot creates a network snapshot
func (net *Network) Snapshot() (*Snapshot, error) { func (self *Network) Snapshot() (*Snapshot, error) {
net.lock.Lock() self.lock.Lock()
defer net.lock.Unlock() defer self.lock.Unlock()
snap := &Snapshot{ snap := &Snapshot{
Nodes: make([]NodeSnapshot, len(net.Nodes)), Nodes: make([]NodeSnapshot, len(self.Nodes)),
Conns: make([]Conn, len(net.Conns)), Conns: make([]Conn, len(self.Conns)),
} }
for i, node := range net.Nodes { for i, node := range self.Nodes {
snap.Nodes[i] = NodeSnapshot{Node: *node} snap.Nodes[i] = NodeSnapshot{Node: *node}
if !node.Up { if !node.Up {
continue continue
@ -657,33 +658,33 @@ func (net *Network) Snapshot() (*Snapshot, error) {
} }
snap.Nodes[i].Snapshots = snapshots snap.Nodes[i].Snapshots = snapshots
} }
for i, conn := range net.Conns { for i, conn := range self.Conns {
snap.Conns[i] = *conn snap.Conns[i] = *conn
} }
return snap, nil return snap, nil
} }
// Load loads a network snapshot // Load loads a network snapshot
func (net *Network) Load(snap *Snapshot) error { func (self *Network) Load(snap *Snapshot) error {
for _, n := range snap.Nodes { for _, n := range snap.Nodes {
if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { if _, err := self.NewNodeWithConfig(n.Node.Config); err != nil {
return err return err
} }
if !n.Node.Up { if !n.Node.Up {
continue continue
} }
if err := net.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil { if err := self.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil {
return err return err
} }
} }
for _, conn := range snap.Conns { for _, conn := range snap.Conns {
if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up { if !self.GetNode(conn.One).Up || !self.GetNode(conn.Other).Up {
//in this case, at least one of the nodes of a connection is not up, //in this case, at least one of the nodes of a connection is not up,
//so it would result in the snapshot `Load` to fail //so it would result in the snapshot `Load` to fail
continue continue
} }
if err := net.Connect(conn.One, conn.Other); err != nil { if err := self.Connect(conn.One, conn.Other); err != nil {
return err return err
} }
} }
@ -691,7 +692,7 @@ func (net *Network) Load(snap *Snapshot) error {
} }
// Subscribe reads control events from a channel and executes them // Subscribe reads control events from a channel and executes them
func (net *Network) Subscribe(events chan *Event) { func (self *Network) Subscribe(events chan *Event) {
for { for {
select { select {
case event, ok := <-events: case event, ok := <-events:
@ -699,23 +700,23 @@ func (net *Network) Subscribe(events chan *Event) {
return return
} }
if event.Control { if event.Control {
net.executeControlEvent(event) self.executeControlEvent(event)
} }
case <-net.quitc: case <-self.quitc:
return return
} }
} }
} }
func (net *Network) executeControlEvent(event *Event) { func (self *Network) executeControlEvent(event *Event) {
log.Trace("execute control event", "type", event.Type, "event", event) log.Trace("execute control event", "type", event.Type, "event", event)
switch event.Type { switch event.Type {
case EventTypeNode: case EventTypeNode:
if err := net.executeNodeEvent(event); err != nil { if err := self.executeNodeEvent(event); err != nil {
log.Error("error executing node event", "event", event, "err", err) log.Error("error executing node event", "event", event, "err", err)
} }
case EventTypeConn: case EventTypeConn:
if err := net.executeConnEvent(event); err != nil { if err := self.executeConnEvent(event); err != nil {
log.Error("error executing conn event", "event", event, "err", err) log.Error("error executing conn event", "event", event, "err", err)
} }
case EventTypeMsg: case EventTypeMsg:
@ -723,21 +724,21 @@ func (net *Network) executeControlEvent(event *Event) {
} }
} }
func (net *Network) executeNodeEvent(e *Event) error { func (self *Network) executeNodeEvent(e *Event) error {
if !e.Node.Up { if !e.Node.Up {
return net.Stop(e.Node.ID()) return self.Stop(e.Node.ID())
} }
if _, err := net.NewNodeWithConfig(e.Node.Config); err != nil { if _, err := self.NewNodeWithConfig(e.Node.Config); err != nil {
return err return err
} }
return net.Start(e.Node.ID()) return self.Start(e.Node.ID())
} }
func (net *Network) executeConnEvent(e *Event) error { func (self *Network) executeConnEvent(e *Event) error {
if e.Conn.Up { if e.Conn.Up {
return net.Connect(e.Conn.One, e.Conn.Other) return self.Connect(e.Conn.One, e.Conn.Other)
} else { } else {
return net.Disconnect(e.Conn.One, e.Conn.Other) return self.Disconnect(e.Conn.One, e.Conn.Other)
} }
} }

View file

@ -22,7 +22,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
) )
@ -39,10 +39,9 @@ func TestNetworkSimulation(t *testing.T) {
}) })
defer network.Shutdown() defer network.Shutdown()
nodeCount := 20 nodeCount := 20
ids := make([]enode.ID, nodeCount) ids := make([]discover.NodeID, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
conf := adapters.RandomNodeConfig() node, err := network.NewNode()
node, err := network.NewNodeWithConfig(conf)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
@ -64,7 +63,7 @@ func TestNetworkSimulation(t *testing.T) {
} }
return nil return nil
} }
check := func(ctx context.Context, id enode.ID) (bool, error) { check := func(ctx context.Context, id discover.NodeID) (bool, error) {
// check we haven't run out of time // check we haven't run out of time
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -102,7 +101,7 @@ func TestNetworkSimulation(t *testing.T) {
defer cancel() defer cancel()
// trigger a check every 100ms // trigger a check every 100ms
trigger := make(chan enode.ID) trigger := make(chan discover.NodeID)
go triggerChecks(ctx, ids, trigger, 100*time.Millisecond) go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
result := NewSimulation(network).Run(ctx, &Step{ result := NewSimulation(network).Run(ctx, &Step{
@ -140,7 +139,7 @@ func TestNetworkSimulation(t *testing.T) {
} }
} }
func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { func triggerChecks(ctx context.Context, ids []discover.NodeID, trigger chan discover.NodeID, interval time.Duration) {
tick := time.NewTicker(interval) tick := time.NewTicker(interval)
defer tick.Stop() defer tick.Stop()
for { for {

View file

@ -1,55 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package pipes
import (
"net"
)
// NetPipe wraps net.Pipe in a signature returning an error
func NetPipe() (net.Conn, net.Conn, error) {
p1, p2 := net.Pipe()
return p1, p2, nil
}
// TCPPipe creates an in process full duplex pipe based on a localhost TCP socket
func TCPPipe() (net.Conn, net.Conn, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
defer l.Close()
var aconn net.Conn
aerr := make(chan error, 1)
go func() {
var err error
aconn, err = l.Accept()
aerr <- err
}()
dconn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
<-aerr
return nil, nil, err
}
if err := <-aerr; err != nil {
dconn.Close()
return nil, nil, err
}
return aconn, dconn, nil
}

View file

@ -20,7 +20,7 @@ import (
"context" "context"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
// Simulation provides a framework for running actions in a simulated network // Simulation provides a framework for running actions in a simulated network
@ -55,7 +55,7 @@ func (s *Simulation) Run(ctx context.Context, step *Step) (result *StepResult) {
} }
// wait for all node expectations to either pass, error or timeout // wait for all node expectations to either pass, error or timeout
nodes := make(map[enode.ID]struct{}, len(step.Expect.Nodes)) nodes := make(map[discover.NodeID]struct{}, len(step.Expect.Nodes))
for _, id := range step.Expect.Nodes { for _, id := range step.Expect.Nodes {
nodes[id] = struct{}{} nodes[id] = struct{}{}
} }
@ -119,7 +119,7 @@ type Step struct {
// Trigger is a channel which receives node ids and triggers an // Trigger is a channel which receives node ids and triggers an
// expectation check for that node // expectation check for that node
Trigger chan enode.ID Trigger chan discover.NodeID
// Expect is the expectation to wait for when performing this step // Expect is the expectation to wait for when performing this step
Expect *Expectation Expect *Expectation
@ -127,15 +127,15 @@ type Step struct {
type Expectation struct { type Expectation struct {
// Nodes is a list of nodes to check // Nodes is a list of nodes to check
Nodes []enode.ID Nodes []discover.NodeID
// Check checks whether a given node meets the expectation // Check checks whether a given node meets the expectation
Check func(context.Context, enode.ID) (bool, error) Check func(context.Context, discover.NodeID) (bool, error)
} }
func newStepResult() *StepResult { func newStepResult() *StepResult {
return &StepResult{ return &StepResult{
Passes: make(map[enode.ID]time.Time), Passes: make(map[discover.NodeID]time.Time),
} }
} }
@ -150,7 +150,7 @@ type StepResult struct {
FinishedAt time.Time FinishedAt time.Time
// Passes are the timestamps of the successful node expectations // Passes are the timestamps of the successful node expectations
Passes map[enode.ID]time.Time Passes map[discover.NodeID]time.Time
// NetworkEvents are the network events which occurred during the step // NetworkEvents are the network events which occurred during the step
NetworkEvents []*Event NetworkEvents []*Event

View file

@ -21,22 +21,22 @@ import (
"sync" "sync"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
) )
type TestPeer interface { type TestPeer interface {
ID() enode.ID ID() discover.NodeID
Drop(error) Drop(error)
} }
// TestPeerPool is an example peerPool to demonstrate registration of peer connections // TestPeerPool is an example peerPool to demonstrate registration of peer connections
type TestPeerPool struct { type TestPeerPool struct {
lock sync.Mutex lock sync.Mutex
peers map[enode.ID]TestPeer peers map[discover.NodeID]TestPeer
} }
func NewTestPeerPool() *TestPeerPool { func NewTestPeerPool() *TestPeerPool {
return &TestPeerPool{peers: make(map[enode.ID]TestPeer)} return &TestPeerPool{peers: make(map[discover.NodeID]TestPeer)}
} }
func (self *TestPeerPool) Add(p TestPeer) { func (self *TestPeerPool) Add(p TestPeer) {
@ -53,15 +53,15 @@ func (self *TestPeerPool) Remove(p TestPeer) {
delete(self.peers, p.ID()) delete(self.peers, p.ID())
} }
func (p *TestPeerPool) Has(id enode.ID) bool { func (self *TestPeerPool) Has(id discover.NodeID) bool {
p.lock.Lock() self.lock.Lock()
defer p.lock.Unlock() defer self.lock.Unlock()
_, ok := p.peers[id] _, ok := self.peers[id]
return ok return ok
} }
func (p *TestPeerPool) Get(id enode.ID) TestPeer { func (self *TestPeerPool) Get(id discover.NodeID) TestPeer {
p.lock.Lock() self.lock.Lock()
defer p.lock.Unlock() defer self.lock.Unlock()
return p.peers[id] return self.peers[id]
} }

View file

@ -24,7 +24,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
) )
@ -35,7 +35,7 @@ var errTimedOut = errors.New("timed out")
// receive (expect) messages // receive (expect) messages
type ProtocolSession struct { type ProtocolSession struct {
Server *p2p.Server Server *p2p.Server
Nodes []*enode.Node IDs []discover.NodeID
adapter *adapters.SimAdapter adapter *adapters.SimAdapter
events chan *p2p.PeerEvent events chan *p2p.PeerEvent
} }
@ -56,32 +56,32 @@ type Exchange struct {
// Trigger is part of the exchange, incoming message for the pivot node // Trigger is part of the exchange, incoming message for the pivot node
// sent by a peer // sent by a peer
type Trigger struct { type Trigger struct {
Msg interface{} // type of message to be sent Msg interface{} // type of message to be sent
Code uint64 // code of message is given Code uint64 // code of message is given
Peer enode.ID // the peer to send the message to Peer discover.NodeID // the peer to send the message to
Timeout time.Duration // timeout duration for the sending Timeout time.Duration // timeout duration for the sending
} }
// Expect is part of an exchange, outgoing message from the pivot node // Expect is part of an exchange, outgoing message from the pivot node
// received by a peer // received by a peer
type Expect struct { type Expect struct {
Msg interface{} // type of message to expect Msg interface{} // type of message to expect
Code uint64 // code of message is now given Code uint64 // code of message is now given
Peer enode.ID // the peer that expects the message Peer discover.NodeID // the peer that expects the message
Timeout time.Duration // timeout duration for receiving Timeout time.Duration // timeout duration for receiving
} }
// Disconnect represents a disconnect event, used and checked by TestDisconnected // Disconnect represents a disconnect event, used and checked by TestDisconnected
type Disconnect struct { type Disconnect struct {
Peer enode.ID // discconnected peer Peer discover.NodeID // discconnected peer
Error error // disconnect reason Error error // disconnect reason
} }
// trigger sends messages from peers // trigger sends messages from peers
func (self *ProtocolSession) trigger(trig Trigger) error { func (self *ProtocolSession) trigger(trig Trigger) error {
simNode, ok := self.adapter.GetNode(trig.Peer) simNode, ok := self.adapter.GetNode(trig.Peer)
if !ok { if !ok {
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.Nodes)) return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.IDs))
} }
mockNode, ok := simNode.Services()[0].(*mockNode) mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok { if !ok {
@ -109,7 +109,7 @@ func (self *ProtocolSession) trigger(trig Trigger) error {
// expect checks an expectation of a message sent out by the pivot node // expect checks an expectation of a message sent out by the pivot node
func (self *ProtocolSession) expect(exps []Expect) error { func (self *ProtocolSession) expect(exps []Expect) error {
// construct a map of expectations for each node // construct a map of expectations for each node
peerExpects := make(map[enode.ID][]Expect) peerExpects := make(map[discover.NodeID][]Expect)
for _, exp := range exps { for _, exp := range exps {
if exp.Msg == nil { if exp.Msg == nil {
return errors.New("no message to expect") return errors.New("no message to expect")
@ -118,11 +118,11 @@ func (self *ProtocolSession) expect(exps []Expect) error {
} }
// construct a map of mockNodes for each node // construct a map of mockNodes for each node
mockNodes := make(map[enode.ID]*mockNode) mockNodes := make(map[discover.NodeID]*mockNode)
for nodeID := range peerExpects { for nodeID := range peerExpects {
simNode, ok := self.adapter.GetNode(nodeID) simNode, ok := self.adapter.GetNode(nodeID)
if !ok { if !ok {
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.Nodes)) return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs))
} }
mockNode, ok := simNode.Services()[0].(*mockNode) mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok { if !ok {
@ -251,7 +251,7 @@ func (self *ProtocolSession) testExchange(e Exchange) error {
// TestDisconnected tests the disconnections given as arguments // TestDisconnected tests the disconnections given as arguments
// the disconnect structs describe what disconnect error is expected on which peer // the disconnect structs describe what disconnect error is expected on which peer
func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
expects := make(map[enode.ID]error) expects := make(map[discover.NodeID]error)
for _, disconnect := range disconnects { for _, disconnect := range disconnects {
expects[disconnect.Peer] = disconnect.Error expects[disconnect.Peer] = disconnect.Error
} }

View file

@ -34,7 +34,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node" "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/enode" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations" "github.com/XinFinOrg/XDPoSChain/p2p/simulations"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -51,7 +51,7 @@ type ProtocolTester struct {
// NewProtocolTester constructs a new ProtocolTester // NewProtocolTester constructs a new ProtocolTester
// it takes as argument the pivot node id, the number of dummy peers and the // it takes as argument the pivot node id, the number of dummy peers and the
// protocol run function called on a peer connection by the p2p server // protocol run function called on a peer connection by the p2p server
func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
services := adapters.Services{ services := adapters.Services{
"test": func(ctx *adapters.ServiceContext) (node.Service, error) { "test": func(ctx *adapters.ServiceContext) (node.Service, error) {
return &testNode{run}, nil return &testNode{run}, nil
@ -75,17 +75,17 @@ func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p
node := net.GetNode(id).Node.(*adapters.SimNode) node := net.GetNode(id).Node.(*adapters.SimNode)
peers := make([]*adapters.NodeConfig, n) peers := make([]*adapters.NodeConfig, n)
nodes := make([]*enode.Node, n) peerIDs := make([]discover.NodeID, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
peers[i] = adapters.RandomNodeConfig() peers[i] = adapters.RandomNodeConfig()
peers[i].Services = []string{"mock"} peers[i].Services = []string{"mock"}
nodes[i] = peers[i].Node() peerIDs[i] = peers[i].ID
} }
events := make(chan *p2p.PeerEvent, 1000) events := make(chan *p2p.PeerEvent, 1000)
node.SubscribeEvents(events) node.SubscribeEvents(events)
ps := &ProtocolSession{ ps := &ProtocolSession{
Server: node.Server(), Server: node.Server(),
Nodes: nodes, IDs: peerIDs,
adapter: adapter, adapter: adapter,
events: events, events: events,
} }
@ -107,7 +107,7 @@ func (self *ProtocolTester) Stop() error {
// Connect brings up the remote peer node and connects it using the // Connect brings up the remote peer node and connects it using the
// p2p/simulations network connection with the in memory network adapter // p2p/simulations network connection with the in memory network adapter
func (self *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) { func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
for _, peer := range peers { for _, peer := range peers {
log.Trace(fmt.Sprintf("start node %v", peer.ID)) log.Trace(fmt.Sprintf("start node %v", peer.ID))
if _, err := self.network.NewNodeWithConfig(peer); err != nil { if _, err := self.network.NewNodeWithConfig(peer); err != nil {

View file

@ -1,12 +0,0 @@
TESTING KEY-----
MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9
SjY1bIw4iAJm2gsvvZhIrCHS3l6afab4pZB
l2+XsDlrKBxKKtDrGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTtqJQIDAQAB
AoGAGRzwwir7XvBOAy5tuV6ef6anZzus1s1Y1Clb6HbnWWF/wbZGOpet
3m4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKZTXtdZrh+k7hx0nTP8Jcb
uqFk541awmMogY/EfbWd6IOkp+4xqjlFBEDytgbIECQQDvH/6nk+hgN4H
qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz84SHEg1Ak/7KCxmD/sfgS5TeuNi8DoUBEmiSJwm7FX
ftxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su43sjXNueLKH8+ph2UfQuU9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCzjA0CQQDU
y2pGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtI痂
qUn3Xh9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JMhNRcVFMO8dDaFo
f9Oeos0UotgiDktdQHxdNEwLjQlJBz+OtwwA=---E RATTIEY-

View file

@ -1,15 +0,0 @@
¸&^£áo‡È—-----BEGIN RSA TESTING KEY-----
MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9
SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB
l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB
AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet
3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb
uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H
qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp
jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY
fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U
fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU
y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX
qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo
f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
-----END RSA TESTING KEY-----Q_

View file

@ -1 +0,0 @@
π½apοΏοοοΏ½οΏ½οΏοΏΏ½½½ΏΏ½½οΏ½οΏ½Ώ½οΏοΏ½οΏοΣΜV½Ώ½οοοΏοΏ½#οΏοΏ½&οΏ½οΏ½

View file

@ -1,11 +0,0 @@
TAKBgDuLnQA3gey3VBznB39JUtxjeE6myuDkM/uGlfjb
S1w4iA5sBzzh8uxEbi4nW91IJm2gsvvZhICHS3l6ab4pZB
l2DulrKBxKKtD1rGxlG4LncabFn9vLZad2bSysqz/qTAUSTvqJQIDAQAB
AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet
3Z4vMXc7jpTLryzTQIvVdfQbRc6+MUVeLKZatTXtdZrhu+Jk7hx0nTPy8Jcb
uJqFk54MogxEcfbWd6IOkp+4xqFLBEDtgbIECnk+hgN4H
qzzxxr397vWrjrIgbJpQvBv8QeeuNi8DoUBEmiSJwa7FXY
FUtxuvL7XvjwjN5B30pEbc6Iuyt7y4MQJBAIt21su4b3sjphy2tuUE9xblTu14qgHZ6+AiZovGKU--FfYAqVXVlxtIX
qyU3X9ps8ZfjLZ45l6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo
f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
-----END RSA T

View file

@ -1 +0,0 @@
0000000000000000000000000000000000000000000000000000000000000000000000000

Some files were not shown because too many files have changed in this diff Show more